cc-codeconductor 1.1.0 → 1.2.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.
Files changed (143) hide show
  1. package/README.md +4 -2
  2. package/dist/core/verification/verification-runner.d.ts +7 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1413 -309
  5. package/dist/library.js +29 -1
  6. package/dist/validation/schemas.d.ts +97 -26
  7. package/package.json +1 -1
  8. package/presets/agy/AGENTS.md +10 -9
  9. package/presets/agy/hooks.json +2 -2
  10. package/presets/agy/scripts/invoke-hook.cjs +115 -0
  11. package/presets/agy/skills/backlog/SKILL.md +40 -70
  12. package/presets/agy/skills/cc-spec-mutation/SKILL.md +165 -0
  13. package/presets/agy/skills/cc-tdd-cycle/SKILL.md +3 -0
  14. package/presets/agy/skills/evaluation/SKILL.md +61 -2
  15. package/presets/agy/skills/openspec/SKILL.md +49 -19
  16. package/presets/agy/skills/testing-tdd/SKILL.md +53 -0
  17. package/presets/agy/skills/using-cc-skills/SKILL.md +48 -0
  18. package/presets/agy/workflows/cc-api-contract.md +14 -0
  19. package/presets/agy/workflows/cc-db-migration.md +14 -0
  20. package/presets/agy/workflows/cc-feature.md +18 -0
  21. package/presets/agy/workflows/cc-fix.md +14 -0
  22. package/presets/agy/workflows/cc-iterative.md +14 -0
  23. package/presets/agy/workflows/cc-openspec.md +14 -0
  24. package/presets/agy/workflows/cc-scorecard.md +2 -0
  25. package/presets/agy/workflows/cc-spec-mutation.md +191 -0
  26. package/presets/agy/workflows/cc-tdd-cycle.md +14 -0
  27. package/presets/claude/commands/cc/api-contract.md +14 -0
  28. package/presets/claude/commands/cc/db-migration.md +14 -0
  29. package/presets/claude/commands/cc/feature.md +18 -0
  30. package/presets/claude/commands/cc/fix.md +17 -0
  31. package/presets/claude/commands/cc/iterative.md +14 -0
  32. package/presets/claude/commands/cc/openspec.md +14 -0
  33. package/presets/claude/commands/cc/review.md +3 -0
  34. package/presets/claude/commands/cc/scorecard.md +2 -0
  35. package/presets/claude/commands/cc/spec-mutation.md +190 -0
  36. package/presets/claude/commands/cc/tdd-cycle.md +17 -0
  37. package/presets/claude/settings.json +13 -11
  38. package/presets/claude/skills/backlog/SKILL.md +40 -70
  39. package/presets/claude/skills/evaluation/SKILL.md +47 -24
  40. package/presets/claude/skills/openspec/SKILL.md +46 -38
  41. package/presets/claude/skills/testing-tdd/SKILL.md +53 -0
  42. package/presets/claude/skills/using-cc-skills/SKILL.md +48 -0
  43. package/presets/codex/AGENTS.md +16 -12
  44. package/presets/codex/skills/backlog/SKILL.md +61 -0
  45. package/presets/codex/skills/cc-api-contract/SKILL.md +87 -0
  46. package/presets/codex/skills/cc-backlog/SKILL.md +108 -0
  47. package/presets/codex/skills/cc-clarify/SKILL.md +36 -0
  48. package/presets/codex/skills/cc-council/SKILL.md +92 -0
  49. package/presets/codex/skills/cc-db-migration/SKILL.md +88 -0
  50. package/presets/codex/skills/cc-explore/SKILL.md +40 -0
  51. package/presets/codex/skills/cc-feature/SKILL.md +154 -0
  52. package/presets/codex/skills/cc-fix/SKILL.md +165 -0
  53. package/presets/codex/skills/cc-handoff/SKILL.md +45 -0
  54. package/presets/codex/skills/cc-iterative/SKILL.md +150 -0
  55. package/presets/codex/skills/cc-openspec/SKILL.md +191 -0
  56. package/presets/codex/skills/cc-pagespeed/SKILL.md +124 -0
  57. package/presets/codex/skills/cc-prototype/SKILL.md +42 -0
  58. package/presets/codex/skills/cc-refactor/SKILL.md +163 -0
  59. package/presets/codex/skills/cc-review/SKILL.md +152 -0
  60. package/presets/codex/skills/cc-scorecard/SKILL.md +82 -0
  61. package/presets/codex/skills/cc-security/SKILL.md +182 -0
  62. package/presets/codex/skills/cc-spec-mutation/SKILL.md +192 -0
  63. package/presets/codex/skills/cc-tdd-cycle/SKILL.md +266 -0
  64. package/presets/codex/skills/cc-test-plan/SKILL.md +153 -0
  65. package/presets/codex/skills/cc-triage/SKILL.md +38 -0
  66. package/presets/codex/skills/evaluation/SKILL.md +65 -0
  67. package/presets/codex/skills/openspec/SKILL.md +66 -0
  68. package/presets/codex/skills/testing-tdd/SKILL.md +53 -0
  69. package/presets/codex/skills/using-cc-skills/SKILL.md +48 -0
  70. package/presets/cursor/commands/cc/api-contract.md +14 -0
  71. package/presets/cursor/commands/cc/db-migration.md +14 -0
  72. package/presets/cursor/commands/cc/feature.md +18 -0
  73. package/presets/cursor/commands/cc/fix.md +17 -0
  74. package/presets/cursor/commands/cc/iterative.md +14 -0
  75. package/presets/cursor/commands/cc/openspec.md +14 -0
  76. package/presets/cursor/commands/cc/scorecard.md +2 -0
  77. package/presets/cursor/commands/cc/spec-mutation.md +190 -0
  78. package/presets/cursor/commands/cc/tdd-cycle.md +14 -0
  79. package/presets/cursor/skills/backlog/SKILL.md +40 -70
  80. package/presets/cursor/skills/evaluation/SKILL.md +61 -4
  81. package/presets/cursor/skills/openspec/SKILL.md +46 -36
  82. package/presets/cursor/skills/testing-tdd/SKILL.md +35 -574
  83. package/presets/cursor/skills/using-cc-skills/SKILL.md +48 -0
  84. package/presets/gemini/commands/cc/api-contract.toml +82 -0
  85. package/presets/gemini/commands/cc/ask.toml +54 -0
  86. package/presets/gemini/commands/cc/backlog.toml +103 -0
  87. package/presets/gemini/commands/cc/clarify.toml +31 -0
  88. package/presets/gemini/commands/cc/council.toml +87 -0
  89. package/presets/gemini/commands/cc/db-migration.toml +83 -0
  90. package/presets/gemini/commands/cc/explore.toml +35 -0
  91. package/presets/gemini/commands/cc/feature.toml +153 -0
  92. package/presets/gemini/commands/cc/fix.toml +163 -0
  93. package/presets/gemini/commands/cc/handoff.toml +40 -0
  94. package/presets/gemini/commands/cc/iterative.toml +145 -0
  95. package/presets/gemini/commands/cc/openspec.toml +186 -0
  96. package/presets/gemini/commands/cc/pagespeed.toml +119 -0
  97. package/presets/gemini/commands/cc/prototype.toml +37 -0
  98. package/presets/gemini/commands/cc/refactor.toml +158 -0
  99. package/presets/gemini/commands/cc/review.toml +150 -0
  100. package/presets/gemini/commands/cc/scorecard.toml +77 -0
  101. package/presets/gemini/commands/cc/security.toml +177 -0
  102. package/presets/gemini/commands/cc/spec-mutation.toml +187 -0
  103. package/presets/gemini/commands/cc/tdd-cycle.toml +264 -0
  104. package/presets/gemini/commands/cc/test-plan.toml +148 -0
  105. package/presets/gemini/commands/cc/triage.toml +33 -0
  106. package/presets/opencode/README.md +24 -21
  107. package/presets/opencode/agents/architect.md +6 -0
  108. package/presets/opencode/agents/implementer.md +7 -0
  109. package/presets/opencode/agents/reviewer.md +6 -0
  110. package/presets/opencode/agents/tester.md +6 -0
  111. package/presets/opencode/commands/cc-api-contract.md +14 -0
  112. package/presets/opencode/commands/cc-db-migration.md +14 -0
  113. package/presets/opencode/commands/cc-feature.md +18 -0
  114. package/presets/opencode/commands/cc-fix.md +17 -0
  115. package/presets/opencode/commands/cc-iterative.md +14 -0
  116. package/presets/opencode/commands/cc-openspec.md +14 -0
  117. package/presets/opencode/commands/cc-scorecard.md +2 -0
  118. package/presets/opencode/commands/cc-spec-mutation.md +190 -0
  119. package/presets/opencode/commands/cc-tdd-cycle.md +14 -0
  120. package/presets/opencode/opencode.jsonc +1 -1
  121. package/presets/opencode/prompts/v1.0.0/architect.md +6 -0
  122. package/presets/opencode/prompts/v1.0.0/implementer.md +7 -0
  123. package/presets/opencode/prompts/v1.0.0/reviewer.md +6 -0
  124. package/presets/opencode/prompts/v1.0.0/tester.md +6 -0
  125. package/presets/opencode/skills/backlog/SKILL.md +40 -70
  126. package/presets/opencode/skills/evaluation/SKILL.md +61 -2
  127. package/presets/opencode/skills/openspec/SKILL.md +46 -34
  128. package/presets/opencode/skills/testing-tdd/SKILL.md +35 -574
  129. package/presets/opencode/skills/using-cc-skills/SKILL.md +48 -0
  130. package/presets/shared/__pycache__/mutation_runner.cpython-314.pyc +0 -0
  131. package/presets/shared/invoke-hook.cjs +115 -0
  132. package/presets/shared/mutation_runner.py +273 -0
  133. package/src/presets/manifests/agy.yml +2 -0
  134. package/src/presets/manifests/claude.yml +3 -0
  135. package/src/presets/manifests/gemini.yml +15 -0
  136. package/src/presets/models/agy.yml +24 -24
  137. package/src/presets/models/claude.yml +10 -10
  138. package/src/presets/models/codex.yml +10 -10
  139. package/src/presets/models/cursor.yml +10 -10
  140. package/src/presets/models/gemini.yml +10 -10
  141. package/src/presets/models/opencode.yml +10 -10
  142. package/presets/agy/scripts/post-tool.sh +0 -25
  143. package/presets/agy/scripts/pre-tool.sh +0 -56
