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/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