cc-codeconductor 1.0.0 → 1.1.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 (122) hide show
  1. package/README.md +324 -87
  2. package/dist/core/compilation/compile-checker.d.ts +2 -0
  3. package/dist/core/loop/loop-engine.d.ts +3 -0
  4. package/dist/index.js +2701 -577
  5. package/dist/library.js +167 -18
  6. package/dist/validation/schemas.d.ts +390 -44
  7. package/package.json +4 -1
  8. package/presets/agy/AGENTS.md +3 -0
  9. package/presets/agy/gates/pre-commit/GATE.md +5 -4
  10. package/presets/agy/skills/backlog/SKILL.md +91 -0
  11. package/presets/agy/skills/openspec/SKILL.md +5 -1
  12. package/presets/agy/skills/security-ai-llm/SKILL.md +43 -0
  13. package/presets/agy/skills/security-blue-team/SKILL.md +43 -0
  14. package/presets/agy/skills/security-cloud/SKILL.md +43 -0
  15. package/presets/agy/skills/security-crypto/SKILL.md +43 -0
  16. package/presets/agy/skills/security-exploit-dev/SKILL.md +45 -0
  17. package/presets/agy/skills/security-grc/SKILL.md +43 -0
  18. package/presets/agy/skills/security-incident-response/SKILL.md +45 -0
  19. package/presets/agy/skills/security-log-analysis/SKILL.md +43 -0
  20. package/presets/agy/skills/security-malware-analysis/SKILL.md +44 -0
  21. package/presets/agy/skills/security-mobile/SKILL.md +43 -0
  22. package/presets/agy/skills/security-network/SKILL.md +43 -0
  23. package/presets/agy/skills/security-ot-ics/SKILL.md +43 -0
  24. package/presets/agy/skills/security-recon/SKILL.md +45 -0
  25. package/presets/agy/skills/security-red-team/SKILL.md +44 -0
  26. package/presets/agy/skills/security-reverse-engineering/SKILL.md +44 -0
  27. package/presets/agy/skills/security-soc-automation/SKILL.md +43 -0
  28. package/presets/agy/skills/security-threat-hunting/SKILL.md +43 -0
  29. package/presets/agy/skills/security-vuln-assessment/SKILL.md +45 -0
  30. package/presets/agy/skills/security-web/SKILL.md +44 -0
  31. package/presets/agy/workflows/cc-ask.md +2 -1
  32. package/presets/agy/workflows/cc-backlog.md +69 -0
  33. package/presets/agy/workflows/cc-explore.md +1 -1
  34. package/presets/agy/workflows/cc-openspec.md +3 -1
  35. package/presets/agy/workflows/cc-security.md +180 -0
  36. package/presets/claude/CLAUDE.md +4 -0
  37. package/presets/claude/commands/cc/ask.md +2 -1
  38. package/presets/claude/commands/cc/backlog.md +104 -0
  39. package/presets/claude/commands/cc/explore.md +1 -1
  40. package/presets/claude/commands/cc/openspec.md +16 -1
  41. package/presets/claude/commands/cc/security.md +179 -0
  42. package/presets/claude/gates/pre-commit/GATE.md +5 -4
  43. package/presets/claude/skills/backlog/SKILL.md +91 -0
  44. package/presets/claude/skills/openspec/SKILL.md +5 -1
  45. package/presets/claude/skills/security/SKILL.md +382 -0
  46. package/presets/claude/skills/security-ai-llm/SKILL.md +43 -0
  47. package/presets/claude/skills/security-blue-team/SKILL.md +43 -0
  48. package/presets/claude/skills/security-cloud/SKILL.md +43 -0
  49. package/presets/claude/skills/security-crypto/SKILL.md +43 -0
  50. package/presets/claude/skills/security-exploit-dev/SKILL.md +45 -0
  51. package/presets/claude/skills/security-grc/SKILL.md +43 -0
  52. package/presets/claude/skills/security-incident-response/SKILL.md +45 -0
  53. package/presets/claude/skills/security-log-analysis/SKILL.md +43 -0
  54. package/presets/claude/skills/security-malware-analysis/SKILL.md +44 -0
  55. package/presets/claude/skills/security-mobile/SKILL.md +43 -0
  56. package/presets/claude/skills/security-network/SKILL.md +43 -0
  57. package/presets/claude/skills/security-ot-ics/SKILL.md +43 -0
  58. package/presets/claude/skills/security-recon/SKILL.md +45 -0
  59. package/presets/claude/skills/security-red-team/SKILL.md +44 -0
  60. package/presets/claude/skills/security-reverse-engineering/SKILL.md +44 -0
  61. package/presets/claude/skills/security-soc-automation/SKILL.md +43 -0
  62. package/presets/claude/skills/security-threat-hunting/SKILL.md +43 -0
  63. package/presets/claude/skills/security-vuln-assessment/SKILL.md +45 -0
  64. package/presets/claude/skills/security-web/SKILL.md +44 -0
  65. package/presets/codex/commands/cc-ask.md +2 -1
  66. package/presets/codex/gates/pre-commit/GATE.md +5 -4
  67. package/presets/cursor/AGENTS.md +2 -2
  68. package/presets/cursor/commands/cc/ask.md +2 -1
  69. package/presets/cursor/commands/cc/backlog.md +105 -0
  70. package/presets/cursor/commands/cc/explore.md +1 -1
  71. package/presets/cursor/commands/cc/openspec.md +16 -1
  72. package/presets/cursor/commands/cc/security.md +179 -0
  73. package/presets/cursor/gates/pre-commit/GATE.md +5 -4
  74. package/presets/cursor/skills/backlog/SKILL.md +91 -0
  75. package/presets/cursor/skills/openspec/SKILL.md +5 -1
  76. package/presets/cursor/skills/security-ai-llm/SKILL.md +43 -0
  77. package/presets/cursor/skills/security-blue-team/SKILL.md +43 -0
  78. package/presets/cursor/skills/security-cloud/SKILL.md +43 -0
  79. package/presets/cursor/skills/security-crypto/SKILL.md +43 -0
  80. package/presets/cursor/skills/security-exploit-dev/SKILL.md +45 -0
  81. package/presets/cursor/skills/security-grc/SKILL.md +43 -0
  82. package/presets/cursor/skills/security-incident-response/SKILL.md +45 -0
  83. package/presets/cursor/skills/security-log-analysis/SKILL.md +43 -0
  84. package/presets/cursor/skills/security-malware-analysis/SKILL.md +44 -0
  85. package/presets/cursor/skills/security-mobile/SKILL.md +43 -0
  86. package/presets/cursor/skills/security-network/SKILL.md +43 -0
  87. package/presets/cursor/skills/security-ot-ics/SKILL.md +43 -0
  88. package/presets/cursor/skills/security-recon/SKILL.md +45 -0
  89. package/presets/cursor/skills/security-red-team/SKILL.md +44 -0
  90. package/presets/cursor/skills/security-reverse-engineering/SKILL.md +44 -0
  91. package/presets/cursor/skills/security-soc-automation/SKILL.md +43 -0
  92. package/presets/cursor/skills/security-threat-hunting/SKILL.md +43 -0
  93. package/presets/cursor/skills/security-vuln-assessment/SKILL.md +45 -0
  94. package/presets/cursor/skills/security-web/SKILL.md +44 -0
  95. package/presets/opencode/commands/cc-ask.md +2 -1
  96. package/presets/opencode/commands/cc-backlog.md +68 -0
  97. package/presets/opencode/commands/cc-explore.md +1 -1
  98. package/presets/opencode/commands/cc-openspec.md +3 -1
  99. package/presets/opencode/commands/cc-security.md +179 -0
  100. package/presets/opencode/gates/pre-commit/GATE.md +5 -4
  101. package/presets/opencode/skills/backlog/SKILL.md +91 -0
  102. package/presets/opencode/skills/openspec/SKILL.md +5 -1
  103. package/presets/opencode/skills/security-ai-llm/SKILL.md +43 -0
  104. package/presets/opencode/skills/security-blue-team/SKILL.md +43 -0
  105. package/presets/opencode/skills/security-cloud/SKILL.md +43 -0
  106. package/presets/opencode/skills/security-crypto/SKILL.md +43 -0
  107. package/presets/opencode/skills/security-exploit-dev/SKILL.md +45 -0
  108. package/presets/opencode/skills/security-grc/SKILL.md +43 -0
  109. package/presets/opencode/skills/security-incident-response/SKILL.md +45 -0
  110. package/presets/opencode/skills/security-log-analysis/SKILL.md +43 -0
  111. package/presets/opencode/skills/security-malware-analysis/SKILL.md +44 -0
  112. package/presets/opencode/skills/security-mobile/SKILL.md +43 -0
  113. package/presets/opencode/skills/security-network/SKILL.md +43 -0
  114. package/presets/opencode/skills/security-ot-ics/SKILL.md +43 -0
  115. package/presets/opencode/skills/security-recon/SKILL.md +45 -0
  116. package/presets/opencode/skills/security-red-team/SKILL.md +44 -0
  117. package/presets/opencode/skills/security-reverse-engineering/SKILL.md +44 -0
  118. package/presets/opencode/skills/security-soc-automation/SKILL.md +43 -0
  119. package/presets/opencode/skills/security-threat-hunting/SKILL.md +43 -0
  120. package/presets/opencode/skills/security-vuln-assessment/SKILL.md +45 -0
  121. package/presets/opencode/skills/security-web/SKILL.md +44 -0
  122. package/src/presets/council/council.yml +12 -0
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.0.0",
86
+ version: "1.1.0",
87
87
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
88
88
  keywords: [
89
89
  "ai",
@@ -132,7 +132,10 @@ var init_package = __esm(() => {
132
132
  "build:lib": "bun build src/index.ts --target=node --outfile=dist/library.js --packages=external",
133
133
  "build:types": "tsc -p tsconfig.lib.json --emitDeclarationOnly",
134
134
  test: "bun test",
135
+ "test:fast": "bun run scripts/test-fast.ts",
135
136
  typecheck: "tsc --noEmit",
137
+ lint: "bun run scripts/lint.ts",
138
+ "check:coverage": "bun run scripts/check-coverage.ts",
136
139
  "check:prompt-changelog": "bun run scripts/check-prompt-changelog.ts",
137
140
  "release:patch": "bash release.sh patch",
138
141
  "release:minor": "bash release.sh minor",
@@ -11274,6 +11277,10 @@ __export(exports_schemas, {
11274
11277
  validateMemoryIndex: () => validateMemoryIndex,
11275
11278
  validateKnowledgeEntity: () => validateKnowledgeEntity,
11276
11279
  validateImplementerOutput: () => validateImplementerOutput,
11280
+ validateHarnessSuite: () => validateHarnessSuite,
11281
+ validateHarnessOverlay: () => validateHarnessOverlay,
11282
+ validateHarnessExperiment: () => validateHarnessExperiment,
11283
+ validateHarnessCatalog: () => validateHarnessCatalog,
11277
11284
  validateGoalGraph: () => validateGoalGraph,
11278
11285
  validateExecutionContext: () => validateExecutionContext,
11279
11286
  validateEvidence: () => validateEvidence,
@@ -11337,6 +11344,15 @@ __export(exports_schemas, {
11337
11344
  ImplementerTestsSchema: () => ImplementerTestsSchema,
11338
11345
  ImplementerOutputSchema: () => ImplementerOutputSchema,
11339
11346
  ImpactReportSchema: () => ImpactReportSchema,
11347
+ HarnessToggleKindSchema: () => HarnessToggleKindSchema,
11348
+ HarnessSuiteTaskSchema: () => HarnessSuiteTaskSchema,
11349
+ HarnessSuiteSchema: () => HarnessSuiteSchema,
11350
+ HarnessOverlaySchema: () => HarnessOverlaySchema,
11351
+ HarnessExperimentSchema: () => HarnessExperimentSchema,
11352
+ HarnessComponentLayerSchema: () => HarnessComponentLayerSchema,
11353
+ HarnessComponentIdSchema: () => HarnessComponentIdSchema,
11354
+ HarnessCatalogSchema: () => HarnessCatalogSchema,
11355
+ HarnessCatalogEntrySchema: () => HarnessCatalogEntrySchema,
11340
11356
  GraphRelationSchema: () => GraphRelationSchema,
11341
11357
  GoalTaskSchema: () => GoalTaskSchema,
11342
11358
  GoalGraphSchema: () => GoalGraphSchema,
@@ -11427,6 +11443,18 @@ function validateScorecardRecord(data) {
11427
11443
  function validateTaskOutcome(data) {
11428
11444
  return TaskOutcomeSchema.parse(data);
11429
11445
  }
11446
+ function validateHarnessCatalog(data) {
11447
+ return HarnessCatalogSchema.parse(data);
11448
+ }
11449
+ function validateHarnessOverlay(data) {
11450
+ return HarnessOverlaySchema.parse(data);
11451
+ }
11452
+ function validateHarnessExperiment(data) {
11453
+ return HarnessExperimentSchema.parse(data);
11454
+ }
11455
+ function validateHarnessSuite(data) {
11456
+ return HarnessSuiteSchema.parse(data);
11457
+ }
11430
11458
  function validateCommandEnvelope(data) {
11431
11459
  return CommandEnvelopeSchema.parse(data);
11432
11460
  }
@@ -11466,7 +11494,7 @@ function validateCanonicalTaskCard(data) {
11466
11494
  function validateProductEvent(data) {
11467
11495
  return ProductEventSchema.parse(data);
11468
11496
  }
11469
- 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, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema;
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;
11470
11498
  var init_schemas = __esm(() => {
11471
11499
  init_zod();
11472
11500
  CouncilAgentSpecSchema = exports_external.object({
@@ -11488,7 +11516,7 @@ var init_schemas = __esm(() => {
11488
11516
  version: exports_external.string(),
11489
11517
  description: exports_external.string(),
11490
11518
  outputContract: exports_external.string(),
11491
- agents: exports_external.array(CouncilAgentSpecSchema)
11519
+ agents: exports_external.array(CouncilAgentSpecSchema).min(1).refine((agents) => new Set(agents.map((a) => a.id.trim().toLowerCase())).size === agents.length, { message: "council agent ids must be unique" })
11492
11520
  });
11493
11521
  ProjectProfileSchema = exports_external.object({
11494
11522
  rootDir: exports_external.string(),
@@ -11610,7 +11638,7 @@ var init_schemas = __esm(() => {
11610
11638
  status: exports_external.enum(["APPROVED", "REJECTED", "ABSTAIN"]),
11611
11639
  securityVeto: exports_external.boolean(),
11612
11640
  complianceVeto: exports_external.boolean().optional(),
11613
- confidence: exports_external.number().min(0).max(1).optional(),
11641
+ confidence: exports_external.number().min(0).max(1),
11614
11642
  findings: exports_external.array(CouncilFindingSchema),
11615
11643
  summary: exports_external.string()
11616
11644
  });
@@ -11618,10 +11646,15 @@ var init_schemas = __esm(() => {
11618
11646
  algorithm: exports_external.enum(["majority", "unanimous"]),
11619
11647
  allowSecurityVeto: exports_external.boolean(),
11620
11648
  allowComplianceVeto: exports_external.boolean().optional(),
11621
- expectedAgentIds: exports_external.array(exports_external.string().min(1).refine((id) => id.trim().length > 0)).min(1).refine((ids) => new Set(ids.map((id) => id.trim().toLowerCase())).size === ids.length).optional()
11649
+ expectedAgentIds: exports_external.array(exports_external.string().min(1).refine((id) => id.trim().length > 0)).min(1).refine((ids) => new Set(ids.map((id) => id.trim().toLowerCase())).size === ids.length).optional(),
11650
+ quorum: exports_external.number().int().positive().optional(),
11651
+ criticalFindingsPolicy: exports_external.enum(["escalate", "reject", "ignore"]).optional()
11622
11652
  }).refine((config) => config.algorithm !== "unanimous" || config.expectedAgentIds !== undefined, {
11623
11653
  message: "unanimous consensus requires a non-empty expectedAgentIds roster",
11624
11654
  path: ["expectedAgentIds"]
11655
+ }).refine((config) => !config.allowSecurityVeto || config.expectedAgentIds === undefined || config.expectedAgentIds.some((id) => id.toLowerCase().includes("security")), {
11656
+ message: "allowSecurityVeto requires at least one security agent in expectedAgentIds",
11657
+ path: ["expectedAgentIds"]
11625
11658
  });
11626
11659
  CouncilVerdictSchema = exports_external.object({
11627
11660
  status: exports_external.enum(["APPROVED", "REJECTED", "ESCALATED"]),
@@ -11773,8 +11806,17 @@ var init_schemas = __esm(() => {
11773
11806
  agentType: exports_external.string(),
11774
11807
  evidenceRequired: exports_external.array(exports_external.string()).default([]),
11775
11808
  status: CanonicalTaskCardStatusSchema,
11776
- type: exports_external.enum(["feature", "fix", "refactor", "review", "docs", "test"]).optional(),
11777
- linkedCapabilities: exports_external.array(exports_external.string()).default([])
11809
+ type: exports_external.enum(["feature", "fix", "refactor", "review", "docs", "test"]),
11810
+ linkedCapabilities: exports_external.array(exports_external.string()).default([]),
11811
+ boundaries: exports_external.array(exports_external.string()).default([]),
11812
+ requiresHumanReview: exports_external.boolean().optional(),
11813
+ requiresTests: exports_external.boolean().optional(),
11814
+ contextScope: exports_external.enum(["isolated", "continuation", "full"]).optional(),
11815
+ actualBehavior: exports_external.string().optional(),
11816
+ expectedBehavior: exports_external.string().optional(),
11817
+ reproductionSteps: exports_external.array(exports_external.string()).optional(),
11818
+ domain: exports_external.string().optional(),
11819
+ authorization: exports_external.string().optional()
11778
11820
  });
11779
11821
  ProductGraphNodeSchema = exports_external.object({
11780
11822
  id: exports_external.string(),
@@ -11932,7 +11974,8 @@ var init_schemas = __esm(() => {
11932
11974
  modelHint: exports_external.string().optional(),
11933
11975
  dependsOn: exports_external.array(exports_external.string()).default([]),
11934
11976
  acceptanceCriteria: exports_external.array(exports_external.string()),
11935
- status: OpenspecTaskCardStatusSchema
11977
+ status: OpenspecTaskCardStatusSchema,
11978
+ itemHash: exports_external.string().optional()
11936
11979
  });
11937
11980
  OpenspecStateSchema = exports_external.object({
11938
11981
  version: exports_external.literal(1),
@@ -11954,6 +11997,7 @@ var init_schemas = __esm(() => {
11954
11997
  "db-migration",
11955
11998
  "pagespeed",
11956
11999
  "openspec",
12000
+ "backlog",
11957
12001
  "scorecard",
11958
12002
  "council",
11959
12003
  "iterative",
@@ -11961,7 +12005,8 @@ var init_schemas = __esm(() => {
11961
12005
  "triage",
11962
12006
  "prototype",
11963
12007
  "handoff",
11964
- "clarify"
12008
+ "clarify",
12009
+ "security"
11965
12010
  ]);
11966
12011
  CcepOutputFormatSchema = exports_external.enum(["taskcard", "plan", "verdict"]);
11967
12012
  CommandEnvelopeSchema = exports_external.object({
@@ -12195,7 +12240,86 @@ var init_schemas = __esm(() => {
12195
12240
  tokensIn: exports_external.number().optional(),
12196
12241
  tokensOut: exports_external.number().optional(),
12197
12242
  costUsd: exports_external.number().optional(),
12198
- durationMs: exports_external.number().optional()
12243
+ durationMs: exports_external.number().optional(),
12244
+ experimentId: exports_external.string().optional(),
12245
+ variantId: exports_external.string().optional(),
12246
+ suiteTaskId: exports_external.string().optional(),
12247
+ harnessFingerprint: exports_external.string().optional(),
12248
+ disabledComponents: exports_external.array(exports_external.string()).optional()
12249
+ });
12250
+ HarnessComponentIdSchema = exports_external.enum([
12251
+ "wayfinding",
12252
+ "intake",
12253
+ "design",
12254
+ "test_first",
12255
+ "review",
12256
+ "docs",
12257
+ "confirmation_gates",
12258
+ "compile_loop",
12259
+ "council",
12260
+ "product_graph"
12261
+ ]);
12262
+ HarnessComponentLayerSchema = exports_external.enum([
12263
+ "phase",
12264
+ "gate",
12265
+ "loop",
12266
+ "knowledge",
12267
+ "council"
12268
+ ]);
12269
+ HarnessToggleKindSchema = exports_external.enum([
12270
+ "ccep_phase",
12271
+ "ccep_gates",
12272
+ "config_loop",
12273
+ "config_council",
12274
+ "product_graph"
12275
+ ]);
12276
+ HarnessCatalogEntrySchema = exports_external.object({
12277
+ id: HarnessComponentIdSchema,
12278
+ layer: HarnessComponentLayerSchema,
12279
+ label: exports_external.string(),
12280
+ defaultOn: exports_external.boolean().default(true),
12281
+ toggle: exports_external.object({
12282
+ kind: HarnessToggleKindSchema,
12283
+ phaseId: exports_external.string().optional()
12284
+ })
12285
+ });
12286
+ HarnessCatalogSchema = exports_external.object({
12287
+ version: exports_external.literal(1),
12288
+ components: exports_external.array(HarnessCatalogEntrySchema).min(1)
12289
+ });
12290
+ HarnessOverlaySchema = exports_external.object({
12291
+ experimentId: exports_external.string().optional(),
12292
+ variantId: exports_external.string(),
12293
+ disabledComponents: exports_external.array(HarnessComponentIdSchema),
12294
+ disableProductGraph: exports_external.boolean().optional().default(false),
12295
+ contractVersion: exports_external.string().optional(),
12296
+ ccepProfile: exports_external.string().optional().default("feature")
12297
+ });
12298
+ HarnessExperimentSchema = exports_external.object({
12299
+ id: exports_external.string(),
12300
+ suiteId: exports_external.string(),
12301
+ createdAt: exports_external.string(),
12302
+ contractVersion: exports_external.string(),
12303
+ components: exports_external.array(HarnessComponentIdSchema),
12304
+ variants: exports_external.array(exports_external.string()).min(1),
12305
+ suiteTaskIds: exports_external.array(exports_external.string()),
12306
+ suitePath: exports_external.string().optional()
12307
+ });
12308
+ HarnessSuiteTaskSchema = exports_external.object({
12309
+ id: exports_external.string(),
12310
+ title: exports_external.string(),
12311
+ type: exports_external.enum(["feature", "fix", "refactor"]),
12312
+ risk: exports_external.enum(["low", "medium", "high"]).default("low"),
12313
+ prompt: exports_external.string(),
12314
+ scope: exports_external.string(),
12315
+ acceptanceCriteria: exports_external.array(exports_external.string()).min(1),
12316
+ testCommand: exports_external.string().optional()
12317
+ });
12318
+ HarnessSuiteSchema = exports_external.object({
12319
+ id: exports_external.string(),
12320
+ version: exports_external.number().int().positive(),
12321
+ fixtureDir: exports_external.string().optional(),
12322
+ tasks: exports_external.array(HarnessSuiteTaskSchema).min(1)
12199
12323
  });
12200
12324
  EvaluationIndexSchema = exports_external.object({
12201
12325
  version: exports_external.literal(1),
@@ -12377,6 +12501,24 @@ var init_profiles = __esm(() => {
12377
12501
  routing: { default: ["validate-backlog", "discover", "design", "test", "implement", "review"] },
12378
12502
  confirmationGate: baseGate
12379
12503
  },
12504
+ backlog: {
12505
+ id: "backlog",
12506
+ version: 1,
12507
+ command: "backlog",
12508
+ taskCard: {
12509
+ type: "feature",
12510
+ requiredFields: ["title", "type", "risk", "scope", "context", "acceptanceCriteria"]
12511
+ },
12512
+ phases: [
12513
+ { id: "wayfinding", agent: "repo-explorer", outputSchema: "agent-output" },
12514
+ { id: "intake", agent: "task-coach", outputSchema: "planner-output", stopGate: "confirmation" },
12515
+ { id: "write-backlog", agent: "docs" },
12516
+ { id: "validate-backlog", agent: "orchestrator", type: "cli-gate" },
12517
+ { id: "plan-changes", agent: "orchestrator", type: "cli-gate" }
12518
+ ],
12519
+ routing: { default: ["wayfinding", "intake", "write-backlog", "validate-backlog", "plan-changes"] },
12520
+ confirmationGate: baseGate
12521
+ },
12380
12522
  scorecard: {
12381
12523
  id: "scorecard",
12382
12524
  version: 1,
@@ -12507,13 +12649,42 @@ var init_profiles = __esm(() => {
12507
12649
  ],
12508
12650
  routing: { default: ["re-explain"] },
12509
12651
  confirmationGate: { stopOnHighRisk: false, stopOnQuestions: true }
12652
+ },
12653
+ security: {
12654
+ id: "security",
12655
+ version: 1,
12656
+ command: "security",
12657
+ taskCard: {
12658
+ type: "fix",
12659
+ requiredFields: ["domain", "authorization", "risk", "scope"]
12660
+ },
12661
+ phases: [
12662
+ { id: "wayfinding", agent: "repo-explorer", outputSchema: "agent-output" },
12663
+ { id: "intake", agent: "task-coach", outputSchema: "planner-output", stopGate: "confirmation" },
12664
+ { id: "design", agent: "architect", stopGate: "approval" },
12665
+ { id: "test", agent: "tester" },
12666
+ { id: "implement", agent: "implementer", dependsOn: ["test"] },
12667
+ { id: "review", agent: "reviewer" }
12668
+ ],
12669
+ routing: {
12670
+ default: ["wayfinding", "intake", "test", "implement"],
12671
+ riskRules: [
12672
+ { when: { risk: "low" }, then: ["wayfinding", "intake", "test", "implement"] },
12673
+ {
12674
+ when: { risk: ["medium", "high"] },
12675
+ then: ["wayfinding", "intake", "design", "test", "implement", "review"]
12676
+ }
12677
+ ]
12678
+ },
12679
+ confirmationGate: baseGate
12510
12680
  }
12511
12681
  };
12512
12682
  });
12513
12683
 
12514
12684
  // src/core/ccep/profile-yaml.ts
12515
12685
  import { existsSync, readFileSync } from "node:fs";
12516
- import { join } from "node:path";
12686
+ import { dirname as dirname2, join } from "node:path";
12687
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
12517
12688
  function workflowYamlPath(command, projectRoot) {
12518
12689
  if (projectRoot) {
12519
12690
  const projectPath = join(projectRoot, ".codeconductor", "workflows", `${command}.yml`);
@@ -12551,7 +12722,7 @@ var init_profile_yaml = __esm(() => {
12551
12722
  init_dist();
12552
12723
  init_schemas();
12553
12724
  init_profiles();
12554
- BUNDLED_WORKFLOWS_DIR = join(import.meta.dir, "workflows");
12725
+ BUNDLED_WORKFLOWS_DIR = join(dirname2(fileURLToPath2(import.meta.url)), "workflows");
12555
12726
  });
12556
12727
 
12557
12728
  // src/core/ccep/workflow-init.ts
@@ -13130,17 +13301,65 @@ function resolveAssets(target) {
13130
13301
  }
13131
13302
  var CURRENT_PRESET_VERSION = "v1.0.0";
13132
13303
 
13304
+ // src/core/openspec/openspec-gitignore.ts
13305
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
13306
+ import { homedir } from "node:os";
13307
+ import { resolve as resolve4 } from "node:path";
13308
+ function gitignoreHasEntry(content, entry) {
13309
+ return content.split(/\r?\n/).some((line) => line.trim() === entry);
13310
+ }
13311
+ async function ensureOpenspecGitignore(projectRoot) {
13312
+ if (projectRoot === homedir())
13313
+ return null;
13314
+ const gitignorePath = resolve4(projectRoot, ".gitignore");
13315
+ let existing = "";
13316
+ try {
13317
+ existing = await readFile2(gitignorePath, "utf-8");
13318
+ } catch {
13319
+ existing = "";
13320
+ }
13321
+ const missing = OPENSPEC_GITIGNORE_ENTRIES.filter((entry) => !gitignoreHasEntry(existing, entry));
13322
+ if (missing.length === 0)
13323
+ return null;
13324
+ const chunks = [];
13325
+ if (existing.length > 0 && !existing.endsWith(`
13326
+ `)) {
13327
+ chunks.push(`
13328
+ `);
13329
+ }
13330
+ if (!existing.includes(OPENSPEC_GITIGNORE_HEADER)) {
13331
+ if (existing.length > 0)
13332
+ chunks.push(`
13333
+ `);
13334
+ chunks.push(OPENSPEC_GITIGNORE_HEADER, `
13335
+ `);
13336
+ }
13337
+ chunks.push(missing.join(`
13338
+ `), `
13339
+ `);
13340
+ await writeFile2(gitignorePath, existing + chunks.join(""), "utf-8");
13341
+ return ".gitignore";
13342
+ }
13343
+ var OPENSPEC_GITIGNORE_HEADER = "# CodeConductor OpenSpec (local operational artifacts)", OPENSPEC_GITIGNORE_ENTRIES;
13344
+ var init_openspec_gitignore = __esm(() => {
13345
+ OPENSPEC_GITIGNORE_ENTRIES = [
13346
+ "BACKLOG.md",
13347
+ "openspec/",
13348
+ ".codeconductor/openspec-state.json"
13349
+ ];
13350
+ });
13351
+
13133
13352
  // src/commands/init.command.ts
13134
13353
  var exports_init_command = {};
13135
13354
  __export(exports_init_command, {
13136
13355
  initCommand: () => initCommand
13137
13356
  });
13138
- import { access as access4, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
13139
- import { homedir } from "node:os";
13140
- import { basename, resolve as resolve4 } from "node:path";
13357
+ import { access as access4, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
13358
+ import { homedir as homedir2 } from "node:os";
13359
+ import { basename, resolve as resolve5 } from "node:path";
13141
13360
  async function initCommand(options) {
13142
13361
  const { projectRoot, dryRun, force, global: isGlobal, output, locale } = options;
13143
- const baseDir = isGlobal ? homedir() : projectRoot;
13362
+ const baseDir = isGlobal ? homedir2() : projectRoot;
13144
13363
  try {
13145
13364
  let profile = null;
13146
13365
  if (!isGlobal) {
@@ -13217,6 +13436,7 @@ async function initCommand(options) {
13217
13436
  }
13218
13437
  const copiedPresets = await copyPresets(baseDir, presetsToCopy, force);
13219
13438
  const openspecCreated = await initOpenspecArtifacts(baseDir, force);
13439
+ const gitignoreCreated = await ensureOpenspecGitignore(baseDir);
13220
13440
  const evalCreated = await initEvaluationArtifacts(baseDir, force);
13221
13441
  const workflowsCreated = await initWorkflowArtifacts(baseDir, force);
13222
13442
  return {
@@ -13228,6 +13448,7 @@ async function initCommand(options) {
13228
13448
  ".codeconductor/config.yml",
13229
13449
  ...copiedPresets,
13230
13450
  ...openspecCreated,
13451
+ ...gitignoreCreated ? [gitignoreCreated] : [],
13231
13452
  ...evalCreated,
13232
13453
  ...workflowsCreated
13233
13454
  ],
@@ -13256,7 +13477,7 @@ async function initCommand(options) {
13256
13477
  }
13257
13478
  async function resolvePresetsToCopy() {
13258
13479
  const sources = [];
13259
- const bundledCouncil = resolve4(SRC_PRESETS_DIR, "council", "council.yml");
13480
+ const bundledCouncil = resolve5(SRC_PRESETS_DIR, "council", "council.yml");
13260
13481
  if (await fileExists3(bundledCouncil)) {
13261
13482
  sources.push({ name: "council.yml", sourcePath: bundledCouncil });
13262
13483
  }
@@ -13267,17 +13488,17 @@ async function resolvePresetsToCopy() {
13267
13488
  return sources;
13268
13489
  }
13269
13490
  async function copyPresets(baseDir, presets, force) {
13270
- const presetsDir = resolve4(baseDir, ".codeconductor", "presets");
13491
+ const presetsDir = resolve5(baseDir, ".codeconductor", "presets");
13271
13492
  await mkdir3(presetsDir, { recursive: true });
13272
13493
  const copied = [];
13273
13494
  for (const preset of presets) {
13274
- const destPath = resolve4(presetsDir, preset.name);
13495
+ const destPath = resolve5(presetsDir, preset.name);
13275
13496
  if (!force && await fileExists3(destPath)) {
13276
13497
  continue;
13277
13498
  }
13278
13499
  try {
13279
- const content = await readFile2(preset.sourcePath, "utf-8");
13280
- await writeFile2(destPath, content, "utf-8");
13500
+ const content = await readFile3(preset.sourcePath, "utf-8");
13501
+ await writeFile3(destPath, content, "utf-8");
13281
13502
  copied.push(`.codeconductor/presets/${preset.name}`);
13282
13503
  } catch (err2) {
13283
13504
  const code = err2.code;
@@ -13298,21 +13519,21 @@ async function fileExists3(path) {
13298
13519
  }
13299
13520
  }
13300
13521
  async function initOpenspecArtifacts(baseDir, force) {
13301
- if (baseDir === homedir())
13522
+ if (baseDir === homedir2())
13302
13523
  return [];
13303
13524
  const created = [];
13304
- const backlogDest = resolve4(baseDir, "BACKLOG.md");
13305
- const backlogTemplate = resolve4(ROOT_PRESETS_DIR, "templates", "BACKLOG.md");
13525
+ const backlogDest = resolve5(baseDir, "BACKLOG.md");
13526
+ const backlogTemplate = resolve5(ROOT_PRESETS_DIR, "templates", "BACKLOG.md");
13306
13527
  if (force || !await fileExists3(backlogDest)) {
13307
13528
  if (await fileExists3(backlogTemplate)) {
13308
- const content = await readFile2(backlogTemplate, "utf-8");
13309
- await writeFile2(backlogDest, content, "utf-8");
13529
+ const content = await readFile3(backlogTemplate, "utf-8");
13530
+ await writeFile3(backlogDest, content, "utf-8");
13310
13531
  created.push("BACKLOG.md");
13311
13532
  }
13312
13533
  }
13313
- const stateDir = resolve4(baseDir, ".codeconductor");
13534
+ const stateDir = resolve5(baseDir, ".codeconductor");
13314
13535
  await mkdir3(stateDir, { recursive: true });
13315
- const stateDest = resolve4(stateDir, "openspec-state.json");
13536
+ const stateDest = resolve5(stateDir, "openspec-state.json");
13316
13537
  if (force || !await fileExists3(stateDest)) {
13317
13538
  const initialState = JSON.stringify({
13318
13539
  version: 1,
@@ -13320,34 +13541,34 @@ async function initOpenspecArtifacts(baseDir, force) {
13320
13541
  changePaths: {},
13321
13542
  itemSnapshots: {}
13322
13543
  }, null, 2);
13323
- await writeFile2(stateDest, initialState, "utf-8");
13544
+ await writeFile3(stateDest, initialState, "utf-8");
13324
13545
  created.push(".codeconductor/openspec-state.json");
13325
13546
  }
13326
13547
  return created;
13327
13548
  }
13328
13549
  async function initEvaluationArtifacts(baseDir, force) {
13329
- if (baseDir === homedir())
13550
+ if (baseDir === homedir2())
13330
13551
  return [];
13331
13552
  const created = [];
13332
- const evalDir = resolve4(baseDir, ".codeconductor", "evaluation");
13553
+ const evalDir = resolve5(baseDir, ".codeconductor", "evaluation");
13333
13554
  await mkdir3(evalDir, { recursive: true });
13334
- await mkdir3(resolve4(evalDir, "scorecards"), { recursive: true });
13335
- const indexPath = resolve4(evalDir, "index.json");
13555
+ await mkdir3(resolve5(evalDir, "scorecards"), { recursive: true });
13556
+ const indexPath = resolve5(evalDir, "index.json");
13336
13557
  if (force || !await fileExists3(indexPath)) {
13337
- await writeFile2(indexPath, JSON.stringify({ version: 1 }, null, 2), "utf-8");
13558
+ await writeFile3(indexPath, JSON.stringify({ version: 1 }, null, 2), "utf-8");
13338
13559
  created.push(".codeconductor/evaluation/index.json");
13339
13560
  }
13340
- const outcomesPath = resolve4(evalDir, "outcomes.jsonl");
13561
+ const outcomesPath = resolve5(evalDir, "outcomes.jsonl");
13341
13562
  if (force || !await fileExists3(outcomesPath)) {
13342
- await writeFile2(outcomesPath, "", "utf-8");
13563
+ await writeFile3(outcomesPath, "", "utf-8");
13343
13564
  created.push(".codeconductor/evaluation/outcomes.jsonl");
13344
13565
  }
13345
- const profileDest = resolve4(evalDir, "execution-profile.yml");
13346
- const profileTemplate = resolve4(ROOT_PRESETS_DIR, "templates", "execution-profile.yml");
13566
+ const profileDest = resolve5(evalDir, "execution-profile.yml");
13567
+ const profileTemplate = resolve5(ROOT_PRESETS_DIR, "templates", "execution-profile.yml");
13347
13568
  if (force || !await fileExists3(profileDest)) {
13348
13569
  if (await fileExists3(profileTemplate)) {
13349
- const content = await readFile2(profileTemplate, "utf-8");
13350
- await writeFile2(profileDest, content, "utf-8");
13570
+ const content = await readFile3(profileTemplate, "utf-8");
13571
+ await writeFile3(profileDest, content, "utf-8");
13351
13572
  created.push(".codeconductor/evaluation/execution-profile.yml");
13352
13573
  }
13353
13574
  }
@@ -13358,6 +13579,7 @@ var init_init_command = __esm(() => {
13358
13579
  init_workflow_init();
13359
13580
  init_project_detector();
13360
13581
  init_package_paths();
13582
+ init_openspec_gitignore();
13361
13583
  });
13362
13584
 
13363
13585
  // src/commands/detect.command.ts
@@ -13670,12 +13892,12 @@ var init_opencode_lsp_generator = __esm(() => {
13670
13892
  });
13671
13893
 
13672
13894
  // src/core/config/config-loader.ts
13673
- import { readFile as readFile3 } from "node:fs/promises";
13674
- import { resolve as resolve5 } from "node:path";
13895
+ import { readFile as readFile4 } from "node:fs/promises";
13896
+ import { resolve as resolve6 } from "node:path";
13675
13897
  async function loadConfig(projectRoot) {
13676
13898
  try {
13677
- const configPath = resolve5(projectRoot, CONFIG_FILE2);
13678
- const content = await readFile3(configPath, "utf-8");
13899
+ const configPath = resolve6(projectRoot, CONFIG_FILE2);
13900
+ const content = await readFile4(configPath, "utf-8");
13679
13901
  const data = $parse(content);
13680
13902
  if (!data) {
13681
13903
  return err(new ValidationError("Empty config file"));
@@ -13694,8 +13916,8 @@ async function loadConfig(projectRoot) {
13694
13916
  }
13695
13917
  async function configExists(projectRoot) {
13696
13918
  try {
13697
- const configPath = resolve5(projectRoot, CONFIG_FILE2);
13698
- await readFile3(configPath, "utf-8");
13919
+ const configPath = resolve6(projectRoot, CONFIG_FILE2);
13920
+ await readFile4(configPath, "utf-8");
13699
13921
  return true;
13700
13922
  } catch {
13701
13923
  return false;
@@ -13711,7 +13933,7 @@ var init_config_loader = __esm(() => {
13711
13933
  // src/core/filesystem/path-containment.ts
13712
13934
  import { constants as constants2 } from "node:fs";
13713
13935
  import { lstat, mkdir as mkdir4, open, realpath } from "node:fs/promises";
13714
- import { basename as basename2, dirname as dirname2, isAbsolute, join as join3, relative, resolve as resolve6, sep as sep2 } from "node:path";
13936
+ import { basename as basename2, dirname as dirname3, isAbsolute, join as join3, relative, resolve as resolve7, sep as sep2 } from "node:path";
13715
13937
  function isInside(root, target) {
13716
13938
  const rel = relative(root, target);
13717
13939
  return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute(rel);
@@ -13730,8 +13952,8 @@ async function resolveWithinRoot(projectRoot, relPath) {
13730
13952
  if (isAbsolute(relPath)) {
13731
13953
  return;
13732
13954
  }
13733
- const rootAbs = resolve6(projectRoot);
13734
- const candidate = resolve6(rootAbs, relPath);
13955
+ const rootAbs = resolve7(projectRoot);
13956
+ const candidate = resolve7(rootAbs, relPath);
13735
13957
  if (!isInside(rootAbs, candidate)) {
13736
13958
  return;
13737
13959
  }
@@ -13747,7 +13969,7 @@ async function readFileWithinRoot(projectRoot, relPath) {
13747
13969
  if (candidateReal === undefined) {
13748
13970
  return;
13749
13971
  }
13750
- const rootReal = await realpath(resolve6(projectRoot));
13972
+ const rootReal = await realpath(resolve7(projectRoot));
13751
13973
  let handle;
13752
13974
  try {
13753
13975
  handle = await open(candidateReal, constants2.O_RDONLY | O_NOFOLLOW);
@@ -13782,7 +14004,7 @@ async function canonicalizeExistingPrefix(target) {
13782
14004
  if (error.code !== "ENOENT") {
13783
14005
  throw error;
13784
14006
  }
13785
- const parent = dirname2(current);
14007
+ const parent = dirname3(current);
13786
14008
  if (parent === current) {
13787
14009
  throw error;
13788
14010
  }
@@ -13795,8 +14017,8 @@ async function resolveOutputWithinRoot(projectRoot, relPath) {
13795
14017
  if (relPath === "" || isAbsolute(relPath)) {
13796
14018
  return;
13797
14019
  }
13798
- const rootAbs = resolve6(projectRoot);
13799
- const candidate = resolve6(rootAbs, relPath);
14020
+ const rootAbs = resolve7(projectRoot);
14021
+ const candidate = resolve7(rootAbs, relPath);
13800
14022
  if (!isInside(rootAbs, candidate)) {
13801
14023
  return;
13802
14024
  }
@@ -13879,12 +14101,12 @@ async function writeContainedFile(projectRoot, relPath, content, options) {
13879
14101
  if (resolved === undefined) {
13880
14102
  throw new OutputPathError(`Output path is not an allowed destination: ${relPath}`);
13881
14103
  }
13882
- await mkdir4(dirname2(resolved), { recursive: true });
14104
+ await mkdir4(dirname3(resolved), { recursive: true });
13883
14105
  const outputPath = await resolveOutputWithinRoot(projectRoot, relPath);
13884
14106
  if (outputPath === undefined) {
13885
14107
  throw new OutputPathError(`Output path is not an allowed destination: ${relPath}`);
13886
14108
  }
13887
- const rootReal = await realpath(resolve6(projectRoot));
14109
+ const rootReal = await realpath(resolve7(projectRoot));
13888
14110
  let handle;
13889
14111
  try {
13890
14112
  handle = await openOutputHandle(outputPath, force);
@@ -14035,7 +14257,7 @@ var init_file_writer = __esm(() => {
14035
14257
 
14036
14258
  // src/core/lsp/binary-integrity.ts
14037
14259
  import { createHash } from "node:crypto";
14038
- import { isAbsolute as isAbsolute2, resolve as resolve7, sep as sep3 } from "node:path";
14260
+ import { isAbsolute as isAbsolute2, resolve as resolve8, sep as sep3 } from "node:path";
14039
14261
  function assertPinnedBinaryUrl(url) {
14040
14262
  let parsed;
14041
14263
  try {
@@ -14071,8 +14293,8 @@ function resolveSafeArchiveEntry(destRoot, entry) {
14071
14293
  if (normalized === "" || isAbsolute2(normalized) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || normalized.split("/").includes("..")) {
14072
14294
  throw new Error(`Unsafe archive entry rejected: ${entry}`);
14073
14295
  }
14074
- const root = resolve7(destRoot);
14075
- const target = resolve7(root, normalized);
14296
+ const root = resolve8(destRoot);
14297
+ const target = resolve8(root, normalized);
14076
14298
  const rel = target.slice(root.length);
14077
14299
  if (target !== root && (rel === "" || !rel.startsWith(sep3))) {
14078
14300
  throw new Error(`Unsafe archive entry rejected: ${entry}`);
@@ -14322,7 +14544,7 @@ function parseContentLength(raw, url) {
14322
14544
  return declared;
14323
14545
  }
14324
14546
  function performRequest(target, remainingMs, originalUrl, totalTimeout, followRedirects) {
14325
- return new Promise((resolve8, reject) => {
14547
+ return new Promise((resolve9, reject) => {
14326
14548
  const isHttps = target.url.protocol === "https:";
14327
14549
  const port = target.url.port !== "" ? Number(target.url.port) : isHttps ? 443 : 80;
14328
14550
  let timer;
@@ -14341,7 +14563,7 @@ function performRequest(target, remainingMs, originalUrl, totalTimeout, followRe
14341
14563
  return;
14342
14564
  settled = true;
14343
14565
  clearTimeout(timer);
14344
- resolve8(response);
14566
+ resolve9(response);
14345
14567
  };
14346
14568
  timer = setTimeout(() => {
14347
14569
  fail(new SafeFetchError(`Request timed out after ${totalTimeout}ms: ${originalUrl}`));
@@ -14445,7 +14667,7 @@ async function safeFetchWithInternals(urlString, options = {}, internals = {}) {
14445
14667
  }
14446
14668
  }
14447
14669
  async function delay(ms) {
14448
- return new Promise((resolve8) => setTimeout(resolve8, ms));
14670
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
14449
14671
  }
14450
14672
  var MAX_BODY_BYTES, MAX_REDIRECTS = 5, USER_AGENT = "CodeConductor-SEO/0.3.0", ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", SafeFetchError, BLOCKED_IPV4_CIDRS, BLOCKED_IPV4;
14451
14673
  var init_safe_fetch_internal = __esm(() => {
@@ -14664,8 +14886,8 @@ var init_binary_downloader = __esm(() => {
14664
14886
 
14665
14887
  // src/core/lsp/lsp-installer.ts
14666
14888
  import { execFile } from "node:child_process";
14667
- import { access as access6, mkdir as mkdir5, mkdtemp, rename, rm, writeFile as writeFile3 } from "node:fs/promises";
14668
- import { homedir as homedir2, tmpdir } from "node:os";
14889
+ import { access as access6, mkdir as mkdir5, mkdtemp, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
14890
+ import { homedir as homedir3, tmpdir } from "node:os";
14669
14891
  import { join as join4 } from "node:path";
14670
14892
  import { promisify } from "node:util";
14671
14893
  async function assertTarArchiveSafe(archivePath, destRoot) {
@@ -14687,7 +14909,7 @@ async function extractHardenedTar(archivePath, destRoot) {
14687
14909
  class LspInstaller {
14688
14910
  lspBinDir;
14689
14911
  constructor() {
14690
- this.lspBinDir = join4(homedir2(), ".codeconductor", "lsp", "bin");
14912
+ this.lspBinDir = join4(homedir3(), ".codeconductor", "lsp", "bin");
14691
14913
  }
14692
14914
  async checkInstalled(def) {
14693
14915
  try {
@@ -14757,11 +14979,9 @@ class LspInstaller {
14757
14979
  const results = [];
14758
14980
  for (const lsp of lsps) {
14759
14981
  if (options.dryRun) {
14760
- const status = await this.checkInstalled(lsp);
14761
14982
  results.push({
14762
14983
  lspId: lsp.id,
14763
- status: status.installed ? "already-installed" : "installed",
14764
- version: status.version
14984
+ status: "installed"
14765
14985
  });
14766
14986
  } else {
14767
14987
  const result = await this.installLsp(lsp);
@@ -14833,7 +15053,7 @@ class LspInstaller {
14833
15053
  try {
14834
15054
  const archiveName = binary.url.endsWith(".zip") ? "server.zip" : "server.tar.gz";
14835
15055
  const archivePath = join4(workDir, archiveName);
14836
- await writeFile3(archivePath, bytes);
15056
+ await writeFile4(archivePath, bytes);
14837
15057
  if (archiveName.endsWith(".zip")) {
14838
15058
  throw new Error(`Zip binary installs are not supported yet for ${def.serverName}; use a .tar.gz artifact`);
14839
15059
  }
@@ -14976,11 +15196,11 @@ var exports_install_lsp_command = {};
14976
15196
  __export(exports_install_lsp_command, {
14977
15197
  installLspCommand: () => installLspCommand
14978
15198
  });
14979
- import { homedir as homedir3 } from "node:os";
14980
- import { resolve as resolve8 } from "node:path";
15199
+ import { homedir as homedir4 } from "node:os";
15200
+ import { resolve as resolve9 } from "node:path";
14981
15201
  async function installLspCommand(options) {
14982
15202
  const { target, lang, dryRun, force, global: isGlobal, output, projectRoot } = options;
14983
- const baseDir = isGlobal ? homedir3() : projectRoot;
15203
+ const baseDir = isGlobal ? homedir4() : projectRoot;
14984
15204
  try {
14985
15205
  const runnerTarget = parseRunnerTarget(target);
14986
15206
  const targets = getIndividualTargets(runnerTarget);
@@ -15028,7 +15248,7 @@ async function installLspCommand(options) {
15028
15248
  const generatedFiles = generator.generate(installReport.results);
15029
15249
  const resolvedFiles = generatedFiles.map((f) => ({
15030
15250
  ...f,
15031
- path: resolve8(baseDir, f.path)
15251
+ path: resolve9(baseDir, f.path)
15032
15252
  }));
15033
15253
  const results = await writeGeneratedFiles(resolvedFiles, {
15034
15254
  ...writeOptions,
@@ -15919,18 +16139,18 @@ function countOccurrences(content, needle) {
15919
16139
  var MANAGED_BEGIN_MARKER = "<!-- CODECONDUCTOR:BEGIN managed -->", MANAGED_END_MARKER = "<!-- CODECONDUCTOR:END managed -->";
15920
16140
 
15921
16141
  // src/core/presets/complementary-detector.ts
15922
- import { execSync } from "node:child_process";
16142
+ import { execFileSync } from "node:child_process";
15923
16143
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
15924
16144
  import { join as join5 } from "node:path";
15925
- import { homedir as homedir4 } from "node:os";
16145
+ import { homedir as homedir5 } from "node:os";
15926
16146
  function detectComplementaryTools() {
15927
16147
  if (cachedStatus) {
15928
16148
  return cachedStatus;
15929
16149
  }
15930
16150
  const isCmdAvailable = (cmd) => {
15931
16151
  try {
15932
- const command = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
15933
- execSync(command, { stdio: "ignore", timeout: 1000 });
16152
+ const binary = process.platform === "win32" ? "where" : "which";
16153
+ execFileSync(binary, [cmd], { stdio: "ignore", timeout: 1000 });
15934
16154
  return true;
15935
16155
  } catch {
15936
16156
  return false;
@@ -15938,7 +16158,7 @@ function detectComplementaryTools() {
15938
16158
  };
15939
16159
  const hasCaveman = () => {
15940
16160
  try {
15941
- const globalSettingsPath = join5(homedir4(), ".claude", "settings.json");
16161
+ const globalSettingsPath = join5(homedir5(), ".claude", "settings.json");
15942
16162
  if (existsSync2(globalSettingsPath)) {
15943
16163
  const content = readFileSync2(globalSettingsPath, "utf8");
15944
16164
  const settings = JSON.parse(content);
@@ -15973,9 +16193,9 @@ var cachedStatus = null;
15973
16193
  var init_complementary_detector = () => {};
15974
16194
 
15975
16195
  // src/core/presets/file-copier.ts
15976
- import { lstat as lstat2, readdir as readdir2, readFile as readFile4, stat } from "node:fs/promises";
15977
- import { homedir as homedir5 } from "node:os";
15978
- import { join as join6, relative as relative3, resolve as resolve9 } from "node:path";
16196
+ import { lstat as lstat2, readdir as readdir2, readFile as readFile5, stat } from "node:fs/promises";
16197
+ import { homedir as homedir6 } from "node:os";
16198
+ import { join as join6, relative as relative3, resolve as resolve10 } from "node:path";
15979
16199
  function isMaintainerReservedDest(baseDir, destPath) {
15980
16200
  const rel = relative3(baseDir, destPath).replace(/\\/g, "/");
15981
16201
  if (MAINTAINER_RESERVED_EXACT.has(rel))
@@ -16004,17 +16224,17 @@ async function fileExists4(path) {
16004
16224
  }
16005
16225
  }
16006
16226
  async function resolveEntryFiles(entry, presetsDir, baseDir) {
16007
- const srcAbsolute = resolve9(presetsDir, entry.src);
16227
+ const srcAbsolute = resolve10(presetsDir, entry.src);
16008
16228
  try {
16009
16229
  const s = await stat(srcAbsolute);
16010
16230
  if (s.isDirectory()) {
16011
16231
  const files = await listFilesRecursive(srcAbsolute);
16012
16232
  return files.map((f) => ({
16013
16233
  src: join6(srcAbsolute, f),
16014
- dest: resolve9(baseDir, entry.dest, f)
16234
+ dest: resolve10(baseDir, entry.dest, f)
16015
16235
  }));
16016
16236
  }
16017
- return [{ src: srcAbsolute, dest: resolve9(baseDir, entry.dest) }];
16237
+ return [{ src: srcAbsolute, dest: resolve10(baseDir, entry.dest) }];
16018
16238
  } catch {
16019
16239
  return [];
16020
16240
  }
@@ -16282,7 +16502,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
16282
16502
  }
16283
16503
  let content;
16284
16504
  try {
16285
- content = await readFile4(srcPath, "utf-8");
16505
+ content = await readFile5(srcPath, "utf-8");
16286
16506
  } catch (e) {
16287
16507
  return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
16288
16508
  }
@@ -16293,7 +16513,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
16293
16513
  if (strategy === "append") {
16294
16514
  let existing = "";
16295
16515
  try {
16296
- existing = await readFile4(destPath, "utf-8");
16516
+ existing = await readFile5(destPath, "utf-8");
16297
16517
  } catch {}
16298
16518
  if (existing) {
16299
16519
  finalContent = existing + `
@@ -16306,7 +16526,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
16306
16526
  } else if (strategy === "merge-json") {
16307
16527
  let existing = {};
16308
16528
  try {
16309
- existing = JSON.parse(await readFile4(destPath, "utf-8"));
16529
+ existing = JSON.parse(await readFile5(destPath, "utf-8"));
16310
16530
  } catch {}
16311
16531
  try {
16312
16532
  const incoming = JSON.parse(incomingContent);
@@ -16318,7 +16538,7 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
16318
16538
  } else if (strategy === "merge-managed") {
16319
16539
  let existing = null;
16320
16540
  try {
16321
- existing = await readFile4(destPath, "utf-8");
16541
+ existing = await readFile5(destPath, "utf-8");
16322
16542
  } catch {}
16323
16543
  const markerless = existing !== null && !existing.includes(MANAGED_BEGIN_MARKER) && !existing.includes(MANAGED_END_MARKER);
16324
16544
  if (markerless && !force) {
@@ -16359,7 +16579,7 @@ async function copyFromManifest(manifest, presetsDir, baseDir, isGlobal, dryRun,
16359
16579
  let resolvedBaseDir = baseDir;
16360
16580
  let resolvedEntry = entry;
16361
16581
  if (manifest.target === "agy" && isGlobal) {
16362
- resolvedBaseDir = join6(homedir5(), ".gemini", "config");
16582
+ resolvedBaseDir = join6(homedir6(), ".gemini", "config");
16363
16583
  resolvedEntry = {
16364
16584
  ...entry,
16365
16585
  dest: entry.dest.replace(/^\.agents\/?/, "")
@@ -16391,17 +16611,17 @@ var init_file_copier = __esm(() => {
16391
16611
  });
16392
16612
 
16393
16613
  // src/core/presets/manifest-loader.ts
16394
- import { readFile as readFile5 } from "node:fs/promises";
16614
+ import { readFile as readFile6 } from "node:fs/promises";
16395
16615
  import { join as join7 } from "node:path";
16396
16616
  async function loadManifest(target) {
16397
16617
  const manifestPath = join7(MANIFESTS_DIR, `${target}.yml`);
16398
- const content = await readFile5(manifestPath, "utf-8");
16618
+ const content = await readFile6(manifestPath, "utf-8");
16399
16619
  const data = $parse(content);
16400
16620
  return InstallManifestSchema.parse(data);
16401
16621
  }
16402
16622
  async function loadModelConfig(target) {
16403
16623
  const modelPath = join7(MODELS_DIR, `${target}.yml`);
16404
- const content = await readFile5(modelPath, "utf-8");
16624
+ const content = await readFile6(modelPath, "utf-8");
16405
16625
  const data = $parse(content);
16406
16626
  return ModelConfigSchema.parse(data);
16407
16627
  }
@@ -16421,16 +16641,16 @@ __export(exports_preset_loader, {
16421
16641
  loadPreset: () => loadPreset,
16422
16642
  loadCouncilPreset: () => loadCouncilPreset
16423
16643
  });
16424
- import { readFile as readFile6 } from "node:fs/promises";
16425
- import { resolve as resolve10 } from "node:path";
16644
+ import { readFile as readFile7 } from "node:fs/promises";
16645
+ import { resolve as resolve11 } from "node:path";
16426
16646
  async function loadPreset(name, projectRoot = process.cwd()) {
16427
16647
  const candidates = [
16428
- resolve10(projectRoot, ".codeconductor", "presets", `${name}.yml`),
16429
- resolve10(SRC_PRESETS_DIR, name, `${name}.yml`)
16648
+ resolve11(projectRoot, ".codeconductor", "presets", `${name}.yml`),
16649
+ resolve11(SRC_PRESETS_DIR, name, `${name}.yml`)
16430
16650
  ];
16431
16651
  for (const presetPath of candidates) {
16432
16652
  try {
16433
- const content = await readFile6(presetPath, "utf-8");
16653
+ const content = await readFile7(presetPath, "utf-8");
16434
16654
  const data = $parse(content);
16435
16655
  if (!data)
16436
16656
  continue;
@@ -16464,11 +16684,11 @@ __export(exports_install_command, {
16464
16684
  installPresetCommand: () => installPresetCommand,
16465
16685
  installCommand: () => installCommand
16466
16686
  });
16467
- import { homedir as homedir6 } from "node:os";
16468
- import { resolve as resolve11 } from "node:path";
16687
+ import { homedir as homedir7 } from "node:os";
16688
+ import { resolve as resolve12 } from "node:path";
16469
16689
  async function installCommand(options) {
16470
16690
  const { target, dryRun, force, global: isGlobal, output, projectRoot } = options;
16471
- const baseDir = isGlobal ? homedir6() : projectRoot;
16691
+ const baseDir = isGlobal ? homedir7() : projectRoot;
16472
16692
  try {
16473
16693
  const runnerTarget = parseRunnerTarget(target);
16474
16694
  const targets = getIndividualTargets(runnerTarget);
@@ -16509,10 +16729,10 @@ async function installCommand(options) {
16509
16729
  }
16510
16730
  const generatedFiles = await installer.generate();
16511
16731
  const isAgyGlobal = (t === "agy" || t === "gemini") && isGlobal;
16512
- const targetBase = isAgyGlobal ? resolve11(homedir6(), ".gemini", "config") : baseDir;
16732
+ const targetBase = isAgyGlobal ? resolve12(homedir7(), ".gemini", "config") : baseDir;
16513
16733
  const resolvedFiles = generatedFiles.map((f) => ({
16514
16734
  ...f,
16515
- path: resolve11(targetBase, isAgyGlobal ? f.path.replace(/^\.agents\/?/, "") : f.path)
16735
+ path: resolve12(targetBase, isAgyGlobal ? f.path.replace(/^\.agents\/?/, "") : f.path)
16516
16736
  }));
16517
16737
  const results = await writeGeneratedFiles(resolvedFiles, {
16518
16738
  ...writeOptions,
@@ -16574,7 +16794,7 @@ async function installCommand(options) {
16574
16794
  }
16575
16795
  async function installPresetCommand(options) {
16576
16796
  const { target, dryRun, force, global: isGlobal, projectRoot } = options;
16577
- const baseDir = isGlobal ? homedir6() : projectRoot;
16797
+ const baseDir = isGlobal ? homedir7() : projectRoot;
16578
16798
  try {
16579
16799
  const runnerTarget = parseRunnerTarget(target);
16580
16800
  const targets = getIndividualTargets(runnerTarget);
@@ -16637,9 +16857,9 @@ var init_install_command = __esm(() => {
16637
16857
  });
16638
16858
 
16639
16859
  // src/core/security/target-compatibility.ts
16640
- import { readFile as readFile7 } from "node:fs/promises";
16860
+ import { readFile as readFile8 } from "node:fs/promises";
16641
16861
  async function loadTargetSecurityCompatibility() {
16642
- const policy = $parse(await readFile7(POLICY_PATH, "utf-8"));
16862
+ const policy = $parse(await readFile8(POLICY_PATH, "utf-8"));
16643
16863
  const targets = [
16644
16864
  "opencode",
16645
16865
  "claude",
@@ -16673,14 +16893,14 @@ var init_target_compatibility = __esm(() => {
16673
16893
  });
16674
16894
 
16675
16895
  // src/core/presets/update-checker.ts
16676
- import { stat as stat2, readFile as readFile8 } from "node:fs/promises";
16677
- import { resolve as resolve12, join as join8 } from "node:path";
16678
- import { homedir as homedir7 } from "node:os";
16896
+ import { stat as stat2, readFile as readFile9 } from "node:fs/promises";
16897
+ import { resolve as resolve13, join as join8 } from "node:path";
16898
+ import { homedir as homedir8 } from "node:os";
16679
16899
  function getTargetInstallationPath(target, basePath, isGlobal) {
16680
16900
  if (target === "agy") {
16681
- return isGlobal ? resolve12(basePath, ".gemini", "config") : resolve12(basePath, ".agents");
16901
+ return isGlobal ? resolve13(basePath, ".gemini", "config") : resolve13(basePath, ".agents");
16682
16902
  }
16683
- return resolve12(basePath, `.${target}`);
16903
+ return resolve13(basePath, `.${target}`);
16684
16904
  }
16685
16905
  async function isTargetInstalled(target, basePath, isGlobal) {
16686
16906
  const path = getTargetInstallationPath(target, basePath, isGlobal);
@@ -16693,12 +16913,12 @@ async function isTargetInstalled(target, basePath, isGlobal) {
16693
16913
  }
16694
16914
  async function validateAgentFileSizes(basePath, isGlobal) {
16695
16915
  const filesToCheck = [];
16696
- filesToCheck.push(resolve12(basePath, ".claude", "CLAUDE.md"));
16697
- filesToCheck.push(resolve12(basePath, ".codex", "AGENTS.md"));
16916
+ filesToCheck.push(resolve13(basePath, ".claude", "CLAUDE.md"));
16917
+ filesToCheck.push(resolve13(basePath, ".codex", "AGENTS.md"));
16698
16918
  if (isGlobal) {
16699
- filesToCheck.push(resolve12(basePath, ".gemini", "config", "AGENTS.md"));
16919
+ filesToCheck.push(resolve13(basePath, ".gemini", "config", "AGENTS.md"));
16700
16920
  } else {
16701
- filesToCheck.push(resolve12(basePath, ".agents", "AGENTS.md"));
16921
+ filesToCheck.push(resolve13(basePath, ".agents", "AGENTS.md"));
16702
16922
  }
16703
16923
  const largeFiles = [];
16704
16924
  for (const filePath of filesToCheck) {
@@ -16740,7 +16960,7 @@ async function validateAgentMarkers(basePath, isGlobal) {
16740
16960
  let resolvedEntry = entry;
16741
16961
  let targetBaseDir = basePath;
16742
16962
  if (target === "agy" && isGlobal) {
16743
- targetBaseDir = join8(homedir7(), ".gemini", "config");
16963
+ targetBaseDir = join8(homedir8(), ".gemini", "config");
16744
16964
  resolvedEntry = {
16745
16965
  ...entry,
16746
16966
  dest: entry.dest.replace(/^\.agents\/?/, "")
@@ -16749,7 +16969,7 @@ async function validateAgentMarkers(basePath, isGlobal) {
16749
16969
  const files = await resolveEntryFiles(resolvedEntry, PRESETS_DIR, targetBaseDir);
16750
16970
  for (const { dest } of files) {
16751
16971
  try {
16752
- const content = await readFile8(dest, "utf-8");
16972
+ const content = await readFile9(dest, "utf-8");
16753
16973
  const beginCount = content.split(MANAGED_BEGIN_MARKER).length - 1;
16754
16974
  const endCount = content.split(MANAGED_END_MARKER).length - 1;
16755
16975
  if (beginCount === 0 && endCount === 0) {
@@ -16773,8 +16993,8 @@ async function validateAgentMarkers(basePath, isGlobal) {
16773
16993
  }
16774
16994
  async function fileContentDiffers(pathA, pathB) {
16775
16995
  try {
16776
- const contentA = await readFile8(pathA, "utf-8");
16777
- const contentB = await readFile8(pathB, "utf-8");
16996
+ const contentA = await readFile9(pathA, "utf-8");
16997
+ const contentB = await readFile9(pathB, "utf-8");
16778
16998
  return contentA.trim() !== contentB.trim();
16779
16999
  } catch {
16780
17000
  try {
@@ -16787,12 +17007,12 @@ async function fileContentDiffers(pathA, pathB) {
16787
17007
  }
16788
17008
  async function loadSkillsLock(basePath) {
16789
17009
  const paths = [
16790
- resolve12(basePath, ".codeconductor", "skills-lock.json"),
16791
- resolve12(basePath, ".agents", "skills-lock.json")
17010
+ resolve13(basePath, ".codeconductor", "skills-lock.json"),
17011
+ resolve13(basePath, ".agents", "skills-lock.json")
16792
17012
  ];
16793
17013
  for (const p of paths) {
16794
17014
  try {
16795
- const content = await readFile8(p, "utf-8");
17015
+ const content = await readFile9(p, "utf-8");
16796
17016
  return JSON.parse(content);
16797
17017
  } catch {}
16798
17018
  }
@@ -16808,9 +17028,9 @@ async function getLatestSkillVersion(skillId) {
16808
17028
  "cursor"
16809
17029
  ];
16810
17030
  for (const target of targets) {
16811
- const skillPath = resolve12(ROOT_PRESETS_DIR, target, "skills", skillId, "SKILL.md");
17031
+ const skillPath = resolve13(ROOT_PRESETS_DIR, target, "skills", skillId, "SKILL.md");
16812
17032
  try {
16813
- const content = await readFile8(skillPath, "utf-8");
17033
+ const content = await readFile9(skillPath, "utf-8");
16814
17034
  const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
16815
17035
  if (fmMatch) {
16816
17036
  const parsed = $parse(fmMatch[1]);
@@ -16823,10 +17043,10 @@ async function getLatestSkillVersion(skillId) {
16823
17043
  return null;
16824
17044
  }
16825
17045
  async function checkUpdates(basePath, isGlobal) {
16826
- const localCouncil = resolve12(basePath, ".codeconductor", "presets", "council.yml");
16827
- const bundledCouncil = resolve12(SRC_PRESETS_DIR, "council", "council.yml");
17046
+ const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
17047
+ const bundledCouncil = resolve13(SRC_PRESETS_DIR, "council", "council.yml");
16828
17048
  const councilHasUpdate = await fileContentDiffers(localCouncil, bundledCouncil);
16829
- const localPolicy = resolve12(basePath, ".codeconductor", "presets", "policy.yml");
17049
+ const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
16830
17050
  const policyHasUpdate = await fileContentDiffers(localPolicy, POLICY_PATH);
16831
17051
  const targetsToCheck = [
16832
17052
  "opencode",
@@ -16865,7 +17085,7 @@ async function checkUpdates(basePath, isGlobal) {
16865
17085
  let resolvedEntry = entry;
16866
17086
  let targetBaseDir = basePath;
16867
17087
  if (target === "agy" && isGlobal) {
16868
- targetBaseDir = join8(homedir7(), ".gemini", "config");
17088
+ targetBaseDir = join8(homedir8(), ".gemini", "config");
16869
17089
  resolvedEntry = {
16870
17090
  ...entry,
16871
17091
  dest: entry.dest.replace(/^\.agents\/?/, "")
@@ -16876,14 +17096,14 @@ async function checkUpdates(basePath, isGlobal) {
16876
17096
  for (const { src, dest } of files) {
16877
17097
  let expectedContent;
16878
17098
  try {
16879
- const srcContent = await readFile8(src, "utf-8");
17099
+ const srcContent = await readFile9(src, "utf-8");
16880
17100
  expectedContent = isTemplate && modelConfig ? renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
16881
17101
  } catch {
16882
17102
  continue;
16883
17103
  }
16884
17104
  let destContent;
16885
17105
  try {
16886
- destContent = await readFile8(dest, "utf-8");
17106
+ destContent = await readFile9(dest, "utf-8");
16887
17107
  } catch {
16888
17108
  changedFiles.push(dest);
16889
17109
  continue;
@@ -16955,13 +17175,13 @@ async function checkUpdates(basePath, isGlobal) {
16955
17175
  let targetPath = f.path;
16956
17176
  let targetBase = basePath;
16957
17177
  if ((target === "agy" || target === "gemini") && isGlobal) {
16958
- targetBase = join8(homedir7(), ".gemini", "config");
17178
+ targetBase = join8(homedir8(), ".gemini", "config");
16959
17179
  targetPath = targetPath.replace(/^\.agents\/?/, "");
16960
17180
  }
16961
- const dest = resolve12(targetBase, targetPath);
17181
+ const dest = resolve13(targetBase, targetPath);
16962
17182
  let destContent;
16963
17183
  try {
16964
- destContent = await readFile8(dest, "utf-8");
17184
+ destContent = await readFile9(dest, "utf-8");
16965
17185
  if (destContent.trim() !== f.content.trim()) {
16966
17186
  changedFiles.push(dest);
16967
17187
  }
@@ -17032,7 +17252,7 @@ __export(exports_doctor_command, {
17032
17252
  doctorCommand: () => doctorCommand
17033
17253
  });
17034
17254
  import { join as join9 } from "node:path";
17035
- import { homedir as homedir8 } from "node:os";
17255
+ import { homedir as homedir9 } from "node:os";
17036
17256
  async function doctorCommand(options) {
17037
17257
  const { projectRoot, output } = options;
17038
17258
  const checks = [];
@@ -17108,7 +17328,7 @@ async function doctorCommand(options) {
17108
17328
  });
17109
17329
  }
17110
17330
  const localUpdates = await checkUpdates(projectRoot, false);
17111
- const globalUpdates = await checkUpdates(homedir8(), true);
17331
+ const globalUpdates = await checkUpdates(homedir9(), true);
17112
17332
  const updateDetails = [];
17113
17333
  if (localUpdates.hasUpdates) {
17114
17334
  if (localUpdates.council)
@@ -17152,7 +17372,7 @@ async function doctorCommand(options) {
17152
17372
  });
17153
17373
  }
17154
17374
  const largeFilesLocal = await validateAgentFileSizes(projectRoot, false);
17155
- const largeFilesGlobal = await validateAgentFileSizes(homedir8(), true);
17375
+ const largeFilesGlobal = await validateAgentFileSizes(homedir9(), true);
17156
17376
  const allLargeFiles = [...largeFilesLocal, ...largeFilesGlobal];
17157
17377
  if (allLargeFiles.length > 0) {
17158
17378
  checks.push({
@@ -17168,7 +17388,7 @@ async function doctorCommand(options) {
17168
17388
  });
17169
17389
  }
17170
17390
  const missingMarkersLocal = await validateAgentMarkers(projectRoot, false);
17171
- const missingMarkersGlobal = await validateAgentMarkers(homedir8(), true);
17391
+ const missingMarkersGlobal = await validateAgentMarkers(homedir9(), true);
17172
17392
  const allMissingMarkers = [...missingMarkersLocal, ...missingMarkersGlobal];
17173
17393
  if (allMissingMarkers.length > 0) {
17174
17394
  checks.push({
@@ -17196,8 +17416,8 @@ async function doctorCommand(options) {
17196
17416
  const graphPath = join9(projectRoot, ".codeconductor", "product-graph.json");
17197
17417
  if (existsSync3(graphPath)) {
17198
17418
  try {
17199
- const { readFile: readFile9 } = await import("node:fs/promises");
17200
- const raw = await readFile9(graphPath, "utf-8");
17419
+ const { readFile: readFile10 } = await import("node:fs/promises");
17420
+ const raw = await readFile10(graphPath, "utf-8");
17201
17421
  const graph = ProductGraphSchema2.parse(JSON.parse(raw));
17202
17422
  checks.push({
17203
17423
  name: "product-graph",
@@ -17284,12 +17504,12 @@ var exports_update_command = {};
17284
17504
  __export(exports_update_command, {
17285
17505
  updateCommand: () => updateCommand
17286
17506
  });
17287
- import { homedir as homedir9 } from "node:os";
17288
- import { resolve as resolve13, dirname as dirname3 } from "node:path";
17289
- import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile4, stat as stat3 } from "node:fs/promises";
17507
+ import { homedir as homedir10 } from "node:os";
17508
+ import { resolve as resolve14, dirname as dirname4 } from "node:path";
17509
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile5, stat as stat3 } from "node:fs/promises";
17290
17510
  async function updateCommand(options) {
17291
17511
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
17292
- const basePath = isGlobal ? homedir9() : projectRoot;
17512
+ const basePath = isGlobal ? homedir10() : projectRoot;
17293
17513
  try {
17294
17514
  const configResult = await loadConfig(basePath);
17295
17515
  if (!configResult.success) {
@@ -17351,23 +17571,23 @@ async function updateCommand(options) {
17351
17571
  }
17352
17572
  const updated = [];
17353
17573
  if (updateResults.council) {
17354
- const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
17355
- const bundledCouncil = resolve13(SRC_PRESETS_DIR, "council", "council.yml");
17574
+ const localCouncil = resolve14(basePath, ".codeconductor", "presets", "council.yml");
17575
+ const bundledCouncil = resolve14(SRC_PRESETS_DIR, "council", "council.yml");
17356
17576
  try {
17357
- const content = await readFile9(bundledCouncil, "utf-8");
17358
- await mkdir6(dirname3(localCouncil), { recursive: true });
17359
- await writeFile4(localCouncil, content, "utf-8");
17577
+ const content = await readFile10(bundledCouncil, "utf-8");
17578
+ await mkdir6(dirname4(localCouncil), { recursive: true });
17579
+ await writeFile5(localCouncil, content, "utf-8");
17360
17580
  updated.push(localCouncil);
17361
17581
  } catch (e) {
17362
17582
  throw new Error(`Failed to update council.yml: ${e}`);
17363
17583
  }
17364
17584
  }
17365
17585
  if (updateResults.policy) {
17366
- const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
17586
+ const localPolicy = resolve14(basePath, ".codeconductor", "presets", "policy.yml");
17367
17587
  try {
17368
- const content = await readFile9(POLICY_PATH, "utf-8");
17369
- await mkdir6(dirname3(localPolicy), { recursive: true });
17370
- await writeFile4(localPolicy, content, "utf-8");
17588
+ const content = await readFile10(POLICY_PATH, "utf-8");
17589
+ await mkdir6(dirname4(localPolicy), { recursive: true });
17590
+ await writeFile5(localPolicy, content, "utf-8");
17371
17591
  updated.push(localPolicy);
17372
17592
  } catch (e) {
17373
17593
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -17416,10 +17636,10 @@ async function updateCommand(options) {
17416
17636
  if (installer) {
17417
17637
  const generatedFiles = await installer.generate();
17418
17638
  const isAgyGlobal = (targetName === "agy" || targetName === "gemini") && isGlobal;
17419
- const targetBase = isAgyGlobal ? resolve13(homedir9(), ".gemini", "config") : basePath;
17639
+ const targetBase = isAgyGlobal ? resolve14(homedir10(), ".gemini", "config") : basePath;
17420
17640
  const resolvedFiles = generatedFiles.map((f) => ({
17421
17641
  ...f,
17422
- path: resolve13(targetBase, isAgyGlobal ? f.path.replace(/^\.agents\/?/, "") : f.path)
17642
+ path: resolve14(targetBase, isAgyGlobal ? f.path.replace(/^\.agents\/?/, "") : f.path)
17423
17643
  }));
17424
17644
  const filesToWrite = resolvedFiles.filter((f) => t.files.includes(f.path));
17425
17645
  if (filesToWrite.length > 0) {
@@ -17446,19 +17666,19 @@ async function updateCommand(options) {
17446
17666
  for (const s of updateResults.skills) {
17447
17667
  newSkillsLock[s.id] = s.latestVersion;
17448
17668
  }
17449
- let lockDest = resolve13(basePath, ".codeconductor", "skills-lock.json");
17669
+ let lockDest = resolve14(basePath, ".codeconductor", "skills-lock.json");
17450
17670
  try {
17451
- const statAgents = await stat3(resolve13(basePath, ".agents"));
17671
+ const statAgents = await stat3(resolve14(basePath, ".agents"));
17452
17672
  if (statAgents.isDirectory()) {
17453
- const statCodeConductor = await stat3(resolve13(basePath, ".codeconductor")).catch(() => null);
17673
+ const statCodeConductor = await stat3(resolve14(basePath, ".codeconductor")).catch(() => null);
17454
17674
  if (!statCodeConductor) {
17455
- lockDest = resolve13(basePath, ".agents", "skills-lock.json");
17675
+ lockDest = resolve14(basePath, ".agents", "skills-lock.json");
17456
17676
  }
17457
17677
  }
17458
17678
  } catch {}
17459
17679
  try {
17460
- await mkdir6(dirname3(lockDest), { recursive: true });
17461
- await writeFile4(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
17680
+ await mkdir6(dirname4(lockDest), { recursive: true });
17681
+ await writeFile5(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
17462
17682
  updated.push(lockDest);
17463
17683
  } catch (e) {
17464
17684
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -17504,8 +17724,38 @@ function recommendAskFlow(problem) {
17504
17724
  const flow = catalog.get(command);
17505
17725
  return { command: flow.command, slash: flow.slash, reason };
17506
17726
  };
17507
- if (includesAny(text, ["openspec", "backlog", "tracer bullet", "bc-"])) {
17508
- return pick("openspec", "The request names OpenSpec, BACKLOG, or a BC-xxx item, so /cc:openspec is the delivery loop.");
17727
+ if (includesAny(text, ["bc-", "tracer bullet", "openspec plan", "openspec next"]) || text.includes("deliver") && includesAny(text, ["backlog", "openspec", "bc-"])) {
17728
+ return pick("openspec", "The request names OpenSpec delivery, a tracer bullet, or a BC-xxx item, so /cc:openspec is the delivery loop.");
17729
+ }
17730
+ if (includesAny(text, [
17731
+ "create backlog",
17732
+ "write backlog",
17733
+ "add to backlog",
17734
+ "append to backlog",
17735
+ "new backlog",
17736
+ "author backlog",
17737
+ "add a backlog",
17738
+ "add backlog"
17739
+ ]) || text.includes("backlog") && includesAny(text, ["create", "write", "append", "objetivo", "objective", "objectives"])) {
17740
+ return pick("backlog", "The request is to create or append BACKLOG.md, so /cc:backlog is the authoring loop.");
17741
+ }
17742
+ if (includesAny(text, ["openspec", "backlog"])) {
17743
+ return pick("openspec", "The request names OpenSpec or BACKLOG without authoring verbs, so /cc:openspec is the delivery loop.");
17744
+ }
17745
+ if (includesAny(text, [
17746
+ "/cc-security",
17747
+ "/cc:security",
17748
+ "authorized security",
17749
+ "vulnerability assessment",
17750
+ "vuln assessment",
17751
+ "threat hunting",
17752
+ "incident response",
17753
+ "penetration test",
17754
+ "pentest",
17755
+ "purple team",
17756
+ "security hardening"
17757
+ ])) {
17758
+ return pick("security", "The request is authorized defensive security work, so /cc:security is the fit.");
17509
17759
  }
17510
17760
  if (includesAny(text, [
17511
17761
  "tdd",
@@ -17576,10 +17826,20 @@ var init_recommend_flow = __esm(() => {
17576
17826
  slash: "/cc:tdd-cycle",
17577
17827
  blurb: "one red-green-refactor cycle starting from a failing test"
17578
17828
  },
17829
+ {
17830
+ command: "backlog",
17831
+ slash: "/cc:backlog",
17832
+ blurb: "create or append BACKLOG.md items and OpenSpec change folders"
17833
+ },
17579
17834
  {
17580
17835
  command: "openspec",
17581
17836
  slash: "/cc:openspec",
17582
17837
  blurb: "deliver a BACKLOG.md / OpenSpec tracer bullet"
17838
+ },
17839
+ {
17840
+ command: "security",
17841
+ slash: "/cc:security",
17842
+ blurb: "authorized defensive security work with an authorization gate"
17583
17843
  }
17584
17844
  ];
17585
17845
  });
@@ -17766,7 +18026,7 @@ var exports_debt_harvest_command = {};
17766
18026
  __export(exports_debt_harvest_command, {
17767
18027
  debtHarvestCommand: () => debtHarvestCommand
17768
18028
  });
17769
- import { readdir as readdir4, readFile as readFile10, writeFile as writeFile5, mkdir as mkdir7 } from "node:fs/promises";
18029
+ import { readdir as readdir4, readFile as readFile11, writeFile as writeFile6, mkdir as mkdir7 } from "node:fs/promises";
17770
18030
  import { join as join11, relative as relative4, extname } from "node:path";
17771
18031
  async function walkDir(dir, extensions) {
17772
18032
  const results = [];
@@ -17844,7 +18104,7 @@ async function debtHarvestCommand(options) {
17844
18104
  const files = await walkDir(targetDir, SOURCE_EXTENSIONS);
17845
18105
  const allEntries = [];
17846
18106
  for (const file of files) {
17847
- const content = await readFile10(file, "utf-8");
18107
+ const content = await readFile11(file, "utf-8");
17848
18108
  const entries = extractDefers(content, relative4(projectRoot, file));
17849
18109
  allEntries.push(...entries);
17850
18110
  }
@@ -17852,7 +18112,7 @@ async function debtHarvestCommand(options) {
17852
18112
  await mkdir7(ledgerDir, { recursive: true });
17853
18113
  const ledgerPath = join11(ledgerDir, "debt-ledger.md");
17854
18114
  const ledgerContent = renderLedger(allEntries);
17855
- await writeFile5(ledgerPath, ledgerContent, "utf-8");
18115
+ await writeFile6(ledgerPath, ledgerContent, "utf-8");
17856
18116
  if (output === "json") {
17857
18117
  return {
17858
18118
  code: 0,
@@ -18322,11 +18582,11 @@ __export(exports_graph_store, {
18322
18582
  findPath: () => findPath,
18323
18583
  countByType: () => countByType
18324
18584
  });
18325
- import { mkdir as mkdir8, readFile as readFile11, writeFile as writeFile6 } from "node:fs/promises";
18326
- import { dirname as dirname4 } from "node:path";
18585
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile7 } from "node:fs/promises";
18586
+ import { dirname as dirname5 } from "node:path";
18327
18587
  async function loadGraph(projectRoot) {
18328
18588
  try {
18329
- const raw = await readFile11(productGraphPath(projectRoot), "utf-8");
18589
+ const raw = await readFile12(productGraphPath(projectRoot), "utf-8");
18330
18590
  return ok(ProductGraphSchema.parse(JSON.parse(raw)));
18331
18591
  } catch (e) {
18332
18592
  if (e.code === "ENOENT") {
@@ -18339,8 +18599,8 @@ async function saveGraph(projectRoot, graph) {
18339
18599
  try {
18340
18600
  const validated = ProductGraphSchema.parse(graph);
18341
18601
  const path = productGraphPath(projectRoot);
18342
- await mkdir8(dirname4(path), { recursive: true });
18343
- await writeFile6(path, JSON.stringify(validated, null, 2), "utf-8");
18602
+ await mkdir8(dirname5(path), { recursive: true });
18603
+ await writeFile7(path, JSON.stringify(validated, null, 2), "utf-8");
18344
18604
  return ok(undefined);
18345
18605
  } catch (e) {
18346
18606
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -18507,8 +18767,8 @@ __export(exports_goal_state, {
18507
18767
  markTaskBlocked: () => markTaskBlocked,
18508
18768
  loadGoal: () => loadGoal
18509
18769
  });
18510
- import { mkdir as mkdir9, readFile as readFile12, writeFile as writeFile7 } from "node:fs/promises";
18511
- import { resolve as resolve14 } from "node:path";
18770
+ import { mkdir as mkdir9, readFile as readFile13, writeFile as writeFile8 } from "node:fs/promises";
18771
+ import { resolve as resolve15 } from "node:path";
18512
18772
  function validateGoalGraph2(graph) {
18513
18773
  const ids = new Set(graph.tasks.map((t) => t.id));
18514
18774
  if (ids.size !== graph.tasks.length) {
@@ -18551,16 +18811,16 @@ async function writeGoal(projectRoot, graph) {
18551
18811
  const validation = validateGoalGraph2(graph);
18552
18812
  if (!validation.success)
18553
18813
  return validation;
18554
- const dir = resolve14(projectRoot, ".codeconductor");
18814
+ const dir = resolve15(projectRoot, ".codeconductor");
18555
18815
  await mkdir9(dir, { recursive: true });
18556
18816
  const yaml = $stringify(graph);
18557
- await writeFile7(resolve14(dir, "current-goal.yml"), yaml, "utf-8");
18817
+ await writeFile8(resolve15(dir, "current-goal.yml"), yaml, "utf-8");
18558
18818
  return ok(undefined);
18559
18819
  }
18560
18820
  async function loadGoal(projectRoot) {
18561
18821
  try {
18562
- const filePath = resolve14(projectRoot, GOAL_FILE);
18563
- const content = await readFile12(filePath, "utf-8");
18822
+ const filePath = resolve15(projectRoot, GOAL_FILE);
18823
+ const content = await readFile13(filePath, "utf-8");
18564
18824
  const data = $parse(content);
18565
18825
  const validated = GoalGraphSchema.parse(data);
18566
18826
  return validateGoalGraph2(validated);
@@ -18722,8 +18982,8 @@ var init_goal_command = __esm(() => {
18722
18982
  });
18723
18983
 
18724
18984
  // src/core/openspec/backlog-parser.ts
18725
- import { readFile as readFile13 } from "node:fs/promises";
18726
- import { resolve as resolve15 } from "node:path";
18985
+ import { readFile as readFile14 } from "node:fs/promises";
18986
+ import { resolve as resolve16 } from "node:path";
18727
18987
  function parseBool(value) {
18728
18988
  const v = value.trim().toLowerCase();
18729
18989
  return v === "yes" || v === "true";
@@ -18897,8 +19157,8 @@ function parseBacklogMarkdown(content) {
18897
19157
  }
18898
19158
  async function loadBacklog(projectRoot) {
18899
19159
  try {
18900
- const filePath = resolve15(projectRoot, BACKLOG_FILE);
18901
- const content = await readFile13(filePath, "utf-8");
19160
+ const filePath = resolve16(projectRoot, BACKLOG_FILE);
19161
+ const content = await readFile14(filePath, "utf-8");
18902
19162
  return parseBacklogMarkdown(content);
18903
19163
  } catch (e) {
18904
19164
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -18947,11 +19207,11 @@ function slugify(text) {
18947
19207
  }
18948
19208
 
18949
19209
  // src/core/knowledge/source-parsers.ts
18950
- import { readFile as readFile14 } from "node:fs/promises";
19210
+ import { readFile as readFile15 } from "node:fs/promises";
18951
19211
  import { join as join13 } from "node:path";
18952
19212
  async function parseAdrFile(filePath) {
18953
19213
  try {
18954
- const content = await readFile14(filePath, "utf-8");
19214
+ const content = await readFile15(filePath, "utf-8");
18955
19215
  const basename3 = filePath.split("/").pop() ?? "";
18956
19216
  const idMatch = basename3.match(/adr-(\d+)/i);
18957
19217
  const id = idMatch ? `adr-${idMatch[1]}` : slugify(basename3);
@@ -19000,7 +19260,7 @@ function adrToEntities(adr, source) {
19000
19260
  }
19001
19261
  async function parseReadmeEntities(projectRoot, productName) {
19002
19262
  try {
19003
- const content = await readFile14(join13(projectRoot, "README.md"), "utf-8");
19263
+ const content = await readFile15(join13(projectRoot, "README.md"), "utf-8");
19004
19264
  const firstPara = content.split(`
19005
19265
 
19006
19266
  `).find((p) => p.trim() && !p.startsWith("#") && !p.startsWith(">")) ?? "";
@@ -19021,7 +19281,7 @@ async function parseReadmeEntities(projectRoot, productName) {
19021
19281
  }
19022
19282
  async function parseGraphifyEntities(projectRoot) {
19023
19283
  try {
19024
- const raw = await readFile14(join13(projectRoot, "graphify-out", "graph.json"), "utf-8");
19284
+ const raw = await readFile15(join13(projectRoot, "graphify-out", "graph.json"), "utf-8");
19025
19285
  const graph = JSON.parse(raw);
19026
19286
  const entities = [];
19027
19287
  for (const node2 of graph.nodes ?? []) {
@@ -19077,8 +19337,8 @@ async function scanSrcComponents(projectRoot) {
19077
19337
  var init_source_parsers = () => {};
19078
19338
 
19079
19339
  // src/core/memory/episodic-store.ts
19080
- import { appendFile, mkdir as mkdir10, readFile as readFile15 } from "node:fs/promises";
19081
- import { resolve as resolve16 } from "node:path";
19340
+ import { appendFile, mkdir as mkdir10, readFile as readFile16 } from "node:fs/promises";
19341
+ import { resolve as resolve17 } from "node:path";
19082
19342
  async function appendEvent(projectRoot, event) {
19083
19343
  try {
19084
19344
  const full = ProductEventSchema.parse({
@@ -19086,7 +19346,7 @@ async function appendEvent(projectRoot, event) {
19086
19346
  id: event.id ?? `evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
19087
19347
  });
19088
19348
  const path = eventsPath(projectRoot);
19089
- await mkdir10(resolve16(projectRoot, ".codeconductor"), { recursive: true });
19349
+ await mkdir10(resolve17(projectRoot, ".codeconductor"), { recursive: true });
19090
19350
  await appendFile(path, JSON.stringify(full) + `
19091
19351
  `, "utf-8");
19092
19352
  return ok(full);
@@ -19096,7 +19356,7 @@ async function appendEvent(projectRoot, event) {
19096
19356
  }
19097
19357
  async function listEvents(projectRoot, since, type) {
19098
19358
  try {
19099
- const content = await readFile15(eventsPath(projectRoot), "utf-8");
19359
+ const content = await readFile16(eventsPath(projectRoot), "utf-8");
19100
19360
  const events = [];
19101
19361
  for (const line of content.split(`
19102
19362
  `)) {
@@ -19124,9 +19384,9 @@ var init_episodic_store = __esm(() => {
19124
19384
 
19125
19385
  // src/core/knowledge/ingest-pipeline.ts
19126
19386
  import { createHash as createHash2 } from "node:crypto";
19127
- import { readFile as readFile16 } from "node:fs/promises";
19387
+ import { readFile as readFile17 } from "node:fs/promises";
19128
19388
  import { join as join14 } from "node:path";
19129
- import { mkdir as mkdir11, writeFile as writeFile8 } from "node:fs/promises";
19389
+ import { mkdir as mkdir11, writeFile as writeFile9 } from "node:fs/promises";
19130
19390
  async function mapLimit(items, limit, fn) {
19131
19391
  if (items.length === 0)
19132
19392
  return [];
@@ -19146,7 +19406,7 @@ async function mapLimit(items, limit, fn) {
19146
19406
  }
19147
19407
  async function hashFile(path) {
19148
19408
  try {
19149
- const content = await readFile16(path, "utf-8");
19409
+ const content = await readFile17(path, "utf-8");
19150
19410
  return createHash2("sha256").update(content).digest("hex").slice(0, 16);
19151
19411
  } catch {
19152
19412
  return null;
@@ -19173,7 +19433,7 @@ async function collectDeferRisks(projectRoot) {
19173
19433
  await walk(join14(projectRoot, "src"));
19174
19434
  const perFile = await mapLimit(files, WALK_READ_CONCURRENCY, async (full) => {
19175
19435
  const entities = [];
19176
- const content = await readFile16(full, "utf-8");
19436
+ const content = await readFile17(full, "utf-8");
19177
19437
  const rel = full.replace(projectRoot + "/", "");
19178
19438
  const deferRe = new RegExp(DEFER_REGEX_SOURCE, "gm");
19179
19439
  let match;
@@ -19341,7 +19601,7 @@ async function runIngest(projectRoot, productName) {
19341
19601
  sourceHashes
19342
19602
  };
19343
19603
  await mkdir11(join14(projectRoot, ".codeconductor"), { recursive: true });
19344
- await writeFile8(productMetaPath(projectRoot), JSON.stringify(ProductMetaSchema.parse(meta), null, 2), "utf-8");
19604
+ await writeFile9(productMetaPath(projectRoot), JSON.stringify(ProductMetaSchema.parse(meta), null, 2), "utf-8");
19345
19605
  await appendEvent(projectRoot, {
19346
19606
  type: "ingest.completed",
19347
19607
  timestamp: new Date().toISOString(),
@@ -19422,11 +19682,11 @@ var init_ingest_command = __esm(() => {
19422
19682
  });
19423
19683
 
19424
19684
  // src/core/memory/operational-state.ts
19425
- import { mkdir as mkdir12, readFile as readFile17, writeFile as writeFile9 } from "node:fs/promises";
19426
- import { resolve as resolve17 } from "node:path";
19685
+ import { mkdir as mkdir12, readFile as readFile18, writeFile as writeFile10 } from "node:fs/promises";
19686
+ import { resolve as resolve18 } from "node:path";
19427
19687
  async function loadOperationalState(projectRoot) {
19428
19688
  try {
19429
- const raw = await readFile17(operationalStatePath(projectRoot), "utf-8");
19689
+ const raw = await readFile18(operationalStatePath(projectRoot), "utf-8");
19430
19690
  return ok(OperationalStateSchema.parse(JSON.parse(raw)));
19431
19691
  } catch (e) {
19432
19692
  if (e.code === "ENOENT") {
@@ -19448,8 +19708,8 @@ async function saveOperationalState(projectRoot, state) {
19448
19708
  updatedAt: new Date().toISOString()
19449
19709
  });
19450
19710
  const path = operationalStatePath(projectRoot);
19451
- await mkdir12(resolve17(projectRoot, ".codeconductor"), { recursive: true });
19452
- await writeFile9(path, JSON.stringify(validated, null, 2), "utf-8");
19711
+ await mkdir12(resolve18(projectRoot, ".codeconductor"), { recursive: true });
19712
+ await writeFile10(path, JSON.stringify(validated, null, 2), "utf-8");
19453
19713
  return ok(undefined);
19454
19714
  } catch (e) {
19455
19715
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -19483,11 +19743,11 @@ var init_operational_state = __esm(() => {
19483
19743
  });
19484
19744
 
19485
19745
  // src/core/memory/strategic-memory.ts
19486
- import { mkdir as mkdir13, readFile as readFile18, writeFile as writeFile10 } from "node:fs/promises";
19487
- import { resolve as resolve18 } from "node:path";
19746
+ import { mkdir as mkdir13, readFile as readFile19, writeFile as writeFile11 } from "node:fs/promises";
19747
+ import { resolve as resolve19 } from "node:path";
19488
19748
  async function loadStrategicMemory(projectRoot) {
19489
19749
  try {
19490
- const raw = await readFile18(strategicPath(projectRoot), "utf-8");
19750
+ const raw = await readFile19(strategicPath(projectRoot), "utf-8");
19491
19751
  return ok(StrategicMemorySchema.parse(JSON.parse(raw)));
19492
19752
  } catch (e) {
19493
19753
  if (e.code === "ENOENT") {
@@ -19508,8 +19768,8 @@ async function saveStrategicMemory(projectRoot, memory) {
19508
19768
  updatedAt: new Date().toISOString()
19509
19769
  });
19510
19770
  const path = strategicPath(projectRoot);
19511
- await mkdir13(resolve18(projectRoot, ".codeconductor"), { recursive: true });
19512
- await writeFile10(path, JSON.stringify(validated, null, 2), "utf-8");
19771
+ await mkdir13(resolve19(projectRoot, ".codeconductor"), { recursive: true });
19772
+ await writeFile11(path, JSON.stringify(validated, null, 2), "utf-8");
19513
19773
  return ok(undefined);
19514
19774
  } catch (e) {
19515
19775
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -19521,8 +19781,8 @@ var init_strategic_memory = __esm(() => {
19521
19781
  });
19522
19782
 
19523
19783
  // src/core/memory/memory-index.ts
19524
- import { mkdir as mkdir14, readFile as readFile19, writeFile as writeFile11 } from "node:fs/promises";
19525
- import { resolve as resolve19 } from "node:path";
19784
+ import { mkdir as mkdir14, readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
19785
+ import { resolve as resolve20 } from "node:path";
19526
19786
  function parseMarkdown(content) {
19527
19787
  const startIdx = content.indexOf(SENTINEL_START);
19528
19788
  const endIdx = content.indexOf(SENTINEL_END);
@@ -19541,8 +19801,8 @@ function parseMarkdown(content) {
19541
19801
  }
19542
19802
  async function loadMemoryIndex(projectRoot) {
19543
19803
  try {
19544
- const filePath = resolve19(projectRoot, MEMORY_FILE);
19545
- const content = await readFile19(filePath, "utf-8");
19804
+ const filePath = resolve20(projectRoot, MEMORY_FILE);
19805
+ const content = await readFile20(filePath, "utf-8");
19546
19806
  const parsed = parseMarkdown(content);
19547
19807
  if (!parsed) {
19548
19808
  return err(new Error("Failed to parse memory index from memory.md"));
@@ -19559,28 +19819,28 @@ var init_memory_index = __esm(() => {
19559
19819
  });
19560
19820
 
19561
19821
  // src/core/evaluation/outcome-store.ts
19562
- import { appendFile as appendFile2, mkdir as mkdir15, readFile as readFile20, writeFile as writeFile12 } from "node:fs/promises";
19563
- import { resolve as resolve20 } from "node:path";
19822
+ import { appendFile as appendFile2, mkdir as mkdir15, readFile as readFile21, writeFile as writeFile13 } from "node:fs/promises";
19823
+ import { resolve as resolve21 } from "node:path";
19564
19824
  async function ensureEvaluationDir(projectRoot) {
19565
- const dir = resolve20(projectRoot, EVAL_DIR);
19825
+ const dir = resolve21(projectRoot, EVAL_DIR);
19566
19826
  await mkdir15(dir, { recursive: true });
19567
- await mkdir15(resolve20(dir, SCORECARDS_DIR), { recursive: true });
19827
+ await mkdir15(resolve21(dir, SCORECARDS_DIR), { recursive: true });
19568
19828
  return dir;
19569
19829
  }
19570
19830
  async function appendOutcome(projectRoot, outcome) {
19571
19831
  try {
19572
19832
  const dir = await ensureEvaluationDir(projectRoot);
19573
19833
  const validated = TaskOutcomeSchema.parse(outcome);
19574
- await appendFile2(resolve20(dir, OUTCOMES_FILE), JSON.stringify(validated) + `
19834
+ await appendFile2(resolve21(dir, OUTCOMES_FILE), JSON.stringify(validated) + `
19575
19835
  `, "utf-8");
19576
- const indexPath = resolve20(dir, INDEX_FILE);
19836
+ const indexPath = resolve21(dir, INDEX_FILE);
19577
19837
  let index = { version: 1, lastOutcomeId: validated.id };
19578
19838
  try {
19579
- const raw = await readFile20(indexPath, "utf-8");
19839
+ const raw = await readFile21(indexPath, "utf-8");
19580
19840
  index = EvaluationIndexSchema.parse(JSON.parse(raw));
19581
19841
  index.lastOutcomeId = validated.id;
19582
19842
  } catch {}
19583
- await writeFile12(indexPath, JSON.stringify(index, null, 2), "utf-8");
19843
+ await writeFile13(indexPath, JSON.stringify(index, null, 2), "utf-8");
19584
19844
  return ok(undefined);
19585
19845
  } catch (e) {
19586
19846
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -19588,8 +19848,8 @@ async function appendOutcome(projectRoot, outcome) {
19588
19848
  }
19589
19849
  async function listOutcomes(projectRoot, filter = {}) {
19590
19850
  try {
19591
- const path = resolve20(projectRoot, EVAL_DIR, OUTCOMES_FILE);
19592
- const content = await readFile20(path, "utf-8");
19851
+ const path = resolve21(projectRoot, EVAL_DIR, OUTCOMES_FILE);
19852
+ const content = await readFile21(path, "utf-8");
19593
19853
  const lines = content.split(`
19594
19854
  `).filter((l) => l.trim().length > 0);
19595
19855
  const outcomes = [];
@@ -19607,6 +19867,12 @@ async function listOutcomes(projectRoot, filter = {}) {
19607
19867
  continue;
19608
19868
  if (filter.since && parsed.timestamp < filter.since)
19609
19869
  continue;
19870
+ if (filter.experimentId && parsed.experimentId !== filter.experimentId)
19871
+ continue;
19872
+ if (filter.variantId && parsed.variantId !== filter.variantId)
19873
+ continue;
19874
+ if (filter.suiteTaskId && parsed.suiteTaskId !== filter.suiteTaskId)
19875
+ continue;
19610
19876
  outcomes.push(parsed);
19611
19877
  }
19612
19878
  return ok(outcomes);
@@ -19621,8 +19887,8 @@ async function saveScorecard(projectRoot, record) {
19621
19887
  try {
19622
19888
  const dir = await ensureEvaluationDir(projectRoot);
19623
19889
  const validated = ScorecardRecordSchema.parse(record);
19624
- const filePath = resolve20(dir, SCORECARDS_DIR, `${validated.id}.json`);
19625
- await writeFile12(filePath, JSON.stringify(validated, null, 2), "utf-8");
19890
+ const filePath = resolve21(dir, SCORECARDS_DIR, `${validated.id}.json`);
19891
+ await writeFile13(filePath, JSON.stringify(validated, null, 2), "utf-8");
19626
19892
  return ok(filePath);
19627
19893
  } catch (e) {
19628
19894
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -19630,8 +19896,8 @@ async function saveScorecard(projectRoot, record) {
19630
19896
  }
19631
19897
  async function loadScorecard(projectRoot, id) {
19632
19898
  try {
19633
- const filePath = resolve20(projectRoot, EVAL_DIR, SCORECARDS_DIR, `${id}.json`);
19634
- const content = await readFile20(filePath, "utf-8");
19899
+ const filePath = resolve21(projectRoot, EVAL_DIR, SCORECARDS_DIR, `${id}.json`);
19900
+ const content = await readFile21(filePath, "utf-8");
19635
19901
  return ok(ScorecardRecordSchema.parse(JSON.parse(content)));
19636
19902
  } catch (e) {
19637
19903
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -19643,10 +19909,12 @@ function aggregateOutcomes(outcomes) {
19643
19909
  const avgWeightedScore = withScore.length > 0 ? withScore.reduce((s, o) => s + (o.weightedScore ?? 0), 0) / withScore.length : 0;
19644
19910
  const byAgent = {};
19645
19911
  const byModel = {};
19912
+ const byVariant = {};
19646
19913
  const agentScoreCounts = {};
19647
19914
  const modelScoreCounts = {};
19648
19915
  const modelCostCounts = {};
19649
19916
  const modelTokenCounts = {};
19917
+ const variantScoreCounts = {};
19650
19918
  for (const o of outcomes) {
19651
19919
  if (!byAgent[o.agent])
19652
19920
  byAgent[o.agent] = { count: 0, avgScore: 0 };
@@ -19671,6 +19939,15 @@ function aggregateOutcomes(outcomes) {
19671
19939
  byModel[o.model].avgTokens = (byModel[o.model].avgTokens ?? 0) + tokens;
19672
19940
  modelTokenCounts[o.model] = (modelTokenCounts[o.model] ?? 0) + 1;
19673
19941
  }
19942
+ if (o.variantId) {
19943
+ if (!byVariant[o.variantId])
19944
+ byVariant[o.variantId] = { count: 0, avgScore: 0 };
19945
+ byVariant[o.variantId].count++;
19946
+ if (o.weightedScore !== undefined) {
19947
+ byVariant[o.variantId].avgScore += o.weightedScore;
19948
+ variantScoreCounts[o.variantId] = (variantScoreCounts[o.variantId] ?? 0) + 1;
19949
+ }
19950
+ }
19674
19951
  }
19675
19952
  for (const agent of Object.keys(byAgent)) {
19676
19953
  const entry = byAgent[agent];
@@ -19686,12 +19963,18 @@ function aggregateOutcomes(outcomes) {
19686
19963
  if (entry.avgTokens !== undefined)
19687
19964
  entry.avgTokens /= modelTokenCounts[model];
19688
19965
  }
19966
+ for (const variant of Object.keys(byVariant)) {
19967
+ const entry = byVariant[variant];
19968
+ const scoreCount = variantScoreCounts[variant] ?? 0;
19969
+ entry.avgScore = scoreCount > 0 ? entry.avgScore / scoreCount : 0;
19970
+ }
19689
19971
  return {
19690
19972
  total: outcomes.length,
19691
19973
  passRate: outcomes.length > 0 ? passed.length / outcomes.length : 0,
19692
19974
  avgWeightedScore: Math.round(avgWeightedScore * 1000) / 1000,
19693
19975
  byAgent,
19694
- byModel
19976
+ byModel,
19977
+ byVariant
19695
19978
  };
19696
19979
  }
19697
19980
  function generateEvalId(prefix) {
@@ -19705,11 +19988,11 @@ var init_outcome_store = __esm(() => {
19705
19988
  });
19706
19989
 
19707
19990
  // src/core/feedback/feedback-ingestor.ts
19708
- import { readFile as readFile21 } from "node:fs/promises";
19991
+ import { readFile as readFile22 } from "node:fs/promises";
19709
19992
  import { join as join15 } from "node:path";
19710
19993
  async function parseChangelogLearnings(projectRoot) {
19711
19994
  try {
19712
- const content = await readFile21(join15(projectRoot, "CHANGELOG.md"), "utf-8");
19995
+ const content = await readFile22(join15(projectRoot, "CHANGELOG.md"), "utf-8");
19713
19996
  const unreleased = content.match(/## \[Unreleased\]([\s\S]*?)(?=## \[|\n## )/i);
19714
19997
  if (!unreleased)
19715
19998
  return [];
@@ -19743,6 +20026,15 @@ async function generateInsights(projectRoot) {
19743
20026
  });
19744
20027
  }
19745
20028
  }
20029
+ const tagged = outcomes.data.filter((o) => o.experimentId && o.variantId);
20030
+ if (tagged.length > 0) {
20031
+ const experiments = new Set(tagged.map((o) => o.experimentId));
20032
+ insights.push({
20033
+ type: "harness_ablation",
20034
+ message: `${experiments.size} harness experiment(s), ${tagged.length} tagged outcomes`,
20035
+ confidence: "medium"
20036
+ });
20037
+ }
19746
20038
  }
19747
20039
  const learnings = await parseChangelogLearnings(projectRoot);
19748
20040
  for (const learning of learnings.slice(0, 5)) {
@@ -19820,7 +20112,7 @@ var exports_product_command = {};
19820
20112
  __export(exports_product_command, {
19821
20113
  productCommand: () => productCommand
19822
20114
  });
19823
- import { writeFile as writeFile13 } from "node:fs/promises";
20115
+ import { writeFile as writeFile14 } from "node:fs/promises";
19824
20116
  async function productCommand(options) {
19825
20117
  const { subcommand, projectRoot, output, query, from, to, since, format } = options;
19826
20118
  try {
@@ -20023,7 +20315,7 @@ async function handleExport(projectRoot, format, output) {
20023
20315
  ...queryNodes(graph.data, "risk").map((n) => `- ${n.name}`)
20024
20316
  ].join(`
20025
20317
  `);
20026
- await writeFile13(productReportPath(projectRoot), md, "utf-8");
20318
+ await writeFile14(productReportPath(projectRoot), md, "utf-8");
20027
20319
  return {
20028
20320
  code: 0,
20029
20321
  data: {
@@ -20226,8 +20518,8 @@ async function runCompileCheck(options) {
20226
20518
  })();
20227
20519
  collectOutput.catch(() => {});
20228
20520
  let timeoutId;
20229
- const timeout = new Promise((resolve21) => {
20230
- timeoutId = setTimeout(() => resolve21("timeout"), timeoutMs);
20521
+ const timeout = new Promise((resolve22) => {
20522
+ timeoutId = setTimeout(() => resolve22("timeout"), timeoutMs);
20231
20523
  });
20232
20524
  try {
20233
20525
  const outcome = await Promise.race([collectOutput, timeout]);
@@ -20297,9 +20589,9 @@ var init_compile_checker = __esm(() => {
20297
20589
 
20298
20590
  // src/core/verification/verification-runner.ts
20299
20591
  import { existsSync as existsSync3 } from "node:fs";
20300
- import { readFile as readFile22 } from "node:fs/promises";
20301
- import { dirname as dirname5, join as join16, resolve as resolve21 } from "node:path";
20302
- import { mkdir as mkdir16, readdir as readdir5, writeFile as writeFile14 } from "node:fs/promises";
20592
+ import { readFile as readFile23 } from "node:fs/promises";
20593
+ 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";
20303
20595
  function isPassingEvidence(ev) {
20304
20596
  switch (ev.type) {
20305
20597
  case "verification":
@@ -20317,8 +20609,8 @@ function isPassingEvidence(ev) {
20317
20609
  }
20318
20610
  }
20319
20611
  function evidenceFilePath(evDir, id) {
20320
- const path = resolve21(evDir, `${id.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
20321
- if (dirname5(path) !== resolve21(evDir)) {
20612
+ const path = resolve22(evDir, `${id.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
20613
+ if (dirname6(path) !== resolve22(evDir)) {
20322
20614
  return err(new Error(`Refusing to write evidence for "${id}" outside ${evDir}`));
20323
20615
  }
20324
20616
  return ok(path);
@@ -20330,7 +20622,7 @@ async function persistEvidence(projectRoot, evidence) {
20330
20622
  return evidencePath;
20331
20623
  try {
20332
20624
  await mkdir16(evDir, { recursive: true });
20333
- await writeFile14(evidencePath.data, JSON.stringify(evidence, null, 2), "utf-8");
20625
+ await writeFile15(evidencePath.data, JSON.stringify(evidence, null, 2), "utf-8");
20334
20626
  } catch (e) {
20335
20627
  return err(e instanceof Error ? e : new Error(String(e)));
20336
20628
  }
@@ -20352,7 +20644,7 @@ async function collectTaskEvidence(projectRoot, taskId) {
20352
20644
  if (!file.endsWith(".json"))
20353
20645
  continue;
20354
20646
  try {
20355
- const raw = await readFile22(join16(evDir, file), "utf-8");
20647
+ const raw = await readFile23(join16(evDir, file), "utf-8");
20356
20648
  const ev = EvidenceSchema.parse(JSON.parse(raw));
20357
20649
  if (ev.relatedTask === taskId)
20358
20650
  records.push(ev);
@@ -20556,7 +20848,8 @@ function goalTaskToCanonicalCard(task, objective) {
20556
20848
  evidenceRequired: task.evidenceRequired ?? ["acceptance_criteria_met"],
20557
20849
  status: task.status === "in-progress" ? "in-progress" : task.status === "done" ? "done" : task.status === "blocked" ? "blocked" : "ready",
20558
20850
  type: task.type,
20559
- linkedCapabilities: []
20851
+ linkedCapabilities: [],
20852
+ boundaries: []
20560
20853
  };
20561
20854
  }
20562
20855
  function buildTaskEnvelope(card, projectRoot, projectName) {
@@ -20987,6 +21280,8 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
20987
21280
  let state = createInitialState({ maxIterations, maxTokenBudget });
20988
21281
  let totalErrors = 0;
20989
21282
  let feedbackText;
21283
+ let compileCheck;
21284
+ let compileCheckSkipReason;
20990
21285
  logTransition(state.phase, "INIT", state.iteration);
20991
21286
  const startResult = loopStateMachine(state, { type: "START" });
20992
21287
  state = startResult.state;
@@ -21022,6 +21317,12 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
21022
21317
  logTransition(state.phase, "COMPILE_CHECK", state.iteration);
21023
21318
  const compileResult = await compileCheckFn();
21024
21319
  totalErrors += compileResult.errors.length;
21320
+ if (compileResult.skipped) {
21321
+ compileCheck = "skipped";
21322
+ compileCheckSkipReason = compileResult.skipReason;
21323
+ } else {
21324
+ compileCheck = "ran";
21325
+ }
21025
21326
  const checkResult = loopStateMachine(state, {
21026
21327
  type: "COMPILE_CHECK_COMPLETED",
21027
21328
  errors: compileResult.errors
@@ -21045,7 +21346,9 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
21045
21346
  iterations: state.iteration,
21046
21347
  totalErrors,
21047
21348
  finalPhase: "DONE",
21048
- errorHistory: state.errorHistory
21349
+ errorHistory: state.errorHistory,
21350
+ compileCheck,
21351
+ compileCheckSkipReason
21049
21352
  };
21050
21353
  }
21051
21354
  case "ESCALATED": {
@@ -21057,7 +21360,9 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
21057
21360
  totalErrors,
21058
21361
  finalPhase: "ESCALATED",
21059
21362
  escalationReport: report,
21060
- errorHistory: state.errorHistory
21363
+ errorHistory: state.errorHistory,
21364
+ compileCheck,
21365
+ compileCheckSkipReason
21061
21366
  };
21062
21367
  }
21063
21368
  case "FAILED": {
@@ -21067,7 +21372,9 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
21067
21372
  iterations: state.iteration,
21068
21373
  totalErrors,
21069
21374
  finalPhase: "FAILED",
21070
- errorHistory: state.errorHistory
21375
+ errorHistory: state.errorHistory,
21376
+ compileCheck,
21377
+ compileCheckSkipReason
21071
21378
  };
21072
21379
  }
21073
21380
  default: {
@@ -21077,7 +21384,9 @@ async function runLoop(generateFn, compileCheckFn, config = {}, taskTitle = "com
21077
21384
  iterations: state.iteration,
21078
21385
  totalErrors,
21079
21386
  finalPhase: state.phase,
21080
- errorHistory: state.errorHistory
21387
+ errorHistory: state.errorHistory,
21388
+ compileCheck,
21389
+ compileCheckSkipReason
21081
21390
  };
21082
21391
  }
21083
21392
  }
@@ -21134,6 +21443,15 @@ function buildEscalationReport(taskTitle, state, originalTask) {
21134
21443
  recommendedAction
21135
21444
  };
21136
21445
  }
21446
+ function skippedCompileResult(reason) {
21447
+ return {
21448
+ ...CLEAN_COMPILE,
21449
+ skipped: true,
21450
+ skipReason: reason,
21451
+ stderr: `compileCheck: skipped
21452
+ ${reason}`
21453
+ };
21454
+ }
21137
21455
  function shouldRunAgentLoop(type, agentType, phase) {
21138
21456
  if (phase === "implement" || phase === "test")
21139
21457
  return true;
@@ -21146,11 +21464,13 @@ async function runLoopForProject(projectRoot, options) {
21146
21464
  const loopCfg = configResult.success ? configResult.data.loop : undefined;
21147
21465
  const compileCheck = configResult.success ? configResult.data.safety.compileCheck : undefined;
21148
21466
  const compileCheckFn = async () => {
21149
- if (!compileCheck?.enabled || !compileCheck.command)
21150
- return CLEAN_COMPILE;
21467
+ if (!compileCheck?.enabled || !compileCheck.command) {
21468
+ return skippedCompileResult(!compileCheck?.enabled ? "Compile check is not enabled in config." : "Compile check is enabled but no command is configured.");
21469
+ }
21151
21470
  const allowed = options.allowCompileCheck === true || isAllowlistedCompileCommand(compileCheck.command);
21152
- if (!allowed)
21153
- return CLEAN_COMPILE;
21471
+ if (!allowed) {
21472
+ return skippedCompileResult("Compile check not executed: the command is not on the compile allowlist. " + "Re-run with --allow-compile-check to trust it.");
21473
+ }
21154
21474
  const result = await runCompileCheck({
21155
21475
  command: compileCheck.command,
21156
21476
  cwd: projectRoot,
@@ -21737,10 +22057,15 @@ var init_prompt_compiler = __esm(() => {
21737
22057
  "needsConfirmation": true
21738
22058
  }`,
21739
22059
  "council-verdict": `{
21740
- "status": "APPROVED" | "BLOCKED",
21741
- "confidence": 0.0,
22060
+ "status": "APPROVED" | "REJECTED" | "ESCALATED",
22061
+ "totalAgents": 0,
22062
+ "approvedCount": 0,
22063
+ "rejectedCount": 0,
22064
+ "abstainedCount": 0,
22065
+ "vetoApplied": false,
21742
22066
  "findings": [],
21743
- "next_actions": []
22067
+ "summary": "",
22068
+ "individualVerdicts": []
21744
22069
  }`,
21745
22070
  "technical-plan": '{ "approach": "", "filesAffected": [], "risks": [] }',
21746
22071
  "fix-intake-output": '{ "actualBehavior": "", "expectedBehavior": "", "reproductionSteps": [] }',
@@ -21784,7 +22109,14 @@ function resolveOutputSchemaName(schemaName, role) {
21784
22109
  }
21785
22110
  function validateAgentOutputBySchema(schemaName, data, role) {
21786
22111
  const resolved = resolveOutputSchemaName(schemaName, role);
21787
- const schema = SCHEMA_REGISTRY[resolved] ?? AgentOutputSchema;
22112
+ const schema = SCHEMA_REGISTRY[resolved];
22113
+ if (!schema) {
22114
+ return {
22115
+ valid: false,
22116
+ schema: resolved,
22117
+ errors: [`Unknown output schema: ${resolved}`]
22118
+ };
22119
+ }
21788
22120
  const parsed = schema.safeParse(data);
21789
22121
  if (parsed.success) {
21790
22122
  return { valid: true, schema: resolved, data: parsed.data };
@@ -21843,8 +22175,14 @@ var init_output_validator = __esm(() => {
21843
22175
  "review-report": ReviewerOutputSchema,
21844
22176
  "technical-plan": TechnicalPlanOutputSchema,
21845
22177
  "fix-intake-output": AgentOutputSchema,
21846
- "council-verdict": AgentOutputSchema,
21847
- "agent-output": AgentOutputSchema
22178
+ "council-verdict": CouncilVerdictSchema,
22179
+ "agent-output": AgentOutputSchema,
22180
+ taskcard: CanonicalTaskCardSchema,
22181
+ "test-plan": AgentOutputSchema,
22182
+ "complexity-audit": AgentOutputSchema,
22183
+ "api-contract": AgentOutputSchema,
22184
+ "pagespeed-report": AgentOutputSchema,
22185
+ "scorecard-record": ScorecardRecordSchema
21848
22186
  };
21849
22187
  });
21850
22188
 
@@ -21917,6 +22255,7 @@ var init_command_parser = __esm(() => {
21917
22255
  "db-migration": { outputFormat: "plan", needConfirmation: true, riskThreshold: "high" },
21918
22256
  pagespeed: { outputFormat: "verdict", needConfirmation: false, riskThreshold: "low" },
21919
22257
  openspec: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
22258
+ backlog: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
21920
22259
  scorecard: { outputFormat: "verdict", needConfirmation: false, riskThreshold: "low" },
21921
22260
  council: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
21922
22261
  iterative: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
@@ -21924,96 +22263,8 @@ var init_command_parser = __esm(() => {
21924
22263
  triage: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
21925
22264
  prototype: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "medium" },
21926
22265
  handoff: { outputFormat: "plan", needConfirmation: false, riskThreshold: "low" },
21927
- clarify: { outputFormat: "plan", needConfirmation: false, riskThreshold: "low" }
21928
- };
21929
- });
21930
-
21931
- // src/core/ccep/context-resolver.ts
21932
- import { existsSync as existsSync5 } from "node:fs";
21933
- import { readFile as readFile23 } from "node:fs/promises";
21934
- import { join as join18 } from "node:path";
21935
- async function loadProductKnowledge(projectRoot) {
21936
- const graphPath = productGraphPath(projectRoot);
21937
- if (!existsSync5(graphPath)) {
21938
- return {};
21939
- }
21940
- try {
21941
- const raw = await readFile23(graphPath, "utf-8");
21942
- const graph = ProductGraphSchema.parse(JSON.parse(raw));
21943
- return {
21944
- productName: graph.productName,
21945
- domains: queryNodes(graph, "domain").map((n) => n.name),
21946
- decisions: queryNodes(graph, "decision").slice(0, 10).map((n) => ({ id: n.id, name: n.name, data: n.data })),
21947
- risks: queryNodes(graph, "risk").slice(0, 10).map((n) => ({ id: n.id, name: n.name })),
21948
- requirements: queryNodes(graph, "requirement").slice(0, 10).map((n) => ({ id: n.id, name: n.name, status: n.data.status })),
21949
- nodeCount: graph.nodes.length
21950
- };
21951
- } catch {
21952
- return {};
21953
- }
21954
- }
21955
- async function resolveAstSource(projectRoot) {
21956
- const graphPath = productGraphPath(projectRoot);
21957
- if (existsSync5(graphPath)) {
21958
- return { source: "product-graph", confidence: "high" };
21959
- }
21960
- const graphifyPath = join18(projectRoot, "graphify-out", "graph.json");
21961
- if (existsSync5(graphifyPath)) {
21962
- return { source: "graphify", confidence: "medium" };
21963
- }
21964
- try {
21965
- const profile = await detectProject(projectRoot);
21966
- if (profile.signals.length > 0) {
21967
- return { source: "detect", confidence: profile.confidence };
21968
- }
21969
- } catch {}
21970
- return { source: "manual", confidence: "low" };
21971
- }
21972
- async function resolveContext(envelope, profile, projectRoot) {
21973
- const ast = await resolveAstSource(projectRoot);
21974
- const knowledge = await loadProductKnowledge(projectRoot);
21975
- const firstPhase = profile.phases[0];
21976
- let stack = envelope.repoContext.stack;
21977
- if (stack.length === 0 || stack.length === 1 && stack[0] === "unknown") {
21978
- try {
21979
- const detected = await detectProject(projectRoot);
21980
- stack = [...new Set([...detected.languages, ...detected.frameworks])];
21981
- } catch {
21982
- stack = envelope.repoContext.stack;
21983
- }
21984
- }
21985
- const context = {
21986
- envelope,
21987
- profile,
21988
- intent: {
21989
- type: envelope.command,
21990
- goal: envelope.userRequest,
21991
- domain: envelope.repoContext.domain
21992
- },
21993
- project: {
21994
- name: envelope.projectId,
21995
- rootDir: projectRoot,
21996
- stack
21997
- },
21998
- knowledge,
21999
- ast,
22000
- policies: DEFAULT_POLICIES,
22001
- outputSchema: firstPhase?.outputSchema ?? profile.intakeSchema ?? "agent-output"
22002
- };
22003
- return validateExecutionContext(context);
22004
- }
22005
- var DEFAULT_POLICIES;
22006
- var init_context_resolver = __esm(() => {
22007
- init_project_detector();
22008
- init_schemas();
22009
- init_command_parser();
22010
- init_paths();
22011
- init_graph_store();
22012
- DEFAULT_POLICIES = {
22013
- architecture: "modular",
22014
- testing: "required",
22015
- documentation: "required",
22016
- breakingChanges: "approval"
22266
+ clarify: { outputFormat: "plan", needConfirmation: false, riskThreshold: "low" },
22267
+ security: { outputFormat: "taskcard", needConfirmation: true, riskThreshold: "high" }
22017
22268
  };
22018
22269
  });
22019
22270
 
@@ -22039,25 +22290,821 @@ var init_workflow_profile_loader = __esm(() => {
22039
22290
  init_profiles();
22040
22291
  });
22041
22292
 
22042
- // src/commands/ccep.command.ts
22043
- var exports_ccep_command = {};
22044
- __export(exports_ccep_command, {
22045
- ccepCommand: () => ccepCommand
22046
- });
22047
- function parseWorkflowCommand(command) {
22048
- if (!command) {
22049
- return { ok: false, error: "Missing required --command flag" };
22293
+ // src/core/evaluation/harness-catalog.ts
22294
+ import { createHash as createHash3 } from "node:crypto";
22295
+ 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";
22297
+ import { dirname as dirname7, join as join18, resolve as resolve23 } from "node:path";
22298
+ function catalogIds(catalog = DEFAULT_HARNESS_CATALOG) {
22299
+ return catalog.components.map((c) => c.id);
22300
+ }
22301
+ function isHarnessComponentId(value) {
22302
+ return catalogIds().includes(value);
22303
+ }
22304
+ function parseComponentsFlag(raw, catalog = DEFAULT_HARNESS_CATALOG) {
22305
+ if (!raw || raw.trim() === "" || raw.trim() === "all") {
22306
+ return ok(catalogIds(catalog));
22050
22307
  }
22051
- const result = WorkflowCommandSchema.safeParse(command);
22052
- if (!result.success) {
22053
- return { ok: false, error: `Unknown workflow command: ${command}` };
22308
+ const ids = [];
22309
+ for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
22310
+ if (!isHarnessComponentId(part)) {
22311
+ return err(new Error(`Unknown harness component: ${part}`));
22312
+ }
22313
+ ids.push(part);
22054
22314
  }
22055
- return { ok: true, command: result.data };
22315
+ return ok(ids);
22056
22316
  }
22057
- function containedContextError(contextPath) {
22058
- return `Could not read --context file: ${contextPath}. ` + "It must be a regular file at a relative path inside the project root.";
22317
+ function variantIdFor(component) {
22318
+ return component ? `minus:${component}` : "baseline";
22059
22319
  }
22060
- async function readContainedContext(projectRoot, contextPath) {
22320
+ function parseVariantId(variantId) {
22321
+ if (!variantId || variantId === "baseline")
22322
+ return [];
22323
+ if (variantId.startsWith("minus:")) {
22324
+ const id = variantId.slice("minus:".length);
22325
+ return isHarnessComponentId(id) ? [id] : [];
22326
+ }
22327
+ return [];
22328
+ }
22329
+ function variantDirName(variantId) {
22330
+ return variantId.replaceAll(":", "-");
22331
+ }
22332
+ function componentStates(disabled, catalog = DEFAULT_HARNESS_CATALOG) {
22333
+ const off = new Set(disabled);
22334
+ const states = {};
22335
+ for (const entry of catalog.components) {
22336
+ states[entry.id] = entry.defaultOn && !off.has(entry.id);
22337
+ }
22338
+ return states;
22339
+ }
22340
+ function harnessFingerprint(disabled, contractVersion, ccepProfile = "feature", catalog = DEFAULT_HARNESS_CATALOG) {
22341
+ const states = componentStates(disabled, catalog);
22342
+ const payload = {
22343
+ components: Object.fromEntries(Object.entries(states).sort(([a], [b]) => a.localeCompare(b))),
22344
+ contractVersion,
22345
+ ccepProfile
22346
+ };
22347
+ return createHash3("sha256").update(JSON.stringify(payload)).digest("hex").slice(0, 16);
22348
+ }
22349
+ async function loadHarnessCatalog(projectRoot) {
22350
+ const overridePath = resolve23(projectRoot, EVAL_DIR2, CATALOG_FILE);
22351
+ try {
22352
+ const raw = await readFile24(overridePath, "utf-8");
22353
+ return HarnessCatalogSchema.parse($parse(raw));
22354
+ } catch {
22355
+ return DEFAULT_HARNESS_CATALOG;
22356
+ }
22357
+ }
22358
+ function activeOverlayPath(projectRoot) {
22359
+ return resolve23(projectRoot, EVAL_DIR2, ACTIVE_OVERLAY_FILE);
22360
+ }
22361
+ async function readActiveOverlay(projectRoot) {
22362
+ try {
22363
+ const raw = await readFile24(activeOverlayPath(projectRoot), "utf-8");
22364
+ return HarnessOverlaySchema.parse($parse(raw));
22365
+ } catch {
22366
+ return null;
22367
+ }
22368
+ }
22369
+ function isProductGraphDisabled(projectRoot) {
22370
+ try {
22371
+ const path = activeOverlayPath(projectRoot);
22372
+ if (!existsSync5(path))
22373
+ return false;
22374
+ const overlay = HarnessOverlaySchema.parse($parse(readFileSync4(path, "utf-8")));
22375
+ return overlay.disableProductGraph === true || overlay.disabledComponents.includes("product_graph");
22376
+ } catch {
22377
+ return false;
22378
+ }
22379
+ }
22380
+ function cloneProfile(profile) {
22381
+ return structuredClone(profile);
22382
+ }
22383
+ function dropPhase(profile, phaseId) {
22384
+ profile.routing.default = profile.routing.default.filter((id) => id !== phaseId);
22385
+ if (profile.routing.riskRules) {
22386
+ for (const rule of profile.routing.riskRules) {
22387
+ rule.then = rule.then.filter((id) => id !== phaseId);
22388
+ }
22389
+ }
22390
+ }
22391
+ function disableGates(profile) {
22392
+ profile.confirmationGate.stopOnHighRisk = false;
22393
+ profile.confirmationGate.stopOnQuestions = false;
22394
+ for (const phase of profile.phases) {
22395
+ delete phase.stopGate;
22396
+ }
22397
+ }
22398
+ function disableCouncilPhases(profile) {
22399
+ for (const phaseId of ["deliberation", "council-review"]) {
22400
+ dropPhase(profile, phaseId);
22401
+ }
22402
+ }
22403
+ function applyCatalogToProfile(profile, disabled) {
22404
+ const next = cloneProfile(profile);
22405
+ for (const id of disabled) {
22406
+ const phaseId = PHASE_BY_COMPONENT[id];
22407
+ if (phaseId)
22408
+ dropPhase(next, phaseId);
22409
+ if (id === "confirmation_gates")
22410
+ disableGates(next);
22411
+ if (id === "council")
22412
+ disableCouncilPhases(next);
22413
+ }
22414
+ return next;
22415
+ }
22416
+ async function restoreOverlayBackup(projectRoot) {
22417
+ const backupRoot = resolve23(projectRoot, EVAL_DIR2, OVERLAY_BACKUP_DIR);
22418
+ if (!existsSync5(backupRoot))
22419
+ return;
22420
+ const configBackup = join18(backupRoot, "config.yml");
22421
+ const configDest = resolve23(projectRoot, ".codeconductor", "config.yml");
22422
+ if (existsSync5(configBackup)) {
22423
+ await mkdir17(dirname7(configDest), { recursive: true });
22424
+ await copyFile2(configBackup, configDest);
22425
+ }
22426
+ const workflowsBackup = join18(backupRoot, "workflows");
22427
+ const workflowsDest = resolve23(projectRoot, ".codeconductor", "workflows");
22428
+ if (existsSync5(workflowsBackup)) {
22429
+ await mkdir17(workflowsDest, { recursive: true });
22430
+ const files = await readdir6(workflowsBackup);
22431
+ for (const file of files) {
22432
+ await copyFile2(join18(workflowsBackup, file), join18(workflowsDest, file));
22433
+ }
22434
+ }
22435
+ const overlayBackup = join18(backupRoot, ACTIVE_OVERLAY_FILE);
22436
+ const overlayDest = activeOverlayPath(projectRoot);
22437
+ if (existsSync5(overlayBackup)) {
22438
+ await copyFile2(overlayBackup, overlayDest);
22439
+ } else if (existsSync5(overlayDest)) {
22440
+ await rm2(overlayDest, { force: true });
22441
+ }
22442
+ }
22443
+ async function snapshotForBackup(projectRoot) {
22444
+ const backupRoot = resolve23(projectRoot, EVAL_DIR2, OVERLAY_BACKUP_DIR);
22445
+ if (existsSync5(backupRoot))
22446
+ return;
22447
+ await mkdir17(backupRoot, { recursive: true });
22448
+ const configPath = resolve23(projectRoot, ".codeconductor", "config.yml");
22449
+ if (existsSync5(configPath)) {
22450
+ await copyFile2(configPath, join18(backupRoot, "config.yml"));
22451
+ }
22452
+ const workflowsDir = resolve23(projectRoot, ".codeconductor", "workflows");
22453
+ if (existsSync5(workflowsDir)) {
22454
+ const dest = join18(backupRoot, "workflows");
22455
+ await mkdir17(dest, { recursive: true });
22456
+ const files = await readdir6(workflowsDir);
22457
+ for (const file of files) {
22458
+ if (file.endsWith(".yml")) {
22459
+ await copyFile2(join18(workflowsDir, file), join18(dest, file));
22460
+ }
22461
+ }
22462
+ }
22463
+ const overlay = activeOverlayPath(projectRoot);
22464
+ if (existsSync5(overlay)) {
22465
+ await copyFile2(overlay, join18(backupRoot, ACTIVE_OVERLAY_FILE));
22466
+ }
22467
+ }
22468
+ async function applyHarnessOverlay(projectRoot, disabled, options = {}) {
22469
+ try {
22470
+ if (options.backup) {
22471
+ await restoreOverlayBackup(projectRoot);
22472
+ await snapshotForBackup(projectRoot);
22473
+ }
22474
+ const workflowsDir = resolve23(projectRoot, ".codeconductor", "workflows");
22475
+ await mkdir17(workflowsDir, { recursive: true });
22476
+ await mkdir17(resolve23(projectRoot, EVAL_DIR2), { recursive: true });
22477
+ for (const command of WORKFLOW_OVERLAY_COMMANDS) {
22478
+ const base = loadWorkflowProfile(command);
22479
+ const next = applyCatalogToProfile(base, disabled);
22480
+ await writeFile16(join18(workflowsDir, `${command}.yml`), $stringify(next), "utf-8");
22481
+ }
22482
+ const needsConfig = disabled.includes("compile_loop") || disabled.includes("council");
22483
+ if (needsConfig) {
22484
+ const loaded = await loadConfig(projectRoot);
22485
+ const current = loaded.success ? loaded.data : structuredClone(DEFAULT_CONFIG);
22486
+ if (disabled.includes("compile_loop")) {
22487
+ current.loop = { ...current.loop, enabled: false };
22488
+ }
22489
+ if (disabled.includes("council")) {
22490
+ current.presets = {
22491
+ ...current.presets,
22492
+ council: { ...current.presets.council, enabled: false }
22493
+ };
22494
+ }
22495
+ const write = await writeConfig(projectRoot, current, true);
22496
+ if (!write.success) {
22497
+ return err(write.error);
22498
+ }
22499
+ }
22500
+ const overlay = HarnessOverlaySchema.parse({
22501
+ experimentId: options.experimentId,
22502
+ variantId: options.variantId ?? (disabled[0] ? variantIdFor(disabled[0]) : "baseline"),
22503
+ disabledComponents: [...disabled],
22504
+ disableProductGraph: disabled.includes("product_graph"),
22505
+ contractVersion: options.contractVersion,
22506
+ ccepProfile: options.ccepProfile ?? "feature"
22507
+ });
22508
+ await writeFile16(activeOverlayPath(projectRoot), $stringify(overlay), "utf-8");
22509
+ return ok(overlay);
22510
+ } catch (e) {
22511
+ return err(e instanceof Error ? e : new Error(String(e)));
22512
+ }
22513
+ }
22514
+ async function currentFingerprint(projectRoot, contractVersion) {
22515
+ const catalog = await loadHarnessCatalog(projectRoot);
22516
+ const overlay = await readActiveOverlay(projectRoot);
22517
+ const disabled = overlay?.disabledComponents ?? [];
22518
+ return {
22519
+ fingerprint: harnessFingerprint(disabled, overlay?.contractVersion ?? contractVersion, overlay?.ccepProfile ?? "feature", catalog),
22520
+ overlay,
22521
+ states: componentStates(disabled, catalog)
22522
+ };
22523
+ }
22524
+ var EVAL_DIR2 = ".codeconductor/evaluation", ACTIVE_OVERLAY_FILE = "active-overlay.yml", CATALOG_FILE = "harness-catalog.yml", OVERLAY_BACKUP_DIR = "overlay-backup", WORKFLOW_OVERLAY_COMMANDS, PHASE_BY_COMPONENT, DEFAULT_HARNESS_CATALOG;
22525
+ var init_harness_catalog = __esm(() => {
22526
+ init_dist();
22527
+ init_schemas();
22528
+ init_codeconductor_config();
22529
+ init_config_loader();
22530
+ init_config_writer();
22531
+ init_workflow_profile_loader();
22532
+ WORKFLOW_OVERLAY_COMMANDS = ["feature", "fix", "refactor", "council"];
22533
+ PHASE_BY_COMPONENT = {
22534
+ wayfinding: "wayfinding",
22535
+ intake: "intake",
22536
+ design: "design",
22537
+ test_first: "test",
22538
+ review: "review",
22539
+ docs: "docs"
22540
+ };
22541
+ DEFAULT_HARNESS_CATALOG = {
22542
+ version: 1,
22543
+ components: [
22544
+ {
22545
+ id: "wayfinding",
22546
+ layer: "phase",
22547
+ label: "Wayfinding (repo-explorer)",
22548
+ defaultOn: true,
22549
+ toggle: { kind: "ccep_phase", phaseId: "wayfinding" }
22550
+ },
22551
+ {
22552
+ id: "intake",
22553
+ layer: "phase",
22554
+ label: "Intake (task-coach)",
22555
+ defaultOn: true,
22556
+ toggle: { kind: "ccep_phase", phaseId: "intake" }
22557
+ },
22558
+ {
22559
+ id: "design",
22560
+ layer: "phase",
22561
+ label: "Design (architect)",
22562
+ defaultOn: true,
22563
+ toggle: { kind: "ccep_phase", phaseId: "design" }
22564
+ },
22565
+ {
22566
+ id: "test_first",
22567
+ layer: "phase",
22568
+ label: "Test-first (tester before implement)",
22569
+ defaultOn: true,
22570
+ toggle: { kind: "ccep_phase", phaseId: "test" }
22571
+ },
22572
+ {
22573
+ id: "review",
22574
+ layer: "phase",
22575
+ label: "Review (reviewer)",
22576
+ defaultOn: true,
22577
+ toggle: { kind: "ccep_phase", phaseId: "review" }
22578
+ },
22579
+ {
22580
+ id: "docs",
22581
+ layer: "phase",
22582
+ label: "Docs",
22583
+ defaultOn: true,
22584
+ toggle: { kind: "ccep_phase", phaseId: "docs" }
22585
+ },
22586
+ {
22587
+ id: "confirmation_gates",
22588
+ layer: "gate",
22589
+ label: "Confirmation / stop gates",
22590
+ defaultOn: true,
22591
+ toggle: { kind: "ccep_gates" }
22592
+ },
22593
+ {
22594
+ id: "compile_loop",
22595
+ layer: "loop",
22596
+ label: "Compile-fix loop",
22597
+ defaultOn: true,
22598
+ toggle: { kind: "config_loop" }
22599
+ },
22600
+ {
22601
+ id: "council",
22602
+ layer: "council",
22603
+ label: "Council deliberation and review",
22604
+ defaultOn: true,
22605
+ toggle: { kind: "config_council" }
22606
+ },
22607
+ {
22608
+ id: "product_graph",
22609
+ layer: "knowledge",
22610
+ label: "Product-graph knowledge layer",
22611
+ defaultOn: true,
22612
+ toggle: { kind: "product_graph" }
22613
+ }
22614
+ ]
22615
+ };
22616
+ });
22617
+
22618
+ // src/core/ccep/context-resolver.ts
22619
+ import { existsSync as existsSync6 } from "node:fs";
22620
+ import { readFile as readFile25 } from "node:fs/promises";
22621
+ import { join as join19 } from "node:path";
22622
+ async function loadProductKnowledge(projectRoot) {
22623
+ if (isProductGraphDisabled(projectRoot)) {
22624
+ return {};
22625
+ }
22626
+ const graphPath = productGraphPath(projectRoot);
22627
+ if (!existsSync6(graphPath)) {
22628
+ return {};
22629
+ }
22630
+ try {
22631
+ const raw = await readFile25(graphPath, "utf-8");
22632
+ const graph = ProductGraphSchema.parse(JSON.parse(raw));
22633
+ return {
22634
+ productName: graph.productName,
22635
+ domains: queryNodes(graph, "domain").map((n) => n.name),
22636
+ decisions: queryNodes(graph, "decision").slice(0, 10).map((n) => ({ id: n.id, name: n.name, data: n.data })),
22637
+ risks: queryNodes(graph, "risk").slice(0, 10).map((n) => ({ id: n.id, name: n.name })),
22638
+ requirements: queryNodes(graph, "requirement").slice(0, 10).map((n) => ({ id: n.id, name: n.name, status: n.data.status })),
22639
+ nodeCount: graph.nodes.length
22640
+ };
22641
+ } catch {
22642
+ return {};
22643
+ }
22644
+ }
22645
+ async function resolveAstSource(projectRoot) {
22646
+ if (isProductGraphDisabled(projectRoot)) {
22647
+ const graphifyPath2 = join19(projectRoot, "graphify-out", "graph.json");
22648
+ if (existsSync6(graphifyPath2)) {
22649
+ return { source: "graphify", confidence: "medium" };
22650
+ }
22651
+ return { source: "manual", confidence: "low" };
22652
+ }
22653
+ const graphPath = productGraphPath(projectRoot);
22654
+ if (existsSync6(graphPath)) {
22655
+ return { source: "product-graph", confidence: "high" };
22656
+ }
22657
+ const graphifyPath = join19(projectRoot, "graphify-out", "graph.json");
22658
+ if (existsSync6(graphifyPath)) {
22659
+ return { source: "graphify", confidence: "medium" };
22660
+ }
22661
+ try {
22662
+ const profile = await detectProject(projectRoot);
22663
+ if (profile.signals.length > 0) {
22664
+ return { source: "detect", confidence: profile.confidence };
22665
+ }
22666
+ } catch {}
22667
+ return { source: "manual", confidence: "low" };
22668
+ }
22669
+ async function resolveContext(envelope, profile, projectRoot) {
22670
+ const ast = await resolveAstSource(projectRoot);
22671
+ const knowledge = await loadProductKnowledge(projectRoot);
22672
+ const firstPhase = profile.phases[0];
22673
+ let stack = envelope.repoContext.stack;
22674
+ if (stack.length === 0 || stack.length === 1 && stack[0] === "unknown") {
22675
+ try {
22676
+ const detected = await detectProject(projectRoot);
22677
+ stack = [...new Set([...detected.languages, ...detected.frameworks])];
22678
+ } catch {
22679
+ stack = envelope.repoContext.stack;
22680
+ }
22681
+ }
22682
+ const context = {
22683
+ envelope,
22684
+ profile,
22685
+ intent: {
22686
+ type: envelope.command,
22687
+ goal: envelope.userRequest,
22688
+ domain: envelope.repoContext.domain
22689
+ },
22690
+ project: {
22691
+ name: envelope.projectId,
22692
+ rootDir: projectRoot,
22693
+ stack
22694
+ },
22695
+ knowledge,
22696
+ ast,
22697
+ policies: DEFAULT_POLICIES,
22698
+ outputSchema: firstPhase?.outputSchema ?? profile.intakeSchema ?? "agent-output"
22699
+ };
22700
+ return validateExecutionContext(context);
22701
+ }
22702
+ var DEFAULT_POLICIES;
22703
+ var init_context_resolver = __esm(() => {
22704
+ init_project_detector();
22705
+ init_schemas();
22706
+ init_command_parser();
22707
+ init_paths();
22708
+ init_graph_store();
22709
+ init_harness_catalog();
22710
+ DEFAULT_POLICIES = {
22711
+ architecture: "modular",
22712
+ testing: "required",
22713
+ documentation: "required",
22714
+ breakingChanges: "approval"
22715
+ };
22716
+ });
22717
+
22718
+ // src/domain/council/council-consensus.ts
22719
+ function canonicalAgentId(agentId) {
22720
+ return agentId.trim().toLowerCase();
22721
+ }
22722
+ function isSecurityCategory(category) {
22723
+ return SECURITY_CATEGORIES.has(category.trim().toLowerCase());
22724
+ }
22725
+ function resolveQuorum(config, ballotCount) {
22726
+ if (typeof config.quorum === "number") {
22727
+ const required = Math.max(1, config.quorum);
22728
+ return { required, met: ballotCount >= required };
22729
+ }
22730
+ if (config.expectedAgentIds && config.expectedAgentIds.length > 0) {
22731
+ const required = Math.ceil(config.expectedAgentIds.length / 2);
22732
+ return { required, met: ballotCount >= required };
22733
+ }
22734
+ if (config.algorithm === "majority") {
22735
+ return { required: 3, met: ballotCount >= 3 };
22736
+ }
22737
+ return { required: 1, met: ballotCount >= 1 };
22738
+ }
22739
+ function validateBallotBox(verdicts, expectedAgentIds) {
22740
+ if (expectedAgentIds !== undefined) {
22741
+ if (expectedAgentIds.length === 0 || expectedAgentIds.some((id) => typeof id !== "string" || canonicalAgentId(id) === "")) {
22742
+ return "invalid expected agent roster";
22743
+ }
22744
+ const canonicalExpected = expectedAgentIds.map(canonicalAgentId);
22745
+ if (new Set(canonicalExpected).size !== canonicalExpected.length) {
22746
+ return "duplicate agent IDs in expected roster";
22747
+ }
22748
+ }
22749
+ const invalidCount = verdicts.filter((v) => typeof v.agentId !== "string" || v.agentId.trim() === "" || !VALID_STATUSES.has(v.status)).length;
22750
+ if (invalidCount > 0) {
22751
+ return `${invalidCount} invalid verdict(s) received`;
22752
+ }
22753
+ const missingConfidence = verdicts.filter((v) => typeof v.confidence !== "number").length;
22754
+ if (missingConfidence > 0) {
22755
+ return `${missingConfidence} verdict(s) missing confidence`;
22756
+ }
22757
+ const seen = new Set;
22758
+ const duplicates = new Set;
22759
+ for (const v of verdicts) {
22760
+ const canonicalId = canonicalAgentId(v.agentId);
22761
+ if (seen.has(canonicalId)) {
22762
+ duplicates.add(v.agentId);
22763
+ }
22764
+ seen.add(canonicalId);
22765
+ }
22766
+ if (duplicates.size > 0) {
22767
+ return `duplicate verdicts from ${[...duplicates].join(", ")}`;
22768
+ }
22769
+ return;
22770
+ }
22771
+ function unanimousFailure(verdicts, expectedAgentIds) {
22772
+ const box = validateBallotBox(verdicts, expectedAgentIds);
22773
+ if (box)
22774
+ return box;
22775
+ if (expectedAgentIds === undefined) {
22776
+ return "missing expected agent roster";
22777
+ }
22778
+ const seen = new Set(verdicts.map((v) => canonicalAgentId(v.agentId)));
22779
+ const missing = expectedAgentIds.filter((id) => !seen.has(canonicalAgentId(id)));
22780
+ if (missing.length > 0) {
22781
+ return `missing verdicts from ${missing.join(", ")}`;
22782
+ }
22783
+ const expected = new Set(expectedAgentIds.map(canonicalAgentId));
22784
+ const unexpected = verdicts.filter((v) => !expected.has(canonicalAgentId(v.agentId))).map((v) => v.agentId);
22785
+ if (unexpected.length > 0) {
22786
+ return `unexpected verdicts from ${unexpected.join(", ")}`;
22787
+ }
22788
+ const nonApproving = verdicts.filter((v) => v.status !== "APPROVED").length;
22789
+ if (nonApproving > 0) {
22790
+ return `${nonApproving} agent(s) did not approve`;
22791
+ }
22792
+ return;
22793
+ }
22794
+ function escalated(verdicts, summary, counts) {
22795
+ return {
22796
+ status: "ESCALATED",
22797
+ totalAgents: verdicts.length,
22798
+ approvedCount: counts?.approvedCount ?? 0,
22799
+ rejectedCount: counts?.rejectedCount ?? 0,
22800
+ abstainedCount: counts?.abstainedCount ?? 0,
22801
+ vetoApplied: false,
22802
+ complianceVetoApplied: false,
22803
+ averageConfidence: counts?.averageConfidence,
22804
+ findings: counts?.findings ? [...counts.findings] : [],
22805
+ summary,
22806
+ individualVerdicts: verdicts
22807
+ };
22808
+ }
22809
+ function councilConsensus(verdicts, config = DEFAULT_CONFIG2) {
22810
+ const totalAgents = verdicts.length;
22811
+ if (totalAgents === 0) {
22812
+ return escalated(verdicts, "No verdicts submitted — escalated for human review.");
22813
+ }
22814
+ const ballotFailure = validateBallotBox(verdicts, config.expectedAgentIds);
22815
+ if (ballotFailure) {
22816
+ return escalated(verdicts, `Ballot box invalid: ${ballotFailure} — escalated for human review.`);
22817
+ }
22818
+ let approvedCount = 0;
22819
+ let rejectedCount = 0;
22820
+ let abstainedCount = 0;
22821
+ let vetoApplied = false;
22822
+ let complianceVetoApplied = false;
22823
+ let vetoByAgentId;
22824
+ let totalConfidence = 0;
22825
+ let hasLowConfidence = false;
22826
+ const allFindings = [];
22827
+ const allowCompliance = typeof config.allowComplianceVeto === "boolean" ? config.allowComplianceVeto : true;
22828
+ const policy = config.criticalFindingsPolicy ?? "escalate";
22829
+ for (const v of verdicts) {
22830
+ switch (v.status) {
22831
+ case "APPROVED":
22832
+ approvedCount++;
22833
+ break;
22834
+ case "REJECTED":
22835
+ rejectedCount++;
22836
+ break;
22837
+ case "ABSTAIN":
22838
+ abstainedCount++;
22839
+ break;
22840
+ }
22841
+ if (typeof v.confidence !== "number") {
22842
+ return escalated(verdicts, "Ballot box invalid: verdict(s) missing confidence — escalated for human review.");
22843
+ }
22844
+ const confidence = v.confidence;
22845
+ totalConfidence += confidence;
22846
+ if (confidence < 0.6) {
22847
+ hasLowConfidence = true;
22848
+ }
22849
+ if (config.allowSecurityVeto && v.securityVeto && v.status === "REJECTED") {
22850
+ vetoApplied = true;
22851
+ vetoByAgentId = v.agentId;
22852
+ }
22853
+ if (allowCompliance && v.complianceVeto && v.status === "REJECTED") {
22854
+ complianceVetoApplied = true;
22855
+ vetoByAgentId = v.agentId;
22856
+ }
22857
+ for (const finding of v.findings) {
22858
+ allFindings.push(finding);
22859
+ if (config.allowSecurityVeto && finding.severity === "critical" && isSecurityCategory(finding.category)) {
22860
+ vetoApplied = true;
22861
+ vetoByAgentId = v.agentId;
22862
+ }
22863
+ }
22864
+ }
22865
+ const averageConfidence = totalConfidence / totalAgents;
22866
+ const counts = {
22867
+ approvedCount,
22868
+ rejectedCount,
22869
+ abstainedCount,
22870
+ averageConfidence,
22871
+ findings: allFindings
22872
+ };
22873
+ if (vetoApplied || complianceVetoApplied) {
22874
+ const vetoAgent = verdicts.find((v) => v.agentId === vetoByAgentId);
22875
+ const vetoType = vetoApplied ? "Security" : "Compliance";
22876
+ return {
22877
+ status: "REJECTED",
22878
+ totalAgents,
22879
+ approvedCount,
22880
+ rejectedCount,
22881
+ abstainedCount,
22882
+ vetoApplied,
22883
+ complianceVetoApplied,
22884
+ vetoByAgentId,
22885
+ averageConfidence,
22886
+ findings: allFindings,
22887
+ summary: `${vetoType} veto applied by ${vetoAgent?.agentRole ?? vetoByAgentId} — rejected regardless of majority.`,
22888
+ individualVerdicts: verdicts
22889
+ };
22890
+ }
22891
+ const critical = allFindings.filter((f) => f.severity === "critical");
22892
+ if (critical.length > 0 && policy === "reject") {
22893
+ return {
22894
+ status: "REJECTED",
22895
+ totalAgents,
22896
+ approvedCount,
22897
+ rejectedCount,
22898
+ abstainedCount,
22899
+ vetoApplied: false,
22900
+ complianceVetoApplied: false,
22901
+ averageConfidence,
22902
+ findings: allFindings,
22903
+ summary: `${critical.length} critical finding(s) — rejected by criticalFindingsPolicy.`,
22904
+ individualVerdicts: verdicts
22905
+ };
22906
+ }
22907
+ if (critical.length > 0 && policy === "escalate") {
22908
+ return escalated(verdicts, `${critical.length} critical finding(s) — escalated by criticalFindingsPolicy.`, counts);
22909
+ }
22910
+ const quorum = resolveQuorum(config, totalAgents);
22911
+ if (!quorum.met) {
22912
+ return escalated(verdicts, `Quorum not reached (${totalAgents}/${quorum.required}) — escalated for human review.`, counts);
22913
+ }
22914
+ if (hasLowConfidence || averageConfidence < 0.7) {
22915
+ const reason = hasLowConfidence ? "one or more agents reported confidence below 0.6" : `average confidence (${averageConfidence.toFixed(2)}) is below 0.7`;
22916
+ return escalated(verdicts, `Confidence threshold breach: ${reason} — escalated for human review.`, {
22917
+ approvedCount,
22918
+ rejectedCount,
22919
+ abstainedCount,
22920
+ averageConfidence,
22921
+ findings: allFindings
22922
+ });
22923
+ }
22924
+ if (config.algorithm === "majority") {
22925
+ if (approvedCount > rejectedCount) {
22926
+ return {
22927
+ status: "APPROVED",
22928
+ totalAgents,
22929
+ approvedCount,
22930
+ rejectedCount,
22931
+ abstainedCount,
22932
+ vetoApplied: false,
22933
+ complianceVetoApplied: false,
22934
+ averageConfidence,
22935
+ findings: allFindings,
22936
+ summary: `Approved by majority (${approvedCount}/${totalAgents}).`,
22937
+ individualVerdicts: verdicts
22938
+ };
22939
+ }
22940
+ }
22941
+ if (config.algorithm === "unanimous") {
22942
+ const failure = unanimousFailure(verdicts, config.expectedAgentIds);
22943
+ if (!failure) {
22944
+ return {
22945
+ status: "APPROVED",
22946
+ totalAgents,
22947
+ approvedCount,
22948
+ rejectedCount,
22949
+ abstainedCount,
22950
+ vetoApplied: false,
22951
+ complianceVetoApplied: false,
22952
+ averageConfidence,
22953
+ findings: allFindings,
22954
+ summary: `Approved unanimously (${approvedCount}/${totalAgents}).`,
22955
+ individualVerdicts: verdicts
22956
+ };
22957
+ }
22958
+ return escalated(verdicts, `Unanimity not reached: ${failure} — escalated for human review.`, {
22959
+ approvedCount,
22960
+ rejectedCount,
22961
+ abstainedCount,
22962
+ averageConfidence,
22963
+ findings: allFindings
22964
+ });
22965
+ }
22966
+ return escalated(verdicts, `No clear consensus (${approvedCount} approved, ${rejectedCount} rejected, ${abstainedCount} abstained) — escalated for human review.`, {
22967
+ approvedCount,
22968
+ rejectedCount,
22969
+ abstainedCount,
22970
+ averageConfidence,
22971
+ findings: allFindings
22972
+ });
22973
+ }
22974
+ var DEFAULT_CONFIG2, VALID_STATUSES, SECURITY_CATEGORIES;
22975
+ var init_council_consensus = __esm(() => {
22976
+ DEFAULT_CONFIG2 = {
22977
+ algorithm: "majority",
22978
+ allowSecurityVeto: true,
22979
+ allowComplianceVeto: true,
22980
+ criticalFindingsPolicy: "escalate"
22981
+ };
22982
+ VALID_STATUSES = new Set(["APPROVED", "REJECTED", "ABSTAIN"]);
22983
+ SECURITY_CATEGORIES = new Set([
22984
+ "security",
22985
+ "auth",
22986
+ "injection",
22987
+ "credentials",
22988
+ "supply-chain"
22989
+ ]);
22990
+ });
22991
+
22992
+ // src/core/shared/vague-criterion.ts
22993
+ function isVagueCriterion(text) {
22994
+ const t = text.trim();
22995
+ if (t.length < 8)
22996
+ return true;
22997
+ return VAGUE_PATTERNS.some((p) => p.test(t));
22998
+ }
22999
+ var VAGUE_PATTERNS;
23000
+ var init_vague_criterion = __esm(() => {
23001
+ VAGUE_PATTERNS = [
23002
+ /^mejorar\s/i,
23003
+ /^fix\s+bugs?$/i,
23004
+ /^refactor$/i,
23005
+ /^hacer\s+refactor/i,
23006
+ /^arreglar\s+bugs?/i,
23007
+ /^improve\s+ux$/i,
23008
+ /^better\s+ux$/i,
23009
+ /^cleanup$/i,
23010
+ /^misc$/i
23011
+ ];
23012
+ });
23013
+
23014
+ // src/core/ccep/task-card-validator.ts
23015
+ function isNonEmpty(value) {
23016
+ if (value === undefined || value === null)
23017
+ return false;
23018
+ if (typeof value === "string")
23019
+ return value.trim().length > 0;
23020
+ if (Array.isArray(value)) {
23021
+ return value.length > 0 && value.every((entry) => isNonEmpty(entry));
23022
+ }
23023
+ return true;
23024
+ }
23025
+ function fieldValue(card, field) {
23026
+ if (field === "scope") {
23027
+ return card.targetFiles;
23028
+ }
23029
+ return card[field];
23030
+ }
23031
+ function validateTaskCardForProfile(profile, card) {
23032
+ const issues = [];
23033
+ const parsed = CanonicalTaskCardSchema.safeParse(card);
23034
+ if (!parsed.success) {
23035
+ issues.push({
23036
+ code: "SCHEMA",
23037
+ message: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
23038
+ });
23039
+ return issues;
23040
+ }
23041
+ const data = parsed.data;
23042
+ const required = profile.taskCard?.requiredFields ?? [];
23043
+ for (const field of required) {
23044
+ if (!isNonEmpty(fieldValue(data, field))) {
23045
+ issues.push({
23046
+ code: "MISSING_FIELD",
23047
+ message: `Required field "${field}" is missing or empty`,
23048
+ itemId: data.id
23049
+ });
23050
+ }
23051
+ }
23052
+ if (data.status === "draft") {
23053
+ issues.push({
23054
+ code: "NOT_ROUTABLE",
23055
+ message: 'TaskCard status "draft" is not routable',
23056
+ itemId: data.id
23057
+ });
23058
+ }
23059
+ if (data.risk === "high" && data.requiresHumanReview !== true) {
23060
+ issues.push({
23061
+ code: "HIGH_RISK_REVIEW",
23062
+ message: "risk: high requires requiresHumanReview: true",
23063
+ itemId: data.id
23064
+ });
23065
+ }
23066
+ if (data.type !== "docs" && data.type !== "review" && data.requiresTests !== true) {
23067
+ issues.push({
23068
+ code: "TESTS_REQUIRED",
23069
+ message: `type "${data.type}" requires requiresTests: true`,
23070
+ itemId: data.id
23071
+ });
23072
+ }
23073
+ for (const criterion of data.acceptanceCriteria) {
23074
+ if (isVagueCriterion(criterion)) {
23075
+ issues.push({
23076
+ code: "VAGUE_ACCEPTANCE",
23077
+ message: `Vague acceptance criterion: "${criterion}"`,
23078
+ itemId: data.id
23079
+ });
23080
+ }
23081
+ }
23082
+ return issues;
23083
+ }
23084
+ var init_task_card_validator = __esm(() => {
23085
+ init_schemas();
23086
+ init_vague_criterion();
23087
+ });
23088
+
23089
+ // src/commands/ccep.command.ts
23090
+ var exports_ccep_command = {};
23091
+ __export(exports_ccep_command, {
23092
+ ccepCommand: () => ccepCommand
23093
+ });
23094
+ function parseWorkflowCommand(command) {
23095
+ if (!command) {
23096
+ return { ok: false, error: "Missing required --command flag" };
23097
+ }
23098
+ const result = WorkflowCommandSchema.safeParse(command);
23099
+ if (!result.success) {
23100
+ return { ok: false, error: `Unknown workflow command: ${command}` };
23101
+ }
23102
+ return { ok: true, command: result.data };
23103
+ }
23104
+ function containedContextError(contextPath) {
23105
+ return `Could not read --context file: ${contextPath}. ` + "It must be a regular file at a relative path inside the project root.";
23106
+ }
23107
+ async function readContainedContext(projectRoot, contextPath) {
22061
23108
  try {
22062
23109
  return await readFileWithinRoot(projectRoot, contextPath);
22063
23110
  } catch {
@@ -22095,6 +23142,37 @@ async function readInputPayload(projectRoot, input, rest) {
22095
23142
  return { ok: false, error: `Invalid JSON input: ${String(err3)}` };
22096
23143
  }
22097
23144
  }
23145
+ function consensusExitCode(status) {
23146
+ if (status === "APPROVED")
23147
+ return 0;
23148
+ if (status === "REJECTED")
23149
+ return 1;
23150
+ return 2;
23151
+ }
23152
+ function parseConsensusPayload(data) {
23153
+ if (Array.isArray(data)) {
23154
+ return { ok: true, verdicts: data };
23155
+ }
23156
+ if (typeof data !== "object" || data === null) {
23157
+ return { ok: false, error: "Consensus input must be a verdicts array or { verdicts, config? }" };
23158
+ }
23159
+ const record = data;
23160
+ if (!Array.isArray(record.verdicts)) {
23161
+ return { ok: false, error: "Consensus input must be a verdicts array or { verdicts, config? }" };
23162
+ }
23163
+ let config;
23164
+ if (record.config !== undefined) {
23165
+ const parsed = ConsensusConfigSchema.safeParse(record.config);
23166
+ if (!parsed.success) {
23167
+ return {
23168
+ ok: false,
23169
+ error: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
23170
+ };
23171
+ }
23172
+ config = parsed.data;
23173
+ }
23174
+ return { ok: true, verdicts: record.verdicts, config };
23175
+ }
22098
23176
  async function ccepCommand(options) {
22099
23177
  const {
22100
23178
  subcommand,
@@ -22106,6 +23184,7 @@ async function ccepCommand(options) {
22106
23184
  input,
22107
23185
  contextPath,
22108
23186
  promptVersion = "v1.0.0",
23187
+ config,
22109
23188
  rest
22110
23189
  } = options;
22111
23190
  switch (subcommand) {
@@ -22260,6 +23339,22 @@ async function ccepCommand(options) {
22260
23339
  data: { success: false, command: "ccep validate", errors: [payload.error] }
22261
23340
  };
22262
23341
  }
23342
+ if (profile.taskCard && CanonicalTaskCardSchema.safeParse(payload.data).success) {
23343
+ const issues = validateTaskCardForProfile(profile, payload.data);
23344
+ return {
23345
+ code: issues.length === 0 ? 0 : 1,
23346
+ data: {
23347
+ success: issues.length === 0,
23348
+ command: "ccep validate",
23349
+ phase: phaseId,
23350
+ role: agentRole,
23351
+ schema: "taskcard",
23352
+ valid: issues.length === 0,
23353
+ issues,
23354
+ errors: issues.map((issue) => issue.message)
23355
+ }
23356
+ };
23357
+ }
22263
23358
  const result = validateOutputForRole(agentRole, resolvedPhase.outputSchema, payload.data);
22264
23359
  return {
22265
23360
  code: result.valid ? 0 : 1,
@@ -22275,6 +23370,95 @@ async function ccepCommand(options) {
22275
23370
  }
22276
23371
  };
22277
23372
  }
23373
+ case "taskcard": {
23374
+ const parsed = parseWorkflowCommand(command);
23375
+ if (!parsed.ok) {
23376
+ return {
23377
+ code: 1,
23378
+ data: { success: false, command: "ccep taskcard", errors: [parsed.error] }
23379
+ };
23380
+ }
23381
+ const profile = loadWorkflowProfile(parsed.command, projectRoot);
23382
+ if (!profile.taskCard) {
23383
+ return {
23384
+ code: 1,
23385
+ data: {
23386
+ success: false,
23387
+ command: "ccep taskcard",
23388
+ errors: [`Workflow "${parsed.command}" does not declare a taskCard contract`]
23389
+ }
23390
+ };
23391
+ }
23392
+ const payload = await readInputPayload(projectRoot, input, rest);
23393
+ if (!payload.ok) {
23394
+ return {
23395
+ code: 1,
23396
+ data: { success: false, command: "ccep taskcard", errors: [payload.error] }
23397
+ };
23398
+ }
23399
+ const issues = validateTaskCardForProfile(profile, payload.data);
23400
+ return {
23401
+ code: issues.length === 0 ? 0 : 1,
23402
+ data: {
23403
+ success: issues.length === 0,
23404
+ command: "ccep taskcard",
23405
+ workflow: parsed.command,
23406
+ issues,
23407
+ errors: issues.map((issue) => issue.message)
23408
+ }
23409
+ };
23410
+ }
23411
+ case "consensus": {
23412
+ const payload = await readInputPayload(projectRoot, input, rest);
23413
+ if (!payload.ok) {
23414
+ return {
23415
+ code: 1,
23416
+ data: { success: false, command: "ccep consensus", errors: [payload.error] }
23417
+ };
23418
+ }
23419
+ const parsedBallots = parseConsensusPayload(payload.data);
23420
+ if (!parsedBallots.ok) {
23421
+ return {
23422
+ code: 1,
23423
+ data: { success: false, command: "ccep consensus", errors: [parsedBallots.error] }
23424
+ };
23425
+ }
23426
+ let mergedConfig = parsedBallots.config;
23427
+ if (config) {
23428
+ const configPayload = await readInputPayload(projectRoot, config);
23429
+ if (!configPayload.ok) {
23430
+ return {
23431
+ code: 1,
23432
+ data: {
23433
+ success: false,
23434
+ command: "ccep consensus",
23435
+ errors: [`Invalid --config: ${configPayload.error}`]
23436
+ }
23437
+ };
23438
+ }
23439
+ const parsedConfig = ConsensusConfigSchema.safeParse(configPayload.data);
23440
+ if (!parsedConfig.success) {
23441
+ return {
23442
+ code: 1,
23443
+ data: {
23444
+ success: false,
23445
+ command: "ccep consensus",
23446
+ errors: parsedConfig.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
23447
+ }
23448
+ };
23449
+ }
23450
+ mergedConfig = { ...mergedConfig, ...parsedConfig.data };
23451
+ }
23452
+ const verdict = councilConsensus(parsedBallots.verdicts, mergedConfig);
23453
+ return {
23454
+ code: consensusExitCode(verdict.status),
23455
+ data: {
23456
+ success: verdict.status === "APPROVED",
23457
+ command: "ccep consensus",
23458
+ verdict
23459
+ }
23460
+ };
23461
+ }
22278
23462
  case "evaluate": {
22279
23463
  const parsed = parseWorkflowCommand(command);
22280
23464
  if (!parsed.ok) {
@@ -22350,7 +23534,7 @@ async function ccepCommand(options) {
22350
23534
  success: false,
22351
23535
  command: "ccep",
22352
23536
  errors: [
22353
- `Unknown subcommand: ${subcommand}. Use: parse, profile, resolve, compile, validate, evaluate`
23537
+ `Unknown subcommand: ${subcommand}. Use: parse, profile, resolve, compile, validate, evaluate, consensus, taskcard`
22354
23538
  ]
22355
23539
  }
22356
23540
  };
@@ -22363,6 +23547,8 @@ var init_ccep_command = __esm(() => {
22363
23547
  init_command_parser();
22364
23548
  init_context_resolver();
22365
23549
  init_workflow_profile_loader();
23550
+ init_council_consensus();
23551
+ init_task_card_validator();
22366
23552
  init_schemas();
22367
23553
  });
22368
23554
 
@@ -22398,12 +23584,6 @@ function detectCycle(items) {
22398
23584
  }
22399
23585
  return null;
22400
23586
  }
22401
- function isVagueCriterion(text) {
22402
- const t = text.trim();
22403
- if (t.length < 8)
22404
- return true;
22405
- return VAGUE_PATTERNS.some((p) => p.test(t));
22406
- }
22407
23587
  function validateBacklog(doc) {
22408
23588
  const errors3 = [];
22409
23589
  const recommendations = [];
@@ -22505,30 +23685,42 @@ function buildRecommendations(errors3, extra = []) {
22505
23685
  }
22506
23686
  return recs;
22507
23687
  }
22508
- var VAGUE_PATTERNS;
22509
23688
  var init_backlog_validator = __esm(() => {
22510
23689
  init_schemas();
22511
- VAGUE_PATTERNS = [
22512
- /^mejorar\s/i,
22513
- /^fix\s+bugs?$/i,
22514
- /^refactor$/i,
22515
- /^hacer\s+refactor/i,
22516
- /^arreglar\s+bugs?/i,
22517
- /^improve\s+ux$/i,
22518
- /^better\s+ux$/i,
22519
- /^cleanup$/i,
22520
- /^misc$/i
22521
- ];
23690
+ init_vague_criterion();
22522
23691
  });
22523
23692
 
22524
23693
  // src/core/openspec/openspec-state.ts
22525
- import { createHash as createHash3 } from "node:crypto";
22526
- import { mkdir as mkdir17, readFile as readFile24, writeFile as writeFile15 } from "node:fs/promises";
22527
- import { resolve as resolve22 } from "node:path";
23694
+ import { createHash as createHash4 } from "node:crypto";
23695
+ import { mkdir as mkdir18, readFile as readFile26, rename as rename2, writeFile as writeFile17 } from "node:fs/promises";
23696
+ import { resolve as resolve24 } from "node:path";
23697
+ function canTransition(from, to) {
23698
+ return ALLOWED_TRANSITIONS[from]?.includes(to) ?? false;
23699
+ }
23700
+ function escapeRegExp(value) {
23701
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23702
+ }
23703
+ function applyBacklogTransition(content, itemId, from, to, progress) {
23704
+ if (from === to) {
23705
+ if (progress === undefined)
23706
+ return ok(content);
23707
+ return ok(updateBacklogItemInMarkdown(content, itemId, { progress }));
23708
+ }
23709
+ if (!canTransition(from, to)) {
23710
+ const allowed = ALLOWED_TRANSITIONS[from] ?? [];
23711
+ return err(new Error(`Illegal backlog transition for ${itemId}: ${from} → ${to}. ` + `Allowed: ${allowed.length > 0 ? allowed.join(", ") : "none"}.`));
23712
+ }
23713
+ return ok(updateBacklogItemInMarkdown(content, itemId, { status: to, progress }));
23714
+ }
23715
+ async function writeFileAtomic(path, content) {
23716
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
23717
+ await writeFile17(tmp, content, "utf-8");
23718
+ await rename2(tmp, path);
23719
+ }
22528
23720
  async function loadOpenspecState(projectRoot) {
22529
23721
  try {
22530
- const filePath = resolve22(projectRoot, STATE_FILE);
22531
- const content = await readFile24(filePath, "utf-8");
23722
+ const filePath = resolve24(projectRoot, STATE_FILE);
23723
+ const content = await readFile26(filePath, "utf-8");
22532
23724
  const data = JSON.parse(content);
22533
23725
  return ok(OpenspecStateSchema.parse(data));
22534
23726
  } catch (e) {
@@ -22545,15 +23737,19 @@ async function loadOpenspecState(projectRoot) {
22545
23737
  }
22546
23738
  async function writeOpenspecState(projectRoot, state) {
22547
23739
  try {
22548
- const dir = resolve22(projectRoot, ".codeconductor");
22549
- await mkdir17(dir, { recursive: true });
23740
+ const dir = resolve24(projectRoot, ".codeconductor");
23741
+ await mkdir18(dir, { recursive: true });
22550
23742
  const validated = OpenspecStateSchema.parse(state);
22551
- await writeFile15(resolve22(dir, "openspec-state.json"), JSON.stringify(validated, null, 2), "utf-8");
23743
+ await writeFileAtomic(resolve24(dir, "openspec-state.json"), JSON.stringify(validated, null, 2));
22552
23744
  return ok(undefined);
22553
23745
  } catch (e) {
22554
23746
  return err(e instanceof Error ? e : new Error(String(e)));
22555
23747
  }
22556
23748
  }
23749
+ function setTaskCardStatus(state, cardId, status) {
23750
+ const taskCards = state.taskCards.map((c) => c.id === cardId ? { ...c, status } : c);
23751
+ return { ...state, taskCards };
23752
+ }
22557
23753
  function getNextTaskCard(state) {
22558
23754
  const done = new Set(state.taskCards.filter((c) => c.status === "done").map((c) => c.id));
22559
23755
  for (const card of state.taskCards) {
@@ -22566,13 +23762,25 @@ function getNextTaskCard(state) {
22566
23762
  return null;
22567
23763
  }
22568
23764
  function hashContent(content) {
22569
- return createHash3("sha256").update(content).digest("hex").slice(0, 16);
23765
+ return createHash4("sha256").update(content).digest("hex").slice(0, 16);
23766
+ }
23767
+ function serializeItemSnapshot(item) {
23768
+ return JSON.stringify({
23769
+ status: item.status,
23770
+ progress: item.progress,
23771
+ title: item.title,
23772
+ description: item.description,
23773
+ scope: item.scope,
23774
+ type: item.type,
23775
+ priority: item.priority,
23776
+ acceptanceCriteria: item.acceptanceCriteria
23777
+ });
22570
23778
  }
22571
23779
  function updateBacklogItemInMarkdown(content, itemId, updates) {
22572
23780
  const lines = content.split(`
22573
23781
  `);
22574
23782
  let inTargetItem = false;
22575
- const idPattern = new RegExp(`^###\\s+.*${itemId}\\b`, "i");
23783
+ const idPattern = new RegExp(`^###\\s+.*${escapeRegExp(itemId)}\\b`, "i");
22576
23784
  for (let i = 0;i < lines.length; i++) {
22577
23785
  const line = lines[i];
22578
23786
  if (line.match(/^###\s+/)) {
@@ -22591,34 +23799,69 @@ function updateBacklogItemInMarkdown(content, itemId, updates) {
22591
23799
  return lines.join(`
22592
23800
  `);
22593
23801
  }
22594
- var STATE_FILE = ".codeconductor/openspec-state.json";
23802
+ function archiveItemInMarkdown(content, itemId) {
23803
+ const lines = content.split(`
23804
+ `);
23805
+ const itemStart = lines.findIndex((l) => l.match(new RegExp(`^###\\s+.*${escapeRegExp(itemId)}\\b`, "i")));
23806
+ if (itemStart < 0)
23807
+ return content;
23808
+ let itemEnd = lines.length;
23809
+ for (let i = itemStart + 1;i < lines.length; i++) {
23810
+ if (lines[i].match(/^###\s+/) || lines[i].match(/^##\s+Archive/i)) {
23811
+ itemEnd = i;
23812
+ break;
23813
+ }
23814
+ }
23815
+ const block = lines.slice(itemStart, itemEnd);
23816
+ const statusLine = block.findIndex((l) => l.match(/^-\s+Status:/i));
23817
+ if (statusLine >= 0)
23818
+ block[statusLine] = "- Status: DONE";
23819
+ const progressLine = block.findIndex((l) => l.match(/^-\s+Progress:/i));
23820
+ if (progressLine >= 0)
23821
+ block[progressLine] = "- Progress: 100%";
23822
+ const withoutItem = [...lines.slice(0, itemStart), ...lines.slice(itemEnd)];
23823
+ const archiveIdx = withoutItem.findIndex((l) => /^##\s+Archive/i.test(l));
23824
+ if (archiveIdx < 0) {
23825
+ withoutItem.push("", "## Archive", ...block);
23826
+ } else {
23827
+ withoutItem.splice(archiveIdx + 1, 0, ...block);
23828
+ }
23829
+ return withoutItem.join(`
23830
+ `);
23831
+ }
23832
+ var STATE_FILE = ".codeconductor/openspec-state.json", ALLOWED_TRANSITIONS;
22595
23833
  var init_openspec_state = __esm(() => {
22596
23834
  init_schemas();
23835
+ ALLOWED_TRANSITIONS = {
23836
+ TODO: ["READY"],
23837
+ READY: ["PLANNED", "BLOCKED"],
23838
+ PLANNED: ["IN_PROGRESS", "BLOCKED"],
23839
+ IN_PROGRESS: ["REVIEW", "BLOCKED"],
23840
+ BLOCKED: ["READY"],
23841
+ REVIEW: ["DONE", "IN_PROGRESS"],
23842
+ DONE: []
23843
+ };
22597
23844
  });
22598
23845
 
22599
23846
  // src/core/openspec/backlog-scanner.ts
22600
- import { execSync as execSync2 } from "node:child_process";
22601
- import { readFile as readFile25 } from "node:fs/promises";
22602
- import { resolve as resolve23 } from "node:path";
23847
+ import { execFileSync as execFileSync2 } from "node:child_process";
23848
+ import { readFile as readFile27 } from "node:fs/promises";
23849
+ import { resolve as resolve25 } from "node:path";
22603
23850
  function itemSnapshot(items) {
22604
23851
  const snap = {};
22605
23852
  for (const item of items) {
22606
- snap[item.id] = JSON.stringify({
22607
- status: item.status,
22608
- progress: item.progress,
22609
- title: item.title
22610
- });
23853
+ snap[item.id] = serializeItemSnapshot(item);
22611
23854
  }
22612
23855
  return snap;
22613
23856
  }
22614
23857
  async function scanBacklog(projectRoot, previousSnapshot = {}) {
22615
23858
  try {
22616
- const filePath = resolve23(projectRoot, BACKLOG_FILENAME);
22617
- const content = await readFile25(filePath, "utf-8");
23859
+ const filePath = resolve25(projectRoot, BACKLOG_FILENAME);
23860
+ const content = await readFile27(filePath, "utf-8");
22618
23861
  const hash = hashContent(content);
22619
23862
  let fileChanged = true;
22620
23863
  try {
22621
- const diff = execSync2(`git diff --name-only -- ${BACKLOG_FILENAME}`, {
23864
+ const diff = execFileSync2("git", ["diff", "--name-only", "--", BACKLOG_FILENAME], {
22622
23865
  cwd: projectRoot,
22623
23866
  encoding: "utf-8",
22624
23867
  stdio: ["ignore", "pipe", "ignore"]
@@ -22752,8 +23995,17 @@ function phaseAcceptance(phase, item) {
22752
23995
  function getPhaseOrder(doc) {
22753
23996
  return doc.global.tddRequired ? [...TDD_PHASE_ORDER] : [...DEFAULT_PHASE_ORDER];
22754
23997
  }
22755
- function planTaskCardsForItem(item, doc, existingCards = []) {
23998
+ function hashItemSnapshot(item) {
23999
+ return hashContent(serializeItemSnapshot(item));
24000
+ }
24001
+ function planTaskCardsForItem(item, doc, existingCards = [], previousSnapshot) {
22756
24002
  const phases = getPhaseOrder(doc);
24003
+ const currentHash = hashItemSnapshot(item);
24004
+ const snapshotDrifted = Boolean(previousSnapshot && hashContent(previousSnapshot) !== currentHash);
24005
+ const existingForItem = existingCards.filter((c) => c.backlogId === item.id);
24006
+ const previousOrder = existingForItem.map((c) => c.phase);
24007
+ const orderChanged = previousOrder.length > 0 && previousOrder.join(",") !== phases.join(",");
24008
+ const invalidatedCards = [];
22757
24009
  const cards = [];
22758
24010
  let prevId;
22759
24011
  for (const phase of phases) {
@@ -22761,6 +24013,11 @@ function planTaskCardsForItem(item, doc, existingCards = []) {
22761
24013
  const existing = existingCards.find((c) => c.id === id);
22762
24014
  const route = routePhaseToAgent(phase);
22763
24015
  const dependsOn = prevId ? [prevId] : [];
24016
+ const drifted = snapshotDrifted || Boolean(existing?.itemHash && existing.itemHash !== currentHash);
24017
+ const reset = drifted || orderChanged;
24018
+ if (existing && reset) {
24019
+ invalidatedCards.push(existing.id);
24020
+ }
22764
24021
  cards.push({
22765
24022
  id,
22766
24023
  backlogId: item.id,
@@ -22771,11 +24028,22 @@ function planTaskCardsForItem(item, doc, existingCards = []) {
22771
24028
  modelHint: route.modelKey,
22772
24029
  dependsOn,
22773
24030
  acceptanceCriteria: phaseAcceptance(phase, item),
22774
- status: existing?.status ?? "pending"
24031
+ status: reset ? "pending" : existing?.status ?? "pending",
24032
+ itemHash: currentHash
22775
24033
  });
22776
24034
  prevId = id;
22777
24035
  }
22778
- return cards;
24036
+ const leftover = existingForItem.filter((c) => !phases.includes(c.phase));
24037
+ for (const card of leftover) {
24038
+ invalidatedCards.push(card.id);
24039
+ }
24040
+ const uniqueInvalidated = [...new Set(invalidatedCards)];
24041
+ const tddImpact = orderChanged ? {
24042
+ previousOrder,
24043
+ nextOrder: phases,
24044
+ resetCardIds: uniqueInvalidated
24045
+ } : undefined;
24046
+ return { cards, invalidatedCards: uniqueInvalidated, tddImpact };
22779
24047
  }
22780
24048
  function comparePriority(a, b) {
22781
24049
  const order = { P0: 0, P1: 1, P2: 2, P3: 3 };
@@ -22808,6 +24076,7 @@ function buildChangeSlug(item) {
22808
24076
  var DEFAULT_PHASE_ORDER, TDD_PHASE_ORDER;
22809
24077
  var init_backlog_planner = __esm(() => {
22810
24078
  init_agent_router();
24079
+ init_openspec_state();
22811
24080
  DEFAULT_PHASE_ORDER = [
22812
24081
  "discover",
22813
24082
  "design",
@@ -22825,8 +24094,8 @@ var init_backlog_planner = __esm(() => {
22825
24094
  });
22826
24095
 
22827
24096
  // src/core/openspec/openspec-generator.ts
22828
- import { mkdir as mkdir18, writeFile as writeFile16 } from "node:fs/promises";
22829
- import { resolve as resolve24 } from "node:path";
24097
+ import { mkdir as mkdir19, rename as rename3, writeFile as writeFile18 } from "node:fs/promises";
24098
+ import { basename as basename3, resolve as resolve26 } from "node:path";
22830
24099
  function proposalContent(item) {
22831
24100
  return `# Proposal: ${item.title}
22832
24101
 
@@ -22869,6 +24138,10 @@ ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
22869
24138
  `)}
22870
24139
  `;
22871
24140
  }
24141
+ async function writeTasksMarkdown(projectRoot, changePath, cards) {
24142
+ await mkdir19(resolve26(projectRoot, changePath), { recursive: true });
24143
+ await writeFile18(resolve26(projectRoot, changePath, "tasks.md"), tasksContent(cards), "utf-8");
24144
+ }
22872
24145
  function tasksContent(cards) {
22873
24146
  const implCards = cards.filter((c) => c.phase === "implement" || c.phase === "test");
22874
24147
  const lines = ["# Implementation Tasks", ""];
@@ -22884,11 +24157,11 @@ function tasksContent(cards) {
22884
24157
  }
22885
24158
  async function generateOpenspecChange(projectRoot, item, taskCards) {
22886
24159
  const slug = buildChangeSlug(item);
22887
- const changeDir = resolve24(projectRoot, "openspec", "changes", slug);
22888
- await mkdir18(resolve24(changeDir, "specs"), { recursive: true });
22889
- await writeFile16(resolve24(changeDir, "proposal.md"), proposalContent(item), "utf-8");
22890
- await writeFile16(resolve24(changeDir, "design.md"), designPlaceholder(item), "utf-8");
22891
- await writeFile16(resolve24(changeDir, "tasks.md"), tasksContent(taskCards), "utf-8");
24160
+ const changeDir = resolve26(projectRoot, "openspec", "changes", slug);
24161
+ await mkdir19(resolve26(changeDir, "specs"), { recursive: true });
24162
+ 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");
22892
24165
  const specStub = `# Delta Spec: ${item.title}
22893
24166
 
22894
24167
  ## ADDED Requirements
@@ -22896,20 +24169,29 @@ async function generateOpenspecChange(projectRoot, item, taskCards) {
22896
24169
  ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
22897
24170
  `)}
22898
24171
  `;
22899
- await writeFile16(resolve24(changeDir, "specs", "delta.md"), specStub, "utf-8");
24172
+ await writeFile18(resolve26(changeDir, "specs", "delta.md"), specStub, "utf-8");
22900
24173
  return `openspec/changes/${slug}`;
22901
24174
  }
24175
+ async function archiveChangeFolder(projectRoot, changePath) {
24176
+ const src = resolve26(projectRoot, changePath);
24177
+ const slug = basename3(changePath);
24178
+ const destDir = resolve26(projectRoot, "openspec", "changes", "archive");
24179
+ await mkdir19(destDir, { recursive: true });
24180
+ const dest = resolve26(destDir, slug);
24181
+ await rename3(src, dest);
24182
+ return `openspec/changes/archive/${slug}`;
24183
+ }
22902
24184
  async function ensureOpenspecConfig(projectRoot) {
22903
- const configPath = resolve24(projectRoot, "openspec", "config.yaml");
24185
+ const configPath = resolve26(projectRoot, "openspec", "config.yaml");
22904
24186
  try {
22905
24187
  const { access: access7 } = await import("node:fs/promises");
22906
24188
  await access7(configPath);
22907
24189
  } catch {
22908
- await mkdir18(resolve24(projectRoot, "openspec"), { recursive: true });
24190
+ await mkdir19(resolve26(projectRoot, "openspec"), { recursive: true });
22909
24191
  const stub = `# OpenSpec project config (CodeConductor native compatible)
22910
24192
  schema: spec-driven
22911
24193
  `;
22912
- await writeFile16(configPath, stub, "utf-8");
24194
+ await writeFile18(configPath, stub, "utf-8");
22913
24195
  }
22914
24196
  }
22915
24197
  var init_openspec_generator = __esm(() => {
@@ -22921,10 +24203,10 @@ var exports_openspec_command = {};
22921
24203
  __export(exports_openspec_command, {
22922
24204
  openspecCommand: () => openspecCommand
22923
24205
  });
22924
- import { readFile as readFile26, writeFile as writeFile17 } from "node:fs/promises";
22925
- import { resolve as resolve25 } from "node:path";
24206
+ import { readFile as readFile28 } from "node:fs/promises";
24207
+ import { resolve as resolve27 } from "node:path";
22926
24208
  async function openspecCommand(options) {
22927
- const { subcommand, itemId, projectRoot, output } = options;
24209
+ const { subcommand, itemId, projectRoot, output, reason } = options;
22928
24210
  switch (subcommand) {
22929
24211
  case "validate":
22930
24212
  return handleValidate(projectRoot);
@@ -22936,6 +24218,14 @@ async function openspecCommand(options) {
22936
24218
  return handleStatus2(projectRoot);
22937
24219
  case "next":
22938
24220
  return handleNext2(projectRoot);
24221
+ case "start":
24222
+ return handleStart(projectRoot, itemId);
24223
+ case "done":
24224
+ return handleDone(projectRoot, itemId);
24225
+ case "block":
24226
+ return handleBlock(projectRoot, itemId, reason);
24227
+ case "archive":
24228
+ return handleArchive(projectRoot, itemId);
22939
24229
  default:
22940
24230
  return {
22941
24231
  code: 1,
@@ -22943,12 +24233,46 @@ async function openspecCommand(options) {
22943
24233
  success: false,
22944
24234
  command: "openspec",
22945
24235
  errors: [
22946
- `Unknown subcommand: ${subcommand}. Use: validate, scan, plan, status, next`
24236
+ `Unknown subcommand: ${subcommand}. Use: ${KNOWN_SUBCOMMANDS}`
22947
24237
  ]
22948
24238
  }
22949
24239
  };
22950
24240
  }
22951
24241
  }
24242
+ function fail(command, errors3, extra = {}) {
24243
+ return {
24244
+ code: 1,
24245
+ data: { success: false, command, errors: errors3, ...extra }
24246
+ };
24247
+ }
24248
+ async function persistBacklog(projectRoot, content) {
24249
+ await writeFileAtomic(resolve27(projectRoot, BACKLOG_FILENAME), content);
24250
+ }
24251
+ async function persistState(projectRoot, state) {
24252
+ const written = await writeOpenspecState(projectRoot, state);
24253
+ if (!written.success) {
24254
+ return { ok: false, error: written.error.message };
24255
+ }
24256
+ return { ok: true };
24257
+ }
24258
+ async function readBacklogMarkdown(projectRoot) {
24259
+ return readFile28(resolve27(projectRoot, BACKLOG_FILENAME), "utf-8");
24260
+ }
24261
+ async function transitionItem(projectRoot, item, to, progress) {
24262
+ const content = await readBacklogMarkdown(projectRoot);
24263
+ const next = applyBacklogTransition(content, item.id, item.status, to, progress);
24264
+ if (!next.success) {
24265
+ return { ok: false, error: next.error.message };
24266
+ }
24267
+ await persistBacklog(projectRoot, next.data);
24268
+ return { ok: true };
24269
+ }
24270
+ function findCard(state, cardId) {
24271
+ return state.taskCards.find((c) => c.id === cardId);
24272
+ }
24273
+ function itemCards(state, backlogId) {
24274
+ return state.taskCards.filter((c) => c.backlogId === backlogId);
24275
+ }
22952
24276
  async function handleValidate(projectRoot) {
22953
24277
  const loadResult = await loadBacklog(projectRoot);
22954
24278
  if (!loadResult.success) {
@@ -23001,7 +24325,10 @@ async function handleScan(projectRoot) {
23001
24325
  lastScanAt: new Date().toISOString(),
23002
24326
  itemSnapshots: snapshots
23003
24327
  };
23004
- await writeOpenspecState(projectRoot, state);
24328
+ const written = await persistState(projectRoot, state);
24329
+ if (!written.ok) {
24330
+ return fail("openspec scan", [written.error]);
24331
+ }
23005
24332
  }
23006
24333
  return {
23007
24334
  code: 0,
@@ -23041,7 +24368,13 @@ async function handlePlan(projectRoot, itemId) {
23041
24368
  changePaths: {},
23042
24369
  itemSnapshots: {}
23043
24370
  };
23044
- const taskCards = planTaskCardsForItem(item, doc, existingState.taskCards);
24371
+ if (item.status !== "PLANNED" && !canTransition(item.status, "PLANNED")) {
24372
+ return fail("openspec plan", [
24373
+ `Illegal backlog transition for ${item.id}: ${item.status} → PLANNED.`
24374
+ ]);
24375
+ }
24376
+ const planned = planTaskCardsForItem(item, doc, existingState.taskCards, existingState.itemSnapshots?.[item.id]);
24377
+ const taskCards = planned.cards;
23045
24378
  await ensureOpenspecConfig(projectRoot);
23046
24379
  const changePath = await generateOpenspecChange(projectRoot, item, taskCards);
23047
24380
  const newState = {
@@ -23052,16 +24385,22 @@ async function handlePlan(projectRoot, itemId) {
23052
24385
  changePaths: {
23053
24386
  ...existingState.changePaths,
23054
24387
  [item.id]: changePath
24388
+ },
24389
+ itemSnapshots: {
24390
+ ...existingState.itemSnapshots,
24391
+ [item.id]: serializeItemSnapshot(item)
23055
24392
  }
23056
24393
  };
23057
- await writeOpenspecState(projectRoot, newState);
23058
- const backlogPath = resolve25(projectRoot, BACKLOG_FILENAME);
23059
- let content = await readFile26(backlogPath, "utf-8");
23060
- content = updateBacklogItemInMarkdown(content, item.id, {
23061
- status: "PLANNED",
23062
- progress: item.progress
23063
- });
23064
- await writeFile17(backlogPath, content, "utf-8");
24394
+ const written = await persistState(projectRoot, newState);
24395
+ if (!written.ok) {
24396
+ return fail("openspec plan", [written.error]);
24397
+ }
24398
+ const content = await readBacklogMarkdown(projectRoot);
24399
+ const transition = applyBacklogTransition(content, item.id, item.status, "PLANNED", item.progress);
24400
+ if (!transition.success) {
24401
+ return fail("openspec plan", [transition.error.message]);
24402
+ }
24403
+ await persistBacklog(projectRoot, transition.data);
23065
24404
  return {
23066
24405
  code: 0,
23067
24406
  data: {
@@ -23070,7 +24409,9 @@ async function handlePlan(projectRoot, itemId) {
23070
24409
  itemId: item.id,
23071
24410
  title: item.title,
23072
24411
  changePath,
23073
- taskCards
24412
+ taskCards,
24413
+ invalidatedCards: planned.invalidatedCards,
24414
+ tddImpact: planned.tddImpact
23074
24415
  }
23075
24416
  };
23076
24417
  }
@@ -23119,23 +24460,244 @@ async function handleNext2(projectRoot) {
23119
24460
  }
23120
24461
  };
23121
24462
  }
23122
- let loop = undefined;
23123
- if (shouldRunAgentLoop(undefined, next.agent, next.phase)) {
23124
- loop = await runLoopForProject(projectRoot, {
23125
- taskTitle: next.title,
23126
- originalTask: next.prompt
23127
- });
24463
+ let loop = undefined;
24464
+ if (shouldRunAgentLoop(undefined, next.agent, next.phase)) {
24465
+ loop = await runLoopForProject(projectRoot, {
24466
+ taskTitle: next.title,
24467
+ originalTask: next.prompt
24468
+ });
24469
+ }
24470
+ return {
24471
+ code: loop && !loop.success ? 1 : 0,
24472
+ data: {
24473
+ success: !(loop && !loop.success),
24474
+ command: "openspec next",
24475
+ taskCard: next,
24476
+ ...loop ? { loop } : {}
24477
+ }
24478
+ };
24479
+ }
24480
+ async function loadStateOrFail(projectRoot, command) {
24481
+ const stateResult = await loadOpenspecState(projectRoot);
24482
+ if (!stateResult.success) {
24483
+ return { ok: false, response: fail(command, [stateResult.error.message]) };
24484
+ }
24485
+ return { ok: true, state: stateResult.data };
24486
+ }
24487
+ async function loadItemOrFail(projectRoot, itemId, command) {
24488
+ const loadResult = await loadBacklog(projectRoot);
24489
+ if (!loadResult.success) {
24490
+ return { ok: false, response: fail(command, [loadResult.error.message]) };
24491
+ }
24492
+ const item = loadResult.data.items.find((i) => i.id === itemId) ?? loadResult.data.archive.find((i) => i.id === itemId);
24493
+ if (!item) {
24494
+ return { ok: false, response: fail(command, [`Backlog item ${itemId} not found`]) };
24495
+ }
24496
+ return {
24497
+ ok: true,
24498
+ item,
24499
+ reviewRequired: loadResult.data.global.reviewRequired
24500
+ };
24501
+ }
24502
+ async function handleStart(projectRoot, cardId) {
24503
+ const command = "openspec start";
24504
+ if (!cardId) {
24505
+ return fail(command, ["Missing card id. Usage: openspec start <cardId>"]);
24506
+ }
24507
+ const loaded = await loadStateOrFail(projectRoot, command);
24508
+ if (!loaded.ok)
24509
+ return loaded.response;
24510
+ const card = findCard(loaded.state, cardId);
24511
+ if (!card) {
24512
+ return fail(command, [`Task card ${cardId} not found`]);
24513
+ }
24514
+ if (card.status !== "pending" && card.status !== "doing") {
24515
+ return fail(command, [`Card ${cardId} cannot start from status ${card.status}`]);
24516
+ }
24517
+ const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
24518
+ if (!itemLoaded.ok)
24519
+ return itemLoaded.response;
24520
+ let state = loaded.state;
24521
+ if (card.status === "pending") {
24522
+ state = setTaskCardStatus(state, cardId, "doing");
24523
+ const written = await persistState(projectRoot, state);
24524
+ if (!written.ok)
24525
+ return fail(command, [written.error]);
24526
+ }
24527
+ const transition = await transitionItem(projectRoot, itemLoaded.item, "IN_PROGRESS", itemLoaded.item.progress);
24528
+ if (!transition.ok)
24529
+ return fail(command, [transition.error]);
24530
+ return {
24531
+ code: 0,
24532
+ data: {
24533
+ success: true,
24534
+ command,
24535
+ cardId,
24536
+ cardStatus: "doing",
24537
+ itemId: itemLoaded.item.id,
24538
+ itemStatus: "IN_PROGRESS"
24539
+ }
24540
+ };
24541
+ }
24542
+ async function handleDone(projectRoot, cardId) {
24543
+ const command = "openspec done";
24544
+ if (!cardId) {
24545
+ return fail(command, ["Missing card id. Usage: openspec done <cardId>"]);
24546
+ }
24547
+ const loaded = await loadStateOrFail(projectRoot, command);
24548
+ if (!loaded.ok)
24549
+ return loaded.response;
24550
+ const card = findCard(loaded.state, cardId);
24551
+ if (!card) {
24552
+ return fail(command, [`Task card ${cardId} not found`]);
24553
+ }
24554
+ if (card.status === "done") {
24555
+ return {
24556
+ code: 0,
24557
+ data: { success: true, command, cardId, cardStatus: "done", alreadyDone: true }
24558
+ };
24559
+ }
24560
+ if (card.status !== "doing") {
24561
+ return fail(command, [`Card ${cardId} must be doing before done (got ${card.status})`]);
24562
+ }
24563
+ const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
24564
+ if (!itemLoaded.ok)
24565
+ return itemLoaded.response;
24566
+ const state = setTaskCardStatus(loaded.state, cardId, "done");
24567
+ const cards = itemCards(state, card.backlogId);
24568
+ const doneCount = cards.filter((c) => c.status === "done").length;
24569
+ const progress = cards.length === 0 ? 0 : Math.round(doneCount / cards.length * 100);
24570
+ const allDone = cards.length > 0 && doneCount === cards.length;
24571
+ const written = await persistState(projectRoot, state);
24572
+ if (!written.ok)
24573
+ return fail(command, [written.error]);
24574
+ const changePath = state.changePaths[card.backlogId];
24575
+ if (changePath) {
24576
+ await writeTasksMarkdown(projectRoot, changePath, cards);
24577
+ }
24578
+ const nextStatus = allDone ? "REVIEW" : "IN_PROGRESS";
24579
+ const transition = await transitionItem(projectRoot, itemLoaded.item, nextStatus, progress);
24580
+ if (!transition.ok)
24581
+ return fail(command, [transition.error]);
24582
+ return {
24583
+ code: 0,
24584
+ data: {
24585
+ success: true,
24586
+ command,
24587
+ cardId,
24588
+ cardStatus: "done",
24589
+ itemId: itemLoaded.item.id,
24590
+ progress,
24591
+ itemStatus: nextStatus,
24592
+ allCardsDone: allDone
24593
+ }
24594
+ };
24595
+ }
24596
+ async function handleBlock(projectRoot, cardId, reason) {
24597
+ const command = "openspec block";
24598
+ if (!cardId) {
24599
+ return fail(command, ["Missing card id. Usage: openspec block <cardId> --reason <text>"]);
24600
+ }
24601
+ if (!reason || !reason.trim()) {
24602
+ return fail(command, ["--reason is required"]);
24603
+ }
24604
+ const loaded = await loadStateOrFail(projectRoot, command);
24605
+ if (!loaded.ok)
24606
+ return loaded.response;
24607
+ const card = findCard(loaded.state, cardId);
24608
+ if (!card) {
24609
+ return fail(command, [`Task card ${cardId} not found`]);
24610
+ }
24611
+ const itemLoaded = await loadItemOrFail(projectRoot, card.backlogId, command);
24612
+ if (!itemLoaded.ok)
24613
+ return itemLoaded.response;
24614
+ const state = setTaskCardStatus(loaded.state, cardId, "blocked");
24615
+ const written = await persistState(projectRoot, state);
24616
+ if (!written.ok)
24617
+ return fail(command, [written.error]);
24618
+ const transition = await transitionItem(projectRoot, itemLoaded.item, "BLOCKED");
24619
+ if (!transition.ok)
24620
+ return fail(command, [transition.error]);
24621
+ return {
24622
+ code: 0,
24623
+ data: {
24624
+ success: true,
24625
+ command,
24626
+ cardId,
24627
+ cardStatus: "blocked",
24628
+ itemId: itemLoaded.item.id,
24629
+ itemStatus: "BLOCKED",
24630
+ reason: reason.trim()
24631
+ }
24632
+ };
24633
+ }
24634
+ async function handleArchive(projectRoot, itemId) {
24635
+ const command = "openspec archive";
24636
+ if (!itemId) {
24637
+ return fail(command, ["Missing item id. Usage: openspec archive <itemId>"]);
24638
+ }
24639
+ const loaded = await loadStateOrFail(projectRoot, command);
24640
+ if (!loaded.ok)
24641
+ return loaded.response;
24642
+ const itemLoaded = await loadItemOrFail(projectRoot, itemId, command);
24643
+ if (!itemLoaded.ok)
24644
+ return itemLoaded.response;
24645
+ const cards = itemCards(loaded.state, itemId);
24646
+ if (cards.length === 0) {
24647
+ return fail(command, [`No task cards found for ${itemId}. Run openspec plan first.`]);
24648
+ }
24649
+ const unfinished = cards.filter((c) => c.status !== "done");
24650
+ if (unfinished.length > 0) {
24651
+ return fail(command, [
24652
+ `Cannot archive ${itemId}: ${unfinished.length} card(s) are not done (${unfinished.map((c) => c.id).join(", ")})`
24653
+ ]);
24654
+ }
24655
+ if (itemLoaded.reviewRequired) {
24656
+ const reviewCard = cards.find((c) => c.phase === "review");
24657
+ if (!reviewCard || reviewCard.status !== "done") {
24658
+ return fail(command, [
24659
+ `Cannot archive ${itemId}: global.reviewRequired needs the review phase card done`
24660
+ ]);
24661
+ }
24662
+ }
24663
+ if (itemLoaded.item.status !== "DONE") {
24664
+ const toDone = await transitionItem(projectRoot, itemLoaded.item, "DONE", 100);
24665
+ if (!toDone.ok)
24666
+ return fail(command, [toDone.error]);
24667
+ }
24668
+ const content = await readBacklogMarkdown(projectRoot);
24669
+ const archived = archiveItemInMarkdown(content, itemId);
24670
+ await persistBacklog(projectRoot, archived);
24671
+ let archivedPath;
24672
+ const changePath = loaded.state.changePaths[itemId];
24673
+ if (changePath && !changePath.includes("/archive/")) {
24674
+ try {
24675
+ archivedPath = await archiveChangeFolder(projectRoot, changePath);
24676
+ } catch (e) {
24677
+ return fail(command, [
24678
+ `Backlog archived but change folder move failed: ${e instanceof Error ? e.message : String(e)}`
24679
+ ]);
24680
+ }
23128
24681
  }
24682
+ const nextState = {
24683
+ ...loaded.state,
24684
+ activeItemId: loaded.state.activeItemId === itemId ? undefined : loaded.state.activeItemId,
24685
+ changePaths: archivedPath ? { ...loaded.state.changePaths, [itemId]: archivedPath } : loaded.state.changePaths
24686
+ };
24687
+ const written = await persistState(projectRoot, nextState);
24688
+ if (!written.ok)
24689
+ return fail(command, [written.error]);
23129
24690
  return {
23130
- code: loop && !loop.success ? 1 : 0,
24691
+ code: 0,
23131
24692
  data: {
23132
- success: !(loop && !loop.success),
23133
- command: "openspec next",
23134
- taskCard: next,
23135
- ...loop ? { loop } : {}
24693
+ success: true,
24694
+ command,
24695
+ itemId,
24696
+ archivedPath: archivedPath ?? changePath
23136
24697
  }
23137
24698
  };
23138
24699
  }
24700
+ var KNOWN_SUBCOMMANDS = "validate, scan, plan, status, next, start, done, block, archive";
23139
24701
  var init_openspec_command = __esm(() => {
23140
24702
  init_backlog_parser();
23141
24703
  init_backlog_validator();
@@ -23543,13 +25105,13 @@ function ccGainToScorecardImpact(result) {
23543
25105
  }
23544
25106
 
23545
25107
  // src/core/evaluation/scorecard-signals.ts
23546
- import { execSync as execSync3 } from "node:child_process";
25108
+ import { execFileSync as execFileSync3 } from "node:child_process";
23547
25109
  function collectScorecardSignals(projectRoot, scopeFiles) {
23548
25110
  const findings = [];
23549
25111
  const overrides = {};
23550
25112
  let diff = "";
23551
25113
  try {
23552
- diff = execSync3("git diff HEAD", {
25114
+ diff = execFileSync3("git", ["diff", "HEAD"], {
23553
25115
  cwd: projectRoot,
23554
25116
  encoding: "utf-8",
23555
25117
  stdio: ["ignore", "pipe", "ignore"]
@@ -23608,11 +25170,11 @@ var init_scorecard_signals = __esm(() => {
23608
25170
  });
23609
25171
 
23610
25172
  // src/core/evaluation/execution-profile.ts
23611
- import { readFile as readFile27 } from "node:fs/promises";
23612
- import { resolve as resolve26 } from "node:path";
25173
+ import { readFile as readFile29 } from "node:fs/promises";
25174
+ import { resolve as resolve28 } from "node:path";
23613
25175
  async function loadExecutionProfile(projectRoot, defaultTarget = "opencode") {
23614
25176
  try {
23615
- const content = await readFile27(resolve26(projectRoot, PROFILE_FILE), "utf-8");
25177
+ const content = await readFile29(resolve28(projectRoot, PROFILE_FILE), "utf-8");
23616
25178
  const data = $parse(content);
23617
25179
  return ExecutionProfileSchema.parse(data);
23618
25180
  } catch {
@@ -23692,14 +25254,14 @@ var init_execution_profile = __esm(() => {
23692
25254
  });
23693
25255
 
23694
25256
  // src/core/evaluation/prompt-diff.ts
23695
- import { readFile as readFile28, readdir as readdir6 } from "node:fs/promises";
23696
- import { join as join19, resolve as resolve27 } from "node:path";
25257
+ import { readFile as readFile30, readdir as readdir7 } from "node:fs/promises";
25258
+ import { join as join20, resolve as resolve29 } from "node:path";
23697
25259
  function normalizeVersion(v) {
23698
25260
  return v.startsWith("v") ? v : `v${v}`;
23699
25261
  }
23700
25262
  async function listPromptFiles(dir) {
23701
25263
  try {
23702
- const entries = await readdir6(dir, { withFileTypes: true });
25264
+ const entries = await readdir7(dir, { withFileTypes: true });
23703
25265
  return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
23704
25266
  } catch {
23705
25267
  return [];
@@ -23742,30 +25304,30 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
23742
25304
  const fromV = normalizeVersion(fromVersion);
23743
25305
  const toV = normalizeVersion(toVersion);
23744
25306
  const searchRoots = [
23745
- resolve27(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
23746
- join19(ROOT_PRESETS_DIR, target, "prompts"),
23747
- join19(ROOT_PRESETS_DIR, "opencode", "prompts")
25307
+ resolve29(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
25308
+ join20(ROOT_PRESETS_DIR, target, "prompts"),
25309
+ join20(ROOT_PRESETS_DIR, "opencode", "prompts")
23748
25310
  ];
23749
25311
  let fromDir = "";
23750
25312
  let toDir = "";
23751
25313
  for (const root of searchRoots) {
23752
- const f = join19(root, fromV);
23753
- const t = join19(root, toV);
25314
+ const f = join20(root, fromV);
25315
+ const t = join20(root, toV);
23754
25316
  try {
23755
- await readFile28(join19(f, "orchestrator.md"), "utf-8");
25317
+ await readFile30(join20(f, "orchestrator.md"), "utf-8");
23756
25318
  fromDir = f;
23757
25319
  toDir = t;
23758
25320
  break;
23759
25321
  } catch {}
23760
25322
  }
23761
25323
  if (!fromDir) {
23762
- fromDir = join19(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
23763
- toDir = join19(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
25324
+ fromDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
25325
+ toDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
23764
25326
  } else {
23765
25327
  try {
23766
- await readFile28(join19(toDir, "orchestrator.md"), "utf-8");
25328
+ await readFile30(join20(toDir, "orchestrator.md"), "utf-8");
23767
25329
  } catch {
23768
- toDir = join19(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
25330
+ toDir = join20(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
23769
25331
  }
23770
25332
  }
23771
25333
  const fromFiles = await listPromptFiles(fromDir);
@@ -23783,12 +25345,12 @@ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
23783
25345
  let oldText = "";
23784
25346
  let newText = "";
23785
25347
  try {
23786
- oldText = await readFile28(join19(fromDir, name), "utf-8");
25348
+ oldText = await readFile30(join20(fromDir, name), "utf-8");
23787
25349
  } catch {
23788
25350
  oldText = "";
23789
25351
  }
23790
25352
  try {
23791
- newText = await readFile28(join19(toDir, name), "utf-8");
25353
+ newText = await readFile30(join20(toDir, name), "utf-8");
23792
25354
  } catch {
23793
25355
  newText = "";
23794
25356
  }
@@ -23827,13 +25389,13 @@ var init_prompt_diff = __esm(() => {
23827
25389
  });
23828
25390
 
23829
25391
  // src/core/evaluation/regression-checklist.ts
23830
- import { execFileSync } from "node:child_process";
23831
- import { mkdir as mkdir19, readFile as readFile29, writeFile as writeFile18 } from "node:fs/promises";
23832
- import { dirname as dirname6, resolve as resolve28 } from "node:path";
25392
+ 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";
23833
25395
  async function loadRegressionChecklist() {
23834
25396
  try {
23835
- const path = resolve28(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
23836
- const content = await readFile29(path, "utf-8");
25397
+ const path = resolve30(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
25398
+ const content = await readFile31(path, "utf-8");
23837
25399
  const data = $parse(content);
23838
25400
  return data.checks ?? [];
23839
25401
  } catch {
@@ -23847,7 +25409,7 @@ function runCheckCommand(cmd, cwd) {
23847
25409
  if (!file)
23848
25410
  return { passed: false, message: "Empty command" };
23849
25411
  try {
23850
- execFileSync(file, args, {
25412
+ execFileSync4(file, args, {
23851
25413
  cwd,
23852
25414
  encoding: "utf-8",
23853
25415
  stdio: ["ignore", "pipe", "pipe"],
@@ -23888,9 +25450,9 @@ async function runRegressionChecklist(projectRoot, options = {}) {
23888
25450
  checks,
23889
25451
  timestamp: new Date().toISOString()
23890
25452
  };
23891
- const outPath = resolve28(projectRoot, ".codeconductor/evaluation/regression-last.json");
23892
- await mkdir19(dirname6(outPath), { recursive: true });
23893
- await writeFile18(outPath, JSON.stringify(report, null, 2), "utf-8");
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");
23894
25456
  return ok(report);
23895
25457
  } catch (e) {
23896
25458
  return err(e instanceof Error ? e : new Error(String(e)));
@@ -23932,8 +25494,8 @@ var init_model_comparison = __esm(() => {
23932
25494
  });
23933
25495
 
23934
25496
  // src/core/evaluation/cost-quality-matrix.ts
23935
- import { writeFile as writeFile19 } from "node:fs/promises";
23936
- import { resolve as resolve29 } from "node:path";
25497
+ import { writeFile as writeFile20 } from "node:fs/promises";
25498
+ import { resolve as resolve31 } from "node:path";
23937
25499
  async function buildCostQualityMatrix(projectRoot, outcomes) {
23938
25500
  const agg = aggregateOutcomes(outcomes);
23939
25501
  const rows = Object.entries(agg.byModel).map(([model, entry]) => {
@@ -23953,7 +25515,7 @@ async function buildCostQualityMatrix(projectRoot, outcomes) {
23953
25515
  rows
23954
25516
  };
23955
25517
  const dir = await ensureEvaluationDir(projectRoot);
23956
- await writeFile19(resolve29(dir, "matrix.yml"), $stringify(matrix), "utf-8");
25518
+ await writeFile20(resolve31(dir, "matrix.yml"), $stringify(matrix), "utf-8");
23957
25519
  return matrix;
23958
25520
  }
23959
25521
  function formatMatrixMarkdown(matrix) {
@@ -23976,13 +25538,385 @@ var init_cost_quality_matrix = __esm(() => {
23976
25538
  init_outcome_store();
23977
25539
  });
23978
25540
 
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
+ // src/core/evaluation/ablation-report.ts
25732
+ import { mkdir as mkdir22, writeFile as writeFile22 } from "node:fs/promises";
25733
+ import { resolve as resolve33 } from "node:path";
25734
+ function isPass(o) {
25735
+ return o.verdict === "PASS" || o.status === "pass";
25736
+ }
25737
+ function mean(values) {
25738
+ if (values.length === 0)
25739
+ return;
25740
+ return values.reduce((s, v) => s + v, 0) / values.length;
25741
+ }
25742
+ function metricsFrom(outcomes, criteria) {
25743
+ const scores = outcomes.map((o) => o.weightedScore).filter((n) => n !== undefined);
25744
+ const costs = outcomes.map((o) => o.costUsd).filter((n) => n !== undefined);
25745
+ const durations = outcomes.map((o) => o.durationMs).filter((n) => n !== undefined);
25746
+ return {
25747
+ count: outcomes.length,
25748
+ avgScore: mean(scores) ?? 0,
25749
+ passRate: outcomes.length > 0 ? outcomes.filter(isPass).length / outcomes.length : 0,
25750
+ avgCost: mean(costs),
25751
+ avgDurationMs: mean(durations),
25752
+ criteria
25753
+ };
25754
+ }
25755
+ function classifyAblation(deltaScore, deltaPassRate) {
25756
+ const scoreNeutral = Math.abs(deltaScore) < SCORE_THRESHOLD;
25757
+ const passNeutral = Math.abs(deltaPassRate) < PASS_RATE_THRESHOLD;
25758
+ if (scoreNeutral && passNeutral)
25759
+ return "no_change";
25760
+ if (deltaScore <= -SCORE_THRESHOLD)
25761
+ return "degrades";
25762
+ if (deltaScore >= SCORE_THRESHOLD)
25763
+ return "improves";
25764
+ if (deltaPassRate <= -PASS_RATE_THRESHOLD)
25765
+ return "degrades";
25766
+ if (deltaPassRate >= PASS_RATE_THRESHOLD)
25767
+ return "improves";
25768
+ return "no_change";
25769
+ }
25770
+ async function averageCriteria(projectRoot, outcomes) {
25771
+ const buckets = {};
25772
+ for (const o of outcomes) {
25773
+ if (!o.scorecardId)
25774
+ continue;
25775
+ const loaded = await loadScorecard(projectRoot, o.scorecardId);
25776
+ if (!loaded.success)
25777
+ continue;
25778
+ for (const c of loaded.data.criteria) {
25779
+ if (!buckets[c.id])
25780
+ buckets[c.id] = [];
25781
+ buckets[c.id].push(c.score);
25782
+ }
25783
+ }
25784
+ const ids = Object.keys(buckets);
25785
+ if (ids.length === 0)
25786
+ return;
25787
+ const avg = {};
25788
+ for (const id of ids) {
25789
+ avg[id] = mean(buckets[id]) ?? 0;
25790
+ }
25791
+ return avg;
25792
+ }
25793
+ async function buildAblationReport(projectRoot, outcomes, experimentId) {
25794
+ const scoped = experimentId ? outcomes.filter((o) => o.experimentId === experimentId) : outcomes.filter((o) => o.variantId);
25795
+ const byComponent = new Map;
25796
+ const keyOf = (o) => o.suiteTaskId ?? o.taskId;
25797
+ const treatmentIds = (o) => {
25798
+ const fromField = o.disabledComponents?.filter((id) => catalogIds().includes(id));
25799
+ if (fromField && fromField.length > 0)
25800
+ return fromField;
25801
+ return parseVariantId(o.variantId ?? "");
25802
+ };
25803
+ const baselines = scoped.filter((o) => o.variantId === "baseline");
25804
+ const treatments = scoped.filter((o) => o.variantId && o.variantId !== "baseline");
25805
+ const baselineKeys = new Set(baselines.map(keyOf));
25806
+ for (const o of treatments) {
25807
+ const ids = treatmentIds(o);
25808
+ const hasPair = baselineKeys.has(keyOf(o)) || baselines.length > 0;
25809
+ if (!hasPair)
25810
+ continue;
25811
+ for (const id of ids) {
25812
+ if (!byComponent.has(id))
25813
+ byComponent.set(id, { baseline: [], treatment: [] });
25814
+ byComponent.get(id).treatment.push(o);
25815
+ }
25816
+ }
25817
+ for (const [id, pair] of byComponent) {
25818
+ const treatmentKeys = new Set(pair.treatment.map(keyOf));
25819
+ const pairedBaselines = baselines.filter((o) => treatmentKeys.has(keyOf(o)));
25820
+ pair.baseline = pairedBaselines.length > 0 ? pairedBaselines : baselines;
25821
+ }
25822
+ const rows = [];
25823
+ for (const id of catalogIds()) {
25824
+ const pair = byComponent.get(id);
25825
+ if (!pair || pair.treatment.length === 0 || pair.baseline.length === 0)
25826
+ continue;
25827
+ const baselineCriteria = await averageCriteria(projectRoot, pair.baseline);
25828
+ const treatmentCriteria = await averageCriteria(projectRoot, pair.treatment);
25829
+ const baseline = metricsFrom(pair.baseline, baselineCriteria);
25830
+ const treatment = metricsFrom(pair.treatment, treatmentCriteria);
25831
+ const deltaScore = treatment.avgScore - baseline.avgScore;
25832
+ const deltaPassRate = treatment.passRate - baseline.passRate;
25833
+ const deltaCriteria = {};
25834
+ const criterionIds = new Set([
25835
+ ...Object.keys(baseline.criteria ?? {}),
25836
+ ...Object.keys(treatment.criteria ?? {})
25837
+ ]);
25838
+ for (const cid of criterionIds) {
25839
+ deltaCriteria[cid] = (treatment.criteria?.[cid] ?? 0) - (baseline.criteria?.[cid] ?? 0);
25840
+ }
25841
+ rows.push({
25842
+ component: id,
25843
+ variantId: `minus:${id}`,
25844
+ baseline,
25845
+ treatment,
25846
+ deltaScore: Math.round(deltaScore * 1000) / 1000,
25847
+ deltaPassRate: Math.round(deltaPassRate * 1000) / 1000,
25848
+ deltaCost: baseline.avgCost !== undefined && treatment.avgCost !== undefined ? Math.round((treatment.avgCost - baseline.avgCost) * 1e4) / 1e4 : undefined,
25849
+ deltaDurationMs: baseline.avgDurationMs !== undefined && treatment.avgDurationMs !== undefined ? Math.round(treatment.avgDurationMs - baseline.avgDurationMs) : undefined,
25850
+ deltaCriteria,
25851
+ verdict: classifyAblation(deltaScore, deltaPassRate)
25852
+ });
25853
+ }
25854
+ return {
25855
+ generatedAt: new Date().toISOString(),
25856
+ experimentId,
25857
+ outcomeCount: scoped.length,
25858
+ pairedCount: rows.reduce((s, r) => s + Math.min(r.baseline.count, r.treatment.count), 0),
25859
+ rows
25860
+ };
25861
+ }
25862
+ function formatAblationMarkdown(report) {
25863
+ const lines = [
25864
+ "# Harness Ablation Report",
25865
+ "",
25866
+ `Generated: ${report.generatedAt}`,
25867
+ report.experimentId ? `Experiment: ${report.experimentId}` : "Experiment: (all tagged outcomes)",
25868
+ `Outcomes: ${report.outcomeCount}`,
25869
+ "",
25870
+ "| Component | Verdict | Δ score | Δ pass | Δ cost | Δ duration | Baseline n | Treatment n |",
25871
+ "|-----------|---------|---------|--------|--------|------------|------------|-------------|"
25872
+ ];
25873
+ for (const row of report.rows) {
25874
+ lines.push(`| ${row.component} | ${row.verdict} | ${row.deltaScore.toFixed(2)} | ${(row.deltaPassRate * 100).toFixed(0)}pp | ${row.deltaCost?.toFixed(4) ?? "—"} | ${row.deltaDurationMs ?? "—"} | ${row.baseline.count} | ${row.treatment.count} |`);
25875
+ }
25876
+ if (report.rows.length === 0) {
25877
+ lines.push("| — | — | — | — | — | — | — | — |");
25878
+ lines.push("", "No paired baseline/treatment outcomes yet.");
25879
+ } else {
25880
+ lines.push("", "## Criterion deltas", "");
25881
+ for (const row of report.rows) {
25882
+ const keys = Object.keys(row.deltaCriteria);
25883
+ if (keys.length === 0)
25884
+ continue;
25885
+ lines.push(`### minus:${row.component}`);
25886
+ for (const key of keys) {
25887
+ const delta = row.deltaCriteria[key] ?? 0;
25888
+ lines.push(`- ${key}: ${delta >= 0 ? "+" : ""}${delta.toFixed(2)}`);
25889
+ }
25890
+ lines.push("");
25891
+ }
25892
+ lines.push("## How to read", "");
25893
+ lines.push(`- \`improves\`: removing the component raised score by ≥ ${SCORE_THRESHOLD} or pass rate by ≥ ${PASS_RATE_THRESHOLD * 100}pp.`);
25894
+ lines.push(`- \`degrades\`: removing the component lowered score by ≥ ${SCORE_THRESHOLD} or pass rate by ≥ ${PASS_RATE_THRESHOLD * 100}pp.`);
25895
+ lines.push(`- \`no_change\`: both deltas stay inside those thresholds.`);
25896
+ }
25897
+ return lines.join(`
25898
+ `);
25899
+ }
25900
+ async function persistAblationReport(projectRoot, markdown) {
25901
+ const dir = resolve33(projectRoot, EVAL_DIR2);
25902
+ await mkdir22(dir, { recursive: true });
25903
+ const path = resolve33(dir, "ablation-last.md");
25904
+ await writeFile22(path, markdown, "utf-8");
25905
+ return path;
25906
+ }
25907
+ var SCORE_THRESHOLD = 0.1, PASS_RATE_THRESHOLD = 0.05;
25908
+ var init_ablation_report = __esm(() => {
25909
+ init_harness_catalog();
25910
+ init_outcome_store();
25911
+ });
25912
+
23979
25913
  // src/commands/scorecard.command.ts
23980
25914
  var exports_scorecard_command = {};
23981
25915
  __export(exports_scorecard_command, {
23982
25916
  scorecardCommand: () => scorecardCommand
23983
25917
  });
23984
- import { readFile as readFile30 } from "node:fs/promises";
23985
- import { resolve as resolve30 } from "node:path";
25918
+ import { readFile as readFile33 } from "node:fs/promises";
25919
+ import { resolve as resolve34 } from "node:path";
23986
25920
  function contractVersion() {
23987
25921
  return package_default.version;
23988
25922
  }
@@ -24009,6 +25943,14 @@ async function scorecardCommand(options) {
24009
25943
  return handleMatrix(projectRoot);
24010
25944
  case "compare-models":
24011
25945
  return handleCompareModels(projectRoot, options);
25946
+ case "catalog":
25947
+ return handleCatalog(projectRoot);
25948
+ case "fingerprint":
25949
+ return handleFingerprint(projectRoot);
25950
+ case "experiment":
25951
+ return handleExperiment(projectRoot, options);
25952
+ case "ablation":
25953
+ return handleAblation(projectRoot, options);
24012
25954
  default:
24013
25955
  return {
24014
25956
  code: 1,
@@ -24016,7 +25958,7 @@ async function scorecardCommand(options) {
24016
25958
  success: false,
24017
25959
  command: "scorecard",
24018
25960
  errors: [
24019
- `Unknown subcommand: ${subcommand}. Use: create, show, record, list, aggregate, models, prompt-diff, regression, matrix, compare-models`
25961
+ `Unknown subcommand: ${subcommand}. Use: create, show, record, list, aggregate, models, prompt-diff, regression, matrix, compare-models, catalog, fingerprint, experiment, ablation`
24020
25962
  ]
24021
25963
  }
24022
25964
  };
@@ -24085,6 +26027,12 @@ async function handleShow(projectRoot, id) {
24085
26027
  }
24086
26028
  async function handleRecord(projectRoot, options) {
24087
26029
  const taskId = options.taskId ?? "unknown-task";
26030
+ const overlay = await readActiveOverlay(projectRoot);
26031
+ const experimentId = options.experimentId ?? overlay?.experimentId;
26032
+ const variantId = options.variantId ?? overlay?.variantId;
26033
+ const suiteTaskId = options.suiteTaskId;
26034
+ const disabled = overlay?.disabledComponents ?? (variantId ? parseVariantId(variantId) : undefined);
26035
+ const fingerprint = experimentId || variantId ? harnessFingerprint(disabled ?? [], overlay?.contractVersion ?? contractVersion(), overlay?.ccepProfile ?? "feature") : undefined;
24088
26036
  const outcome = {
24089
26037
  id: generateEvalId("out"),
24090
26038
  taskId,
@@ -24099,6 +26047,11 @@ async function handleRecord(projectRoot, options) {
24099
26047
  costUsd: options.costUsd,
24100
26048
  tokensIn: options.tokens,
24101
26049
  durationMs: options.durationMs,
26050
+ experimentId,
26051
+ variantId,
26052
+ suiteTaskId,
26053
+ harnessFingerprint: fingerprint,
26054
+ disabledComponents: disabled && disabled.length > 0 ? [...disabled] : disabled ? [] : undefined,
24102
26055
  status: options.verdict === "PASS" ? "pass" : options.verdict === "REJECT" ? "reject" : undefined
24103
26056
  };
24104
26057
  const result = await appendOutcome(projectRoot, outcome);
@@ -24113,7 +26066,10 @@ async function handleList(projectRoot, options) {
24113
26066
  model: options.model,
24114
26067
  taskId: options.taskId,
24115
26068
  since: options.since,
24116
- source: options.source
26069
+ source: options.source,
26070
+ experimentId: options.experimentId,
26071
+ variantId: options.variantId,
26072
+ suiteTaskId: options.suiteTaskId
24117
26073
  };
24118
26074
  const result = await listOutcomes(projectRoot, filter);
24119
26075
  if (!result.success) {
@@ -24125,7 +26081,13 @@ async function handleList(projectRoot, options) {
24125
26081
  };
24126
26082
  }
24127
26083
  async function handleAggregate(projectRoot, options) {
24128
- const filter = { since: options.since, agent: options.agent, model: options.model };
26084
+ const filter = {
26085
+ since: options.since,
26086
+ agent: options.agent,
26087
+ model: options.model,
26088
+ experimentId: options.experimentId,
26089
+ variantId: options.variantId
26090
+ };
24129
26091
  const list = await listOutcomes(projectRoot, filter);
24130
26092
  if (!list.success) {
24131
26093
  return { code: 1, data: { success: false, errors: [list.error.message] } };
@@ -24206,13 +26168,13 @@ async function handleCompareModels(projectRoot, options) {
24206
26168
  outcomes = outcomes.filter((o) => options.modelsFilter.includes(o.model));
24207
26169
  }
24208
26170
  const markdown = generateModelComparisonMarkdown(outcomes, options.modelsFilter);
24209
- const templatePath = resolve30(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
26171
+ const templatePath = resolve34(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
24210
26172
  try {
24211
- await readFile30(templatePath);
26173
+ await readFile33(templatePath);
24212
26174
  } catch {}
24213
- const { writeFile: writeFile20, mkdir: mkdir20 } = await import("node:fs/promises");
24214
- await mkdir20(resolve30(projectRoot, ".codeconductor/evaluation"), { recursive: true });
24215
- await writeFile20(templatePath, markdown, "utf-8");
26175
+ const { writeFile: writeFile23, mkdir: mkdir23 } = await import("node:fs/promises");
26176
+ await mkdir23(resolve34(projectRoot, ".codeconductor/evaluation"), { recursive: true });
26177
+ await writeFile23(templatePath, markdown, "utf-8");
24216
26178
  return {
24217
26179
  code: 0,
24218
26180
  data: {
@@ -24224,6 +26186,119 @@ async function handleCompareModels(projectRoot, options) {
24224
26186
  }
24225
26187
  };
24226
26188
  }
26189
+ async function handleCatalog(projectRoot) {
26190
+ const catalog = await loadHarnessCatalog(projectRoot);
26191
+ return {
26192
+ code: 0,
26193
+ data: { success: true, command: "scorecard catalog", catalog }
26194
+ };
26195
+ }
26196
+ async function handleFingerprint(projectRoot) {
26197
+ const snap = await currentFingerprint(projectRoot, contractVersion());
26198
+ return {
26199
+ code: 0,
26200
+ data: {
26201
+ success: true,
26202
+ command: "scorecard fingerprint",
26203
+ fingerprint: snap.fingerprint,
26204
+ overlay: snap.overlay,
26205
+ components: snap.states
26206
+ }
26207
+ };
26208
+ }
26209
+ async function handleExperiment(projectRoot, options) {
26210
+ const action = options.experimentAction ?? "list";
26211
+ if (action === "list") {
26212
+ const experiments = await listExperiments(projectRoot);
26213
+ return {
26214
+ code: 0,
26215
+ data: { success: true, command: "scorecard experiment list", experiments, count: experiments.length }
26216
+ };
26217
+ }
26218
+ if (action === "show") {
26219
+ if (!options.experimentId && !options.id) {
26220
+ return { code: 1, data: { success: false, errors: ["Usage: scorecard experiment show <id>"] } };
26221
+ }
26222
+ const loaded = await loadExperiment(projectRoot, options.experimentId ?? options.id);
26223
+ if (!loaded.success) {
26224
+ return { code: 1, data: { success: false, errors: [loaded.error.message] } };
26225
+ }
26226
+ return { code: 0, data: { success: true, command: "scorecard experiment show", experiment: loaded.data } };
26227
+ }
26228
+ if (action === "start") {
26229
+ const parsed = parseComponentsFlag(options.components);
26230
+ if (!parsed.success) {
26231
+ return { code: 1, data: { success: false, errors: [parsed.error.message] } };
26232
+ }
26233
+ const started = await startHarnessExperiment(projectRoot, {
26234
+ suiteId: options.suiteId ?? "harness-v1",
26235
+ suitePath: options.suitePath,
26236
+ components: parsed.data,
26237
+ contractVersion: contractVersion(),
26238
+ experimentId: options.experimentId ?? options.id
26239
+ });
26240
+ if (!started.success) {
26241
+ return { code: 1, data: { success: false, errors: [started.error.message] } };
26242
+ }
26243
+ return {
26244
+ code: 0,
26245
+ data: { success: true, command: "scorecard experiment start", experiment: started.data }
26246
+ };
26247
+ }
26248
+ if (action === "apply") {
26249
+ const experimentId = options.experimentId ?? options.id;
26250
+ const variantId = options.variantId;
26251
+ if (!experimentId || !variantId) {
26252
+ return {
26253
+ code: 1,
26254
+ data: { success: false, errors: ["Usage: scorecard experiment apply --id <exp> --variant baseline|minus:<id>"] }
26255
+ };
26256
+ }
26257
+ const applied = await applyExperimentVariant(projectRoot, experimentId, variantId, contractVersion());
26258
+ if (!applied.success) {
26259
+ return { code: 1, data: { success: false, errors: [applied.error.message] } };
26260
+ }
26261
+ return {
26262
+ code: 0,
26263
+ data: {
26264
+ success: true,
26265
+ command: "scorecard experiment apply",
26266
+ experiment: applied.data,
26267
+ variantId
26268
+ }
26269
+ };
26270
+ }
26271
+ return {
26272
+ code: 1,
26273
+ data: {
26274
+ success: false,
26275
+ errors: ["Usage: scorecard experiment start|apply|list|show"]
26276
+ }
26277
+ };
26278
+ }
26279
+ async function handleAblation(projectRoot, options) {
26280
+ const filter = {
26281
+ since: options.since,
26282
+ experimentId: options.experimentId ?? options.id
26283
+ };
26284
+ const list = await listOutcomes(projectRoot, filter);
26285
+ if (!list.success) {
26286
+ return { code: 1, data: { success: false, errors: [list.error.message] } };
26287
+ }
26288
+ const report = await buildAblationReport(projectRoot, list.data, filter.experimentId);
26289
+ const markdown = formatAblationMarkdown(report);
26290
+ const path = await persistAblationReport(projectRoot, markdown);
26291
+ return {
26292
+ code: 0,
26293
+ data: {
26294
+ success: true,
26295
+ command: "scorecard ablation",
26296
+ report,
26297
+ markdown,
26298
+ path
26299
+ }
26300
+ };
26301
+ }
24227
26302
  var init_scorecard_command = __esm(() => {
24228
26303
  init_package();
24229
26304
  init_config_loader();
@@ -24235,6 +26310,9 @@ var init_scorecard_command = __esm(() => {
24235
26310
  init_regression_checklist();
24236
26311
  init_model_comparison();
24237
26312
  init_cost_quality_matrix();
26313
+ init_harness_catalog();
26314
+ init_harness_experiment();
26315
+ init_ablation_report();
24238
26316
  init_backlog_parser();
24239
26317
  });
24240
26318
 
@@ -25538,7 +27616,7 @@ var exports_seo_audit_command = {};
25538
27616
  __export(exports_seo_audit_command, {
25539
27617
  seoAuditCommand: () => seoAuditCommand
25540
27618
  });
25541
- import { join as join20 } from "node:path";
27619
+ import { join as join22 } from "node:path";
25542
27620
  async function seoAuditCommand(options) {
25543
27621
  const { url, sitemap, format, failOn, delay: delay2, output, followRedirects, force } = options;
25544
27622
  if (!url && !sitemap) {
@@ -25551,7 +27629,7 @@ async function seoAuditCommand(options) {
25551
27629
  }
25552
27630
  };
25553
27631
  }
25554
- const defaultMarkdownPath = output === undefined && format === "markdown" ? join20("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
27632
+ const defaultMarkdownPath = output === undefined && format === "markdown" ? join22("seo-reports", `audit-report-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19)}.md`) : undefined;
25555
27633
  const outputPathToWrite = output ?? defaultMarkdownPath;
25556
27634
  if (outputPathToWrite !== undefined) {
25557
27635
  const preflightError = await preflightContainedOutput(options.projectRoot, outputPathToWrite, { force });
@@ -25814,7 +27892,7 @@ var init_seo_llms_command = __esm(() => {
25814
27892
  init_llms_generator();
25815
27893
  });
25816
27894
 
25817
- // src/cli/main.ts
27895
+ // src/cli/execute.ts
25818
27896
  init_errors();
25819
27897
 
25820
27898
  // src/cli/router.ts
@@ -25940,9 +28018,10 @@ Published commands (package ${package_default.version}):
25940
28018
  ask Recommend a /cc: slash command from a natural-language problem
25941
28019
  cc-help Show preset inventory (skills, subagents, commands)
25942
28020
  debt-harvest / harvest Scan source files for deferred debt items
25943
- ccep Compile and evaluate CCEP workflow contracts
25944
- openspec OpenSpec loop: backlog + delivery (validate/scan/plan/status/next)
28021
+ ccep CCEP contracts: parse/profile/validate/evaluate/consensus/taskcard
28022
+ openspec OpenSpec loop: validate/scan/plan/status/next/start/done/block/archive
25945
28023
  scorecard Record and aggregate evaluation outcomes
28024
+ (catalog / fingerprint / experiment / ablation)
25946
28025
 
25947
28026
  v1.0.0 (in this repo, not in published ${package_default.version}):
25948
28027
  goal / cc-goal Plan goal into task graph with dependencies
@@ -25978,7 +28057,7 @@ Orchestration loops:
25978
28057
  src/core/ccep/). Prefer /cc-iterative, /cc-triage, and /cc-handoff;
25979
28058
  other CCEP commands are supporting profiles.
25980
28059
  OpenSpec is a delivery loop (CLI openspec + /cc-openspec) on top of
25981
- BACKLOG.md — not only a scanner.
28060
+ BACKLOG.md — not only a scanner. Author BACKLOG.md with /cc-backlog.
25982
28061
  runWorkflowPipeline() in src/core/pipeline/workflow-loop.ts is an
25983
28062
  experimental library-only 8-phase loop — not a shipped CLI runtime.
25984
28063
 
@@ -26020,14 +28099,23 @@ Examples:
26020
28099
  npx cc-codeconductor ccep profile council --output json
26021
28100
  npx cc-codeconductor ccep resolve --command feature "Add CRUD"
26022
28101
  npx cc-codeconductor ccep evaluate --command feature --input @planner.json
28102
+ npx cc-codeconductor ccep consensus --input @verdicts.json
28103
+ npx cc-codeconductor ccep taskcard --command feature --input @card.json
26023
28104
  npx cc-codeconductor openspec validate
26024
28105
  npx cc-codeconductor openspec scan
26025
28106
  npx cc-codeconductor openspec plan BC-001
26026
28107
  npx cc-codeconductor openspec status
26027
28108
  npx cc-codeconductor openspec next
28109
+ npx cc-codeconductor openspec start BC-001-discover
28110
+ npx cc-codeconductor openspec done BC-001-discover
28111
+ npx cc-codeconductor openspec block BC-001-implement --reason "waiting on design"
28112
+ npx cc-codeconductor openspec archive BC-001
26028
28113
  npx cc-codeconductor scorecard create --task BC-001 --from-diff
26029
28114
  npx cc-codeconductor scorecard models
26030
28115
  npx cc-codeconductor scorecard aggregate
28116
+ npx cc-codeconductor scorecard catalog
28117
+ npx cc-codeconductor scorecard experiment start --suite harness-v1 --components review
28118
+ npx cc-codeconductor scorecard ablation --experiment <id>
26031
28119
  npx cc-codeconductor help
26032
28120
  npx cc-codeconductor ask "login fails with 500"
26033
28121
  npx cc-codeconductor cc-help --target opencode
@@ -26288,7 +28376,7 @@ async function routeCommand(args, projectRoot) {
26288
28376
  });
26289
28377
  }
26290
28378
  case "ccep": {
26291
- const validSubs = ["parse", "profile", "resolve", "compile", "validate", "evaluate"];
28379
+ const validSubs = ["parse", "profile", "resolve", "compile", "validate", "evaluate", "consensus", "taskcard"];
26292
28380
  if (subcommand && !validSubs.includes(subcommand)) {
26293
28381
  return unknownSubcommand("ccep", subcommand, validSubs);
26294
28382
  }
@@ -26296,7 +28384,7 @@ async function routeCommand(args, projectRoot) {
26296
28384
  const workflowCommand = options.command || undefined;
26297
28385
  const requestParts = args.rest ?? [];
26298
28386
  const userRequest = ccepSub === "profile" && !workflowCommand ? requestParts[0] : ["compile", "resolve", "parse", "evaluate"].includes(ccepSub) ? requestParts.join(" ").trim() : requestParts.join(" ").trim();
26299
- const validateRest = (ccepSub === "validate" || ccepSub === "evaluate") && !options.input ? requestParts : undefined;
28387
+ const validateRest = (ccepSub === "validate" || ccepSub === "evaluate" || ccepSub === "consensus" || ccepSub === "taskcard") && !options.input ? requestParts : undefined;
26300
28388
  const { ccepCommand: ccepCommand2 } = await Promise.resolve().then(() => (init_ccep_command(), exports_ccep_command));
26301
28389
  return ccepCommand2({
26302
28390
  subcommand: ccepSub,
@@ -26309,12 +28397,23 @@ async function routeCommand(args, projectRoot) {
26309
28397
  input: options.input,
26310
28398
  contextPath: options.context || options.contextPath,
26311
28399
  promptVersion: options["prompt-version"] || options.promptVersion,
28400
+ config: options.config,
26312
28401
  rest: validateRest
26313
28402
  });
26314
28403
  }
26315
28404
  case "openspec":
26316
28405
  case "cc-openspec": {
26317
- const validSubs = ["validate", "scan", "plan", "status", "next"];
28406
+ const validSubs = [
28407
+ "validate",
28408
+ "scan",
28409
+ "plan",
28410
+ "status",
28411
+ "next",
28412
+ "start",
28413
+ "done",
28414
+ "block",
28415
+ "archive"
28416
+ ];
26318
28417
  let openspecSub = "validate";
26319
28418
  let itemId = options.item || undefined;
26320
28419
  if (subcommand && validSubs.includes(subcommand)) {
@@ -26330,6 +28429,7 @@ async function routeCommand(args, projectRoot) {
26330
28429
  return openspecCommand2({
26331
28430
  subcommand: openspecSub,
26332
28431
  itemId,
28432
+ reason: typeof options.reason === "string" ? options.reason : undefined,
26333
28433
  projectRoot,
26334
28434
  output: flags.output
26335
28435
  });
@@ -26346,7 +28446,11 @@ async function routeCommand(args, projectRoot) {
26346
28446
  "prompt-diff",
26347
28447
  "regression",
26348
28448
  "matrix",
26349
- "compare-models"
28449
+ "compare-models",
28450
+ "catalog",
28451
+ "fingerprint",
28452
+ "experiment",
28453
+ "ablation"
26350
28454
  ];
26351
28455
  if (subcommand && !validSubs.includes(subcommand)) {
26352
28456
  return unknownSubcommand(command, subcommand, validSubs);
@@ -26361,7 +28465,7 @@ async function routeCommand(args, projectRoot) {
26361
28465
  subcommand: scorecardSub,
26362
28466
  projectRoot,
26363
28467
  output: flags.output,
26364
- id: scorecardSub === "show" ? args.rest?.[0] ?? options.id : options.id,
28468
+ id: scorecardSub === "show" || scorecardSub === "ablation" ? args.rest?.[0] ?? options.id : options.id,
26365
28469
  taskId: options.task ?? options.item,
26366
28470
  agent: options.agent,
26367
28471
  model: options.model,
@@ -26376,7 +28480,14 @@ async function routeCommand(args, projectRoot) {
26376
28480
  weightedScore: options.score ? parseFloat(String(options.score)) : undefined,
26377
28481
  source: options.source,
26378
28482
  since: options.since,
26379
- modelsFilter
28483
+ modelsFilter,
28484
+ experimentId: options.experiment ?? (scorecardSub === "experiment" && args.rest?.[1] ? args.rest[1] : undefined) ?? (scorecardSub === "ablation" ? args.rest?.[0] : undefined),
28485
+ variantId: options.variant,
28486
+ suiteTaskId: options["suite-task"] ?? options.suiteTask,
28487
+ suiteId: options.suite,
28488
+ suitePath: options["suite-path"] ?? options.suitePath,
28489
+ components: options.components,
28490
+ experimentAction: scorecardSub === "experiment" ? args.rest?.[0] : undefined
26380
28491
  });
26381
28492
  }
26382
28493
  case "seo": {
@@ -26430,107 +28541,120 @@ async function routeCommand(args, projectRoot) {
26430
28541
  }
26431
28542
  }
26432
28543
 
26433
- // src/cli/main.ts
26434
- async function runCli(args) {
26435
- const parsed = parseArgs(args.slice(2));
28544
+ // src/cli/execute.ts
28545
+ async function executeCli(args, projectRoot) {
28546
+ const parsed = parseArgs(args);
26436
28547
  if (parsed.flags.version) {
26437
28548
  console.log(getVersion());
26438
- process.exit(0);
28549
+ return 0;
26439
28550
  }
26440
28551
  if (parsed.flags.help || !parsed.command || parsed.command === "help") {
26441
28552
  console.log(getHelp());
26442
- process.exit(0);
28553
+ return 0;
26443
28554
  }
26444
- const projectRoot = process.cwd();
26445
28555
  try {
26446
28556
  const result = await routeCommand(parsed, projectRoot);
26447
- if (parsed.flags.output === "json") {
26448
- console.log(JSON.stringify(result.data, null, 2));
26449
- } else {
26450
- if (result.data) {
26451
- const data = result.data;
26452
- if ("help" in data) {
26453
- console.log(data.help);
26454
- } else if ("message" in data && typeof data.message === "string") {
26455
- console.log(data.message);
26456
- } else {
26457
- if ("errors" in data && Array.isArray(data.errors) && data.errors.length > 0) {
26458
- data.errors.forEach((e) => console.error(e));
26459
- }
26460
- if ("checks" in data) {
26461
- const checks = data.checks;
26462
- checks.forEach((c) => {
26463
- const icon = c.status === "pass" ? "✓" : c.status === "warn" ? "⚠" : "✗";
26464
- console.log(`${icon} ${c.name}: ${c.message}`);
26465
- });
26466
- } else if ("fileResults" in data) {
26467
- const fileResults = data.fileResults;
26468
- const actionIcon = {
26469
- written: "✓",
26470
- appended: "→",
26471
- merged: "~",
26472
- skipped: "∅",
26473
- error: "✗"
26474
- };
26475
- fileResults.forEach((r) => {
26476
- if (r.action === "skipped")
26477
- return;
26478
- const icon = actionIcon[r.action] ?? "?";
26479
- const prefix = r.dryRun ? "[dry-run] " : "";
26480
- const suffix = r.error ? `: ${r.error}` : "";
26481
- console.log(`${icon} ${prefix}${r.dest}${suffix}`);
26482
- });
26483
- const acted = fileResults.filter((r) => r.action !== "skipped");
26484
- const errCount = acted.filter((r) => r.action === "error").length;
26485
- const count = acted.length - errCount;
26486
- const note = data.dryRun ? " (dry-run)" : "";
26487
- console.log(`
28557
+ emitCliResult(parsed, result);
28558
+ return result.code;
28559
+ } catch (error) {
28560
+ emitCliError(parsed, error);
28561
+ return getExitCode(error);
28562
+ }
28563
+ }
28564
+ function emitCliResult(parsed, result) {
28565
+ if (parsed.flags.output === "json") {
28566
+ console.log(JSON.stringify(result.data, null, 2));
28567
+ return;
28568
+ }
28569
+ if (!result.data) {
28570
+ return;
28571
+ }
28572
+ const data = result.data;
28573
+ if ("help" in data) {
28574
+ console.log(data.help);
28575
+ return;
28576
+ }
28577
+ if ("message" in data && typeof data.message === "string") {
28578
+ console.log(data.message);
28579
+ return;
28580
+ }
28581
+ if ("errors" in data && Array.isArray(data.errors) && data.errors.length > 0) {
28582
+ data.errors.forEach((e) => console.error(e));
28583
+ }
28584
+ if ("checks" in data) {
28585
+ const checks = data.checks;
28586
+ checks.forEach((c) => {
28587
+ const icon = c.status === "pass" ? "✓" : c.status === "warn" ? "⚠" : "✗";
28588
+ console.log(`${icon} ${c.name}: ${c.message}`);
28589
+ });
28590
+ } else if ("fileResults" in data) {
28591
+ const fileResults = data.fileResults;
28592
+ const actionIcon = {
28593
+ written: "",
28594
+ appended: "",
28595
+ merged: "~",
28596
+ skipped: "",
28597
+ error: "✗"
28598
+ };
28599
+ fileResults.forEach((r) => {
28600
+ if (r.action === "skipped")
28601
+ return;
28602
+ const icon = actionIcon[r.action] ?? "?";
28603
+ const prefix = r.dryRun ? "[dry-run] " : "";
28604
+ const suffix = r.error ? `: ${r.error}` : "";
28605
+ console.log(`${icon} ${prefix}${r.dest}${suffix}`);
28606
+ });
28607
+ const acted = fileResults.filter((r) => r.action !== "skipped");
28608
+ const errCount = acted.filter((r) => r.action === "error").length;
28609
+ const count = acted.length - errCount;
28610
+ const note = data.dryRun ? " (dry-run)" : "";
28611
+ console.log(`
26488
28612
  ${count} files processed${errCount > 0 ? `, ${errCount} errors` : ""}${note}`);
26489
- } else if ("written" in data) {
26490
- const written = data.written;
26491
- if (written.length > 0) {
26492
- if ("targets" in data && Array.isArray(data.targets) && data.targets.length > 0) {
26493
- console.log(`Installed to: ${data.targets.join(", ")} (${written.length} files)`);
26494
- } else {
26495
- console.log(`Written ${written.length} files`);
26496
- }
26497
- }
26498
- } else if ("wouldCreate" in data) {
26499
- const items = data.wouldCreate;
26500
- console.log(`Would create ${items.length} files:`);
26501
- items.forEach((f) => console.log(` + ${f}`));
26502
- } else if ("created" in data) {
26503
- console.log(`Created ${data.created.length} files`);
26504
- } else if ("detected" in data) {
26505
- console.log("Detected:");
26506
- const detected = data.detected;
26507
- Object.entries(detected).forEach(([key, value]) => {
26508
- if (Array.isArray(value) && value.length > 0) {
26509
- console.log(` - ${key}: ${value.join(", ")}`);
26510
- } else if (typeof value === "string" && value.length > 0) {
26511
- console.log(` - ${key}: ${value}`);
26512
- }
26513
- });
26514
- } else if ("output" in data && typeof data.output === "string") {
26515
- console.log(data.output);
26516
- }
26517
- }
28613
+ } else if ("written" in data) {
28614
+ const written = data.written;
28615
+ if (written.length > 0) {
28616
+ if ("targets" in data && Array.isArray(data.targets) && data.targets.length > 0) {
28617
+ console.log(`Installed to: ${data.targets.join(", ")} (${written.length} files)`);
28618
+ } else {
28619
+ console.log(`Written ${written.length} files`);
28620
+ }
28621
+ }
28622
+ } else if ("wouldCreate" in data) {
28623
+ const items = data.wouldCreate;
28624
+ console.log(`Would create ${items.length} files:`);
28625
+ items.forEach((f) => console.log(` + ${f}`));
28626
+ } else if ("created" in data) {
28627
+ console.log(`Created ${data.created.length} files`);
28628
+ } else if ("detected" in data) {
28629
+ console.log("Detected:");
28630
+ const detected = data.detected;
28631
+ Object.entries(detected).forEach(([key, value]) => {
28632
+ if (Array.isArray(value) && value.length > 0) {
28633
+ console.log(` - ${key}: ${value.join(", ")}`);
28634
+ } else if (typeof value === "string" && value.length > 0) {
28635
+ console.log(` - ${key}: ${value}`);
26518
28636
  }
26519
- }
26520
- process.exit(result.code);
26521
- } catch (error) {
26522
- const exitCode = getExitCode(error);
26523
- if (parsed.flags.output === "json") {
26524
- console.log(JSON.stringify({
26525
- success: false,
26526
- errors: [String(error)]
26527
- }, null, 2));
26528
- } else {
26529
- console.error(String(error));
26530
- }
26531
- process.exit(exitCode);
28637
+ });
28638
+ } else if ("output" in data && typeof data.output === "string") {
28639
+ console.log(data.output);
26532
28640
  }
26533
28641
  }
28642
+ function emitCliError(parsed, error) {
28643
+ if (parsed.flags.output === "json") {
28644
+ console.log(JSON.stringify({
28645
+ success: false,
28646
+ errors: [String(error)]
28647
+ }, null, 2));
28648
+ return;
28649
+ }
28650
+ console.error(String(error));
28651
+ }
28652
+
28653
+ // src/cli/main.ts
28654
+ async function runCli(args) {
28655
+ const code = await executeCli(args.slice(2), process.cwd());
28656
+ process.exit(code);
28657
+ }
26534
28658
  runCli(process.argv);
26535
28659
  export {
26536
28660
  runCli