@phamvuhoang/otto-core 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cli-help.d.ts +6 -0
  2. package/dist/cli-help.d.ts.map +1 -1
  3. package/dist/cli-help.js +7 -1
  4. package/dist/cli-help.js.map +1 -1
  5. package/dist/git.d.ts +7 -0
  6. package/dist/git.d.ts.map +1 -1
  7. package/dist/git.js +20 -0
  8. package/dist/git.js.map +1 -1
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/input-sharpness.d.ts +80 -0
  14. package/dist/input-sharpness.d.ts.map +1 -0
  15. package/dist/input-sharpness.js +125 -0
  16. package/dist/input-sharpness.js.map +1 -0
  17. package/dist/inspect.d.ts.map +1 -1
  18. package/dist/inspect.js +25 -0
  19. package/dist/inspect.js.map +1 -1
  20. package/dist/loop.d.ts +6 -0
  21. package/dist/loop.d.ts.map +1 -1
  22. package/dist/loop.js +94 -4
  23. package/dist/loop.js.map +1 -1
  24. package/dist/report-finalize.d.ts.map +1 -1
  25. package/dist/report-finalize.js +49 -1
  26. package/dist/report-finalize.js.map +1 -1
  27. package/dist/run-bin.d.ts.map +1 -1
  28. package/dist/run-bin.js +5 -0
  29. package/dist/run-bin.js.map +1 -1
  30. package/dist/run-report.d.ts +15 -0
  31. package/dist/run-report.d.ts.map +1 -1
  32. package/dist/run-report.js.map +1 -1
  33. package/dist/verification-evidence.d.ts +41 -0
  34. package/dist/verification-evidence.d.ts.map +1 -0
  35. package/dist/verification-evidence.js +184 -0
  36. package/dist/verification-evidence.js.map +1 -0
  37. package/dist/verification-matrix.d.ts +159 -0
  38. package/dist/verification-matrix.d.ts.map +1 -0
  39. package/dist/verification-matrix.js +337 -0
  40. package/dist/verification-matrix.js.map +1 -0
  41. package/package.json +1 -1
  42. package/templates/plan.md +2 -0
  43. package/templates/verify.md +41 -1
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Verification matrix (issue #181 P24, slice 1 — "make verification provable").
3
+ *
4
+ * A pure data model + scorer for the roadmap's verification-artifact matrix:
5
+ * each requirement (a plan task / acceptance criterion) paired with HOW it was
6
+ * checked, the concrete artifact that proves it (a `file:line`, a commit SHA, a
7
+ * command transcript, a screenshot path), the result, and a confidence. Today
8
+ * the `verify` stage emits this as free-form DONE/GAP/DEFERRED prose; P24 turns
9
+ * it into a structured matrix a maintainer can scan, and a "verification
10
+ * gallery" a non-engineer can accept/reject from.
11
+ *
12
+ * This slice is the substrate — the type, a summary scorer (counts, artifact
13
+ * coverage, an overall verdict), and a formatter that renders the matrix and
14
+ * surfaces failures / unproven requirements as explicit risks rather than
15
+ * burying them in logs. INERT on the loop: nothing here runs a stage or changes
16
+ * behavior; later P24 slices populate it from the verify stage and fold it into
17
+ * run reports + `otto-inspect`. Mirrors how the plan/context/report rubrics
18
+ * shipped pure-then-wired.
19
+ */
20
+ const METHODS = new Set([
21
+ "test",
22
+ "command",
23
+ "visual",
24
+ "inspection",
25
+ "manual",
26
+ ]);
27
+ const RESULTS = new Set([
28
+ "pass",
29
+ "fail",
30
+ "partial",
31
+ "deferred",
32
+ ]);
33
+ const CONFIDENCES = new Set(["high", "medium", "low"]);
34
+ /**
35
+ * Whether a string is a *typed*, durable artifact reference that actually proves
36
+ * something — a `file:line` (or `file:line-range`), a path with a separator or a
37
+ * file extension (a transcript / screenshot / source file), or a 7–40 char commit
38
+ * SHA. A bare command (`node --test`), prose (`read the code`), or a placeholder
39
+ * (`TODO`) is NOT proof and returns false, so the coverage signal can't be earned
40
+ * by an unverifiable string (#181 review). Pure.
41
+ */
42
+ export function isValidArtifactReference(ref) {
43
+ const r = ref.trim();
44
+ if (!r)
45
+ return false;
46
+ if (/\s/.test(r))
47
+ return false; // commands / prose carry whitespace
48
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(r))
49
+ return false; // URLs (scheme://)
50
+ if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(r))
51
+ return false; // path traversal
52
+ if (/^[0-9a-f]{7,40}$/i.test(r))
53
+ return true; // commit SHA
54
+ if (/^[\w./\\-]+:\d+(?:-\d+)?$/.test(r))
55
+ return true; // file:line or file:line-range
56
+ return /[/\\]/.test(r) || /\.[A-Za-z0-9]{1,8}$/.test(r); // a path, or a file with an extension
57
+ }
58
+ /**
59
+ * Whether an entry carries a *valid, typed* proving artifact. Syntactically valid
60
+ * (see {@link isValidArtifactReference}) AND not marked non-existent by the loop's
61
+ * filesystem/git validation (`artifactExists === false`). When `artifactExists` is
62
+ * undefined the syntax check stands alone — the pure scorer cannot touch the
63
+ * filesystem, so the loop sets `artifactExists` before this matters at runtime.
64
+ */
65
+ function hasArtifact(e) {
66
+ return (Boolean(e.artifactPath) &&
67
+ isValidArtifactReference(e.artifactPath) &&
68
+ e.artifactExists !== false);
69
+ }
70
+ /**
71
+ * Parse an agent-emitted verification matrix (a JSON `VerificationEntry[]`,
72
+ * e.g. the `.otto-tmp/verify-matrix.json` the verify stage writes), keeping the
73
+ * parse diagnostics so a malformed/partial matrix is reported, not silently
74
+ * dropped (#181 review). Tolerant: never throws, drops any entry missing a
75
+ * non-empty `requirement` or carrying an unknown `method`/`result`, defaults an
76
+ * absent/invalid `confidence` to `medium`, and counts each dropped row. Non-array
77
+ * / malformed JSON ⇒ `{ entries: [], dropped: 0, parsed: false }`. Pure.
78
+ */
79
+ export function parseVerificationMatrixWithDiagnostics(raw) {
80
+ let parsed;
81
+ try {
82
+ parsed = JSON.parse(raw);
83
+ }
84
+ catch {
85
+ return { entries: [], dropped: 0, parsed: false };
86
+ }
87
+ if (!Array.isArray(parsed))
88
+ return { entries: [], dropped: 0, parsed: false };
89
+ const entries = [];
90
+ let dropped = 0;
91
+ for (const item of parsed) {
92
+ const entry = coerceEntry(item);
93
+ if (entry)
94
+ entries.push(entry);
95
+ else
96
+ dropped += 1;
97
+ }
98
+ return { entries, dropped, parsed: true };
99
+ }
100
+ function coerceEntry(item) {
101
+ if (!item || typeof item !== "object")
102
+ return null;
103
+ const r = item;
104
+ const requirement = typeof r.requirement === "string" ? r.requirement.trim() : "";
105
+ if (!requirement)
106
+ return null;
107
+ if (typeof r.method !== "string" || !METHODS.has(r.method))
108
+ return null;
109
+ if (typeof r.result !== "string" || !RESULTS.has(r.result))
110
+ return null;
111
+ const confidence = typeof r.confidence === "string" && CONFIDENCES.has(r.confidence)
112
+ ? r.confidence
113
+ : "medium";
114
+ return {
115
+ requirement,
116
+ method: r.method,
117
+ check: typeof r.check === "string" ? r.check : "",
118
+ ...(typeof r.artifactPath === "string" && r.artifactPath
119
+ ? { artifactPath: r.artifactPath }
120
+ : {}),
121
+ ...(typeof r.beforePath === "string" && r.beforePath
122
+ ? { beforePath: r.beforePath }
123
+ : {}),
124
+ result: r.result,
125
+ confidence,
126
+ ...(typeof r.note === "string" && r.note ? { note: r.note } : {}),
127
+ };
128
+ }
129
+ /** Parse a verification matrix into valid entries (diagnostics discarded). Pure. */
130
+ export function parseVerificationMatrix(raw) {
131
+ return parseVerificationMatrixWithDiagnostics(raw).entries;
132
+ }
133
+ /** Whether a requirement is one we expect a concrete artifact for (i.e. not deferred). */
134
+ function isVerifiable(e) {
135
+ return e.result !== "deferred";
136
+ }
137
+ /** Tally a verification matrix into counts, artifact coverage, and a verdict. Pure. */
138
+ export function summarizeVerification(entries) {
139
+ const total = entries.length;
140
+ const pass = entries.filter((e) => e.result === "pass").length;
141
+ const fail = entries.filter((e) => e.result === "fail").length;
142
+ const partial = entries.filter((e) => e.result === "partial").length;
143
+ const deferred = entries.filter((e) => e.result === "deferred").length;
144
+ const verifiable = entries.filter(isVerifiable);
145
+ const withArtifact = entries.filter(hasArtifact).length;
146
+ const verifiableProven = verifiable.filter(hasArtifact).length;
147
+ // Vacuous truth: an all-deferred matrix (entries, but none verifiable) is fully
148
+ // covered — there is nothing left to prove (#181 review). An empty matrix has
149
+ // nothing measured, so coverage is 0.
150
+ const coverage = total === 0
151
+ ? 0
152
+ : verifiable.length > 0
153
+ ? verifiableProven / verifiable.length
154
+ : 1;
155
+ let verdict;
156
+ if (total === 0)
157
+ verdict = "empty";
158
+ else if (fail > 0)
159
+ verdict = "gaps";
160
+ else if (verifiable.length > 0 && verifiableProven === 0)
161
+ verdict = "unproven";
162
+ else if (coverage >= 1 && partial === 0)
163
+ verdict = "verified";
164
+ else
165
+ verdict = "partial";
166
+ return {
167
+ total,
168
+ pass,
169
+ fail,
170
+ partial,
171
+ deferred,
172
+ withArtifact,
173
+ coverage,
174
+ verdict,
175
+ };
176
+ }
177
+ /**
178
+ * Score a verification matrix against the roadmap's coverage bar (P24): every
179
+ * verifiable requirement must carry a valid artifact, none may fail, none may be
180
+ * left partial, and no rows may have been dropped during parsing. `dropped` rows
181
+ * are unknown requirements that cannot be assumed proven, so any drop FAILs the
182
+ * gate (#181 re-review). The `unproven`/`failed`/`incomplete` lists + `dropped`
183
+ * are exactly why the gate did not pass. Pure.
184
+ */
185
+ export function scoreVerificationCoverage(entries, dropped = 0) {
186
+ const summary = summarizeVerification(entries);
187
+ return {
188
+ passed: summary.verdict === "verified" && dropped === 0,
189
+ coverage: summary.coverage,
190
+ unproven: entries
191
+ .filter((e) => isVerifiable(e) && !hasArtifact(e))
192
+ .map((e) => e.requirement),
193
+ failed: entries
194
+ .filter((e) => e.result === "fail")
195
+ .map((e) => e.requirement),
196
+ incomplete: entries
197
+ .filter((e) => e.result === "partial")
198
+ .map((e) => e.requirement),
199
+ dropped,
200
+ };
201
+ }
202
+ /**
203
+ * Render the verification-coverage gate as a report block (mirrors the emit-time
204
+ * legibility gate): a PASS/FAIL verdict, the artifact-backed coverage, and — on
205
+ * FAIL — the unproven/failed requirements plus how to clear them. Empty string
206
+ * for an empty matrix, so a run with no verification adds no gate. Pure.
207
+ */
208
+ export function formatVerificationCoverageGate(entries, dropped = 0) {
209
+ if (entries.length === 0 && dropped === 0)
210
+ return "";
211
+ const g = scoreVerificationCoverage(entries, dropped);
212
+ const lines = [
213
+ "## Verification Coverage Gate",
214
+ "",
215
+ `Gate: **${g.passed ? "PASS" : "FAIL"}** — ${pct.format(g.coverage * 100)}% of verifiable requirements are artifact-backed.`,
216
+ ];
217
+ if (!g.passed) {
218
+ if (g.dropped > 0) {
219
+ lines.push("", `Dropped ${g.dropped} malformed matrix row(s) — those requirements are unknown and unverified; fix the matrix.`);
220
+ }
221
+ if (g.failed.length > 0) {
222
+ lines.push("", `Failed: ${g.failed.join(", ")}.`);
223
+ }
224
+ if (g.incomplete.length > 0) {
225
+ lines.push("", `Incomplete (partial — finish or split): ${g.incomplete.join(", ")}.`);
226
+ }
227
+ if (g.unproven.length > 0) {
228
+ lines.push("", `Unproven (cite a valid artifact — \`file:line\`, a commit SHA, a transcript/screenshot path — or mark the requirement \`deferred\`): ${g.unproven.join(", ")}.`);
229
+ }
230
+ // A FAIL with no failed/incomplete/unproven items would be unexplained; make
231
+ // the residual reason explicit rather than leaving an empty gate (#181 review).
232
+ if (g.dropped === 0 &&
233
+ g.failed.length === 0 &&
234
+ g.incomplete.length === 0 &&
235
+ g.unproven.length === 0) {
236
+ lines.push("", "No requirement is fully proven, yet none is individually failed/partial/unproven — review the matrix.");
237
+ }
238
+ }
239
+ return lines.join("\n");
240
+ }
241
+ /** Path prefix of artifacts relocated into the run bundle (see
242
+ * `verification-evidence.ts`); only these resolve relative to report.md. */
243
+ export const BUNDLE_ARTIFACT_PREFIX = "verification/";
244
+ const IMAGE_RE = /\.(?:png|jpe?g|gif|webp|svg|avif)$/i;
245
+ /**
246
+ * A path safe to embed as a markdown image: it was actually copied into the
247
+ * physical run bundle by the impure layer (`bundled === true` — not a string
248
+ * prefix the agent could spoof) and has an image extension. So a rejected URL, an
249
+ * un-relocated path, or an agent-supplied `verification/…` masquerade is never
250
+ * emitted as an image (#181 boundary review).
251
+ */
252
+ function embeddableImage(p, bundled) {
253
+ return (bundled === true &&
254
+ typeof p === "string" &&
255
+ !p.includes("..") &&
256
+ IMAGE_RE.test(p));
257
+ }
258
+ /**
259
+ * Render the visual evidence (P24 visual half) as a markdown "Screenshot
260
+ * Evidence" section that *embeds* each `visual` entry's captured screenshot —
261
+ * a single image, or a before → after pair — so a non-engineer reading the run
262
+ * report sees the proof, not just a path. Only artifacts the impure layer actually
263
+ * **copied into the bundle** (`artifactBundled`/`beforeBundled`) and that are
264
+ * images are embedded; a visual check the environment could not render, or any
265
+ * unbundled/spoofed path, is left to the coverage gate to flag, never emitted as
266
+ * an image. Empty string when no embeddable visual evidence exists. Pure.
267
+ */
268
+ export function formatVisualEvidence(entries) {
269
+ const visuals = entries.filter((e) => e.method === "visual" &&
270
+ embeddableImage(e.artifactPath, e.artifactBundled));
271
+ if (visuals.length === 0)
272
+ return "";
273
+ const lines = ["## Screenshot Evidence", ""];
274
+ for (const e of visuals) {
275
+ lines.push(`### ${e.requirement}`, "");
276
+ if (embeddableImage(e.beforePath, e.beforeBundled)) {
277
+ lines.push(`- Before: ![before](${e.beforePath})`);
278
+ lines.push(`- After: ![after](${e.artifactPath})`);
279
+ }
280
+ else {
281
+ lines.push(`![${e.requirement}](${e.artifactPath})`);
282
+ }
283
+ lines.push("");
284
+ }
285
+ return lines.join("\n").trimEnd();
286
+ }
287
+ const RESULT_MARK = {
288
+ pass: "✓",
289
+ fail: "✗",
290
+ partial: "~",
291
+ deferred: "·",
292
+ };
293
+ const pct = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
294
+ /** Collapse a (possibly multi-line) check to one bounded, fence-safe line. */
295
+ function oneLine(s, max = 80) {
296
+ const flat = s.replace(/\s+/g, " ").trim();
297
+ return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
298
+ }
299
+ /**
300
+ * Render a verification matrix as a scannable report: a header verdict line, one
301
+ * row per requirement (mark · method · requirement · artifact), and an explicit
302
+ * **Risks** section listing every failed or unproven requirement so a reader
303
+ * cannot miss them. Pure.
304
+ */
305
+ export function formatVerificationMatrix(entries) {
306
+ if (entries.length === 0) {
307
+ return "Verification: no verification recorded for this run.";
308
+ }
309
+ const s = summarizeVerification(entries);
310
+ const lines = [
311
+ `Verification: ${s.verdict} — ${s.pass}/${s.total} pass` +
312
+ (s.fail > 0 ? `, ${s.fail} fail` : "") +
313
+ (s.partial > 0 ? `, ${s.partial} partial` : "") +
314
+ (s.deferred > 0 ? `, ${s.deferred} deferred` : "") +
315
+ ` · ${pct.format(s.coverage * 100)}% artifact-backed`,
316
+ ];
317
+ for (const e of entries) {
318
+ const artifact = hasArtifact(e)
319
+ ? ` → ${e.artifactPath}`
320
+ : e.artifactPath
321
+ ? ` → ${e.artifactPath} (not a valid artifact)`
322
+ : " → (no artifact)";
323
+ const conf = e.confidence !== "high" ? ` [${e.confidence}]` : "";
324
+ const check = e.check ? ` · check: ${oneLine(e.check)}` : "";
325
+ lines.push(` ${RESULT_MARK[e.result]} ${e.method}: ${e.requirement}${conf}${artifact}${check}`);
326
+ }
327
+ const risks = entries.filter((e) => e.result === "fail" || (isVerifiable(e) && !hasArtifact(e)));
328
+ if (risks.length > 0) {
329
+ lines.push("", `Risks (${risks.length}):`);
330
+ for (const e of risks) {
331
+ const why = e.result === "fail" ? "failed" : "unproven (no valid artifact)";
332
+ lines.push(` - ${e.requirement} — ${why}`);
333
+ }
334
+ }
335
+ return lines.join("\n");
336
+ }
337
+ //# sourceMappingURL=verification-matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verification-matrix.js","sourceRoot":"","sources":["../src/verification-matrix.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phamvuhoang/otto-core",
3
- "version": "0.29.0",
3
+ "version": "0.31.0",
4
4
  "description": "Claude Code AFK orchestration: iteration loop, native-sandbox runner, template renderer.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/templates/plan.md CHANGED
