cc-codeconductor 0.4.2 → 0.5.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 (172) hide show
  1. package/README.md +75 -4
  2. package/dist/index.js +2698 -230
  3. package/package.json +1 -1
  4. package/presets/agy/skills/evaluation/SKILL.md +6 -0
  5. package/presets/agy/skills/openspec/SKILL.md +32 -0
  6. package/presets/agy/workflows/cc-openspec.md +62 -0
  7. package/presets/agy/workflows/cc-pipeline.md +69 -0
  8. package/presets/agy/workflows/cc-scorecard.md +17 -0
  9. package/presets/claude/commands/cc/openspec.md +144 -0
  10. package/presets/claude/commands/cc/review.md +13 -2
  11. package/presets/claude/commands/cc/scorecard.md +65 -0
  12. package/presets/claude/skills/evaluation/SKILL.md +42 -0
  13. package/presets/claude/skills/openspec/SKILL.md +54 -0
  14. package/presets/codex/AGENTS.md +57 -0
  15. package/presets/cursor/.cursorignore +15 -0
  16. package/presets/cursor/AGENTS.md +504 -0
  17. package/presets/cursor/agents/architect.md +211 -0
  18. package/presets/cursor/agents/complexity-auditor.md +76 -0
  19. package/presets/cursor/agents/contract-builder.md +75 -0
  20. package/presets/cursor/agents/docs.md +180 -0
  21. package/presets/cursor/agents/goal-planner.md +71 -0
  22. package/presets/cursor/agents/implementer.md +161 -0
  23. package/presets/cursor/agents/orchestrator.md +377 -0
  24. package/presets/cursor/agents/repo-explorer.md +100 -0
  25. package/presets/cursor/agents/reviewer.md +237 -0
  26. package/presets/cursor/agents/security-reviewer.md +113 -0
  27. package/presets/cursor/agents/task-coach.md +145 -0
  28. package/presets/cursor/agents/tester.md +241 -0
  29. package/presets/cursor/commands/cc/api-contract.md +58 -0
  30. package/presets/cursor/commands/cc/db-migration.md +58 -0
  31. package/presets/cursor/commands/cc/feature.md +115 -0
  32. package/presets/cursor/commands/cc/fix.md +121 -0
  33. package/presets/cursor/commands/cc/openspec.md +144 -0
  34. package/presets/cursor/commands/cc/pagespeed.md +103 -0
  35. package/presets/cursor/commands/cc/refactor.md +148 -0
  36. package/presets/cursor/commands/cc/review.md +137 -0
  37. package/presets/cursor/commands/cc/scorecard.md +65 -0
  38. package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
  39. package/presets/cursor/commands/cc/test-plan.md +138 -0
  40. package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
  41. package/presets/cursor/rules/context-budget.mdc +12 -0
  42. package/presets/cursor/rules/orchestration.mdc +12 -0
  43. package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
  44. package/presets/cursor/skills/android/SKILL.md +122 -0
  45. package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
  46. package/presets/cursor/skills/astro/SKILL.md +322 -0
  47. package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
  48. package/presets/cursor/skills/code-review/SKILL.md +208 -0
  49. package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
  50. package/presets/cursor/skills/django-orm/SKILL.md +463 -0
  51. package/presets/cursor/skills/django-testing/SKILL.md +417 -0
  52. package/presets/cursor/skills/django-uv/SKILL.md +409 -0
  53. package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
  54. package/presets/cursor/skills/evaluation/SKILL.md +8 -0
  55. package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
  56. package/presets/cursor/skills/find-skills/SKILL.md +144 -0
  57. package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
  58. package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
  59. package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
  60. package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
  61. package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
  62. package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
  63. package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
  64. package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
  65. package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
  66. package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
  67. package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
  68. package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
  69. package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
  70. package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
  71. package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
  72. package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
  73. package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
  74. package/presets/cursor/skills/openspec/SKILL.md +52 -0
  75. package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
  76. package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
  77. package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
  78. package/presets/cursor/skills/php-pro/SKILL.md +210 -0
  79. package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
  80. package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
  81. package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
  82. package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
  83. package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
  84. package/presets/cursor/skills/python/SKILL.md +613 -0
  85. package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
  86. package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
  87. package/presets/cursor/skills/security/SKILL.md +384 -0
  88. package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
  89. package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
  90. package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
  91. package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
  92. package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
  93. package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
  94. package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
  95. package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
  96. package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
  97. package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
  98. package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
  99. package/presets/laravel-tall/agents/architect.md +8 -0
  100. package/presets/laravel-tall/agents/implementer.md +12 -0
  101. package/presets/laravel-tall/laravel-tall.yml +38 -0
  102. package/presets/opencode/agents/architect.md +153 -61
  103. package/presets/opencode/agents/complexity-auditor.md +1 -0
  104. package/presets/opencode/agents/contract-builder.md +93 -0
  105. package/presets/opencode/agents/docs.md +125 -40
  106. package/presets/opencode/agents/goal-planner.md +82 -0
  107. package/presets/opencode/agents/implementer.md +107 -38
  108. package/presets/opencode/agents/orchestrator.md +90 -71
  109. package/presets/opencode/agents/repo-explorer.md +1 -2
  110. package/presets/opencode/agents/reviewer.md +164 -75
  111. package/presets/opencode/agents/security-reviewer.md +129 -0
  112. package/presets/opencode/agents/task-coach.md +110 -59
  113. package/presets/opencode/agents/tester.md +1 -2
  114. package/presets/opencode/commands/cc-openspec.md +61 -0
  115. package/presets/opencode/commands/cc-scorecard.md +16 -0
  116. package/presets/opencode/prompts/v0.4.0/architect.md +221 -0
  117. package/presets/opencode/prompts/v0.4.0/complexity-auditor.md +89 -0
  118. package/presets/opencode/prompts/v0.4.0/docs.md +189 -0
  119. package/presets/opencode/prompts/v0.4.0/implementer.md +162 -0
  120. package/presets/opencode/prompts/v0.4.0/orchestrator.md +348 -0
  121. package/presets/opencode/prompts/v0.4.0/repo-explorer.md +110 -0
  122. package/presets/opencode/prompts/v0.4.0/reviewer.md +225 -0
  123. package/presets/opencode/prompts/v0.4.0/task-coach.md +155 -0
  124. package/presets/opencode/prompts/v0.4.0/tester.md +251 -0
  125. package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
  126. package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
  127. package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
  128. package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
  129. package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
  130. package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
  131. package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
  132. package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
  133. package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
  134. package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
  135. package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
  136. package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
  137. package/presets/opencode/skills/auth-token-inspector/SKILL.md +31 -0
  138. package/presets/opencode/skills/drizzle-schema-architect/SKILL.md +51 -0
  139. package/presets/opencode/skills/evaluation/SKILL.md +6 -0
  140. package/presets/opencode/skills/fastapi-pydantic-strict/SKILL.md +44 -0
  141. package/presets/opencode/skills/jpa-nplusone-detector/SKILL.md +46 -0
  142. package/presets/opencode/skills/livewire-alpine-bridge/SKILL.md +36 -0
  143. package/presets/opencode/skills/openspec/SKILL.md +50 -0
  144. package/presets/opencode/skills/seo-analytics-injector/SKILL.md +44 -0
  145. package/presets/opencode/skills/spring-auth-auditor/SKILL.md +30 -0
  146. package/presets/opencode/skills/tailwind-responsive-auditor/SKILL.md +30 -0
  147. package/presets/opencode/skills/tdd-mutation-tester/SKILL.md +28 -0
  148. package/presets/python-data-api/agents/architect.md +8 -0
  149. package/presets/python-data-api/agents/implementer.md +9 -0
  150. package/presets/python-data-api/python-data-api.yml +37 -0
  151. package/presets/spring-kotlin-jpa/agents/architect.md +8 -0
  152. package/presets/spring-kotlin-jpa/agents/implementer.md +9 -0
  153. package/presets/spring-kotlin-jpa/spring-kotlin-jpa.yml +38 -0
  154. package/presets/templates/BACKLOG.md +33 -0
  155. package/presets/templates/execution-profile.yml +6 -0
  156. package/presets/templates/model-comparison.md +11 -0
  157. package/presets/templates/regression-checklist.yml +10 -0
  158. package/presets/ts-next-drizzle/agents/architect.md +8 -0
  159. package/presets/ts-next-drizzle/agents/implementer.md +10 -0
  160. package/presets/ts-next-drizzle/ts-next-drizzle.yml +41 -0
  161. package/src/presets/manifests/agy.yml +2 -2
  162. package/src/presets/manifests/claude.yml +2 -2
  163. package/src/presets/manifests/codex.yml +2 -2
  164. package/src/presets/manifests/cursor.yml +19 -3
  165. package/src/presets/manifests/gemini.yml +2 -2
  166. package/src/presets/manifests/opencode.yml +2 -2
  167. package/src/presets/models/agy.yml +21 -0
  168. package/src/presets/models/claude.yml +18 -0
  169. package/src/presets/models/codex.yml +18 -0
  170. package/src/presets/models/cursor.yml +39 -9
  171. package/src/presets/models/gemini.yml +18 -0
  172. package/src/presets/models/opencode.yml +18 -0
