cc-codeconductor 0.4.3 → 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 (141) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +2642 -226
  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-scorecard.md +17 -0
  8. package/presets/claude/commands/cc/openspec.md +144 -0
  9. package/presets/claude/commands/cc/review.md +13 -2
  10. package/presets/claude/commands/cc/scorecard.md +65 -0
  11. package/presets/claude/skills/evaluation/SKILL.md +42 -0
  12. package/presets/claude/skills/openspec/SKILL.md +54 -0
  13. package/presets/codex/AGENTS.md +57 -0
  14. package/presets/cursor/.cursorignore +15 -0
  15. package/presets/cursor/AGENTS.md +504 -0
  16. package/presets/cursor/agents/architect.md +211 -0
  17. package/presets/cursor/agents/complexity-auditor.md +76 -0
  18. package/presets/cursor/agents/contract-builder.md +75 -0
  19. package/presets/cursor/agents/docs.md +180 -0
  20. package/presets/cursor/agents/goal-planner.md +71 -0
  21. package/presets/cursor/agents/implementer.md +161 -0
  22. package/presets/cursor/agents/orchestrator.md +377 -0
  23. package/presets/cursor/agents/repo-explorer.md +100 -0
  24. package/presets/cursor/agents/reviewer.md +237 -0
  25. package/presets/cursor/agents/security-reviewer.md +113 -0
  26. package/presets/cursor/agents/task-coach.md +145 -0
  27. package/presets/cursor/agents/tester.md +241 -0
  28. package/presets/cursor/commands/cc/api-contract.md +58 -0
  29. package/presets/cursor/commands/cc/db-migration.md +58 -0
  30. package/presets/cursor/commands/cc/feature.md +115 -0
  31. package/presets/cursor/commands/cc/fix.md +121 -0
  32. package/presets/cursor/commands/cc/openspec.md +144 -0
  33. package/presets/cursor/commands/cc/pagespeed.md +103 -0
  34. package/presets/cursor/commands/cc/refactor.md +148 -0
  35. package/presets/cursor/commands/cc/review.md +137 -0
  36. package/presets/cursor/commands/cc/scorecard.md +65 -0
  37. package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
  38. package/presets/cursor/commands/cc/test-plan.md +138 -0
  39. package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
  40. package/presets/cursor/rules/context-budget.mdc +12 -0
  41. package/presets/cursor/rules/orchestration.mdc +12 -0
  42. package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
  43. package/presets/cursor/skills/android/SKILL.md +122 -0
  44. package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
  45. package/presets/cursor/skills/astro/SKILL.md +322 -0
  46. package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
  47. package/presets/cursor/skills/code-review/SKILL.md +208 -0
  48. package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
  49. package/presets/cursor/skills/django-orm/SKILL.md +463 -0
  50. package/presets/cursor/skills/django-testing/SKILL.md +417 -0
  51. package/presets/cursor/skills/django-uv/SKILL.md +409 -0
  52. package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
  53. package/presets/cursor/skills/evaluation/SKILL.md +8 -0
  54. package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
  55. package/presets/cursor/skills/find-skills/SKILL.md +144 -0
  56. package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
  57. package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
  58. package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
  59. package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
  60. package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
  61. package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
  62. package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
  63. package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
  64. package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
  65. package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
  66. package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
  67. package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
  68. package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
  69. package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
  70. package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
  71. package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
  72. package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
  73. package/presets/cursor/skills/openspec/SKILL.md +52 -0
  74. package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
  75. package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
  76. package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
  77. package/presets/cursor/skills/php-pro/SKILL.md +210 -0
  78. package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
  79. package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
  80. package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
  81. package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
  82. package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
  83. package/presets/cursor/skills/python/SKILL.md +613 -0
  84. package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
  85. package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
  86. package/presets/cursor/skills/security/SKILL.md +384 -0
  87. package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
  88. package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
  89. package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
  90. package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
  91. package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
  92. package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
  93. package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
  94. package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
  95. package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
  96. package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
  97. package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
  98. package/presets/opencode/agents/architect.md +1 -2
  99. package/presets/opencode/agents/complexity-auditor.md +1 -0
  100. package/presets/opencode/agents/contract-builder.md +93 -0
  101. package/presets/opencode/agents/docs.md +1 -2
  102. package/presets/opencode/agents/goal-planner.md +82 -0
  103. package/presets/opencode/agents/implementer.md +9 -2
  104. package/presets/opencode/agents/orchestrator.md +50 -12
  105. package/presets/opencode/agents/repo-explorer.md +0 -1
  106. package/presets/opencode/agents/reviewer.md +23 -2
  107. package/presets/opencode/agents/security-reviewer.md +129 -0
  108. package/presets/opencode/agents/task-coach.md +1 -2
  109. package/presets/opencode/agents/tester.md +1 -2
  110. package/presets/opencode/commands/cc-openspec.md +61 -0
  111. package/presets/opencode/commands/cc-scorecard.md +16 -0
  112. package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
  113. package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
  114. package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
  115. package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
  116. package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
  117. package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
  118. package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
  119. package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
  120. package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
  121. package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
  122. package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
  123. package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
  124. package/presets/opencode/skills/evaluation/SKILL.md +6 -0
  125. package/presets/opencode/skills/openspec/SKILL.md +50 -0
  126. package/presets/templates/BACKLOG.md +33 -0
  127. package/presets/templates/execution-profile.yml +6 -0
  128. package/presets/templates/model-comparison.md +11 -0
  129. package/presets/templates/regression-checklist.yml +10 -0
  130. package/src/presets/manifests/agy.yml +2 -2
  131. package/src/presets/manifests/claude.yml +2 -2
  132. package/src/presets/manifests/codex.yml +2 -2
  133. package/src/presets/manifests/cursor.yml +19 -3
  134. package/src/presets/manifests/gemini.yml +2 -2
  135. package/src/presets/manifests/opencode.yml +2 -2
  136. package/src/presets/models/agy.yml +21 -0
  137. package/src/presets/models/claude.yml +18 -0
  138. package/src/presets/models/codex.yml +18 -0
  139. package/src/presets/models/cursor.yml +39 -9
  140. package/src/presets/models/gemini.yml +18 -0
  141. 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, MemoryPointerSchema, MemoryIndexSchema, 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()