@@ -18,6 +18,8 @@
18
18
 
19
19
  </inputs>
20
20
 
21
+ {{ SHARPENING }}
22
+
21
23
  # PLAN (AUTHOR THE SPEC + PLAN — DO NOT IMPLEMENT)
22
24
 
23
25
  You are PLANNING, not implementing. Turn the thin task in `<inputs>` into a
@@ -22,7 +22,7 @@
22
22
 
23
23
  You are VERIFYING, not implementing. The `<inputs>` block names a plan and PRD (conventionally file paths). `Read` them.
24
24
 
25
- **Make NO commits and NO source edits.** You may read files and run the test/type suites. The only file you may write is the report named at the end.
25
+ **Make NO commits and NO source edits.** You may read files and run the test/type suites. The only files you may write are the verification report and matrix named below and any verification artifacts they cite (e.g. screenshots), all under the gitignored `.otto-tmp/` scratch dir — nothing else.
26
26
 
27
27
  # RECONCILE
28
28
 
@@ -49,6 +49,46 @@ Write your report to `.otto-tmp/verify-report.md` using the `Write` tool (this p
49
49
 
50
50
  @include:quality-report.md
51
51
 
52
+ # VERIFICATION MATRIX (MACHINE-READABLE)
53
+
54
+ Also write a structured verification matrix to `.otto-tmp/verify-matrix.json`
55
+ using the `Write` tool (gitignored scratch — this, the report above, and any
56
+ screenshot artifacts cited below are the only writes you may make). It is a JSON
57
+ array, one entry per plan task /
58
+ acceptance criterion you reconciled, so a maintainer (or a non-engineer) can scan
59
+ exactly what was proven and how:
60
+
61
+ ```json
62
+ [
63
+ {
64
+ "requirement": "<the task / acceptance criterion>",
65
+ "method": "test | command | visual | inspection | manual",
66
+ "check": "<the exact command run, the assertion, or the visual checked>",
67
+ "artifactPath": "<proof: file:line, a commit SHA, a transcript/screenshot path; omit if none>",
68
+ "result": "pass | fail | partial | deferred",
69
+ "confidence": "high | medium | low"
70
+ }
71
+ ]
72
+ ```
73
+
74
+ Rules: one entry per task; `result` mirrors your DONE/GAP/DEFERRED classification
75
+ (`pass` = DONE, `fail`/`partial` = GAP, `deferred` = DEFERRED); always cite an
76
+ `artifactPath` when one exists (a `file:line`, the commit SHA that implements it,
77
+ or the suite command) — an entry with no artifact is an unproven claim and should
78
+ say so via `"confidence": "low"`. Use the real commands you ran; do not invent
79
+ proof. A malformed file is ignored, so prefer omitting a field to guessing.
80
+
81
+ **Visual evidence (opportunistic).** For a UI/web requirement, if a screenshot
82
+ tool and a renderable target (a running dev server or a static built artifact)
83
+ are actually available to you, capture a screenshot, save it under the gitignored
84
+ `.otto-tmp/` scratch dir, and emit a `"method": "visual"` entry whose
85
+ `artifactPath` is that screenshot path; for a before/after change set `beforePath`
86
+ to the prior-state screenshot and `artifactPath` to the new one. **Never fabricate
87
+ a screenshot or a path.** If you cannot render or capture the UI in this
88
+ environment, still emit the `visual` entry but **omit `artifactPath`** and set
89
+ `"confidence": "low"` — the coverage gate then reports the gap honestly instead of
90
+ claiming unproven visual proof.
91
+
52
92
  # CROSS-RUN QUALITY SUMMARY (READ-ONLY)
53
93
 
54
94
  Beyond _this_ run, give the maintainer a quality rollup **across** runs so they can