Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions reviews/aefb5700713bdcd9b9cd44a8b305905b78ace4ed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"recordVersion": 1,
"sha": "aefb5700713bdcd9b9cd44a8b305905b78ace4ed",
"governing": [
"docs/adr/ADR-0065-write-health-loudness.md"
],
"reviewer": {
"agent": "general-purpose",
"model": "opus",
"rounds": 2
},
"generated": "2026-10-19T14:47:38.212Z",
"findings": [
{
"id": "F1",
"severity": "minor",
"status": "fixed",
"summary": "guard extracted only [--lower value] flags: unbracketed --tags, --Why, bare --bogus, unbracketed --zzz stayed green; now every --flag token after `add` is extracted",
"acceptedBy": null
},
{
"id": "F2",
"severity": "minor",
"status": "open",
"summary": "guard feeds its own values, not the advertised ones, so a bad value form ([--tag a b]) passes; carried, non-blocking",
"acceptedBy": null
},
{
"id": "F3",
"severity": "minor",
"status": "open",
"summary": "slice starts at the first \" add \": a TMPDIR containing \" add --x\" false-reds; default macOS/CI temp paths do not; non-blocking",
"acceptedBy": null
}
],
"findingsCount": 3,
"ruledOut": [
"source-text guard: drives dist/cli.js via the real `add`",
"--status/--valid-until/--constitutional with a dummy value: accepted, no false red",
"other agent-facing --tags in src/docs/skills: none (args.ts trap comment, RFC-033 history, rejection tests only)",
"AI attribution / secrets: none",
"stale dist: npm test runs tsc in pretest; CI builds"
],
"mutations": [
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "hint reverted to [--tags a,b]",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "unbracketed --tags a,b",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "[--Why \"…\"]",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "bare [--bogus]",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "unbracketed --zzz x",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "invented [--foo x]",
"observedRed": true
},
{
"guard": "test/capture-fallback.test.ts > every optional flag it advertises is one the real CLI `add` accepts",
"mutation": "all bracketed flags removed",
"observedRed": true
}
],
"mutationsNote": null,
"verdict": "MERGE"
}
2 changes: 1 addition & 1 deletion src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ export function renderCaptureFallback(engineDir = dirname(fileURLToPath(import.m
return (
`capture fallback: if the kb_* tools error or disappear, the CLI still writes ` +
`(same engine, separate process; journaled per ADR-0064) —\n` +
` VFKB_DATA_DIR="${resolvePath(brainDir())}" ${target} add <type> "…" [--tags a,b] [--why "…"]`
` VFKB_DATA_DIR="${resolvePath(brainDir())}" ${target} add <type> "…" [--tag a,b] [--why "…"]`
);
}

Expand Down
28 changes: 26 additions & 2 deletions test/capture-fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
// runnable, and survive budget pressure — otherwise it is documentation that
// vanishes exactly when it is needed.
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { join, basename } from 'node:path';
import { join, basename, resolve } from 'node:path';
import { renderCaptureFallback, renderContextBundle, resolveCliFace, SESSION_BUDGET_CHARS } from '../src/engine.js';

let brain: string;
Expand Down Expand Up @@ -51,6 +52,29 @@ describe('ADR-0065 §1 — the capture-fallback line', () => {
expect(line).toMatch(/node .+ add <type>/);
});

it('every optional flag it advertises is one the real CLI `add` accepts', () => {
// The hint is pasted by an agent mid-loss, so a flag the CLI rejects defeats it.
// It once advertised `--tags`, which `add` rejects with "unknown flag --tags"
// (args.ts names it the observed trap). This drives the real built CLI with
// every [--flag value] the line advertises; it does not grep the source.
const d = join(brain, 'r');
mkdirSync(d, { recursive: true });
writeFileSync(join(d, 'cli.js'), '');
const line = renderCaptureFallback(d);
// EVERY --flag token after `add`, bracketed or not and whatever its case, so the
// same bug written another way (`--tags a,b` without brackets, `--Tag`) is caught too.
const cmd = line.slice(line.indexOf(' add '));
const flags = [...new Set([...cmd.matchAll(/(--[A-Za-z][A-Za-z-]*)/g)].map((m) => m[1]))];
expect(flags.length).toBeGreaterThan(0);
const cli = resolve(__dirname, '../dist/cli.js');
const args = ['add', 'fact', 'fallback hint probe'];
for (const f of flags) args.push(f, f === '--tag' || f === '--tags' ? 'probe,hint' : 'because');
execFileSync('node', [cli, ...args], { env: { ...process.env, VFKB_DATA_DIR: brain }, encoding: 'utf8', stdio: 'pipe' });
const entry = readFileSync(join(brain, 'entries.jsonl'), 'utf8').trim().split('\n').map((l) => JSON.parse(l)).pop();
expect(entry.text).toContain('fallback hint probe');
if (flags.includes('--tag')) expect(entry.tags).toEqual(['probe', 'hint']);
});

it('QUOTES both paths — an unquoted path truncates at the first space', () => {
// Plugin cache paths carry a version segment and may contain spaces. An
// unquoted command would silently run against the wrong path: precisely the
Expand Down
Loading