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