speckeeper 0.9.4 → 0.10.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 +122 -2
- package/cli-contract.yaml +739 -25
- package/dist/cli.js +1177 -1
- package/dist/cli.js.map +1 -1
- package/docs/cli-reference.md +345 -20
- package/package.json +13 -2
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
4
5
|
import { dirname, join, resolve, isAbsolute } from 'path';
|
|
@@ -7,9 +8,533 @@ import chalk4 from 'chalk';
|
|
|
7
8
|
import { parse } from 'yaml';
|
|
8
9
|
import 'crypto';
|
|
9
10
|
import { glob } from 'glob';
|
|
10
|
-
import { z } from 'zod';
|
|
11
11
|
import nodeSqlParser from 'node-sql-parser';
|
|
12
|
+
import { writeFile as writeFile$1 } from 'fs/promises';
|
|
12
13
|
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
16
|
+
var __esm = (fn, res) => function __init() {
|
|
17
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/generated/dsl/agents.ts
|
|
25
|
+
var specQualityReviewer, agentRegistry;
|
|
26
|
+
var init_agents = __esm({
|
|
27
|
+
"src/generated/dsl/agents.ts"() {
|
|
28
|
+
specQualityReviewer = {
|
|
29
|
+
id: "spec-quality-reviewer",
|
|
30
|
+
role_name: "Spec Quality Reviewer",
|
|
31
|
+
purpose: "Review design specifications for quality, verifiability, consistency, and traceability. Evaluates requirements from a PM/architect perspective, identifying ambiguity, untestable criteria, mixed abstraction levels, and missing traceability links.",
|
|
32
|
+
mode: "read-only",
|
|
33
|
+
dispatch_only: false,
|
|
34
|
+
can_read_artifacts: [
|
|
35
|
+
"spec-definitions",
|
|
36
|
+
"agent-review-output"
|
|
37
|
+
],
|
|
38
|
+
can_write_artifacts: [],
|
|
39
|
+
can_execute_tools: [
|
|
40
|
+
"speckeeper-cli"
|
|
41
|
+
],
|
|
42
|
+
can_invoke_agents: [],
|
|
43
|
+
can_return_handoffs: [
|
|
44
|
+
"requirement-audit-result",
|
|
45
|
+
"trace-link-result",
|
|
46
|
+
"impact-explain-result",
|
|
47
|
+
"acceptance-criteria-result"
|
|
48
|
+
],
|
|
49
|
+
responsibilities: [
|
|
50
|
+
"Evaluate requirement verifiability and measurability",
|
|
51
|
+
"Detect ambiguous or vague wording in specifications",
|
|
52
|
+
"Identify mixed requirement vs design content",
|
|
53
|
+
"Assess granularity consistency across spec types",
|
|
54
|
+
"Detect terminology inconsistencies across models",
|
|
55
|
+
"Estimate traceability link candidates with confidence scores",
|
|
56
|
+
"Explain impact analysis results for non-technical stakeholders",
|
|
57
|
+
"Propose acceptance criteria drafts for requirements and use cases"
|
|
58
|
+
],
|
|
59
|
+
constraints: [
|
|
60
|
+
"Do not modify spec definition files or assign spec IDs",
|
|
61
|
+
"Do not claim a spec is valid without citing specific evidence",
|
|
62
|
+
"Reference speckeeper lint results when findings overlap",
|
|
63
|
+
"Candidate trace links must always include confidence scores",
|
|
64
|
+
"explain output must use language suitable for PM and executive audiences",
|
|
65
|
+
"Output must conform to the task\u2019s result handoff schema: RequirementAuditResult, TraceLinkResult, ImpactExplainResult, or AcceptanceCriteriaResult",
|
|
66
|
+
"Findings must use speckeeper category vocabulary (verifiability, ambiguity, granularity, terminology, design-mixing, traceability, completeness)",
|
|
67
|
+
"Each finding must include a recommendation when severity is warning or above"
|
|
68
|
+
],
|
|
69
|
+
rules: [
|
|
70
|
+
{
|
|
71
|
+
"id": "R-SPEC-001",
|
|
72
|
+
"description": "Requirements without measurable acceptance criteria are flagged",
|
|
73
|
+
"severity": "mandatory"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "R-SPEC-002",
|
|
77
|
+
"description": 'Ambiguous terms (e.g. "appropriate", "fast", "user-friendly") without quantification are flagged',
|
|
78
|
+
"severity": "mandatory"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "R-SPEC-003",
|
|
82
|
+
"description": "Mixed requirement and design content in the same spec is flagged",
|
|
83
|
+
"severity": "recommended"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "R-SPEC-004",
|
|
87
|
+
"description": "Trace link candidates must include confidence score and rationale",
|
|
88
|
+
"severity": "mandatory"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "R-SPEC-005",
|
|
92
|
+
"description": "Explain output must be readable by non-technical stakeholders",
|
|
93
|
+
"severity": "mandatory"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "R-SPEC-006",
|
|
97
|
+
"description": "Terminology inconsistencies across spec models must be flagged with specific examples",
|
|
98
|
+
"severity": "mandatory"
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
escalation_criteria: [
|
|
102
|
+
{
|
|
103
|
+
"condition": "Config file cannot be loaded or design models are structurally invalid",
|
|
104
|
+
"action": "stop_and_report"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"condition": "No specs found in the loaded configuration",
|
|
108
|
+
"action": "stop_and_report"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"condition": "speckeeper-cli execution fails (lint or impact returns unexpected error)",
|
|
112
|
+
"action": "stop_and_report"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"condition": "Output schema validation fails on generated result",
|
|
116
|
+
"action": "stop_and_report"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"condition": "Stdin is absent or malformed for explain-impact-result task",
|
|
120
|
+
"action": "stop_and_report"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"condition": "Context exceeds LLM context window limit",
|
|
124
|
+
"action": "stop_and_report"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
agentRegistry = {
|
|
129
|
+
"spec-quality-reviewer": specQualityReviewer
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// src/generated/dsl/tasks.ts
|
|
135
|
+
var auditRequirementQuality, proposeTraceLinks, explainImpactResult, proposeAcceptanceCriteria, taskRegistry;
|
|
136
|
+
var init_tasks = __esm({
|
|
137
|
+
"src/generated/dsl/tasks.ts"() {
|
|
138
|
+
auditRequirementQuality = {
|
|
139
|
+
id: "audit-requirement-quality",
|
|
140
|
+
description: "Audit design specs for quality, verifiability, and consistency",
|
|
141
|
+
target_agent: "spec-quality-reviewer",
|
|
142
|
+
allowed_from_agents: [
|
|
143
|
+
"spec-quality-reviewer"
|
|
144
|
+
],
|
|
145
|
+
workflow: "requirement-audit",
|
|
146
|
+
invocation_handoff: "spec-review-request",
|
|
147
|
+
result_handoff: "requirement-audit-result",
|
|
148
|
+
input_artifacts: [
|
|
149
|
+
"spec-definitions"
|
|
150
|
+
],
|
|
151
|
+
responsibilities: [
|
|
152
|
+
"Evaluate all specs for verifiability and measurability",
|
|
153
|
+
"Detect ambiguous or vague wording",
|
|
154
|
+
"Identify mixed requirement vs design content",
|
|
155
|
+
"Check terminology consistency across models",
|
|
156
|
+
"Assess granularity appropriateness"
|
|
157
|
+
],
|
|
158
|
+
completion_criteria: [
|
|
159
|
+
"All target specs have been evaluated",
|
|
160
|
+
"Findings classified by severity and category",
|
|
161
|
+
"Each finding includes a recommendation",
|
|
162
|
+
"Output conforms to RequirementAuditResult schema"
|
|
163
|
+
],
|
|
164
|
+
optional: false
|
|
165
|
+
};
|
|
166
|
+
proposeTraceLinks = {
|
|
167
|
+
id: "propose-trace-links",
|
|
168
|
+
description: "Propose candidate traceability links between specs and external artifacts",
|
|
169
|
+
target_agent: "spec-quality-reviewer",
|
|
170
|
+
allowed_from_agents: [
|
|
171
|
+
"spec-quality-reviewer"
|
|
172
|
+
],
|
|
173
|
+
workflow: "trace-link-proposal",
|
|
174
|
+
invocation_handoff: "spec-review-request",
|
|
175
|
+
result_handoff: "trace-link-result",
|
|
176
|
+
input_artifacts: [
|
|
177
|
+
"spec-definitions"
|
|
178
|
+
],
|
|
179
|
+
responsibilities: [
|
|
180
|
+
"Analyze spec definitions and global scanner results",
|
|
181
|
+
"Identify candidate links between specs and external artifacts",
|
|
182
|
+
"Assign confidence scores to each candidate",
|
|
183
|
+
"Provide rationale for each proposed link"
|
|
184
|
+
],
|
|
185
|
+
completion_criteria: [
|
|
186
|
+
"All specs and scanner results have been cross-referenced",
|
|
187
|
+
"Each candidate link has a confidence score and rationale",
|
|
188
|
+
"Existing relations are not duplicated",
|
|
189
|
+
"Output conforms to TraceLinkResult schema"
|
|
190
|
+
],
|
|
191
|
+
optional: false
|
|
192
|
+
};
|
|
193
|
+
explainImpactResult = {
|
|
194
|
+
id: "explain-impact-result",
|
|
195
|
+
description: "Explain speckeeper impact command output in human-readable form",
|
|
196
|
+
target_agent: "spec-quality-reviewer",
|
|
197
|
+
allowed_from_agents: [
|
|
198
|
+
"spec-quality-reviewer"
|
|
199
|
+
],
|
|
200
|
+
workflow: "impact-explanation",
|
|
201
|
+
invocation_handoff: "spec-review-request",
|
|
202
|
+
result_handoff: "impact-explain-result",
|
|
203
|
+
input_artifacts: [
|
|
204
|
+
"spec-definitions"
|
|
205
|
+
],
|
|
206
|
+
responsibilities: [
|
|
207
|
+
"Summarize impact output for non-technical readers",
|
|
208
|
+
"Provide concrete next steps as recommendedActions",
|
|
209
|
+
"Explain spec relationships in plain language"
|
|
210
|
+
],
|
|
211
|
+
completion_criteria: [
|
|
212
|
+
"Explanation accurately summarizes the impact output",
|
|
213
|
+
"Action items are concrete and actionable",
|
|
214
|
+
"Technical terms explained for PM/executive audiences",
|
|
215
|
+
"Severity reflects urgency of the underlying change"
|
|
216
|
+
],
|
|
217
|
+
optional: false
|
|
218
|
+
};
|
|
219
|
+
proposeAcceptanceCriteria = {
|
|
220
|
+
id: "propose-acceptance-criteria",
|
|
221
|
+
description: "Propose acceptance criteria drafts for design specs",
|
|
222
|
+
target_agent: "spec-quality-reviewer",
|
|
223
|
+
allowed_from_agents: [
|
|
224
|
+
"spec-quality-reviewer"
|
|
225
|
+
],
|
|
226
|
+
workflow: "acceptance-proposal",
|
|
227
|
+
invocation_handoff: "spec-review-request",
|
|
228
|
+
result_handoff: "acceptance-criteria-result",
|
|
229
|
+
input_artifacts: [
|
|
230
|
+
"spec-definitions"
|
|
231
|
+
],
|
|
232
|
+
responsibilities: [
|
|
233
|
+
"Analyze target spec definitions and related specs",
|
|
234
|
+
"Propose testable acceptance criteria for each spec",
|
|
235
|
+
"Provide rationale for each proposed criterion"
|
|
236
|
+
],
|
|
237
|
+
completion_criteria: [
|
|
238
|
+
"Proposed criteria are specific and testable",
|
|
239
|
+
"Each proposal includes a rationale",
|
|
240
|
+
"Criteria do not duplicate existing acceptance conditions",
|
|
241
|
+
"Output conforms to AcceptanceCriteriaResult handoff schema"
|
|
242
|
+
],
|
|
243
|
+
optional: false
|
|
244
|
+
};
|
|
245
|
+
taskRegistry = {
|
|
246
|
+
"audit-requirement-quality": auditRequirementQuality,
|
|
247
|
+
"propose-trace-links": proposeTraceLinks,
|
|
248
|
+
"explain-impact-result": explainImpactResult,
|
|
249
|
+
"propose-acceptance-criteria": proposeAcceptanceCriteria
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
var SpecReviewRequestSchema, RequirementAuditResultSchema, TraceLinkResultSchema, ImpactExplainResultSchema, AcceptanceCriteriaResultSchema, handoffSchemas, handoffs;
|
|
254
|
+
var init_handoffs = __esm({
|
|
255
|
+
"src/generated/dsl/handoffs.ts"() {
|
|
256
|
+
SpecReviewRequestSchema = z.object({
|
|
257
|
+
task_id: z.string(),
|
|
258
|
+
context: z.string()
|
|
259
|
+
});
|
|
260
|
+
RequirementAuditResultSchema = z.object({
|
|
261
|
+
summary: z.string(),
|
|
262
|
+
riskLevel: z.enum(["low", "medium", "high", "critical"]),
|
|
263
|
+
findings: z.array(z.object({
|
|
264
|
+
id: z.string().optional(),
|
|
265
|
+
severity: z.enum(["info", "warning", "error", "critical"]),
|
|
266
|
+
category: z.string(),
|
|
267
|
+
target: z.string().optional(),
|
|
268
|
+
location: z.string().optional(),
|
|
269
|
+
message: z.string(),
|
|
270
|
+
recommendation: z.string().optional(),
|
|
271
|
+
confidence: z.number().optional(),
|
|
272
|
+
evidence: z.array(z.object({
|
|
273
|
+
kind: z.enum(["file", "command", "schema", "diff", "stdout", "stderr", "text"]).optional(),
|
|
274
|
+
target: z.string().optional(),
|
|
275
|
+
location: z.string().optional(),
|
|
276
|
+
excerpt: z.string().optional()
|
|
277
|
+
})).optional(),
|
|
278
|
+
details: z.record(z.string(), z.unknown()).optional()
|
|
279
|
+
})),
|
|
280
|
+
recommendedActions: z.array(z.object({
|
|
281
|
+
kind: z.enum(["run_command", "edit_file", "review", "confirm", "block", "ignore"]),
|
|
282
|
+
title: z.string(),
|
|
283
|
+
command: z.string().optional(),
|
|
284
|
+
target: z.string().optional(),
|
|
285
|
+
rationale: z.string().optional()
|
|
286
|
+
})).optional(),
|
|
287
|
+
metadata: z.object({
|
|
288
|
+
tool: z.string().optional(),
|
|
289
|
+
command: z.string().optional(),
|
|
290
|
+
version: z.string().optional(),
|
|
291
|
+
generatedAt: z.string().optional(),
|
|
292
|
+
adapter: z.string().optional(),
|
|
293
|
+
model: z.string().optional()
|
|
294
|
+
}).optional()
|
|
295
|
+
});
|
|
296
|
+
TraceLinkResultSchema = z.object({
|
|
297
|
+
summary: z.string(),
|
|
298
|
+
riskLevel: z.enum(["low", "medium", "high", "critical"]),
|
|
299
|
+
findings: z.array(z.object({
|
|
300
|
+
severity: z.enum(["info", "warning", "error", "critical"]),
|
|
301
|
+
category: z.string(),
|
|
302
|
+
message: z.string(),
|
|
303
|
+
recommendation: z.string().optional()
|
|
304
|
+
})),
|
|
305
|
+
candidateLinks: z.array(z.object({
|
|
306
|
+
from: z.string(),
|
|
307
|
+
relation: z.string(),
|
|
308
|
+
to: z.string(),
|
|
309
|
+
confidence: z.number(),
|
|
310
|
+
reason: z.string()
|
|
311
|
+
})),
|
|
312
|
+
recommendedActions: z.array(z.object({
|
|
313
|
+
kind: z.enum(["run_command", "edit_file", "review", "confirm", "block", "ignore"]),
|
|
314
|
+
title: z.string(),
|
|
315
|
+
command: z.string().optional(),
|
|
316
|
+
target: z.string().optional(),
|
|
317
|
+
rationale: z.string().optional()
|
|
318
|
+
})).optional(),
|
|
319
|
+
metadata: z.object({
|
|
320
|
+
tool: z.string().optional(),
|
|
321
|
+
command: z.string().optional(),
|
|
322
|
+
version: z.string().optional(),
|
|
323
|
+
generatedAt: z.string().optional(),
|
|
324
|
+
adapter: z.string().optional(),
|
|
325
|
+
model: z.string().optional()
|
|
326
|
+
}).optional()
|
|
327
|
+
});
|
|
328
|
+
ImpactExplainResultSchema = z.object({
|
|
329
|
+
summary: z.string(),
|
|
330
|
+
riskLevel: z.enum(["low", "medium", "high", "critical"]),
|
|
331
|
+
explanation: z.string(),
|
|
332
|
+
findings: z.array(z.object({
|
|
333
|
+
severity: z.enum(["info", "warning", "error", "critical"]),
|
|
334
|
+
category: z.string(),
|
|
335
|
+
message: z.string()
|
|
336
|
+
})).optional(),
|
|
337
|
+
affectedArtifacts: z.array(z.object({
|
|
338
|
+
type: z.enum(["spec", "api", "ddl", "test", "annotation"]),
|
|
339
|
+
id: z.string(),
|
|
340
|
+
summary: z.string()
|
|
341
|
+
})).optional(),
|
|
342
|
+
testConsiderations: z.array(z.string()).optional(),
|
|
343
|
+
releaseRisk: z.enum(["low", "medium", "high"]).optional(),
|
|
344
|
+
recommendedActions: z.array(z.object({
|
|
345
|
+
kind: z.enum(["run_command", "edit_file", "review", "confirm", "block", "ignore"]),
|
|
346
|
+
title: z.string(),
|
|
347
|
+
rationale: z.string().optional()
|
|
348
|
+
})).optional(),
|
|
349
|
+
sourceCommand: z.string().optional(),
|
|
350
|
+
metadata: z.object({
|
|
351
|
+
tool: z.string().optional(),
|
|
352
|
+
command: z.string().optional(),
|
|
353
|
+
version: z.string().optional(),
|
|
354
|
+
generatedAt: z.string().optional(),
|
|
355
|
+
adapter: z.string().optional(),
|
|
356
|
+
model: z.string().optional()
|
|
357
|
+
}).optional()
|
|
358
|
+
});
|
|
359
|
+
AcceptanceCriteriaResultSchema = z.object({
|
|
360
|
+
summary: z.string(),
|
|
361
|
+
riskLevel: z.enum(["low", "medium", "high", "critical"]),
|
|
362
|
+
findings: z.array(z.object({
|
|
363
|
+
severity: z.enum(["info", "warning", "error", "critical"]),
|
|
364
|
+
category: z.string(),
|
|
365
|
+
message: z.string(),
|
|
366
|
+
recommendation: z.string().optional()
|
|
367
|
+
})),
|
|
368
|
+
criteriaProposals: z.array(z.object({
|
|
369
|
+
specId: z.string(),
|
|
370
|
+
criteria: z.array(z.string()),
|
|
371
|
+
rationale: z.string()
|
|
372
|
+
})),
|
|
373
|
+
recommendedActions: z.array(z.object({
|
|
374
|
+
kind: z.enum(["run_command", "edit_file", "review", "confirm", "block", "ignore"]),
|
|
375
|
+
title: z.string(),
|
|
376
|
+
command: z.string().optional(),
|
|
377
|
+
target: z.string().optional(),
|
|
378
|
+
rationale: z.string().optional()
|
|
379
|
+
})).optional(),
|
|
380
|
+
metadata: z.object({
|
|
381
|
+
tool: z.string().optional(),
|
|
382
|
+
command: z.string().optional(),
|
|
383
|
+
version: z.string().optional(),
|
|
384
|
+
generatedAt: z.string().optional(),
|
|
385
|
+
adapter: z.string().optional(),
|
|
386
|
+
model: z.string().optional()
|
|
387
|
+
}).optional()
|
|
388
|
+
});
|
|
389
|
+
handoffSchemas = {
|
|
390
|
+
"spec-review-request": SpecReviewRequestSchema,
|
|
391
|
+
"requirement-audit-result": RequirementAuditResultSchema,
|
|
392
|
+
"trace-link-result": TraceLinkResultSchema,
|
|
393
|
+
"impact-explain-result": ImpactExplainResultSchema,
|
|
394
|
+
"acceptance-criteria-result": AcceptanceCriteriaResultSchema
|
|
395
|
+
};
|
|
396
|
+
handoffs = {
|
|
397
|
+
specReviewRequest(payload) {
|
|
398
|
+
return {
|
|
399
|
+
type: "spec-review-request",
|
|
400
|
+
version: 1,
|
|
401
|
+
payload: SpecReviewRequestSchema.parse(payload)
|
|
402
|
+
};
|
|
403
|
+
},
|
|
404
|
+
requirementAuditResult(payload) {
|
|
405
|
+
return {
|
|
406
|
+
type: "requirement-audit-result",
|
|
407
|
+
version: 1,
|
|
408
|
+
payload: RequirementAuditResultSchema.parse(payload)
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
traceLinkResult(payload) {
|
|
412
|
+
return {
|
|
413
|
+
type: "trace-link-result",
|
|
414
|
+
version: 1,
|
|
415
|
+
payload: TraceLinkResultSchema.parse(payload)
|
|
416
|
+
};
|
|
417
|
+
},
|
|
418
|
+
impactExplainResult(payload) {
|
|
419
|
+
return {
|
|
420
|
+
type: "impact-explain-result",
|
|
421
|
+
version: 1,
|
|
422
|
+
payload: ImpactExplainResultSchema.parse(payload)
|
|
423
|
+
};
|
|
424
|
+
},
|
|
425
|
+
acceptanceCriteriaResult(payload) {
|
|
426
|
+
return {
|
|
427
|
+
type: "acceptance-criteria-result",
|
|
428
|
+
version: 1,
|
|
429
|
+
payload: AcceptanceCriteriaResultSchema.parse(payload)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// src/generated/dsl/workflows.ts
|
|
437
|
+
var requirementAudit, traceLinkProposal, impactExplanation, acceptanceProposal, workflowRegistry;
|
|
438
|
+
var init_workflows = __esm({
|
|
439
|
+
"src/generated/dsl/workflows.ts"() {
|
|
440
|
+
requirementAudit = {
|
|
441
|
+
id: "requirement-audit",
|
|
442
|
+
description: "Audit requirement specs for quality, verifiability, and consistency",
|
|
443
|
+
trigger: "cli-command",
|
|
444
|
+
entry_conditions: [
|
|
445
|
+
"Design models are loaded and specs are available"
|
|
446
|
+
],
|
|
447
|
+
steps: [
|
|
448
|
+
{
|
|
449
|
+
type: "delegate",
|
|
450
|
+
task: "audit-requirement-quality",
|
|
451
|
+
from_agent: "spec-quality-reviewer",
|
|
452
|
+
description: "",
|
|
453
|
+
optional: false,
|
|
454
|
+
max_retries: 0
|
|
455
|
+
}
|
|
456
|
+
]
|
|
457
|
+
};
|
|
458
|
+
traceLinkProposal = {
|
|
459
|
+
id: "trace-link-proposal",
|
|
460
|
+
description: "Propose candidate traceability links between specs and external artifacts",
|
|
461
|
+
trigger: "cli-command",
|
|
462
|
+
entry_conditions: [
|
|
463
|
+
"Design models and global scanner results are available"
|
|
464
|
+
],
|
|
465
|
+
steps: [
|
|
466
|
+
{
|
|
467
|
+
type: "delegate",
|
|
468
|
+
task: "propose-trace-links",
|
|
469
|
+
from_agent: "spec-quality-reviewer",
|
|
470
|
+
description: "",
|
|
471
|
+
optional: false,
|
|
472
|
+
max_retries: 0
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
impactExplanation = {
|
|
477
|
+
id: "impact-explanation",
|
|
478
|
+
description: "Explain impact analysis results in human-readable form",
|
|
479
|
+
trigger: "cli-command",
|
|
480
|
+
entry_conditions: [
|
|
481
|
+
"JSON output from speckeeper impact is available on stdin"
|
|
482
|
+
],
|
|
483
|
+
steps: [
|
|
484
|
+
{
|
|
485
|
+
type: "delegate",
|
|
486
|
+
task: "explain-impact-result",
|
|
487
|
+
from_agent: "spec-quality-reviewer",
|
|
488
|
+
description: "",
|
|
489
|
+
optional: false,
|
|
490
|
+
max_retries: 0
|
|
491
|
+
}
|
|
492
|
+
]
|
|
493
|
+
};
|
|
494
|
+
acceptanceProposal = {
|
|
495
|
+
id: "acceptance-proposal",
|
|
496
|
+
description: "Propose acceptance criteria drafts for specs",
|
|
497
|
+
trigger: "cli-command",
|
|
498
|
+
entry_conditions: [
|
|
499
|
+
"Target specs are loaded and available"
|
|
500
|
+
],
|
|
501
|
+
steps: [
|
|
502
|
+
{
|
|
503
|
+
type: "delegate",
|
|
504
|
+
task: "propose-acceptance-criteria",
|
|
505
|
+
from_agent: "spec-quality-reviewer",
|
|
506
|
+
description: "",
|
|
507
|
+
optional: false,
|
|
508
|
+
max_retries: 0
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
};
|
|
512
|
+
workflowRegistry = {
|
|
513
|
+
"requirement-audit": requirementAudit,
|
|
514
|
+
"trace-link-proposal": traceLinkProposal,
|
|
515
|
+
"impact-explanation": impactExplanation,
|
|
516
|
+
"acceptance-proposal": acceptanceProposal
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// src/generated/dsl/index.ts
|
|
522
|
+
var dsl_exports = {};
|
|
523
|
+
__export(dsl_exports, {
|
|
524
|
+
agentRegistry: () => agentRegistry,
|
|
525
|
+
handoffSchemas: () => handoffSchemas,
|
|
526
|
+
handoffs: () => handoffs,
|
|
527
|
+
taskRegistry: () => taskRegistry,
|
|
528
|
+
workflowRegistry: () => workflowRegistry
|
|
529
|
+
});
|
|
530
|
+
var init_dsl = __esm({
|
|
531
|
+
"src/generated/dsl/index.ts"() {
|
|
532
|
+
init_agents();
|
|
533
|
+
init_tasks();
|
|
534
|
+
init_handoffs();
|
|
535
|
+
init_workflows();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
13
538
|
var defaultConfig = {
|
|
14
539
|
srcDir: "src",
|
|
15
540
|
designDir: "design",
|
|
@@ -2252,6 +2777,605 @@ function printDiagnostics(diagnostics) {
|
|
|
2252
2777
|
}
|
|
2253
2778
|
}
|
|
2254
2779
|
|
|
2780
|
+
// src/agents/context-builder.ts
|
|
2781
|
+
function extractSpecSummaries(models, specs) {
|
|
2782
|
+
const summaries = [];
|
|
2783
|
+
for (const model of models) {
|
|
2784
|
+
const modelSpecs = getSpecsFromConfig(
|
|
2785
|
+
specs,
|
|
2786
|
+
model.id
|
|
2787
|
+
);
|
|
2788
|
+
for (const spec of modelSpecs) {
|
|
2789
|
+
const s = spec;
|
|
2790
|
+
summaries.push({
|
|
2791
|
+
id: s.id,
|
|
2792
|
+
name: s.name,
|
|
2793
|
+
description: s.description,
|
|
2794
|
+
modelType: model.name ?? model.id,
|
|
2795
|
+
relations: s.relations
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return summaries;
|
|
2800
|
+
}
|
|
2801
|
+
function formatSpecForContext(spec) {
|
|
2802
|
+
const lines = [];
|
|
2803
|
+
lines.push(`### ${spec.id} (${spec.modelType})`);
|
|
2804
|
+
if (spec.name) lines.push(`Name: ${spec.name}`);
|
|
2805
|
+
if (spec.description) lines.push(`Description: ${spec.description}`);
|
|
2806
|
+
if (spec.relations && spec.relations.length > 0) {
|
|
2807
|
+
lines.push("Relations:");
|
|
2808
|
+
for (const rel of spec.relations) {
|
|
2809
|
+
lines.push(` - ${rel.type} \u2192 ${rel.target}`);
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return lines.join("\n");
|
|
2813
|
+
}
|
|
2814
|
+
function runLintForContext(models, specs) {
|
|
2815
|
+
const issues = [];
|
|
2816
|
+
for (const model of models) {
|
|
2817
|
+
const modelSpecs = getSpecsFromConfig(
|
|
2818
|
+
specs,
|
|
2819
|
+
model.id
|
|
2820
|
+
);
|
|
2821
|
+
if (modelSpecs.length === 0) continue;
|
|
2822
|
+
try {
|
|
2823
|
+
const lintResults = model.lintAll(modelSpecs);
|
|
2824
|
+
for (const result of lintResults) {
|
|
2825
|
+
issues.push({
|
|
2826
|
+
rule: result.ruleId,
|
|
2827
|
+
severity: result.severity,
|
|
2828
|
+
message: result.message,
|
|
2829
|
+
specId: result.specId,
|
|
2830
|
+
modelType: model.name
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
} catch {
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
return issues;
|
|
2837
|
+
}
|
|
2838
|
+
async function buildAuditRequirementsContext(configPath) {
|
|
2839
|
+
const config = await loadConfig(configPath);
|
|
2840
|
+
const models = config.models || [];
|
|
2841
|
+
const specs = config.specs;
|
|
2842
|
+
const specSummaries = extractSpecSummaries(models, specs);
|
|
2843
|
+
const lintIssues = runLintForContext(models, specs);
|
|
2844
|
+
const sections = [];
|
|
2845
|
+
sections.push("# Requirement Quality Audit Request");
|
|
2846
|
+
sections.push(
|
|
2847
|
+
`## Project Configuration
|
|
2848
|
+
|
|
2849
|
+
- Design directory: ${config.designDir || "design"}
|
|
2850
|
+
- Models: ${models.length}
|
|
2851
|
+
- Total specs: ${specSummaries.length}`
|
|
2852
|
+
);
|
|
2853
|
+
if (specSummaries.length > 0) {
|
|
2854
|
+
sections.push("## Spec Definitions\n");
|
|
2855
|
+
for (const spec of specSummaries) {
|
|
2856
|
+
sections.push(formatSpecForContext(spec));
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
if (lintIssues.length > 0) {
|
|
2860
|
+
sections.push("## Existing Lint Issues\n");
|
|
2861
|
+
for (const issue of lintIssues) {
|
|
2862
|
+
const prefix = issue.specId ? `[${issue.specId}] ` : "";
|
|
2863
|
+
sections.push(`- [${issue.severity}] ${prefix}${issue.message} (rule: ${issue.rule})`);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
return sections.join("\n\n");
|
|
2867
|
+
}
|
|
2868
|
+
async function buildProposeTraceLinksContext(configPath) {
|
|
2869
|
+
const config = await loadConfig(configPath);
|
|
2870
|
+
const models = config.models || [];
|
|
2871
|
+
const specs = config.specs;
|
|
2872
|
+
const specSummaries = extractSpecSummaries(models, specs);
|
|
2873
|
+
const sections = [];
|
|
2874
|
+
sections.push("# Trace Link Proposal Request");
|
|
2875
|
+
sections.push(
|
|
2876
|
+
`## Project Configuration
|
|
2877
|
+
|
|
2878
|
+
- Design directory: ${config.designDir || "design"}
|
|
2879
|
+
- Models: ${models.length}
|
|
2880
|
+
- Total specs: ${specSummaries.length}`
|
|
2881
|
+
);
|
|
2882
|
+
if (specSummaries.length > 0) {
|
|
2883
|
+
sections.push("## Spec Definitions\n");
|
|
2884
|
+
for (const spec of specSummaries) {
|
|
2885
|
+
sections.push(formatSpecForContext(spec));
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
const existingRelations = [];
|
|
2889
|
+
for (const spec of specSummaries) {
|
|
2890
|
+
if (spec.relations) {
|
|
2891
|
+
for (const rel of spec.relations) {
|
|
2892
|
+
existingRelations.push(`${spec.id} --[${rel.type}]--> ${rel.target}`);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
if (existingRelations.length > 0) {
|
|
2897
|
+
sections.push("## Existing Relations\n\n" + existingRelations.join("\n"));
|
|
2898
|
+
}
|
|
2899
|
+
const sources = config.sources ?? [];
|
|
2900
|
+
if (sources.length > 0) {
|
|
2901
|
+
sections.push("## Configured External Sources\n");
|
|
2902
|
+
for (const source of sources) {
|
|
2903
|
+
sections.push(`- Type: ${source.type}, Paths: ${source.paths?.join(", ") ?? "N/A"}`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
return sections.join("\n\n");
|
|
2907
|
+
}
|
|
2908
|
+
function buildExplainImpactContext(stdinJson, sourceCommand) {
|
|
2909
|
+
const sections = [];
|
|
2910
|
+
sections.push("# Impact Explanation Request");
|
|
2911
|
+
sections.push(`## Impact Output
|
|
2912
|
+
|
|
2913
|
+
\`\`\`json
|
|
2914
|
+
${stdinJson}
|
|
2915
|
+
\`\`\``);
|
|
2916
|
+
return sections.join("\n\n");
|
|
2917
|
+
}
|
|
2918
|
+
async function buildProposeAcceptanceCriteriaContext(configPath, targetSpecIds) {
|
|
2919
|
+
const config = await loadConfig(configPath);
|
|
2920
|
+
const models = config.models || [];
|
|
2921
|
+
const specs = config.specs;
|
|
2922
|
+
const allSummaries = extractSpecSummaries(models, specs);
|
|
2923
|
+
const lintIssues = runLintForContext(models, specs);
|
|
2924
|
+
const targetSpecs = targetSpecIds && targetSpecIds.length > 0 ? allSummaries.filter((s) => targetSpecIds.includes(s.id)) : allSummaries;
|
|
2925
|
+
const sections = [];
|
|
2926
|
+
sections.push("# Acceptance Criteria Proposal Request");
|
|
2927
|
+
sections.push(
|
|
2928
|
+
`## Project Configuration
|
|
2929
|
+
|
|
2930
|
+
- Design directory: ${config.designDir || "design"}
|
|
2931
|
+
- Target specs: ${targetSpecs.length}` + (targetSpecIds ? ` (filtered from ${allSummaries.length} total)` : "")
|
|
2932
|
+
);
|
|
2933
|
+
if (targetSpecs.length > 0) {
|
|
2934
|
+
sections.push("## Target Specs\n");
|
|
2935
|
+
for (const spec of targetSpecs) {
|
|
2936
|
+
sections.push(formatSpecForContext(spec));
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
if (targetSpecIds && targetSpecIds.length > 0) {
|
|
2940
|
+
const relatedIds = /* @__PURE__ */ new Set();
|
|
2941
|
+
for (const spec of targetSpecs) {
|
|
2942
|
+
if (spec.relations) {
|
|
2943
|
+
for (const rel of spec.relations) {
|
|
2944
|
+
if (!targetSpecIds.includes(rel.target)) {
|
|
2945
|
+
relatedIds.add(rel.target);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
const relatedSpecs = allSummaries.filter((s) => relatedIds.has(s.id));
|
|
2951
|
+
if (relatedSpecs.length > 0) {
|
|
2952
|
+
sections.push("## Related Specs (for context)\n");
|
|
2953
|
+
for (const spec of relatedSpecs) {
|
|
2954
|
+
sections.push(formatSpecForContext(spec));
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
const relevantLintIssues = targetSpecIds ? lintIssues.filter((i) => i.specId && targetSpecIds.includes(i.specId)) : lintIssues;
|
|
2959
|
+
if (relevantLintIssues.length > 0) {
|
|
2960
|
+
sections.push("## Lint Issues (relevant)\n");
|
|
2961
|
+
for (const issue of relevantLintIssues) {
|
|
2962
|
+
sections.push(`- [${issue.severity}] [${issue.specId}] ${issue.message}`);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
return sections.join("\n\n");
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
// src/agents/orchestrator.ts
|
|
2969
|
+
var EXIT_RUNTIME_MISSING = 11;
|
|
2970
|
+
var EXIT_ADAPTER_ERROR = 12;
|
|
2971
|
+
async function createAdapter(runtimePkg, name, config) {
|
|
2972
|
+
switch (name) {
|
|
2973
|
+
case "mock": {
|
|
2974
|
+
const mod = await import(`${runtimePkg}/adapters/mock`);
|
|
2975
|
+
return new mod.MockAdapter();
|
|
2976
|
+
}
|
|
2977
|
+
case "cursor": {
|
|
2978
|
+
const mod = await import(`${runtimePkg}/adapters/cursor-sdk`);
|
|
2979
|
+
const apiKey = process.env.CURSOR_API_KEY;
|
|
2980
|
+
if (!apiKey) {
|
|
2981
|
+
throw new Error(
|
|
2982
|
+
"CURSOR_API_KEY environment variable is required for the cursor adapter.\nGet your key from: https://cursor.com/dashboard/integrations"
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
return mod.CursorSdkAdapter.create({ apiKey, model: config.model ?? "claude-sonnet-4" });
|
|
2986
|
+
}
|
|
2987
|
+
case "claude": {
|
|
2988
|
+
const mod = await import(`${runtimePkg}/adapters/claude-agent-sdk`);
|
|
2989
|
+
return new mod.ClaudeAgentSdkAdapter({
|
|
2990
|
+
model: config.model,
|
|
2991
|
+
tools: ["Read", "Glob", "Grep"],
|
|
2992
|
+
permissionMode: "bypassPermissions"
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
case "openai": {
|
|
2996
|
+
const mod = await import(`${runtimePkg}/adapters/openai-agents-sdk`);
|
|
2997
|
+
return new mod.OpenAIAgentsSdkAdapter({
|
|
2998
|
+
model: config.model ?? "o3-mini",
|
|
2999
|
+
maxTurns: 1
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
case "gemini": {
|
|
3003
|
+
const mod = await import(`${runtimePkg}/adapters/gemini-sdk`);
|
|
3004
|
+
return new mod.GeminiSdkAdapter({
|
|
3005
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
3006
|
+
model: config.model ?? "gemini-2.5-pro",
|
|
3007
|
+
temperature: config.temperature
|
|
3008
|
+
});
|
|
3009
|
+
}
|
|
3010
|
+
default:
|
|
3011
|
+
throw new Error(
|
|
3012
|
+
`Unsupported adapter: "${name}". Available: mock, cursor, claude, openai, gemini.`
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
async function runAgentTask(userRequest, taskId, auditConfig, options) {
|
|
3017
|
+
if (options.dryRun) {
|
|
3018
|
+
return {
|
|
3019
|
+
taskId,
|
|
3020
|
+
data: null,
|
|
3021
|
+
raw: "",
|
|
3022
|
+
prompt: userRequest,
|
|
3023
|
+
dryRun: true,
|
|
3024
|
+
status: "success",
|
|
3025
|
+
followUpsUsed: 0,
|
|
3026
|
+
retriesUsed: 0
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
const RUNTIME_PKG = ["agent-contracts", "runtime"].join("-");
|
|
3030
|
+
let runTask;
|
|
3031
|
+
let agentRegistry2, taskRegistry2, handoffSchemas2;
|
|
3032
|
+
try {
|
|
3033
|
+
const runtime = await import(RUNTIME_PKG);
|
|
3034
|
+
runTask = runtime.runTask;
|
|
3035
|
+
} catch {
|
|
3036
|
+
throw Object.assign(
|
|
3037
|
+
new Error(
|
|
3038
|
+
"agent-contracts-runtime is not installed. Install it to use this command, or use --dry-run to inspect the prompt.\n npm install agent-contracts-runtime"
|
|
3039
|
+
),
|
|
3040
|
+
{ exitCode: EXIT_RUNTIME_MISSING }
|
|
3041
|
+
);
|
|
3042
|
+
}
|
|
3043
|
+
try {
|
|
3044
|
+
const dsl = await Promise.resolve().then(() => (init_dsl(), dsl_exports));
|
|
3045
|
+
agentRegistry2 = dsl.agentRegistry;
|
|
3046
|
+
taskRegistry2 = dsl.taskRegistry;
|
|
3047
|
+
handoffSchemas2 = dsl.handoffSchemas;
|
|
3048
|
+
} catch {
|
|
3049
|
+
agentRegistry2 = {};
|
|
3050
|
+
taskRegistry2 = {};
|
|
3051
|
+
handoffSchemas2 = {};
|
|
3052
|
+
}
|
|
3053
|
+
const adapterName = auditConfig.adapter ?? "mock";
|
|
3054
|
+
let adapter;
|
|
3055
|
+
try {
|
|
3056
|
+
adapter = await createAdapter(RUNTIME_PKG, adapterName, auditConfig);
|
|
3057
|
+
} catch (err) {
|
|
3058
|
+
throw Object.assign(err, { exitCode: EXIT_ADAPTER_ERROR });
|
|
3059
|
+
}
|
|
3060
|
+
const result = await runTask(adapter, taskId, {
|
|
3061
|
+
user_request: userRequest
|
|
3062
|
+
}, {
|
|
3063
|
+
maxFollowUps: 3,
|
|
3064
|
+
maxRetries: 1,
|
|
3065
|
+
agentRegistry: agentRegistry2,
|
|
3066
|
+
taskRegistry: taskRegistry2,
|
|
3067
|
+
handoffSchemas: handoffSchemas2
|
|
3068
|
+
});
|
|
3069
|
+
const outcome = result.outcome;
|
|
3070
|
+
return {
|
|
3071
|
+
taskId,
|
|
3072
|
+
data: outcome.status === "success" ? outcome.data : null,
|
|
3073
|
+
raw: outcome.raw ?? "",
|
|
3074
|
+
prompt: userRequest,
|
|
3075
|
+
dryRun: false,
|
|
3076
|
+
status: outcome.status,
|
|
3077
|
+
errorMessage: outcome.status === "error" ? outcome.message : outcome.status === "escalation" ? outcome.reason : outcome.status === "validation_error" ? outcome.errors?.message : void 0,
|
|
3078
|
+
followUpsUsed: result.follow_ups_used,
|
|
3079
|
+
retriesUsed: result.retries_used
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
function computeExitCode(result, options) {
|
|
3083
|
+
if (result.dryRun) return 0;
|
|
3084
|
+
if (result.status !== "success" || !result.data) return 1;
|
|
3085
|
+
const failOn = options.failOn ?? "error";
|
|
3086
|
+
const severityOrder = ["info", "warning", "error", "critical"];
|
|
3087
|
+
const threshold = severityOrder.indexOf(failOn);
|
|
3088
|
+
const hasBlocking = result.data.findings.some(
|
|
3089
|
+
(f) => severityOrder.indexOf(f.severity) >= threshold
|
|
3090
|
+
);
|
|
3091
|
+
return hasBlocking ? 10 : 0;
|
|
3092
|
+
}
|
|
3093
|
+
function formatResultText(result) {
|
|
3094
|
+
if (result.dryRun) {
|
|
3095
|
+
return result.prompt;
|
|
3096
|
+
}
|
|
3097
|
+
if (result.status !== "success" || !result.data) {
|
|
3098
|
+
return result.errorMessage ?? `Task failed with status: ${result.status}`;
|
|
3099
|
+
}
|
|
3100
|
+
const data = result.data;
|
|
3101
|
+
const lines = [];
|
|
3102
|
+
lines.push(`Risk Level: ${data.riskLevel.toUpperCase()}`);
|
|
3103
|
+
lines.push(`Summary: ${data.summary}`);
|
|
3104
|
+
lines.push("");
|
|
3105
|
+
if (data.findings.length > 0) {
|
|
3106
|
+
lines.push(`Findings (${data.findings.length}):`);
|
|
3107
|
+
lines.push("");
|
|
3108
|
+
for (const finding of data.findings) {
|
|
3109
|
+
const prefix = severityIcon(finding.severity);
|
|
3110
|
+
lines.push(` ${prefix} [${finding.category}] ${finding.message}`);
|
|
3111
|
+
if (finding.location) {
|
|
3112
|
+
lines.push(` Location: ${finding.location}`);
|
|
3113
|
+
}
|
|
3114
|
+
if (finding.recommendation) {
|
|
3115
|
+
lines.push(` Recommendation: ${finding.recommendation}`);
|
|
3116
|
+
}
|
|
3117
|
+
lines.push("");
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
if (data.recommendedActions && data.recommendedActions.length > 0) {
|
|
3121
|
+
lines.push("Recommended Actions:");
|
|
3122
|
+
for (const action of data.recommendedActions) {
|
|
3123
|
+
lines.push(` - [${action.kind}] ${action.title}`);
|
|
3124
|
+
if (action.command) {
|
|
3125
|
+
lines.push(` $ ${action.command}`);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
return lines.join("\n");
|
|
3130
|
+
}
|
|
3131
|
+
function formatResultJson(result) {
|
|
3132
|
+
if (result.dryRun) {
|
|
3133
|
+
return JSON.stringify({ dryRun: true, prompt: result.prompt }, null, 2);
|
|
3134
|
+
}
|
|
3135
|
+
if (!result.data) {
|
|
3136
|
+
return JSON.stringify({ error: result.errorMessage, status: result.status }, null, 2);
|
|
3137
|
+
}
|
|
3138
|
+
return JSON.stringify(result.data, null, 2);
|
|
3139
|
+
}
|
|
3140
|
+
function formatResultYaml(result) {
|
|
3141
|
+
if (result.dryRun) {
|
|
3142
|
+
return `dryRun: true
|
|
3143
|
+
prompt: |
|
|
3144
|
+
${indent(result.prompt)}`;
|
|
3145
|
+
}
|
|
3146
|
+
if (!result.data) {
|
|
3147
|
+
return `error: ${JSON.stringify(result.errorMessage)}
|
|
3148
|
+
status: ${result.status}`;
|
|
3149
|
+
}
|
|
3150
|
+
return toSimpleYaml(result.data);
|
|
3151
|
+
}
|
|
3152
|
+
function formatResult(result, format) {
|
|
3153
|
+
switch (format) {
|
|
3154
|
+
case "json":
|
|
3155
|
+
return formatResultJson(result);
|
|
3156
|
+
case "yaml":
|
|
3157
|
+
return formatResultYaml(result);
|
|
3158
|
+
case "text":
|
|
3159
|
+
return formatResultText(result);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
async function writeOutput(content, outputPath) {
|
|
3163
|
+
if (outputPath) {
|
|
3164
|
+
await writeFile$1(outputPath, content + "\n", "utf-8");
|
|
3165
|
+
} else {
|
|
3166
|
+
process.stdout.write(content + "\n");
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
function severityIcon(severity) {
|
|
3170
|
+
switch (severity) {
|
|
3171
|
+
case "critical":
|
|
3172
|
+
return "\u2716";
|
|
3173
|
+
case "error":
|
|
3174
|
+
return "\u2716";
|
|
3175
|
+
case "warning":
|
|
3176
|
+
return "\u26A0";
|
|
3177
|
+
case "info":
|
|
3178
|
+
return "\u2139";
|
|
3179
|
+
}
|
|
3180
|
+
}
|
|
3181
|
+
function indent(text, spaces = 2) {
|
|
3182
|
+
const pad = " ".repeat(spaces);
|
|
3183
|
+
return text.split("\n").map((l) => pad + l).join("\n");
|
|
3184
|
+
}
|
|
3185
|
+
function toSimpleYaml(obj, depth = 0) {
|
|
3186
|
+
const pad = " ".repeat(depth);
|
|
3187
|
+
if (obj === null || obj === void 0) return `${pad}null`;
|
|
3188
|
+
if (typeof obj === "string") {
|
|
3189
|
+
if (obj.includes("\n")) return `|
|
|
3190
|
+
${indent(obj, (depth + 1) * 2)}`;
|
|
3191
|
+
return JSON.stringify(obj);
|
|
3192
|
+
}
|
|
3193
|
+
if (typeof obj === "number" || typeof obj === "boolean") return String(obj);
|
|
3194
|
+
if (Array.isArray(obj)) {
|
|
3195
|
+
if (obj.length === 0) return "[]";
|
|
3196
|
+
return obj.map((item) => {
|
|
3197
|
+
const val = toSimpleYaml(item, depth + 1);
|
|
3198
|
+
if (typeof item === "object" && item !== null) {
|
|
3199
|
+
return `${pad}- ${val.trimStart()}`;
|
|
3200
|
+
}
|
|
3201
|
+
return `${pad}- ${val}`;
|
|
3202
|
+
}).join("\n");
|
|
3203
|
+
}
|
|
3204
|
+
if (typeof obj === "object") {
|
|
3205
|
+
const entries = Object.entries(obj);
|
|
3206
|
+
if (entries.length === 0) return "{}";
|
|
3207
|
+
return entries.map(([k, v]) => {
|
|
3208
|
+
const val = toSimpleYaml(v, depth + 1);
|
|
3209
|
+
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
3210
|
+
return `${pad}${k}:
|
|
3211
|
+
${val}`;
|
|
3212
|
+
}
|
|
3213
|
+
if (Array.isArray(v) && v.length > 0) {
|
|
3214
|
+
return `${pad}${k}:
|
|
3215
|
+
${val}`;
|
|
3216
|
+
}
|
|
3217
|
+
return `${pad}${k}: ${val}`;
|
|
3218
|
+
}).join("\n");
|
|
3219
|
+
}
|
|
3220
|
+
return String(obj);
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
// src/cli/audit-requirements.ts
|
|
3224
|
+
async function commandAuditRequirements(opts) {
|
|
3225
|
+
const context = await buildAuditRequirementsContext(opts.config);
|
|
3226
|
+
const auditConfig = {
|
|
3227
|
+
adapter: opts.adapter,
|
|
3228
|
+
model: opts.model
|
|
3229
|
+
};
|
|
3230
|
+
const auditOpts = {
|
|
3231
|
+
dryRun: opts.dryRun,
|
|
3232
|
+
failOn: opts.failOn
|
|
3233
|
+
};
|
|
3234
|
+
try {
|
|
3235
|
+
const result = await runAgentTask(
|
|
3236
|
+
context,
|
|
3237
|
+
"audit-requirement-quality",
|
|
3238
|
+
auditConfig,
|
|
3239
|
+
auditOpts
|
|
3240
|
+
);
|
|
3241
|
+
const content = formatResult(result, opts.reportFormat ?? "text");
|
|
3242
|
+
await writeOutput(content, opts.output);
|
|
3243
|
+
const exitCode = computeExitCode(result, auditOpts);
|
|
3244
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
3245
|
+
} catch (err) {
|
|
3246
|
+
const exitCode = err.exitCode;
|
|
3247
|
+
if (exitCode === EXIT_RUNTIME_MISSING) {
|
|
3248
|
+
console.error(chalk4.red(err.message));
|
|
3249
|
+
process.exit(EXIT_RUNTIME_MISSING);
|
|
3250
|
+
}
|
|
3251
|
+
if (exitCode === EXIT_ADAPTER_ERROR) {
|
|
3252
|
+
console.error(chalk4.red(err.message));
|
|
3253
|
+
process.exit(EXIT_ADAPTER_ERROR);
|
|
3254
|
+
}
|
|
3255
|
+
throw err;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
async function commandProposeTraceLinks(opts) {
|
|
3259
|
+
const context = await buildProposeTraceLinksContext(opts.config);
|
|
3260
|
+
const auditConfig = {
|
|
3261
|
+
adapter: opts.adapter,
|
|
3262
|
+
model: opts.model
|
|
3263
|
+
};
|
|
3264
|
+
const auditOpts = {
|
|
3265
|
+
dryRun: opts.dryRun,
|
|
3266
|
+
failOn: opts.failOn
|
|
3267
|
+
};
|
|
3268
|
+
try {
|
|
3269
|
+
const result = await runAgentTask(
|
|
3270
|
+
context,
|
|
3271
|
+
"propose-trace-links",
|
|
3272
|
+
auditConfig,
|
|
3273
|
+
auditOpts
|
|
3274
|
+
);
|
|
3275
|
+
const content = formatResult(result, opts.reportFormat ?? "json");
|
|
3276
|
+
await writeOutput(content, opts.output);
|
|
3277
|
+
const exitCode = computeExitCode(result, auditOpts);
|
|
3278
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
3279
|
+
} catch (err) {
|
|
3280
|
+
const exitCode = err.exitCode;
|
|
3281
|
+
if (exitCode === EXIT_RUNTIME_MISSING) {
|
|
3282
|
+
console.error(chalk4.red(err.message));
|
|
3283
|
+
process.exit(EXIT_RUNTIME_MISSING);
|
|
3284
|
+
}
|
|
3285
|
+
if (exitCode === EXIT_ADAPTER_ERROR) {
|
|
3286
|
+
console.error(chalk4.red(err.message));
|
|
3287
|
+
process.exit(EXIT_ADAPTER_ERROR);
|
|
3288
|
+
}
|
|
3289
|
+
throw err;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
async function commandExplainImpact(opts) {
|
|
3293
|
+
const stdin = await readStdin();
|
|
3294
|
+
if (!stdin.trim()) {
|
|
3295
|
+
console.error(chalk4.red("Error: No input received on stdin."));
|
|
3296
|
+
console.error("Usage: speckeeper impact FR-001 --format json | speckeeper explain-impact");
|
|
3297
|
+
process.exit(2);
|
|
3298
|
+
}
|
|
3299
|
+
const context = buildExplainImpactContext(stdin);
|
|
3300
|
+
const auditConfig = {
|
|
3301
|
+
adapter: opts.adapter,
|
|
3302
|
+
model: opts.model
|
|
3303
|
+
};
|
|
3304
|
+
const auditOpts = {
|
|
3305
|
+
dryRun: opts.dryRun,
|
|
3306
|
+
failOn: opts.failOn
|
|
3307
|
+
};
|
|
3308
|
+
try {
|
|
3309
|
+
const result = await runAgentTask(
|
|
3310
|
+
context,
|
|
3311
|
+
"explain-impact-result",
|
|
3312
|
+
auditConfig,
|
|
3313
|
+
auditOpts
|
|
3314
|
+
);
|
|
3315
|
+
const content = formatResult(result, opts.reportFormat ?? "json");
|
|
3316
|
+
await writeOutput(content, opts.output);
|
|
3317
|
+
const exitCode = computeExitCode(result, auditOpts);
|
|
3318
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
3319
|
+
} catch (err) {
|
|
3320
|
+
const exitCode = err.exitCode;
|
|
3321
|
+
if (exitCode === EXIT_RUNTIME_MISSING) {
|
|
3322
|
+
console.error(chalk4.red(err.message));
|
|
3323
|
+
process.exit(EXIT_RUNTIME_MISSING);
|
|
3324
|
+
}
|
|
3325
|
+
if (exitCode === EXIT_ADAPTER_ERROR) {
|
|
3326
|
+
console.error(chalk4.red(err.message));
|
|
3327
|
+
process.exit(EXIT_ADAPTER_ERROR);
|
|
3328
|
+
}
|
|
3329
|
+
throw err;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
function readStdin() {
|
|
3333
|
+
return new Promise((resolve3, reject) => {
|
|
3334
|
+
const chunks = [];
|
|
3335
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
3336
|
+
process.stdin.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
|
|
3337
|
+
process.stdin.on("error", reject);
|
|
3338
|
+
if (process.stdin.isTTY) {
|
|
3339
|
+
resolve3("");
|
|
3340
|
+
}
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
async function commandProposeAcceptanceCriteria(specIds, opts) {
|
|
3344
|
+
const targetSpecIds = specIds.length > 0 ? specIds : void 0;
|
|
3345
|
+
const context = await buildProposeAcceptanceCriteriaContext(opts.config, targetSpecIds);
|
|
3346
|
+
const auditConfig = {
|
|
3347
|
+
adapter: opts.adapter,
|
|
3348
|
+
model: opts.model
|
|
3349
|
+
};
|
|
3350
|
+
const auditOpts = {
|
|
3351
|
+
dryRun: opts.dryRun,
|
|
3352
|
+
failOn: opts.failOn
|
|
3353
|
+
};
|
|
3354
|
+
try {
|
|
3355
|
+
const result = await runAgentTask(
|
|
3356
|
+
context,
|
|
3357
|
+
"propose-acceptance-criteria",
|
|
3358
|
+
auditConfig,
|
|
3359
|
+
auditOpts
|
|
3360
|
+
);
|
|
3361
|
+
const content = formatResult(result, opts.reportFormat ?? "json");
|
|
3362
|
+
await writeOutput(content, opts.output);
|
|
3363
|
+
const exitCode = computeExitCode(result, auditOpts);
|
|
3364
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
3365
|
+
} catch (err) {
|
|
3366
|
+
const exitCode = err.exitCode;
|
|
3367
|
+
if (exitCode === EXIT_RUNTIME_MISSING) {
|
|
3368
|
+
console.error(chalk4.red(err.message));
|
|
3369
|
+
process.exit(EXIT_RUNTIME_MISSING);
|
|
3370
|
+
}
|
|
3371
|
+
if (exitCode === EXIT_ADAPTER_ERROR) {
|
|
3372
|
+
console.error(chalk4.red(err.message));
|
|
3373
|
+
process.exit(EXIT_ADAPTER_ERROR);
|
|
3374
|
+
}
|
|
3375
|
+
throw err;
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
2255
3379
|
// src/cli/index.ts
|
|
2256
3380
|
function getVersion() {
|
|
2257
3381
|
try {
|
|
@@ -2272,6 +3396,58 @@ program.command("new").description("Create a new element with auto-generated ID"
|
|
|
2272
3396
|
program.command("impact").description("Analyze impact of changes to an ID").argument("<id>", "ID to analyze (e.g., REQ-001, ENT-ORDER)").option("-c, --config <path>", "Path to config file").option("-d, --depth <depth>", "Analysis depth (reference tracking level)", "3").option("--direction <direction>", "Analysis direction: upstream, downstream, both", "both").option("-f, --format <format>", "Output format: text, json, mermaid", "text").action(impactCommand);
|
|
2273
3397
|
program.command("init").description("Initialize a new speckeeper project with starter templates").option("-f, --force", "Overwrite existing files").action(runInit);
|
|
2274
3398
|
program.command("scaffold").description("Generate _models/ from a mermaid flowchart definition").requiredOption("-s, --source <path>", "Path to Markdown file containing mermaid flowchart").option("-o, --output <path>", "Output directory (default: design/)").option("-f, --force", "Overwrite existing files").option("--dry-run", "Preview generated files without writing").action(scaffoldCommand);
|
|
3399
|
+
var llmOptions = (cmd) => cmd.option("-a, --adapter <name>", "SDK adapter (cursor, claude, openai, gemini, mock)").option("--model <name>", "LLM model override").option("-n, --dry-run", "Output prompt without calling LLM").option("--fail-on <level>", "Minimum severity for non-zero exit (warning, error, critical)", "error").option("-o, --output <file>", "Write result to a file instead of stdout").option("--report-format <fmt>", "Output format (json, text, yaml)", "text");
|
|
3400
|
+
llmOptions(
|
|
3401
|
+
program.command("audit-requirements").description("LLM-based requirement quality audit").option("-c, --config <path>", "Path to config file")
|
|
3402
|
+
).action((opts) => {
|
|
3403
|
+
commandAuditRequirements({
|
|
3404
|
+
config: opts.config,
|
|
3405
|
+
adapter: opts.adapter,
|
|
3406
|
+
model: opts.model,
|
|
3407
|
+
dryRun: opts.dryRun,
|
|
3408
|
+
failOn: opts.failOn,
|
|
3409
|
+
output: opts.output,
|
|
3410
|
+
reportFormat: opts.reportFormat
|
|
3411
|
+
});
|
|
3412
|
+
});
|
|
3413
|
+
llmOptions(
|
|
3414
|
+
program.command("propose-trace-links").description("LLM-based traceability link proposal").option("-c, --config <path>", "Path to config file")
|
|
3415
|
+
).action((opts) => {
|
|
3416
|
+
commandProposeTraceLinks({
|
|
3417
|
+
config: opts.config,
|
|
3418
|
+
adapter: opts.adapter,
|
|
3419
|
+
model: opts.model,
|
|
3420
|
+
dryRun: opts.dryRun,
|
|
3421
|
+
failOn: opts.failOn,
|
|
3422
|
+
output: opts.output,
|
|
3423
|
+
reportFormat: opts.reportFormat
|
|
3424
|
+
});
|
|
3425
|
+
});
|
|
3426
|
+
llmOptions(
|
|
3427
|
+
program.command("explain-impact").description("LLM-based explanation of impact analysis output")
|
|
3428
|
+
).action((opts) => {
|
|
3429
|
+
commandExplainImpact({
|
|
3430
|
+
adapter: opts.adapter,
|
|
3431
|
+
model: opts.model,
|
|
3432
|
+
dryRun: opts.dryRun,
|
|
3433
|
+
failOn: opts.failOn,
|
|
3434
|
+
output: opts.output,
|
|
3435
|
+
reportFormat: opts.reportFormat
|
|
3436
|
+
});
|
|
3437
|
+
});
|
|
3438
|
+
llmOptions(
|
|
3439
|
+
program.command("propose-acceptance-criteria [specIds...]").description("LLM-based acceptance criteria proposal").option("-c, --config <path>", "Path to config file")
|
|
3440
|
+
).action((specIds, opts) => {
|
|
3441
|
+
commandProposeAcceptanceCriteria(specIds, {
|
|
3442
|
+
config: opts.config,
|
|
3443
|
+
adapter: opts.adapter,
|
|
3444
|
+
model: opts.model,
|
|
3445
|
+
dryRun: opts.dryRun,
|
|
3446
|
+
failOn: opts.failOn,
|
|
3447
|
+
output: opts.output,
|
|
3448
|
+
reportFormat: opts.reportFormat
|
|
3449
|
+
});
|
|
3450
|
+
});
|
|
2275
3451
|
program.parse();
|
|
2276
3452
|
//# sourceMappingURL=cli.js.map
|
|
2277
3453
|
//# sourceMappingURL=cli.js.map
|