@@ -11341,6 +11342,153 @@ var init_schemas = __esm(() => {
11341
11342
  tasks: exports_external.array(GoalTaskSchema),
11342
11343
  created_at: exports_external.string()
11343
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
+ });
11344
11492
  });
11345
11493
 
11346
11494
  // src/core/presets/package-paths.ts
@@ -12166,7 +12314,7 @@ init_errors();
12166
12314
  // package.json
12167
12315
  var package_default = {
12168
12316
  name: "cc-codeconductor",
12169
- version: "0.4.3",
12317
+ version: "0.5.0",
12170
12318
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
12171
12319
  keywords: [
12172
12320
  "ai",
@@ -12998,7 +13146,11 @@ function extractAgentRole(filePath) {
12998
13146
  "reviewer",
12999
13147
  "docs",
13000
13148
  "task-coach",
13001
- "repo-explorer"
13149
+ "repo-explorer",
13150
+ "complexity-auditor",
13151
+ "security-reviewer",
13152
+ "goal-planner",
13153
+ "contract-builder"
13002
13154
  ];
13003
13155
  return knownRoles.includes(name) ? name : null;
13004
13156
  }
@@ -13074,7 +13226,7 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13074
13226
  if (agentRole && modelConfig.agents[agentRole]) {
13075
13227
  const agentModels = modelConfig.agents[agentRole];
13076
13228
  const targetModel = agentModels[modelConfig.target];
13077
- 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));
13078
13230
  if (modelConfig.tools || modelConfig.permissions) {
13079
13231
  result2 = substituteToolNames(result2, modelConfig);
13080
13232
  }
@@ -13089,7 +13241,10 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13089
13241
  "reviewer",
13090
13242
  "docs",
13091
13243
  "repo-explorer",
13092
- "goal-planner"
13244
+ "goal-planner",
13245
+ "complexity-auditor",
13246
+ "security-reviewer",
13247
+ "contract-builder"
13093
13248
  ];
13094
13249
  let result = templatedContent;
