@tea-agent/loop-agent 0.13.0-alpha.0 → 0.13.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/AGENTS.md +4 -0
- package/CHANGELOG.md +57 -53
- package/README.md +12 -3
- package/dist/application/dag/generate-task-dag.js +28 -58
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +52 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +130 -0
- package/dist/cli/command-definitions.js +17 -4
- package/dist/cli/program.js +8 -4
- package/dist/commands/eval.js +235 -0
- package/dist/commands/init.js +131 -24
- package/dist/executors/pi-sdk-executor.js +38 -24
- package/dist/executors/shell-executor.js +226 -15
- package/dist/executors/shell-presets.js +20 -0
- package/dist/executors/shell-verification.js +7 -0
- package/dist/governance/manifest-types.js +1 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/task/config-types.js +23 -0
- package/dist/worker/observe/routes.js +18 -3
- package/dist/worker/observe/spec-evidence.js +1 -1
- package/dist/worker/observe/static/dom.js +160 -1
- package/dist/worker/observe/static/state.js +14 -0
- package/dist/worker/observe/static/views/dag-inspector.js +35 -4
- package/dist/worker/observe/static/views/dag.js +9 -0
- package/dist/worker/observe/static/views/dashboard.js +702 -445
- package/dist/worker/observe/static/views/session-timeline.js +15 -1
- package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
- package/dist/workflows/dag/backend-test-result-contract.js +568 -0
- package/dist/workflows/dag/decision-envelope.js +57 -2
- package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
- package/dist/workflows/dag/frontend-project-capability.js +309 -0
- package/dist/workflows/dag/frontend-repair.js +341 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-verification-trace.js +190 -0
- package/dist/workflows/dag/init-hybrid.js +2407 -297
- package/dist/workflows/dag/node-execution.js +9 -0
- package/dist/workflows/dag/prompt.js +9 -0
- package/dist/workflows/dag/repair-artifact.js +43 -3
- package/dist/workflows/dag/report.js +35 -1
- package/dist/workflows/dag/runner.js +28 -2
- package/dist/workflows/dag/skill-instructions.js +4 -2
- package/dist/workflows/dag/task-demand-routing.js +383 -0
- package/dist/workflows/dag/types.js +71 -13
- package/dist/workflows/dag/upstream-artifacts.js +1 -0
- package/dist/workflows/dag/validate.js +59 -1
- package/docs/README.md +6 -3
- package/docs/agent-dag-recovery-playbook.md +5 -3
- package/docs/agent-dag-runner.md +3 -3
- package/docs/architecture/README.md +3 -3
- package/docs/architecture/dag-execution.md +1 -1
- package/docs/architecture/evolution.md +13 -13
- package/docs/architecture/facts-and-state.md +1 -1
- package/docs/architecture/runtime-boundaries.md +7 -7
- package/docs/architecture/system-overview.md +3 -3
- package/docs/architecture/worker-and-feature.md +3 -3
- package/docs/design/README.md +124 -42
- package/docs/development-principles.md +4 -4
- package/docs/exec-plans/active/README.md +12 -11
- package/docs/exec-plans/completed/README.md +33 -0
- package/docs/feature-workflow.md +114 -39
- package/docs/init-surface.manifest.json +30 -3
- package/docs/loop-agent-harness.md +9 -8
- package/docs/production-readiness.md +1 -1
- package/docs/progress/README.md +23 -1
- package/docs/reports/README.md +65 -6
- package/docs/skills/vetted-skill-registry.md +2 -0
- package/docs/templates/agent-dag.schema.json +29 -1
- package/docs/templates/agent-dag.supervised-implementation.json +127 -8
- package/docs/templates/backend-test-analysis.schema.json +44 -0
- package/docs/templates/backend-test-case-manifest.schema.json +190 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +81 -16
- package/docs/templates/backend-test-dag.json +311 -40
- package/docs/templates/backend-test-dag.retrospect.prompt.md +44 -30
- package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -8
- package/docs/templates/backend-test-execution.schema.json +133 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +93 -0
- package/docs/templates/frontend-design-contract.md +9 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +27 -0
- package/docs/templates/frontend-task-constraints.md +10 -0
- package/docs/templates/frontend-task-requirement.md +9 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.json +23 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +1 -1
- package/docs/templates/knowledge-sync-dag.json +1 -0
- package/docs/verification-matrix.md +4 -1
- package/examples/decision-gate-agent-dag.json +4 -4
- package/examples/hybrid-loop-agent-dag.json +1 -1
- package/package.json +2 -2
- package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
- package/scripts/kb-graph-incremental-prepare.mjs +19 -5
- package/scripts/kb-graph-promote.mjs +12 -1
- package/skills/ai-engineering-context/SKILL.md +2 -2
- package/skills/analyze-product-dependencies/SKILL.md +67 -0
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
- package/skills/analyze-product-requirements/SKILL.md +90 -0
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
- package/skills/analyze-product-requirements/references/example.md +86 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/frontend-design-review/SKILL.md +6 -1
- package/skills/frontend-design-review/references/review-checklist.md +25 -4
- package/skills/frontend-implementation/SKILL.md +25 -30
- package/skills/frontend-implementation/references/code-standards.md +20 -22
- package/skills/frontend-implementation/references/node-contracts.md +17 -53
- package/skills/frontend-review/SKILL.md +10 -4
- package/skills/frontend-review/references/review-findings.md +8 -3
- package/skills/frontend-verification/SKILL.md +22 -9
- package/skills/frontend-verification/references/verification-checklist.md +17 -5
- package/skills/grill-with-docs/SKILL.md +5 -5
- package/skills/grill-with-docs/adr-format.md +3 -3
- package/skills/init-capability-evolution/SKILL.md +5 -5
- package/skills/loop-agent/SKILL.md +5 -5
- package/skills/loop-agent/references/README.md +3 -3
- package/skills/loop-agent/references/command-reference.md +98 -24
- package/skills/loop-agent/references/docs-converge.md +15 -15
- package/skills/loop-agent/references/harness-policy.md +2 -2
- package/skills/loop-agent/references/hybrid-dag.md +32 -22
- package/skills/loop-agent/references/multi-worktree.md +1 -1
- package/skills/loop-agent/references/orchestrator-and-interventions.md +8 -8
- package/skills/loop-agent/references/task-workflow.md +1 -1
- package/skills/loop-agent/references/verification-and-failure-handling.md +6 -4
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-cli-case-generator/SKILL.md +74 -0
- package/skills/requesting-code-review/SKILL.md +1 -1
- package/skills/systematic-debugging/CREATION-LOG.md +3 -3
- package/skills/systematic-debugging/SKILL.md +1 -1
- package/skills/systematic-debugging/test-academic.md +1 -1
- package/skills/systematic-debugging/test-pressure-1.md +1 -1
- package/skills/systematic-debugging/test-pressure-2.md +1 -1
- package/skills/systematic-debugging/test-pressure-3.md +1 -1
- package/skills/verification-before-completion/SKILL.md +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { reportDagUseCase } from "../dag/report-dag.js";
|
|
5
|
+
import { readReplaySpec, writeReplayArtifacts, } from "../../infrastructure/evaluation/store.js";
|
|
6
|
+
function sha256(content) {
|
|
7
|
+
return createHash("sha256").update(content).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
function pairKey(input) {
|
|
10
|
+
return `${input.split}\u0000${input.taskRef}\u0000${input.seed}`;
|
|
11
|
+
}
|
|
12
|
+
async function verifyEvidenceHash(input) {
|
|
13
|
+
const content = await readFile(input.filePath);
|
|
14
|
+
const actual = sha256(content);
|
|
15
|
+
if (actual !== input.expected) {
|
|
16
|
+
throw new Error(`${input.label} hash mismatch: expected ${input.expected}, got ${actual}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function sumOptional(values) {
|
|
20
|
+
if (values.some((value) => value === undefined)) {
|
|
21
|
+
return { value: null, missing: true };
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
value: values.reduce((sum, value) => sum + (value ?? 0), 0),
|
|
25
|
+
missing: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function verifyPassed(input) {
|
|
29
|
+
if (input.status !== "finished")
|
|
30
|
+
return false;
|
|
31
|
+
const verificationNodes = input.nodes.filter((node) => node.executor === "shell" ||
|
|
32
|
+
node.nodeId.includes("verify") ||
|
|
33
|
+
node.nodeId.includes("gate"));
|
|
34
|
+
return (verificationNodes.length > 0 &&
|
|
35
|
+
verificationNodes.every((node) => node.status === "FINISHED" &&
|
|
36
|
+
(!node.failureCategory || node.failureCategory === "success")));
|
|
37
|
+
}
|
|
38
|
+
function metricsForRun(run) {
|
|
39
|
+
const tokens = sumOptional(run.nodes.map((node) => node.tokensUsed));
|
|
40
|
+
const duration = sumOptional(run.nodes.map((node) => node.durationMs));
|
|
41
|
+
const missingFields = [];
|
|
42
|
+
if (tokens.missing)
|
|
43
|
+
missingFields.push("tokens");
|
|
44
|
+
if (duration.missing)
|
|
45
|
+
missingFields.push("durationMs");
|
|
46
|
+
return {
|
|
47
|
+
metrics: {
|
|
48
|
+
verifyPassed: verifyPassed(run),
|
|
49
|
+
tokens: tokens.value,
|
|
50
|
+
durationMs: duration.value,
|
|
51
|
+
executorCalls: run.nodes.length,
|
|
52
|
+
repairPasses: run.convergence?.currentPass ?? 0,
|
|
53
|
+
},
|
|
54
|
+
missingFields,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function compareRows(incumbent, challenger) {
|
|
58
|
+
const reasons = [];
|
|
59
|
+
let verdict;
|
|
60
|
+
if (incumbent.metrics.verifyPassed !== challenger.metrics.verifyPassed) {
|
|
61
|
+
verdict = challenger.metrics.verifyPassed
|
|
62
|
+
? "challenger_win"
|
|
63
|
+
: "incumbent_win";
|
|
64
|
+
reasons.push("verification_outcome");
|
|
65
|
+
}
|
|
66
|
+
else if (!incumbent.metrics.verifyPassed) {
|
|
67
|
+
verdict = "tie";
|
|
68
|
+
reasons.push("both_failed_verification");
|
|
69
|
+
}
|
|
70
|
+
else if (incumbent.metrics.tokens === null ||
|
|
71
|
+
challenger.metrics.tokens === null ||
|
|
72
|
+
incumbent.metrics.durationMs === null ||
|
|
73
|
+
challenger.metrics.durationMs === null) {
|
|
74
|
+
verdict = "incomparable";
|
|
75
|
+
reasons.push("missing_cost_metrics");
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const challengerNoWorse = challenger.metrics.tokens <= incumbent.metrics.tokens &&
|
|
79
|
+
challenger.metrics.durationMs <= incumbent.metrics.durationMs;
|
|
80
|
+
const incumbentNoWorse = incumbent.metrics.tokens <= challenger.metrics.tokens &&
|
|
81
|
+
incumbent.metrics.durationMs <= challenger.metrics.durationMs;
|
|
82
|
+
if (challengerNoWorse && !incumbentNoWorse) {
|
|
83
|
+
verdict = "challenger_win";
|
|
84
|
+
reasons.push("lower_cost");
|
|
85
|
+
}
|
|
86
|
+
else if (incumbentNoWorse && !challengerNoWorse) {
|
|
87
|
+
verdict = "incumbent_win";
|
|
88
|
+
reasons.push("lower_cost");
|
|
89
|
+
}
|
|
90
|
+
else if (challengerNoWorse && incumbentNoWorse) {
|
|
91
|
+
verdict = "tie";
|
|
92
|
+
reasons.push("equal_metrics");
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
verdict = "incomparable";
|
|
96
|
+
reasons.push("cost_tradeoff");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
taskRef: incumbent.taskRef,
|
|
101
|
+
seed: incumbent.seed,
|
|
102
|
+
split: incumbent.split,
|
|
103
|
+
incumbentRunId: incumbent.runId,
|
|
104
|
+
challengerRunId: challenger.runId,
|
|
105
|
+
verdict,
|
|
106
|
+
reasons,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function buildScorecard(input) {
|
|
110
|
+
const rows = [...input.rows].sort((left, right) => [
|
|
111
|
+
left.split,
|
|
112
|
+
left.taskRef,
|
|
113
|
+
String(left.seed).padStart(12, "0"),
|
|
114
|
+
left.candidateId,
|
|
115
|
+
left.runId,
|
|
116
|
+
]
|
|
117
|
+
.join("\u0000")
|
|
118
|
+
.localeCompare([
|
|
119
|
+
right.split,
|
|
120
|
+
right.taskRef,
|
|
121
|
+
String(right.seed).padStart(12, "0"),
|
|
122
|
+
right.candidateId,
|
|
123
|
+
right.runId,
|
|
124
|
+
].join("\u0000")));
|
|
125
|
+
const incumbentByKey = new Map(rows
|
|
126
|
+
.filter((row) => row.candidateId === input.incumbentCandidateId)
|
|
127
|
+
.map((row) => [pairKey(row), row]));
|
|
128
|
+
const challengerByKey = new Map(rows
|
|
129
|
+
.filter((row) => row.candidateId === input.challengerCandidateId)
|
|
130
|
+
.map((row) => [pairKey(row), row]));
|
|
131
|
+
const keys = [
|
|
132
|
+
...new Set([...incumbentByKey.keys(), ...challengerByKey.keys()]),
|
|
133
|
+
].sort();
|
|
134
|
+
const comparisons = [];
|
|
135
|
+
let unpairedEvidenceCount = 0;
|
|
136
|
+
for (const key of keys) {
|
|
137
|
+
const incumbent = incumbentByKey.get(key);
|
|
138
|
+
const challenger = challengerByKey.get(key);
|
|
139
|
+
if (!incumbent || !challenger) {
|
|
140
|
+
unpairedEvidenceCount +=
|
|
141
|
+
Number(Boolean(incumbent)) + Number(Boolean(challenger));
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
comparisons.push(compareRows(incumbent, challenger));
|
|
145
|
+
}
|
|
146
|
+
const reasons = ["replay_only"];
|
|
147
|
+
if (comparisons.length === 0)
|
|
148
|
+
reasons.push("insufficient_samples");
|
|
149
|
+
if (unpairedEvidenceCount > 0)
|
|
150
|
+
reasons.push("unpaired_evidence");
|
|
151
|
+
return {
|
|
152
|
+
schemaVersion: 1,
|
|
153
|
+
replayId: input.replayId,
|
|
154
|
+
incumbentCandidateId: input.incumbentCandidateId,
|
|
155
|
+
challengerCandidateId: input.challengerCandidateId,
|
|
156
|
+
rows,
|
|
157
|
+
comparisons,
|
|
158
|
+
aggregate: {
|
|
159
|
+
pairedSampleCount: comparisons.length,
|
|
160
|
+
incumbentWins: comparisons.filter((item) => item.verdict === "incumbent_win").length,
|
|
161
|
+
challengerWins: comparisons.filter((item) => item.verdict === "challenger_win").length,
|
|
162
|
+
ties: comparisons.filter((item) => item.verdict === "tie").length,
|
|
163
|
+
incomparable: comparisons.filter((item) => item.verdict === "incomparable").length,
|
|
164
|
+
unpairedEvidenceCount,
|
|
165
|
+
promotionEligible: false,
|
|
166
|
+
reasons,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
export function formatReplayMarkdown(scorecard) {
|
|
171
|
+
const lines = [
|
|
172
|
+
`# Eval Replay Scorecard: ${scorecard.replayId}`,
|
|
173
|
+
"",
|
|
174
|
+
"> Replay-only evidence. This report never authorizes promotion or executes Pi/DAG work.",
|
|
175
|
+
"",
|
|
176
|
+
`- incumbent: ${scorecard.incumbentCandidateId}`,
|
|
177
|
+
`- challenger: ${scorecard.challengerCandidateId}`,
|
|
178
|
+
`- promotionEligible: ${scorecard.aggregate.promotionEligible}`,
|
|
179
|
+
`- reasons: ${scorecard.aggregate.reasons.join(", ")}`,
|
|
180
|
+
"",
|
|
181
|
+
"## Evidence",
|
|
182
|
+
"",
|
|
183
|
+
"| split | task | seed | candidate | run | verified | tokens | durationMs | calls | repairPasses | missing |",
|
|
184
|
+
"|---|---|---:|---|---|---|---:|---:|---:|---:|---|",
|
|
185
|
+
];
|
|
186
|
+
for (const row of scorecard.rows) {
|
|
187
|
+
lines.push(`| ${row.split} | ${row.taskRef} | ${row.seed} | ${row.candidateId} | ${row.runId} | ${row.metrics.verifyPassed} | ${row.metrics.tokens ?? "n/a"} | ${row.metrics.durationMs ?? "n/a"} | ${row.metrics.executorCalls} | ${row.metrics.repairPasses} | ${row.missingFields.join(", ") || "none"} |`);
|
|
188
|
+
}
|
|
189
|
+
lines.push("", "## Paired Comparisons", "", "| split | task | seed | incumbent run | challenger run | verdict | reasons |", "|---|---|---:|---|---|---|---|");
|
|
190
|
+
for (const comparison of scorecard.comparisons) {
|
|
191
|
+
lines.push(`| ${comparison.split} | ${comparison.taskRef} | ${comparison.seed} | ${comparison.incumbentRunId} | ${comparison.challengerRunId} | ${comparison.verdict} | ${comparison.reasons.join(", ")} |`);
|
|
192
|
+
}
|
|
193
|
+
if (scorecard.comparisons.length === 0) {
|
|
194
|
+
lines.push("| - | - | - | - | - | - | no paired evidence |");
|
|
195
|
+
}
|
|
196
|
+
return `${lines.join("\n")}\n`;
|
|
197
|
+
}
|
|
198
|
+
export async function replayEvaluation(input) {
|
|
199
|
+
const spec = await readReplaySpec(input.repoRoot, input.specPath);
|
|
200
|
+
const rows = [];
|
|
201
|
+
for (const evidence of spec.evidence) {
|
|
202
|
+
const runDir = path.join(input.repoRoot, ".harness", "dag-runs", "completed", evidence.runId);
|
|
203
|
+
const statePath = path.join(runDir, "state.json");
|
|
204
|
+
const runPath = path.join(runDir, "run.json");
|
|
205
|
+
const stateRefPath = path
|
|
206
|
+
.relative(input.repoRoot, statePath)
|
|
207
|
+
.split(path.sep)
|
|
208
|
+
.join("/");
|
|
209
|
+
const runRefPath = path
|
|
210
|
+
.relative(input.repoRoot, runPath)
|
|
211
|
+
.split(path.sep)
|
|
212
|
+
.join("/");
|
|
213
|
+
await verifyEvidenceHash({
|
|
214
|
+
filePath: statePath,
|
|
215
|
+
expected: evidence.stateSha256,
|
|
216
|
+
label: "state.json",
|
|
217
|
+
});
|
|
218
|
+
await verifyEvidenceHash({
|
|
219
|
+
filePath: runPath,
|
|
220
|
+
expected: evidence.runSha256,
|
|
221
|
+
label: "run.json",
|
|
222
|
+
});
|
|
223
|
+
const report = await reportDagUseCase({
|
|
224
|
+
repoRoot: input.repoRoot,
|
|
225
|
+
runId: evidence.runId,
|
|
226
|
+
lifecycle: "completed",
|
|
227
|
+
failedOnly: false,
|
|
228
|
+
latest: false,
|
|
229
|
+
});
|
|
230
|
+
const run = report.runs[0];
|
|
231
|
+
if (!run) {
|
|
232
|
+
throw new Error(`completed DAG run not found: ${evidence.runId}`);
|
|
233
|
+
}
|
|
234
|
+
if (run.evaluationAssociation.status === "present") {
|
|
235
|
+
if (run.evaluationAssociation.candidateId !== evidence.candidateId) {
|
|
236
|
+
throw new Error(`replay evidence candidateId conflict for ${evidence.runId}: evidence=${evidence.candidateId}, run=${run.evaluationAssociation.candidateId}`);
|
|
237
|
+
}
|
|
238
|
+
if (run.evaluationAssociation.seed !== evidence.seed) {
|
|
239
|
+
throw new Error(`replay evidence seed conflict for ${evidence.runId}: evidence=${evidence.seed}, run=${run.evaluationAssociation.seed}`);
|
|
240
|
+
}
|
|
241
|
+
if (run.evaluationAssociation.split &&
|
|
242
|
+
run.evaluationAssociation.split !== evidence.split) {
|
|
243
|
+
throw new Error(`replay evidence split conflict for ${evidence.runId}: evidence=${evidence.split}, run=${run.evaluationAssociation.split}`);
|
|
244
|
+
}
|
|
245
|
+
if (run.evaluationAssociation.taskRef &&
|
|
246
|
+
run.evaluationAssociation.taskRef !== evidence.taskRef) {
|
|
247
|
+
throw new Error(`replay evidence taskRef conflict for ${evidence.runId}: evidence=${evidence.taskRef}, run=${run.evaluationAssociation.taskRef}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (![
|
|
251
|
+
"finished",
|
|
252
|
+
"failed",
|
|
253
|
+
"partial_failed",
|
|
254
|
+
"superseded",
|
|
255
|
+
"abandoned",
|
|
256
|
+
].includes(run.status)) {
|
|
257
|
+
throw new Error(`completed DAG run ${evidence.runId} is not terminal (status=${run.status})`);
|
|
258
|
+
}
|
|
259
|
+
const { metrics, missingFields } = metricsForRun(run);
|
|
260
|
+
rows.push({
|
|
261
|
+
...evidence,
|
|
262
|
+
lifecycle: "completed",
|
|
263
|
+
runStatus: run.status,
|
|
264
|
+
metrics,
|
|
265
|
+
missingFields,
|
|
266
|
+
evidenceRefs: [
|
|
267
|
+
{ path: stateRefPath, sha256: evidence.stateSha256 },
|
|
268
|
+
{ path: runRefPath, sha256: evidence.runSha256 },
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const scorecard = buildScorecard({
|
|
273
|
+
replayId: spec.replayId,
|
|
274
|
+
incumbentCandidateId: spec.incumbentCandidateId,
|
|
275
|
+
challengerCandidateId: spec.challengerCandidateId,
|
|
276
|
+
rows,
|
|
277
|
+
});
|
|
278
|
+
const markdown = formatReplayMarkdown(scorecard);
|
|
279
|
+
if (input.writeArtifacts === false) {
|
|
280
|
+
return { scorecard, markdown };
|
|
281
|
+
}
|
|
282
|
+
const paths = await writeReplayArtifacts({
|
|
283
|
+
repoRoot: input.repoRoot,
|
|
284
|
+
replayId: spec.replayId,
|
|
285
|
+
scorecard,
|
|
286
|
+
markdown,
|
|
287
|
+
});
|
|
288
|
+
return { scorecard, markdown, ...paths };
|
|
289
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const evalSplitSchema = z.enum(["public", "private", "held_out"]);
|
|
3
|
+
export const replayEvidenceRefSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
candidateId: z.string().min(1),
|
|
6
|
+
taskRef: z.string().min(1),
|
|
7
|
+
seed: z.number().int().nonnegative(),
|
|
8
|
+
split: evalSplitSchema,
|
|
9
|
+
runId: z
|
|
10
|
+
.string()
|
|
11
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "runId must be one safe path segment"),
|
|
12
|
+
stateSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
13
|
+
runSha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
export const replaySpecSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
schemaVersion: z.literal(1),
|
|
19
|
+
replayId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
|
|
20
|
+
incumbentCandidateId: z.string().min(1),
|
|
21
|
+
challengerCandidateId: z.string().min(1),
|
|
22
|
+
evidence: z.array(replayEvidenceRefSchema).min(1),
|
|
23
|
+
})
|
|
24
|
+
.strict()
|
|
25
|
+
.superRefine((spec, ctx) => {
|
|
26
|
+
if (spec.incumbentCandidateId === spec.challengerCandidateId) {
|
|
27
|
+
ctx.addIssue({
|
|
28
|
+
code: z.ZodIssueCode.custom,
|
|
29
|
+
message: "incumbentCandidateId and challengerCandidateId must differ",
|
|
30
|
+
path: ["challengerCandidateId"],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const allowed = new Set([
|
|
34
|
+
spec.incumbentCandidateId,
|
|
35
|
+
spec.challengerCandidateId,
|
|
36
|
+
]);
|
|
37
|
+
const pairKeys = new Set();
|
|
38
|
+
for (let i = 0; i < spec.evidence.length; i += 1) {
|
|
39
|
+
const evidence = spec.evidence[i];
|
|
40
|
+
if (!allowed.has(evidence.candidateId)) {
|
|
41
|
+
ctx.addIssue({
|
|
42
|
+
code: z.ZodIssueCode.custom,
|
|
43
|
+
message: "evidence candidateId must match incumbent or challenger",
|
|
44
|
+
path: ["evidence", i, "candidateId"],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const key = `${evidence.candidateId}\u0000${evidence.split}\u0000${evidence.taskRef}\u0000${evidence.seed}`;
|
|
48
|
+
if (pairKeys.has(key)) {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: z.ZodIssueCode.custom,
|
|
51
|
+
message: "duplicate evidence for candidateId + split + taskRef + seed",
|
|
52
|
+
path: ["evidence", i],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
pairKeys.add(key);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// --- Candidate Registry (M2 W2.1–W2.2) ---
|
|
59
|
+
export const candidateIdSchema = z
|
|
60
|
+
.string()
|
|
61
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/, "candidateId must be one safe path segment");
|
|
62
|
+
export const candidateKindSchema = z.enum([
|
|
63
|
+
"prompt",
|
|
64
|
+
"skill",
|
|
65
|
+
"context_policy",
|
|
66
|
+
"model_routing",
|
|
67
|
+
"profile",
|
|
68
|
+
"composite",
|
|
69
|
+
]);
|
|
70
|
+
export const candidateContentRefSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
path: z
|
|
73
|
+
.string()
|
|
74
|
+
.min(1)
|
|
75
|
+
.refine((value) => !pathIsAbsoluteLike(value), "content ref path must be repo-relative"),
|
|
76
|
+
sha256: z
|
|
77
|
+
.string()
|
|
78
|
+
.regex(/^(sha256:)?[a-f0-9]{64}$/i, "sha256 must be 64 hex digits"),
|
|
79
|
+
})
|
|
80
|
+
.strict();
|
|
81
|
+
function pathIsAbsoluteLike(value) {
|
|
82
|
+
if (value.startsWith("/") || value.startsWith("\\"))
|
|
83
|
+
return true;
|
|
84
|
+
if (/^[A-Za-z]:[\\/]/.test(value))
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
export const candidateManifestInputSchema = z
|
|
89
|
+
.object({
|
|
90
|
+
schemaVersion: z.literal(1),
|
|
91
|
+
candidateId: candidateIdSchema,
|
|
92
|
+
parentCandidateId: candidateIdSchema.nullable().optional(),
|
|
93
|
+
candidateKind: candidateKindSchema,
|
|
94
|
+
createdAt: z.string().min(1),
|
|
95
|
+
description: z.string().optional(),
|
|
96
|
+
contentRefs: z.array(candidateContentRefSchema).min(1),
|
|
97
|
+
// optional on input; always computed/verified on register/read
|
|
98
|
+
bundleHash: z
|
|
99
|
+
.string()
|
|
100
|
+
.regex(/^(sha256:)?[a-f0-9]{64}$/i)
|
|
101
|
+
.optional(),
|
|
102
|
+
})
|
|
103
|
+
.strict();
|
|
104
|
+
export const candidateManifestSchema = candidateManifestInputSchema
|
|
105
|
+
.extend({
|
|
106
|
+
parentCandidateId: candidateIdSchema.nullable(),
|
|
107
|
+
bundleHash: z.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
108
|
+
})
|
|
109
|
+
.strict();
|
|
110
|
+
export const lifecycleStateSchema = z.enum([
|
|
111
|
+
"proposed",
|
|
112
|
+
"eligible",
|
|
113
|
+
"experimenting",
|
|
114
|
+
"accepted",
|
|
115
|
+
"rejected",
|
|
116
|
+
"invalid",
|
|
117
|
+
"retired",
|
|
118
|
+
]);
|
|
119
|
+
export const lifecycleEventSchema = z
|
|
120
|
+
.object({
|
|
121
|
+
schemaVersion: z.literal(1),
|
|
122
|
+
seq: z.number().int().positive(),
|
|
123
|
+
from: lifecycleStateSchema.nullable(),
|
|
124
|
+
to: lifecycleStateSchema,
|
|
125
|
+
reason: z.string().min(1),
|
|
126
|
+
at: z.string().min(1),
|
|
127
|
+
previousEventHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
128
|
+
eventHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
129
|
+
})
|
|
130
|
+
.strict();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { runDoctor } from "../commands/doctor.js";
|
|
2
2
|
import { runDocsArchive } from "../commands/docs-archive.js";
|
|
3
3
|
import { runDocsAudit } from "../commands/docs-audit.js";
|
|
4
|
+
import { runEval } from "../commands/eval.js";
|
|
4
5
|
import { runCoverageAudit } from "../commands/coverage-audit.js";
|
|
5
6
|
import { runExamples } from "../commands/examples.js";
|
|
6
7
|
import { runCloseout } from "../commands/closeout.js";
|
|
@@ -10,7 +11,7 @@ import { runInstructions } from "../commands/instructions.js";
|
|
|
10
11
|
import { runNewTask } from "../commands/new-task.js";
|
|
11
12
|
import { runImportPrd } from "../commands/import-prd.js";
|
|
12
13
|
import { runPlanList } from "../commands/plan-list.js";
|
|
13
|
-
import { runPlanCheck, runPlanComplete, runPlanCreate } from "../commands/plan.js";
|
|
14
|
+
import { runPlanCheck, runPlanComplete, runPlanCreate, } from "../commands/plan.js";
|
|
14
15
|
import { runPromoteRun } from "../commands/promote-run.js";
|
|
15
16
|
import { runSpine } from "../commands/spine.js";
|
|
16
17
|
import { runStats } from "../commands/stats.js";
|
|
@@ -76,6 +77,7 @@ const INIT_SUBCOMMANDS = [
|
|
|
76
77
|
"update",
|
|
77
78
|
];
|
|
78
79
|
const EXAMPLES_SUBCOMMANDS = ["list", "show", "copy"];
|
|
80
|
+
const EVAL_SUBCOMMANDS = ["replay", "report", "candidate"];
|
|
79
81
|
const CLOSEOUT_SUBCOMMANDS = ["task"];
|
|
80
82
|
const PLAN_SUBCOMMANDS = ["list", "create", "complete", "check"];
|
|
81
83
|
const SPINE_SUBCOMMANDS = ["audit"];
|
|
@@ -194,6 +196,17 @@ export const COMMAND_DEFINITIONS = [
|
|
|
194
196
|
await runExamples(repoRoot, [subcommand, ...rest].filter(Boolean));
|
|
195
197
|
},
|
|
196
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: "eval",
|
|
201
|
+
adapter: "required",
|
|
202
|
+
tier: "operator",
|
|
203
|
+
intent: "Replay completed DAG evidence and manage immutable Candidate Registry lifecycle without live model execution or promotion.",
|
|
204
|
+
usage: "eval <replay|report|candidate> ...; candidate <register|show|list|transition> [--json|--markdown]",
|
|
205
|
+
subcommands: [...EVAL_SUBCOMMANDS],
|
|
206
|
+
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
207
|
+
await runEval(repoRoot, [subcommand, ...rest].filter((arg) => Boolean(arg)));
|
|
208
|
+
},
|
|
209
|
+
},
|
|
197
210
|
{
|
|
198
211
|
name: "new-task",
|
|
199
212
|
adapter: "required",
|
|
@@ -286,17 +299,17 @@ export const COMMAND_DEFINITIONS = [
|
|
|
286
299
|
if (subcommand === "create") {
|
|
287
300
|
const [planId, ...titleParts] = rest;
|
|
288
301
|
if (!planId)
|
|
289
|
-
throw new Error(
|
|
302
|
+
throw new Error('usage: plan create <plan-id> "<title>"');
|
|
290
303
|
await runPlanCreate(repoRoot, planId, titleParts.join(" "));
|
|
291
304
|
return;
|
|
292
305
|
}
|
|
293
306
|
if (subcommand === "complete") {
|
|
294
307
|
const [planId, ...summaryParts] = rest;
|
|
295
308
|
if (!planId)
|
|
296
|
-
throw new Error(
|
|
309
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
297
310
|
const summary = parseSummaryFlag(summaryParts);
|
|
298
311
|
if (!summary)
|
|
299
|
-
throw new Error(
|
|
312
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
300
313
|
await runPlanComplete(repoRoot, planId, { summary });
|
|
301
314
|
return;
|
|
302
315
|
}
|
package/dist/cli/program.js
CHANGED
|
@@ -22,6 +22,7 @@ import { runDagWorkflowValidate } from "../commands/dag-workflow-validate.js";
|
|
|
22
22
|
import { runDelegate } from "../commands/delegate.js";
|
|
23
23
|
import { runDocsArchive } from "../commands/docs-archive.js";
|
|
24
24
|
import { runDocsAudit } from "../commands/docs-audit.js";
|
|
25
|
+
import { runEval } from "../commands/eval.js";
|
|
25
26
|
import { runDoctor } from "../commands/doctor.js";
|
|
26
27
|
import { runExamples } from "../commands/examples.js";
|
|
27
28
|
import { runGoal } from "../commands/goal.js";
|
|
@@ -38,7 +39,7 @@ import { runImportPrd } from "../commands/import-prd.js";
|
|
|
38
39
|
import { runPiReuseBenchmark } from "../commands/pi-reuse-benchmark.js";
|
|
39
40
|
import { parsePiPromptArgs, printPiPromptUsage, runPiPrompt, } from "../commands/pi-prompt.js";
|
|
40
41
|
import { runPlanList } from "../commands/plan-list.js";
|
|
41
|
-
import { runPlanCheck, runPlanComplete, runPlanCreate } from "../commands/plan.js";
|
|
42
|
+
import { runPlanCheck, runPlanComplete, runPlanCreate, } from "../commands/plan.js";
|
|
42
43
|
import { runPromoteRun } from "../commands/promote-run.js";
|
|
43
44
|
import { runReferenceIndex } from "../commands/reference-index.js";
|
|
44
45
|
import { runRunDag } from "../commands/run-dag.js";
|
|
@@ -163,17 +164,17 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
163
164
|
if (subcommand === "create") {
|
|
164
165
|
const [planId, ...titleParts] = rest;
|
|
165
166
|
if (!planId)
|
|
166
|
-
throw new Error(
|
|
167
|
+
throw new Error('usage: plan create <plan-id> "<title>"');
|
|
167
168
|
await runPlanCreate(ctx.repoRoot, planId, titleParts.join(" "));
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
if (subcommand === "complete") {
|
|
171
172
|
const [planId, ...summaryParts] = rest;
|
|
172
173
|
if (!planId)
|
|
173
|
-
throw new Error(
|
|
174
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
174
175
|
const summary = parsePlanSummaryFlag(summaryParts);
|
|
175
176
|
if (!summary)
|
|
176
|
-
throw new Error(
|
|
177
|
+
throw new Error('usage: plan complete <plan-id> --summary "<summary>"');
|
|
177
178
|
await runPlanComplete(ctx.repoRoot, planId, { summary });
|
|
178
179
|
return;
|
|
179
180
|
}
|
|
@@ -244,6 +245,9 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
244
245
|
case "knowledge":
|
|
245
246
|
await runKnowledge(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
246
247
|
return;
|
|
248
|
+
case "eval":
|
|
249
|
+
await runEval(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
250
|
+
return;
|
|
247
251
|
case "dag":
|
|
248
252
|
await runDagAction(ctx.repoRoot, subcommand, rest);
|
|
249
253
|
return;
|