package/dist/index.js CHANGED
@@ -83,7 +83,7 @@ var package_default;
83
83
  var init_package = __esm(() => {
84
84
  package_default = {
85
85
  name: "cc-codeconductor",
86
- version: "1.1.0",
86
+ version: "1.2.0",
87
87
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
88
88
  keywords: [
89
89
  "ai",
@@ -11307,6 +11307,8 @@ __export(exports_schemas, {
11307
11307
  TaskOutcomeSourceSchema: () => TaskOutcomeSourceSchema,
11308
11308
  TaskOutcomeSchema: () => TaskOutcomeSchema,
11309
11309
  StrategicMemorySchema: () => StrategicMemorySchema,
11310
+ SpecAnalyzeReportSchema: () => SpecAnalyzeReportSchema,
11311
+ SpecAnalyzeFindingSchema: () => SpecAnalyzeFindingSchema,
11310
11312
  SentryWebhookSchema: () => SentryWebhookSchema,
11311
11313
  SentryStackFrameSchema: () => SentryStackFrameSchema,
11312
11314
  ScorecardVerdictSchema: () => ScorecardVerdictSchema,
@@ -11494,7 +11496,7 @@ function validateCanonicalTaskCard(data) {
11494
11496
  function validateProductEvent(data) {
11495
11497
  return ProductEventSchema.parse(data);
11496
11498
  }
11497
- var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CompileCheckConfigSchema, LoopConfigSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema, ContractTargetSchema, ContractFormatSchema, AgentContractSchema, CouncilFindingSchema, CouncilVerdictInputSchema, ConsensusConfigSchema, CouncilVerdictSchema, ClaudeAgentFileSchema, OpenCodeAgentFileSchema, SentryStackFrameSchema, SentryWebhookSchema, MemoryPointerSchema, MemoryIndexSchema, GoalTaskSchema, GoalGraphSchema, ConfidenceLevelSchema, ProductNodeTypeSchema, GraphRelationSchema, KnowledgeEntitySchema, DecisionSchema, EvidenceSchema, CanonicalTaskCardStatusSchema, CanonicalTaskCardSchema, ProductGraphNodeSchema, ProductGraphEdgeSchema, ProductGraphSchema, ProductMetaSchema, ProductEventTypeSchema, ProductEventSchema, OperationalStateSchema, StrategicMemorySchema, ImpactReportSchema, BusinessReviewOutputSchema, VerificationReportSchema, BacklogStatusSchema, BacklogTypeSchema, BacklogPrioritySchema, BacklogGlobalSchema, BacklogItemSchema, BacklogDocumentSchema, OpenspecTaskCardPhaseSchema, OpenspecTaskCardStatusSchema, OpenspecTaskCardSchema, OpenspecStateSchema, WorkflowCommandSchema, CcepOutputFormatSchema, CommandEnvelopeSchema, WorkflowPhaseSchema, WorkflowRiskRuleSchema, WorkflowProfileSchema, ExecutionContextSchema, PlannerOutputSchema, AgentArtifactSchema, AgentOutputSchema, ImplementerTestsSchema, ImplementerOutputSchema, ReviewAxisSchema, ReviewFindingSchema, ReviewerOutputSchema, TechnicalPlanOutputSchema, ScorecardVerdictSchema, ScorecardCriterionIdSchema, ScorecardCriterionSchema, ScorecardRecordSchema, TaskOutcomeSourceSchema, TaskOutcomeStatusSchema, TaskOutcomeSchema, HarnessComponentIdSchema, HarnessComponentLayerSchema, HarnessToggleKindSchema, HarnessCatalogEntrySchema, HarnessCatalogSchema, HarnessOverlaySchema, HarnessExperimentSchema, HarnessSuiteTaskSchema, HarnessSuiteSchema, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema;
11499
+ var CouncilAgentSpecSchema, CouncilSpecSchema, ProjectProfileSchema, CompileCheckConfigSchema, LoopConfigSchema, CodeConductorConfigSchema, RunnerTargetSchema, InstallStrategySchema, ManifestEntrySchema, InstallManifestSchema, ToolProviderNamesSchema, PermissionProviderNamesSchema, ModelConfigSchema, ContractTargetSchema, ContractFormatSchema, AgentContractSchema, CouncilFindingSchema, CouncilVerdictInputSchema, ConsensusConfigSchema, CouncilVerdictSchema, ClaudeAgentFileSchema, OpenCodeAgentFileSchema, SentryStackFrameSchema, SentryWebhookSchema, MemoryPointerSchema, MemoryIndexSchema, GoalTaskSchema, GoalGraphSchema, ConfidenceLevelSchema, ProductNodeTypeSchema, GraphRelationSchema, KnowledgeEntitySchema, DecisionSchema, EvidenceSchema, CanonicalTaskCardStatusSchema, CanonicalTaskCardSchema, ProductGraphNodeSchema, ProductGraphEdgeSchema, ProductGraphSchema, ProductMetaSchema, ProductEventTypeSchema, ProductEventSchema, OperationalStateSchema, StrategicMemorySchema, ImpactReportSchema, BusinessReviewOutputSchema, VerificationReportSchema, BacklogStatusSchema, BacklogTypeSchema, BacklogPrioritySchema, BacklogGlobalSchema, BacklogItemSchema, BacklogDocumentSchema, OpenspecTaskCardPhaseSchema, OpenspecTaskCardStatusSchema, OpenspecTaskCardSchema, OpenspecStateSchema, SpecAnalyzeFindingSchema, SpecAnalyzeReportSchema, WorkflowCommandSchema, CcepOutputFormatSchema, CommandEnvelopeSchema, WorkflowPhaseSchema, WorkflowRiskRuleSchema, WorkflowProfileSchema, ExecutionContextSchema, PlannerOutputSchema, AgentArtifactSchema, AgentOutputSchema, ImplementerTestsSchema, ImplementerOutputSchema, ReviewAxisSchema, ReviewFindingSchema, ReviewerOutputSchema, TechnicalPlanOutputSchema, ScorecardVerdictSchema, ScorecardCriterionIdSchema, ScorecardCriterionSchema, ScorecardRecordSchema, TaskOutcomeSourceSchema, TaskOutcomeStatusSchema, TaskOutcomeSchema, HarnessComponentIdSchema, HarnessComponentLayerSchema, HarnessToggleKindSchema, HarnessCatalogEntrySchema, HarnessCatalogSchema, HarnessOverlaySchema, HarnessExperimentSchema, HarnessSuiteTaskSchema, HarnessSuiteSchema, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema;
11498
11500
  var init_schemas = __esm(() => {
11499
11501
  init_zod();
11500
11502
  CouncilAgentSpecSchema = exports_external.object({
@@ -11986,6 +11988,24 @@ var init_schemas = __esm(() => {
11986
11988
  changePaths: exports_external.record(exports_external.string(), exports_external.string()).optional().default({}),
11987
11989
  itemSnapshots: exports_external.record(exports_external.string(), exports_external.string()).optional().default({})
11988
11990
  });
11991
+ SpecAnalyzeFindingSchema = exports_external.object({
11992
+ severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
11993
+ code: exports_external.string(),
11994
+ message: exports_external.string()
11995
+ });
11996
+ SpecAnalyzeReportSchema = exports_external.object({
11997
+ changePath: exports_external.string(),
11998
+ frIds: exports_external.array(exports_external.string()),
11999
+ scIds: exports_external.array(exports_external.string()),
12000
+ mappedFr: exports_external.array(exports_external.string()),
12001
+ mappedSc: exports_external.array(exports_external.string()),
12002
+ mappedToTests: exports_external.array(exports_external.string()),
12003
+ frCoveragePct: exports_external.number(),
12004
+ scCoveragePct: exports_external.number(),
12005
+ testCoveragePct: exports_external.number(),
12006
+ findings: exports_external.array(SpecAnalyzeFindingSchema),
12007
+ stop: exports_external.boolean()
12008
+ });
11989
12009
  WorkflowCommandSchema = exports_external.enum([
11990
12010
  "feature",
11991
12011
  "fix",
@@ -11993,6 +12013,7 @@ var init_schemas = __esm(() => {
11993
12013
  "review",
11994
12014
  "test-plan",
11995
12015
  "tdd-cycle",
12016
+ "spec-mutation",
11996
12017
  "api-contract",
11997
12018
  "db-migration",
11998
12019
  "pagespeed",
@@ -12447,6 +12468,28 @@ var init_profiles = __esm(() => {
12447
12468
  routing: { default: ["test", "implement", "refactor"] },
12448
12469
  confirmationGate: { stopOnHighRisk: false, stopOnQuestions: true }
12449
12470
  },
12471
+ "spec-mutation": {
12472
+ id: "spec-mutation",
12473
+ version: 1,
12474
+ command: "spec-mutation",
12475
+ taskCard: {
12476
+ type: "feature",
12477
+ requiredFields: ["title", "type", "risk", "scope", "context", "acceptanceCriteria"]
12478
+ },
12479
+ phases: [
12480
+ { id: "refine", agent: "task-coach", outputSchema: "planner-output", stopGate: "confirmation" },
12481
+ { id: "spec", agent: "contract-builder", outputSchema: "api-contract", stopGate: "approval" },
12482
+ { id: "test", agent: "tester", dependsOn: ["spec"], requires: "red-state" },
12483
+ { id: "implement", agent: "implementer", dependsOn: ["test"], requires: "green-state" },
12484
+ { id: "judge", agent: "reviewer", outputSchema: "review-report", dependsOn: ["implement"] },
12485
+ { id: "mutation", agent: "tester", dependsOn: ["judge"] },
12486
+ { id: "review", agent: "reviewer", dependsOn: ["mutation"] }
12487
+ ],
12488
+ routing: {
12489
+ default: ["refine", "spec", "test", "implement", "judge", "mutation", "review"]
12490
+ },
12491
+ confirmationGate: baseGate
12492
+ },
12450
12493
  "api-contract": {
12451
12494
  id: "api-contract",
12452
12495
  version: 1,
@@ -12494,11 +12537,12 @@ var init_profiles = __esm(() => {
12494
12537
  { id: "validate-backlog", agent: "orchestrator", type: "cli-gate" },
12495
12538
  { id: "discover", agent: "repo-explorer" },
12496
12539
  { id: "design", agent: "architect" },
12540
+ { id: "analyze", agent: "orchestrator", type: "cli-gate" },
12497
12541
  { id: "test", agent: "tester" },
12498
12542
  { id: "implement", agent: "implementer", dependsOn: ["test"] },
12499
12543
  { id: "review", agent: "reviewer" }
12500
12544
  ],
12501
- routing: { default: ["validate-backlog", "discover", "design", "test", "implement", "review"] },
12545
+ routing: { default: ["validate-backlog", "discover", "design", "analyze", "test", "implement", "review"] },
12502
12546
  confirmationGate: baseGate
12503
12547
  },
12504
12548
  backlog: {
@@ -12525,10 +12569,11 @@ var init_profiles = __esm(() => {
12525
12569
  command: "scorecard",
12526
12570
  intakeSchema: "scorecard-task-id",
12527
12571
  phases: [
12572
+ { id: "analyze", agent: "orchestrator", type: "cli-gate" },
12528
12573
  { id: "create", agent: "orchestrator", type: "cli-gate" },
12529
12574
  { id: "evaluate", agent: "reviewer", outputSchema: "scorecard-record" }
12530
12575
  ],
12531
- routing: { default: ["create", "evaluate"] },
12576
+ routing: { default: ["analyze", "create", "evaluate"] },
12532
12577
  confirmationGate: { stopOnHighRisk: false, stopOnQuestions: false }
12533
12578
  },
12534
12579
  council: {
@@ -16379,7 +16424,8 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
16379
16424
  const sectionMatch = result.match(sectionRegex);
16380
16425
  if (sectionMatch) {
16381
16426
  for (const section of sectionMatch) {
16382
- const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(/\{\{MODEL_GROK\}\}/g, agentModels.grok ?? "");
16427
+ const targetModel = agentModels[modelConfig.target];
16428
+ const renderedSection = section.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(/\{\{MODEL_GROK\}\}/g, agentModels.grok ?? "");
16383
16429
  result = result.replace(section, renderedSection);
16384
16430
  }
16385
16431
  }
@@ -17757,6 +17803,18 @@ function recommendAskFlow(problem) {
17757
17803
  ])) {
17758
17804
  return pick("security", "The request is authorized defensive security work, so /cc:security is the fit.");
17759
17805
  }
17806
+ if (includesAny(text, [
17807
+ "mutation testing",
17808
+ "mutation test",
17809
+ "mutant",
17810
+ "stryker",
17811
+ "pitest",
17812
+ "mutmut",
17813
+ "spec-mutation",
17814
+ "gherkin"
17815
+ ])) {
17816
+ return pick("spec-mutation", "The request involves mutation testing or a Gherkin-locked spec gate, which is the /cc:spec-mutation contract.");
17817
+ }
17760
17818
  if (includesAny(text, [
17761
17819
  "tdd",
17762
17820
  "red-green",
@@ -17826,6 +17884,11 @@ var init_recommend_flow = __esm(() => {
17826
17884
  slash: "/cc:tdd-cycle",
17827
17885
  blurb: "one red-green-refactor cycle starting from a failing test"
17828
17886
  },
17887
+ {
17888
+ command: "spec-mutation",
17889
+ slash: "/cc:spec-mutation",
17890
+ blurb: "spec-locked TDD with a mutation-testing gate before merge"
17891
+ },
17829
17892
  {
17830
17893
  command: "backlog",
17831
17894
  slash: "/cc:backlog",
@@ -19819,7 +19882,7 @@ var init_memory_index = __esm(() => {
19819
19882
  });
19820
19883
 
19821
19884
  // src/core/evaluation/outcome-store.ts
19822
- import { appendFile as appendFile2, mkdir as mkdir15, readFile as readFile21, writeFile as writeFile13 } from "node:fs/promises";
19885
+ import { appendFile as appendFile2, mkdir as mkdir15, readFile as readFile21, readdir as readdir5, writeFile as writeFile13 } from "node:fs/promises";
19823
19886
  import { resolve as resolve21 } from "node:path";
19824
19887
  async function ensureEvaluationDir(projectRoot) {
19825
19888
  const dir = resolve21(projectRoot, EVAL_DIR);
@@ -19894,6 +19957,38 @@ async function saveScorecard(projectRoot, record) {
19894
19957
  return err(e instanceof Error ? e : new Error(String(e)));
19895
19958
  }
19896
19959
  }
19960
+ async function listScorecards(projectRoot, filter = {}) {
19961
+ try {
19962
+ const dir = resolve21(projectRoot, EVAL_DIR, SCORECARDS_DIR);
19963
+ let files;
19964
+ try {
19965
+ files = await readdir5(dir);
19966
+ } catch (e) {
19967
+ if (e.code === "ENOENT")
19968
+ return ok([]);
19969
+ throw e;
19970
+ }
19971
+ const records = [];
19972
+ for (const file of files) {
19973
+ if (!file.endsWith(".json"))
19974
+ continue;
19975
+ const parsed = ScorecardRecordSchema.parse(JSON.parse(await readFile21(resolve21(dir, file), "utf-8")));
19976
+ if (filter.backlogId && parsed.backlogId !== filter.backlogId)
19977
+ continue;
19978
+ if (filter.verdict && parsed.verdict !== filter.verdict)
19979
+ continue;
19980
+ records.push(parsed);
19981
+ }
19982
+ records.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
19983
+ return ok(records);
19984
+ } catch (e) {
19985
+ return err(e instanceof Error ? e : new Error(String(e)));
19986
+ }
19987
+ }
19988
+ async function hasPassingScorecard(projectRoot, backlogId) {
19989
+ const listed = await listScorecards(projectRoot, { backlogId, verdict: "PASS" });
19990
+ return listed.success && listed.data.length > 0;
19991
+ }
19897
19992
  async function loadScorecard(projectRoot, id) {
19898
19993
  try {
19899
19994
  const filePath = resolve21(projectRoot, EVAL_DIR, SCORECARDS_DIR, `${id}.json`);
@@ -20591,7 +20686,7 @@ var init_compile_checker = __esm(() => {
20591
20686
  import { existsSync as existsSync3 } from "node:fs";
20592
20687
  import { readFile as readFile23 } from "node:fs/promises";
20593
20688
  import { dirname as dirname6, join as join16, resolve as resolve22 } from "node:path";
20594
- import { mkdir as mkdir16, readdir as readdir5, writeFile as writeFile15 } from "node:fs/promises";
20689
+ import { mkdir as mkdir16, readdir as readdir6, writeFile as writeFile15 } from "node:fs/promises";
20595
20690
  function isPassingEvidence(ev) {
20596
20691
  switch (ev.type) {
20597
20692
  case "verification":
@@ -20636,7 +20731,7 @@ async function collectTaskEvidence(projectRoot, taskId) {
20636
20731
  return ok({ records, invalid });
20637
20732
  let files;
20638
20733
  try {
20639
- files = await readdir5(evDir);
20734
+ files = await readdir6(evDir);
20640
20735
  } catch (e) {
20641
20736
  return err(e instanceof Error ? e : new Error(String(e)));
20642
20737
  }
@@ -20806,6 +20901,13 @@ async function gateTaskCompletion(projectRoot, taskId, evidenceRequired, evidenc
20806
20901
  }
20807
20902
  return ok(true);
20808
20903
  }
20904
+ async function hasTddRunnerEvidence(projectRoot, taskId) {
20905
+ const collected = await collectTaskEvidence(projectRoot, taskId);
20906
+ if (!collected.success)
20907
+ return false;
20908
+ return collected.data.records.some((ev) => ev.type === "tdd" && ev.source === TDD_EVIDENCE_SOURCE && ev.data?.capturedBy === TDD_CAPTURED_BY);
20909
+ }
20910
+ var TDD_EVIDENCE_SOURCE = "cc verify", TDD_CAPTURED_BY = "verification-runner";
20809
20911
  var init_verification_runner = __esm(() => {
20810
20912
  init_schemas();
20811
20913
  init_config_loader();
@@ -22251,6 +22353,7 @@ var init_command_parser = __esm(() => {
22251
22353
  review: { outputFormat: "verdict", needConfirmation: false, riskThreshold: "low" },
22252
22354
  "test-plan": { outputFormat: "plan", needConfirmation: true, riskThreshold: "low" },
22253
22355
  "tdd-cycle": { outputFormat: "taskcard", needConfirmation: false, riskThreshold: "medium" },
22356
+ "spec-mutation": { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "high" },
22254
22357
  "api-contract": { outputFormat: "plan", needConfirmation: true, riskThreshold: "high" },
22255
22358
  "db-migration": { outputFormat: "plan", needConfirmation: true, riskThreshold: "high" },
22256
22359
  pagespeed: { outputFormat: "verdict", needConfirmation: false, riskThreshold: "low" },
@@ -22293,7 +22396,7 @@ var init_workflow_profile_loader = __esm(() => {
22293
22396
  // src/core/evaluation/harness-catalog.ts
22294
22397
  import { createHash as createHash3 } from "node:crypto";
22295
22398
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
22296
- import { copyFile as copyFile2, mkdir as mkdir17, readFile as readFile24, readdir as readdir6, rm as rm2, writeFile as writeFile16 } from "node:fs/promises";
22399
+ import { copyFile as copyFile2, mkdir as mkdir17, readFile as readFile24, readdir as readdir7, rm as rm2, writeFile as writeFile16 } from "node:fs/promises";
22297
22400
  import { dirname as dirname7, join as join18, resolve as resolve23 } from "node:path";
22298
22401
  function catalogIds(catalog = DEFAULT_HARNESS_CATALOG) {
22299
22402
  return catalog.components.map((c) => c.id);
@@ -22427,7 +22530,7 @@ async function restoreOverlayBackup(projectRoot) {
22427
22530
  const workflowsDest = resolve23(projectRoot, ".codeconductor", "workflows");
22428
22531
  if (existsSync5(workflowsBackup)) {
22429
22532
  await mkdir17(workflowsDest, { recursive: true });
22430
- const files = await readdir6(workflowsBackup);
22533
+ const files = await readdir7(workflowsBackup);
22431
22534
  for (const file of files) {
22432
22535
  await copyFile2(join18(workflowsBackup, file), join18(workflowsDest, file));
22433
22536
  }
@@ -22453,7 +22556,7 @@ async function snapshotForBackup(projectRoot) {
22453
22556
  if (existsSync5(workflowsDir)) {
22454
22557
  const dest = join18(backupRoot, "workflows");
22455
22558
  await mkdir17(dest, { recursive: true });
22456
- const files = await readdir6(workflowsDir);
22559
+ const files = await readdir7(workflowsDir);
22457
22560
  for (const file of files) {
22458
22561
  if (file.endsWith(".yml")) {
22459
22562
  await copyFile2(join18(workflowsDir, file), join18(dest, file));
@@ -24096,6 +24199,9 @@ var init_backlog_planner = __esm(() => {
24096
24199
  // src/core/openspec/openspec-generator.ts
24097
24200
  import { mkdir as mkdir19, rename as rename3, writeFile as writeFile18 } from "node:fs/promises";
24098
24201
  import { basename as basename3, resolve as resolve26 } from "node:path";
24202
+ function pad3(n) {
24203
+ return String(n).padStart(3, "0");
24204
+ }
24099
24205
  function proposalContent(item) {
24100
24206
  return `# Proposal: ${item.title}
24101
24207
 
@@ -24111,8 +24217,8 @@ ${item.businessValue ? `**Business value:** ${item.businessValue}` : ""}
24111
24217
 
24112
24218
  ## Capabilities
24113
24219
 
24114
- - **New Capabilities:** (to be refined in design phase)
24115
- - **Modified Capabilities:** (to be refined in design phase)
24220
+ - **New Capabilities:** ${item.title}
24221
+ - **Modified Capabilities:** none
24116
24222
 
24117
24223
  ## Impact
24118
24224
 
@@ -24121,12 +24227,12 @@ ${item.risks ? `Risks: ${item.risks}` : "See design.md for technical impact."}
24121
24227
  **Out of scope:** ${item.outOfScope || "None specified."}
24122
24228
  `;
24123
24229
  }
24124
- function designPlaceholder(item) {
24230
+ function designContent(item) {
24125
24231
  return `# Design: ${item.title}
24126
24232
 
24127
24233
  ## Approach
24128
24234
 
24129
- (To be completed in design phase.)
24235
+ Deliver "${item.title}" inside ${item.scope}. The architect MUST refine files and risks before implementation.
24130
24236
 
24131
24237
  ## Files Affected
24132
24238
 
@@ -24136,40 +24242,94 @@ ${item.scope}
24136
24242
 
24137
24243
  ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
24138
24244
  `)}
24245
+
24246
+ ## Complexity Tracking
24247
+
24248
+ | Violation | Why needed | Simpler alternative rejected |
24249
+ | --------- | ---------- | ---------------------------- |
24250
+ | none | — | — |
24251
+ `;
24252
+ }
24253
+ function specDelta(item) {
24254
+ const criteria = item.acceptanceCriteria.length > 0 ? item.acceptanceCriteria : [`${item.title} is delivered`];
24255
+ const blocks = criteria.map((criterion, index) => {
24256
+ const n = pad3(index + 1);
24257
+ return `### Requirement: FR-${n} ${criterion}
24258
+
24259
+ The system MUST ${criterion}.
24260
+
24261
+ #### Scenario: SC-${n} ${criterion}
24262
+
24263
+ - GIVEN the current behavior in ${item.scope}
24264
+ - WHEN this change is applied
24265
+ - THEN ${criterion}
24266
+ `;
24267
+ });
24268
+ return `# Delta Spec: ${item.title}
24269
+
24270
+ ## ADDED Requirements
24271
+
24272
+ ${blocks.join(`
24273
+ `)}
24139
24274
  `;
24140
24275
  }
24141
- async function writeTasksMarkdown(projectRoot, changePath, cards) {
24276
+ async function writeTasksMarkdown(projectRoot, changePath, cards, options = {}) {
24142
24277
  await mkdir19(resolve26(projectRoot, changePath), { recursive: true });
24143
- await writeFile18(resolve26(projectRoot, changePath, "tasks.md"), tasksContent(cards), "utf-8");
24278
+ await writeFile18(resolve26(projectRoot, changePath, "tasks.md"), tasksContent(cards, options), "utf-8");
24144
24279
  }
24145
- function tasksContent(cards) {
24146
- const implCards = cards.filter((c) => c.phase === "implement" || c.phase === "test");
24147
- const lines = ["# Implementation Tasks", ""];
24148
- for (const card of implCards) {
24280
+ function tasksContent(cards, options = {}) {
24281
+ const criteria = options.acceptanceCriteria ?? [];
24282
+ const lines = [
24283
+ "# Implementation Tasks",
24284
+ "",
24285
+ "## Setup",
24286
+ "",
24287
+ "- [ ] Confirm scope and OpenSpec change folder",
24288
+ "",
24289
+ "## Foundational",
24290
+ "",
24291
+ "- [ ] Read existing conventions in scope",
24292
+ "",
24293
+ "## Requirements",
24294
+ ""
24295
+ ];
24296
+ if (criteria.length === 0) {
24297
+ lines.push("- [ ] Implementation tasks (generated after plan)");
24298
+ } else {
24299
+ for (const [index, criterion] of criteria.entries()) {
24300
+ const n = pad3(index + 1);
24301
+ if (options.tddRequired) {
24302
+ lines.push(`- [ ] Write failing test for FR-${n} (${criterion})`);
24303
+ }
24304
+ lines.push(`- [ ] Implement FR-${n} (${criterion})`);
24305
+ lines.push(`- [ ] Verify SC-${n}`);
24306
+ }
24307
+ }
24308
+ const phaseCards = cards.filter((c) => c.phase === "implement" || c.phase === "test");
24309
+ lines.push("", "## Phase cards", "");
24310
+ for (const card of phaseCards) {
24149
24311
  const checked = card.status === "done" ? "x" : " ";
24150
24312
  lines.push(`- [${checked}] ${card.title} (${card.id})`);
24151
24313
  }
24152
- if (implCards.length === 0) {
24314
+ if (phaseCards.length === 0) {
24153
24315
  lines.push("- [ ] Implementation tasks (generated after plan)");
24154
24316
  }
24317
+ lines.push("", "## Polish", "", "- [ ] Run openspec analyze and scorecard create --from-diff");
24155
24318
  return lines.join(`
24156
24319
  `);
24157
24320
  }
24158
- async function generateOpenspecChange(projectRoot, item, taskCards) {
24321
+ async function generateOpenspecChange(projectRoot, item, taskCards, options = {}) {
24159
24322
  const slug = buildChangeSlug(item);
24160
24323
  const changeDir = resolve26(projectRoot, "openspec", "changes", slug);
24161
24324
  await mkdir19(resolve26(changeDir, "specs"), { recursive: true });
24325
+ const taskOpts = {
24326
+ tddRequired: options.tddRequired,
24327
+ acceptanceCriteria: options.acceptanceCriteria ?? item.acceptanceCriteria
24328
+ };
24162
24329
  await writeFile18(resolve26(changeDir, "proposal.md"), proposalContent(item), "utf-8");
24163
- await writeFile18(resolve26(changeDir, "design.md"), designPlaceholder(item), "utf-8");
24164
- await writeFile18(resolve26(changeDir, "tasks.md"), tasksContent(taskCards), "utf-8");
24165
- const specStub = `# Delta Spec: ${item.title}
24166
-
24167
- ## ADDED Requirements
24168
-
24169
- ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
24170
- `)}
24171
- `;
24172
- await writeFile18(resolve26(changeDir, "specs", "delta.md"), specStub, "utf-8");
24330
+ await writeFile18(resolve26(changeDir, "design.md"), designContent(item), "utf-8");
24331
+ await writeFile18(resolve26(changeDir, "tasks.md"), tasksContent(taskCards, taskOpts), "utf-8");
24332
+ await writeFile18(resolve26(changeDir, "specs", "delta.md"), specDelta(item), "utf-8");
24173
24333
  return `openspec/changes/${slug}`;
24174
24334
  }
24175
24335
  async function archiveChangeFolder(projectRoot, changePath) {
@@ -24198,13 +24358,362 @@ var init_openspec_generator = __esm(() => {
24198
24358
  init_backlog_planner();
24199
24359
  });
24200
24360
 
24361
+ // src/core/openspec/spec-quality.ts
24362
+ import { readdir as readdir8, readFile as readFile28 } from "node:fs/promises";
24363
+ import { join as join20, relative as relative5, resolve as resolve27 } from "node:path";
24364
+ function uniqueIds(matches) {
24365
+ return [...new Set(matches ?? [])];
24366
+ }
24367
+ function requirementBlocks(content) {
24368
+ const parts = content.split(/^### Requirement:/m);
24369
+ return parts.slice(1).map((block) => `### Requirement:${block}`);
24370
+ }
24371
+ function assessSpecMarkdown(content, path) {
24372
+ const issues = [];
24373
+ const clarifications = content.match(NEEDS_CLARIFICATION) ?? [];
24374
+ const needsClarificationCount = clarifications.length;
24375
+ const frIds = uniqueIds(content.match(FR_ID));
24376
+ const scIds = uniqueIds(content.match(SC_ID));
24377
+ const usAcIds = uniqueIds(content.match(US_AC));
24378
+ const hasTraceIds = frIds.length > 0 && (scIds.length > 0 || usAcIds.length > 0);
24379
+ if (PLACEHOLDER.test(content)) {
24380
+ issues.push({
24381
+ code: "PLACEHOLDER",
24382
+ message: 'Spec still contains a "(To be completed" placeholder',
24383
+ path,
24384
+ severity: "error"
24385
+ });
24386
+ }
24387
+ if (needsClarificationCount > MAX_CLARIFICATIONS) {
24388
+ issues.push({
24389
+ code: "TOO_MANY_CLARIFICATIONS",
24390
+ message: `Found ${needsClarificationCount} [NEEDS CLARIFICATION] markers (max ${MAX_CLARIFICATIONS})`,
24391
+ path,
24392
+ severity: "error"
24393
+ });
24394
+ }
24395
+ if (!hasTraceIds) {
24396
+ issues.push({
24397
+ code: "MISSING_TRACE_IDS",
24398
+ message: "Spec MUST include FR-### identifiers and SC-### (or US#/AC#) success criteria",
24399
+ path,
24400
+ severity: "error"
24401
+ });
24402
+ }
24403
+ const blocks = requirementBlocks(content);
24404
+ if (blocks.length === 0) {
24405
+ issues.push({
24406
+ code: "MISSING_REQUIREMENT",
24407
+ message: 'Spec MUST include at least one "### Requirement:" block',
24408
+ path,
24409
+ severity: "error"
24410
+ });
24411
+ }
24412
+ for (const block of blocks) {
24413
+ const heading = block.split(`
24414
+ `, 1)[0] ?? "";
24415
+ if (!RFC2119.test(block)) {
24416
+ issues.push({
24417
+ code: "MISSING_RFC2119",
24418
+ message: `Requirement "${heading}" MUST use RFC 2119 keywords (MUST/SHALL/SHOULD/MAY)`,
24419
+ path,
24420
+ severity: "error"
24421
+ });
24422
+ }
24423
+ if (!GWT.test(block)) {
24424
+ issues.push({
24425
+ code: "MISSING_GWT",
24426
+ message: `Requirement "${heading}" MUST include a Given/When/Then scenario`,
24427
+ path,
24428
+ severity: "error"
24429
+ });
24430
+ }
24431
+ }
24432
+ if (HOW_LEAK.test(content) && /specs[/\\]/.test(path.replace(/\\/g, "/"))) {
24433
+ issues.push({
24434
+ code: "HOW_IN_SPEC",
24435
+ message: "Spec describes stack/HOW details; keep WHAT here and HOW in design.md",
24436
+ path,
24437
+ severity: "warning"
24438
+ });
24439
+ }
24440
+ const requirements = frIds.map((id) => ({ id, text: id }));
24441
+ const successCriteria = [...scIds, ...usAcIds].map((id) => ({ id, text: id }));
24442
+ const errors3 = issues.filter((i) => i.severity === "error");
24443
+ return {
24444
+ valid: errors3.length === 0,
24445
+ needsClarificationCount,
24446
+ stopForClarify: needsClarificationCount > 0 && needsClarificationCount <= MAX_CLARIFICATIONS,
24447
+ issues,
24448
+ requirements,
24449
+ successCriteria
24450
+ };
24451
+ }
24452
+ function mergeSpecQualityReports(reports) {
24453
+ if (reports.length === 0) {
24454
+ return {
24455
+ valid: true,
24456
+ needsClarificationCount: 0,
24457
+ stopForClarify: false,
24458
+ issues: [],
24459
+ requirements: [],
24460
+ successCriteria: []
24461
+ };
24462
+ }
24463
+ const issues = reports.flatMap((r) => r.issues);
24464
+ const requirements = reports.flatMap((r) => r.requirements);
24465
+ const successCriteria = reports.flatMap((r) => r.successCriteria);
24466
+ const needsClarificationCount = reports.reduce((sum, r) => sum + r.needsClarificationCount, 0);
24467
+ return {
24468
+ valid: issues.every((i) => i.severity !== "error"),
24469
+ needsClarificationCount,
24470
+ stopForClarify: needsClarificationCount > 0 && needsClarificationCount <= MAX_CLARIFICATIONS,
24471
+ issues,
24472
+ requirements,
24473
+ successCriteria
24474
+ };
24475
+ }
24476
+ async function walkMarkdown(dir) {
24477
+ const out = [];
24478
+ let entries;
24479
+ try {
24480
+ entries = await readdir8(dir, { withFileTypes: true });
24481
+ } catch {
24482
+ return out;
24483
+ }
24484
+ for (const entry of entries) {
24485
+ const full = join20(dir, entry.name);
24486
+ if (entry.isDirectory()) {
24487
+ out.push(...await walkMarkdown(full));
24488
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
24489
+ out.push(full);
24490
+ }
24491
+ }
24492
+ return out;
24493
+ }
24494
+ async function assessChangeFolder(projectRoot, changePath) {
24495
+ const root = resolve27(projectRoot);
24496
+ const changeRoot = resolve27(root, changePath);
24497
+ if (!changeRoot.startsWith(root)) {
24498
+ return {
24499
+ valid: false,
24500
+ needsClarificationCount: 0,
24501
+ stopForClarify: false,
24502
+ issues: [
24503
+ {
24504
+ code: "PATH_ESCAPE",
24505
+ message: "Change path escapes the project root",
24506
+ path: changePath,
24507
+ severity: "error"
24508
+ }
24509
+ ],
24510
+ requirements: [],
24511
+ successCriteria: []
24512
+ };
24513
+ }
24514
+ const specDir = join20(changeRoot, "specs");
24515
+ const specFiles = await walkMarkdown(specDir);
24516
+ const extra = ["proposal.md", "design.md"].map((name) => join20(changeRoot, name));
24517
+ const reports = [];
24518
+ for (const file of specFiles) {
24519
+ const content = await readFile28(file, "utf-8");
24520
+ reports.push(assessSpecMarkdown(content, relative5(root, file).replace(/\\/g, "/")));
24521
+ }
24522
+ for (const file of extra) {
24523
+ let content;
24524
+ try {
24525
+ content = await readFile28(file, "utf-8");
24526
+ } catch {
24527
+ continue;
24528
+ }
24529
+ const rel = relative5(root, file).replace(/\\/g, "/");
24530
+ if (PLACEHOLDER.test(content)) {
24531
+ reports.push({
24532
+ valid: false,
24533
+ needsClarificationCount: 0,
24534
+ stopForClarify: false,
24535
+ issues: [
24536
+ {
24537
+ code: "PLACEHOLDER",
24538
+ message: 'Artifact still contains a "(To be completed" placeholder',
24539
+ path: rel,
24540
+ severity: "error"
24541
+ }
24542
+ ],
24543
+ requirements: [],
24544
+ successCriteria: []
24545
+ });
24546
+ }
24547
+ }
24548
+ if (specFiles.length === 0) {
24549
+ reports.push({
24550
+ valid: false,
24551
+ needsClarificationCount: 0,
24552
+ stopForClarify: false,
24553
+ issues: [
24554
+ {
24555
+ code: "MISSING_SPEC",
24556
+ message: "Change folder has no specs/*.md artifacts",
24557
+ path: `${changePath.replace(/\\/g, "/")}/specs`,
24558
+ severity: "error"
24559
+ }
24560
+ ],
24561
+ requirements: [],
24562
+ successCriteria: []
24563
+ });
24564
+ }
24565
+ return mergeSpecQualityReports(reports);
24566
+ }
24567
+ var RFC2119, FR_ID, SC_ID, US_AC, GWT, NEEDS_CLARIFICATION, PLACEHOLDER, HOW_LEAK, MAX_CLARIFICATIONS = 3;
24568
+ var init_spec_quality = __esm(() => {
24569
+ RFC2119 = /\b(MUST(?: NOT)?|SHALL(?: NOT)?|SHOULD(?: NOT)?|MAY)\b/;
24570
+ FR_ID = /\bFR-\d{3}\b/g;
24571
+ SC_ID = /\bSC-\d{3}\b/g;
24572
+ US_AC = /\b(?:US|AC)#?\d+\b/g;
24573
+ GWT = /\bGIVEN\b[\s\S]{0,400}\bWHEN\b[\s\S]{0,400}\bTHEN\b/i;
24574
+ NEEDS_CLARIFICATION = /\[NEEDS CLARIFICATION:[^\]]*\]/gi;
24575
+ PLACEHOLDER = /\(To be completed/i;
24576
+ HOW_LEAK = /\b(TypeScript|PostgreSQL|Django|React|Spring Boot|package\.json|implements class)\b/i;
24577
+ });
24578
+
24579
+ // src/core/openspec/spec-analyzer.ts
24580
+ import { readFile as readFile29 } from "node:fs/promises";
24581
+ import { join as join21, resolve as resolve28 } from "node:path";
24582
+ function unique(ids) {
24583
+ return [...new Set(ids)];
24584
+ }
24585
+ function coverage(mapped, total) {
24586
+ if (total === 0)
24587
+ return 100;
24588
+ return Math.round(mapped / total * 1000) / 10;
24589
+ }
24590
+ function mentionsId(haystack, id) {
24591
+ return haystack.includes(id);
24592
+ }
24593
+ function testSection(tasksMarkdown) {
24594
+ const lines = tasksMarkdown.split(`
24595
+ `);
24596
+ const kept = [];
24597
+ for (const line of lines) {
24598
+ if (/\btest\b|TDD|RED|suite/i.test(line)) {
24599
+ kept.push(line);
24600
+ }
24601
+ }
24602
+ return kept.join(`
24603
+ `);
24604
+ }
24605
+ function analyzeSpecArtifacts(input) {
24606
+ const quality = assessSpecMarkdown(input.specMarkdown, `${input.changePath}/specs`);
24607
+ const frIds = unique(quality.requirements.map((r) => r.id));
24608
+ const scIds = unique(quality.successCriteria.map((s) => s.id));
24609
+ const tasks = input.tasksMarkdown;
24610
+ const testTasks = testSection(tasks);
24611
+ const mappedFr = frIds.filter((id) => mentionsId(tasks, id));
24612
+ const mappedSc = scIds.filter((id) => mentionsId(tasks, id));
24613
+ const mappedToTests = frIds.filter((id) => mentionsId(testTasks, id));
24614
+ const findings = [];
24615
+ for (const issue of quality.issues.filter((i) => i.severity === "error")) {
24616
+ findings.push({
24617
+ severity: "CRITICAL",
24618
+ code: issue.code,
24619
+ message: issue.message
24620
+ });
24621
+ }
24622
+ if (input.tddRequired) {
24623
+ for (const id of frIds) {
24624
+ if (!mappedToTests.includes(id)) {
24625
+ findings.push({
24626
+ severity: "CRITICAL",
24627
+ code: "TDD_FR_UNTESTED",
24628
+ message: `${id} has no test task while TDD required is yes`
24629
+ });
24630
+ }
24631
+ }
24632
+ if (input.hasTddEvidence === false && frIds.length > 0) {
24633
+ findings.push({
24634
+ severity: "HIGH",
24635
+ code: "TDD_EVIDENCE_MISSING",
24636
+ message: "TDD required but no verification-runner suite evidence was found"
24637
+ });
24638
+ }
24639
+ }
24640
+ const policy = input.policyText ?? "";
24641
+ const design = input.designMarkdown ?? "";
24642
+ if (/\bMUST\b/.test(policy) && /skip TDD|ignore AGENTS|bypass review/i.test(design)) {
24643
+ findings.push({
24644
+ severity: "CRITICAL",
24645
+ code: "POLICY_CONFLICT",
24646
+ message: "design.md conflicts with a MUST in AGENTS.md / BACKLOG Global"
24647
+ });
24648
+ }
24649
+ if (quality.stopForClarify) {
24650
+ findings.push({
24651
+ severity: "HIGH",
24652
+ code: "NEEDS_CLARIFICATION",
24653
+ message: "Spec has [NEEDS CLARIFICATION] markers — run ccep evaluate and stop"
24654
+ });
24655
+ }
24656
+ const frCoveragePct = coverage(mappedFr.length, frIds.length);
24657
+ const scCoveragePct = coverage(mappedSc.length, scIds.length);
24658
+ const testCoveragePct = coverage(mappedToTests.length, frIds.length);
24659
+ const stop = findings.some((f) => f.severity === "CRITICAL");
24660
+ return {
24661
+ changePath: input.changePath,
24662
+ frIds,
24663
+ scIds,
24664
+ mappedFr,
24665
+ mappedSc,
24666
+ mappedToTests,
24667
+ frCoveragePct,
24668
+ scCoveragePct,
24669
+ testCoveragePct,
24670
+ findings,
24671
+ stop,
24672
+ quality
24673
+ };
24674
+ }
24675
+ async function analyzeChangeFolder(projectRoot, changePath, options) {
24676
+ const changeRoot = resolve28(projectRoot, changePath);
24677
+ const specPath = join21(changeRoot, "specs", "delta.md");
24678
+ let specMarkdown = "";
24679
+ try {
24680
+ specMarkdown = await readFile29(specPath, "utf-8");
24681
+ } catch {
24682
+ specMarkdown = "";
24683
+ }
24684
+ let tasksMarkdown = "";
24685
+ try {
24686
+ tasksMarkdown = await readFile29(join21(changeRoot, "tasks.md"), "utf-8");
24687
+ } catch {
24688
+ tasksMarkdown = "";
24689
+ }
24690
+ let designMarkdown;
24691
+ try {
24692
+ designMarkdown = await readFile29(join21(changeRoot, "design.md"), "utf-8");
24693
+ } catch {
24694
+ designMarkdown = undefined;
24695
+ }
24696
+ return analyzeSpecArtifacts({
24697
+ changePath,
24698
+ specMarkdown,
24699
+ tasksMarkdown,
24700
+ designMarkdown,
24701
+ policyText: options.policyText,
24702
+ tddRequired: options.tddRequired,
24703
+ hasTddEvidence: options.hasTddEvidence
24704
+ });
24705
+ }
24706
+ var init_spec_analyzer = __esm(() => {
24707
+ init_spec_quality();
24708
+ });
24709
+
24201
24710
  // src/commands/openspec.command.ts
24202
24711
  var exports_openspec_command = {};
24203
24712
  __export(exports_openspec_command, {
24204
24713
  openspecCommand: () => openspecCommand
24205
24714
  });
24206
- import { readFile as readFile28 } from "node:fs/promises";
24207
- import { resolve as resolve27 } from "node:path";
24715
+ import { readFile as readFile30 } from "node:fs/promises";
24716
+ import { resolve as resolve29 } from "node:path";
24208
24717
  async function openspecCommand(options) {
24209
24718
  const { subcommand, itemId, projectRoot, output, reason } = options;
24210
24719
  switch (subcommand) {
@@ -24214,6 +24723,8 @@ async function openspecCommand(options) {
24214
24723
  return handleScan(projectRoot);
24215
24724
  case "plan":
24216
24725
  return handlePlan(projectRoot, itemId);
24726
+ case "analyze":
24727
+ return handleAnalyze(projectRoot, itemId);
24217
24728
  case "status":
24218
24729
  return handleStatus2(projectRoot);
24219
24730
  case "next":
@@ -24246,7 +24757,7 @@ function fail(command, errors3, extra = {}) {
24246
24757
  };
24247
24758
  }
24248
24759
  async function persistBacklog(projectRoot, content) {
24249
- await writeFileAtomic(resolve27(projectRoot, BACKLOG_FILENAME), content);
24760
+ await writeFileAtomic(resolve29(projectRoot, BACKLOG_FILENAME), content);
24250
24761
  }
24251
24762
  async function persistState(projectRoot, state) {
24252
24763
  const written = await writeOpenspecState(projectRoot, state);
@@ -24256,7 +24767,7 @@ async function persistState(projectRoot, state) {
24256
24767
  return { ok: true };
24257
24768
  }
24258
24769
  async function readBacklogMarkdown(projectRoot) {
24259
- return readFile28(resolve27(projectRoot, BACKLOG_FILENAME), "utf-8");
24770
+ return readFile30(resolve29(projectRoot, BACKLOG_FILENAME), "utf-8");
24260
24771
  }
24261
24772
  async function transitionItem(projectRoot, item, to, progress) {
24262
24773
  const content = await readBacklogMarkdown(projectRoot);
@@ -24289,14 +24800,31 @@ async function handleValidate(projectRoot) {
24289
24800
  };
24290
24801
  }
24291
24802
  const report = validateBacklog(loadResult.data);
24803
+ const errors3 = report.errors.map((e) => e.message);
24804
+ const recommendations = [...report.recommendations];
24805
+ let specValid = true;
24806
+ const stateResult = await loadOpenspecState(projectRoot);
24807
+ const activeId = stateResult.success ? stateResult.data.activeItemId : undefined;
24808
+ const changePath = activeId && stateResult.success ? stateResult.data.changePaths[activeId] : undefined;
24809
+ if (changePath && !changePath.includes("/archive/")) {
24810
+ const specReport = await assessChangeFolder(projectRoot, changePath);
24811
+ specValid = specReport.valid;
24812
+ for (const issue of specReport.issues.filter((i) => i.severity === "error")) {
24813
+ errors3.push(`${issue.path}: ${issue.message}`);
24814
+ }
24815
+ if (specReport.stopForClarify) {
24816
+ recommendations.push("Spec has [NEEDS CLARIFICATION] markers — run ccep evaluate and wait for human input.");
24817
+ }
24818
+ }
24819
+ const valid = report.valid && specValid;
24292
24820
  return {
24293
- code: report.valid ? 0 : 1,
24821
+ code: valid ? 0 : 1,
24294
24822
  data: {
24295
- success: report.valid,
24823
+ success: valid,
24296
24824
  command: "openspec validate",
24297
- valid: report.valid,
24298
- errors: report.errors.map((e) => e.message),
24299
- recommendations: report.recommendations,
24825
+ valid,
24826
+ errors: errors3,
24827
+ recommendations,
24300
24828
  itemCount: loadResult.data.items.length,
24301
24829
  archiveCount: loadResult.data.archive.length
24302
24830
  }
@@ -24376,7 +24904,10 @@ async function handlePlan(projectRoot, itemId) {
24376
24904
  const planned = planTaskCardsForItem(item, doc, existingState.taskCards, existingState.itemSnapshots?.[item.id]);
24377
24905
  const taskCards = planned.cards;
24378
24906
  await ensureOpenspecConfig(projectRoot);
24379
- const changePath = await generateOpenspecChange(projectRoot, item, taskCards);
24907
+ const changePath = await generateOpenspecChange(projectRoot, item, taskCards, {
24908
+ tddRequired: doc.global.tddRequired,
24909
+ acceptanceCriteria: item.acceptanceCriteria
24910
+ });
24380
24911
  const newState = {
24381
24912
  ...existingState,
24382
24913
  version: 1,
@@ -24415,6 +24946,69 @@ async function handlePlan(projectRoot, itemId) {
24415
24946
  }
24416
24947
  };
24417
24948
  }
24949
+ async function handleAnalyze(projectRoot, itemId) {
24950
+ const command = "openspec analyze";
24951
+ const stateResult = await loadOpenspecState(projectRoot);
24952
+ if (!stateResult.success) {
24953
+ return fail(command, [stateResult.error.message]);
24954
+ }
24955
+ const targetId = itemId ?? stateResult.data.activeItemId;
24956
+ if (!targetId) {
24957
+ return fail(command, ["No active change. Run openspec plan or pass an item id."]);
24958
+ }
24959
+ const changePath = stateResult.data.changePaths[targetId];
24960
+ if (!changePath || changePath.includes("/archive/")) {
24961
+ return fail(command, [`No active change folder for ${targetId}`]);
24962
+ }
24963
+ const backlog = await loadBacklog(projectRoot);
24964
+ const tddRequired = backlog.success ? backlog.data.global.tddRequired : false;
24965
+ const policyParts = [];
24966
+ for (const name of ["AGENTS.md", "BACKLOG.md"]) {
24967
+ try {
24968
+ policyParts.push(await readFile30(resolve29(projectRoot, name), "utf-8"));
24969
+ } catch {}
24970
+ }
24971
+ const cards = itemCards(stateResult.data, targetId);
24972
+ const tddCards = cards.filter((c) => c.phase === "test" || c.phase === "implement");
24973
+ let hasTddEvidence;
24974
+ if (tddCards.length > 0) {
24975
+ hasTddEvidence = false;
24976
+ for (const card of tddCards) {
24977
+ if (await hasTddRunnerEvidence(projectRoot, card.id)) {
24978
+ hasTddEvidence = true;
24979
+ break;
24980
+ }
24981
+ }
24982
+ }
24983
+ const report = await analyzeChangeFolder(projectRoot, changePath, {
24984
+ tddRequired,
24985
+ policyText: policyParts.join(`
24986
+ `),
24987
+ hasTddEvidence
24988
+ });
24989
+ const data = SpecAnalyzeReportSchema.parse({
24990
+ changePath: report.changePath,
24991
+ frIds: report.frIds,
24992
+ scIds: report.scIds,
24993
+ mappedFr: report.mappedFr,
24994
+ mappedSc: report.mappedSc,
24995
+ mappedToTests: report.mappedToTests,
24996
+ frCoveragePct: report.frCoveragePct,
24997
+ scCoveragePct: report.scCoveragePct,
24998
+ testCoveragePct: report.testCoveragePct,
24999
+ findings: report.findings,
25000
+ stop: report.stop
25001
+ });
25002
+ return {
25003
+ code: report.stop ? 1 : 0,
25004
+ data: {
25005
+ success: !report.stop,
25006
+ command,
25007
+ ...data,
25008
+ stopForClarify: report.quality.stopForClarify
25009
+ }
25010
+ };
25011
+ }
24418
25012
  async function handleStatus2(projectRoot) {
24419
25013
  const loadResult = await loadBacklog(projectRoot);
24420
25014
  const stateResult = await loadOpenspecState(projectRoot);
@@ -24563,6 +25157,16 @@ async function handleDone(projectRoot, cardId) {
24563
25157
  const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
24564
25158
  if (!itemLoaded.ok)
24565
25159
  return itemLoaded.response;
25160
+ const backlog = await loadBacklog(projectRoot);
25161
+ const tddRequired = backlog.success ? backlog.data.global.tddRequired : false;
25162
+ if (tddRequired && (card.phase === "test" || card.phase === "implement")) {
25163
+ const evidenced = await hasTddRunnerEvidence(projectRoot, cardId);
25164
+ if (!evidenced) {
25165
+ return fail(command, [
25166
+ `Card ${cardId} (${card.phase}) requires verification-runner TDD evidence. Run captureTddSuiteEvidence before openspec done.`
25167
+ ]);
25168
+ }
25169
+ }
24566
25170
  const state = setTaskCardStatus(loaded.state, cardId, "done");
24567
25171
  const cards = itemCards(state, card.backlogId);
24568
25172
  const doneCount = cards.filter((c) => c.status === "done").length;
@@ -24573,7 +25177,10 @@ async function handleDone(projectRoot, cardId) {
24573
25177
  return fail(command, [written.error]);
24574
25178
  const changePath = state.changePaths[card.backlogId];
24575
25179
  if (changePath) {
24576
- await writeTasksMarkdown(projectRoot, changePath, cards);
25180
+ await writeTasksMarkdown(projectRoot, changePath, cards, {
25181
+ tddRequired,
25182
+ acceptanceCriteria: itemLoaded.item.acceptanceCriteria
25183
+ });
24577
25184
  }
24578
25185
  const nextStatus = allDone ? "REVIEW" : "IN_PROGRESS";
24579
25186
  const transition = await transitionItem(projectRoot, itemLoaded.item, nextStatus, progress);
@@ -24659,6 +25266,12 @@ async function handleArchive(projectRoot, itemId) {
24659
25266
  `Cannot archive ${itemId}: global.reviewRequired needs the review phase card done`
24660
25267
  ]);
24661
25268
  }
25269
+ const passed = await hasPassingScorecard(projectRoot, itemId);
25270
+ if (!passed) {
25271
+ return fail(command, [
25272
+ `Cannot archive ${itemId}: global.reviewRequired needs a PASS scorecard for this backlog id`
25273
+ ]);
25274
+ }
24662
25275
  }
24663
25276
  if (itemLoaded.item.status !== "DONE") {
24664
25277
  const toDone = await transitionItem(projectRoot, itemLoaded.item, "DONE", 100);
@@ -24697,47 +25310,302 @@ async function handleArchive(projectRoot, itemId) {
24697
25310
  }
24698
25311
  };
24699
25312
  }
24700
- var KNOWN_SUBCOMMANDS = "validate, scan, plan, status, next, start, done, block, archive";
25313
+ var KNOWN_SUBCOMMANDS = "validate, scan, plan, analyze, status, next, start, done, block, archive";
24701
25314
  var init_openspec_command = __esm(() => {
24702
25315
  init_backlog_parser();
24703
25316
  init_backlog_validator();
24704
25317
  init_backlog_scanner();
24705
25318
  init_backlog_planner();
24706
25319
  init_openspec_generator();
25320
+ init_spec_quality();
25321
+ init_spec_analyzer();
25322
+ init_schemas();
25323
+ init_verification_runner();
25324
+ init_outcome_store();
24707
25325
  init_openspec_state();
24708
25326
  init_backlog_parser();
24709
25327
  init_loop_engine();
24710
25328
  });
24711
25329
 
24712
- // src/core/evaluation/scorecard-constants.ts
24713
- var SCORECARD_CRITERIA_DEF;
24714
- var init_scorecard_constants = __esm(() => {
24715
- SCORECARD_CRITERIA_DEF = [
24716
- { id: "acceptance", label: "Acceptance criteria met", weight: 0.3 },
24717
- { id: "minimal_diff", label: "Minimal diff (no scope creep)", weight: 0.2 },
24718
- { id: "tests", label: "Tests present and passing", weight: 0.15 },
24719
- { id: "regressions", label: "No regressions introduced", weight: 0.15 },
24720
- { id: "conventions", label: "Code follows project conventions", weight: 0.1 },
24721
- { id: "documentation", label: "Documentation updated if required", weight: 0.05 },
24722
- { id: "context_discipline", label: "Context discipline", weight: 0.05 },
24723
- { id: "cc_gain", label: "Complexity diffusion (cc-gain)", weight: 0.05 }
24724
- ];
24725
- });
24726
-
24727
- // src/core/evaluation/scorecard-calculator.ts
24728
- function createDefaultCriteria(overrides = {}) {
24729
- return SCORECARD_CRITERIA_DEF.map((def) => {
24730
- const o = overrides[def.id];
25330
+ // src/core/hooks/hook-runner.ts
25331
+ function normalizeCommand(raw) {
25332
+ return raw.replace(/\s+/g, " ").trim();
25333
+ }
25334
+ function stripGitBinary(command) {
25335
+ return command.replace(/(?:^|[\\/])git(?:\.exe)?\b/i, "git");
25336
+ }
25337
+ function evaluatePath(filePath) {
25338
+ if (SENSITIVE_PATH.test(filePath.replace(/\//g, "\\")) || SENSITIVE_PATH.test(filePath)) {
25339
+ return deny(MSG_SECRET);
25340
+ }
25341
+ return allow();
25342
+ }
25343
+ function evaluateCommand(rawCommand) {
25344
+ const command = stripGitBinary(normalizeCommand(rawCommand));
25345
+ if (!command)
25346
+ return allow();
25347
+ if (SENSITIVE_PATH.test(command) && READ_LIKE.test(command)) {
25348
+ return deny(MSG_SECRET);
25349
+ }
25350
+ if (isGitPush(command))
25351
+ return deny(MSG_AUTHORITY);
25352
+ if (/^git\s+reset\s+--hard\b/i.test(command))
25353
+ return deny(MSG_AUTHORITY);
25354
+ if (isForcedClean(command))
25355
+ return deny(MSG_AUTHORITY);
25356
+ if (isForcedBranchDelete(command))
25357
+ return deny(MSG_AUTHORITY);
25358
+ if (isDestructiveCheckout(command))
25359
+ return deny(MSG_AUTHORITY);
25360
+ if (isDestructiveRestore(command))
25361
+ return deny(MSG_AUTHORITY);
25362
+ if (/^git\s+rebase\b/i.test(command))
25363
+ return deny(MSG_POLICY);
25364
+ if (/^git\s+push\b/i.test(command) && /(?:\s--force\b|\s-f\b)/.test(command)) {
25365
+ return deny(MSG_AUTHORITY);
25366
+ }
25367
+ if (/^rm\s+-rf\s+(\*|[\\/])/i.test(command))
25368
+ return deny(MSG_POLICY);
25369
+ if (/^sudo\s+/i.test(command))
25370
+ return deny(MSG_POLICY);
25371
+ if (/(?:curl|wget)\b.+\|\s*(sh|bash|zsh|cmd)\b/i.test(command))
25372
+ return deny(MSG_POLICY);
25373
+ if (/^chmod\s+777\b/i.test(command))
25374
+ return deny(MSG_POLICY);
25375
+ if (/^dd\s+/i.test(command))
25376
+ return deny(MSG_POLICY);
25377
+ if (/^mkfs\b/i.test(command))
25378
+ return deny(MSG_POLICY);
25379
+ if (/^git\s+commit\b/i.test(command) || /^git\s+switch\b/i.test(command) || /^docker\s+compose\b/i.test(command)) {
25380
+ return ask();
25381
+ }
25382
+ return allow();
25383
+ }
25384
+ function evaluatePreTool(input) {
25385
+ if (input.filePath) {
25386
+ const pathVerdict = evaluatePath(input.filePath);
25387
+ if (pathVerdict.action === "deny")
25388
+ return pathVerdict;
25389
+ }
25390
+ if (input.command) {
25391
+ return evaluateCommand(input.command);
25392
+ }
25393
+ return allow();
25394
+ }
25395
+ function formatHookOutput(verdict, format) {
25396
+ if (format === "agy") {
25397
+ if (verdict.action === "deny") {
25398
+ return JSON.stringify({ decision: "deny", error: verdict.message });
25399
+ }
25400
+ if (verdict.action === "ask") {
25401
+ return JSON.stringify({ decision: "ask" });
25402
+ }
25403
+ return JSON.stringify({ decision: "allow" });
25404
+ }
25405
+ return "";
25406
+ }
25407
+ function claudeExitCode(verdict) {
25408
+ return verdict.action === "deny" ? 2 : 0;
25409
+ }
25410
+ function parseAgyPayload(raw) {
25411
+ if (!raw.trim())
25412
+ return {};
25413
+ try {
25414
+ const parsed = JSON.parse(raw);
25415
+ const args = parsed.arguments ?? {};
24731
25416
  return {
24732
- id: def.id,
24733
- label: def.label,
24734
- weight: def.weight,
24735
- score: o?.score ?? 2,
24736
- notes: o?.notes,
24737
- autoSuggested: o?.autoSuggested
25417
+ toolName: parsed.toolName,
25418
+ command: args.CommandLine,
25419
+ filePath: args.TargetFile ?? args.AbsolutePath
24738
25420
  };
24739
- });
24740
- }
25421
+ } catch {
25422
+ return {};
25423
+ }
25424
+ }
25425
+ function formatSessionStart(lines) {
25426
+ return lines.filter(Boolean).join(`
25427
+ `);
25428
+ }
25429
+ function allow() {
25430
+ return { action: "allow", message: "", exitCode: 0 };
25431
+ }
25432
+ function ask() {
25433
+ return { action: "ask", message: "", exitCode: 0 };
25434
+ }
25435
+ function deny(message) {
25436
+ return { action: "deny", message, exitCode: 2 };
25437
+ }
25438
+ function isGitPush(command) {
25439
+ return /^git\s+push\b/i.test(command);
25440
+ }
25441
+ function isForcedClean(command) {
25442
+ if (!/^git\s+clean\b/i.test(command))
25443
+ return false;
25444
+ if (/\s--force\b/.test(command))
25445
+ return true;
25446
+ const flagMatch = command.match(/\s-([a-zA-Z]+)/g);
25447
+ return (flagMatch ?? []).some((part) => part.includes("f") || part.includes("F"));
25448
+ }
25449
+ function isForcedBranchDelete(command) {
25450
+ if (!/^git\s+branch\b/i.test(command))
25451
+ return false;
25452
+ if (/\s-D\b/.test(command))
25453
+ return true;
25454
+ return /\s--delete\b/.test(command) && /\s--force\b/.test(command);
25455
+ }
25456
+ function isDestructiveCheckout(command) {
25457
+ if (!/^git\s+checkout\b/i.test(command))
25458
+ return false;
25459
+ if (/\s(?:-f|--force|--discard-changes|--theirs|--ours)\b/.test(command))
25460
+ return true;
25461
+ return /^git\s+checkout\s+-\s*$/i.test(command);
25462
+ }
25463
+ function isDestructiveRestore(command) {
25464
+ if (!/^git\s+restore\b/i.test(command))
25465
+ return false;
25466
+ return /\s(?:-f|--force|--discard-changes|--theirs|--ours)\b/.test(command);
25467
+ }
25468
+ var SENSITIVE_PATH, READ_LIKE, MSG_SECRET = "Bloqueado: intento de leer archivos sensibles", MSG_AUTHORITY = "Bloqueado: El agente no tiene autoridad sobre este comando.", MSG_POLICY = "Bloqueado: Command violated security policy";
25469
+ var init_hook_runner = __esm(() => {
25470
+ SENSITIVE_PATH = /(?:\.env\b|secrets[/\\]|id_rsa|\.pem\b|\.key\b|(?:^|[/\\])\.ssh(?:[/\\]|$)|(?:^|[/\\])\.aws(?:[/\\]|$)|(?:^|[/\\])\.kube(?:[/\\]|$))/i;
25471
+ READ_LIKE = /(?:^|[\s;&|])(cat|less|more|head|tail|type|Get-Content|cp|copy|mv|move|scp)\b/i;
25472
+ });
25473
+
25474
+ // src/commands/hook.command.ts
25475
+ var exports_hook_command = {};
25476
+ __export(exports_hook_command, {
25477
+ readStdinText: () => readStdinText,
25478
+ hookCommand: () => hookCommand
25479
+ });
25480
+ import { spawnSync } from "node:child_process";
25481
+ import { existsSync as existsSync7 } from "node:fs";
25482
+ import { resolve as resolve30 } from "node:path";
25483
+ function detectFormat(explicit, stdinText) {
25484
+ if (explicit)
25485
+ return explicit;
25486
+ const trimmed = stdinText.trim();
25487
+ if (trimmed.startsWith("{"))
25488
+ return "agy";
25489
+ return "claude";
25490
+ }
25491
+ async function readStdinText() {
25492
+ if (process.stdin.isTTY)
25493
+ return "";
25494
+ const chunks = [];
25495
+ for await (const chunk of process.stdin) {
25496
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
25497
+ }
25498
+ return Buffer.concat(chunks).toString("utf8");
25499
+ }
25500
+ function resolvePreToolInput(options, stdinText) {
25501
+ const fromStdin = parseAgyPayload(stdinText);
25502
+ return {
25503
+ command: options.command ?? process.env.CLAUDE_TOOL_INPUT_COMMAND ?? fromStdin.command,
25504
+ filePath: options.filePath ?? process.env.CLAUDE_TOOL_INPUT_FILE_PATH ?? fromStdin.filePath,
25505
+ toolName: fromStdin.toolName
25506
+ };
25507
+ }
25508
+ async function sessionLines(projectRoot) {
25509
+ const backlogPath = resolve30(projectRoot, "BACKLOG.md");
25510
+ const profilePath = resolve30(projectRoot, ".codeconductor", "evaluation", "execution-profile.yml");
25511
+ return [
25512
+ "[CodeConductor] Session start",
25513
+ "Skills: openspec, backlog, evaluation, testing-tdd — invoke via /cc-* then CLI.",
25514
+ existsSync7(backlogPath) ? "BACKLOG.md present — run: bun run dev openspec status" : "No BACKLOG.md — author with /cc-backlog if you need an item queue.",
25515
+ existsSync7(profilePath) ? "Scorecard profile on disk — run: bun run dev scorecard models" : "Scorecard: bun run dev scorecard create --task <id> --from-diff",
25516
+ "Hooks: bun run dev hook pre-tool | post-tool | session-start",
25517
+ "Eval suites: bun run dev scorecard suite-run --suite workflow-gates"
25518
+ ];
25519
+ }
25520
+ async function hookCommand(options) {
25521
+ const event = options.event;
25522
+ const stdinText = options.stdinText ?? "";
25523
+ const format = detectFormat(options.format, stdinText);
25524
+ if (event === "session-start") {
25525
+ const text = formatSessionStart(await sessionLines(options.projectRoot));
25526
+ process.stdout.write(`${text}
25527
+ `);
25528
+ return { code: 0, data: { success: true, command: "hook session-start" } };
25529
+ }
25530
+ if (event === "post-tool") {
25531
+ const filePath = options.filePath ?? process.env.CLAUDE_TOOL_INPUT_FILE_PATH ?? parseAgyPayload(stdinText).filePath;
25532
+ formatWrittenFile(filePath);
25533
+ if (format === "agy") {
25534
+ process.stdout.write(`{}
25535
+ `);
25536
+ }
25537
+ return { code: 0, data: { success: true, command: "hook post-tool" } };
25538
+ }
25539
+ const input = resolvePreToolInput(options, stdinText);
25540
+ const verdict = evaluatePreTool(input);
25541
+ if (format === "agy") {
25542
+ process.stdout.write(`${formatHookOutput(verdict, "agy")}
25543
+ `);
25544
+ return {
25545
+ code: 0,
25546
+ data: { success: verdict.action !== "deny", command: "hook pre-tool", verdict }
25547
+ };
25548
+ }
25549
+ if (verdict.action === "deny") {
25550
+ process.stderr.write(`${verdict.message}
25551
+ `);
25552
+ }
25553
+ return {
25554
+ code: claudeExitCode(verdict),
25555
+ data: { success: verdict.action !== "deny", command: "hook pre-tool", verdict }
25556
+ };
25557
+ }
25558
+ function formatWrittenFile(filePath) {
25559
+ if (!filePath || !existsSync7(filePath))
25560
+ return;
25561
+ const prettier = /\.(ts|tsx|js|jsx|mjs|cjs|json|jsonc|md|mdx|css|scss|html|astro|ya?ml)$/i;
25562
+ const eslint = /\.(ts|tsx|js|jsx|mjs|cjs)$/i;
25563
+ const python = /\.py$/i;
25564
+ const opts = { stdio: "ignore", windowsHide: true };
25565
+ if (prettier.test(filePath)) {
25566
+ spawnSync("npx", ["--no-install", "prettier", "--write", filePath], opts);
25567
+ }
25568
+ if (eslint.test(filePath)) {
25569
+ spawnSync("npx", ["--no-install", "eslint", "--fix", filePath], opts);
25570
+ }
25571
+ if (python.test(filePath)) {
25572
+ spawnSync("ruff", ["format", filePath], opts);
25573
+ spawnSync("ruff", ["check", "--fix", filePath], opts);
25574
+ }
25575
+ }
25576
+ var init_hook_command = __esm(() => {
25577
+ init_hook_runner();
25578
+ });
25579
+
25580
+ // src/core/evaluation/scorecard-constants.ts
25581
+ var SCORECARD_CRITERIA_DEF;
25582
+ var init_scorecard_constants = __esm(() => {
25583
+ SCORECARD_CRITERIA_DEF = [
25584
+ { id: "acceptance", label: "Acceptance criteria met", weight: 0.3 },
25585
+ { id: "minimal_diff", label: "Minimal diff (no scope creep)", weight: 0.2 },
25586
+ { id: "tests", label: "Tests present and passing", weight: 0.15 },
25587
+ { id: "regressions", label: "No regressions introduced", weight: 0.15 },
25588
+ { id: "conventions", label: "Code follows project conventions", weight: 0.1 },
25589
+ { id: "documentation", label: "Documentation updated if required", weight: 0.05 },
25590
+ { id: "context_discipline", label: "Context discipline", weight: 0.05 },
25591
+ { id: "cc_gain", label: "Complexity diffusion (cc-gain)", weight: 0.05 }
25592
+ ];
25593
+ });
25594
+
25595
+ // src/core/evaluation/scorecard-calculator.ts
25596
+ function createDefaultCriteria(overrides = {}) {
25597
+ return SCORECARD_CRITERIA_DEF.map((def) => {
25598
+ const o = overrides[def.id];
25599
+ return {
25600
+ id: def.id,
25601
+ label: def.label,
25602
+ weight: def.weight,
25603
+ score: o?.score ?? 2,
25604
+ notes: o?.notes,
25605
+ autoSuggested: o?.autoSuggested
25606
+ };
25607
+ });
25608
+ }
24741
25609
  function computeWeightedScore(criteria) {
24742
25610
  let total = 0;
24743
25611
  for (const c of criteria) {
@@ -25106,9 +25974,19 @@ function ccGainToScorecardImpact(result) {
25106
25974
 
25107
25975
  // src/core/evaluation/scorecard-signals.ts
25108
25976
  import { execFileSync as execFileSync3 } from "node:child_process";
25109
- function collectScorecardSignals(projectRoot, scopeFiles) {
25977
+ function collectScorecardSignals(projectRoot, scopeFiles, extra) {
25110
25978
  const findings = [];
25111
25979
  const overrides = {};
25980
+ const scopeViolationCount = extra?.scopeViolationCount;
25981
+ if (scopeViolationCount && scopeViolationCount > 0) {
25982
+ overrides.scope = { score: 0, notes: `Found ${scopeViolationCount} scope violations`, autoSuggested: true };
25983
+ findings.push(`Scope violation: ${scopeViolationCount} files outside scope.`);
25984
+ }
25985
+ const hashMismatch = extra?.hashMismatch;
25986
+ if (hashMismatch) {
25987
+ overrides.tests = { score: 0, notes: "Test tampering detected (hash mismatch)", autoSuggested: true };
25988
+ findings.push("Test tampering detected via test-freeze hash mismatch.");
25989
+ }
25112
25990
  let diff = "";
25113
25991
  try {
25114
25992
  diff = execFileSync3("git", ["diff", "HEAD"], {
@@ -25118,11 +25996,11 @@ function collectScorecardSignals(projectRoot, scopeFiles) {
25118
25996
  });
25119
25997
  } catch {
25120
25998
  findings.push("Could not read git diff; scope audit skipped.");
25121
- return { criteriaOverrides: overrides, findings };
25999
+ return { criteriaOverrides: overrides, findings, scopeViolationCount, hashMismatch };
25122
26000
  }
25123
26001
  if (!diff.trim()) {
25124
26002
  overrides.minimal_diff = { score: 2, notes: "No diff detected", autoSuggested: true };
25125
- return { criteriaOverrides: overrides, findings };
26003
+ return { criteriaOverrides: overrides, findings, scopeViolationCount, hashMismatch };
25126
26004
  }
25127
26005
  const changedFiles = new Set;
25128
26006
  for (const line of diff.split(`
@@ -25159,7 +26037,40 @@ function collectScorecardSignals(projectRoot, scopeFiles) {
25159
26037
  } catch {
25160
26038
  findings.push("Complexity audit skipped.");
25161
26039
  }
25162
- return { criteriaOverrides: overrides, findings };
26040
+ return { criteriaOverrides: overrides, findings, scopeViolationCount, hashMismatch };
26041
+ }
26042
+ function applyAnalyzeSignals(hints, analyze) {
26043
+ const criteriaOverrides = { ...hints.criteriaOverrides };
26044
+ const findings = [...hints.findings];
26045
+ if (analyze.frCoveragePct < 50) {
26046
+ criteriaOverrides.acceptance = {
26047
+ score: 0,
26048
+ notes: `FR coverage ${analyze.frCoveragePct}%`,
26049
+ autoSuggested: true
26050
+ };
26051
+ findings.push(`Acceptance auto-suggested 0: FR coverage ${analyze.frCoveragePct}%.`);
26052
+ } else if (analyze.frCoveragePct === 100 && analyze.scCoveragePct === 100) {
26053
+ criteriaOverrides.acceptance = {
26054
+ score: 3,
26055
+ notes: "All FR/SC mapped to tasks",
26056
+ autoSuggested: true
26057
+ };
26058
+ } else {
26059
+ criteriaOverrides.acceptance = {
26060
+ score: 2,
26061
+ notes: `FR coverage ${analyze.frCoveragePct}%, SC coverage ${analyze.scCoveragePct}%`,
26062
+ autoSuggested: true
26063
+ };
26064
+ }
26065
+ if (analyze.tddRequired && analyze.hasTddEvidence === false) {
26066
+ criteriaOverrides.tests = {
26067
+ score: 0,
26068
+ notes: "TDD required but no verification-runner evidence",
26069
+ autoSuggested: true
26070
+ };
26071
+ findings.push("Tests auto-suggested 0: missing verification-runner evidence.");
26072
+ }
26073
+ return { criteriaOverrides, findings };
25163
26074
  }
25164
26075
  function criteriaFromSignals(hints) {
25165
26076
  return createDefaultCriteria(hints.criteriaOverrides);
@@ -25169,12 +26080,315 @@ var init_scorecard_signals = __esm(() => {
25169
26080
  init_scorecard_calculator();
25170
26081
  });
25171
26082
 
26083
+ // src/core/evaluation/harness-experiment.ts
26084
+ import { existsSync as existsSync8 } from "node:fs";
26085
+ import { cp, mkdir as mkdir20, readFile as readFile31, readdir as readdir9, writeFile as writeFile19 } from "node:fs/promises";
26086
+ import { dirname as dirname8, join as join22, resolve as resolve31 } from "node:path";
26087
+ function experimentDir(projectRoot, experimentId) {
26088
+ return resolve31(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR, experimentId);
26089
+ }
26090
+ function experimentYamlPath(projectRoot, experimentId) {
26091
+ return join22(experimentDir(projectRoot, experimentId), "experiment.yml");
26092
+ }
26093
+ async function loadHarnessSuite(projectRoot, suiteId, suitePath) {
26094
+ const candidates = suitePath ? [suitePath] : [
26095
+ resolve31(projectRoot, "eval", "suites", suiteId, "suite.yml"),
26096
+ resolve31(projectRoot, EVAL_DIR2, "suites", suiteId, "suite.yml")
26097
+ ];
26098
+ for (const path of candidates) {
26099
+ if (!existsSync8(path))
26100
+ continue;
26101
+ try {
26102
+ const suite = HarnessSuiteSchema.parse($parse(await readFile31(path, "utf-8")));
26103
+ return ok({ ...suite, path });
26104
+ } catch (e) {
26105
+ return err(e instanceof Error ? e : new Error(String(e)));
26106
+ }
26107
+ }
26108
+ return err(new Error(`Harness suite not found: ${suiteId}`));
26109
+ }
26110
+ async function loadExperiment(projectRoot, experimentId) {
26111
+ try {
26112
+ const raw = await readFile31(experimentYamlPath(projectRoot, experimentId), "utf-8");
26113
+ return ok(HarnessExperimentSchema.parse($parse(raw)));
26114
+ } catch (e) {
26115
+ if (e.code === "ENOENT") {
26116
+ return err(new Error(`Experiment not found: ${experimentId}`));
26117
+ }
26118
+ return err(e instanceof Error ? e : new Error(String(e)));
26119
+ }
26120
+ }
26121
+ async function listExperiments(projectRoot) {
26122
+ const root = resolve31(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR);
26123
+ if (!existsSync8(root))
26124
+ return [];
26125
+ const ids = await readdir9(root);
26126
+ const experiments = [];
26127
+ for (const id of ids) {
26128
+ const loaded = await loadExperiment(projectRoot, id);
26129
+ if (loaded.success)
26130
+ experiments.push(loaded.data);
26131
+ }
26132
+ return experiments;
26133
+ }
26134
+ function formatTaskCard(task, experimentId, variantId) {
26135
+ return [
26136
+ "## Task Card",
26137
+ "",
26138
+ `**Title:** ${task.title}`,
26139
+ `**Type:** ${task.type}`,
26140
+ `**Risk:** ${task.risk}`,
26141
+ `**Scope:** ${task.scope}`,
26142
+ `**suiteTaskId:** ${task.id}`,
26143
+ `**experimentId:** ${experimentId}`,
26144
+ `**variantId:** ${variantId}`,
26145
+ "",
26146
+ "### Context",
26147
+ "",
26148
+ task.prompt,
26149
+ "",
26150
+ "### Acceptance Criteria",
26151
+ "",
26152
+ ...task.acceptanceCriteria.map((c) => `- [ ] ${c}`),
26153
+ "",
26154
+ "### Constraints",
26155
+ "",
26156
+ "- Stay inside the declared scope.",
26157
+ "- Record the outcome with experiment and variant tags.",
26158
+ "",
26159
+ "### Scoring",
26160
+ "",
26161
+ "When done:",
26162
+ "",
26163
+ "```",
26164
+ `bun run dev scorecard create --task ${task.id} --from-diff`,
26165
+ `bun run dev scorecard record --task ${task.id} --verdict PASS|REVISE|REJECT --score <n> --experiment ${experimentId} --variant ${variantId} --suite-task ${task.id}`,
26166
+ "```",
26167
+ task.testCommand ? `
26168
+ Fixture test command: \`${task.testCommand}\`
26169
+ ` : ""
26170
+ ].join(`
26171
+ `);
26172
+ }
26173
+ function formatExperimentMarkdown(experiment) {
26174
+ return [
26175
+ `# Harness experiment ${experiment.id}`,
26176
+ "",
26177
+ `- Suite: ${experiment.suiteId}`,
26178
+ `- Contract: ${experiment.contractVersion}`,
26179
+ `- Created: ${experiment.createdAt}`,
26180
+ `- Variants: ${experiment.variants.join(", ")}`,
26181
+ `- Tasks: ${experiment.suiteTaskIds.join(", ")}`,
26182
+ "",
26183
+ "This runner does not invoke a model. Execute each run directory with the host",
26184
+ "agent, then `scorecard record` with `--experiment` and `--variant`.",
26185
+ "",
26186
+ "Compare with:",
26187
+ "",
26188
+ "```",
26189
+ `bun run dev scorecard ablation --experiment ${experiment.id}`,
26190
+ "```",
26191
+ ""
26192
+ ].join(`
26193
+ `);
26194
+ }
26195
+ function generateExperimentId(suiteId, now = new Date) {
26196
+ const date = now.toISOString().slice(0, 10).replaceAll("-", "");
26197
+ const rand = Math.random().toString(36).slice(2, 6);
26198
+ return `abl-${date}-${suiteId}-${rand}`;
26199
+ }
26200
+ async function copyFixture(suitePath, fixtureDir, dest) {
26201
+ const source = fixtureDir ? resolve31(dirname8(suitePath), fixtureDir) : join22(dirname8(suitePath), "fixture");
26202
+ await mkdir20(dest, { recursive: true });
26203
+ if (existsSync8(source)) {
26204
+ await cp(source, dest, { recursive: true });
26205
+ }
26206
+ }
26207
+ async function startHarnessExperiment(projectRoot, options) {
26208
+ const suite = await loadHarnessSuite(projectRoot, options.suiteId, options.suitePath);
26209
+ if (!suite.success)
26210
+ return suite;
26211
+ const experimentId = options.experimentId ?? generateExperimentId(suite.data.id);
26212
+ const variants = ["baseline", ...options.components.map((id) => variantIdFor(id))];
26213
+ const experiment = {
26214
+ id: experimentId,
26215
+ suiteId: suite.data.id,
26216
+ createdAt: new Date().toISOString(),
26217
+ contractVersion: options.contractVersion,
26218
+ components: options.components,
26219
+ variants,
26220
+ suiteTaskIds: suite.data.tasks.map((t) => t.id),
26221
+ suitePath: suite.data.path
26222
+ };
26223
+ const root = experimentDir(projectRoot, experimentId);
26224
+ await mkdir20(root, { recursive: true });
26225
+ await writeFile19(experimentYamlPath(projectRoot, experimentId), $stringify(experiment), "utf-8");
26226
+ await writeFile19(join22(root, "EXPERIMENT.md"), formatExperimentMarkdown(experiment), "utf-8");
26227
+ for (const task of suite.data.tasks) {
26228
+ for (const variantId of variants) {
26229
+ const runRoot = join22(root, "runs", task.id, variantDirName(variantId));
26230
+ await copyFixture(suite.data.path, suite.data.fixtureDir, runRoot);
26231
+ await writeConfig(runRoot, structuredClone(DEFAULT_CONFIG), true);
26232
+ const disabled = variantId === "baseline" ? [] : options.components.filter((id) => variantIdFor(id) === variantId);
26233
+ const overlay = await applyHarnessOverlay(runRoot, disabled, {
26234
+ experimentId,
26235
+ variantId,
26236
+ contractVersion: options.contractVersion,
26237
+ backup: false
26238
+ });
26239
+ if (!overlay.success)
26240
+ return overlay;
26241
+ await writeFile19(join22(runRoot, "TASK.md"), formatTaskCard(task, experimentId, variantId), "utf-8");
26242
+ }
26243
+ }
26244
+ return ok(experiment);
26245
+ }
26246
+ async function applyExperimentVariant(projectRoot, experimentId, variantId, contractVersion) {
26247
+ const loaded = await loadExperiment(projectRoot, experimentId);
26248
+ if (!loaded.success)
26249
+ return loaded;
26250
+ if (!loaded.data.variants.includes(variantId)) {
26251
+ return err(new Error(`Unknown variant ${variantId} for ${experimentId}`));
26252
+ }
26253
+ const disabled = variantId === "baseline" ? [] : loaded.data.components.filter((id) => variantIdFor(id) === variantId);
26254
+ const overlay = await applyHarnessOverlay(projectRoot, disabled, {
26255
+ experimentId,
26256
+ variantId,
26257
+ contractVersion,
26258
+ backup: true
26259
+ });
26260
+ if (!overlay.success)
26261
+ return overlay;
26262
+ return ok(loaded.data);
26263
+ }
26264
+ var EXPERIMENTS_DIR = "experiments";
26265
+ var init_harness_experiment = __esm(() => {
26266
+ init_dist();
26267
+ init_schemas();
26268
+ init_codeconductor_config();
26269
+ init_config_writer();
26270
+ init_harness_catalog();
26271
+ });
26272
+
26273
+ // src/core/evaluation/suite-run.ts
26274
+ import { spawnSync as spawnSync2 } from "node:child_process";
26275
+ import { existsSync as existsSync9 } from "node:fs";
26276
+ import { resolve as resolve32 } from "node:path";
26277
+ async function runHarnessSuiteTasks(projectRoot, suiteId, suitePath) {
26278
+ const suite = await loadHarnessSuite(projectRoot, suiteId, suitePath);
26279
+ if (!suite.success) {
26280
+ throw suite.error;
26281
+ }
26282
+ const results = [];
26283
+ for (const task of suite.data.tasks) {
26284
+ const started = Date.now();
26285
+ const check = runTaskCommand(projectRoot, task);
26286
+ const durationMs = Date.now() - started;
26287
+ const passed = check.ok;
26288
+ results.push({
26289
+ id: task.id,
26290
+ passed,
26291
+ detail: check.detail,
26292
+ durationMs
26293
+ });
26294
+ await appendOutcome(projectRoot, {
26295
+ id: generateEvalId("out"),
26296
+ taskId: task.id,
26297
+ source: "pipeline",
26298
+ agent: "evaluation",
26299
+ model: "suite-run",
26300
+ contractVersion: package_default.version,
26301
+ timestamp: new Date().toISOString(),
26302
+ status: passed ? "pass" : "reject",
26303
+ verdict: passed ? "PASS" : "REJECT",
26304
+ weightedScore: passed ? 3 : 0,
26305
+ suiteTaskId: task.id,
26306
+ durationMs
26307
+ });
26308
+ }
26309
+ return {
26310
+ suiteId: suite.data.id,
26311
+ passed: results.filter((r) => r.passed).length,
26312
+ failed: results.filter((r) => !r.passed).length,
26313
+ results
26314
+ };
26315
+ }
26316
+ function runTaskCommand(projectRoot, task) {
26317
+ const raw = task.testCommand?.trim() ?? "";
26318
+ if (!raw) {
26319
+ return { ok: false, detail: "Missing testCommand" };
26320
+ }
26321
+ const hookMatch = raw.match(/^hook:(deny|allow)\s+(.+)$/s);
26322
+ if (hookMatch) {
26323
+ const expectDeny = hookMatch[1] === "deny";
26324
+ const verdict = evaluateCommand(hookMatch[2]);
26325
+ const denied = verdict.action === "deny";
26326
+ const ok3 = expectDeny ? denied : !denied;
26327
+ return {
26328
+ ok: ok3,
26329
+ detail: ok3 ? `${hookMatch[1]} ${hookMatch[2]}` : `expected ${hookMatch[1]}, got ${verdict.action}: ${verdict.message}`
26330
+ };
26331
+ }
26332
+ const parts = tokenize(raw);
26333
+ if (parts.length === 0) {
26334
+ return { ok: false, detail: "Empty testCommand" };
26335
+ }
26336
+ const bin = parts[0] === "bun" && existsSync9(resolve32(projectRoot, "src/cli/main.ts")) ? "bun" : parts[0];
26337
+ const args = parts[0] === "bun" ? parts.slice(1) : parts.slice(1);
26338
+ const result = spawnSync2(bin, args, {
26339
+ cwd: projectRoot,
26340
+ encoding: "utf8",
26341
+ windowsHide: true,
26342
+ timeout: 60000
26343
+ });
26344
+ const ok2 = result.status === 0;
26345
+ return {
26346
+ ok: ok2,
26347
+ detail: ok2 ? raw : (result.stderr || result.stdout || `exit ${result.status}`).slice(0, 400)
26348
+ };
26349
+ }
26350
+ function tokenize(command) {
26351
+ const tokens = [];
26352
+ let current = "";
26353
+ let quote = null;
26354
+ for (const ch of command) {
26355
+ if (quote) {
26356
+ if (ch === quote) {
26357
+ quote = null;
26358
+ } else {
26359
+ current += ch;
26360
+ }
26361
+ continue;
26362
+ }
26363
+ if (ch === '"' || ch === "'") {
26364
+ quote = ch;
26365
+ continue;
26366
+ }
26367
+ if (/\s/.test(ch)) {
26368
+ if (current)
26369
+ tokens.push(current);
26370
+ current = "";
26371
+ continue;
26372
+ }
26373
+ current += ch;
26374
+ }
26375
+ if (current)
26376
+ tokens.push(current);
26377
+ return tokens;
26378
+ }
26379
+ var init_suite_run = __esm(() => {
26380
+ init_package();
26381
+ init_hook_runner();
26382
+ init_outcome_store();
26383
+ init_harness_experiment();
26384
+ });
26385
+
25172
26386
  // src/core/evaluation/execution-profile.ts
25173
- import { readFile as readFile29 } from "node:fs/promises";
25174
- import { resolve as resolve28 } from "node:path";
26387
+ import { readFile as readFile32 } from "node:fs/promises";
26388
+ import { resolve as resolve33 } from "node:path";
25175
26389
  async function loadExecutionProfile(projectRoot, defaultTarget = "opencode") {
25176
26390
  try {
25177
- const content = await readFile29(resolve28(projectRoot, PROFILE_FILE), "utf-8");
26391
+ const content = await readFile32(resolve33(projectRoot, PROFILE_FILE), "utf-8");
25178
26392
  const data = $parse(content);
25179
26393
  return ExecutionProfileSchema.parse(data);
25180
26394
  } catch {
@@ -25254,14 +26468,14 @@ var init_execution_profile = __esm(() => {
25254
26468
  });
25255
26469
 
25256
26470
  // src/core/evaluation/prompt-diff.ts
25257
- import { readFile as readFile30, readdir as readdir7 } from "node:fs/promises";
25258
- import { join as join20, resolve as resolve29 } from "node:path";
26471
+ import { readFile as readFile33, readdir as readdir10 } from "node:fs/promises";
26472
+ import { join as join23, resolve as resolve34 } from "node:path";
25259
26473
  function normalizeVersion(v) {
25260
26474
  return v.startsWith("v") ? v : `v${v}`;
25261
26475
  }
25262
26476
  async function listPromptFiles(dir) {
25263
26477
  try {
25264
- const entries = await readdir7(dir, { withFileTypes: true });
26478
+ const entries = await readdir10(dir, { withFileTypes: true });
25265
26479
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
25266
26480
  } catch {
25267
26481
  return [];
@@ -25304,30 +26518,30 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
25304
26518
  const fromV = normalizeVersion(fromVersion);
25305
26519
  const toV = normalizeVersion(toVersion);
25306
26520
  const searchRoots = [
25307
- resolve29(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
25308
- join20(ROOT_PRESETS_DIR, target, "prompts"),
25309
- join20(ROOT_PRESETS_DIR, "opencode", "prompts")
26521
+ resolve34(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
26522
+ join23(ROOT_PRESETS_DIR, target, "prompts"),
26523
+ join23(ROOT_PRESETS_DIR, "opencode", "prompts")
25310
26524
  ];
25311
26525
  let fromDir = "";
25312
26526
  let toDir = "";
25313
26527
  for (const root of searchRoots) {
25314
- const f = join20(root, fromV);
25315
- const t = join20(root, toV);
26528
+ const f = join23(root, fromV);
26529
+ const t = join23(root, toV);
25316
26530
  try {
25317
- await readFile30(join20(f, "orchestrator.md"), "utf-8");
26531
+ await readFile33(join23(f, "orchestrator.md"), "utf-8");
25318
26532
  fromDir = f;
25319
26533
  toDir = t;
25320
26534
  break;
25321
26535
  } catch {}
25322
26536
  }
25323
26537
  if (!fromDir) {
25324
- fromDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
25325
- toDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
26538
+ fromDir = join23(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
26539
+ toDir = join23(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
25326
26540
  } else {
25327
26541
  try {
25328
- await readFile30(join20(toDir, "orchestrator.md"), "utf-8");
26542
+ await readFile33(join23(toDir, "orchestrator.md"), "utf-8");
25329
26543
  } catch {
25330
- toDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
26544
+ toDir = join23(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
25331
26545
  }
25332
26546
  }
25333
26547
  const fromFiles = await listPromptFiles(fromDir);
@@ -25345,12 +26559,12 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
25345
26559
  let oldText = "";
25346
26560
  let newText = "";
25347
26561
  try {
25348
- oldText = await readFile30(join20(fromDir, name), "utf-8");
26562
+ oldText = await readFile33(join23(fromDir, name), "utf-8");
25349
26563
  } catch {
25350
26564
  oldText = "";
25351
26565
  }
25352
26566
  try {
25353
- newText = await readFile30(join20(toDir, name), "utf-8");
26567
+ newText = await readFile33(join23(toDir, name), "utf-8");
25354
26568
  } catch {
25355
26569
  newText = "";
25356
26570
  }
@@ -25390,12 +26604,12 @@ var init_prompt_diff = __esm(() => {
25390
26604
 
25391
26605
  // src/core/evaluation/regression-checklist.ts
25392
26606
  import { execFileSync as execFileSync4 } from "node:child_process";
25393
- import { mkdir as mkdir20, readFile as readFile31, writeFile as writeFile19 } from "node:fs/promises";
25394
- import { dirname as dirname8, resolve as resolve30 } from "node:path";
26607
+ import { mkdir as mkdir21, readFile as readFile34, writeFile as writeFile20 } from "node:fs/promises";
26608
+ import { dirname as dirname9, resolve as resolve35 } from "node:path";
25395
26609
  async function loadRegressionChecklist() {
25396
26610
  try {
25397
- const path = resolve30(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
25398
- const content = await readFile31(path, "utf-8");
26611
+ const path = resolve35(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
26612
+ const content = await readFile34(path, "utf-8");
25399
26613
  const data = $parse(content);
25400
26614
  return data.checks ?? [];
25401
26615
  } catch {
@@ -25450,9 +26664,9 @@ async function runRegressionChecklist(projectRoot, options = {}) {
25450
26664
  checks,
25451
26665
  timestamp: new Date().toISOString()
25452
26666
  };
25453
- const outPath = resolve30(projectRoot, ".codeconductor/evaluation/regression-last.json");
25454
- await mkdir20(dirname8(outPath), { recursive: true });
25455
- await writeFile19(outPath, JSON.stringify(report, null, 2), "utf-8");
26667
+ const outPath = resolve35(projectRoot, ".codeconductor/evaluation/regression-last.json");
26668
+ await mkdir21(dirname9(outPath), { recursive: true });
26669
+ await writeFile20(outPath, JSON.stringify(report, null, 2), "utf-8");
25456
26670
  return ok(report);
25457
26671
  } catch (e) {
25458
26672
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -25494,8 +26708,8 @@ var init_model_comparison = __esm(() => {
25494
26708
  });
25495
26709
 
25496
26710
  // src/core/evaluation/cost-quality-matrix.ts
25497
- import { writeFile as writeFile20 } from "node:fs/promises";
25498
- import { resolve as resolve31 } from "node:path";
26711
+ import { writeFile as writeFile21 } from "node:fs/promises";
26712
+ import { resolve as resolve36 } from "node:path";
25499
26713
  async function buildCostQualityMatrix(projectRoot, outcomes) {
25500
26714
  const agg = aggregateOutcomes(outcomes);
25501
26715
  const rows = Object.entries(agg.byModel).map(([model, entry]) => {
@@ -25515,7 +26729,7 @@ async function buildCostQualityMatrix(projectRoot, outcomes) {
25515
26729
  rows
25516
26730
  };
25517
26731
  const dir = await ensureEvaluationDir(projectRoot);
25518
- await writeFile20(resolve31(dir, "matrix.yml"), $stringify(matrix), "utf-8");
26732
+ await writeFile21(resolve36(dir, "matrix.yml"), $stringify(matrix), "utf-8");
25519
26733
  return matrix;
25520
26734
  }
25521
26735
  function formatMatrixMarkdown(matrix) {
@@ -25538,199 +26752,9 @@ var init_cost_quality_matrix = __esm(() => {
25538
26752
  init_outcome_store();
25539
26753
  });
25540
26754
 
25541
- // src/core/evaluation/harness-experiment.ts
25542
- import { existsSync as existsSync7 } from "node:fs";
25543
- import { cp, mkdir as mkdir21, readFile as readFile32, readdir as readdir8, writeFile as writeFile21 } from "node:fs/promises";
25544
- import { dirname as dirname9, join as join21, resolve as resolve32 } from "node:path";
25545
- function experimentDir(projectRoot, experimentId) {
25546
- return resolve32(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR, experimentId);
25547
- }
25548
- function experimentYamlPath(projectRoot, experimentId) {
25549
- return join21(experimentDir(projectRoot, experimentId), "experiment.yml");
25550
- }
25551
- async function loadHarnessSuite(projectRoot, suiteId, suitePath) {
25552
- const candidates = suitePath ? [suitePath] : [
25553
- resolve32(projectRoot, "eval", "suites", suiteId, "suite.yml"),
25554
- resolve32(projectRoot, EVAL_DIR2, "suites", suiteId, "suite.yml")
25555
- ];
25556
- for (const path of candidates) {
25557
- if (!existsSync7(path))
25558
- continue;
25559
- try {
25560
- const suite = HarnessSuiteSchema.parse($parse(await readFile32(path, "utf-8")));
25561
- return ok({ ...suite, path });
25562
- } catch (e) {
25563
- return err(e instanceof Error ? e : new Error(String(e)));
25564
- }
25565
- }
25566
- return err(new Error(`Harness suite not found: ${suiteId}`));
25567
- }
25568
- async function loadExperiment(projectRoot, experimentId) {
25569
- try {
25570
- const raw = await readFile32(experimentYamlPath(projectRoot, experimentId), "utf-8");
25571
- return ok(HarnessExperimentSchema.parse($parse(raw)));
25572
- } catch (e) {
25573
- if (e.code === "ENOENT") {
25574
- return err(new Error(`Experiment not found: ${experimentId}`));
25575
- }
25576
- return err(e instanceof Error ? e : new Error(String(e)));
25577
- }
25578
- }
25579
- async function listExperiments(projectRoot) {
25580
- const root = resolve32(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR);
25581
- if (!existsSync7(root))
25582
- return [];
25583
- const ids = await readdir8(root);
25584
- const experiments = [];
25585
- for (const id of ids) {
25586
- const loaded = await loadExperiment(projectRoot, id);
25587
- if (loaded.success)
25588
- experiments.push(loaded.data);
25589
- }
25590
- return experiments;
25591
- }
25592
- function formatTaskCard(task, experimentId, variantId) {
25593
- return [
25594
- "## Task Card",
25595
- "",
25596
- `**Title:** ${task.title}`,
25597
- `**Type:** ${task.type}`,
25598
- `**Risk:** ${task.risk}`,
25599
- `**Scope:** ${task.scope}`,
25600
- `**suiteTaskId:** ${task.id}`,
25601
- `**experimentId:** ${experimentId}`,
25602
- `**variantId:** ${variantId}`,
25603
- "",
25604
- "### Context",
25605
- "",
25606
- task.prompt,
25607
- "",
25608
- "### Acceptance Criteria",
25609
- "",
25610
- ...task.acceptanceCriteria.map((c) => `- [ ] ${c}`),
25611
- "",
25612
- "### Constraints",
25613
- "",
25614
- "- Stay inside the declared scope.",
25615
- "- Record the outcome with experiment and variant tags.",
25616
- "",
25617
- "### Scoring",
25618
- "",
25619
- "When done:",
25620
- "",
25621
- "```",
25622
- `bun run dev scorecard create --task ${task.id} --from-diff`,
25623
- `bun run dev scorecard record --task ${task.id} --verdict PASS|REVISE|REJECT --score <n> --experiment ${experimentId} --variant ${variantId} --suite-task ${task.id}`,
25624
- "```",
25625
- task.testCommand ? `
25626
- Fixture test command: \`${task.testCommand}\`
25627
- ` : ""
25628
- ].join(`
25629
- `);
25630
- }
25631
- function formatExperimentMarkdown(experiment) {
25632
- return [
25633
- `# Harness experiment ${experiment.id}`,
25634
- "",
25635
- `- Suite: ${experiment.suiteId}`,
25636
- `- Contract: ${experiment.contractVersion}`,
25637
- `- Created: ${experiment.createdAt}`,
25638
- `- Variants: ${experiment.variants.join(", ")}`,
25639
- `- Tasks: ${experiment.suiteTaskIds.join(", ")}`,
25640
- "",
25641
- "This runner does not invoke a model. Execute each run directory with the host",
25642
- "agent, then `scorecard record` with `--experiment` and `--variant`.",
25643
- "",
25644
- "Compare with:",
25645
- "",
25646
- "```",
25647
- `bun run dev scorecard ablation --experiment ${experiment.id}`,
25648
- "```",
25649
- ""
25650
- ].join(`
25651
- `);
25652
- }
25653
- function generateExperimentId(suiteId, now = new Date) {
25654
- const date = now.toISOString().slice(0, 10).replaceAll("-", "");
25655
- const rand = Math.random().toString(36).slice(2, 6);
25656
- return `abl-${date}-${suiteId}-${rand}`;
25657
- }
25658
- async function copyFixture(suitePath, fixtureDir, dest) {
25659
- const source = fixtureDir ? resolve32(dirname9(suitePath), fixtureDir) : join21(dirname9(suitePath), "fixture");
25660
- await mkdir21(dest, { recursive: true });
25661
- if (existsSync7(source)) {
25662
- await cp(source, dest, { recursive: true });
25663
- }
25664
- }
25665
- async function startHarnessExperiment(projectRoot, options) {
25666
- const suite = await loadHarnessSuite(projectRoot, options.suiteId, options.suitePath);
25667
- if (!suite.success)
25668
- return suite;
25669
- const experimentId = options.experimentId ?? generateExperimentId(suite.data.id);
25670
- const variants = ["baseline", ...options.components.map((id) => variantIdFor(id))];
25671
- const experiment = {
25672
- id: experimentId,
25673
- suiteId: suite.data.id,
25674
- createdAt: new Date().toISOString(),
25675
- contractVersion: options.contractVersion,
25676
- components: options.components,
25677
- variants,
25678
- suiteTaskIds: suite.data.tasks.map((t) => t.id),
25679
- suitePath: suite.data.path
25680
- };
25681
- const root = experimentDir(projectRoot, experimentId);
25682
- await mkdir21(root, { recursive: true });
25683
- await writeFile21(experimentYamlPath(projectRoot, experimentId), $stringify(experiment), "utf-8");
25684
- await writeFile21(join21(root, "EXPERIMENT.md"), formatExperimentMarkdown(experiment), "utf-8");
25685
- for (const task of suite.data.tasks) {
25686
- for (const variantId of variants) {
25687
- const runRoot = join21(root, "runs", task.id, variantDirName(variantId));
25688
- await copyFixture(suite.data.path, suite.data.fixtureDir, runRoot);
25689
- await writeConfig(runRoot, structuredClone(DEFAULT_CONFIG), true);
25690
- const disabled = variantId === "baseline" ? [] : options.components.filter((id) => variantIdFor(id) === variantId);
25691
- const overlay = await applyHarnessOverlay(runRoot, disabled, {
25692
- experimentId,
25693
- variantId,
25694
- contractVersion: options.contractVersion,
25695
- backup: false
25696
- });
25697
- if (!overlay.success)
25698
- return overlay;
25699
- await writeFile21(join21(runRoot, "TASK.md"), formatTaskCard(task, experimentId, variantId), "utf-8");
25700
- }
25701
- }
25702
- return ok(experiment);
25703
- }
25704
- async function applyExperimentVariant(projectRoot, experimentId, variantId, contractVersion) {
25705
- const loaded = await loadExperiment(projectRoot, experimentId);
25706
- if (!loaded.success)
25707
- return loaded;
25708
- if (!loaded.data.variants.includes(variantId)) {
25709
- return err(new Error(`Unknown variant ${variantId} for ${experimentId}`));
25710
- }
25711
- const disabled = variantId === "baseline" ? [] : loaded.data.components.filter((id) => variantIdFor(id) === variantId);
25712
- const overlay = await applyHarnessOverlay(projectRoot, disabled, {
25713
- experimentId,
25714
- variantId,
25715
- contractVersion,
25716
- backup: true
25717
- });
25718
- if (!overlay.success)
25719
- return overlay;
25720
- return ok(loaded.data);
25721
- }
25722
- var EXPERIMENTS_DIR = "experiments";
25723
- var init_harness_experiment = __esm(() => {
25724
- init_dist();
25725
- init_schemas();
25726
- init_codeconductor_config();
25727
- init_config_writer();
25728
- init_harness_catalog();
25729
- });
25730
-
25731
26755
  // src/core/evaluation/ablation-report.ts
25732
26756
  import { mkdir as mkdir22, writeFile as writeFile22 } from "node:fs/promises";
25733
- import { resolve as resolve33 } from "node:path";
26757
+ import { resolve as resolve37 } from "node:path";
25734
26758
  function isPass(o) {
25735
26759
  return o.verdict === "PASS" || o.status === "pass";
25736
26760
  }
@@ -25898,9 +26922,9 @@ function formatAblationMarkdown(report) {
25898
26922
  `);
25899
26923
  }
25900
26924
  async function persistAblationReport(projectRoot, markdown) {
25901
- const dir = resolve33(projectRoot, EVAL_DIR2);
26925
+ const dir = resolve37(projectRoot, EVAL_DIR2);
25902
26926
  await mkdir22(dir, { recursive: true });
25903
- const path = resolve33(dir, "ablation-last.md");
26927
+ const path = resolve37(dir, "ablation-last.md");
25904
26928
  await writeFile22(path, markdown, "utf-8");
25905
26929
  return path;
25906
26930
  }
@@ -25915,8 +26939,8 @@ var exports_scorecard_command = {};
25915
26939
  __export(exports_scorecard_command, {
25916
26940
  scorecardCommand: () => scorecardCommand
25917
26941
  });
25918
- import { readFile as readFile33 } from "node:fs/promises";
25919
- import { resolve as resolve34 } from "node:path";
26942
+ import { readFile as readFile35 } from "node:fs/promises";
26943
+ import { resolve as resolve38 } from "node:path";
25920
26944
  function contractVersion() {
25921
26945
  return package_default.version;
25922
26946
  }
@@ -25951,6 +26975,8 @@ async function scorecardCommand(options) {
25951
26975
  return handleExperiment(projectRoot, options);
25952
26976
  case "ablation":
25953
26977
  return handleAblation(projectRoot, options);
26978
+ case "suite-run":
26979
+ return handleSuiteRun(projectRoot, options);
25954
26980
  default:
25955
26981
  return {
25956
26982
  code: 1,
@@ -25958,7 +26984,7 @@ async function scorecardCommand(options) {
25958
26984
  success: false,
25959
26985
  command: "scorecard",
25960
26986
  errors: [
25961
- `Unknown subcommand: ${subcommand}. Use: create, show, record, list, aggregate, models, prompt-diff, regression, matrix, compare-models, catalog, fingerprint, experiment, ablation`
26987
+ `Unknown subcommand: ${subcommand}. Use: create, show, record, list, aggregate, models, prompt-diff, regression, matrix, compare-models, catalog, fingerprint, experiment, ablation, suite-run`
25962
26988
  ]
25963
26989
  }
25964
26990
  };
@@ -25978,7 +27004,34 @@ async function handleCreate(projectRoot, options) {
25978
27004
  scopeFiles = item.scope.split(/[,;]/).map((s) => s.trim()).filter(Boolean);
25979
27005
  }
25980
27006
  }
25981
- const hints = collectScorecardSignals(projectRoot, scopeFiles);
27007
+ let hints = collectScorecardSignals(projectRoot, scopeFiles);
27008
+ const state = await loadOpenspecState(projectRoot);
27009
+ const backlogId = options.taskId?.startsWith("BC-") ? options.taskId : undefined;
27010
+ const changePath = backlogId && state.success ? state.data.changePaths[backlogId] : undefined;
27011
+ if (changePath && !changePath.includes("/archive/")) {
27012
+ const tddRequired = backlog.success ? backlog.data.global.tddRequired : false;
27013
+ const cards = state.success ? state.data.taskCards.filter((c) => c.backlogId === backlogId) : [];
27014
+ let hasEvidence;
27015
+ if (cards.some((c) => c.phase === "test" || c.phase === "implement")) {
27016
+ hasEvidence = false;
27017
+ for (const card of cards) {
27018
+ if ((card.phase === "test" || card.phase === "implement") && await hasTddRunnerEvidence(projectRoot, card.id)) {
27019
+ hasEvidence = true;
27020
+ break;
27021
+ }
27022
+ }
27023
+ }
27024
+ const analyze = await analyzeChangeFolder(projectRoot, changePath, {
27025
+ tddRequired,
27026
+ hasTddEvidence: hasEvidence
27027
+ });
27028
+ hints = applyAnalyzeSignals(hints, {
27029
+ frCoveragePct: analyze.frCoveragePct,
27030
+ scCoveragePct: analyze.scCoveragePct,
27031
+ tddRequired,
27032
+ hasTddEvidence: hasEvidence
27033
+ });
27034
+ }
25982
27035
  criteria = criteriaFromSignals(hints);
25983
27036
  findings = hints.findings;
25984
27037
  }
@@ -26168,12 +27221,12 @@ async function handleCompareModels(projectRoot, options) {
26168
27221
  outcomes = outcomes.filter((o) => options.modelsFilter.includes(o.model));
26169
27222
  }
26170
27223
  const markdown = generateModelComparisonMarkdown(outcomes, options.modelsFilter);
26171
- const templatePath = resolve34(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
27224
+ const templatePath = resolve38(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
26172
27225
  try {
26173
- await readFile33(templatePath);
27226
+ await readFile35(templatePath);
26174
27227
  } catch {}
26175
27228
  const { writeFile: writeFile23, mkdir: mkdir23 } = await import("node:fs/promises");
26176
- await mkdir23(resolve34(projectRoot, ".codeconductor/evaluation"), { recursive: true });
27229
+ await mkdir23(resolve38(projectRoot, ".codeconductor/evaluation"), { recursive: true });
26177
27230
  await writeFile23(templatePath, markdown, "utf-8");
26178
27231
  return {
26179
27232
  code: 0,
@@ -26299,12 +27352,39 @@ async function handleAblation(projectRoot, options) {
26299
27352
  }
26300
27353
  };
26301
27354
  }
27355
+ async function handleSuiteRun(projectRoot, options) {
27356
+ const suiteId = options.suiteId ?? options.taskId ?? "harness-v1";
27357
+ try {
27358
+ const result = await runHarnessSuiteTasks(projectRoot, suiteId, options.suitePath);
27359
+ return {
27360
+ code: result.failed > 0 ? 1 : 0,
27361
+ data: {
27362
+ success: result.failed === 0,
27363
+ command: "scorecard suite-run",
27364
+ ...result
27365
+ }
27366
+ };
27367
+ } catch (e) {
27368
+ return {
27369
+ code: 1,
27370
+ data: {
27371
+ success: false,
27372
+ command: "scorecard suite-run",
27373
+ errors: [e instanceof Error ? e.message : String(e)]
27374
+ }
27375
+ };
27376
+ }
27377
+ }
26302
27378
  var init_scorecard_command = __esm(() => {
26303
27379
  init_package();
26304
27380
  init_config_loader();
26305
27381
  init_scorecard_calculator();
26306
27382
  init_outcome_store();
26307
27383
  init_scorecard_signals();
27384
+ init_openspec_state();
27385
+ init_spec_analyzer();
27386
+ init_verification_runner();
27387
+ init_suite_run();
26308
27388
  init_execution_profile();
26309
27389
  init_prompt_diff();
26310
27390
  init_regression_checklist();
@@ -27616,7 +28696,7 @@ var exports_seo_audit_command = {};
27616
28696
  __export(exports_seo_audit_command, {
27617
28697
  seoAuditCommand: () => seoAuditCommand
27618
28698
  });
27619
- import { join as join22 } from "node:path";
28699
+ import { join as join24 } from "node:path";
27620
28700
  async function seoAuditCommand(options) {
27621
28701
  const { url, sitemap, format, failOn, delay: delay2, output, followRedirects, force } = options;
27622
28702
  if (!url && !sitemap) {
@@ -27629,7 +28709,7 @@ async function seoAuditCommand(options) {
27629
28709
  }
27630
28710
  };
27631
28711
  }
27632
- const defaultMarkdownPath = output === undefined && format === "markdown" ? join22("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
28712
+ const defaultMarkdownPath = output === undefined && format === "markdown" ? join24("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
27633
28713
  const outputPathToWrite = output ?? defaultMarkdownPath;
27634
28714
  if (outputPathToWrite !== undefined) {
27635
28715
  const preflightError = await preflightContainedOutput(options.projectRoot, outputPathToWrite, { force });
@@ -28019,9 +29099,10 @@ Published commands (package ${package_default.version}):
28019
29099
  cc-help Show preset inventory (skills, subagents, commands)
28020
29100
  debt-harvest / harvest Scan source files for deferred debt items
28021
29101
  ccep CCEP contracts: parse/profile/validate/evaluate/consensus/taskcard
28022
- openspec OpenSpec loop: validate/scan/plan/status/next/start/done/block/archive
29102
+ openspec OpenSpec loop: validate/scan/plan/analyze/status/next/start/done/block/archive
28023
29103
  scorecard Record and aggregate evaluation outcomes
28024
- (catalog / fingerprint / experiment / ablation)
29104
+ (catalog / fingerprint / experiment / ablation / suite-run)
29105
+ hook OS-agnostic agent hooks: pre-tool / post-tool / session-start
28025
29106
 
28026
29107
  v1.0.0 (in this repo, not in published ${package_default.version}):
28027
29108
  goal / cc-goal Plan goal into task graph with dependencies
@@ -28104,6 +29185,7 @@ Examples:
28104
29185
  npx cc-codeconductor openspec validate
28105
29186
  npx cc-codeconductor openspec scan
28106
29187
  npx cc-codeconductor openspec plan BC-001
29188
+ npx cc-codeconductor openspec analyze
28107
29189
  npx cc-codeconductor openspec status
28108
29190
  npx cc-codeconductor openspec next
28109
29191
  npx cc-codeconductor openspec start BC-001-discover
@@ -28407,6 +29489,7 @@ async function routeCommand(args, projectRoot) {
28407
29489
  "validate",
28408
29490
  "scan",
28409
29491
  "plan",
29492
+ "analyze",
28410
29493
  "status",
28411
29494
  "next",
28412
29495
  "start",
@@ -28434,6 +29517,26 @@ async function routeCommand(args, projectRoot) {
28434
29517
  output: flags.output
28435
29518
  });
28436
29519
  }
29520
+ case "hook": {
29521
+ const validHookEvents = ["pre-tool", "post-tool", "session-start"];
29522
+ if (subcommand && !validHookEvents.includes(subcommand)) {
29523
+ return unknownSubcommand(command, subcommand, [...validHookEvents]);
29524
+ }
29525
+ const event = validHookEvents.includes(subcommand) ? subcommand : "pre-tool";
29526
+ const formatRaw = options.format;
29527
+ const format = formatRaw === "agy" || formatRaw === "claude" ? formatRaw : undefined;
29528
+ const { hookCommand: hookCommand2, readStdinText: readStdinText2 } = await Promise.resolve().then(() => (init_hook_command(), exports_hook_command));
29529
+ const stdinText = await readStdinText2();
29530
+ return hookCommand2({
29531
+ event,
29532
+ projectRoot,
29533
+ output: flags.output,
29534
+ format,
29535
+ command: typeof options.command === "string" ? options.command : undefined,
29536
+ filePath: typeof options.file === "string" ? options.file : undefined,
29537
+ stdinText
29538
+ });
29539
+ }
28437
29540
  case "scorecard":
28438
29541
  case "cc-scorecard": {
28439
29542
  const validSubs = [
@@ -28450,7 +29553,8 @@ async function routeCommand(args, projectRoot) {
28450
29553
  "catalog",
28451
29554
  "fingerprint",
28452
29555
  "experiment",
28453
- "ablation"
29556
+ "ablation",
29557
+ "suite-run"
28454
29558
  ];
28455
29559
  if (subcommand && !validSubs.includes(subcommand)) {
28456
29560
  return unknownSubcommand(command, subcommand, validSubs);