13095
13250
  for (const role of knownRoles) {
@@ -13100,7 +13255,7 @@ function renderTemplate(content, modelConfig, filePath, locale = "en") {
13100
13255
  const sectionMatch = result.match(sectionRegex);
13101
13256
  if (sectionMatch) {
13102
13257
  for (const section of sectionMatch) {
13103
- 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 ?? "");
13104
13259
  result = result.replace(section, renderedSection);
13105
13260
  }
13106
13261
  }
@@ -13932,7 +14087,7 @@ async function writeConfig(projectRoot, config = {}, force = false) {
13932
14087
  init_package_paths();
13933
14088
 
13934
14089
  // src/core/presets/preset-resolver.ts
13935
- var CURRENT_PRESET_VERSION = "v0.4.0";
14090
+ var CURRENT_PRESET_VERSION = "v0.5.0";
13936
14091
  function resolvePreset(target, profile) {
13937
14092
  const stack = resolveStack(profile);
13938
14093
  const isMonorepo = profile.signals.some((s) => [
@@ -13995,17 +14150,17 @@ function resolveStack(profile) {
13995
14150
  function resolveAssets(target) {
13996
14151
  switch (target) {
13997
14152
  case "opencode":
13998
- return ["opencode.jsonc", "agents", "commands", "prompts/v0.4.0", "skills"];
14153
+ return ["opencode.jsonc", "agents", "commands", "prompts/v0.5.0", "skills"];
13999
14154
  case "claude":
14000
- return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.4.0"];
14155
+ return ["CLAUDE.md", "settings.json", "commands", "skills", "agents", "prompts/v0.5.0"];
14001
14156
  case "codex":
14002
- return ["AGENTS.md", "skills", "prompts/v0.4.0"];
14157
+ return ["AGENTS.md", "skills", "prompts/v0.5.0"];
14003
14158
  case "gemini":
14004
- return ["agents", "prompts/v0.4.0"];
14159
+ return ["agents", "prompts/v0.5.0"];
14005
14160
  case "cursor":
14006
- return ["agents", "prompts/v0.4.0"];
14161
+ return ["AGENTS.md", ".cursorignore", "rules", "commands", "skills", "agents", "prompts/v0.5.0"];
14007
14162
  case "agy":
14008
- return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.4.0"];
14163
+ return ["AGENTS.md", "rules", "workflows", "skills", "prompts/v0.5.0"];
14009
14164
  }
14010
14165
  }
14011
14166
 
@@ -14088,12 +14243,14 @@ async function initCommand(options) {
14088
14243
  };
14089
14244
  }
14090
14245
  const copiedPresets = await copyPresets(baseDir, presetsToCopy, force);
14246
+ const openspecCreated = await initOpenspecArtifacts(baseDir, force);
14247
+ const evalCreated = await initEvaluationArtifacts(baseDir, force);
14091
14248
  return {
14092
14249
  code: 0,
14093
14250
  data: {
14094
14251
  success: true,
14095
14252
  command: "init",
14096
- created: [".codeconductor/config.yml", ...copiedPresets],
14253
+ created: [".codeconductor/config.yml", ...copiedPresets, ...openspecCreated, ...evalCreated],
14097
14254
  ...profile ? {
14098
14255
  detected: {
14099
14256
  languages: profile.languages,
@@ -14160,6 +14317,62 @@ async function fileExists2(path) {
14160
14317
  return false;
14161
14318
  }
14162
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
+ }
14163
14376
 
14164
14377
  // src/commands/install.command.ts
14165
14378
  init_agy_installer();
@@ -15641,207 +15854,2326 @@ async function goalCommand(options) {
15641
15854
  }
15642
15855
  }
15643
15856
 
15644
- // src/commands/help.command.ts
15645
- import { readdir as readdir3 } from "node:fs/promises";
15646
- import { join as join8 } from "node:path";
15647
- async function scanPresetDir(projectRoot, target) {
15648
- const inventory = {
15649
- target,
15650
- skills: [],
15651
- commands: [],
15652
- agents: [],
15653
- workflows: []
15654
- };
15655
- const presetDir = join8(projectRoot, "presets", target);
15656
- try {
15657
- const entries = await readdir3(presetDir, { withFileTypes: true });
15658
- for (const entry of entries) {
15659
- if (entry.isDirectory()) {
15660
- const subDir = join8(presetDir, entry.name);
15661
- const subEntries = await readdir3(subDir, { withFileTypes: true });
15662
- for (const sub of subEntries) {
15663
- if (sub.isFile()) {
15664
- const itemName = sub.name.replace(/\.[^.]+$/, "");
15665
- if (entry.name === "skills")
15666
- inventory.skills.push(itemName);
15667
- else if (entry.name === "commands")
15668
- inventory.commands.push(itemName);
15669
- else if (entry.name === "agents")
15670
- inventory.agents.push(itemName);
15671
- else if (entry.name === "workflows")
15672
- inventory.workflows.push(itemName);
15673
- } else if (sub.isDirectory()) {
15674
- if (entry.name === "skills")
15675
- inventory.skills.push(sub.name);
15676
- else if (entry.name === "agents")
15677
- inventory.agents.push(sub.name);
15678
- }
15679
- }
15680
- }
15681
- }
15682
- } catch {}
15683
- 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";
15684
15873
  }
15685
- function renderHuman(inventory, defaultTarget) {
15686
- const lines = [
15687
- `CodeConductor Help ${inventory.target}${inventory.target === defaultTarget ? " (active)" : ""}`,
15688
- ""
15689
- ];
15690
- lines.push(`Skills (${inventory.skills.length}):`);
15691
- if (inventory.skills.length === 0) {
15692
- lines.push(" (none)");
15693
- } else {
15694
- for (const skill of inventory.skills.sort()) {
15695
- lines.push(` - ${skill}`);
15696
- }
15697
- }
15698
- lines.push("");
15699
- lines.push(`Subagents (${inventory.agents.length}):`);
15700
- if (inventory.agents.length === 0) {
15701
- lines.push(" (none)");
15702
- } else {
15703
- for (const agent of inventory.agents.sort()) {
15704
- lines.push(` - ${agent}`);
15705
- }
15706
- }
15707
- lines.push("");
15708
- lines.push(`Commands (${inventory.commands.length}):`);
15709
- if (inventory.commands.length === 0) {
15710
- lines.push(" (none)");
15711
- } else {
15712
- for (const cmd of inventory.commands.sort()) {
15713
- lines.push(` - ${cmd}`);
15714
- }
15715
- }
15716
- if (inventory.workflows.length > 0) {
15717
- lines.push("");
15718
- lines.push(`Workflows (${inventory.workflows.length}):`);
15719
- for (const wf of inventory.workflows.sort()) {
15720
- 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;
15721
15906
  }
15722
15907
  }
15723
- lines.push("");
15724
- lines.push("Stack-specific presets (v0.4.0):");
15725
- lines.push(" ts-next-drizzle Next.js / Astro, Tailwind, Drizzle ORM, Bun, Postgres");
15726
- lines.push(" spring-kotlin-jpa Spring Boot, Kotlin/Java, Gradle, JPA, Hibernate");
15727
- lines.push(" laravel-tall Laravel, Blade, Livewire, Alpine.js");
15728
- lines.push(" python-data-api Python, FastAPI, Django, uv");
15729
- lines.push(" Browse: presets/<preset-name>/agents/ — copy into the target runner.");
15730
- lines.push(" Registry: src/core/presets/preset-registry.ts (listPresets / getPreset).");
15731
- return lines.join(`
15732
- `);
15908
+ return { criteria, nextIndex: i };
15733
15909
  }
15734
- async function helpCommand(options) {
15735
- 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) {
15736
15976
  try {
15737
- let defaultTarget = "opencode";
15738
- try {
15739
- const configResult = await loadConfig(projectRoot);
15740
- if (configResult.success) {
15741
- 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;
15742
15990
  }
15743
- } catch {}
15744
- const target = overrideTarget ?? defaultTarget;
15745
- const inventory = await scanPresetDir(projectRoot, target);
15746
- if (output === "json") {
15747
- return {
15748
- code: 0,
15749
- data: {
15750
- success: true,
15751
- command: "help",
15752
- inventory,
15753
- defaultTarget
15754
- }
15755
- };
15756
- }
15757
- return {
15758
- code: 0,
15759
- data: {
15760
- success: true,
15761
- command: "help",
15762
- message: renderHuman(inventory, defaultTarget),
15763
- inventory
15991
+ if (ITEMS_SECTION_RE.test(line)) {
15992
+ section = "items";
15993
+ currentItem = null;
15994
+ continue;
15764
15995
  }
15765
- };
15766
- } catch (error) {
15767
- return {
15768
- code: 1,
15769
- data: {
15770
- success: false,
15771
- command: "help",
15772
- 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;
15773
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
15774
16035
  };
16036
+ return ok(document);
16037
+ } catch (e) {
16038
+ return err(e instanceof Error ? e : new Error(String(e)));
15775
16039
  }
15776
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;
15777
16051
 
15778
- // src/commands/seo-audit.command.ts
15779
- import { writeFile as writeFile7, mkdir as mkdir8 } from "node:fs/promises";
15780
- import { dirname as dirname4, resolve as resolve11 } from "node:path";
15781
-
15782
- // src/infrastructure/http/safe-fetch.ts
15783
- import { lookup } from "node:dns/promises";
15784
- var PRIVATE_IP_RANGES = [
15785
- /^10\./,
15786
- /^172\.(1[6-9]|2\d|3[01])\./,
15787
- /^192\.168\./,
15788
- /^127\./,
15789
- /^0\./,
15790
- /^169\.254\./,
15791
- /^::1$/,
15792
- /^fc00:/i,
15793
- /^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
15794
16064
  ];
15795
- function isPrivateIp(ip) {
15796
- return PRIVATE_IP_RANGES.some((range) => range.test(ip));
16065
+ function allItems(doc) {
16066
+ return [...doc.items, ...doc.archive];
15797
16067
  }
15798
- async function validateUrl(urlString) {
15799
- let parsed;
15800
- try {
15801
- parsed = new URL(urlString);
15802
- } catch {
15803
- 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)));
15804
16073
  }
15805
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
15806
- 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;
15807
16089
  }
15808
- const hostname = parsed.hostname;
15809
- if (hostname === "localhost" || hostname.endsWith(".localhost")) {
15810
- throw new Error(`Blocked hostname: ${hostname}`);
16090
+ for (const id of ids) {
16091
+ if (dfs(id))
16092
+ return id;
15811
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 = [];
15812
16105
  try {
15813
- const addresses = await lookup(hostname, { all: true });
15814
- const addrList = Array.isArray(addresses) ? addresses : [addresses];
15815
- for (const addr of addrList) {
15816
- if (isPrivateIp(addr.address)) {
15817
- 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
+ });
15818
16158
  }
15819
16159
  }
15820
- } catch (error) {
15821
- if (error instanceof Error && error.message.startsWith("SSRF blocked")) {
15822
- 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
+ }
15823
16168
  }
15824
- throw new Error(`DNS resolution failed for ${hostname}: ${String(error)}`);
15825
16169
  }
15826
- }
15827
- async function safeFetch(urlString, options = {}) {
15828
- const { timeout = 1e4, followRedirects = false } = options;
15829
- await validateUrl(urlString);
15830
- const controller = new AbortController;
15831
- const timer = setTimeout(() => controller.abort(), timeout);
15832
- const start = Date.now();
15833
- try {
15834
- const response = await fetch(urlString, {
15835
- method: "GET",
15836
- signal: controller.signal,
15837
- redirect: followRedirects ? "follow" : "manual",
15838
- headers: {
15839
- "User-Agent": "CodeConductor-SEO/0.3.0",
15840
- Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
15841
- }
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
15842
16176
  });
15843
- const responseTime = Date.now() - start;
15844
- const body = await response.text();
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
+ };
16186
+ }
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) {
16216
+ try {
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: {
18171
+ "User-Agent": "CodeConductor-SEO/0.3.0",
18172
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
18173
+ }
18174
+ });
18175
+ const responseTime = Date.now() - start;
18176
+ const body = await response.text();
15845
18177
  const headers = {};
15846
18178
  response.headers.forEach((value, key) => {
15847
18179
  headers[key] = value;
@@ -15864,7 +18196,7 @@ async function safeFetch(urlString, options = {}) {
15864
18196
  }
15865
18197
  }
15866
18198
  async function delay(ms) {
15867
- return new Promise((resolve11) => setTimeout(resolve11, ms));
18199
+ return new Promise((resolve22) => setTimeout(resolve22, ms));
15868
18200
  }
15869
18201
 
15870
18202
  // src/infrastructure/parsers/sitemap-parser.ts
@@ -17068,14 +19400,14 @@ async function seoAuditCommand(options) {
17068
19400
  formattedOutput = formatCli(report);
17069
19401
  }
17070
19402
  if (output) {
17071
- const outputPath = resolve11(options.projectRoot, output);
17072
- await mkdir8(dirname4(outputPath), { recursive: true });
17073
- 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");
17074
19406
  } else if (format === "markdown") {
17075
19407
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
17076
- const defaultPath = resolve11(options.projectRoot, "seo-reports", `audit-report-${timestamp}.md`);
17077
- await mkdir8(dirname4(defaultPath), { recursive: true });
17078
- 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");
17079
19411
  process.stderr.write(`Report saved to: ${defaultPath}
17080
19412
  `);
17081
19413
  }
@@ -17087,7 +19419,7 @@ async function seoAuditCommand(options) {
17087
19419
  command: "seo audit",
17088
19420
  report,
17089
19421
  output: formattedOutput,
17090
- 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
17091
19423
  }
17092
19424
  };
17093
19425
  } catch (error) {
@@ -17103,8 +19435,8 @@ async function seoAuditCommand(options) {
17103
19435
  }
17104
19436
 
17105
19437
  // src/commands/seo-llms.command.ts
17106
- import { writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
17107
- 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";
17108
19440
 
17109
19441
  // src/domain/seo/llms-generator.ts
17110
19442
  function extractTitle2(html) {
@@ -17247,9 +19579,9 @@ async function seoLlmsCommand(options) {
17247
19579
  }
17248
19580
  }) : await generateLlmsTxtFromUrl(url);
17249
19581
  process.stderr.write("\r" + " ".repeat(80) + "\r");
17250
- const outputPath = output ? resolve12(options.projectRoot, output) : resolve12(options.projectRoot, "llms.txt");
17251
- await mkdir9(dirname5(outputPath), { recursive: true });
17252
- 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");
17253
19585
  process.stderr.write(`Generated: ${outputPath} (${result.entries.length} entries)
17254
19586
  `);
17255
19587
  return {
@@ -17276,8 +19608,8 @@ async function seoLlmsCommand(options) {
17276
19608
 
17277
19609
  // src/commands/update.command.ts
17278
19610
  import { homedir as homedir9 } from "node:os";
17279
- import { resolve as resolve13, dirname as dirname6 } from "node:path";
17280
- 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";
17281
19613
  init_package_paths();
17282
19614
  async function updateCommand(options) {
17283
19615
  const { dryRun, force, global: isGlobal, output, projectRoot } = options;
@@ -17343,23 +19675,23 @@ async function updateCommand(options) {
17343
19675
  }
17344
19676
  const updated = [];
17345
19677
  if (updateResults.council) {
17346
- const localCouncil = resolve13(basePath, ".codeconductor", "presets", "council.yml");
17347
- 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");
17348
19680
  try {
17349
- const content = await readFile11(bundledCouncil, "utf-8");
17350
- await mkdir10(dirname6(localCouncil), { recursive: true });
17351
- 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");
17352
19684
  updated.push(localCouncil);
17353
19685
  } catch (e) {
17354
19686
  throw new Error(`Failed to update council.yml: ${e}`);
17355
19687
  }
17356
19688
  }
17357
19689
  if (updateResults.policy) {
17358
- const localPolicy = resolve13(basePath, ".codeconductor", "presets", "policy.yml");
19690
+ const localPolicy = resolve24(basePath, ".codeconductor", "presets", "policy.yml");
17359
19691
  try {
17360
- const content = await readFile11(POLICY_PATH, "utf-8");
17361
- await mkdir10(dirname6(localPolicy), { recursive: true });
17362
- 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");
17363
19695
  updated.push(localPolicy);
17364
19696
  } catch (e) {
17365
19697
  throw new Error(`Failed to update policy.yml: ${e}`);
@@ -17411,12 +19743,12 @@ async function updateCommand(options) {
17411
19743
  let targetPath = f.path;
17412
19744
  let targetBase = basePath;
17413
19745
  if ((targetName === "agy" || targetName === "gemini") && isGlobal) {
17414
- targetBase = resolve13(homedir9(), ".gemini", "config");
19746
+ targetBase = resolve24(homedir9(), ".gemini", "config");
17415
19747
  targetPath = targetPath.replace(/^\.agents\/?/, "");
17416
19748
  }
17417
19749
  return {
17418
19750
  ...f,
17419
- path: resolve13(targetBase, targetPath)
19751
+ path: resolve24(targetBase, targetPath)
17420
19752
  };
17421
19753
  });
17422
19754
  const filesToWrite = resolvedFiles.filter((f) => t.files.includes(f.path));
@@ -17444,19 +19776,19 @@ async function updateCommand(options) {
17444
19776
  for (const s of updateResults.skills) {
17445
19777
  newSkillsLock[s.id] = s.latestVersion;
17446
19778
  }
17447
- let lockDest = resolve13(basePath, ".codeconductor", "skills-lock.json");
19779
+ let lockDest = resolve24(basePath, ".codeconductor", "skills-lock.json");
17448
19780
  try {
17449
- const statAgents = await stat3(resolve13(basePath, ".agents"));
19781
+ const statAgents = await stat3(resolve24(basePath, ".agents"));
17450
19782
  if (statAgents.isDirectory()) {
17451
- const statCodeConductor = await stat3(resolve13(basePath, ".codeconductor")).catch(() => null);
19783
+ const statCodeConductor = await stat3(resolve24(basePath, ".codeconductor")).catch(() => null);
17452
19784
  if (!statCodeConductor) {
17453
- lockDest = resolve13(basePath, ".agents", "skills-lock.json");
19785
+ lockDest = resolve24(basePath, ".agents", "skills-lock.json");
17454
19786
  }
17455
19787
  }
17456
19788
  } catch {}
17457
19789
  try {
17458
- await mkdir10(dirname6(lockDest), { recursive: true });
17459
- 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");
17460
19792
  updated.push(lockDest);
17461
19793
  } catch (e) {
17462
19794
  throw new Error(`Failed to write skills-lock.json: ${e}`);
@@ -17527,25 +19859,38 @@ function parseArgs(args) {
17527
19859
  }
17528
19860
  const command = remaining[0] || "help";
17529
19861
  const subcommand = remaining[1] && !remaining[1].startsWith("-") ? remaining[1] : undefined;
19862
+ const consumed = new Set;
17530
19863
  for (let i = 1;i < remaining.length; i++) {
17531
19864
  const arg = remaining[i];
17532
19865
  if (arg.startsWith("--")) {
17533
19866
  const [key, value] = arg.slice(2).split("=");
17534
19867
  if (key === "lang") {
17535
19868
  const langValue = value !== undefined ? value : remaining[++i];
19869
+ consumed.add(i);
17536
19870
  if (langValue && typeof langValue === "string") {
17537
19871
  options[key] = langValue.split(",").map((s) => s.trim());
17538
19872
  }
17539
19873
  } else if (value !== undefined) {
17540
19874
  options[key] = value;
19875
+ consumed.add(i);
17541
19876
  } else if (remaining[i + 1] && !remaining[i + 1].startsWith("-")) {
17542
19877
  options[key] = remaining[++i];
19878
+ consumed.add(i - 1);
19879
+ consumed.add(i);
17543
19880
  } else {
17544
19881
  options[key] = true;
19882
+ consumed.add(i);
17545
19883
  }
17546
19884
  }
17547
19885
  }
17548
- 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 };
17549
19894
  }
17550
19895
  function getVersion() {
17551
19896
  return `${package_default.name} v${package_default.version}`;
@@ -17607,6 +19952,7 @@ Examples:
17607
19952
  npx cc-codeconductor install preset --target opencode
17608
19953
  npx cc-codeconductor install preset --target claude
17609
19954
  npx cc-codeconductor install preset --target codex
19955
+ npx cc-codeconductor install preset --target cursor
17610
19956
  npx cc-codeconductor install preset --target agy
17611
19957
  npx cc-codeconductor install preset --target all
17612
19958
  npx cc-codeconductor install preset --target claude --global
@@ -17628,6 +19974,14 @@ Examples:
17628
19974
  npx cc-codeconductor seo llms --url https://example.com --output llms.txt
17629
19975
  npx cc-codeconductor goal "Add user authentication"
17630
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
17631
19985
  npx cc-codeconductor help --target opencode
17632
19986
  npx cc-codeconductor help --target claude --output json
17633
19987
 
@@ -17730,6 +20084,68 @@ async function routeCommand(args, projectRoot) {
17730
20084
  projectRoot,
17731
20085
  output: flags.output
17732
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
+ }
17733
20149
  case "seo": {
17734
20150
  if (subcommand === "audit") {
17735
20151
  return seoAuditCommand({