package/dist/index.js CHANGED
@@ -11126,7 +11126,7 @@ function validateCouncilSpec(data) {
11126
11126
  function validateConfig(data) {
11127
11127
  return CodeConductorConfigSchema.parse(data);
11128
11128
  }
11129
- 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, GoalTaskSchema, GoalGraphSchema;
11129
+ 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, BacklogStatusSchema, BacklogTypeSchema, BacklogPrioritySchema, BacklogGlobalSchema, BacklogItemSchema, BacklogDocumentSchema, OpenspecTaskCardPhaseSchema, OpenspecTaskCardStatusSchema, OpenspecTaskCardSchema, OpenspecStateSchema, ScorecardVerdictSchema, ScorecardCriterionIdSchema, ScorecardCriterionSchema, ScorecardRecordSchema, TaskOutcomeSourceSchema, TaskOutcomeStatusSchema, TaskOutcomeSchema, EvaluationIndexSchema, ExecutionProfileNameSchema, ExecutionProfileSchema;
11130
11130
  var init_schemas = __esm(() => {
11131
11131
  init_zod();
11132
11132
  CouncilAgentSpecSchema = exports_external.object({
@@ -11234,7 +11234,8 @@ var init_schemas = __esm(() => {
11234
11234
  codex: exports_external.string().optional(),
11235
11235
  gemini: exports_external.string().optional(),
11236
11236
  cursor: exports_external.string().optional(),
11237
- agy: exports_external.string().optional()
11237
+ agy: exports_external.string().optional(),
11238
+ grok: exports_external.string().optional()
11238
11239
  })),
11239
11240
  tools: exports_external.record(exports_external.string(), ToolProviderNamesSchema).optional(),
11240
11241
  permissions: PermissionProviderNamesSchema.optional()
@@ -11313,6 +11314,18 @@ var init_schemas = __esm(() => {
11313
11314
  environment: exports_external.string().optional(),
11314
11315
  release: exports_external.string().optional()
11315
11316
  });
11317
+ MemoryPointerSchema = exports_external.object({
11318
+ topic_key: exports_external.string().min(1).max(128),
11319
+ id: exports_external.number().int().positive(),
11320
+ file: exports_external.string().min(1),
11321
+ summary: exports_external.string().min(1).max(200),
11322
+ timestamp: exports_external.string().datetime(),
11323
+ tags: exports_external.array(exports_external.string().max(64)).max(10).optional().default([])
11324
+ });
11325
+ MemoryIndexSchema = exports_external.object({
11326
+ version: exports_external.literal(1),
11327
+ pointers: exports_external.array(MemoryPointerSchema)
11328
+ });
11316
11329
  GoalTaskSchema = exports_external.object({
11317
11330
  id: exports_external.string(),
11318
11331
  title: exports_external.string(),
@@ -11329,6 +11342,153 @@ var init_schemas = __esm(() => {
11329
11342
  tasks: exports_external.array(GoalTaskSchema),
11330
11343
  created_at: exports_external.string()
11331
11344
  });
11345
+ BacklogStatusSchema = exports_external.enum([
11346
+ "TODO",
11347
+ "READY",
11348
+ "PLANNED",
11349
+ "IN_PROGRESS",
11350
+ "BLOCKED",
11351
+ "REVIEW",
11352
+ "DONE"
11353
+ ]);
11354
+ BacklogTypeSchema = exports_external.enum(["feature", "bug", "refactor", "tech-debt"]);
11355
+ BacklogPrioritySchema = exports_external.enum(["P0", "P1", "P2", "P3"]);
11356
+ BacklogGlobalSchema = exports_external.object({
11357
+ product: exports_external.string().min(1),
11358
+ strategy: exports_external.string().min(1),
11359
+ policy: exports_external.string().min(1),
11360
+ reviewRequired: exports_external.boolean(),
11361
+ tddRequired: exports_external.boolean()
11362
+ });
11363
+ BacklogItemSchema = exports_external.object({
11364
+ id: exports_external.string().regex(/^BC-\d{3,}$/),
11365
+ title: exports_external.string().min(1),
11366
+ priority: BacklogPrioritySchema,
11367
+ status: BacklogStatusSchema,
11368
+ type: BacklogTypeSchema,
11369
+ owner: exports_external.string().optional(),
11370
+ dependencies: exports_external.array(exports_external.string()).default([]),
11371
+ description: exports_external.string().min(1),
11372
+ scope: exports_external.string().min(1),
11373
+ outOfScope: exports_external.string().default(""),
11374
+ businessValue: exports_external.string().optional(),
11375
+ acceptanceCriteria: exports_external.array(exports_external.string().min(1)).min(1),
11376
+ risks: exports_external.string().optional(),
11377
+ progress: exports_external.number().int().min(0).max(100),
11378
+ branch: exports_external.string().optional(),
11379
+ reviewer: exports_external.string().optional(),
11380
+ lastUpdate: exports_external.string().optional()
11381
+ });
11382
+ BacklogDocumentSchema = exports_external.object({
11383
+ global: BacklogGlobalSchema,
11384
+ items: exports_external.array(BacklogItemSchema),
11385
+ archive: exports_external.array(BacklogItemSchema).default([])
11386
+ });
11387
+ OpenspecTaskCardPhaseSchema = exports_external.enum([
11388
+ "discover",
11389
+ "design",
11390
+ "implement",
11391
+ "test",
11392
+ "review"
11393
+ ]);
11394
+ OpenspecTaskCardStatusSchema = exports_external.enum(["pending", "doing", "blocked", "done"]);
11395
+ OpenspecTaskCardSchema = exports_external.object({
11396
+ id: exports_external.string(),
11397
+ backlogId: exports_external.string(),
11398
+ phase: OpenspecTaskCardPhaseSchema,
11399
+ title: exports_external.string(),
11400
+ prompt: exports_external.string(),
11401
+ agent: exports_external.string(),
11402
+ modelHint: exports_external.string().optional(),
11403
+ dependsOn: exports_external.array(exports_external.string()).default([]),
11404
+ acceptanceCriteria: exports_external.array(exports_external.string()),
11405
+ status: OpenspecTaskCardStatusSchema
11406
+ });
11407
+ OpenspecStateSchema = exports_external.object({
11408
+ version: exports_external.literal(1),
11409
+ activeItemId: exports_external.string().optional(),
11410
+ taskCards: exports_external.array(OpenspecTaskCardSchema).default([]),
11411
+ lastScanHash: exports_external.string().optional(),
11412
+ lastScanAt: exports_external.string().optional(),
11413
+ changePaths: exports_external.record(exports_external.string(), exports_external.string()).optional().default({}),
11414
+ itemSnapshots: exports_external.record(exports_external.string(), exports_external.string()).optional().default({})
11415
+ });
11416
+ ScorecardVerdictSchema = exports_external.enum(["PASS", "REVISE", "REJECT"]);
11417
+ ScorecardCriterionIdSchema = exports_external.enum([
11418
+ "acceptance",
11419
+ "minimal_diff",
11420
+ "tests",
11421
+ "regressions",
11422
+ "conventions",
11423
+ "documentation",
11424
+ "context_discipline",
11425
+ "cc_gain"
11426
+ ]);
11427
+ ScorecardCriterionSchema = exports_external.object({
11428
+ id: ScorecardCriterionIdSchema,
11429
+ label: exports_external.string(),
11430
+ weight: exports_external.number().min(0).max(1),
11431
+ score: exports_external.number().int().min(0).max(3),
11432
+ notes: exports_external.string().optional(),
11433
+ autoSuggested: exports_external.boolean().optional()
11434
+ });
11435
+ ScorecardRecordSchema = exports_external.object({
11436
+ id: exports_external.string(),
11437
+ taskId: exports_external.string(),
11438
+ agent: exports_external.string(),
11439
+ model: exports_external.string().optional(),
11440
+ contractVersion: exports_external.string(),
11441
+ evaluator: exports_external.string().optional(),
11442
+ criteria: exports_external.array(ScorecardCriterionSchema),
11443
+ weightedScore: exports_external.number(),
11444
+ verdict: ScorecardVerdictSchema,
11445
+ findings: exports_external.array(exports_external.string()).optional().default([]),
11446
+ createdAt: exports_external.string(),
11447
+ backlogId: exports_external.string().optional(),
11448
+ source: exports_external.enum(["openspec", "review", "manual", "pipeline"]).optional()
11449
+ });
11450
+ TaskOutcomeSourceSchema = exports_external.enum(["openspec", "review", "manual", "pipeline"]);
11451
+ TaskOutcomeStatusSchema = exports_external.enum([
11452
+ "phase_done",
11453
+ "phase_failed",
11454
+ "pass",
11455
+ "revise",
11456
+ "reject"
11457
+ ]);
11458
+ TaskOutcomeSchema = exports_external.object({
11459
+ id: exports_external.string(),
11460
+ taskId: exports_external.string(),
11461
+ source: TaskOutcomeSourceSchema,
11462
+ agent: exports_external.string(),
11463
+ model: exports_external.string(),
11464
+ contractVersion: exports_external.string(),
11465
+ timestamp: exports_external.string(),
11466
+ status: TaskOutcomeStatusSchema.optional(),
11467
+ verdict: ScorecardVerdictSchema.optional(),
11468
+ weightedScore: exports_external.number().optional(),
11469
+ taskCardId: exports_external.string().optional(),
11470
+ backlogId: exports_external.string().optional(),
11471
+ phase: exports_external.string().optional(),
11472
+ scorecardId: exports_external.string().optional(),
11473
+ tokensIn: exports_external.number().optional(),
11474
+ tokensOut: exports_external.number().optional(),
11475
+ costUsd: exports_external.number().optional(),
11476
+ durationMs: exports_external.number().optional()
11477
+ });
11478
+ EvaluationIndexSchema = exports_external.object({
11479
+ version: exports_external.literal(1),
11480
+ lastOutcomeId: exports_external.string().optional()
11481
+ });
11482
+ ExecutionProfileNameSchema = exports_external.enum(["balanced", "quality", "economical"]);
11483
+ ExecutionProfileSchema = exports_external.object({
11484
+ profile: ExecutionProfileNameSchema.default("balanced"),
11485
+ target: exports_external.enum(["opencode", "claude", "codex", "gemini", "cursor", "agy"]).optional(),
11486
+ overrides: exports_external.record(exports_external.string(), exports_external.string()).optional().default({}),
11487
+ subagentPolicy: exports_external.object({
11488
+ orchestrator: exports_external.enum(["primary", "delegate"]).optional(),
11489
+ testerReviewer: exports_external.enum(["primary", "delegate"]).optional()
11490
+ }).optional()
11491
+ });
11332
11492
  });
11333
11493
 
11334
11494
  // src/core/presets/package-paths.ts
@@ -12154,7 +12314,7 @@ init_errors();
12154
12314
  // package.json
12155
12315
  var package_default = {
12156
12316
  name: "cc-codeconductor",
12157
- version: "0.4.2",
12317
+ version: "0.5.0",
12158
12318
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
12159
12319
  keywords: [
12160
12320
  "ai",
@@ -12986,7 +13146,11 @@ function extractAgentRole(filePath) {
12986
13146
  "reviewer",
12987
13147
  "docs",
12988
13148
  "task-coach",
12989
- "repo-explorer"
13149
+ "repo-explorer",
13150
+ "complexity-auditor",
13151
+ "security-reviewer",
13152
+ "goal-planner",
13153
+ "contract-builder"
12990
13154
  ];
12991
13155
  return knownRoles.includes(name) ? name : null;
12992
13156
  }
@@ -13062,7 +13226,7 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13062
13226
  if (agentRole && modelConfig.agents[agentRole]) {
13063
13227
  const agentModels = modelConfig.agents[agentRole];
13064
13228
  const targetModel = agentModels[modelConfig.target];
13065
- let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
13229
+ let result2 = templatedContent.replace(/\{\{MODEL\}\}/g, targetModel ?? "").replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(/\{\{MODEL_GROK\}\}/g, agentModels.grok ?? "").replace(new RegExp(LOCALE_PLACEHOLDER.replace(/[{}]/g, "\\$&"), "g"), getLanguageInstruction(locale));
13066
13230
  if (modelConfig.tools || modelConfig.permissions) {
13067
13231
  result2 = substituteToolNames(result2, modelConfig);
13068
13232
  }
@@ -13077,7 +13241,10 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13077
13241
  "reviewer",
13078
13242
  "docs",
13079
13243
  "repo-explorer",
13080
- "goal-planner"
13244
+ "goal-planner",
13245
+ "complexity-auditor",
13246
+ "security-reviewer",
13247
+ "contract-builder"
13081
13248
  ];
13082
13249
  let result = templatedContent;
13083
13250
  for (const role of knownRoles) {
@@ -13088,7 +13255,7 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13088
13255
  const sectionMatch = result.match(sectionRegex);
13089
13256
  if (sectionMatch) {
13090
13257
  for (const section of sectionMatch) {
13091
- const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "");
13258
+ const renderedSection = section.replace(/\{\{MODEL_CLAUDE\}\}/g, agentModels.claude ?? "").replace(/\{\{MODEL_OPENCODE\}\}/g, agentModels.opencode ?? "").replace(/\{\{MODEL_CODEX\}\}/g, agentModels.codex ?? "").replace(/\{\{MODEL_GEMINI\}\}/g, agentModels.gemini ?? "").replace(/\{\{MODEL_CURSOR\}\}/g, agentModels.cursor ?? "").replace(/\{\{MODEL_GROK\}\}/g, agentModels.grok ?? "");
13092
13259
  result = result.replace(section, renderedSection);
13093
13260
  }
13094
13261
  }
@@ -13920,7 +14087,7 @@ async function writeConfig(projectRoot, config = {}, force = false) {
13920
14087
  init_package_paths();
13921
14088
 
13922
14089
  // src/core/presets/preset-resolver.ts
13923
- var CURRENT_PRESET_VERSION = "v0.3.0";
14090
+ var CURRENT_PRESET_VERSION = "v0.5.0";
13924
14091
  function resolvePreset(target, profile) {
13925
14092
  const stack = resolveStack(profile);
13926
14093
  const isMonorepo = profile.signals.some((s) => [
@@ -13983,17 +14150,17 @@ function resolveStack(profile) {
13983
14150
  function resolveAssets(target) {
13984
14151
  switch (target) {
13985
14152
  case "opencode":
13986
- return ["opencode.jsonc", "agents", "commands", "prompts/v0.3.0", "skills"];
14153
+ return ["opencode.jsonc", "agents", "commands", "prompts/v0.5.0", "skills"];
13987
14154
  case "claude":
13988
- return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.3.0"];
14155
+ return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.5.0"];
13989
14156
  case "codex":
13990
- return ["AGENTS.md", "skills", "prompts/v0.3.0"];
14157
+ return ["AGENTS.md", "skills", "prompts/v0.5.0"];
13991
14158
  case "gemini":
13992
- return ["agents", "prompts/v0.3.0"];
14159
+ return ["agents", "prompts/v0.5.0"];
13993
14160
  case "cursor":
13994
- return ["agents", "prompts/v0.3.0"];
14161
+ return ["AGENTS.md", ".cursorignore", "rules", "commands", "skills", "agents", "prompts/v0.5.0"];
13995
14162
  case "agy":
13996
- return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.3.0"];
14163
+ return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.5.0"];
13997
14164
  }
13998
14165
  }
13999
14166
 
@@ -14076,12 +14243,14 @@ async function initCommand(options) {
14076
14243
  };
14077
14244
  }
14078
14245
  const copiedPresets = await copyPresets(baseDir, presetsToCopy, force);
14246
+ const openspecCreated = await initOpenspecArtifacts(baseDir, force);
14247
+ const evalCreated = await initEvaluationArtifacts(baseDir, force);
14079
14248
  return {
14080
14249
  code: 0,
14081
14250
  data: {
14082
14251
  success: true,
14083
14252
  command: "init",
14084
- created: [".codeconductor/config.yml", ...copiedPresets],
14253
+ created: [".codeconductor/config.yml", ...copiedPresets, ...openspecCreated, ...evalCreated],
14085
14254
  ...profile ? {
14086
14255
  detected: {
14087
14256
  languages: profile.languages,
@@ -14148,6 +14317,62 @@ async function fileExists2(path) {
14148
14317
  return false;
14149
14318
  }
14150
14319
  }
14320
+ async function initOpenspecArtifacts(baseDir, force) {
14321
+ if (baseDir === homedir5())
14322
+ return [];
14323
+ const created = [];
14324
+ const backlogDest = resolve7(baseDir, "BACKLOG.md");
14325
+ const backlogTemplate = resolve7(ROOT_PRESETS_DIR, "templates", "BACKLOG.md");
14326
+ if (force || !await fileExists2(backlogDest)) {
14327
+ if (await fileExists2(backlogTemplate)) {
14328
+ const content = await readFile8(backlogTemplate, "utf-8");
14329
+ await writeFile3(backlogDest, content, "utf-8");
14330
+ created.push("BACKLOG.md");
14331
+ }
14332
+ }
14333
+ const stateDir = resolve7(baseDir, ".codeconductor");
14334
+ await mkdir3(stateDir, { recursive: true });
14335
+ const stateDest = resolve7(stateDir, "openspec-state.json");
14336
+ if (force || !await fileExists2(stateDest)) {
14337
+ const initialState = JSON.stringify({
14338
+ version: 1,
14339
+ taskCards: [],
14340
+ changePaths: {},
14341
+ itemSnapshots: {}
14342
+ }, null, 2);
14343
+ await writeFile3(stateDest, initialState, "utf-8");
14344
+ created.push(".codeconductor/openspec-state.json");
14345
+ }
14346
+ return created;
14347
+ }
14348
+ async function initEvaluationArtifacts(baseDir, force) {
14349
+ if (baseDir === homedir5())
14350
+ return [];
14351
+ const created = [];
14352
+ const evalDir = resolve7(baseDir, ".codeconductor", "evaluation");
14353
+ await mkdir3(evalDir, { recursive: true });
14354
+ await mkdir3(resolve7(evalDir, "scorecards"), { recursive: true });
14355
+ const indexPath = resolve7(evalDir, "index.json");
14356
+ if (force || !await fileExists2(indexPath)) {
14357
+ await writeFile3(indexPath, JSON.stringify({ version: 1 }, null, 2), "utf-8");
14358
+ created.push(".codeconductor/evaluation/index.json");
14359
+ }
14360
+ const outcomesPath = resolve7(evalDir, "outcomes.jsonl");
14361
+ if (force || !await fileExists2(outcomesPath)) {
14362
+ await writeFile3(outcomesPath, "", "utf-8");
14363
+ created.push(".codeconductor/evaluation/outcomes.jsonl");
14364
+ }
14365
+ const profileDest = resolve7(evalDir, "execution-profile.yml");
14366
+ const profileTemplate = resolve7(ROOT_PRESETS_DIR, "templates", "execution-profile.yml");
14367
+ if (force || !await fileExists2(profileDest)) {
14368
+ if (await fileExists2(profileTemplate)) {
14369
+ const content = await readFile8(profileTemplate, "utf-8");
14370
+ await writeFile3(profileDest, content, "utf-8");
14371
+ created.push(".codeconductor/evaluation/execution-profile.yml");
14372
+ }
14373
+ }
14374
+ return created;
14375
+ }
14151
14376
 
14152
14377
  // src/commands/install.command.ts
14153
14378
  init_agy_installer();
@@ -15629,193 +15854,2320 @@ async function goalCommand(options) {
15629
15854
  }
15630
15855
  }
15631
15856
 
15632
- // src/commands/help.command.ts
15633
- import { readdir as readdir3 } from "node:fs/promises";
15634
- import { join as join8 } from "node:path";
15635
- async function scanPresetDir(projectRoot, target) {
15636
- const inventory = {
15637
- target,
15638
- skills: [],
15639
- commands: [],
15640
- agents: [],
15641
- workflows: []
15642
- };
15643
- const presetDir = join8(projectRoot, "presets", target);
15644
- try {
15645
- const entries = await readdir3(presetDir, { withFileTypes: true });
15646
- for (const entry of entries) {
15647
- if (entry.isDirectory()) {
15648
- const subDir = join8(presetDir, entry.name);
15649
- const subEntries = await readdir3(subDir, { withFileTypes: true });
15650
- for (const sub of subEntries) {
15651
- if (sub.isFile()) {
15652
- const itemName = sub.name.replace(/\.[^.]+$/, "");
15653
- if (entry.name === "skills")
15654
- inventory.skills.push(itemName);
15655
- else if (entry.name === "commands")
15656
- inventory.commands.push(itemName);
15657
- else if (entry.name === "agents")
15658
- inventory.agents.push(itemName);
15659
- else if (entry.name === "workflows")
15660
- inventory.workflows.push(itemName);
15661
- } else if (sub.isDirectory()) {
15662
- if (entry.name === "skills")
15663
- inventory.skills.push(sub.name);
15664
- else if (entry.name === "agents")
15665
- inventory.agents.push(sub.name);
15666
- }
15667
- }
15668
- }
15669
- }
15670
- } catch {}
15671
- return inventory;
15857
+ // src/commands/openspec.command.ts
15858
+ import { readFile as readFile14, writeFile as writeFile9 } from "node:fs/promises";
15859
+ import { resolve as resolve15 } from "node:path";
15860
+
15861
+ // src/core/openspec/backlog-parser.ts
15862
+ import { readFile as readFile11 } from "node:fs/promises";
15863
+ import { resolve as resolve11 } from "node:path";
15864
+ var BACKLOG_FILE = "BACKLOG.md";
15865
+ var ITEM_HEADER_RE = /^###\s+(?:\[[^\]]+\]\s*)*(BC-\d{3,})\s*(?:\||-)\s*(.+)$/i;
15866
+ var GLOBAL_SECTION_RE = /^##\s+Global\s*$/i;
15867
+ var ITEMS_SECTION_RE = /^##\s+Items\s*$/i;
15868
+ var ARCHIVE_SECTION_RE = /^##\s+Archive\s*$/i;
15869
+ var FIELD_RE = /^-\s+([^:]+):\s*(.*)$/;
15870
+ function parseBool(value) {
15871
+ const v = value.trim().toLowerCase();
15872
+ return v === "yes" || v === "true";
15672
15873
  }
15673
- function renderHuman(inventory, defaultTarget) {
15674
- const lines = [
15675
- `CodeConductor Help ${inventory.target}${inventory.target === defaultTarget ? " (active)" : ""}`,
15676
- ""
15677
- ];
15678
- lines.push(`Skills (${inventory.skills.length}):`);
15679
- if (inventory.skills.length === 0) {
15680
- lines.push(" (none)");
15681
- } else {
15682
- for (const skill of inventory.skills.sort()) {
15683
- lines.push(` - ${skill}`);
15684
- }
15685
- }
15686
- lines.push("");
15687
- lines.push(`Subagents (${inventory.agents.length}):`);
15688
- if (inventory.agents.length === 0) {
15689
- lines.push(" (none)");
15690
- } else {
15691
- for (const agent of inventory.agents.sort()) {
15692
- lines.push(` - ${agent}`);
15693
- }
15694
- }
15695
- lines.push("");
15696
- lines.push(`Commands (${inventory.commands.length}):`);
15697
- if (inventory.commands.length === 0) {
15698
- lines.push(" (none)");
15699
- } else {
15700
- for (const cmd of inventory.commands.sort()) {
15701
- lines.push(` - ${cmd}`);
15702
- }
15703
- }
15704
- if (inventory.workflows.length > 0) {
15705
- lines.push("");
15706
- lines.push(`Workflows (${inventory.workflows.length}):`);
15707
- for (const wf of inventory.workflows.sort()) {
15708
- lines.push(` - ${wf}`);
15874
+ function parseDependencies(value) {
15875
+ const v = value.trim().toLowerCase();
15876
+ if (!v || v === "none")
15877
+ return [];
15878
+ return value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
15879
+ }
15880
+ function parseProgress(value) {
15881
+ const match = value.trim().match(/^(\d+)%?$/);
15882
+ if (!match)
15883
+ return 0;
15884
+ const n = parseInt(match[1], 10);
15885
+ return Math.min(100, Math.max(0, n));
15886
+ }
15887
+ function parseAcceptanceLines(lines, startIndex) {
15888
+ const criteria = [];
15889
+ let i = startIndex;
15890
+ while (i < lines.length) {
15891
+ const line = lines[i];
15892
+ if (line.match(/^-\s+\w+:/) || line.match(/^###\s+/) || line.match(/^##\s+/))
15893
+ break;
15894
+ const checkbox = line.match(/^(\s*)-\s+\[[ xX]\]\s*(.+)$/);
15895
+ const bullet = line.match(/^(\s*)-\s+(.+)$/);
15896
+ if (checkbox) {
15897
+ criteria.push(checkbox[2].trim());
15898
+ i++;
15899
+ } else if (bullet && bullet[1].length > 0) {
15900
+ criteria.push(bullet[2].trim());
15901
+ i++;
15902
+ } else if (line.trim() === "") {
15903
+ i++;
15904
+ } else {
15905
+ break;
15709
15906
  }
15710
15907
  }
15711
- return lines.join(`
15712
- `);
15908
+ return { criteria, nextIndex: i };
15713
15909
  }
15714
- async function helpCommand(options) {
15715
- const { projectRoot, target: overrideTarget, output } = options;
15910
+ function parseGlobalField(key, value, global) {
15911
+ const k = key.trim().toLowerCase();
15912
+ if (k === "product")
15913
+ global.product = value.trim();
15914
+ else if (k === "strategy")
15915
+ global.strategy = value.trim();
15916
+ else if (k === "policy")
15917
+ global.policy = value.trim();
15918
+ else if (k === "review required")
15919
+ global.reviewRequired = parseBool(value);
15920
+ else if (k === "tdd required")
15921
+ global.tddRequired = parseBool(value);
15922
+ }
15923
+ function emptyItem(id, title) {
15924
+ return {
15925
+ id,
15926
+ title,
15927
+ priority: "P2",
15928
+ status: "TODO",
15929
+ type: "feature",
15930
+ dependencies: [],
15931
+ description: "",
15932
+ scope: "",
15933
+ outOfScope: "",
15934
+ acceptanceCriteria: [],
15935
+ progress: 0
15936
+ };
15937
+ }
15938
+ function parseItemField(key, value, item, lines, lineIndex) {
15939
+ const k = key.trim().toLowerCase();
15940
+ if (k === "priority")
15941
+ item.priority = value.trim();
15942
+ else if (k === "status")
15943
+ item.status = value.trim().toUpperCase();
15944
+ else if (k === "type")
15945
+ item.type = value.trim();
15946
+ else if (k === "owner")
15947
+ item.owner = value.trim();
15948
+ else if (k === "depends on")
15949
+ item.dependencies = parseDependencies(value);
15950
+ else if (k === "description")
15951
+ item.description = value.trim();
15952
+ else if (k === "scope")
15953
+ item.scope = value.trim();
15954
+ else if (k === "out of scope")
15955
+ item.outOfScope = value.trim();
15956
+ else if (k === "business value")
15957
+ item.businessValue = value.trim();
15958
+ else if (k === "risks")
15959
+ item.risks = value.trim();
15960
+ else if (k === "progress")
15961
+ item.progress = parseProgress(value);
15962
+ else if (k === "branch")
15963
+ item.branch = value.trim();
15964
+ else if (k === "reviewer")
15965
+ item.reviewer = value.trim();
15966
+ else if (k === "last update")
15967
+ item.lastUpdate = value.trim();
15968
+ else if (k === "acceptance") {
15969
+ const { criteria, nextIndex } = parseAcceptanceLines(lines, lineIndex + 1);
15970
+ item.acceptanceCriteria = criteria;
15971
+ return nextIndex - 1;
15972
+ }
15973
+ return lineIndex;
15974
+ }
15975
+ function parseBacklogMarkdown(content) {
15716
15976
  try {
15717
- let defaultTarget = "opencode";
15718
- try {
15719
- const configResult = await loadConfig(projectRoot);
15720
- if (configResult.success) {
15721
- defaultTarget = configResult.data.defaults.target;
15977
+ const lines = content.split(`
15978
+ `);
15979
+ let section = "none";
15980
+ const global = {};
15981
+ const items = [];
15982
+ const archive = [];
15983
+ let currentItem = null;
15984
+ for (let i = 0;i < lines.length; i++) {
15985
+ const line = lines[i];
15986
+ if (GLOBAL_SECTION_RE.test(line)) {
15987
+ section = "global";
15988
+ currentItem = null;
15989
+ continue;
15722
15990
  }
15723
- } catch {}
15724
- const target = overrideTarget ?? defaultTarget;
15725
- const inventory = await scanPresetDir(projectRoot, target);
15726
- if (output === "json") {
15727
- return {
15728
- code: 0,
15729
- data: {
15730
- success: true,
15731
- command: "help",
15732
- inventory,
15733
- defaultTarget
15734
- }
15735
- };
15736
- }
15737
- return {
15738
- code: 0,
15739
- data: {
15740
- success: true,
15741
- command: "help",
15742
- message: renderHuman(inventory, defaultTarget),
15743
- inventory
15991
+ if (ITEMS_SECTION_RE.test(line)) {
15992
+ section = "items";
15993
+ currentItem = null;
15994
+ continue;
15744
15995
  }
15745
- };
15746
- } catch (error) {
15747
- return {
15748
- code: 1,
15749
- data: {
15750
- success: false,
15751
- command: "help",
15752
- errors: [String(error)]
15996
+ if (ARCHIVE_SECTION_RE.test(line)) {
15997
+ section = "archive";
15998
+ currentItem = null;
15999
+ continue;
16000
+ }
16001
+ const headerMatch = line.match(ITEM_HEADER_RE);
16002
+ if (headerMatch) {
16003
+ const id = headerMatch[1].toUpperCase();
16004
+ const title = headerMatch[2].trim();
16005
+ currentItem = emptyItem(id, title);
16006
+ if (section === "archive")
16007
+ archive.push(currentItem);
16008
+ else if (section === "items")
16009
+ items.push(currentItem);
16010
+ continue;
15753
16011
  }
16012
+ const fieldMatch = line.match(FIELD_RE);
16013
+ if (!fieldMatch)
16014
+ continue;
16015
+ const [, key, value] = fieldMatch;
16016
+ if (section === "global") {
16017
+ parseGlobalField(key, value, global);
16018
+ } else if (currentItem && (section === "items" || section === "archive")) {
16019
+ i = parseItemField(key, value, currentItem, lines, i);
16020
+ }
16021
+ }
16022
+ if (!global.product || !global.strategy || !global.policy) {
16023
+ return err(new Error("BACKLOG.md missing required Global fields (Product, Strategy, Policy)"));
16024
+ }
16025
+ const document = {
16026
+ global: {
16027
+ product: global.product,
16028
+ strategy: global.strategy,
16029
+ policy: global.policy,
16030
+ reviewRequired: global.reviewRequired ?? true,
16031
+ tddRequired: global.tddRequired ?? true
16032
+ },
16033
+ items,
16034
+ archive
15754
16035
  };
16036
+ return ok(document);
16037
+ } catch (e) {
16038
+ return err(e instanceof Error ? e : new Error(String(e)));
15755
16039
  }
15756
16040
  }
16041
+ async function loadBacklog(projectRoot) {
16042
+ try {
16043
+ const filePath = resolve11(projectRoot, BACKLOG_FILE);
16044
+ const content = await readFile11(filePath, "utf-8");
16045
+ return parseBacklogMarkdown(content);
16046
+ } catch (e) {
16047
+ return err(e instanceof Error ? e : new Error(String(e)));
16048
+ }
16049
+ }
16050
+ var BACKLOG_FILENAME = BACKLOG_FILE;
15757
16051
 
15758
- // src/commands/seo-audit.command.ts
15759
- import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
15760
- import { dirname as dirname4, resolve as resolve11 } from "node:path";
15761
-
15762
- // src/infrastructure/http/safe-fetch.ts
15763
- import { lookup } from "node:dns/promises";
15764
- var PRIVATE_IP_RANGES = [
15765
- /^10\./,
15766
- /^172\.(1[6-9]|2\d|3[01])\./,
15767
- /^192\.168\./,
15768
- /^127\./,
15769
- /^0\./,
15770
- /^169\.254\./,
15771
- /^::1$/,
15772
- /^fc00:/i,
15773
- /^fe80:/i
16052
+ // src/core/openspec/backlog-validator.ts
16053
+ init_schemas();
16054
+ var VAGUE_PATTERNS = [
16055
+ /^mejorar\s/i,
16056
+ /^fix\s+bugs?$/i,
16057
+ /^refactor$/i,
16058
+ /^hacer\s+refactor/i,
16059
+ /^arreglar\s+bugs?/i,
16060
+ /^improve\s+ux$/i,
16061
+ /^better\s+ux$/i,
16062
+ /^cleanup$/i,
16063
+ /^misc$/i
15774
16064
  ];
15775
- function isPrivateIp(ip) {
15776
- return PRIVATE_IP_RANGES.some((range) => range.test(ip));
16065
+ function allItems(doc) {
16066
+ return [...doc.items, ...doc.archive];
15777
16067
  }
15778
- async function validateUrl(urlString) {
15779
- let parsed;
15780
- try {
15781
- parsed = new URL(urlString);
15782
- } catch {
15783
- throw new Error(`Invalid URL: ${urlString}`);
16068
+ function detectCycle(items) {
16069
+ const ids = new Set(items.map((i) => i.id));
16070
+ const graph = new Map;
16071
+ for (const item of items) {
16072
+ graph.set(item.id, item.dependencies.filter((d) => ids.has(d)));
15784
16073
  }
15785
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
15786
- throw new Error(`Blocked scheme: ${parsed.protocol}. Only http and https are allowed.`);
16074
+ const visited = new Set;
16075
+ const inStack = new Set;
16076
+ function dfs(id) {
16077
+ if (inStack.has(id))
16078
+ return true;
16079
+ if (visited.has(id))
16080
+ return false;
16081
+ visited.add(id);
16082
+ inStack.add(id);
16083
+ for (const dep of graph.get(id) ?? []) {
16084
+ if (dfs(dep))
16085
+ return true;
16086
+ }
16087
+ inStack.delete(id);
16088
+ return false;
15787
16089
  }
15788
- const hostname = parsed.hostname;
15789
- if (hostname === "localhost" || hostname.endsWith(".localhost")) {
15790
- throw new Error(`Blocked hostname: ${hostname}`);
16090
+ for (const id of ids) {
16091
+ if (dfs(id))
16092
+ return id;
15791
16093
  }
16094
+ return null;
16095
+ }
16096
+ function isVagueCriterion(text) {
16097
+ const t = text.trim();
16098
+ if (t.length < 8)
16099
+ return true;
16100
+ return VAGUE_PATTERNS.some((p) => p.test(t));
16101
+ }
16102
+ function validateBacklog(doc) {
16103
+ const errors3 = [];
16104
+ const recommendations = [];
15792
16105
  try {
15793
- const addresses = await lookup(hostname, { all: true });
15794
- const addrList = Array.isArray(addresses) ? addresses : [addresses];
15795
- for (const addr of addrList) {
15796
- if (isPrivateIp(addr.address)) {
15797
- throw new Error(`SSRF blocked: ${hostname} resolves to private IP ${addr.address}`);
16106
+ BacklogDocumentSchema.parse(doc);
16107
+ } catch (e) {
16108
+ errors3.push({
16109
+ code: "SCHEMA",
16110
+ message: e instanceof Error ? e.message : String(e)
16111
+ });
16112
+ return { valid: false, errors: errors3, recommendations: buildRecommendations(errors3) };
16113
+ }
16114
+ const all = allItems(doc);
16115
+ const idCounts = new Map;
16116
+ for (const item of all) {
16117
+ idCounts.set(item.id, (idCounts.get(item.id) ?? 0) + 1);
16118
+ }
16119
+ for (const [id, count] of idCounts) {
16120
+ if (count > 1) {
16121
+ errors3.push({
16122
+ code: "DUPLICATE_ID",
16123
+ message: `Duplicate backlog ID "${id}"`,
16124
+ itemId: id
16125
+ });
16126
+ }
16127
+ }
16128
+ const knownIds = new Set(all.map((i) => i.id));
16129
+ for (const item of doc.items) {
16130
+ if (!item.description.trim()) {
16131
+ errors3.push({
16132
+ code: "MISSING_DESCRIPTION",
16133
+ message: `Item ${item.id} missing Description`,
16134
+ itemId: item.id
16135
+ });
16136
+ }
16137
+ if (!item.scope.trim()) {
16138
+ errors3.push({
16139
+ code: "MISSING_SCOPE",
16140
+ message: `Item ${item.id} missing Scope`,
16141
+ itemId: item.id
16142
+ });
16143
+ }
16144
+ if (item.acceptanceCriteria.length === 0) {
16145
+ errors3.push({
16146
+ code: "MISSING_ACCEPTANCE",
16147
+ message: `Item ${item.id} must have at least one Acceptance criterion`,
16148
+ itemId: item.id
16149
+ });
16150
+ }
16151
+ for (const criterion of item.acceptanceCriteria) {
16152
+ if (isVagueCriterion(criterion)) {
16153
+ errors3.push({
16154
+ code: "VAGUE_ACCEPTANCE",
16155
+ message: `Item ${item.id} has vague acceptance criterion: "${criterion}"`,
16156
+ itemId: item.id
16157
+ });
15798
16158
  }
15799
16159
  }
15800
- } catch (error) {
15801
- if (error instanceof Error && error.message.startsWith("SSRF blocked")) {
15802
- throw error;
16160
+ for (const dep of item.dependencies) {
16161
+ if (!knownIds.has(dep)) {
16162
+ errors3.push({
16163
+ code: "UNKNOWN_DEPENDENCY",
16164
+ message: `Item ${item.id} depends on unknown ID "${dep}"`,
16165
+ itemId: item.id
16166
+ });
16167
+ }
15803
16168
  }
15804
- throw new Error(`DNS resolution failed for ${hostname}: ${String(error)}`);
15805
16169
  }
16170
+ const cycleId = detectCycle(doc.items);
16171
+ if (cycleId) {
16172
+ errors3.push({
16173
+ code: "DEPENDENCY_CYCLE",
16174
+ message: `Dependency cycle detected involving ${cycleId}`,
16175
+ itemId: cycleId
16176
+ });
16177
+ }
16178
+ if (doc.items.length === 0 && doc.archive.length === 0) {
16179
+ recommendations.push("Add at least one item under ## Items with a unique BC-xxx ID.");
16180
+ }
16181
+ return {
16182
+ valid: errors3.length === 0,
16183
+ errors: errors3,
16184
+ recommendations: buildRecommendations(errors3, recommendations)
16185
+ };
15806
16186
  }
15807
- async function safeFetch(urlString, options = {}) {
15808
- const { timeout = 1e4, followRedirects = false } = options;
15809
- await validateUrl(urlString);
15810
- const controller = new AbortController;
15811
- const timer = setTimeout(() => controller.abort(), timeout);
15812
- const start = Date.now();
16187
+ function buildRecommendations(errors3, extra = []) {
16188
+ const recs = [...extra];
16189
+ if (errors3.some((e) => e.code === "SCHEMA" || e.code === "MISSING_ACCEPTANCE")) {
16190
+ recs.push("Use the canonical BACKLOG.md format: ## Global, ## Items, ### BC-001 | Title, with Priority, Status, Type, Depends on, Description, Scope, Acceptance list.");
16191
+ }
16192
+ if (errors3.some((e) => e.code === "VAGUE_ACCEPTANCE")) {
16193
+ recs.push('Acceptance criteria must be measurable (e.g. "openspec validate rejects malformed BACKLOG.md" not "improve UX").');
16194
+ }
16195
+ if (errors3.some((e) => e.code === "UNKNOWN_DEPENDENCY" || e.code === "DEPENDENCY_CYCLE")) {
16196
+ recs.push("Ensure Depends on lists only existing BC-xxx IDs with no circular dependencies.");
16197
+ }
16198
+ if (errors3.some((e) => e.code === "DUPLICATE_ID")) {
16199
+ recs.push("Each BC-xxx ID must appear only once across Items and Archive.");
16200
+ }
16201
+ return recs;
16202
+ }
16203
+
16204
+ // src/core/openspec/backlog-scanner.ts
16205
+ import { execSync as execSync2 } from "node:child_process";
16206
+ import { readFile as readFile13 } from "node:fs/promises";
16207
+ import { resolve as resolve13 } from "node:path";
16208
+
16209
+ // src/core/openspec/openspec-state.ts
16210
+ init_schemas();
16211
+ import { createHash } from "node:crypto";
16212
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile7 } from "node:fs/promises";
16213
+ import { resolve as resolve12 } from "node:path";
16214
+ var STATE_FILE = ".codeconductor/openspec-state.json";
16215
+ async function loadOpenspecState(projectRoot) {
15813
16216
  try {
15814
- const response = await fetch(urlString, {
15815
- method: "GET",
15816
- signal: controller.signal,
15817
- redirect: followRedirects ? "follow" : "manual",
15818
- headers: {
16217
+ const filePath = resolve12(projectRoot, STATE_FILE);
16218
+ const content = await readFile12(filePath, "utf-8");
16219
+ const data = JSON.parse(content);
16220
+ return ok(OpenspecStateSchema.parse(data));
16221
+ } catch (e) {
16222
+ if (e.code === "ENOENT") {
16223
+ return ok({
16224
+ version: 1,
16225
+ taskCards: [],
16226
+ changePaths: {},
16227
+ itemSnapshots: {}
16228
+ });
16229
+ }
16230
+ return err(e instanceof Error ? e : new Error(String(e)));
16231
+ }
16232
+ }
16233
+ async function writeOpenspecState(projectRoot, state) {
16234
+ try {
16235
+ const dir = resolve12(projectRoot, ".codeconductor");
16236
+ await mkdir8(dir, { recursive: true });
16237
+ const validated = OpenspecStateSchema.parse(state);
16238
+ await writeFile7(resolve12(dir, "openspec-state.json"), JSON.stringify(validated, null, 2), "utf-8");
16239
+ return ok(undefined);
16240
+ } catch (e) {
16241
+ return err(e instanceof Error ? e : new Error(String(e)));
16242
+ }
16243
+ }
16244
+ function getNextTaskCard(state) {
16245
+ const done = new Set(state.taskCards.filter((c) => c.status === "done").map((c) => c.id));
16246
+ for (const card of state.taskCards) {
16247
+ if (card.status === "done" || card.status === "blocked")
16248
+ continue;
16249
+ const depsOk = card.dependsOn.every((d) => done.has(d));
16250
+ if (depsOk && card.status === "pending")
16251
+ return card;
16252
+ }
16253
+ return null;
16254
+ }
16255
+ function hashContent(content) {
16256
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
16257
+ }
16258
+ function updateBacklogItemInMarkdown(content, itemId, updates) {
16259
+ const lines = content.split(`
16260
+ `);
16261
+ let inTargetItem = false;
16262
+ const idPattern = new RegExp(`^###\\s+.*${itemId}\\b`, "i");
16263
+ for (let i = 0;i < lines.length; i++) {
16264
+ const line = lines[i];
16265
+ if (line.match(/^###\s+/)) {
16266
+ inTargetItem = idPattern.test(line);
16267
+ continue;
16268
+ }
16269
+ if (!inTargetItem)
16270
+ continue;
16271
+ if (updates.status && line.match(/^-\s+Status:/i)) {
16272
+ lines[i] = `- Status: ${updates.status}`;
16273
+ }
16274
+ if (updates.progress !== undefined && line.match(/^-\s+Progress:/i)) {
16275
+ lines[i] = `- Progress: ${updates.progress}%`;
16276
+ }
16277
+ }
16278
+ return lines.join(`
16279
+ `);
16280
+ }
16281
+
16282
+ // src/core/openspec/backlog-scanner.ts
16283
+ function itemSnapshot(items) {
16284
+ const snap = {};
16285
+ for (const item of items) {
16286
+ snap[item.id] = JSON.stringify({
16287
+ status: item.status,
16288
+ progress: item.progress,
16289
+ title: item.title
16290
+ });
16291
+ }
16292
+ return snap;
16293
+ }
16294
+ async function scanBacklog(projectRoot, previousSnapshot = {}) {
16295
+ try {
16296
+ const filePath = resolve13(projectRoot, BACKLOG_FILENAME);
16297
+ const content = await readFile13(filePath, "utf-8");
16298
+ const hash = hashContent(content);
16299
+ let fileChanged = true;
16300
+ try {
16301
+ const diff = execSync2(`git diff --name-only -- ${BACKLOG_FILENAME}`, {
16302
+ cwd: projectRoot,
16303
+ encoding: "utf-8",
16304
+ stdio: ["ignore", "pipe", "ignore"]
16305
+ });
16306
+ fileChanged = diff.trim().length > 0;
16307
+ } catch {
16308
+ fileChanged = true;
16309
+ }
16310
+ const parseResult = parseBacklogMarkdown(content);
16311
+ if (!parseResult.success)
16312
+ return parseResult;
16313
+ const doc = parseResult.data;
16314
+ const current = itemSnapshot([...doc.items, ...doc.archive]);
16315
+ const newItems = [];
16316
+ const modifiedItems = [];
16317
+ const closedItems = [];
16318
+ for (const [id, snap] of Object.entries(current)) {
16319
+ if (!previousSnapshot[id]) {
16320
+ newItems.push(id);
16321
+ } else if (previousSnapshot[id] !== snap) {
16322
+ modifiedItems.push(id);
16323
+ }
16324
+ }
16325
+ for (const id of Object.keys(previousSnapshot)) {
16326
+ if (!current[id])
16327
+ closedItems.push(id);
16328
+ }
16329
+ for (const item of doc.archive) {
16330
+ if (!closedItems.includes(item.id))
16331
+ closedItems.push(item.id);
16332
+ }
16333
+ for (const item of doc.items) {
16334
+ if (item.status === "DONE" && !closedItems.includes(item.id)) {
16335
+ closedItems.push(item.id);
16336
+ }
16337
+ }
16338
+ return ok({
16339
+ fileChanged,
16340
+ contentHash: hash,
16341
+ newItems,
16342
+ modifiedItems,
16343
+ closedItems
16344
+ });
16345
+ } catch (e) {
16346
+ return err(e instanceof Error ? e : new Error(String(e)));
16347
+ }
16348
+ }
16349
+ function buildItemSnapshot(items, archive) {
16350
+ return itemSnapshot([...items, ...archive]);
16351
+ }
16352
+
16353
+ // src/core/openspec/agent-router.ts
16354
+ var PHASE_ROUTES = {
16355
+ discover: { agent: "repo-explorer", modelKey: "repo-explorer" },
16356
+ design: { agent: "architect", modelKey: "architect" },
16357
+ implement: { agent: "implementer", modelKey: "implementer" },
16358
+ test: { agent: "tester", modelKey: "tester" },
16359
+ review: { agent: "reviewer", modelKey: "reviewer" }
16360
+ };
16361
+ function routePhaseToAgent(phase) {
16362
+ return PHASE_ROUTES[phase];
16363
+ }
16364
+
16365
+ // src/core/openspec/backlog-planner.ts
16366
+ var DEFAULT_PHASE_ORDER = [
16367
+ "discover",
16368
+ "design",
16369
+ "implement",
16370
+ "test",
16371
+ "review"
16372
+ ];
16373
+ var TDD_PHASE_ORDER = [
16374
+ "discover",
16375
+ "design",
16376
+ "test",
16377
+ "implement",
16378
+ "review"
16379
+ ];
16380
+ function slugify(title) {
16381
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
16382
+ }
16383
+ function phaseTitle(phase, item) {
16384
+ const labels = {
16385
+ discover: "Discover scope and impact",
16386
+ design: "Technical design",
16387
+ implement: "Implementation",
16388
+ test: "Test coverage",
16389
+ review: "Review and validation"
16390
+ };
16391
+ return `${item.id}: ${labels[phase]}`;
16392
+ }
16393
+ function phasePrompt(phase, item) {
16394
+ const base = `Backlog item ${item.id} — ${item.title}
16395
+
16396
+ Description: ${item.description}
16397
+ Scope: ${item.scope}`;
16398
+ switch (phase) {
16399
+ case "discover":
16400
+ return `${base}
16401
+
16402
+ Map repository structure, conventions, and impact radius for this change.`;
16403
+ case "design":
16404
+ return `${base}
16405
+
16406
+ Produce a Technical Plan with approach, files affected, risks, and edge cases.`;
16407
+ case "test":
16408
+ return `${base}
16409
+
16410
+ Write TDD tests covering acceptance criteria before or after implementation per project policy.`;
16411
+ case "implement":
16412
+ return `${base}
16413
+
16414
+ Implement the minimal diff to satisfy tests and acceptance criteria.`;
16415
+ case "review":
16416
+ return `${base}
16417
+
16418
+ Verify acceptance criteria, test coverage, scope alignment, and code quality.`;
16419
+ }
16420
+ }
16421
+ function phaseAcceptance(phase, item) {
16422
+ switch (phase) {
16423
+ case "discover":
16424
+ return ["Repo map identifies relevant files and impact radius"];
16425
+ case "design":
16426
+ return ["Technical plan lists files affected and approach"];
16427
+ case "test":
16428
+ return item.acceptanceCriteria.map((c) => `Test covers: ${c}`);
16429
+ case "implement":
16430
+ return item.acceptanceCriteria;
16431
+ case "review":
16432
+ return [
16433
+ "All acceptance criteria verified",
16434
+ "Tests pass",
16435
+ "No scope creep beyond backlog item"
16436
+ ];
16437
+ }
16438
+ }
16439
+ function getPhaseOrder(doc) {
16440
+ return doc.global.tddRequired ? [...TDD_PHASE_ORDER] : [...DEFAULT_PHASE_ORDER];
16441
+ }
16442
+ function planTaskCardsForItem(item, doc, existingCards = []) {
16443
+ const phases = getPhaseOrder(doc);
16444
+ const cards = [];
16445
+ let prevId;
16446
+ for (const phase of phases) {
16447
+ const id = `${item.id}-${phase}`;
16448
+ const existing = existingCards.find((c) => c.id === id);
16449
+ const route = routePhaseToAgent(phase);
16450
+ const dependsOn = prevId ? [prevId] : [];
16451
+ cards.push({
16452
+ id,
16453
+ backlogId: item.id,
16454
+ phase,
16455
+ title: phaseTitle(phase, item),
16456
+ prompt: phasePrompt(phase, item),
16457
+ agent: route.agent,
16458
+ modelHint: route.modelKey,
16459
+ dependsOn,
16460
+ acceptanceCriteria: phaseAcceptance(phase, item),
16461
+ status: existing?.status ?? "pending"
16462
+ });
16463
+ prevId = id;
16464
+ }
16465
+ return cards;
16466
+ }
16467
+ function comparePriority(a, b) {
16468
+ const order = { P0: 0, P1: 1, P2: 2, P3: 3 };
16469
+ return order[a.priority] - order[b.priority];
16470
+ }
16471
+ function selectNextItem(doc, itemId) {
16472
+ const doneOrArchived = new Set(doc.archive.map((i) => i.id).concat(doc.items.filter((i) => i.status === "DONE").map((i) => i.id)));
16473
+ const candidates = doc.items.filter((i) => {
16474
+ if (itemId)
16475
+ return i.id === itemId;
16476
+ if (doneOrArchived.has(i.id))
16477
+ return false;
16478
+ return i.status === "READY" || i.status === "PLANNED" || i.status === "IN_PROGRESS";
16479
+ }).sort(comparePriority);
16480
+ for (const item of candidates) {
16481
+ const depsOk = item.dependencies.every((dep) => {
16482
+ const depItem = doc.items.find((i) => i.id === dep) ?? doc.archive.find((i) => i.id === dep);
16483
+ return depItem?.status === "DONE" || doc.archive.some((a) => a.id === dep);
16484
+ });
16485
+ if (depsOk)
16486
+ return item;
16487
+ }
16488
+ return null;
16489
+ }
16490
+ function buildChangeSlug(item) {
16491
+ const num = item.id.replace(/^BC-/i, "").toLowerCase();
16492
+ const slug = slugify(item.title);
16493
+ return `bc-${num}-${slug}`;
16494
+ }
16495
+
16496
+ // src/core/openspec/openspec-generator.ts
16497
+ import { mkdir as mkdir9, writeFile as writeFile8 } from "node:fs/promises";
16498
+ import { resolve as resolve14 } from "node:path";
16499
+ function proposalContent(item) {
16500
+ return `# Proposal: ${item.title}
16501
+
16502
+ ## Why
16503
+
16504
+ ${item.description}
16505
+
16506
+ ${item.businessValue ? `**Business value:** ${item.businessValue}` : ""}
16507
+
16508
+ ## What Changes
16509
+
16510
+ - ${item.scope}
16511
+
16512
+ ## Capabilities
16513
+
16514
+ - **New Capabilities:** (to be refined in design phase)
16515
+ - **Modified Capabilities:** (to be refined in design phase)
16516
+
16517
+ ## Impact
16518
+
16519
+ ${item.risks ? `Risks: ${item.risks}` : "See design.md for technical impact."}
16520
+
16521
+ **Out of scope:** ${item.outOfScope || "None specified."}
16522
+ `;
16523
+ }
16524
+ function designPlaceholder(item) {
16525
+ return `# Design: ${item.title}
16526
+
16527
+ ## Approach
16528
+
16529
+ (To be completed in design phase.)
16530
+
16531
+ ## Files Affected
16532
+
16533
+ ${item.scope}
16534
+
16535
+ ## Acceptance Criteria
16536
+
16537
+ ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
16538
+ `)}
16539
+ `;
16540
+ }
16541
+ function tasksContent(cards) {
16542
+ const implCards = cards.filter((c) => c.phase === "implement" || c.phase === "test");
16543
+ const lines = ["# Implementation Tasks", ""];
16544
+ for (const card of implCards) {
16545
+ const checked = card.status === "done" ? "x" : " ";
16546
+ lines.push(`- [${checked}] ${card.title} (${card.id})`);
16547
+ }
16548
+ if (implCards.length === 0) {
16549
+ lines.push("- [ ] Implementation tasks (generated after plan)");
16550
+ }
16551
+ return lines.join(`
16552
+ `);
16553
+ }
16554
+ async function generateOpenspecChange(projectRoot, item, taskCards) {
16555
+ const slug = buildChangeSlug(item);
16556
+ const changeDir = resolve14(projectRoot, "openspec", "changes", slug);
16557
+ await mkdir9(resolve14(changeDir, "specs"), { recursive: true });
16558
+ await writeFile8(resolve14(changeDir, "proposal.md"), proposalContent(item), "utf-8");
16559
+ await writeFile8(resolve14(changeDir, "design.md"), designPlaceholder(item), "utf-8");
16560
+ await writeFile8(resolve14(changeDir, "tasks.md"), tasksContent(taskCards), "utf-8");
16561
+ const specStub = `# Delta Spec: ${item.title}
16562
+
16563
+ ## ADDED Requirements
16564
+
16565
+ ${item.acceptanceCriteria.map((c) => `- ${c}`).join(`
16566
+ `)}
16567
+ `;
16568
+ await writeFile8(resolve14(changeDir, "specs", "delta.md"), specStub, "utf-8");
16569
+ return `openspec/changes/${slug}`;
16570
+ }
16571
+ async function ensureOpenspecConfig(projectRoot) {
16572
+ const configPath = resolve14(projectRoot, "openspec", "config.yaml");
16573
+ try {
16574
+ const { access: access6 } = await import("node:fs/promises");
16575
+ await access6(configPath);
16576
+ } catch {
16577
+ await mkdir9(resolve14(projectRoot, "openspec"), { recursive: true });
16578
+ const stub = `# OpenSpec project config (CodeConductor native compatible)
16579
+ schema: spec-driven
16580
+ `;
16581
+ await writeFile8(configPath, stub, "utf-8");
16582
+ }
16583
+ }
16584
+
16585
+ // src/commands/openspec.command.ts
16586
+ async function openspecCommand(options) {
16587
+ const { subcommand, itemId, projectRoot, output } = options;
16588
+ switch (subcommand) {
16589
+ case "validate":
16590
+ return handleValidate(projectRoot);
16591
+ case "scan":
16592
+ return handleScan(projectRoot);
16593
+ case "plan":
16594
+ return handlePlan(projectRoot, itemId);
16595
+ case "status":
16596
+ return handleStatus(projectRoot);
16597
+ case "next":
16598
+ return handleNext(projectRoot);
16599
+ default:
16600
+ return {
16601
+ code: 1,
16602
+ data: {
16603
+ success: false,
16604
+ command: "openspec",
16605
+ errors: [
16606
+ `Unknown subcommand: ${subcommand}. Use: validate, scan, plan, status, next`
16607
+ ]
16608
+ }
16609
+ };
16610
+ }
16611
+ }
16612
+ async function handleValidate(projectRoot) {
16613
+ const loadResult = await loadBacklog(projectRoot);
16614
+ if (!loadResult.success) {
16615
+ return {
16616
+ code: 1,
16617
+ data: {
16618
+ success: false,
16619
+ command: "openspec validate",
16620
+ errors: [loadResult.error.message],
16621
+ recommendations: [
16622
+ "Create BACKLOG.md at project root using the CodeConductor template."
16623
+ ]
16624
+ }
16625
+ };
16626
+ }
16627
+ const report = validateBacklog(loadResult.data);
16628
+ return {
16629
+ code: report.valid ? 0 : 1,
16630
+ data: {
16631
+ success: report.valid,
16632
+ command: "openspec validate",
16633
+ valid: report.valid,
16634
+ errors: report.errors.map((e) => e.message),
16635
+ recommendations: report.recommendations,
16636
+ itemCount: loadResult.data.items.length,
16637
+ archiveCount: loadResult.data.archive.length
16638
+ }
16639
+ };
16640
+ }
16641
+ async function handleScan(projectRoot) {
16642
+ const stateResult = await loadOpenspecState(projectRoot);
16643
+ const prevSnap = stateResult.success ? stateResult.data.itemSnapshots ?? {} : {};
16644
+ const scanResult = await scanBacklog(projectRoot, prevSnap);
16645
+ if (!scanResult.success) {
16646
+ return {
16647
+ code: 1,
16648
+ data: {
16649
+ success: false,
16650
+ command: "openspec scan",
16651
+ errors: [scanResult.error.message]
16652
+ }
16653
+ };
16654
+ }
16655
+ const loadResult = await loadBacklog(projectRoot);
16656
+ if (loadResult.success && stateResult.success) {
16657
+ const snapshots = buildItemSnapshot(loadResult.data.items, loadResult.data.archive);
16658
+ const state = {
16659
+ ...stateResult.data,
16660
+ lastScanHash: scanResult.data.contentHash,
16661
+ lastScanAt: new Date().toISOString(),
16662
+ itemSnapshots: snapshots
16663
+ };
16664
+ await writeOpenspecState(projectRoot, state);
16665
+ }
16666
+ return {
16667
+ code: 0,
16668
+ data: {
16669
+ success: true,
16670
+ command: "openspec scan",
16671
+ ...scanResult.data
16672
+ }
16673
+ };
16674
+ }
16675
+ async function handlePlan(projectRoot, itemId) {
16676
+ const validateFirst = await handleValidate(projectRoot);
16677
+ if (validateFirst.code !== 0)
16678
+ return validateFirst;
16679
+ const loadResult = await loadBacklog(projectRoot);
16680
+ if (!loadResult.success) {
16681
+ return { code: 1, data: { success: false, errors: [loadResult.error.message] } };
16682
+ }
16683
+ const doc = loadResult.data;
16684
+ const item = selectNextItem(doc, itemId);
16685
+ if (!item) {
16686
+ return {
16687
+ code: 1,
16688
+ data: {
16689
+ success: false,
16690
+ command: "openspec plan",
16691
+ errors: [
16692
+ itemId ? `Item ${itemId} not found or not eligible` : "No READY backlog item with satisfied dependencies"
16693
+ ]
16694
+ }
16695
+ };
16696
+ }
16697
+ const stateResult = await loadOpenspecState(projectRoot);
16698
+ const existingState = stateResult.success ? stateResult.data : {
16699
+ version: 1,
16700
+ taskCards: [],
16701
+ changePaths: {},
16702
+ itemSnapshots: {}
16703
+ };
16704
+ const taskCards = planTaskCardsForItem(item, doc, existingState.taskCards);
16705
+ await ensureOpenspecConfig(projectRoot);
16706
+ const changePath = await generateOpenspecChange(projectRoot, item, taskCards);
16707
+ const newState = {
16708
+ ...existingState,
16709
+ version: 1,
16710
+ activeItemId: item.id,
16711
+ taskCards,
16712
+ changePaths: {
16713
+ ...existingState.changePaths,
16714
+ [item.id]: changePath
16715
+ }
16716
+ };
16717
+ await writeOpenspecState(projectRoot, newState);
16718
+ const backlogPath = resolve15(projectRoot, BACKLOG_FILENAME);
16719
+ let content = await readFile14(backlogPath, "utf-8");
16720
+ content = updateBacklogItemInMarkdown(content, item.id, {
16721
+ status: "PLANNED",
16722
+ progress: item.progress
16723
+ });
16724
+ await writeFile9(backlogPath, content, "utf-8");
16725
+ return {
16726
+ code: 0,
16727
+ data: {
16728
+ success: true,
16729
+ command: "openspec plan",
16730
+ itemId: item.id,
16731
+ title: item.title,
16732
+ changePath,
16733
+ taskCards
16734
+ }
16735
+ };
16736
+ }
16737
+ async function handleStatus(projectRoot) {
16738
+ const loadResult = await loadBacklog(projectRoot);
16739
+ const stateResult = await loadOpenspecState(projectRoot);
16740
+ const nextItem = loadResult.success ? selectNextItem(loadResult.data) : null;
16741
+ const state = stateResult.success ? stateResult.data : null;
16742
+ const pendingCards = state?.taskCards.filter((c) => c.status === "pending").length ?? 0;
16743
+ const doneCards = state?.taskCards.filter((c) => c.status === "done").length ?? 0;
16744
+ return {
16745
+ code: 0,
16746
+ data: {
16747
+ success: true,
16748
+ command: "openspec status",
16749
+ activeItemId: state?.activeItemId,
16750
+ nextItemId: nextItem?.id,
16751
+ nextItemTitle: nextItem?.title,
16752
+ taskCardsPending: pendingCards,
16753
+ taskCardsDone: doneCards,
16754
+ changePaths: state?.changePaths ?? {}
16755
+ }
16756
+ };
16757
+ }
16758
+ async function handleNext(projectRoot) {
16759
+ const stateResult = await loadOpenspecState(projectRoot);
16760
+ if (!stateResult.success) {
16761
+ return {
16762
+ code: 1,
16763
+ data: {
16764
+ success: false,
16765
+ command: "openspec next",
16766
+ errors: [stateResult.error.message]
16767
+ }
16768
+ };
16769
+ }
16770
+ const next = getNextTaskCard(stateResult.data);
16771
+ if (!next) {
16772
+ return {
16773
+ code: 0,
16774
+ data: {
16775
+ success: true,
16776
+ command: "openspec next",
16777
+ taskCard: null,
16778
+ message: "No pending task cards. Run openspec plan first."
16779
+ }
16780
+ };
16781
+ }
16782
+ return {
16783
+ code: 0,
16784
+ data: {
16785
+ success: true,
16786
+ command: "openspec next",
16787
+ taskCard: next
16788
+ }
16789
+ };
16790
+ }
16791
+
16792
+ // src/commands/scorecard.command.ts
16793
+ import { readFile as readFile19 } from "node:fs/promises";
16794
+ import { resolve as resolve21 } from "node:path";
16795
+
16796
+ // src/core/evaluation/scorecard-constants.ts
16797
+ var SCORECARD_CRITERIA_DEF = [
16798
+ { id: "acceptance", label: "Acceptance criteria met", weight: 0.3 },
16799
+ { id: "minimal_diff", label: "Minimal diff (no scope creep)", weight: 0.2 },
16800
+ { id: "tests", label: "Tests present and passing", weight: 0.15 },
16801
+ { id: "regressions", label: "No regressions introduced", weight: 0.15 },
16802
+ { id: "conventions", label: "Code follows project conventions", weight: 0.1 },
16803
+ { id: "documentation", label: "Documentation updated if required", weight: 0.05 },
16804
+ { id: "context_discipline", label: "Context discipline", weight: 0.05 },
16805
+ { id: "cc_gain", label: "Complexity diffusion (cc-gain)", weight: 0.05 }
16806
+ ];
16807
+
16808
+ // src/core/evaluation/scorecard-calculator.ts
16809
+ var PASS_THRESHOLD = 2;
16810
+ function createDefaultCriteria(overrides = {}) {
16811
+ return SCORECARD_CRITERIA_DEF.map((def) => {
16812
+ const o = overrides[def.id];
16813
+ return {
16814
+ id: def.id,
16815
+ label: def.label,
16816
+ weight: def.weight,
16817
+ score: o?.score ?? 2,
16818
+ notes: o?.notes,
16819
+ autoSuggested: o?.autoSuggested
16820
+ };
16821
+ });
16822
+ }
16823
+ function computeWeightedScore(criteria) {
16824
+ let total = 0;
16825
+ for (const c of criteria) {
16826
+ total += c.score * c.weight;
16827
+ }
16828
+ return Math.round(total * 1000) / 1000;
16829
+ }
16830
+ function computeVerdict(criteria, weightedScore) {
16831
+ const byId = new Map(criteria.map((c) => [c.id, c.score]));
16832
+ const acceptance = byId.get("acceptance") ?? 0;
16833
+ const minimalDiff = byId.get("minimal_diff") ?? 0;
16834
+ const regressions = byId.get("regressions") ?? 0;
16835
+ if (acceptance === 0 || minimalDiff === 0 || regressions === 0) {
16836
+ return "REJECT";
16837
+ }
16838
+ if (weightedScore < 1.5) {
16839
+ return "REJECT";
16840
+ }
16841
+ if (weightedScore >= PASS_THRESHOLD && !criteria.some((c) => c.score === 0)) {
16842
+ return "PASS";
16843
+ }
16844
+ return "REVISE";
16845
+ }
16846
+ function buildScorecardRecord(params) {
16847
+ const weightedScore = computeWeightedScore(params.criteria);
16848
+ const verdict = computeVerdict(params.criteria, weightedScore);
16849
+ return {
16850
+ id: params.id,
16851
+ taskId: params.taskId,
16852
+ agent: params.agent,
16853
+ model: params.model,
16854
+ contractVersion: params.contractVersion,
16855
+ evaluator: params.evaluator,
16856
+ criteria: params.criteria,
16857
+ weightedScore,
16858
+ verdict,
16859
+ findings: params.findings ?? [],
16860
+ createdAt: new Date().toISOString(),
16861
+ backlogId: params.backlogId,
16862
+ source: params.source
16863
+ };
16864
+ }
16865
+
16866
+ // src/core/evaluation/outcome-store.ts
16867
+ init_schemas();
16868
+ import { appendFile, mkdir as mkdir10, readFile as readFile15, writeFile as writeFile10 } from "node:fs/promises";
16869
+ import { resolve as resolve16 } from "node:path";
16870
+ var EVAL_DIR = ".codeconductor/evaluation";
16871
+ var OUTCOMES_FILE = "outcomes.jsonl";
16872
+ var SCORECARDS_DIR = "scorecards";
16873
+ var INDEX_FILE = "index.json";
16874
+ async function ensureEvaluationDir(projectRoot) {
16875
+ const dir = resolve16(projectRoot, EVAL_DIR);
16876
+ await mkdir10(dir, { recursive: true });
16877
+ await mkdir10(resolve16(dir, SCORECARDS_DIR), { recursive: true });
16878
+ return dir;
16879
+ }
16880
+ async function appendOutcome(projectRoot, outcome) {
16881
+ try {
16882
+ const dir = await ensureEvaluationDir(projectRoot);
16883
+ const validated = TaskOutcomeSchema.parse(outcome);
16884
+ await appendFile(resolve16(dir, OUTCOMES_FILE), JSON.stringify(validated) + `
16885
+ `, "utf-8");
16886
+ const indexPath = resolve16(dir, INDEX_FILE);
16887
+ let index = { version: 1, lastOutcomeId: validated.id };
16888
+ try {
16889
+ const raw = await readFile15(indexPath, "utf-8");
16890
+ index = EvaluationIndexSchema.parse(JSON.parse(raw));
16891
+ index.lastOutcomeId = validated.id;
16892
+ } catch {}
16893
+ await writeFile10(indexPath, JSON.stringify(index, null, 2), "utf-8");
16894
+ return ok(undefined);
16895
+ } catch (e) {
16896
+ return err(e instanceof Error ? e : new Error(String(e)));
16897
+ }
16898
+ }
16899
+ async function listOutcomes(projectRoot, filter = {}) {
16900
+ try {
16901
+ const path = resolve16(projectRoot, EVAL_DIR, OUTCOMES_FILE);
16902
+ const content = await readFile15(path, "utf-8");
16903
+ const lines = content.split(`
16904
+ `).filter((l) => l.trim().length > 0);
16905
+ const outcomes = [];
16906
+ for (const line of lines) {
16907
+ const parsed = TaskOutcomeSchema.parse(JSON.parse(line));
16908
+ if (filter.agent && parsed.agent !== filter.agent)
16909
+ continue;
16910
+ if (filter.model && parsed.model !== filter.model)
16911
+ continue;
16912
+ if (filter.taskId && parsed.taskId !== filter.taskId)
16913
+ continue;
16914
+ if (filter.backlogId && parsed.backlogId !== filter.backlogId)
16915
+ continue;
16916
+ if (filter.source && parsed.source !== filter.source)
16917
+ continue;
16918
+ if (filter.since && parsed.timestamp < filter.since)
16919
+ continue;
16920
+ outcomes.push(parsed);
16921
+ }
16922
+ return ok(outcomes);
16923
+ } catch (e) {
16924
+ if (e.code === "ENOENT") {
16925
+ return ok([]);
16926
+ }
16927
+ return err(e instanceof Error ? e : new Error(String(e)));
16928
+ }
16929
+ }
16930
+ async function saveScorecard(projectRoot, record) {
16931
+ try {
16932
+ const dir = await ensureEvaluationDir(projectRoot);
16933
+ const validated = ScorecardRecordSchema.parse(record);
16934
+ const filePath = resolve16(dir, SCORECARDS_DIR, `${validated.id}.json`);
16935
+ await writeFile10(filePath, JSON.stringify(validated, null, 2), "utf-8");
16936
+ return ok(filePath);
16937
+ } catch (e) {
16938
+ return err(e instanceof Error ? e : new Error(String(e)));
16939
+ }
16940
+ }
16941
+ async function loadScorecard(projectRoot, id) {
16942
+ try {
16943
+ const filePath = resolve16(projectRoot, EVAL_DIR, SCORECARDS_DIR, `${id}.json`);
16944
+ const content = await readFile15(filePath, "utf-8");
16945
+ return ok(ScorecardRecordSchema.parse(JSON.parse(content)));
16946
+ } catch (e) {
16947
+ return err(e instanceof Error ? e : new Error(String(e)));
16948
+ }
16949
+ }
16950
+ function aggregateOutcomes(outcomes) {
16951
+ const withScore = outcomes.filter((o) => o.weightedScore !== undefined);
16952
+ const passed = outcomes.filter((o) => o.verdict === "PASS" || o.status === "pass");
16953
+ const avgWeightedScore = withScore.length > 0 ? withScore.reduce((s, o) => s + (o.weightedScore ?? 0), 0) / withScore.length : 0;
16954
+ const byAgent = {};
16955
+ const byModel = {};
16956
+ for (const o of outcomes) {
16957
+ if (!byAgent[o.agent])
16958
+ byAgent[o.agent] = { count: 0, avgScore: 0 };
16959
+ byAgent[o.agent].count++;
16960
+ if (o.weightedScore !== undefined) {
16961
+ byAgent[o.agent].avgScore += o.weightedScore;
16962
+ }
16963
+ if (!byModel[o.model])
16964
+ byModel[o.model] = { count: 0, avgScore: 0 };
16965
+ byModel[o.model].count++;
16966
+ if (o.weightedScore !== undefined)
16967
+ byModel[o.model].avgScore += o.weightedScore;
16968
+ if (o.costUsd !== undefined) {
16969
+ byModel[o.model].avgCost = (byModel[o.model].avgCost ?? 0) + o.costUsd;
16970
+ }
16971
+ const tokens = (o.tokensIn ?? 0) + (o.tokensOut ?? 0);
16972
+ if (tokens > 0) {
16973
+ byModel[o.model].avgTokens = (byModel[o.model].avgTokens ?? 0) + tokens;
16974
+ }
16975
+ }
16976
+ for (const agent of Object.keys(byAgent)) {
16977
+ const entry = byAgent[agent];
16978
+ entry.avgScore = entry.count > 0 ? entry.avgScore / entry.count : 0;
16979
+ }
16980
+ for (const model of Object.keys(byModel)) {
16981
+ const entry = byModel[model];
16982
+ entry.avgScore = entry.count > 0 ? entry.avgScore / entry.count : 0;
16983
+ if (entry.avgCost !== undefined)
16984
+ entry.avgCost /= entry.count;
16985
+ if (entry.avgTokens !== undefined)
16986
+ entry.avgTokens /= entry.count;
16987
+ }
16988
+ return {
16989
+ total: outcomes.length,
16990
+ passRate: outcomes.length > 0 ? passed.length / outcomes.length : 0,
16991
+ avgWeightedScore: Math.round(avgWeightedScore * 1000) / 1000,
16992
+ byAgent,
16993
+ byModel
16994
+ };
16995
+ }
16996
+ function generateEvalId(prefix) {
16997
+ const ts = Date.now().toString(36);
16998
+ const rand = Math.random().toString(36).slice(2, 6);
16999
+ return `${prefix}-${ts}-${rand}`;
17000
+ }
17001
+
17002
+ // src/core/evaluation/scorecard-signals.ts
17003
+ import { execSync as execSync3 } from "node:child_process";
17004
+
17005
+ // src/core/complexity/complexity-auditor.ts
17006
+ var DIFF_FILE_REGEX = /^\+\+\+ b\/(.+)$/;
17007
+ var HUNK_HEADER_REGEX = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
17008
+ function parseDiffHunks(diffContent) {
17009
+ const lines = diffContent.split(`
17010
+ `);
17011
+ const hunks = [];
17012
+ let current = null;
17013
+ for (const line of lines) {
17014
+ const fileMatch = line.match(DIFF_FILE_REGEX);
17015
+ if (fileMatch) {
17016
+ current = { file: fileMatch[1], addedLines: [], removedLines: [] };
17017
+ hunks.push(current);
17018
+ continue;
17019
+ }
17020
+ if (HUNK_HEADER_REGEX.test(line)) {
17021
+ if (current) {
17022
+ const newHunk = {
17023
+ file: current.file,
17024
+ addedLines: [],
17025
+ removedLines: []
17026
+ };
17027
+ hunks.push(newHunk);
17028
+ current = newHunk;
17029
+ }
17030
+ continue;
17031
+ }
17032
+ if (!current)
17033
+ continue;
17034
+ if (line.startsWith("+") && !line.startsWith("+++")) {
17035
+ current.addedLines.push(line.slice(1));
17036
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
17037
+ current.removedLines.push(line.slice(1));
17038
+ }
17039
+ }
17040
+ return hunks;
17041
+ }
17042
+ function countNonEmptyLines(lines) {
17043
+ return lines.filter((l) => l.trim().length > 0).length;
17044
+ }
17045
+ function analyzeLoc(hunks) {
17046
+ let added = 0;
17047
+ let removed = 0;
17048
+ for (const hunk of hunks) {
17049
+ added += countNonEmptyLines(hunk.addedLines);
17050
+ removed += countNonEmptyLines(hunk.removedLines);
17051
+ }
17052
+ return { added, removed };
17053
+ }
17054
+ var IMPORT_REGEX = /^import\s+(?:.*from\s+)?['"]([^'"]+)['"]/;
17055
+ var REQUIRE_REGEX = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/;
17056
+ var NODE_BUILTINS = new Set([
17057
+ "assert",
17058
+ "buffer",
17059
+ "child_process",
17060
+ "cluster",
17061
+ "console",
17062
+ "constants",
17063
+ "crypto",
17064
+ "dgram",
17065
+ "dns",
17066
+ "domain",
17067
+ "events",
17068
+ "fs",
17069
+ "http",
17070
+ "http2",
17071
+ "https",
17072
+ "inspector",
17073
+ "module",
17074
+ "net",
17075
+ "os",
17076
+ "path",
17077
+ "perf_hooks",
17078
+ "process",
17079
+ "punycode",
17080
+ "querystring",
17081
+ "readline",
17082
+ "repl",
17083
+ "stream",
17084
+ "string_decoder",
17085
+ "sys",
17086
+ "timers",
17087
+ "tls",
17088
+ "trace_events",
17089
+ "tty",
17090
+ "url",
17091
+ "util",
17092
+ "v8",
17093
+ "vm",
17094
+ "worker_threads",
17095
+ "zlib"
17096
+ ]);
17097
+ function isExternalDep(specifier) {
17098
+ const cleaned = specifier.replace(/^node:/, "");
17099
+ if (cleaned.startsWith(".") || cleaned.startsWith("/"))
17100
+ return false;
17101
+ const pkg = cleaned.startsWith("@") ? cleaned.split("/").slice(0, 2).join("/") : cleaned.split("/")[0];
17102
+ return !NODE_BUILTINS.has(pkg);
17103
+ }
17104
+ function analyzeDeps(hunks) {
17105
+ const added = [];
17106
+ const removed = [];
17107
+ for (const hunk of hunks) {
17108
+ for (const line of hunk.addedLines) {
17109
+ const match = line.match(IMPORT_REGEX) || line.match(REQUIRE_REGEX);
17110
+ if (match && isExternalDep(match[1])) {
17111
+ added.push({ name: match[1], type: "import", file: hunk.file });
17112
+ }
17113
+ }
17114
+ for (const line of hunk.removedLines) {
17115
+ const match = line.match(IMPORT_REGEX) || line.match(REQUIRE_REGEX);
17116
+ if (match && isExternalDep(match[1])) {
17117
+ removed.push({ name: match[1], type: "import", file: hunk.file });
17118
+ }
17119
+ }
17120
+ }
17121
+ return { added, removed };
17122
+ }
17123
+ var COMPLEXITY_KEYWORDS = [
17124
+ /\bif\s*\(/,
17125
+ /\belse\s+if\b/,
17126
+ /\bfor\s*\(/,
17127
+ /\bwhile\s*\(/,
17128
+ /\bswitch\s*\(/,
17129
+ /\bcase\s+/,
17130
+ /\bcatch\s*\(/,
17131
+ /\b\?\s*[^.]/,
17132
+ /&&/,
17133
+ /\|\|/
17134
+ ];
17135
+ function countComplexityKeywords(lines) {
17136
+ let count = 0;
17137
+ for (const line of lines) {
17138
+ for (const regex of COMPLEXITY_KEYWORDS) {
17139
+ if (regex.test(line))
17140
+ count++;
17141
+ }
17142
+ }
17143
+ return count;
17144
+ }
17145
+ function analyzeComplexity(hunks) {
17146
+ let added = 0;
17147
+ let removed = 0;
17148
+ for (const hunk of hunks) {
17149
+ added += countComplexityKeywords(hunk.addedLines);
17150
+ removed += countComplexityKeywords(hunk.removedLines);
17151
+ }
17152
+ return { added, removed };
17153
+ }
17154
+ function detectBloatPatterns(hunks) {
17155
+ const findings = [];
17156
+ for (const hunk of hunks) {
17157
+ const allLines = [...hunk.addedLines];
17158
+ for (let i = 0;i < allLines.length; i++) {
17159
+ const line = allLines[i];
17160
+ const lineNum = i + 1;
17161
+ if (/^\s*export\s+interface\s+\w+/.test(line)) {
17162
+ const hasSingleImpl = hunk.addedLines.some((l) => /implements\s+\w+/.test(l) || /implements\s+\{/.test(l));
17163
+ if (hasSingleImpl) {
17164
+ findings.push({
17165
+ severity: "warning",
17166
+ pattern: "single-implementation-interface",
17167
+ file: hunk.file,
17168
+ line: lineNum,
17169
+ message: "Interface with only one implementation — consider a concrete class",
17170
+ action: "delete"
17171
+ });
17172
+ }
17173
+ }
17174
+ if (/^\s*(export\s+)?(async\s+)?function\s+(\w+)/.test(line)) {
17175
+ const funcBody = allLines.slice(i + 1, i + 5).join(`
17176
+ `);
17177
+ if (/return\s+\w+\s*\(/.test(funcBody) && funcBody.split(`
17178
+ `).filter((l) => l.trim()).length <= 3) {
17179
+ findings.push({
17180
+ severity: "info",
17181
+ pattern: "trivial-wrapper",
17182
+ file: hunk.file,
17183
+ line: lineNum,
17184
+ message: "Function appears to be a trivial wrapper — callers could invoke directly",
17185
+ action: "delete"
17186
+ });
17187
+ }
17188
+ }
17189
+ if (/^\s*(export\s+)?class\s+(\w+)/.test(line)) {
17190
+ const classBlock = allLines.slice(i + 1, i + 20).join(`
17191
+ `);
17192
+ const methodCount = (classBlock.match(/\b(method|get\s+\w+|set\s+\w+)\s*\(/g) || []).length;
17193
+ if (methodCount <= 1) {
17194
+ findings.push({
17195
+ severity: "warning",
17196
+ pattern: "one-method-class",
17197
+ file: hunk.file,
17198
+ line: lineNum,
17199
+ message: "Class with only one method — a function may suffice",
17200
+ action: "delete"
17201
+ });
17202
+ }
17203
+ }
17204
+ if (/^\s*import\s+(\w+)/.test(line)) {
17205
+ const importedName = line.match(/import\s+(\w+)/)?.[1];
17206
+ if (importedName) {
17207
+ const restOfFile = allLines.join(`
17208
+ `);
17209
+ const afterImport = allLines.slice(i + 1).join(`
17210
+ `);
17211
+ if (!afterImport.includes(importedName)) {
17212
+ findings.push({
17213
+ severity: "info",
17214
+ pattern: "unused-import",
17215
+ file: hunk.file,
17216
+ line: lineNum,
17217
+ message: `Import "${importedName}" does not appear used in added code`,
17218
+ action: "delete"
17219
+ });
17220
+ }
17221
+ }
17222
+ }
17223
+ if (/^\s*import\s+.*from\s+['"]/.test(line)) {
17224
+ const depMatch = line.match(/from\s+['"]([^'"]+)['"]/);
17225
+ if (depMatch && isExternalDep(depMatch[1])) {
17226
+ const pkg = depMatch[1].startsWith("@") ? depMatch[1].split("/").slice(0, 2).join("/") : depMatch[1].split("/")[0];
17227
+ if (NATIVE_REPLACEMENTS.has(pkg)) {
17228
+ findings.push({
17229
+ severity: "warning",
17230
+ pattern: "external-dep-for-native",
17231
+ file: hunk.file,
17232
+ line: lineNum,
17233
+ message: `"${pkg}" can be replaced with native: ${NATIVE_REPLACEMENTS.get(pkg)}`,
17234
+ action: "replace-native"
17235
+ });
17236
+ }
17237
+ }
17238
+ }
17239
+ if (/^\s*(export\s+)?(abstract\s+)?class\s+(\w+).*extends\s+(\w+)/.test(line)) {
17240
+ findings.push({
17241
+ severity: "info",
17242
+ pattern: "excessive-abstraction",
17243
+ file: hunk.file,
17244
+ line: lineNum,
17245
+ message: "Deep class hierarchy — consider composition over inheritance",
17246
+ action: "delete"
17247
+ });
17248
+ }
17249
+ }
17250
+ }
17251
+ return findings;
17252
+ }
17253
+ var NATIVE_REPLACEMENTS = new Map([
17254
+ ["lodash", "Array/Object native methods"],
17255
+ ["lodash-es", "Array/Object native methods"],
17256
+ ["underscore", "Array/Object native methods"],
17257
+ ["moment", "Intl.DateTimeFormat, Date.parse, Temporal API"],
17258
+ ["date-fns", "Intl.DateTimeFormat, Date methods"],
17259
+ ["axios", "node:https or fetch()"],
17260
+ ["node-fetch", "node:https or global fetch"],
17261
+ ["uuid", "crypto.randomUUID()"],
17262
+ ["qs", "URLSearchParams"],
17263
+ ["semver", "node:semver (stdlib)"],
17264
+ ["chalk", "ANSI escape codes"]
17265
+ ]);
17266
+ function analyzeDiff(diffContent) {
17267
+ const hunks = parseDiffHunks(diffContent);
17268
+ const loc = analyzeLoc(hunks);
17269
+ const deps = analyzeDeps(hunks);
17270
+ const complexity = analyzeComplexity(hunks);
17271
+ const findings = detectBloatPatterns(hunks);
17272
+ return {
17273
+ locAdded: loc.added,
17274
+ locRemoved: loc.removed,
17275
+ locDelta: loc.added - loc.removed,
17276
+ depsAdded: deps.added,
17277
+ depsRemoved: deps.removed,
17278
+ depsDelta: deps.added.length - deps.removed.length,
17279
+ cyclomaticAdded: complexity.added,
17280
+ cyclomaticRemoved: complexity.removed,
17281
+ cyclomaticDelta: complexity.added - complexity.removed,
17282
+ findings
17283
+ };
17284
+ }
17285
+
17286
+ // src/core/complexity/cc-gain.ts
17287
+ function computeCcGain(report) {
17288
+ const locContribution = report.locRemoved * 0.4;
17289
+ const depsAvoided = report.depsRemoved.length - report.depsAdded.length;
17290
+ const depContribution = depsAvoided * 1.5;
17291
+ const complexityReduced = report.cyclomaticRemoved - report.cyclomaticAdded;
17292
+ const complexityContribution = complexityReduced * 2;
17293
+ const abstractionFindings = report.findings.filter((f) => f.pattern === "excessive-abstraction" || f.pattern === "single-implementation-interface" || f.pattern === "one-method-class" || f.pattern === "trivial-wrapper").length;
17294
+ const abstractionPenalty = abstractionFindings * 1;
17295
+ const raw = locContribution + depContribution + complexityContribution - abstractionPenalty;
17296
+ const MAX_RAW = 50;
17297
+ const normalized = Math.max(-1, Math.min(1, raw / MAX_RAW));
17298
+ const verdict = normalized > 0.05 ? "positive" : normalized < -0.05 ? "negative" : "neutral";
17299
+ return {
17300
+ raw,
17301
+ normalized,
17302
+ verdict,
17303
+ breakdown: {
17304
+ locContribution,
17305
+ depContribution,
17306
+ complexityContribution,
17307
+ abstractionPenalty
17308
+ }
17309
+ };
17310
+ }
17311
+ function ccGainToScorecardImpact(result) {
17312
+ if (result.verdict === "negative")
17313
+ return 0;
17314
+ if (result.verdict === "neutral")
17315
+ return 1;
17316
+ if (result.raw >= 10)
17317
+ return 3;
17318
+ return 2;
17319
+ }
17320
+
17321
+ // src/core/evaluation/scorecard-signals.ts
17322
+ function collectScorecardSignals(projectRoot, scopeFiles) {
17323
+ const findings = [];
17324
+ const overrides = {};
17325
+ let diff = "";
17326
+ try {
17327
+ diff = execSync3("git diff HEAD", {
17328
+ cwd: projectRoot,
17329
+ encoding: "utf-8",
17330
+ stdio: ["ignore", "pipe", "ignore"]
17331
+ });
17332
+ } catch {
17333
+ findings.push("Could not read git diff; scope audit skipped.");
17334
+ return { criteriaOverrides: overrides, findings };
17335
+ }
17336
+ if (!diff.trim()) {
17337
+ overrides.minimal_diff = { score: 2, notes: "No diff detected", autoSuggested: true };
17338
+ return { criteriaOverrides: overrides, findings };
17339
+ }
17340
+ const changedFiles = new Set;
17341
+ for (const line of diff.split(`
17342
+ `)) {
17343
+ const m = line.match(/^\+\+\+ b\/(.+)$/);
17344
+ if (m)
17345
+ changedFiles.add(m[1]);
17346
+ }
17347
+ if (scopeFiles && scopeFiles.length > 0) {
17348
+ const outOfScope = [...changedFiles].filter((f) => !scopeFiles.some((s) => f.includes(s.replace(/\*\*/g, ""))));
17349
+ if (outOfScope.length > 0) {
17350
+ overrides.minimal_diff = {
17351
+ score: 0,
17352
+ notes: `Files outside scope: ${outOfScope.slice(0, 5).join(", ")}`,
17353
+ autoSuggested: true
17354
+ };
17355
+ findings.push(`Scope creep detected in ${outOfScope.length} file(s).`);
17356
+ } else {
17357
+ overrides.minimal_diff = { score: 2, notes: "Diff within declared scope", autoSuggested: true };
17358
+ }
17359
+ }
17360
+ try {
17361
+ const report = analyzeDiff(diff);
17362
+ const gain = computeCcGain(report);
17363
+ const ccScore = ccGainToScorecardImpact(gain);
17364
+ overrides.cc_gain = {
17365
+ score: ccScore,
17366
+ notes: `cc-gain raw=${gain.raw}, verdict=${gain.verdict}`,
17367
+ autoSuggested: true
17368
+ };
17369
+ if (report.findings.length > 0) {
17370
+ findings.push(`Complexity auditor: ${report.findings.length} finding(s).`);
17371
+ }
17372
+ } catch {
17373
+ findings.push("Complexity audit skipped.");
17374
+ }
17375
+ return { criteriaOverrides: overrides, findings };
17376
+ }
17377
+ function criteriaFromSignals(hints) {
17378
+ return createDefaultCriteria(hints.criteriaOverrides);
17379
+ }
17380
+
17381
+ // src/core/evaluation/execution-profile.ts
17382
+ init_dist();
17383
+ import { readFile as readFile16 } from "node:fs/promises";
17384
+ import { resolve as resolve17 } from "node:path";
17385
+ init_schemas();
17386
+ var PROFILE_FILE = ".codeconductor/evaluation/execution-profile.yml";
17387
+ var PROFILE_AGENT_OVERRIDES = {
17388
+ balanced: {},
17389
+ quality: {
17390
+ architect: "strong",
17391
+ reviewer: "strong",
17392
+ orchestrator: "strong"
17393
+ },
17394
+ economical: {
17395
+ "repo-explorer": "economical",
17396
+ tester: "economical",
17397
+ docs: "economical",
17398
+ "task-coach": "economical"
17399
+ }
17400
+ };
17401
+ var ECONOMICAL_AGENTS = new Set([
17402
+ "repo-explorer",
17403
+ "tester",
17404
+ "docs",
17405
+ "task-coach",
17406
+ "goal-planner"
17407
+ ]);
17408
+ var STRONG_AGENTS = new Set(["architect", "reviewer", "orchestrator"]);
17409
+ async function loadExecutionProfile(projectRoot, defaultTarget = "opencode") {
17410
+ try {
17411
+ const content = await readFile16(resolve17(projectRoot, PROFILE_FILE), "utf-8");
17412
+ const data = $parse(content);
17413
+ return ExecutionProfileSchema.parse(data);
17414
+ } catch {
17415
+ return ExecutionProfileSchema.parse({ profile: "balanced", target: defaultTarget });
17416
+ }
17417
+ }
17418
+ function pickModelForAgent(modelConfig, target, agentKey, profile) {
17419
+ const agents = modelConfig.agents[agentKey];
17420
+ if (!agents)
17421
+ return;
17422
+ const override = PROFILE_AGENT_OVERRIDES[profile][agentKey];
17423
+ if (override === "economical" && ECONOMICAL_AGENTS.has(agentKey)) {}
17424
+ if (override === "strong" && STRONG_AGENTS.has(agentKey)) {}
17425
+ const t = target ?? modelConfig.target;
17426
+ return agents[t] ?? agents.opencode ?? agents.claude ?? Object.values(agents).find((v) => v !== undefined);
17427
+ }
17428
+ async function resolvePhaseModels(projectRoot, target) {
17429
+ try {
17430
+ const profile = await loadExecutionProfile(projectRoot, target);
17431
+ const effectiveTarget = profile.target ?? target ?? "opencode";
17432
+ const modelConfig = await loadModelConfig(effectiveTarget);
17433
+ const phases = [
17434
+ "discover",
17435
+ "design",
17436
+ "test",
17437
+ "implement",
17438
+ "review"
17439
+ ];
17440
+ const delegateTesterReviewer = profile.subagentPolicy?.testerReviewer === "delegate" || profile.profile === "economical";
17441
+ const resolved = [];
17442
+ for (const phase of phases) {
17443
+ const route = routePhaseToAgent(phase);
17444
+ const overrideModel = profile.overrides?.[route.agent] ?? profile.overrides?.[route.modelKey];
17445
+ const model = overrideModel ?? pickModelForAgent(modelConfig, effectiveTarget, route.modelKey, profile.profile) ?? "unknown";
17446
+ const subagentDelegate = (route.agent === "tester" || route.agent === "reviewer") && delegateTesterReviewer;
17447
+ resolved.push({
17448
+ phase,
17449
+ agent: route.agent,
17450
+ model,
17451
+ modelKey: route.modelKey,
17452
+ subagentDelegate
17453
+ });
17454
+ }
17455
+ return ok(resolved);
17456
+ } catch (e) {
17457
+ return err(e instanceof Error ? e : new Error(String(e)));
17458
+ }
17459
+ }
17460
+
17461
+ // src/core/evaluation/prompt-diff.ts
17462
+ init_package_paths();
17463
+ import { readFile as readFile17, readdir as readdir3 } from "node:fs/promises";
17464
+ import { join as join8, resolve as resolve18 } from "node:path";
17465
+ function normalizeVersion(v) {
17466
+ return v.startsWith("v") ? v : `v${v}`;
17467
+ }
17468
+ async function listPromptFiles(dir) {
17469
+ try {
17470
+ const entries = await readdir3(dir, { withFileTypes: true });
17471
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
17472
+ } catch {
17473
+ return [];
17474
+ }
17475
+ }
17476
+ function lineDiff(oldText, newText, path) {
17477
+ const oldLines = oldText.split(`
17478
+ `);
17479
+ const newLines = newText.split(`
17480
+ `);
17481
+ const hunks = [];
17482
+ let added = 0;
17483
+ let removed = 0;
17484
+ const max = Math.max(oldLines.length, newLines.length);
17485
+ for (let i = 0;i < max; i++) {
17486
+ const o = oldLines[i];
17487
+ const n = newLines[i];
17488
+ if (o === n)
17489
+ continue;
17490
+ if (o === undefined) {
17491
+ added++;
17492
+ hunks.push(`+ ${n}`);
17493
+ } else if (n === undefined) {
17494
+ removed++;
17495
+ hunks.push(`- ${o}`);
17496
+ } else {
17497
+ removed++;
17498
+ added++;
17499
+ hunks.push(`- ${o}`);
17500
+ hunks.push(`+ ${n}`);
17501
+ }
17502
+ }
17503
+ if (hunks.length > 0) {
17504
+ hunks.unshift(`### ${path}`);
17505
+ }
17506
+ return { added, removed, hunks };
17507
+ }
17508
+ async function diffPromptVersions(fromVersion, toVersion, options = {}) {
17509
+ const target = options.target ?? "opencode";
17510
+ const fromV = normalizeVersion(fromVersion);
17511
+ const toV = normalizeVersion(toVersion);
17512
+ const searchRoots = [
17513
+ resolve18(options.projectRoot ?? PACKAGE_ROOT, ".agents", "prompts"),
17514
+ join8(ROOT_PRESETS_DIR, target, "prompts"),
17515
+ join8(ROOT_PRESETS_DIR, "opencode", "prompts")
17516
+ ];
17517
+ let fromDir = "";
17518
+ let toDir = "";
17519
+ for (const root of searchRoots) {
17520
+ const f = join8(root, fromV);
17521
+ const t = join8(root, toV);
17522
+ try {
17523
+ await readFile17(join8(f, "orchestrator.md"), "utf-8");
17524
+ fromDir = f;
17525
+ toDir = t;
17526
+ break;
17527
+ } catch {}
17528
+ }
17529
+ if (!fromDir) {
17530
+ fromDir = join8(ROOT_PRESETS_DIR, "opencode", "prompts", fromV);
17531
+ toDir = join8(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
17532
+ } else {
17533
+ try {
17534
+ await readFile17(join8(toDir, "orchestrator.md"), "utf-8");
17535
+ } catch {
17536
+ toDir = join8(ROOT_PRESETS_DIR, "opencode", "prompts", toV);
17537
+ }
17538
+ }
17539
+ const fromFiles = await listPromptFiles(fromDir);
17540
+ const toFiles = await listPromptFiles(toDir);
17541
+ const allNames = new Set([...fromFiles, ...toFiles]);
17542
+ if (options.agent) {
17543
+ const agentFile = `${options.agent}.md`;
17544
+ if (allNames.has(agentFile)) {
17545
+ allNames.clear();
17546
+ allNames.add(agentFile);
17547
+ }
17548
+ }
17549
+ const files = [];
17550
+ for (const name of allNames) {
17551
+ let oldText = "";
17552
+ let newText = "";
17553
+ try {
17554
+ oldText = await readFile17(join8(fromDir, name), "utf-8");
17555
+ } catch {
17556
+ oldText = "";
17557
+ }
17558
+ try {
17559
+ newText = await readFile17(join8(toDir, name), "utf-8");
17560
+ } catch {
17561
+ newText = "";
17562
+ }
17563
+ const { added, removed, hunks } = lineDiff(oldText, newText, name);
17564
+ files.push({
17565
+ path: name,
17566
+ changed: oldText !== newText,
17567
+ addedLines: added,
17568
+ removedLines: removed,
17569
+ hunks
17570
+ });
17571
+ }
17572
+ return { fromVersion: fromV, toVersion: toV, target, files };
17573
+ }
17574
+ function formatPromptDiffMarkdown(result) {
17575
+ const lines = [
17576
+ `# Prompt diff: ${result.fromVersion} → ${result.toVersion}`,
17577
+ "",
17578
+ `Target: ${result.target}`,
17579
+ ""
17580
+ ];
17581
+ const changed = result.files.filter((f) => f.changed);
17582
+ lines.push(`Changed files: ${changed.length} / ${result.files.length}`, "");
17583
+ for (const f of changed) {
17584
+ lines.push(`## ${f.path} (+${f.addedLines} -${f.removedLines})`, "");
17585
+ lines.push(...f.hunks.slice(0, 40));
17586
+ if (f.hunks.length > 40)
17587
+ lines.push("... (truncated)");
17588
+ lines.push("");
17589
+ }
17590
+ return lines.join(`
17591
+ `);
17592
+ }
17593
+
17594
+ // src/core/evaluation/regression-checklist.ts
17595
+ init_dist();
17596
+ init_package_paths();
17597
+ import { execSync as execSync4 } from "node:child_process";
17598
+ import { readFile as readFile18, writeFile as writeFile11 } from "node:fs/promises";
17599
+ import { resolve as resolve19 } from "node:path";
17600
+ async function loadRegressionChecklist() {
17601
+ try {
17602
+ const path = resolve19(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
17603
+ const content = await readFile18(path, "utf-8");
17604
+ const data = $parse(content);
17605
+ return data.checks ?? [];
17606
+ } catch {
17607
+ return [
17608
+ { id: "doctor_config", command: "npx cc-codeconductor doctor", required: false }
17609
+ ];
17610
+ }
17611
+ }
17612
+ async function runRegressionChecklist(projectRoot, options = {}) {
17613
+ try {
17614
+ const defs = await loadRegressionChecklist();
17615
+ const checks = [];
17616
+ for (const def of defs) {
17617
+ if (def.type === "diff_scope") {
17618
+ checks.push({
17619
+ id: def.id,
17620
+ required: def.required ?? false,
17621
+ passed: true,
17622
+ message: "diff_scope check requires task context; skipped in CLI (manual in workflow)"
17623
+ });
17624
+ continue;
17625
+ }
17626
+ const cmd = options.command && def.id === "tests_pass" ? options.command : def.command;
17627
+ if (!cmd)
17628
+ continue;
17629
+ try {
17630
+ execSync4(cmd, {
17631
+ cwd: projectRoot,
17632
+ encoding: "utf-8",
17633
+ stdio: ["ignore", "pipe", "pipe"],
17634
+ timeout: 120000
17635
+ });
17636
+ checks.push({
17637
+ id: def.id,
17638
+ required: def.required ?? true,
17639
+ passed: true,
17640
+ message: `Command succeeded: ${cmd}`
17641
+ });
17642
+ } catch (e) {
17643
+ const msg = e instanceof Error ? e.message : String(e);
17644
+ checks.push({
17645
+ id: def.id,
17646
+ required: def.required ?? true,
17647
+ passed: false,
17648
+ message: `Command failed: ${cmd} — ${msg.slice(0, 200)}`
17649
+ });
17650
+ }
17651
+ }
17652
+ const report = {
17653
+ passed: checks.every((c) => !c.required || c.passed),
17654
+ checks,
17655
+ timestamp: new Date().toISOString()
17656
+ };
17657
+ const outPath = resolve19(projectRoot, ".codeconductor/evaluation/regression-last.json");
17658
+ await writeFile11(outPath, JSON.stringify(report, null, 2), "utf-8");
17659
+ return ok(report);
17660
+ } catch (e) {
17661
+ return err(e instanceof Error ? e : new Error(String(e)));
17662
+ }
17663
+ }
17664
+
17665
+ // src/core/evaluation/model-comparison.ts
17666
+ function generateModelComparisonMarkdown(outcomes, modelsFilter) {
17667
+ const agg = aggregateOutcomes(outcomes);
17668
+ const models = modelsFilter ?? Object.keys(agg.byModel);
17669
+ const lines = [
17670
+ "# Model Comparison Report",
17671
+ "",
17672
+ `Generated: ${new Date().toISOString()}`,
17673
+ "",
17674
+ "| Model | Tasks | Pass % | Avg score | Avg tokens | Avg cost |",
17675
+ "|-------|-------|--------|-----------|------------|----------|"
17676
+ ];
17677
+ for (const model of models) {
17678
+ const entry = agg.byModel[model];
17679
+ if (!entry)
17680
+ continue;
17681
+ const modelOutcomes = outcomes.filter((o) => o.model === model);
17682
+ const passed = modelOutcomes.filter((o) => o.verdict === "PASS" || o.status === "pass");
17683
+ const passPct = modelOutcomes.length > 0 ? passed.length / modelOutcomes.length * 100 : 0;
17684
+ lines.push(`| ${model} | ${entry.count} | ${passPct.toFixed(0)}% | ${entry.avgScore.toFixed(2)} | ${entry.avgTokens?.toFixed(0) ?? "—"} | ${entry.avgCost?.toFixed(4) ?? "—"} |`);
17685
+ }
17686
+ lines.push("", "## Notes", "", "- Cost and tokens are optional; reported by agents or CLI flags.");
17687
+ return lines.join(`
17688
+ `);
17689
+ }
17690
+
17691
+ // src/core/evaluation/cost-quality-matrix.ts
17692
+ init_dist();
17693
+ import { writeFile as writeFile12 } from "node:fs/promises";
17694
+ import { resolve as resolve20 } from "node:path";
17695
+ async function buildCostQualityMatrix(projectRoot, outcomes) {
17696
+ const agg = aggregateOutcomes(outcomes);
17697
+ const rows = Object.entries(agg.byModel).map(([model, entry]) => {
17698
+ const modelOutcomes = outcomes.filter((o) => o.model === model);
17699
+ const passed = modelOutcomes.filter((o) => o.verdict === "PASS" || o.status === "pass");
17700
+ return {
17701
+ model,
17702
+ taskCount: entry.count,
17703
+ passRate: modelOutcomes.length > 0 ? passed.length / modelOutcomes.length : 0,
17704
+ avgWeightedScore: entry.avgScore,
17705
+ avgTokens: entry.avgTokens,
17706
+ avgCostUsd: entry.avgCost
17707
+ };
17708
+ });
17709
+ const matrix = {
17710
+ generatedAt: new Date().toISOString(),
17711
+ rows
17712
+ };
17713
+ const dir = await ensureEvaluationDir(projectRoot);
17714
+ await writeFile12(resolve20(dir, "matrix.yml"), $stringify(matrix), "utf-8");
17715
+ return matrix;
17716
+ }
17717
+ function formatMatrixMarkdown(matrix) {
17718
+ const lines = [
17719
+ "# Cost / Quality Matrix",
17720
+ "",
17721
+ `Generated: ${matrix.generatedAt}`,
17722
+ "",
17723
+ "| Model | Tasks | Pass rate | Avg score | Avg tokens | Avg cost USD |",
17724
+ "|-------|-------|-----------|-----------|------------|--------------|"
17725
+ ];
17726
+ for (const row of matrix.rows) {
17727
+ lines.push(`| ${row.model} | ${row.taskCount} | ${(row.passRate * 100).toFixed(0)}% | ${row.avgWeightedScore.toFixed(2)} | ${row.avgTokens?.toFixed(0) ?? "—"} | ${row.avgCostUsd?.toFixed(4) ?? "—"} |`);
17728
+ }
17729
+ return lines.join(`
17730
+ `);
17731
+ }
17732
+
17733
+ // src/commands/scorecard.command.ts
17734
+ function contractVersion() {
17735
+ return package_default.version;
17736
+ }
17737
+ async function scorecardCommand(options) {
17738
+ const { subcommand, projectRoot } = options;
17739
+ switch (subcommand) {
17740
+ case "create":
17741
+ return handleCreate(projectRoot, options);
17742
+ case "show":
17743
+ return handleShow(projectRoot, options.id);
17744
+ case "record":
17745
+ return handleRecord(projectRoot, options);
17746
+ case "list":
17747
+ return handleList(projectRoot, options);
17748
+ case "aggregate":
17749
+ return handleAggregate(projectRoot, options);
17750
+ case "models":
17751
+ return handleModels(projectRoot, options.profile);
17752
+ case "prompt-diff":
17753
+ return handlePromptDiff(projectRoot, options);
17754
+ case "regression":
17755
+ return handleRegression(projectRoot);
17756
+ case "matrix":
17757
+ return handleMatrix(projectRoot);
17758
+ case "compare-models":
17759
+ return handleCompareModels(projectRoot, options);
17760
+ default:
17761
+ return {
17762
+ code: 1,
17763
+ data: {
17764
+ success: false,
17765
+ command: "scorecard",
17766
+ errors: [
17767
+ `Unknown subcommand: ${subcommand}. Use: create, show, record, list, aggregate, models, prompt-diff, regression, matrix, compare-models`
17768
+ ]
17769
+ }
17770
+ };
17771
+ }
17772
+ }
17773
+ async function handleCreate(projectRoot, options) {
17774
+ const taskId = options.taskId ?? "unknown-task";
17775
+ const agent = options.agent ?? "reviewer";
17776
+ let criteria = createDefaultCriteria();
17777
+ let findings = [];
17778
+ if (options.fromDiff) {
17779
+ let scopeFiles;
17780
+ const backlog = await loadBacklog(projectRoot);
17781
+ if (backlog.success && options.taskId) {
17782
+ const item = backlog.data.items.find((i) => i.id === options.taskId);
17783
+ if (item) {
17784
+ scopeFiles = item.scope.split(/[,;]/).map((s) => s.trim()).filter(Boolean);
17785
+ }
17786
+ }
17787
+ const hints = collectScorecardSignals(projectRoot, scopeFiles);
17788
+ criteria = criteriaFromSignals(hints);
17789
+ findings = hints.findings;
17790
+ }
17791
+ const id = generateEvalId("sc");
17792
+ const configResult = await loadConfig(projectRoot);
17793
+ const target = configResult.success ? configResult.data.defaults.target : "opencode";
17794
+ const modelResult = await resolvePhaseModels(projectRoot, target);
17795
+ const model = options.model ?? (modelResult.success ? modelResult.data.find((r) => r.agent === agent)?.model : undefined);
17796
+ const record = buildScorecardRecord({
17797
+ id,
17798
+ taskId,
17799
+ agent,
17800
+ contractVersion: contractVersion(),
17801
+ criteria,
17802
+ model,
17803
+ findings,
17804
+ backlogId: options.taskId?.startsWith("BC-") ? options.taskId : undefined,
17805
+ source: options.source ?? "manual"
17806
+ });
17807
+ const save = await saveScorecard(projectRoot, record);
17808
+ if (!save.success) {
17809
+ return { code: 1, data: { success: false, errors: [save.error.message] } };
17810
+ }
17811
+ return {
17812
+ code: 0,
17813
+ data: {
17814
+ success: true,
17815
+ command: "scorecard create",
17816
+ scorecard: record,
17817
+ path: save.data
17818
+ }
17819
+ };
17820
+ }
17821
+ async function handleShow(projectRoot, id) {
17822
+ if (!id) {
17823
+ return {
17824
+ code: 1,
17825
+ data: { success: false, errors: ["Scorecard id required. Usage: scorecard show <id>"] }
17826
+ };
17827
+ }
17828
+ const result = await loadScorecard(projectRoot, id);
17829
+ if (!result.success) {
17830
+ return { code: 1, data: { success: false, errors: [result.error.message] } };
17831
+ }
17832
+ return { code: 0, data: { success: true, command: "scorecard show", scorecard: result.data } };
17833
+ }
17834
+ async function handleRecord(projectRoot, options) {
17835
+ const taskId = options.taskId ?? "unknown-task";
17836
+ const outcome = {
17837
+ id: generateEvalId("out"),
17838
+ taskId,
17839
+ source: options.source ?? "manual",
17840
+ agent: options.agent ?? "reviewer",
17841
+ model: options.model ?? "unknown",
17842
+ contractVersion: contractVersion(),
17843
+ timestamp: new Date().toISOString(),
17844
+ verdict: options.verdict,
17845
+ weightedScore: options.weightedScore,
17846
+ backlogId: taskId.startsWith("BC-") ? taskId : undefined,
17847
+ costUsd: options.costUsd,
17848
+ tokensIn: options.tokens,
17849
+ durationMs: options.durationMs,
17850
+ status: options.verdict === "PASS" ? "pass" : options.verdict === "REJECT" ? "reject" : undefined
17851
+ };
17852
+ const result = await appendOutcome(projectRoot, outcome);
17853
+ if (!result.success) {
17854
+ return { code: 1, data: { success: false, errors: [result.error.message] } };
17855
+ }
17856
+ return { code: 0, data: { success: true, command: "scorecard record", outcome } };
17857
+ }
17858
+ async function handleList(projectRoot, options) {
17859
+ const filter = {
17860
+ agent: options.agent,
17861
+ model: options.model,
17862
+ taskId: options.taskId,
17863
+ since: options.since,
17864
+ source: options.source
17865
+ };
17866
+ const result = await listOutcomes(projectRoot, filter);
17867
+ if (!result.success) {
17868
+ return { code: 1, data: { success: false, errors: [result.error.message] } };
17869
+ }
17870
+ return {
17871
+ code: 0,
17872
+ data: { success: true, command: "scorecard list", outcomes: result.data, count: result.data.length }
17873
+ };
17874
+ }
17875
+ async function handleAggregate(projectRoot, options) {
17876
+ const filter = { since: options.since, agent: options.agent, model: options.model };
17877
+ const list = await listOutcomes(projectRoot, filter);
17878
+ if (!list.success) {
17879
+ return { code: 1, data: { success: false, errors: [list.error.message] } };
17880
+ }
17881
+ const agg = aggregateOutcomes(list.data);
17882
+ return { code: 0, data: { success: true, command: "scorecard aggregate", ...agg } };
17883
+ }
17884
+ async function handleModels(projectRoot, profile) {
17885
+ const configResult = await loadConfig(projectRoot);
17886
+ const target = configResult.success ? configResult.data.defaults.target : "opencode";
17887
+ const result = await resolvePhaseModels(projectRoot, target);
17888
+ if (!result.success) {
17889
+ return { code: 1, data: { success: false, errors: [result.error.message] } };
17890
+ }
17891
+ return {
17892
+ code: 0,
17893
+ data: {
17894
+ success: true,
17895
+ command: "scorecard models",
17896
+ profile: profile ?? "balanced",
17897
+ phases: result.data
17898
+ }
17899
+ };
17900
+ }
17901
+ async function handlePromptDiff(projectRoot, options) {
17902
+ if (!options.fromVersion || !options.toVersion) {
17903
+ return {
17904
+ code: 1,
17905
+ data: {
17906
+ success: false,
17907
+ errors: ["Usage: scorecard prompt-diff <fromVersion> <toVersion>"]
17908
+ }
17909
+ };
17910
+ }
17911
+ const configResult = await loadConfig(projectRoot);
17912
+ const target = configResult.success ? configResult.data.defaults.target : "opencode";
17913
+ const diff = await diffPromptVersions(options.fromVersion, options.toVersion, {
17914
+ target,
17915
+ agent: options.agent,
17916
+ projectRoot
17917
+ });
17918
+ const markdown = formatPromptDiffMarkdown(diff);
17919
+ return {
17920
+ code: 0,
17921
+ data: { success: true, command: "scorecard prompt-diff", diff, markdown }
17922
+ };
17923
+ }
17924
+ async function handleRegression(projectRoot) {
17925
+ const result = await runRegressionChecklist(projectRoot);
17926
+ if (!result.success) {
17927
+ return { code: 1, data: { success: false, errors: [result.error.message] } };
17928
+ }
17929
+ return {
17930
+ code: result.data.passed ? 0 : 1,
17931
+ data: { success: result.data.passed, command: "scorecard regression", report: result.data }
17932
+ };
17933
+ }
17934
+ async function handleMatrix(projectRoot) {
17935
+ const list = await listOutcomes(projectRoot);
17936
+ if (!list.success) {
17937
+ return { code: 1, data: { success: false, errors: [list.error.message] } };
17938
+ }
17939
+ const matrix = await buildCostQualityMatrix(projectRoot, list.data);
17940
+ const markdown = formatMatrixMarkdown(matrix);
17941
+ return {
17942
+ code: 0,
17943
+ data: { success: true, command: "scorecard matrix", matrix, markdown }
17944
+ };
17945
+ }
17946
+ async function handleCompareModels(projectRoot, options) {
17947
+ const filter = { since: options.since };
17948
+ const list = await listOutcomes(projectRoot, filter);
17949
+ if (!list.success) {
17950
+ return { code: 1, data: { success: false, errors: [list.error.message] } };
17951
+ }
17952
+ let outcomes = list.data;
17953
+ if (options.modelsFilter && options.modelsFilter.length > 0) {
17954
+ outcomes = outcomes.filter((o) => options.modelsFilter.includes(o.model));
17955
+ }
17956
+ const markdown = generateModelComparisonMarkdown(outcomes, options.modelsFilter);
17957
+ const templatePath = resolve21(projectRoot, ".codeconductor/evaluation/model-comparison-last.md");
17958
+ try {
17959
+ await readFile19(templatePath);
17960
+ } catch {}
17961
+ const { writeFile: writeFile13, mkdir: mkdir11 } = await import("node:fs/promises");
17962
+ await mkdir11(resolve21(projectRoot, ".codeconductor/evaluation"), { recursive: true });
17963
+ await writeFile13(templatePath, markdown, "utf-8");
17964
+ return {
17965
+ code: 0,
17966
+ data: {
17967
+ success: true,
17968
+ command: "scorecard compare-models",
17969
+ markdown,
17970
+ path: templatePath,
17971
+ outcomeCount: outcomes.length
17972
+ }
17973
+ };
17974
+ }
17975
+
17976
+ // src/commands/help.command.ts
17977
+ import { readdir as readdir4 } from "node:fs/promises";
17978
+ import { join as join9 } from "node:path";
17979
+ async function scanPresetDir(projectRoot, target) {
17980
+ const inventory = {
17981
+ target,
17982
+ skills: [],
17983
+ commands: [],
17984
+ agents: [],
17985
+ workflows: []
17986
+ };
17987
+ const presetDir = join9(projectRoot, "presets", target);
17988
+ try {
17989
+ const entries = await readdir4(presetDir, { withFileTypes: true });
17990
+ for (const entry of entries) {
17991
+ if (entry.isDirectory()) {
17992
+ const subDir = join9(presetDir, entry.name);
17993
+ const subEntries = await readdir4(subDir, { withFileTypes: true });
17994
+ for (const sub of subEntries) {
17995
+ if (sub.isFile()) {
17996
+ const itemName = sub.name.replace(/\.[^.]+$/, "");
17997
+ if (entry.name === "skills")
17998
+ inventory.skills.push(itemName);
17999
+ else if (entry.name === "commands")
18000
+ inventory.commands.push(itemName);
18001
+ else if (entry.name === "agents")
18002
+ inventory.agents.push(itemName);
18003
+ else if (entry.name === "workflows")
18004
+ inventory.workflows.push(itemName);
18005
+ } else if (sub.isDirectory()) {
18006
+ if (entry.name === "skills")
18007
+ inventory.skills.push(sub.name);
18008
+ else if (entry.name === "agents")
18009
+ inventory.agents.push(sub.name);
18010
+ }
18011
+ }
18012
+ }
18013
+ }
18014
+ } catch {}
18015
+ return inventory;
18016
+ }
18017
+ function renderHuman(inventory, defaultTarget) {
18018
+ const lines = [
18019
+ `CodeConductor Help — ${inventory.target}${inventory.target === defaultTarget ? " (active)" : ""}`,
18020
+ ""
18021
+ ];
18022
+ lines.push(`Skills (${inventory.skills.length}):`);
18023
+ if (inventory.skills.length === 0) {
18024
+ lines.push(" (none)");
18025
+ } else {
18026
+ for (const skill of inventory.skills.sort()) {
18027
+ lines.push(` - ${skill}`);
18028
+ }
18029
+ }
18030
+ lines.push("");
18031
+ lines.push(`Subagents (${inventory.agents.length}):`);
18032
+ if (inventory.agents.length === 0) {
18033
+ lines.push(" (none)");
18034
+ } else {
18035
+ for (const agent of inventory.agents.sort()) {
18036
+ lines.push(` - ${agent}`);
18037
+ }
18038
+ }
18039
+ lines.push("");
18040
+ lines.push(`Commands (${inventory.commands.length}):`);
18041
+ if (inventory.commands.length === 0) {
18042
+ lines.push(" (none)");
18043
+ } else {
18044
+ for (const cmd of inventory.commands.sort()) {
18045
+ lines.push(` - ${cmd}`);
18046
+ }
18047
+ }
18048
+ if (inventory.workflows.length > 0) {
18049
+ lines.push("");
18050
+ lines.push(`Workflows (${inventory.workflows.length}):`);
18051
+ for (const wf of inventory.workflows.sort()) {
18052
+ lines.push(` - ${wf}`);
18053
+ }
18054
+ }
18055
+ lines.push("");
18056
+ lines.push("Stack-specific presets (v0.4.0):");
18057
+ lines.push(" ts-next-drizzle Next.js / Astro, Tailwind, Drizzle ORM, Bun, Postgres");
18058
+ lines.push(" spring-kotlin-jpa Spring Boot, Kotlin/Java, Gradle, JPA, Hibernate");
18059
+ lines.push(" laravel-tall Laravel, Blade, Livewire, Alpine.js");
18060
+ lines.push(" python-data-api Python, FastAPI, Django, uv");
18061
+ lines.push(" Browse: presets/<preset-name>/agents/ — copy into the target runner.");
18062
+ lines.push(" Registry: src/core/presets/preset-registry.ts (listPresets / getPreset).");
18063
+ return lines.join(`
18064
+ `);
18065
+ }
18066
+ async function helpCommand(options) {
18067
+ const { projectRoot, target: overrideTarget, output } = options;
18068
+ try {
18069
+ let defaultTarget = "opencode";
18070
+ try {
18071
+ const configResult = await loadConfig(projectRoot);
18072
+ if (configResult.success) {
18073
+ defaultTarget = configResult.data.defaults.target;
18074
+ }
18075
+ } catch {}
18076
+ const target = overrideTarget ?? defaultTarget;
18077
+ const inventory = await scanPresetDir(projectRoot, target);
18078
+ if (output === "json") {
18079
+ return {
18080
+ code: 0,
18081
+ data: {
18082
+ success: true,
18083
+ command: "help",
18084
+ inventory,
18085
+ defaultTarget
18086
+ }
18087
+ };
18088
+ }
18089
+ return {
18090
+ code: 0,
18091
+ data: {
18092
+ success: true,
18093
+ command: "help",
18094
+ message: renderHuman(inventory, defaultTarget),
18095
+ inventory
18096
+ }
18097
+ };
18098
+ } catch (error) {
18099
+ return {
18100
+ code: 1,
18101
+ data: {
18102
+ success: false,
18103
+ command: "help",
18104
+ errors: [String(error)]
18105
+ }
18106
+ };
18107
+ }
18108
+ }
18109
+
18110
+ // src/commands/seo-audit.command.ts
18111
+ import { writeFile as writeFile13, mkdir as mkdir11 } from "node:fs/promises";
18112
+ import { dirname as dirname4, resolve as resolve22 } from "node:path";
18113
+
18114
+ // src/infrastructure/http/safe-fetch.ts
18115
+ import { lookup } from "node:dns/promises";
18116
+ var PRIVATE_IP_RANGES = [
18117
+ /^10\./,
18118
+ /^172\.(1[6-9]|2\d|3[01])\./,
18119
+ /^192\.168\./,
18120
+ /^127\./,
18121
+ /^0\./,
18122
+ /^169\.254\./,
18123
+ /^::1$/,
18124
+ /^fc00:/i,
18125
+ /^fe80:/i
18126
+ ];
18127
+ function isPrivateIp(ip) {
18128
+ return PRIVATE_IP_RANGES.some((range) => range.test(ip));
18129
+ }
18130
+ async function validateUrl(urlString) {
18131
+ let parsed;
18132
+ try {
18133
+ parsed = new URL(urlString);
18134
+ } catch {
18135
+ throw new Error(`Invalid URL: ${urlString}`);
18136
+ }
18137
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
18138
+ throw new Error(`Blocked scheme: ${parsed.protocol}. Only http and https are allowed.`);
18139
+ }
18140
+ const hostname = parsed.hostname;
18141
+ if (hostname === "localhost" || hostname.endsWith(".localhost")) {
18142
+ throw new Error(`Blocked hostname: ${hostname}`);
18143
+ }
18144
+ try {
18145
+ const addresses = await lookup(hostname, { all: true });
18146
+ const addrList = Array.isArray(addresses) ? addresses : [addresses];
18147
+ for (const addr of addrList) {
18148
+ if (isPrivateIp(addr.address)) {
18149
+ throw new Error(`SSRF blocked: ${hostname} resolves to private IP ${addr.address}`);
18150
+ }
18151
+ }
18152
+ } catch (error) {
18153
+ if (error instanceof Error && error.message.startsWith("SSRF blocked")) {
18154
+ throw error;
18155
+ }
18156
+ throw new Error(`DNS resolution failed for ${hostname}: ${String(error)}`);
18157
+ }
18158
+ }
18159
+ async function safeFetch(urlString, options = {}) {
18160
+ const { timeout = 1e4, followRedirects = false } = options;
18161
+ await validateUrl(urlString);
18162
+ const controller = new AbortController;
18163
+ const timer = setTimeout(() => controller.abort(), timeout);
18164
+ const start = Date.now();
18165
+ try {
18166
+ const response = await fetch(urlString, {
18167
+ method: "GET",
18168
+ signal: controller.signal,
18169
+ redirect: followRedirects ? "follow" : "manual",
18170
+ headers: {
15819
18171
  "User-Agent": "CodeConductor-SEO/0.3.0",
15820
18172
  Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
15821
18173
  }
@@ -15844,7 +18196,7 @@ async function safeFetch(urlString, options = {}) {
15844
18196
  }
15845
18197
  }
15846
18198
  async function delay(ms) {
15847
- return new Promise((resolve11) => setTimeout(resolve11, ms));
18199
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
15848
18200
  }
15849
18201
 
15850
18202
  // src/infrastructure/parsers/sitemap-parser.ts
@@ -17048,14 +19400,14 @@ async function seoAuditCommand(options) {
17048
19400
  formattedOutput = formatCli(report);
17049
19401
  }
17050
19402
  if (output) {
17051
- const outputPath = resolve11(options.projectRoot, output);
17052
- await mkdir8(dirname4(outputPath), { recursive: true });
17053
- await writeFile7(outputPath, formattedOutput, "utf-8");
19403
+ const outputPath = resolve22(options.projectRoot, output);
19404
+ await mkdir11(dirname4(outputPath), { recursive: true });
19405
+ await writeFile13(outputPath, formattedOutput, "utf-8");
17054
19406
  } else if (format === "markdown") {
17055
19407
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
17056
- const defaultPath = resolve11(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
17057
- await mkdir8(dirname4(defaultPath), { recursive: true });
17058
- await writeFile7(defaultPath, formattedOutput, "utf-8");
19408
+ const defaultPath = resolve22(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
19409
+ await mkdir11(dirname4(defaultPath), { recursive: true });
19410
+ await writeFile13(defaultPath, formattedOutput, "utf-8");
17059
19411
  process.stderr.write(`Report saved to: ${defaultPath}
17060
19412
  `);
17061
19413
  }
@@ -17067,7 +19419,7 @@ async function seoAuditCommand(options) {
17067
19419
  command: "seo audit",
17068
19420
  report,
17069
19421
  output: formattedOutput,
17070
- outputFile: output ? resolve11(options.projectRoot, output) : format === "markdown" ? resolve11(options.projectRoot, "seo-reports") : undefined
19422
+ outputFile: output ? resolve22(options.projectRoot, output) : format === "markdown" ? resolve22(options.projectRoot, "seo-reports") : undefined
17071
19423
  }
17072
19424
  };
17073
19425
  } catch (error) {
@@ -17083,8 +19435,8 @@ async function seoAuditCommand(options) {
17083
19435
  }
17084
19436
 
17085
19437
  // src/commands/seo-llms.command.ts
17086
- import { writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
17087
- import { dirname as dirname5, resolve as resolve12 } from "node:path";
19438
+ import { writeFile as writeFile14, mkdir as mkdir12 } from "node:fs/promises";
19439
+ import { dirname as dirname5, resolve as resolve23 } from "node:path";
17088
19440
 
17089
19441
  // src/domain/seo/llms-generator.ts
17090
19442
  function extractTitle2(html) {
@@ -17227,9 +19579,9 @@ async function seoLlmsCommand(options) {
17227
19579
  }
17228
19580
  }) : await generateLlmsTxtFromUrl(url);
17229
19581
  process.stderr.write("\r" + " ".repeat(80) + "\r");
17230
- const outputPath = output ? resolve12(options.projectRoot, output) : resolve12(options.projectRoot, "llms.txt");
17231
- await mkdir9(dirname5(outputPath), { recursive: true });
17232
- await writeFile8(outputPath, result.content, "utf-8");
19582
+ const outputPath = output ? resolve23(options.projectRoot, output) : resolve23(options.projectRoot, "llms.txt");
19583
+ await mkdir12(dirname5(outputPath), { recursive: true });
19584
+ await writeFile14(outputPath, result.content, "utf-8");
17233
19585
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
17234
19586
  `);
17235
19587
  return {
@@ -17256,8 +19608,8 @@ async function seoLlmsCommand(options) {
17256
19608
 
17257
19609
  // src/commands/update.command.ts
17258
19610
  import { homedir as homedir9 } from "node:os";
17259
- import { resolve as resolve13, dirname as dirname6 } from "node:path";
17260
- import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile9, stat as stat3 } from "node:fs/promises";
19611
+ import { resolve as resolve24, dirname as dirname6 } from "node:path";
19612
+ import { mkdir as mkdir13, readFile as readFile20, writeFile as writeFile15, stat as stat3 } from "node:fs/promises";
17261
19613
  init_package_paths();
17262
19614
  async function updateCommand(options) {
17263
19615
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
@@ -17323,23 +19675,23 @@ async function updateCommand(options) {
17323
19675
  }
17324
19676
  const updated = [];
17325
19677
  if (updateResults.council) {
17326
- const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
17327
- const bundledCouncil = resolve13(SRC_PRESETS_DIR, "council", "council.yml");
19678
+ const localCouncil = resolve24(basePath, ".codeconductor", "presets", "council.yml");
19679
+ const bundledCouncil = resolve24(SRC_PRESETS_DIR, "council", "council.yml");
17328
19680
  try {
17329
- const content = await readFile11(bundledCouncil, "utf-8");
17330
- await mkdir10(dirname6(localCouncil), { recursive: true });
17331
- await writeFile9(localCouncil, content, "utf-8");
19681
+ const content = await readFile20(bundledCouncil, "utf-8");
19682
+ await mkdir13(dirname6(localCouncil), { recursive: true });
19683
+ await writeFile15(localCouncil, content, "utf-8");
17332
19684
  updated.push(localCouncil);
17333
19685
  } catch (e) {
17334
19686
  throw new Error(`Failed to update council.yml: ${e}`);
17335
19687
  }
17336
19688
  }
17337
19689
  if (updateResults.policy) {
17338
- const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
19690
+ const localPolicy = resolve24(basePath, ".codeconductor", "presets", "policy.yml");
17339
19691
  try {
17340
- const content = await readFile11(POLICY_PATH, "utf-8");
17341
- await mkdir10(dirname6(localPolicy), { recursive: true });
17342
- await writeFile9(localPolicy, content, "utf-8");
19692
+ const content = await readFile20(POLICY_PATH, "utf-8");
19693
+ await mkdir13(dirname6(localPolicy), { recursive: true });
19694
+ await writeFile15(localPolicy, content, "utf-8");
17343
19695
  updated.push(localPolicy);
17344
19696
  } catch (e) {
17345
19697
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -17391,12 +19743,12 @@ async function updateCommand(options) {
17391
19743
  let targetPath = f.path;
17392
19744
  let targetBase = basePath;
17393
19745
  if ((targetName === "agy" || targetName === "gemini") && isGlobal) {
17394
- targetBase = resolve13(homedir9(), ".gemini", "config");
19746
+ targetBase = resolve24(homedir9(), ".gemini", "config");
17395
19747
  targetPath = targetPath.replace(/^\.agents\/?/, "");
17396
19748
  }
17397
19749
  return {
17398
19750
  ...f,
17399
- path: resolve13(targetBase, targetPath)
19751
+ path: resolve24(targetBase, targetPath)
17400
19752
  };
17401
19753
  });
17402
19754
  const filesToWrite = resolvedFiles.filter((f) => t.files.includes(f.path));
@@ -17424,19 +19776,19 @@ async function updateCommand(options) {
17424
19776
  for (const s of updateResults.skills) {
17425
19777
  newSkillsLock[s.id] = s.latestVersion;
17426
19778
  }
17427
- let lockDest = resolve13(basePath, ".codeconductor", "skills-lock.json");
19779
+ let lockDest = resolve24(basePath, ".codeconductor", "skills-lock.json");
17428
19780
  try {
17429
- const statAgents = await stat3(resolve13(basePath, ".agents"));
19781
+ const statAgents = await stat3(resolve24(basePath, ".agents"));
17430
19782
  if (statAgents.isDirectory()) {
17431
- const statCodeConductor = await stat3(resolve13(basePath, ".codeconductor")).catch(() => null);
19783
+ const statCodeConductor = await stat3(resolve24(basePath, ".codeconductor")).catch(() => null);
17432
19784
  if (!statCodeConductor) {
17433
- lockDest = resolve13(basePath, ".agents", "skills-lock.json");
19785
+ lockDest = resolve24(basePath, ".agents", "skills-lock.json");
17434
19786
  }
17435
19787
  }
17436
19788
  } catch {}
17437
19789
  try {
17438
- await mkdir10(dirname6(lockDest), { recursive: true });
17439
- await writeFile9(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
19790
+ await mkdir13(dirname6(lockDest), { recursive: true });
19791
+ await writeFile15(lockDest, JSON.stringify(newSkillsLock, null, 2), "utf-8");
17440
19792
  updated.push(lockDest);
17441
19793
  } catch (e) {
17442
19794
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -17507,25 +19859,38 @@ function parseArgs(args) {
17507
19859
  }
17508
19860
  const command = remaining[0] || "help";
17509
19861
  const subcommand = remaining[1] && !remaining[1].startsWith("-") ? remaining[1] : undefined;
19862
+ const consumed = new Set;
17510
19863
  for (let i = 1;i < remaining.length; i++) {
17511
19864
  const arg = remaining[i];
17512
19865
  if (arg.startsWith("--")) {
17513
19866
  const [key, value] = arg.slice(2).split("=");
17514
19867
  if (key === "lang") {
17515
19868
  const langValue = value !== undefined ? value : remaining[++i];
19869
+ consumed.add(i);
17516
19870
  if (langValue && typeof langValue === "string") {
17517
19871
  options[key] = langValue.split(",").map((s) => s.trim());
17518
19872
  }
17519
19873
  } else if (value !== undefined) {
17520
19874
  options[key] = value;
19875
+ consumed.add(i);
17521
19876
  } else if (remaining[i + 1] && !remaining[i + 1].startsWith("-")) {
17522
19877
  options[key] = remaining[++i];
19878
+ consumed.add(i - 1);
19879
+ consumed.add(i);
17523
19880
  } else {
17524
19881
  options[key] = true;
19882
+ consumed.add(i);
17525
19883
  }
17526
19884
  }
17527
19885
  }
17528
- return { command, subcommand, options, flags };
19886
+ const rest = [];
19887
+ const startIdx = subcommand ? 2 : 1;
19888
+ for (let i = startIdx;i < remaining.length; i++) {
19889
+ if (!consumed.has(i) && !remaining[i].startsWith("-")) {
19890
+ rest.push(remaining[i]);
19891
+ }
19892
+ }
19893
+ return { command, subcommand, options, flags, rest };
17529
19894
  }
17530
19895
  function getVersion() {
17531
19896
  return `${package_default.name} v${package_default.version}`;
@@ -17537,27 +19902,47 @@ Usage: npx cc-codeconductor <command> [options]
17537
19902
 
17538
19903
  Commands:
17539
19904
  init Initialize CodeConductor in a project
17540
- detect Detect project stack and recommended presets
17541
- install council Install generated council spec files to runner targets
17542
- install preset Install full preset (agents, prompts, skills, commands)
17543
- install lsp Install and configure LSP servers for AI coding tools
17544
- seo audit Run SEO audit on a URL or sitemap
17545
- seo llms Generate llms.txt from a URL or sitemap
17546
- doctor Validate configuration and generated files
17547
- update Update installed presets
17548
- help / cc-help Show preset inventory (skills, subagents, commands)
17549
- debt-harvest / harvest Scan source files for deferred debt items
17550
- goal / cc-goal Plan goal into task graph with dependencies
19905
+ detect Detect project stack and recommended presets
19906
+ install council Install generated council spec files to runner targets
19907
+ install preset Install full preset (agents, prompts, skills, commands)
19908
+ install lsp Install and configure LSP servers for AI coding tools
19909
+ seo audit Run SEO audit on a URL or sitemap
19910
+ seo llms Generate llms.txt from a URL or sitemap
19911
+ doctor Validate configuration and generated files
19912
+ update Update installed presets
19913
+ help / cc-help Show preset inventory (skills, subagents, commands)
19914
+ debt-harvest / harvest Scan source files for deferred debt items
19915
+ goal / cc-goal Plan goal into task graph with dependencies
17551
19916
 
17552
19917
  Options:
17553
- --help, -h Show this help message
17554
- --version, -v Show package version
17555
- --dry-run Show what would happen without writing files
17556
- --force Allow overwriting existing files
17557
- --global Install to home directory (~/.claude, ~/.opencode, etc.)
17558
- --output, -o Output mode: human or json
17559
- --lang Comma-separated list of languages (e.g., typescript,php,python)
17560
- --locale Instruction language for agent files: en (default) | es
19918
+ --help, -h Show this help message
19919
+ --version, -v Show package version
19920
+ --dry-run Show what would happen without writing files
19921
+ --force Allow overwriting existing files
19922
+ --global Install to home directory (~/.claude, ~/.opencode, etc.)
19923
+ --output, -o Output mode: human or json
19924
+ --lang Comma-separated list of languages (e.g., typescript,php,python)
19925
+ --locale Instruction language for agent files: en (default) | es
19926
+ --target Runner target: opencode, claude, codex, gemini, cursor, agy, all
19927
+
19928
+ Stack-specific presets (v0.4.0, registered in preset-registry):
19929
+ ts-next-drizzle Next.js / Astro, Tailwind, Drizzle ORM, Bun, Postgres
19930
+ spring-kotlin-jpa Spring Boot, Kotlin/Java, Gradle, JPA, Hibernate
19931
+ laravel-tall Laravel, Blade, Livewire, Alpine.js
19932
+ python-data-api Python, FastAPI, Django, uv
19933
+ Listed programmatically via listPresets() in src/core/presets/preset-registry.ts.
19934
+ Stack detection wires matching specialized skills automatically when
19935
+ init/detect identifies the stack.
19936
+
19937
+ Workflow Loop Core (v0.4.0):
19938
+ runWorkflowPipeline() in src/core/pipeline/workflow-loop.ts runs the
19939
+ 8-phase loop (intake -> structure -> design -> test -> implement ->
19940
+ validate -> council -> compact) with wall-clock, files-modified and
19941
+ lines-changed guardrails and STOP gates after Design and Council.
19942
+
19943
+ Council consensus v0.4.0:
19944
+ Per-agent confidence thresholds (< 0.6 or average < 0.7 escalate)
19945
+ and a complianceVeto channel that overrides majority like securityVeto.
17561
19946
 
17562
19947
  Examples:
17563
19948
  npx cc-codeconductor init
@@ -17567,6 +19952,7 @@ Examples:
17567
19952
  npx cc-codeconductor install preset --target opencode
17568
19953
  npx cc-codeconductor install preset --target claude
17569
19954
  npx cc-codeconductor install preset --target codex
19955
+ npx cc-codeconductor install preset --target cursor
17570
19956
  npx cc-codeconductor install preset --target agy
17571
19957
  npx cc-codeconductor install preset --target all
17572
19958
  npx cc-codeconductor install preset --target claude --global
@@ -17586,6 +19972,24 @@ Examples:
17586
19972
  npx cc-codeconductor seo audit --sitemap https://example.com/sitemap.xml --format markdown
17587
19973
  npx cc-codeconductor seo llms --sitemap https://example.com/sitemap.xml
17588
19974
  npx cc-codeconductor seo llms --url https://example.com --output llms.txt
19975
+ npx cc-codeconductor goal "Add user authentication"
19976
+ npx cc-codeconductor cc-goal "Implement CRUD for invoices"
19977
+ npx cc-codeconductor openspec validate
19978
+ npx cc-codeconductor openspec scan
19979
+ npx cc-codeconductor openspec plan BC-001
19980
+ npx cc-codeconductor openspec status
19981
+ npx cc-codeconductor openspec next
19982
+ npx cc-codeconductor scorecard create --task BC-001 --from-diff
19983
+ npx cc-codeconductor scorecard models
19984
+ npx cc-codeconductor scorecard aggregate
19985
+ npx cc-codeconductor help --target opencode
19986
+ npx cc-codeconductor help --target claude --output json
19987
+
19988
+ Docs: https://github.com/lgzarturo/codeconductor/tree/main/docs
19989
+ docs/v0.4.0-release-notes.md v0.4.0 feature breakdown
19990
+ docs/routing-policy.md Risk-based routing (v0.4.0)
19991
+ docs/prompt-versioning.md Agent contract versions
19992
+ docs/usage-cli.md Detailed CLI reference
17589
19993
  `;
17590
19994
  }
17591
19995
  async function routeCommand(args, projectRoot) {
@@ -17680,6 +20084,68 @@ async function routeCommand(args, projectRoot) {
17680
20084
  projectRoot,
17681
20085
  output: flags.output
17682
20086
  });
20087
+ case "openspec":
20088
+ case "cc-openspec": {
20089
+ const validSubs = ["validate", "scan", "plan", "status", "next"];
20090
+ let openspecSub = "validate";
20091
+ let itemId = options.item || undefined;
20092
+ if (subcommand && validSubs.includes(subcommand)) {
20093
+ openspecSub = subcommand;
20094
+ itemId = itemId ?? args.rest?.[0];
20095
+ } else if (subcommand && /^BC-\d+$/i.test(subcommand)) {
20096
+ openspecSub = "plan";
20097
+ itemId = subcommand;
20098
+ } else if (subcommand) {
20099
+ openspecSub = subcommand;
20100
+ }
20101
+ return openspecCommand({
20102
+ subcommand: openspecSub,
20103
+ itemId,
20104
+ projectRoot,
20105
+ output: flags.output
20106
+ });
20107
+ }
20108
+ case "scorecard":
20109
+ case "cc-scorecard": {
20110
+ const validSubs = [
20111
+ "create",
20112
+ "show",
20113
+ "record",
20114
+ "list",
20115
+ "aggregate",
20116
+ "models",
20117
+ "prompt-diff",
20118
+ "regression",
20119
+ "matrix",
20120
+ "compare-models"
20121
+ ];
20122
+ let scorecardSub = subcommand && validSubs.includes(subcommand) ? subcommand : "aggregate";
20123
+ if (!subcommand || !validSubs.includes(subcommand)) {
20124
+ scorecardSub = "aggregate";
20125
+ }
20126
+ const modelsFilter = typeof options.models === "string" ? options.models.split(",").map((s) => s.trim()) : undefined;
20127
+ return scorecardCommand({
20128
+ subcommand: scorecardSub,
20129
+ projectRoot,
20130
+ output: flags.output,
20131
+ id: scorecardSub === "show" ? args.rest?.[0] ?? options.id : options.id,
20132
+ taskId: options.task ?? options.item,
20133
+ agent: options.agent,
20134
+ model: options.model,
20135
+ fromDiff: options["from-diff"] === true || options.fromDiff === true,
20136
+ fromVersion: scorecardSub === "prompt-diff" ? args.rest?.[0] ?? options.from : options.from,
20137
+ toVersion: scorecardSub === "prompt-diff" ? args.rest?.[1] ?? options.to : options.to,
20138
+ profile: options.profile,
20139
+ costUsd: options.cost ? parseFloat(String(options.cost)) : undefined,
20140
+ tokens: options.tokens ? parseInt(String(options.tokens), 10) : undefined,
20141
+ durationMs: options.duration ? parseInt(String(options.duration), 10) : undefined,
20142
+ verdict: options.verdict,
20143
+ weightedScore: options.score ? parseFloat(String(options.score)) : undefined,
20144
+ source: options.source,
20145
+ since: options.since,
20146
+ modelsFilter
20147
+ });
20148
+ }
17683
20149
  case "seo": {
17684
20150
  if (subcommand === "audit") {
17685
20151
  return seoAuditCommand({
@@ -17744,6 +20210,8 @@ async function runCli(args) {
17744
20210
  const data = result.data;
17745
20211
  if ("help" in data) {
17746
20212
  console.log(data.help);
20213
+ } else if ("message" in data && typeof data.message === "string") {
20214
+ console.log(data.message);
17747
20215
  } else {
17748
20216
  if ("errors" in data && Array.isArray(data.errors) && data.errors.length > 0) {
17749
20217
  data.errors.forEach((e) => console.error(e));