@popoverai/dotrequirements 0.24.3 → 0.26.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/README.md +7 -8
- package/dist/cli.js +8 -1
- package/dist/codebase-to-spec/dispatch.d.ts +60 -14
- package/dist/codebase-to-spec/dispatch.js +381 -15
- package/dist/codebase-to-spec/pack.d.ts +7 -0
- package/dist/codebase-to-spec/pack.js +29 -8
- package/dist/codebase-to-spec/present.d.ts +9 -0
- package/dist/codebase-to-spec/present.js +23 -2
- package/dist/codebase-to-spec/prompts/editor.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/editor.js +1 -1
- package/dist/codebase-to-spec/prompts/specifier.d.ts +1 -1
- package/dist/codebase-to-spec/prompts/specifier.js +3 -2
- package/dist/codebase-to-spec/schemas.d.ts +153 -0
- package/dist/codebase-to-spec/schemas.js +111 -0
- package/dist/codebase-to-spec/skill-install.d.ts +42 -29
- package/dist/codebase-to-spec/skill-install.js +122 -112
- package/dist/codebase-to-spec/version-check.d.ts +31 -0
- package/dist/codebase-to-spec/version-check.js +56 -0
- package/dist/commands/ai-setup.d.ts +12 -1
- package/dist/commands/ai-setup.js +65 -33
- package/dist/commands/codebase-to-spec/dispatch-context.d.ts +2 -5
- package/dist/commands/codebase-to-spec/dispatch-context.js +2 -5
- package/dist/commands/codebase-to-spec/dispatch-editor.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-editor.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.d.ts +0 -1
- package/dist/commands/codebase-to-spec/dispatch-planner.js +0 -1
- package/dist/commands/codebase-to-spec/dispatch-spec.d.ts +3 -6
- package/dist/commands/codebase-to-spec/dispatch-spec.js +3 -6
- package/dist/commands/codebase-to-spec/index.js +3 -2
- package/dist/commands/codebase-to-spec/pack.d.ts +9 -0
- package/dist/commands/codebase-to-spec/pack.js +23 -3
- package/dist/commands/codebase-to-spec/skill-install.js +2 -9
- package/dist/commands/init.js +6 -1
- package/dist/commands/link-resolution.d.ts +79 -0
- package/dist/commands/link-resolution.js +141 -0
- package/dist/commands/link.d.ts +14 -4
- package/dist/commands/link.js +369 -16
- package/dist/commands/pull.js +19 -2
- package/dist/commands/push.js +36 -2
- package/dist/convex.d.ts +5 -3
- package/dist/convex.js +5 -3
- package/dist/harness/cache.d.ts +0 -14
- package/dist/harness/cache.js +1 -41
- package/dist/harness/finalize.js +2 -2
- package/dist/harness/prepare.js +1 -3
- package/dist/harness/requirementsLoader.d.ts +3 -3
- package/dist/harness/requirementsLoader.js +13 -8
- package/dist/mcp/handlers/authoring.d.ts +5 -5
- package/dist/mcp/handlers/authoring.js +9 -9
- package/dist/mcp/handlers/push.d.ts +2 -2
- package/dist/mcp/handlers/push.js +36 -3
- package/dist/mcp/handlers/review.d.ts +4 -4
- package/dist/mcp/handlers/review.js +4 -4
- package/dist/mcp/handlers/search.d.ts +1 -1
- package/dist/mcp/handlers/search.js +1 -1
- package/dist/mcp/index.js +29 -0
- package/dist/push/core.d.ts +18 -0
- package/dist/push/core.js +70 -3
- package/dist/push/index.d.ts +1 -1
- package/dist/push/index.js +1 -1
- package/dist/schema/parser-core.js +5 -1
- package/dist/schema/parser.js +5 -1
- package/dist/schema/run-marker.d.ts +38 -0
- package/dist/schema/run-marker.js +138 -0
- package/dist/schema/schemas.d.ts +12 -0
- package/dist/schema/schemas.js +1 -0
- package/dist/templates/agents/cts-worker.md +3 -3
- package/dist/templates/skills/codebase-to-spec/SKILL.md +56 -158
- package/dist/templates/workflows/specify-codebase.js +374 -0
- package/dist/utils/own-package.d.ts +10 -0
- package/dist/utils/own-package.js +13 -0
- package/dist/utils/project-selector.d.ts +5 -0
- package/dist/utils/project-selector.js +4 -0
- package/package.json +3 -3
- package/dist/templates/hooks/cts-worker-persona.sh +0 -76
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: 'specify-codebase',
|
|
3
|
+
description:
|
|
4
|
+
'Codebase-to-spec: autonomously plan a behavioral outline, fan out specifiers across its areas, and converge each area via independent review + editor passes — no mid-run human input',
|
|
5
|
+
whenToUse:
|
|
6
|
+
'Invoked by the codebase-to-spec skill after `cts pack`. Not run directly.',
|
|
7
|
+
phases: [
|
|
8
|
+
{ title: 'Outline' },
|
|
9
|
+
{ title: 'Specify' },
|
|
10
|
+
{ title: 'Converge' },
|
|
11
|
+
{ title: 'Reconcile' },
|
|
12
|
+
],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Injected globals: agent, pipeline, parallel, phase, log, args.
|
|
16
|
+
//
|
|
17
|
+
// args:
|
|
18
|
+
// cli — resolved dotrequirements CLI invocation workers shell out to
|
|
19
|
+
// (e.g. "dotrequirements" or "node /abs/path/packages/cli/dist/cli.js")
|
|
20
|
+
// roundsCap — max review→edit rounds per area before giving up (default 5)
|
|
21
|
+
// outlineRoundsCap — max plan→review→revise rounds for the outline (default 4)
|
|
22
|
+
// crossAreaRoundsCap — max cross-area review→edit rounds on the composed spec (default 5)
|
|
23
|
+
//
|
|
24
|
+
// The caps are a runaway backstop, not a target: an area stops the moment its
|
|
25
|
+
// reviewer approves, and reports `unconverged` if it can't converge in N rounds.
|
|
26
|
+
// Defaults are set high enough to give careful review room on messy codebases.
|
|
27
|
+
//
|
|
28
|
+
// args may arrive as a JSON string depending on how the caller passes it; normalize.
|
|
29
|
+
const input = typeof args === 'string' ? JSON.parse(args) : args || {}
|
|
30
|
+
// `cli` defaults to the invocation stamped at install time (a version-pinned
|
|
31
|
+
// `npx` form for installed users, so no PATH binary is needed and every
|
|
32
|
+
// shell-out in a run resolves the same version); the dev repo passes a
|
|
33
|
+
// `node <repo>/dist/cli.js` invocation explicitly.
|
|
34
|
+
const {
|
|
35
|
+
cli = '{{DOTREQ_CLI}}',
|
|
36
|
+
roundsCap = 5,
|
|
37
|
+
outlineRoundsCap = 4,
|
|
38
|
+
crossAreaRoundsCap = 5,
|
|
39
|
+
} = input
|
|
40
|
+
|
|
41
|
+
// --- Schemas (mirrors of the JSON Schemas in schemas.ts; a sync test guards drift) ---
|
|
42
|
+
|
|
43
|
+
const PARTIAL_RESULT_SCHEMA = {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
area_prefix: { type: 'string' },
|
|
47
|
+
partial_path: { type: 'string' },
|
|
48
|
+
status: { type: 'string', enum: ['drafted', 'skipped-resume'] },
|
|
49
|
+
requirement_count: { type: 'integer', minimum: 0 },
|
|
50
|
+
},
|
|
51
|
+
required: ['area_prefix', 'partial_path', 'status'],
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const REVIEW_VERDICT_SCHEMA = {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
result: { type: 'string', enum: ['approved', 'needs-revision'] },
|
|
59
|
+
revisions: { type: 'array', items: { type: 'string' } },
|
|
60
|
+
},
|
|
61
|
+
required: ['result'],
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Shape `cts dispatch-spec` emits (one entry per approved-outline area).
|
|
66
|
+
const AREAS_SCHEMA = {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
areas: {
|
|
70
|
+
type: 'array',
|
|
71
|
+
items: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
dispatch_id: { type: 'string' },
|
|
75
|
+
area_prefix: { type: 'string' },
|
|
76
|
+
area_name: { type: 'string' },
|
|
77
|
+
output_path: { type: 'string' },
|
|
78
|
+
},
|
|
79
|
+
required: ['dispatch_id', 'area_prefix', 'area_name', 'output_path'],
|
|
80
|
+
additionalProperties: false,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
required: ['areas'],
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Thin self-compose wrapper: a worker fetches its full instructions from the CLI.
|
|
89
|
+
function follow(line) {
|
|
90
|
+
return [
|
|
91
|
+
`Run exactly this command: ${line}`,
|
|
92
|
+
`It prints JSON of the form {"prompt": "..."}. Read the "prompt" field and follow it EXACTLY.`,
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================ Stage 0: outline ============================
|
|
97
|
+
// plan → review → revise → re-review, until the outline reviewer approves the
|
|
98
|
+
// area decomposition or the cap is hit. Sequential (one outline), so the
|
|
99
|
+
// reviewer's writes to the document-level review.thread never contend.
|
|
100
|
+
phase('Outline')
|
|
101
|
+
|
|
102
|
+
await agent(
|
|
103
|
+
[
|
|
104
|
+
`You are a codebase-to-spec planner. Produce the behavioral-area outline for the packed codebase.`,
|
|
105
|
+
...follow(`${cli} cts dispatch-context planner-initial`),
|
|
106
|
+
`End with a one-line confirmation of the outline path you wrote.`,
|
|
107
|
+
].join('\n'),
|
|
108
|
+
{ agentType: 'cts-worker', label: 'plan outline', phase: 'Outline' },
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
let outlineApproved = false
|
|
112
|
+
for (let round = 1; round <= outlineRoundsCap; round++) {
|
|
113
|
+
const verdict = await agent(
|
|
114
|
+
[
|
|
115
|
+
`You are a codebase-to-spec outline reviewer (independent of the planner).`,
|
|
116
|
+
...follow(`${cli} cts dispatch-context outline-reviewer`),
|
|
117
|
+
`It tells you to record your verdict in the outline's top-level review thread and return it.`,
|
|
118
|
+
`Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
|
|
119
|
+
].join('\n'),
|
|
120
|
+
{
|
|
121
|
+
schema: REVIEW_VERDICT_SCHEMA,
|
|
122
|
+
agentType: 'cts-worker',
|
|
123
|
+
label: `review outline r${round}`,
|
|
124
|
+
phase: 'Outline',
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// Fail-closed: only an explicit "approved" verdict advances. A null verdict
|
|
129
|
+
// (reviewer subagent died after retries) is treated as non-approval — fall
|
|
130
|
+
// through to revise/re-review so we never fan out an unreviewed outline.
|
|
131
|
+
if (verdict?.result === 'approved') {
|
|
132
|
+
outlineApproved = true
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
if (round === outlineRoundsCap) break
|
|
136
|
+
|
|
137
|
+
await agent(
|
|
138
|
+
[
|
|
139
|
+
`You are a codebase-to-spec planner applying the reviewer's outline revisions.`,
|
|
140
|
+
...follow(`${cli} cts dispatch-context planner-revise-${round + 1}`),
|
|
141
|
+
`End with a one-line confirmation.`,
|
|
142
|
+
].join('\n'),
|
|
143
|
+
{ agentType: 'cts-worker', label: `revise outline r${round}`, phase: 'Outline' },
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!outlineApproved) {
|
|
148
|
+
// No mid-run HITL: don't fan out a decomposition the reviewer wouldn't pass.
|
|
149
|
+
log(`outline did not converge within ${outlineRoundsCap} rounds`)
|
|
150
|
+
return {
|
|
151
|
+
status: 'outline-unconverged',
|
|
152
|
+
outline_rounds: outlineRoundsCap,
|
|
153
|
+
areas: [],
|
|
154
|
+
converged_count: 0,
|
|
155
|
+
unconverged: [],
|
|
156
|
+
failed: [],
|
|
157
|
+
total: 0,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ===================== Stage 1+2: fan out + converge =====================
|
|
162
|
+
// Enumerate the approved outline's areas (an agent runs the CLI; the script
|
|
163
|
+
// can't), then run each area through specify → review/edit independently.
|
|
164
|
+
phase('Specify')
|
|
165
|
+
|
|
166
|
+
const enumerated = await agent(
|
|
167
|
+
[
|
|
168
|
+
`Run exactly this command: ${cli} cts dispatch-spec`,
|
|
169
|
+
`It prints a JSON array, one entry per area of the approved outline.`,
|
|
170
|
+
`Return { "areas": <that array, verbatim> }.`,
|
|
171
|
+
].join('\n'),
|
|
172
|
+
{ schema: AREAS_SCHEMA, agentType: 'cts-worker', label: 'enumerate areas', phase: 'Specify' },
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
// Fail-closed: a dead enumerate agent (null return) must surface as a failure,
|
|
176
|
+
// not read as an empty-but-successful decomposition that sails through fan-out
|
|
177
|
+
// and reconcile to a hollow `done`.
|
|
178
|
+
if (!enumerated) {
|
|
179
|
+
log('area enumeration failed — cannot fan out')
|
|
180
|
+
return {
|
|
181
|
+
status: 'enumerate-failed',
|
|
182
|
+
areas: [],
|
|
183
|
+
converged_count: 0,
|
|
184
|
+
unconverged: [],
|
|
185
|
+
failed: [],
|
|
186
|
+
total: 0,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const areas = enumerated.areas
|
|
190
|
+
|
|
191
|
+
async function specify(area) {
|
|
192
|
+
return agent(
|
|
193
|
+
[
|
|
194
|
+
`You are a codebase-to-spec specifier worker for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
|
|
195
|
+
``,
|
|
196
|
+
`Step 1 — Resume check. If a non-empty partial already exists at:`,
|
|
197
|
+
` ${area.output_path}`,
|
|
198
|
+
` then do NOT regenerate it. Return`,
|
|
199
|
+
` { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}", "status": "skipped-resume" }.`,
|
|
200
|
+
``,
|
|
201
|
+
`Step 2 — Otherwise, run exactly this command:`,
|
|
202
|
+
` ${cli} cts dispatch-context specifier-${area.area_prefix}`,
|
|
203
|
+
` Read the "prompt" field of its JSON output and follow it EXACTLY — which source files to`,
|
|
204
|
+
` read, how to ground requirements in this area's customers, where to write the partial,`,
|
|
205
|
+
` and how to validate + style-check it.`,
|
|
206
|
+
``,
|
|
207
|
+
`Step 3 — Return`,
|
|
208
|
+
` { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}",`,
|
|
209
|
+
` "status": "drafted", "requirement_count": <number of requirements you wrote> }.`,
|
|
210
|
+
].join('\n'),
|
|
211
|
+
{
|
|
212
|
+
schema: PARTIAL_RESULT_SCHEMA,
|
|
213
|
+
agentType: 'cts-worker',
|
|
214
|
+
label: `specify ${area.area_name}`,
|
|
215
|
+
phase: 'Specify',
|
|
216
|
+
},
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function reviewEditLoop(specifyResult, area) {
|
|
221
|
+
// Fail-closed (CTSO-INTEG-3.2): a dead specifier (null return) means there may
|
|
222
|
+
// be no partial to review — record the failure and continue with the other
|
|
223
|
+
// areas, instead of burning review/edit rounds on a draft that may not exist.
|
|
224
|
+
if (!specifyResult) {
|
|
225
|
+
log(`area ${area.area_prefix}: specifier failed — recording and moving on`)
|
|
226
|
+
return {
|
|
227
|
+
area_prefix: area.area_prefix,
|
|
228
|
+
area_name: area.area_name,
|
|
229
|
+
partial_path: area.output_path,
|
|
230
|
+
status: 'failed',
|
|
231
|
+
rounds: 0,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
for (let round = 1; round <= roundsCap; round++) {
|
|
236
|
+
const verdict = await agent(
|
|
237
|
+
[
|
|
238
|
+
`You are a codebase-to-spec reviewer for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
|
|
239
|
+
...follow(`${cli} cts dispatch-context reviewer-${area.area_prefix}`),
|
|
240
|
+
`It tells you to review the partial, record your verdict in the outline, and return it.`,
|
|
241
|
+
`Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
|
|
242
|
+
].join('\n'),
|
|
243
|
+
{
|
|
244
|
+
schema: REVIEW_VERDICT_SCHEMA,
|
|
245
|
+
agentType: 'cts-worker',
|
|
246
|
+
label: `review ${area.area_name} r${round}`,
|
|
247
|
+
phase: 'Converge',
|
|
248
|
+
},
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if (verdict?.result === 'approved') {
|
|
252
|
+
return {
|
|
253
|
+
area_prefix: area.area_prefix,
|
|
254
|
+
area_name: area.area_name,
|
|
255
|
+
partial_path: area.output_path,
|
|
256
|
+
status: 'converged',
|
|
257
|
+
rounds: round - 1,
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Fail-closed: a null verdict (dead reviewer) is NOT approval — but it also
|
|
262
|
+
// recorded no fresh revisions in the outline, so dispatching an editor here
|
|
263
|
+
// would only re-apply the previous round's already-applied list (or have
|
|
264
|
+
// nothing to act on in round 1). Skip straight to the next review round.
|
|
265
|
+
if (!verdict) continue
|
|
266
|
+
|
|
267
|
+
// Mirror the outline and cross-area loops: the final round's verdict is
|
|
268
|
+
// final. Without this guard a last editor pass would run whose output is
|
|
269
|
+
// never re-reviewed before composition.
|
|
270
|
+
if (round === roundsCap) break
|
|
271
|
+
|
|
272
|
+
await agent(
|
|
273
|
+
[
|
|
274
|
+
`You are a codebase-to-spec editor for the area "${area.area_name}" (prefix ${area.area_prefix}).`,
|
|
275
|
+
...follow(`${cli} cts dispatch-context editor-${area.area_prefix}`),
|
|
276
|
+
`It embeds the reviewer's revisions and tells you to apply them, then validate + style-check.`,
|
|
277
|
+
`Return { "area_prefix": "${area.area_prefix}", "partial_path": "${area.output_path}",`,
|
|
278
|
+
` "status": "drafted", "requirement_count": <number of requirements now in the partial> }.`,
|
|
279
|
+
].join('\n'),
|
|
280
|
+
{
|
|
281
|
+
schema: PARTIAL_RESULT_SCHEMA,
|
|
282
|
+
agentType: 'cts-worker',
|
|
283
|
+
label: `edit ${area.area_name} r${round}`,
|
|
284
|
+
phase: 'Converge',
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
log(`area ${area.area_prefix} hit roundsCap=${roundsCap} without approval`)
|
|
290
|
+
return {
|
|
291
|
+
area_prefix: area.area_prefix,
|
|
292
|
+
area_name: area.area_name,
|
|
293
|
+
partial_path: area.output_path,
|
|
294
|
+
status: 'unconverged',
|
|
295
|
+
rounds: roundsCap,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
log(`Outline approved — fanning out ${areas.length} area(s)`)
|
|
300
|
+
|
|
301
|
+
const results = (await pipeline(areas, specify, reviewEditLoop)).filter(Boolean)
|
|
302
|
+
|
|
303
|
+
// ===================== Stage 3: compose + cross-area reconcile =====================
|
|
304
|
+
// The per-area partials have converged. Assemble them into one composed spec
|
|
305
|
+
// (deterministic), then run the document-level cross-area review/edit loop
|
|
306
|
+
// (CTSO-CONV-5) — duplication, terminology drift, and seam gaps that only a
|
|
307
|
+
// whole-document view catches — before the skill presents the result.
|
|
308
|
+
phase('Reconcile')
|
|
309
|
+
|
|
310
|
+
await agent(
|
|
311
|
+
[
|
|
312
|
+
`Run exactly this command: ${cli} cts compose-orchestrator`,
|
|
313
|
+
`It deterministically assembles the converged area partials into a single composed spec.`,
|
|
314
|
+
`End with a one-line confirmation of the composed-spec path it reports.`,
|
|
315
|
+
].join('\n'),
|
|
316
|
+
{ agentType: 'cts-worker', label: 'compose spec', phase: 'Reconcile' },
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
let crossAreaRounds = 0
|
|
320
|
+
let crossAreaConverged = false
|
|
321
|
+
for (let round = 1; round <= crossAreaRoundsCap; round++) {
|
|
322
|
+
crossAreaRounds = round
|
|
323
|
+
const verdict = await agent(
|
|
324
|
+
[
|
|
325
|
+
`You are a codebase-to-spec cross-area reviewer (document-level, independent).`,
|
|
326
|
+
...follow(`${cli} cts dispatch-context cross-area-reviewer`),
|
|
327
|
+
`It tells you to review the composed spec for cross-area + document-level issues, record your verdict in the outline, and return it.`,
|
|
328
|
+
`Return { "result": "approved" | "needs-revision", "revisions": [...] }.`,
|
|
329
|
+
].join('\n'),
|
|
330
|
+
{
|
|
331
|
+
schema: REVIEW_VERDICT_SCHEMA,
|
|
332
|
+
agentType: 'cts-worker',
|
|
333
|
+
label: `cross-area review r${round}`,
|
|
334
|
+
phase: 'Reconcile',
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
// Fail-closed: only an explicit "approved" verdict ends the loop. A null
|
|
339
|
+
// verdict (dead reviewer) is non-approval — fall through to edit/re-review.
|
|
340
|
+
if (verdict?.result === 'approved') {
|
|
341
|
+
crossAreaConverged = true
|
|
342
|
+
break
|
|
343
|
+
}
|
|
344
|
+
if (round === crossAreaRoundsCap) break
|
|
345
|
+
|
|
346
|
+
// The reviewer recorded its revisions in the outline's crossAreaReview thread;
|
|
347
|
+
// the compose editor self-composes from there and edits the composed spec.
|
|
348
|
+
await agent(
|
|
349
|
+
[
|
|
350
|
+
`You are a codebase-to-spec compose editor applying cross-area revisions to the composed spec.`,
|
|
351
|
+
...follow(`${cli} cts dispatch-context compose-editor`),
|
|
352
|
+
`It embeds the cross-area reviewer's revisions and tells you to apply them, then validate.`,
|
|
353
|
+
`End with a one-line confirmation.`,
|
|
354
|
+
].join('\n'),
|
|
355
|
+
{ agentType: 'cts-worker', label: `compose edit r${round}`, phase: 'Reconcile' },
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!crossAreaConverged) {
|
|
360
|
+
log(`cross-area review did not converge within ${crossAreaRoundsCap} rounds`)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
status: 'done',
|
|
365
|
+
areas: results,
|
|
366
|
+
converged_count: results.filter((r) => r.status === 'converged').length,
|
|
367
|
+
unconverged: results
|
|
368
|
+
.filter((r) => r.status === 'unconverged')
|
|
369
|
+
.map((r) => r.area_prefix),
|
|
370
|
+
failed: results.filter((r) => r.status === 'failed').map((r) => r.area_prefix),
|
|
371
|
+
total: areas.length,
|
|
372
|
+
cross_area_rounds: crossAreaRounds,
|
|
373
|
+
cross_area_converged: crossAreaConverged,
|
|
374
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read this package's own package.json (name + version). Works from both
|
|
3
|
+
* src/ (vitest) and dist/ (compiled) since the two trees sit at the same
|
|
4
|
+
* depth under packages/cli.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getOwnPackage(): {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=own-package.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* Read this package's own package.json (name + version). Works from both
|
|
6
|
+
* src/ (vitest) and dist/ (compiled) since the two trees sit at the same
|
|
7
|
+
* depth under packages/cli.
|
|
8
|
+
*/
|
|
9
|
+
export function getOwnPackage() {
|
|
10
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
11
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=own-package.js.map
|
|
@@ -7,6 +7,11 @@ export interface SelectedProject {
|
|
|
7
7
|
export interface SelectedTeam {
|
|
8
8
|
teamId: string;
|
|
9
9
|
tier: string;
|
|
10
|
+
/** Display name; present when selected via the shared selectTeam */
|
|
11
|
+
name?: string;
|
|
12
|
+
tierName?: string;
|
|
13
|
+
projectCount?: number;
|
|
14
|
+
projectLimit?: number;
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
17
|
* Prompt user to select a team from their available teams.
|
|
@@ -31,6 +31,10 @@ export async function selectTeam(client) {
|
|
|
31
31
|
return {
|
|
32
32
|
teamId: selectedTeamId,
|
|
33
33
|
tier: selectedTeam?.tier ?? "free",
|
|
34
|
+
name: selectedTeam?.name,
|
|
35
|
+
tierName: selectedTeam?.tierName,
|
|
36
|
+
projectCount: selectedTeam?.projectCount,
|
|
37
|
+
projectLimit: selectedTeam?.projectLimit,
|
|
34
38
|
};
|
|
35
39
|
}
|
|
36
40
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@popoverai/dotrequirements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Requirements tracking CLI, test harness, and MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"email": "support@dotrequirements.io"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
47
|
+
"node": ">=20"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@babel/parser": "^7.28.5",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@types/node": "^20",
|
|
72
72
|
"@types/uuid": "^11.0.0",
|
|
73
73
|
"typescript": "^5",
|
|
74
|
-
"vitest": "^4.
|
|
74
|
+
"vitest": "^4.1.8"
|
|
75
75
|
},
|
|
76
76
|
"files": [
|
|
77
77
|
"dist",
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# PreToolUse hook for Task(subagent_type=cts-worker) dispatches.
|
|
3
|
-
# Composes the worker's first-turn prompt by calling
|
|
4
|
-
# `dotrequirements cts dispatch-context <dispatch-id>` and emits the
|
|
5
|
-
# result via `updatedInput.prompt`.
|
|
6
|
-
#
|
|
7
|
-
# The orchestrator's convention: the original Task prompt MUST include
|
|
8
|
-
# the verbatim token `dispatch-id=<id>` so the hook can extract it.
|
|
9
|
-
#
|
|
10
|
-
# Bundled with the codebase-to-spec skill (CTSO-CLI-1).
|
|
11
|
-
#
|
|
12
|
-
# CLI resolution order:
|
|
13
|
-
# 1. DOTREQUIREMENTS_CLI env var (explicit override)
|
|
14
|
-
# 2. If running inside the dotrequirements monorepo itself
|
|
15
|
-
# ($CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js exists), use that
|
|
16
|
-
# local build. Lets dotrequirements developers use the orchestrator
|
|
17
|
-
# against any worktree without a global install.
|
|
18
|
-
# 3. `dotrequirements` from PATH (the canonical end-user setup).
|
|
19
|
-
|
|
20
|
-
INPUT=$(cat)
|
|
21
|
-
SUBAGENT_TYPE=$(echo "$INPUT" | jq -r '.tool_input.subagent_type // ""')
|
|
22
|
-
|
|
23
|
-
# Self-filter: only act on cts-worker dispatches.
|
|
24
|
-
if [[ "$SUBAGENT_TYPE" != "cts-worker" ]]; then
|
|
25
|
-
exit 0
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
ORIGINAL_PROMPT=$(echo "$INPUT" | jq -r '.tool_input.prompt // ""')
|
|
29
|
-
|
|
30
|
-
# Extract dispatch-id token from the original prompt.
|
|
31
|
-
DISPATCH_ID=$(echo "$ORIGINAL_PROMPT" | grep -oE 'dispatch-id=[a-zA-Z0-9_-]+' | head -1 | sed 's/dispatch-id=//')
|
|
32
|
-
|
|
33
|
-
if [[ -z "$DISPATCH_ID" ]]; then
|
|
34
|
-
# No dispatch-id in the prompt — let it through unmodified.
|
|
35
|
-
exit 0
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
# Resolve the CLI invocation (see header for order).
|
|
39
|
-
if [[ -n "${DOTREQUIREMENTS_CLI:-}" ]]; then
|
|
40
|
-
CLI="$DOTREQUIREMENTS_CLI"
|
|
41
|
-
elif [[ -n "${CLAUDE_PROJECT_DIR:-}" && -f "$CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js" ]]; then
|
|
42
|
-
CLI="node $CLAUDE_PROJECT_DIR/packages/cli/dist/cli.js"
|
|
43
|
-
else
|
|
44
|
-
CLI="dotrequirements"
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# Compose the dispatch context. CLI returns JSON like {"prompt": "..."}.
|
|
48
|
-
COMPOSED=$($CLI cts dispatch-context "$DISPATCH_ID" 2>/dev/null)
|
|
49
|
-
COMPOSE_STATUS=$?
|
|
50
|
-
|
|
51
|
-
if [[ $COMPOSE_STATUS -ne 0 || -z "$COMPOSED" ]]; then
|
|
52
|
-
# CLI call failed — let the dispatch through unmodified so the
|
|
53
|
-
# worker's failure is legible.
|
|
54
|
-
exit 0
|
|
55
|
-
fi
|
|
56
|
-
|
|
57
|
-
PROMPT=$(echo "$COMPOSED" | jq -r '.prompt // ""')
|
|
58
|
-
|
|
59
|
-
if [[ -z "$PROMPT" ]]; then
|
|
60
|
-
exit 0
|
|
61
|
-
fi
|
|
62
|
-
|
|
63
|
-
# Emit updatedInput so the worker subagent sees the composed prompt
|
|
64
|
-
# as its first-turn input. (Field name is `updatedInput`, not
|
|
65
|
-
# `modifiedInput` — verified by spike work; see working doc.)
|
|
66
|
-
jq -nc --arg p "$PROMPT" '{
|
|
67
|
-
hookSpecificOutput: {
|
|
68
|
-
hookEventName: "PreToolUse",
|
|
69
|
-
permissionDecision: "allow",
|
|
70
|
-
updatedInput: {
|
|
71
|
-
prompt: $p
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}'
|
|
75
|
-
|
|
76
|
-
exit 0
|