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