Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions codev/projects/1357-codev-sdk-controller-subpath-d/status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
id: '1357'
title: codev-sdk-controller-subpath-d
protocol: air
phase: pr
plan_phases: []
current_plan_phase: null
gates:
pr:
status: approved
requested_at: '2026-08-05T22:37:24.073Z'
approved_at: '2026-08-05T22:39:37.067Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-08-05T22:31:40.320Z'
updated_at: '2026-08-05T22:39:37.068Z'
pr_ready_for_human: false
47 changes: 47 additions & 0 deletions codev/state/air-1357_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# air-1357 — codev-sdk controller subpath: overview wire type re-exports

## 2026-08-06 — Implement phase

AIR strict mode, issue #1357. Two-part scope per architect instruction:

1. **Type-only re-exports** of `OverviewData`, `OverviewBuilder`, `OverviewPR`,
`OverviewBacklogItem` from `@cluesmith/codev-types` on the sdk's
`./controller` subpath (the streamdeck migration's import-boundary
criterion in #1347 depends on this). Also added `OverviewData` to
`./tower-client` since `getOverview` returns it. Both use the
whole-statement `export type { ... } from` form — the mixed
`export { type X } from` form still emits a runtime re-export statement,
so the boundary test pins the erased form specifically.

2. **Packaging fix**: `@cluesmith/codev-types` moved from devDependencies to
dependencies. Published `.d.ts` files reference it (`import type ... from
'@cluesmith/codev-types'` in tower-client.d.ts), so a fresh npm consumer
running tsc could not resolve it as a devDep. codev-types is published
(3.2.4 on npm) and types-only, so the zero-RUNTIME-deps posture holds.
The import-boundary test's `dependencies` assertion restated: exactly
`['@cluesmith/codev-types']` allowed, with the intent documented (zero
runtime deps; contract-types dep permitted for .d.ts resolution).

Boundary test extensions:
- New UNIVERSAL rule: value re-exports of codev-types forbidden
(`export type` form required), mirroring the existing `import type` rule.
- New pin test: controller.ts must re-export the four overview types via
`export type { ... } from '@cluesmith/codev-types'`.

Lockfile updated (dep group move). Building + testing next.

## PR phase

- Implement checks green (build 8.4s, tests 28.1s). Emit inspection confirmed
the re-exports are erased from dist JS and present in the .d.ts.
- PR #1358 created with review in the body (AIR: no spec/plan/review files).
- Running porch PR-phase checks (pr_exists + e2e_tests), then notifying the
architect and stopping at the pr gate.

## Gate approved, merge held

- PR-phase checks green; pr gate approved by the architect after review.
- Merge NOT yet authorized: workspace policy requires Amr's explicit per-PR
word. Holding until the architect relays it.
- Architect confirmed the consult-skip was within AIR's builder discretion;
the protocol-doc contradiction that made it ambiguous is tracked as #1359.
4 changes: 3 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@
"test": "vitest run",
"prepublishOnly": "pnpm build"
},
"dependencies": {
"@cluesmith/codev-types": "workspace:*"
},
"devDependencies": {
"@cluesmith/codev-types": "workspace:*",
"@types/node": "22.x",
"typescript": "catalog:",
"vitest": "^4.0.15"
Expand Down
38 changes: 34 additions & 4 deletions packages/sdk/src/__tests__/import-boundary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import { fileURLToPath } from 'node:url';
* Node, and React Native (Metro). Shipped source therefore has zero Node
* builtins, zero vscode imports, zero direct fetch calls (transport is an
* injected adapter), zero DOM-global usage, and zero runtime dependencies.
* `@cluesmith/codev-types` is type-only (erased at build). `@cluesmith/codev-core`
* is forbidden outright: core and sdk never import each other.
* `@cluesmith/codev-types` is type-only (erased at build): it may appear in
* `dependencies` so published `.d.ts` files resolve for npm consumers (issue
* #1357), but no runtime code from it may ever load — every import and
* re-export of it must use the `import type` / `export type` form. Nothing
* else belongs in `dependencies`. `@cluesmith/codev-core` is forbidden
* outright: core and sdk never import each other.
*
* The single exception is `src/node/`, the explicitly Node-only adapter subpath
* (`@cluesmith/codev-sdk/node`). It may use Node builtins; nothing outside it
Expand Down Expand Up @@ -56,6 +60,10 @@ const UNIVERSAL: Array<{ label: string; pattern: RegExp }> = [
{ label: 'fetch()', pattern: /\bfetch\s*\(/ },
{ label: '@cluesmith/codev-core', pattern: /['"]@cluesmith\/codev-core/ },
{ label: 'runtime import of @cluesmith/codev-types (must be `import type`)', pattern: /^import\s+(?!type\b)[^;]*['"]@cluesmith\/codev-types/m },
// `export { type X } from` is NOT equivalent: it emits a runtime re-export
// statement. Only the whole-statement `export type { ... } from` form is
// fully erased.
{ label: 'runtime re-export of @cluesmith/codev-types (must be `export type`)', pattern: /^export\s+(?!type\b)[^;]*['"]@cluesmith\/codev-types/m },
];

/** Additional rules for the environment-agnostic graph (everything outside src/node/). */
Expand Down Expand Up @@ -90,10 +98,32 @@ describe('import boundary', () => {
expect(violations).toEqual([]);
});

it('declares zero runtime dependencies', () => {
/**
* Intent: zero RUNTIME dependencies. `@cluesmith/codev-types` is the one
* allowed entry — it is a regular dependency only so published `.d.ts`
* files typecheck for npm consumers (issue #1357); the source rules above
* guarantee it is erased at build and never loaded at runtime.
*/
it('declares no dependencies beyond the type-only contract package', () => {
const pkg = JSON.parse(readFileSync(join(srcRoot, '..', 'package.json'), 'utf8')) as {
dependencies?: Record<string, string>;
};
expect(Object.keys(pkg.dependencies ?? {})).toEqual([]);
expect(Object.keys(pkg.dependencies ?? {})).toEqual(['@cluesmith/codev-types']);
});

/**
* Issue #1357: the controller subpath ships the overview wire types its
* consumers read (`getOverview` returns `OverviewData`), so integrations
* need no direct `@cluesmith/codev-types` dependency. Pin both presence
* and the erased `export type` form.
*/
it('controller subpath re-exports the overview wire types type-only', () => {
const text = readFileSync(join(srcRoot, 'controller.ts'), 'utf8');
const match = text.match(/^export type \{([^}]*)\} from ['"]@cluesmith\/codev-types['"]/m);
expect(match, 'controller.ts must `export type { ... } from "@cluesmith/codev-types"`').not.toBeNull();
const names = match![1].split(',').map((name) => name.trim()).filter(Boolean);
for (const name of ['OverviewData', 'OverviewBuilder', 'OverviewPR', 'OverviewBacklogItem']) {
expect(names).toContain(name);
}
});
});
14 changes: 14 additions & 0 deletions packages/sdk/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,17 @@ export {
} from './tower-client.js';
export { parseSseText, type SseEnvelope } from './sse.js';
export { DEFAULT_TOWER_PORT } from './constants.js';

/*
* The overview wire types a controller reads (issue #1357): `getOverview`
* returns `OverviewData`, so the contract ships with the client — one import,
* no direct `@cluesmith/codev-types` dependency for integrations. Must stay
* the `export type` form (erased at build) so the sdk keeps zero runtime
* dependencies; the import-boundary test pins this.
*/
export type {
OverviewData,
OverviewBuilder,
OverviewPR,
OverviewBacklogItem,
} from '@cluesmith/codev-types';
7 changes: 7 additions & 0 deletions packages/sdk/src/tower-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ import type { DashboardState, OverviewData, IssueView, IssueSearchResponse, Reso
import { DEFAULT_TOWER_PORT } from './constants.js';
import { parseSseText, type SseEnvelope } from './sse.js';

/*
* `getOverview` returns `OverviewData`, so the subpath carries the contract
* with the client (issue #1357). `export type` only — erased at build, keeping
* the zero-runtime-deps posture; the import-boundary test pins the form.
*/
export type { OverviewData } from '@cluesmith/codev-types';

const REQUEST_TIMEOUT_MS = 10000;

/**
Expand Down
3 changes: 2 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading