ts-procedures 8.3.0 → 8.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +26 -8
- package/agent_config/claude-code/skills/ts-procedures/templates/client.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/hono.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/procedure.md +3 -3
- package/agent_config/claude-code/skills/ts-procedures/templates/stream-procedure.md +3 -3
- package/build/client/call.js +1 -1
- package/build/client/call.js.map +1 -1
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +23 -1
- package/build/client/index.js.map +1 -1
- package/build/client/index.test.js +87 -0
- package/build/client/index.test.js.map +1 -1
- package/build/client/resolve-options.d.ts +5 -4
- package/build/client/resolve-options.js +18 -7
- package/build/client/resolve-options.js.map +1 -1
- package/build/client/resolve-options.test.js +53 -24
- package/build/client/resolve-options.test.js.map +1 -1
- package/build/client/stream.js +1 -1
- package/build/client/stream.js.map +1 -1
- package/build/client/types.d.ts +31 -3
- package/build/codegen/__fixtures__/make-envelope.d.ts +41 -0
- package/build/codegen/__fixtures__/make-envelope.js +38 -0
- package/build/codegen/__fixtures__/make-envelope.js.map +1 -0
- package/build/codegen/bin/cli.d.ts +15 -0
- package/build/codegen/bin/cli.js +46 -21
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +54 -1
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/bin/flag-specs.d.ts +10 -0
- package/build/codegen/bin/flag-specs.js +62 -0
- package/build/codegen/bin/flag-specs.js.map +1 -0
- package/build/codegen/bin/flag-specs.test.d.ts +1 -0
- package/build/codegen/bin/flag-specs.test.js +35 -0
- package/build/codegen/bin/flag-specs.test.js.map +1 -0
- package/build/codegen/collect-models.d.ts +48 -0
- package/build/codegen/collect-models.js +84 -0
- package/build/codegen/collect-models.js.map +1 -0
- package/build/codegen/collect-models.test.d.ts +1 -0
- package/build/codegen/collect-models.test.js +59 -0
- package/build/codegen/collect-models.test.js.map +1 -0
- package/build/codegen/emit-client-runtime.js +1 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/build/codegen/emit-models.d.ts +26 -0
- package/build/codegen/emit-models.js +53 -0
- package/build/codegen/emit-models.js.map +1 -0
- package/build/codegen/emit-models.test.d.ts +1 -0
- package/build/codegen/emit-models.test.js +42 -0
- package/build/codegen/emit-models.test.js.map +1 -0
- package/build/codegen/emit-scope.d.ts +10 -0
- package/build/codegen/emit-scope.js +119 -34
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-types.d.ts +26 -1
- package/build/codegen/emit-types.js +27 -5
- package/build/codegen/emit-types.js.map +1 -1
- package/build/codegen/index.d.ts +15 -0
- package/build/codegen/index.js +5 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/model-refs.d.ts +27 -0
- package/build/codegen/model-refs.js +49 -0
- package/build/codegen/model-refs.js.map +1 -0
- package/build/codegen/model-refs.test.d.ts +1 -0
- package/build/codegen/model-refs.test.js +33 -0
- package/build/codegen/model-refs.test.js.map +1 -0
- package/build/codegen/pipeline.d.ts +7 -0
- package/build/codegen/pipeline.js +6 -1
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/schema-walk.d.ts +13 -0
- package/build/codegen/schema-walk.js +26 -0
- package/build/codegen/schema-walk.js.map +1 -0
- package/build/codegen/schema-walk.test.d.ts +1 -0
- package/build/codegen/schema-walk.test.js +35 -0
- package/build/codegen/schema-walk.test.js.map +1 -0
- package/build/codegen/targets/_shared/target-run.d.ts +15 -0
- package/build/codegen/targets/ts/run.js +37 -1
- package/build/codegen/targets/ts/run.js.map +1 -1
- package/build/codegen/targets/ts/shared-models.test.d.ts +1 -0
- package/build/codegen/targets/ts/shared-models.test.js +354 -0
- package/build/codegen/targets/ts/shared-models.test.js.map +1 -0
- package/build/doc-envelope.d.ts +13 -0
- package/build/doc-envelope.js +23 -0
- package/build/doc-envelope.js.map +1 -0
- package/build/doc-envelope.test.d.ts +1 -0
- package/build/doc-envelope.test.js +31 -0
- package/build/doc-envelope.test.js.map +1 -0
- package/build/exports.d.ts +2 -0
- package/build/exports.js +1 -0
- package/build/exports.js.map +1 -1
- package/docs/client-and-codegen.md +163 -0
- package/docs/handoffs/ajsc-named-type-collision.md +134 -0
- package/docs/handoffs/ajsc-named-type-support.md +181 -0
- package/docs/handoffs/shared-models-auto-resolve-response.md +181 -0
- package/docs/superpowers/plans/2026-06-05-dx-feedback-round.md +1292 -0
- package/docs/superpowers/plans/2026-06-06-shared-models-convention-and-diagnostics.md +659 -0
- package/docs/superpowers/specs/2026-06-05-dx-feedback-round-design.md +285 -0
- package/package.json +2 -2
- package/src/client/call.ts +1 -1
- package/src/client/index.test.ts +98 -0
- package/src/client/index.ts +32 -1
- package/src/client/resolve-options.test.ts +73 -26
- package/src/client/resolve-options.ts +23 -9
- package/src/client/stream.ts +1 -1
- package/src/client/types.ts +34 -3
- package/src/codegen/__fixtures__/make-envelope.ts +89 -0
- package/src/codegen/bin/cli.test.ts +65 -1
- package/src/codegen/bin/cli.ts +51 -22
- package/src/codegen/bin/flag-specs.test.ts +38 -0
- package/src/codegen/bin/flag-specs.ts +71 -0
- package/src/codegen/collect-models.test.ts +68 -0
- package/src/codegen/collect-models.ts +125 -0
- package/src/codegen/emit-client-runtime.ts +1 -0
- package/src/codegen/emit-models.test.ts +48 -0
- package/src/codegen/emit-models.ts +63 -0
- package/src/codegen/emit-scope.ts +145 -33
- package/src/codegen/emit-types.ts +48 -7
- package/src/codegen/index.ts +20 -0
- package/src/codegen/model-refs.test.ts +37 -0
- package/src/codegen/model-refs.ts +57 -0
- package/src/codegen/pipeline.ts +13 -1
- package/src/codegen/schema-walk.test.ts +37 -0
- package/src/codegen/schema-walk.ts +23 -0
- package/src/codegen/targets/_shared/target-run.ts +15 -0
- package/src/codegen/targets/ts/run.ts +50 -0
- package/src/codegen/targets/ts/shared-models.test.ts +391 -0
- package/src/doc-envelope.test.ts +35 -0
- package/src/doc-envelope.ts +30 -0
- package/src/exports.ts +2 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# Shared-Models Convention + Diagnostics Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Make the v8.4.0 `sharedTypesImport` single-source-of-truth path self-maintaining and footgun-free — collapse the hand-maintained `$id → {module,name}` map to a one-value `sharedModelsModule` convention, surface the silent generated-twin fallback via an always-on summary plus an opt-in `--strict-shared-models` hard-fail, and give all three a CLI flag + config + programmatic surface.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Keep the codegen **pure-envelope** — no consumer source-tree scanning. The convention and diagnostics live entirely in `collect-models.ts` (`resolveModelImports`) and `targets/ts/run.ts`, fed by two new options threaded through the existing `GenerateClientOptions → PipelineOptions → TargetRunInput` chain and the CLI. Precedence in `resolveModelImports`: explicit `sharedTypesImport[$id]` → `sharedModelsModule` convention → generate locally. The existing per-`$id` map stays as the multi-consumer / rename escape hatch (unchanged).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript (ESM, `.js` import specifiers), Vitest, ajsc (dynamic import). Branch: `dx-feedback` (already checked out).
|
|
10
|
+
|
|
11
|
+
**Explicitly out of scope (decided with the user):**
|
|
12
|
+
- `--shared-types-from <dir|glob>` source scanning — declined; breaks the envelope-decoupling invariant. Reasoning written up for downstream in Task 7.
|
|
13
|
+
- `x-shared-import` schema keyword — deferred (adds a third overlapping mechanism; the convention + existing map cover the spectrum). Not built this round.
|
|
14
|
+
- ts-channels parity / cross-package coordination — not this project's concern.
|
|
15
|
+
|
|
16
|
+
**Conventions to follow:**
|
|
17
|
+
- Run a single test file: `npx vitest run <path>`. Full suite: `npm run test`. Lint: `npm run lint`. Build: `npm run build`.
|
|
18
|
+
- ESM imports use `.js` specifiers even for `.ts` sources.
|
|
19
|
+
- Commit after each green task.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## File Structure
|
|
24
|
+
|
|
25
|
+
| File | Responsibility | Tasks |
|
|
26
|
+
|------|----------------|-------|
|
|
27
|
+
| `src/codegen/collect-models.ts` (modify) | `resolveModelImports` gains the `sharedModelsModule` convention fallback | 1 |
|
|
28
|
+
| `src/codegen/collect-models.test.ts` (modify) | Unit coverage for the convention precedence | 1 |
|
|
29
|
+
| `src/codegen/targets/_shared/target-run.ts` (modify) | `TargetRunInput` carries `sharedModelsModule` + `strictSharedModels` | 2, 3 |
|
|
30
|
+
| `src/codegen/pipeline.ts` (modify) | `PipelineOptions` + `base` thread both options | 2, 3 |
|
|
31
|
+
| `src/codegen/index.ts` (modify) | `GenerateClientOptions` + `generateClient` pass-through | 2, 3 |
|
|
32
|
+
| `src/codegen/targets/ts/run.ts` (modify) | Pass convention to `resolveModelImports`; emit summary log; strict hard-fail | 2, 3 |
|
|
33
|
+
| `src/codegen/targets/ts/shared-models.test.ts` (modify) | End-to-end coverage for convention + diagnostics | 2, 3 |
|
|
34
|
+
| `src/codegen/bin/flag-specs.ts` (modify) | `--shared-models-module` + `--strict-shared-models` spec entries | 4 |
|
|
35
|
+
| `src/codegen/bin/flag-specs.test.ts` (modify) | Assert the new flags are catalogued | 4 |
|
|
36
|
+
| `src/codegen/bin/cli.ts` (modify) | Config keys, `parseArgs` branches, `ParsedArgs`, watch + main pass-through | 5 |
|
|
37
|
+
| `src/codegen/bin/cli.test.ts` (modify) | CLI parse coverage | 5 |
|
|
38
|
+
| `CLAUDE.md` (modify) | Document the convention + diagnostics in the codegen section | 6 |
|
|
39
|
+
| `docs/client-and-codegen.md` (modify) | Consumer-facing guidance for the convention | 6 |
|
|
40
|
+
| `docs/handoffs/shared-models-auto-resolve-response.md` (new) | Downstream response: what shipped + why the scanner was declined | 7 |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## #A — `sharedModelsModule` convention
|
|
45
|
+
|
|
46
|
+
### Task 1: `resolveModelImports` convention fallback
|
|
47
|
+
|
|
48
|
+
**Files:**
|
|
49
|
+
- Modify: `src/codegen/collect-models.ts:100-108`
|
|
50
|
+
- Test: `src/codegen/collect-models.test.ts`
|
|
51
|
+
|
|
52
|
+
- [ ] **Step 1: Write the failing test**
|
|
53
|
+
|
|
54
|
+
Append to `src/codegen/collect-models.test.ts`:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
it('resolveModelImports falls back to sharedModelsModule when no map entry matches', () => {
|
|
58
|
+
const models = collectModels([
|
|
59
|
+
{ jsonSchema: { res: { body: { $id: 'urn:msg', title: 'Message', type: 'object', properties: {} } } } } as never,
|
|
60
|
+
])
|
|
61
|
+
const resolved = resolveModelImports(models, {}, '@app/schemas')
|
|
62
|
+
expect(resolved[0]?.import).toEqual({ module: '@app/schemas', name: 'Message' })
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('resolveModelImports: explicit map entry wins over the convention', () => {
|
|
66
|
+
const models = collectModels([
|
|
67
|
+
{ jsonSchema: { res: { body: { $id: 'urn:msg', title: 'Message', type: 'object', properties: {} } } } } as never,
|
|
68
|
+
])
|
|
69
|
+
const resolved = resolveModelImports(
|
|
70
|
+
models,
|
|
71
|
+
{ 'urn:msg': { module: '@override/pkg', name: 'Msg' } },
|
|
72
|
+
'@app/schemas',
|
|
73
|
+
)
|
|
74
|
+
expect(resolved[0]?.import).toEqual({ module: '@override/pkg', name: 'Msg' })
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('resolveModelImports: empty-string convention is treated as unset (generated locally)', () => {
|
|
78
|
+
const models = collectModels([
|
|
79
|
+
{ jsonSchema: { res: { body: { $id: 'urn:msg', title: 'Message', type: 'object', properties: {} } } } } as never,
|
|
80
|
+
])
|
|
81
|
+
expect(resolveModelImports(models, {}, '')[0]?.import).toBeUndefined()
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
86
|
+
|
|
87
|
+
Run: `npx vitest run src/codegen/collect-models.test.ts`
|
|
88
|
+
Expected: FAIL — `resolveModelImports` ignores the third argument; the first two new tests' `import` is `undefined`.
|
|
89
|
+
|
|
90
|
+
- [ ] **Step 3: Implement the convention fallback**
|
|
91
|
+
|
|
92
|
+
Replace `resolveModelImports` in `src/codegen/collect-models.ts` (lines 100-108):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
/**
|
|
96
|
+
* Tags each collected model with its external import. Precedence:
|
|
97
|
+
* 1. an explicit `sharedTypesImport[$id]` entry (per-type override / rename),
|
|
98
|
+
* 2. otherwise, when `sharedModelsModule` is set, the convention
|
|
99
|
+
* `{ module: sharedModelsModule, name: model.name }` — every shared model
|
|
100
|
+
* re-exports from one module under its derived name (`$id`/`title` === export),
|
|
101
|
+
* 3. otherwise `import` stays undefined and the model is generated locally.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveModelImports(
|
|
104
|
+
models: CollectedModel[],
|
|
105
|
+
map: SharedTypesImportMap = {},
|
|
106
|
+
sharedModelsModule?: string,
|
|
107
|
+
): ResolvedModel[] {
|
|
108
|
+
return models.map((model) => {
|
|
109
|
+
const mapped = map[model.id]
|
|
110
|
+
if (mapped) return { ...model, import: mapped }
|
|
111
|
+
if (sharedModelsModule != null && sharedModelsModule !== '') {
|
|
112
|
+
return { ...model, import: { module: sharedModelsModule, name: model.name } }
|
|
113
|
+
}
|
|
114
|
+
return { ...model }
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
120
|
+
|
|
121
|
+
Run: `npx vitest run src/codegen/collect-models.test.ts`
|
|
122
|
+
Expected: PASS (all existing + 3 new tests).
|
|
123
|
+
|
|
124
|
+
- [ ] **Step 5: Commit**
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git add src/codegen/collect-models.ts src/codegen/collect-models.test.ts
|
|
128
|
+
git commit -m "feat(codegen): sharedModelsModule convention fallback in resolveModelImports"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Task 2: Thread `sharedModelsModule` end-to-end and consume it in the TS run
|
|
134
|
+
|
|
135
|
+
**Files:**
|
|
136
|
+
- Modify: `src/codegen/targets/_shared/target-run.ts:14-33`
|
|
137
|
+
- Modify: `src/codegen/pipeline.ts:14-36,61-75`
|
|
138
|
+
- Modify: `src/codegen/index.ts:8-31,33-56`
|
|
139
|
+
- Modify: `src/codegen/targets/ts/run.ts:22-64`
|
|
140
|
+
- Test: `src/codegen/targets/ts/shared-models.test.ts`
|
|
141
|
+
|
|
142
|
+
- [ ] **Step 1: Write the failing test**
|
|
143
|
+
|
|
144
|
+
Append inside the existing `describe('shared models ...')` block in `src/codegen/targets/ts/shared-models.test.ts`:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
it('sharedModelsModule re-exports every $id model from the convention module', async () => {
|
|
148
|
+
const files = await runPipeline({
|
|
149
|
+
envelope: modelEnvelope(),
|
|
150
|
+
outDir: 'out',
|
|
151
|
+
dryRun: true,
|
|
152
|
+
shareModels: true,
|
|
153
|
+
namespaceTypes: true,
|
|
154
|
+
selfContained: false,
|
|
155
|
+
sharedModelsModule: '@app/schemas',
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const modelsFile = findFile(files, '_models.ts')!
|
|
159
|
+
// Re-exported, not generated.
|
|
160
|
+
expect(modelsFile.code).toContain("export { Message } from '@app/schemas'")
|
|
161
|
+
expect(countMatches(modelsFile.code, /export type Message =/g)).toBe(0)
|
|
162
|
+
|
|
163
|
+
// Scopes still import the shared name from ./_models.
|
|
164
|
+
expect(findFile(files, 'messages.ts')!.code).toContain("from './_models'")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('explicit sharedTypesImport overrides the sharedModelsModule convention per $id', async () => {
|
|
168
|
+
const files = await runPipeline({
|
|
169
|
+
envelope: modelEnvelope(),
|
|
170
|
+
outDir: 'out',
|
|
171
|
+
dryRun: true,
|
|
172
|
+
shareModels: true,
|
|
173
|
+
namespaceTypes: true,
|
|
174
|
+
selfContained: false,
|
|
175
|
+
sharedModelsModule: '@app/schemas',
|
|
176
|
+
sharedTypesImport: { 'urn:msg': { module: '@override/pkg', name: 'Message' } },
|
|
177
|
+
})
|
|
178
|
+
expect(findFile(files, '_models.ts')!.code).toContain("export { Message } from '@override/pkg'")
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
183
|
+
|
|
184
|
+
Run: `npx vitest run src/codegen/targets/ts/shared-models.test.ts`
|
|
185
|
+
Expected: FAIL — `runPipeline` does not accept/forward `sharedModelsModule`; `_models.ts` still generates `export type Message = …` rather than re-exporting.
|
|
186
|
+
|
|
187
|
+
- [ ] **Step 3a: Add the field to `TargetRunInput`**
|
|
188
|
+
|
|
189
|
+
In `src/codegen/targets/_shared/target-run.ts`, add to the `TargetRunInput` interface (after the `sharedTypesImport` field, line 32):
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
/** Single module every $id-bearing model re-exports from (convention; map entries override). */
|
|
193
|
+
sharedModelsModule?: string
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- [ ] **Step 3b: Add to `PipelineOptions` and forward in `base`**
|
|
197
|
+
|
|
198
|
+
In `src/codegen/pipeline.ts`, add to the `PipelineOptions` interface (after line 25, `sharedTypesImport?`):
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
sharedModelsModule?: string
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
And add to the `base` object (after line 74, `sharedTypesImport: options.sharedTypesImport,`):
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
sharedModelsModule: options.sharedModelsModule,
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- [ ] **Step 3c: Add to `GenerateClientOptions` and forward in `generateClient`**
|
|
211
|
+
|
|
212
|
+
In `src/codegen/index.ts`, add to `GenerateClientOptions` (after line 20, `sharedTypesImport?`):
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
/** Single module every $id-bearing model re-exports from (convention; `sharedTypesImport` entries override). */
|
|
216
|
+
sharedModelsModule?: string
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
And in the `runPipeline({ ... })` call (after line 46, `sharedTypesImport: options.sharedTypesImport,`):
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
sharedModelsModule: options.sharedModelsModule,
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- [ ] **Step 3d: Consume in the TS run**
|
|
226
|
+
|
|
227
|
+
In `src/codegen/targets/ts/run.ts`, add `sharedModelsModule` to the destructure (after line 35, `sharedTypesImport,`):
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
sharedModelsModule,
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
And pass it to `resolveModelImports` (replace lines 62-64):
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const models = shareModels
|
|
237
|
+
? resolveModelImports(collectModels(envelope.routes), sharedTypesImport, sharedModelsModule)
|
|
238
|
+
: []
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
242
|
+
|
|
243
|
+
Run: `npx vitest run src/codegen/targets/ts/shared-models.test.ts`
|
|
244
|
+
Expected: PASS (existing + 2 new tests).
|
|
245
|
+
|
|
246
|
+
- [ ] **Step 5: Commit**
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
git add src/codegen/targets/_shared/target-run.ts src/codegen/pipeline.ts src/codegen/index.ts src/codegen/targets/ts/run.ts src/codegen/targets/ts/shared-models.test.ts
|
|
250
|
+
git commit -m "feat(codegen): thread sharedModelsModule convention through pipeline to TS run"
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## #B — Footgun diagnostics
|
|
256
|
+
|
|
257
|
+
### Task 3: Always-on summary + `--strict-shared-models` hard-fail
|
|
258
|
+
|
|
259
|
+
**Files:**
|
|
260
|
+
- Modify: `src/codegen/targets/_shared/target-run.ts` (add `strictSharedModels?: boolean`)
|
|
261
|
+
- Modify: `src/codegen/pipeline.ts` (`PipelineOptions` + `base`)
|
|
262
|
+
- Modify: `src/codegen/index.ts` (`GenerateClientOptions` + pass-through)
|
|
263
|
+
- Modify: `src/codegen/targets/ts/run.ts` (summary log + strict throw)
|
|
264
|
+
- Test: `src/codegen/targets/ts/shared-models.test.ts`
|
|
265
|
+
|
|
266
|
+
- [ ] **Step 1: Write the failing test**
|
|
267
|
+
|
|
268
|
+
Append inside the existing `describe('shared models ...')` block in `src/codegen/targets/ts/shared-models.test.ts`:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
it('logs a neutral summary of the re-exported / generated split', async () => {
|
|
272
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
273
|
+
try {
|
|
274
|
+
await runPipeline({
|
|
275
|
+
envelope: modelEnvelope(),
|
|
276
|
+
outDir: 'out',
|
|
277
|
+
dryRun: true,
|
|
278
|
+
shareModels: true,
|
|
279
|
+
namespaceTypes: true,
|
|
280
|
+
selfContained: false,
|
|
281
|
+
})
|
|
282
|
+
expect(log).toHaveBeenCalledWith(
|
|
283
|
+
expect.stringContaining('Shared models: 1 total — 0 re-exported, 1 generated locally.'),
|
|
284
|
+
)
|
|
285
|
+
} finally {
|
|
286
|
+
log.mockRestore()
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('does not log a summary when there are no $id-bearing models', async () => {
|
|
291
|
+
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
|
292
|
+
try {
|
|
293
|
+
await runPipeline({
|
|
294
|
+
envelope: noModelEnvelope(),
|
|
295
|
+
outDir: 'out',
|
|
296
|
+
dryRun: true,
|
|
297
|
+
shareModels: true,
|
|
298
|
+
namespaceTypes: true,
|
|
299
|
+
selfContained: false,
|
|
300
|
+
})
|
|
301
|
+
expect(log.mock.calls.flat().join('\n')).not.toContain('Shared models:')
|
|
302
|
+
} finally {
|
|
303
|
+
log.mockRestore()
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('strictSharedModels throws listing every $id generated as a local twin', async () => {
|
|
308
|
+
await expect(
|
|
309
|
+
runPipeline({
|
|
310
|
+
envelope: modelEnvelope(),
|
|
311
|
+
outDir: 'out',
|
|
312
|
+
dryRun: true,
|
|
313
|
+
shareModels: true,
|
|
314
|
+
namespaceTypes: true,
|
|
315
|
+
selfContained: false,
|
|
316
|
+
strictSharedModels: true,
|
|
317
|
+
}),
|
|
318
|
+
).rejects.toThrow(/--strict-shared-models/)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('strictSharedModels passes once every model is covered by the convention', async () => {
|
|
322
|
+
await expect(
|
|
323
|
+
runPipeline({
|
|
324
|
+
envelope: modelEnvelope(),
|
|
325
|
+
outDir: 'out',
|
|
326
|
+
dryRun: true,
|
|
327
|
+
shareModels: true,
|
|
328
|
+
namespaceTypes: true,
|
|
329
|
+
selfContained: false,
|
|
330
|
+
strictSharedModels: true,
|
|
331
|
+
sharedModelsModule: '@app/schemas',
|
|
332
|
+
}),
|
|
333
|
+
).resolves.toBeDefined()
|
|
334
|
+
})
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Ensure `vi` is imported at the top of the file (change `import { describe, it, expect } from 'vitest'` to include `vi`):
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
344
|
+
|
|
345
|
+
Run: `npx vitest run src/codegen/targets/ts/shared-models.test.ts`
|
|
346
|
+
Expected: FAIL — no summary log is emitted; `strictSharedModels` is not accepted/enforced so the strict-throw test does not reject.
|
|
347
|
+
|
|
348
|
+
- [ ] **Step 3a: Thread `strictSharedModels` through the type chain**
|
|
349
|
+
|
|
350
|
+
In `src/codegen/targets/_shared/target-run.ts`, add to `TargetRunInput` (after the `sharedModelsModule` field added in Task 2):
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
/** When true, hard-fail if any $id-bearing model has no shared-type mapping (would be a local twin). */
|
|
354
|
+
strictSharedModels?: boolean
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
In `src/codegen/pipeline.ts`, add to `PipelineOptions` (after `sharedModelsModule?: string`):
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
strictSharedModels?: boolean
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
And to `base` (after `sharedModelsModule: options.sharedModelsModule,`):
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
strictSharedModels: options.strictSharedModels,
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
In `src/codegen/index.ts`, add to `GenerateClientOptions` (after `sharedModelsModule?: string`):
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
/** Hard-fail codegen if any $id-bearing model would be generated as a local structural twin. */
|
|
373
|
+
strictSharedModels?: boolean
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
And to the `runPipeline({ ... })` call (after `sharedModelsModule: options.sharedModelsModule,`):
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
strictSharedModels: options.strictSharedModels,
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- [ ] **Step 3b: Emit the summary + strict check in the TS run**
|
|
383
|
+
|
|
384
|
+
In `src/codegen/targets/ts/run.ts`, add `strictSharedModels` to the destructure (after `sharedModelsModule,`):
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
strictSharedModels = false,
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Then insert this block immediately after the `_models` reserved-filename loop (after the closing `}` of the `if (shareModels) { for (const group of groups) { … } }` at line 75):
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
if (shareModels && models.length > 0) {
|
|
394
|
+
const generated = models.filter((m) => m.import == null)
|
|
395
|
+
if (strictSharedModels && generated.length > 0) {
|
|
396
|
+
const list = generated.map((m) => `"${m.id}" (${m.name})`).join(', ')
|
|
397
|
+
throw new Error(
|
|
398
|
+
`[ts-procedures-codegen] --strict-shared-models: ${generated.length} $id-bearing schema(s) have no shared-type mapping and would be generated as local structural twins: ${list}. Add a sharedTypesImport entry, set sharedModelsModule, or drop --strict-shared-models.`,
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
const reExported = models.length - generated.length
|
|
402
|
+
console.log(
|
|
403
|
+
`[ts-procedures-codegen] Shared models: ${models.length} total — ${reExported} re-exported, ${generated.length} generated locally.`,
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
409
|
+
|
|
410
|
+
Run: `npx vitest run src/codegen/targets/ts/shared-models.test.ts`
|
|
411
|
+
Expected: PASS (existing + 4 new tests).
|
|
412
|
+
|
|
413
|
+
- [ ] **Step 5: Commit**
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
git add src/codegen/targets/_shared/target-run.ts src/codegen/pipeline.ts src/codegen/index.ts src/codegen/targets/ts/run.ts src/codegen/targets/ts/shared-models.test.ts
|
|
417
|
+
git commit -m "feat(codegen): shared-models summary log + --strict-shared-models hard-fail"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## #C — CLI surface
|
|
423
|
+
|
|
424
|
+
### Task 4: Flag catalog entries
|
|
425
|
+
|
|
426
|
+
**Files:**
|
|
427
|
+
- Modify: `src/codegen/bin/flag-specs.ts:9-43`
|
|
428
|
+
- Test: `src/codegen/bin/flag-specs.test.ts`
|
|
429
|
+
|
|
430
|
+
- [ ] **Step 1: Write the failing test**
|
|
431
|
+
|
|
432
|
+
Append to `src/codegen/bin/flag-specs.test.ts` (inside the existing `describe('flag-specs', …)` block):
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
it('catalogs the shared-models convention + strict flags', () => {
|
|
436
|
+
expect(KNOWN_FLAGS).toContain('--shared-models-module')
|
|
437
|
+
expect(KNOWN_FLAGS).toContain('--strict-shared-models')
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('documents the new flags in --help output', () => {
|
|
441
|
+
const help = formatHelp()
|
|
442
|
+
expect(help).toContain('--shared-models-module')
|
|
443
|
+
expect(help).toContain('--strict-shared-models')
|
|
444
|
+
})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
448
|
+
|
|
449
|
+
Run: `npx vitest run src/codegen/bin/flag-specs.test.ts`
|
|
450
|
+
Expected: FAIL — neither flag is in `FLAG_SPECS`, so `KNOWN_FLAGS`/`formatHelp` lack them.
|
|
451
|
+
|
|
452
|
+
- [ ] **Step 3: Add the spec entries**
|
|
453
|
+
|
|
454
|
+
In `src/codegen/bin/flag-specs.ts`, add to the `FLAG_SPECS` array in the `Codegen` group (after the `--no-share-models` entry, line 29):
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
{ name: '--shared-models-module', arg: '<module>', description: 'Re-export every $id model from one module (convention; sharedTypesImport overrides)', group: 'Codegen' },
|
|
458
|
+
{ name: '--strict-shared-models', description: 'Fail if any $id model would be generated as a local twin', group: 'Codegen' },
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
462
|
+
|
|
463
|
+
Run: `npx vitest run src/codegen/bin/flag-specs.test.ts`
|
|
464
|
+
Expected: PASS.
|
|
465
|
+
|
|
466
|
+
- [ ] **Step 5: Commit**
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
git add src/codegen/bin/flag-specs.ts src/codegen/bin/flag-specs.test.ts
|
|
470
|
+
git commit -m "feat(codegen): catalog --shared-models-module and --strict-shared-models flags"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
### Task 5: CLI parse, config, watch + main wiring
|
|
476
|
+
|
|
477
|
+
**Files:**
|
|
478
|
+
- Modify: `src/codegen/bin/cli.ts:14-33` (`CodegenConfig`), `35-54` (`ParsedArgs`), `152-340` (`parseArgs`), `360-441` (`runWithWatch`), `496-568` (`main`)
|
|
479
|
+
- Test: `src/codegen/bin/cli.test.ts`
|
|
480
|
+
|
|
481
|
+
- [ ] **Step 1: Write the failing test**
|
|
482
|
+
|
|
483
|
+
Append to `src/codegen/bin/cli.test.ts` (match the file's existing `parseArgs` describe/import style):
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
it('parses --shared-models-module into sharedModelsModule', () => {
|
|
487
|
+
const parsed = parseArgs(['--out', 'gen', '--file', 'e.json', '--shared-models-module', '@app/schemas'])
|
|
488
|
+
expect(parsed.sharedModelsModule).toBe('@app/schemas')
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
it('parses --strict-shared-models as a boolean (default false)', () => {
|
|
492
|
+
expect(parseArgs(['--out', 'gen', '--file', 'e.json']).strictSharedModels).toBe(false)
|
|
493
|
+
expect(
|
|
494
|
+
parseArgs(['--out', 'gen', '--file', 'e.json', '--strict-shared-models']).strictSharedModels,
|
|
495
|
+
).toBe(true)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('CLI --shared-models-module overrides a config value', () => {
|
|
499
|
+
const parsed = parseArgs(
|
|
500
|
+
['--out', 'gen', '--file', 'e.json', '--shared-models-module', '@cli/pkg'],
|
|
501
|
+
{ sharedModelsModule: '@config/pkg' },
|
|
502
|
+
)
|
|
503
|
+
expect(parsed.sharedModelsModule).toBe('@cli/pkg')
|
|
504
|
+
})
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
- [ ] **Step 2: Run test to verify it fails**
|
|
508
|
+
|
|
509
|
+
Run: `npx vitest run src/codegen/bin/cli.test.ts`
|
|
510
|
+
Expected: FAIL — `parseArgs` rejects `--shared-models-module` as an unknown flag (and `ParsedArgs` lacks the fields).
|
|
511
|
+
|
|
512
|
+
- [ ] **Step 3a: Extend `CodegenConfig` and `ParsedArgs`**
|
|
513
|
+
|
|
514
|
+
In `src/codegen/bin/cli.ts`, add to `CodegenConfig` (after `sharedTypesImport?: SharedTypesImportMap`, line 32):
|
|
515
|
+
|
|
516
|
+
```ts
|
|
517
|
+
sharedModelsModule?: string
|
|
518
|
+
strictSharedModels?: boolean
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Add to `ParsedArgs` (after `sharedTypesImport?: SharedTypesImportMap`, line 53):
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
sharedModelsModule?: string
|
|
525
|
+
strictSharedModels: boolean
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
- [ ] **Step 3b: Read config defaults + add parse branches**
|
|
529
|
+
|
|
530
|
+
In `parseArgs`, add config-seeded locals (after line 172, `const sharedTypesImport = config?.sharedTypesImport`):
|
|
531
|
+
|
|
532
|
+
```ts
|
|
533
|
+
let sharedModelsModule: string | undefined = config?.sharedModelsModule
|
|
534
|
+
let strictSharedModels = config?.strictSharedModels ?? false
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Add parse branches (after the `--share-models` / `--no-share-models` branches, line 262):
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
} else if (arg === '--shared-models-module') {
|
|
541
|
+
sharedModelsModule = argv[++i]
|
|
542
|
+
} else if (arg === '--strict-shared-models') {
|
|
543
|
+
strictSharedModels = true
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Add to the returned object (after `...(sharedTypesImport !== undefined ? { sharedTypesImport } : {}),`, line 338):
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
...(sharedModelsModule !== undefined ? { sharedModelsModule } : {}),
|
|
550
|
+
strictSharedModels,
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
- [ ] **Step 3c: Forward through `runWithWatch` and `main`**
|
|
554
|
+
|
|
555
|
+
In `runWithWatch`, add to the `runPipeline({ … })` call (after `...(parsed.sharedTypesImport !== undefined ? { sharedTypesImport: parsed.sharedTypesImport } : {}),`, line 427):
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
...(parsed.sharedModelsModule !== undefined ? { sharedModelsModule: parsed.sharedModelsModule } : {}),
|
|
559
|
+
strictSharedModels: parsed.strictSharedModels,
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
In `main`, add to the `generateClient({ … })` call (after `...(parsed.sharedTypesImport !== undefined ? { sharedTypesImport: parsed.sharedTypesImport } : {}),`, line 557):
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
...(parsed.sharedModelsModule !== undefined ? { sharedModelsModule: parsed.sharedModelsModule } : {}),
|
|
566
|
+
strictSharedModels: parsed.strictSharedModels,
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
- [ ] **Step 4: Run test to verify it passes**
|
|
570
|
+
|
|
571
|
+
Run: `npx vitest run src/codegen/bin/cli.test.ts`
|
|
572
|
+
Expected: PASS.
|
|
573
|
+
|
|
574
|
+
- [ ] **Step 5: Full suite + lint + build**
|
|
575
|
+
|
|
576
|
+
Run: `npm run test && npm run lint && npm run build`
|
|
577
|
+
Expected: all green (catches any missed threading site, e.g. a type error in `runWithWatch`/`main`).
|
|
578
|
+
|
|
579
|
+
- [ ] **Step 6: Commit**
|
|
580
|
+
|
|
581
|
+
```bash
|
|
582
|
+
git add src/codegen/bin/cli.ts src/codegen/bin/cli.test.ts
|
|
583
|
+
git commit -m "feat(codegen): CLI flags for sharedModelsModule + strict-shared-models"
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## #D — Documentation & downstream response
|
|
589
|
+
|
|
590
|
+
### Task 6: Update repo docs
|
|
591
|
+
|
|
592
|
+
**Files:**
|
|
593
|
+
- Modify: `CLAUDE.md` (the `Client Code Generation` section, around the `shareModels` / `sharedTypesImport` bullets)
|
|
594
|
+
- Modify: `docs/client-and-codegen.md`
|
|
595
|
+
|
|
596
|
+
- [ ] **Step 1: Update CLAUDE.md**
|
|
597
|
+
|
|
598
|
+
In the `Client Code Generation` section, after the existing `sharedTypesImport` bullet, add:
|
|
599
|
+
|
|
600
|
+
```markdown
|
|
601
|
+
- `sharedModelsModule` (config / `--shared-models-module <module>`) is the convention form of `sharedTypesImport`: every `$id`-bearing model with no explicit map entry is re-exported from this single module under its derived name (`$id`/`title` === export). Precedence in `resolveModelImports`: explicit `sharedTypesImport[$id]` → `sharedModelsModule` → generate locally. Use the per-`$id` map only for overrides (rename, a different package per type, multi-consumer setups).
|
|
602
|
+
- **Shared-models diagnostics:** every run with `$id`-bearing models logs a neutral one-line summary (`Shared models: N total — X re-exported, Y generated locally.`) so a silently-generated structural twin is visible. `strictSharedModels` (`--strict-shared-models`, default off) turns that into a hard error listing each `$id` that would be generated locally — a CI guard against the single-source-of-truth silently degrading. (Source-tree scanning, e.g. `--shared-types-from <dir>`, was deliberately rejected: codegen stays pure-envelope so `--url`/offline `--file` codegen never depends on a local source tree.)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
- [ ] **Step 2: Update docs/client-and-codegen.md**
|
|
606
|
+
|
|
607
|
+
Locate the shared-types / `sharedTypesImport` subsection and add a short subsection documenting `sharedModelsModule` (the one-value convention) with a config + CLI example, plus the summary log and `--strict-shared-models` CI usage. Match the surrounding heading depth and prose style. Example block to include:
|
|
608
|
+
|
|
609
|
+
```jsonc
|
|
610
|
+
// ts-procedures-codegen.config.json — collapse the per-$id map to one module
|
|
611
|
+
{ "sharedModelsModule": "@app/schemas" }
|
|
612
|
+
```
|
|
613
|
+
```bash
|
|
614
|
+
# CI: fail the build if any domain entity is generated as a structural twin
|
|
615
|
+
npx ts-procedures-codegen --file envelope.json --out gen --strict-shared-models
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
- [ ] **Step 3: Verify the docs gate passes**
|
|
619
|
+
|
|
620
|
+
Run: `npm run lint`
|
|
621
|
+
Expected: PASS (also satisfies any `check-docs` gate referenced in recent commits).
|
|
622
|
+
|
|
623
|
+
- [ ] **Step 4: Commit**
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
git add CLAUDE.md docs/client-and-codegen.md
|
|
627
|
+
git commit -m "docs: document sharedModelsModule convention + shared-models diagnostics"
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
### Task 7: Downstream response write-up
|
|
633
|
+
|
|
634
|
+
**Files:**
|
|
635
|
+
- Create: `docs/handoffs/shared-models-auto-resolve-response.md`
|
|
636
|
+
|
|
637
|
+
- [ ] **Step 1: Write the response doc**
|
|
638
|
+
|
|
639
|
+
Create `docs/handoffs/shared-models-auto-resolve-response.md` with:
|
|
640
|
+
- **What shipped:** `sharedModelsModule` (one value replaces the hand-maintained map for the common single-module case), the always-on summary, and `--strict-shared-models` (replaces their hand-written `toEqualTypeOf` drift test with a CI flag). All available as CLI flag + config + `generateClient()` option.
|
|
641
|
+
- **Maps their three pain points to the fix:** (1) two-places-in-lockstep → one `sharedModelsModule` value, derived not hand-maintained; (2) silent fallback → always-on summary + strict hard-fail; (3) redundant map → convention (`name = $id`, module supplied once).
|
|
642
|
+
- **Why `--shared-types-from <dir|glob>` was declined:** the codegen's input contract is the DocEnvelope only — it never reads the consumer source tree. Scanning source breaks `--url`/offline `--file` (local source can disagree with the envelope, yielding a silently-wrong-but-typechecking single source of truth), fails for published-package consumers, and drags a TS parser/loader + glob + export-resolution heuristics into codegen. The convention achieves the same "define-once, no second table" goal without any of that.
|
|
643
|
+
- **Escape hatch unchanged:** the per-`$id` `sharedTypesImport` map remains for rename / different-package-per-type / multi-consumer.
|
|
644
|
+
|
|
645
|
+
- [ ] **Step 2: Commit**
|
|
646
|
+
|
|
647
|
+
```bash
|
|
648
|
+
git add docs/handoffs/shared-models-auto-resolve-response.md
|
|
649
|
+
git commit -m "docs(handoff): shared-models auto-resolve response; rationale for declining source scanning"
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## Self-Review
|
|
655
|
+
|
|
656
|
+
- **Spec coverage:** Pain #1 (two-places-in-lockstep, redundant map) → Tasks 1–2 (`sharedModelsModule` convention) + Task 5 (CLI) + Task 6 (docs). Pain #2 (silent fallback) → Task 3 (summary + strict). Secondary ask (config-only / no CLI flag) → Tasks 4–5. Declined primary (`--shared-types-from`) → Task 7 rationale. Out-of-scope items called out in the header. ✅
|
|
657
|
+
- **Placeholder scan:** every code step shows the exact insertion; Task 6 step 2 references "the surrounding style" but provides concrete example blocks. No TODO/TBD. ✅
|
|
658
|
+
- **Type consistency:** option names are identical across every layer — `sharedModelsModule: string`, `strictSharedModels: boolean` — in `TargetRunInput`, `PipelineOptions`, `GenerateClientOptions`, `CodegenConfig`, `ParsedArgs`; CLI flags `--shared-models-module`, `--strict-shared-models`; `resolveModelImports(models, map, sharedModelsModule)`. ✅
|
|
659
|
+
- **Backward compatibility:** all new options default to unset/false; when neither is set, `resolveModelImports` and emitted output are byte-identical to today (summary only prints when `$id` models exist). ✅
|