@dougharness/cli 1.0.1 → 1.0.2

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.
@@ -3,7 +3,6 @@ export interface DougConfig {
3
3
  version: 1;
4
4
  doug: {
5
5
  name: string;
6
- version: string;
7
6
  };
8
7
  packageManager: string | null;
9
8
  commands: Record<string, string>;
@@ -1,16 +1,6 @@
1
- import { createRequire } from "node:module";
2
1
  import { homedir } from "node:os";
3
2
  import { join } from "node:path";
4
3
  import { projectSlug } from "@dougharness/flow/lib/cost.mjs";
5
- function cliVersion() {
6
- try {
7
- const require = createRequire(import.meta.url);
8
- return require("../../package.json").version;
9
- }
10
- catch {
11
- return "0.0.0";
12
- }
13
- }
14
4
  export function generateConfig(d) {
15
5
  const n = d.node;
16
6
  const p = d.python;
@@ -121,7 +111,7 @@ export function generateConfig(d) {
121
111
  }
122
112
  return {
123
113
  version: 1,
124
- doug: { name: "Doug", version: cliVersion() },
114
+ doug: { name: "Doug" },
125
115
  packageManager: n.packageManager,
126
116
  commands,
127
117
  formatter: n.formatter ? { command: n.formatter.command, extensions: n.formatter.extensions } : null,
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/generate/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/generate/config.ts"],"names":[],"mappings":"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"}
@@ -56,6 +56,15 @@ export function buildProposal(d) {
56
56
  const target = `.doug/hooks/${f.rel}`;
57
57
  changes.push({ path: target, before: readIfExists(join(dir, target)), after: readFileSync(f.abs, "utf8"), reason: "Vendored from @dougharness/gates. Zero dependencies; refreshed by re-running `doug init`." });
58
58
  }
59
+ // 2b. .doug/hooks/VERSION: records the installed @dougharness/gates version the status line reads, so it
60
+ // stays in sync with the vendored scripts above without a user hand-maintaining it.
61
+ const gatesPkgVersion = JSON.parse(readFileSync(join(src, "package.json"), "utf8")).version;
62
+ changes.push({
63
+ path: ".doug/hooks/VERSION",
64
+ before: readIfExists(join(dir, ".doug/hooks/VERSION")),
65
+ after: `${gatesPkgVersion}\n`,
66
+ reason: "Records the installed @dougharness/gates version so the status line prints it, instead of a version hand-maintained in config. Re-synced on every `doug init` re-run.",
67
+ });
59
68
  // 3. .claude/settings.json (merge).
60
69
  const settingsPath = join(dir, ".claude/settings.json");
61
70
  const existingSettingsText = readIfExists(settingsPath);
@@ -1 +1 @@
1
- {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/generate/proposal.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/generate/proposal.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "doug-flow",
3
3
  "displayName": "Doug Flow",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "description": "Plan, approve, implement one task per worktree, verify, review. A deterministic Workflow-tool template plus planner, implementer, verifier, and reviewer subagents. The plan is a file; nothing is implemented until you approve it.",
6
6
  "author": { "name": "Doug contributors" },
7
7
  "agents": ["./agents/planner.md", "./agents/researcher.md", "./agents/lead.md", "./agents/implementer.md", "./agents/verifier.md", "./agents/reviewer.md", "./agents/adversary.md", "./agents/adversary-claude.md"],
@@ -0,0 +1,103 @@
1
+ {
2
+ "margin": 0.15,
3
+ "paraphrase": [
4
+ [
5
+ "The pre-commit hook re-runs the whole gate, so committing without waiting for tests wastes the developer's time.",
6
+ "Skipping ahead of the test run before a commit is pointless because the hook repeats every check anyway."
7
+ ],
8
+ [
9
+ "Worktrees under .claude/worktrees are gitignored and safe to delete once a task lands.",
10
+ "Once a task merges, the per-task checkout folder can be removed since it was never tracked by git."
11
+ ],
12
+ [
13
+ "Plan verify commands should call pnpm test:unit, never the full suite, because pnpm test spawns a live Codex call.",
14
+ "Use the unit-only test script for a plan's verify step; running the full suite triggers an actual Codex invocation."
15
+ ],
16
+ [
17
+ "A worker nearing its context limit should stop at a clean boundary and report a partial result instead of guessing.",
18
+ "When an agent's context window is almost full, it ought to pause at a good stopping point and flag the work as unfinished."
19
+ ],
20
+ [
21
+ "node:sqlite requires Node 22.16 or newer, so an older runtime must fail with one clear message rather than a stack trace.",
22
+ "Running on a Node version below 22.16 needs to produce a single readable error, not a crash dump, since the built-in SQLite module needs that version."
23
+ ],
24
+ [
25
+ "Force-deleting a git branch is blocked by the bash guard; merge it first and use the plain delete flag.",
26
+ "The shell guard refuses a hard branch removal, so branches must be merged before a normal delete works."
27
+ ],
28
+ [
29
+ "Lessons are append-only: a later insight can supersede an older one but the original text is never rewritten.",
30
+ "The store never edits a lesson's wording after the fact; a newer entry can mark an old one as replaced instead."
31
+ ],
32
+ [
33
+ "Reviewers should flag a pull request that widens scope beyond what the plan's tasks actually own.",
34
+ "When a change touches files outside what a task was granted, that overreach is exactly what review should catch."
35
+ ],
36
+ [
37
+ "The adversary review runs on Codex by default, falling back to an Opus pass when Codex can't be reached.",
38
+ "Codex normally does the adversarial check, but the workflow switches to Opus whenever Codex is unavailable."
39
+ ],
40
+ [
41
+ "Auto memory import should be idempotent, never rewriting files it did not itself create in the memory directory.",
42
+ "Re-running the import of automatic memory must not touch files outside its own directory and must produce the same result each time."
43
+ ],
44
+ [
45
+ "A swarm's lead splits one task into worker briefs and merges their results before verification starts.",
46
+ "In a swarm, the lead agent breaks a task into pieces for workers and combines their output ahead of the verify stage."
47
+ ],
48
+ [
49
+ "Keep CLAUDE.md under sixty lines since hooks already enforce most of the guardrails it would otherwise restate.",
50
+ "The instructions file should stay short, under sixty lines, because the hook layer already covers most of what it would repeat."
51
+ ]
52
+ ],
53
+ "unrelated": [
54
+ [
55
+ "The pre-commit hook re-runs the whole test gate before allowing a commit to land.",
56
+ "Ollama serves an OpenAI-compatible /v1/embeddings endpoint on port 11434 by default."
57
+ ],
58
+ [
59
+ "Worktrees under .claude/worktrees are gitignored and removed once a task merges.",
60
+ "A pull request title should stay under seventy characters so it reads well in a list view."
61
+ ],
62
+ [
63
+ "pnpm test:unit skips the live Codex test so a plan's verify step never spends real usage.",
64
+ "Vectors returned by the embedding provider are L2-normalized before they are stored in the database."
65
+ ],
66
+ [
67
+ "A worker close to its context limit should stop at a clean boundary and report a partial result.",
68
+ "The board's Ready column lists the next three small cards ahead of the larger context-window card."
69
+ ],
70
+ [
71
+ "node:sqlite needs Node 22.16 or newer for the built-in FTS5 support the lessons table relies on.",
72
+ "The reflect command proposes at most three new lessons per report, filtered by a cheap-row model pass."
73
+ ],
74
+ [
75
+ "Force-deleting a git branch is blocked by the bash guard; use a normal merge and delete instead.",
76
+ "Voyage AI's embeddings endpoint accepts an output_dimension parameter to control vector width."
77
+ ],
78
+ [
79
+ "Lessons are append-only; an older entry can be superseded but its text is never rewritten.",
80
+ "The doctor subcommand probes a configured provider or a local Ollama installation and prints what it found."
81
+ ],
82
+ [
83
+ "Review should catch a change that touches files outside what its task was granted to own.",
84
+ "A hand-track landing records the card id, commit, wall-clock time, and gate line, but has no report file."
85
+ ],
86
+ [
87
+ "The adversary review defaults to Codex and falls back to an Opus pass when Codex cannot run.",
88
+ "Recall fuses BM25 keyword scores with cosine similarity using reciprocal rank fusion when a provider is configured."
89
+ ],
90
+ [
91
+ "Importing auto memory is idempotent and never rewrites files outside its own memory directory.",
92
+ "The bash guard also blocks deleting a remote branch directly; ask first or merge it locally."
93
+ ],
94
+ [
95
+ "A swarm's lead agent splits a task into worker briefs and merges their output before verification.",
96
+ "CLAUDE.md documents only what hooks cannot already enforce, and stays under sixty lines."
97
+ ],
98
+ [
99
+ "Keep CLAUDE.md under sixty lines because the hook layer already enforces most of the guardrails.",
100
+ "A citation of the form path:line is re-checked against the checkout only when the directory contains a .doug folder."
101
+ ]
102
+ ]
103
+ }
@@ -15,7 +15,7 @@ import { unifiedDiff, writeProposals, countLines, insertBulletUnderHeading, remo
15
15
  const DECISIONS_RELDIR = "docs/decisions";
16
16
  const RULES_RELDIR = ".claude/rules";
17
17
  const CLAUDE_MD_RELPATH = "CLAUDE.md";
18
- const GATES_SCRIPTS_RELDIR = "plugins/doug-gates/scripts";
18
+ const HOOK_SCRIPTS_RELDIR = ".doug/hooks/scripts";
19
19
 
20
20
  // Scans docs/decisions/NNNN-*.md and returns the next zero-padded number ("0001" when the directory is empty
21
21
  // or missing). A file that does not match the NNNN-*.md shape (a stray README, say) is ignored rather than
@@ -185,7 +185,7 @@ function validateRemoveEntry(entry, oldLines, dir) {
185
185
  if (!nonEmptyString(entry.source)) return { ok: false, reason: "each remove entry needs a non-empty string \"source\"" };
186
186
  if (!nonEmptyString(entry.enforcedBy)) return { ok: false, reason: "each remove entry needs a non-empty string \"enforcedBy\"" };
187
187
  if (entry.enforcedBy.endsWith(".mjs")) {
188
- const relHook = `${GATES_SCRIPTS_RELDIR}/${entry.enforcedBy}`;
188
+ const relHook = `${HOOK_SCRIPTS_RELDIR}/${entry.enforcedBy}`;
189
189
  if (!existsSync(join(dir, relHook))) return { ok: false, reason: `enforcedBy names a hook that does not exist: ${relHook}` };
190
190
  }
191
191
  return { ok: true };
@@ -131,10 +131,10 @@ function l2normalize(vec) {
131
131
  // pairs and a dozen known-unrelated pairs, embedded together, must separate by a stated margin or the
132
132
  // configured embedder is not doing semantic work. Failing loudly here catches that before reembed commits to
133
133
  // it.
134
- // This path lies outside plugins/doug-flow, at the repo root — so it is outside a Doug plugin's own `files`
135
- // list. A standalone-published copy of this plugin (installed on its own, without the rest of this repo
136
- // checked out alongside it) will not have it; pass `--pairs <file>` in that case.
137
- export const DEFAULT_SANITY_PAIRS = join(dirname(fileURLToPath(import.meta.url)), "../../../evals/memory/pairs.json");
134
+ // This path lives inside plugins/doug-flow/lib, under this plugin's own `files` list in package.json, so a
135
+ // standalone-published copy of this plugin (installed on its own, without the rest of this repo checked out
136
+ // alongside it) still has it.
137
+ export const DEFAULT_SANITY_PAIRS = join(dirname(fileURLToPath(import.meta.url)), "data/pairs.json");
138
138
 
139
139
  function meanOf(nums) {
140
140
  return nums.reduce((a, b) => a + b, 0) / nums.length;
@@ -14,12 +14,69 @@ import { listOutcomes, listLessons, jaccard, reflectTokens, REFLECT_DUPLICATE_JA
14
14
 
15
15
  // --- collectSignals ----------------------------------------------------------------------------------------
16
16
 
17
+ // card op-learn-gate-shape: a hand row's gate text is free-form per project ("typecheck 0; unit 639 passed" for
18
+ // Doug itself, "pytest 0" or "ruff 0; pytest 2" for a pytest project, "npm test 1" for an npm one, ...), so
19
+ // reading it as a failure unless it starts with Doug's own literal "typecheck 0" misclassified every other
20
+ // project's green gate as a failure. readGate reads each top-level ";"/","-separated clause instead: a
21
+ // ";" or "," inside a clause's own parenthesised note (e.g. "pytest 0 (41 passed, 2 skipped)") never splits
22
+ // that clause, since the note is free text and may itself carry commas. Within a clause: a `<n> passed`
23
+ // count is neutral, a clause naming a fail/error word is a failure, and a clause ending in an exit code (an
24
+ // optional parenthesised note allowed) is a pass or a failure by that code - independent of Doug's own shape.
25
+ const PASSED_COUNT_RE = /^.*\S\s+\d+\s+passed$/i;
26
+ const FAIL_WORD_RE = /\b(fail|fails|failed|failure|failures|error|errors)\b/i;
27
+ const EXIT_CLAUSE_RE = /^(.*\S)\s+(\d+)(\s*\([^)]*\))?$/;
28
+
29
+ // Splits on a top-level ";" or "," only - one inside parentheses (depth tracked, not just "the last paren")
30
+ // stays part of its clause, so a parenthesised note's own comma (a pass/skip breakdown) never fractures the
31
+ // clause it belongs to.
32
+ function splitGateClauses(gate) {
33
+ const clauses = [];
34
+ let depth = 0;
35
+ let current = "";
36
+ for (const ch of gate) {
37
+ if (ch === "(") depth += 1;
38
+ else if (ch === ")") depth = Math.max(0, depth - 1);
39
+ if ((ch === ";" || ch === ",") && depth === 0) {
40
+ clauses.push(current);
41
+ current = "";
42
+ } else {
43
+ current += ch;
44
+ }
45
+ }
46
+ clauses.push(current);
47
+ return clauses.map((c) => c.trim()).filter((c) => c.length > 0);
48
+ }
49
+
50
+ // Pure: reads one hand-track gate string into "pass" or "fail" (see the rules in the comment above). Exported
51
+ // for both isGateFailure below and run-report/core-next's own use of the same reading.
52
+ export function readGate(gate) {
53
+ if (typeof gate !== "string" || !gate.trim()) return "fail";
54
+ const clauses = splitGateClauses(gate);
55
+ let hasFailingClause = false;
56
+ let hasPassingExitClause = false;
57
+ for (const clause of clauses) {
58
+ if (PASSED_COUNT_RE.test(clause)) continue; // a) a pass count - neutral
59
+ if (FAIL_WORD_RE.test(clause)) {
60
+ hasFailingClause = true; // b) a fail/error word - failing, checked before c)
61
+ continue;
62
+ }
63
+ const m = EXIT_CLAUSE_RE.exec(clause);
64
+ if (m) {
65
+ if (Number(m[2]) !== 0) hasFailingClause = true; // c) a non-zero exit clause - failing
66
+ else hasPassingExitClause = true; // c) a zero exit clause - passing
67
+ continue;
68
+ }
69
+ // d) anything else - neutral
70
+ }
71
+ return !hasFailingClause && hasPassingExitClause ? "pass" : "fail";
72
+ }
73
+
17
74
  // A hand row's own gate text is the only record of pass/fail on that track (no independent verifier runs on
18
75
  // it); a flow row's `verified` column is that record on the flow track. See memory.mjs's OUTCOMES_COLUMNS_SQL
19
76
  // comment for why the two tracks are never averaged together.
20
77
  function isGateFailure(row) {
21
78
  if (row.track === "flow") return row.verified === 0;
22
- if (row.track === "hand") return !(typeof row.gate === "string" && row.gate.startsWith("typecheck 0"));
79
+ if (row.track === "hand") return readGate(row.gate) === "fail";
23
80
  return false;
24
81
  }
25
82
 
@@ -6,7 +6,7 @@ import { basename, join, resolve } from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  import { createHash } from "node:crypto";
8
8
  import { projectSlug } from "./cost.mjs";
9
- import { addLesson, getLesson, listLessons, supersedeLesson } from "./memory.mjs";
9
+ import { addLesson, getLesson, listLessons, supersedeLesson, unstaleLesson } from "./memory.mjs";
10
10
 
11
11
  export function autoMemoryDir(projectDir, claudeDir = join(homedir(), ".claude")) {
12
12
  return join(claudeDir, "projects", projectSlug(projectDir), "memory");
@@ -77,10 +77,11 @@ export function parseFrontmatter(text) {
77
77
 
78
78
  export function importAutoMemory(m, opts = {}) {
79
79
  const { projectDir, claudeDir, now } = opts;
80
- void now; // accepted for interface consistency; every timestamp here is measured (data.modified or file mtime)
80
+ // `now` (defaults to the current time) is only the retirement timestamp written to a stale row; every other
81
+ // timestamp here is measured (data.modified or file mtime).
81
82
  const dir = autoMemoryDir(projectDir, claudeDir);
82
83
  if (!existsSync(dir)) {
83
- return { dir, found: false, added: 0, updated: 0, unchanged: 0, skipped: 0, files: [] };
84
+ return { dir, found: false, added: 0, updated: 0, unchanged: 0, skipped: 0, retired: 0, files: [] };
84
85
  }
85
86
  const names = readdirSync(dir, { withFileTypes: true })
86
87
  .filter((e) => e.isFile() && e.name.endsWith(".md") && e.name !== "MEMORY.md")
@@ -105,6 +106,7 @@ export function importAutoMemory(m, opts = {}) {
105
106
  const id = createHash("sha256").update(`auto-memory\n${name}\n${text}`).digest("hex").slice(0, 16);
106
107
  const existing = getLesson(m, id);
107
108
  if (existing) {
109
+ if (existing.stale) unstaleLesson(m, id);
108
110
  unchanged++;
109
111
  files.push({ file: name, id, action: "unchanged" });
110
112
  continue;
@@ -141,5 +143,21 @@ export function importAutoMemory(m, opts = {}) {
141
143
  files.push({ file: name, id, action: "added" });
142
144
  }
143
145
  }
144
- return { dir, found: true, added, updated, unchanged, skipped, files };
146
+ // Retire (mark stale) every live row this importer wrote (source_agent "auto-memory") whose file is no longer
147
+ // under the directory: a rewritten file supersedes its prior row above, but a deleted file left no trace here
148
+ // to react to. Keyed on basename, the same key the per-file loop above uses to find a prior row, so a row
149
+ // whose citation dir differs from this run's dir but whose basename is still present is not retired (card
150
+ // memory-import-retires-deleted).
151
+ const nameSet = new Set(names);
152
+ const retiredAt = (now ? new Date(now) : new Date()).toISOString();
153
+ let retired = 0;
154
+ for (const lesson of listLessons(m)) {
155
+ if (lesson.source_agent !== "auto-memory") continue;
156
+ if (lesson.stale) continue;
157
+ if (nameSet.has(basename(lesson.citation))) continue;
158
+ m.prepare("UPDATE lessons SET stale = ? WHERE id = ?").run(retiredAt, lesson.id);
159
+ retired++;
160
+ files.push({ file: basename(lesson.citation), id: lesson.id, action: "retired" });
161
+ }
162
+ return { dir, found: true, added, updated, unchanged, skipped, retired, files };
145
163
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougharness/flow",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "private": true,
5
5
  "description": "Plan, approve, implement one task per worktree, verify, review: a Workflow-tool template and subagents for Claude Code.",
6
6
  "type": "module",
@@ -128,8 +128,9 @@
128
128
  // path; keyword-only, and says so, with no provider or no vectors
129
129
  // memory.mjs embed-check [dir] [--pairs <file>] [--json]
130
130
  // cosine sanity check (card memory-measure): embeds the dozen
131
- // paraphrase and dozen unrelated lesson pairs in evals/memory/
132
- // pairs.json (or --pairs) against the configured provider and
131
+ // paraphrase and dozen unrelated lesson pairs in
132
+ // plugins/doug-flow/lib/data/pairs.json (or --pairs) against the
133
+ // configured provider and
133
134
  // confirms paraphrases score higher than unrelated pairs by the
134
135
  // file's stated margin; exit 0 ok, 1 on a failed check or an
135
136
  // unreachable/unconfigured provider, 2 on usage; refuses when
@@ -469,8 +470,8 @@ function printProposalResult(result, jsonOut) {
469
470
  }
470
471
 
471
472
  // `index build`'s own pairs loader: an explicit --pairs that is missing fails clearly (same as loadPairsOrFail,
472
- // same exit code), but with no --pairs given at all, a missing default file (a standalone-published copy of
473
- // this plugin outside this checkout - DEFAULT_SANITY_PAIRS lives at the repo root) is not a hard failure: build
473
+ // same exit code), but with no --pairs given at all, a missing default file (DEFAULT_SANITY_PAIRS lives inside
474
+ // plugins/doug-flow/lib/data, so this only happens if that file was removed) is not a hard failure: build
474
475
  // still runs, and buildIndex itself reports a check failure only if a model/dims pair actually new to the code
475
476
  // index shows up needing one.
476
477
  function loadIndexPairs(opts) {
@@ -817,7 +818,7 @@ try {
817
818
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
818
819
  } else {
819
820
  process.stdout.write(
820
- `Imported ${result.added + result.updated} lesson(s) from ${result.dir}: ${result.added} added, ${result.updated} updated (old row superseded), ${result.unchanged} unchanged, ${result.skipped} skipped (empty).\n`
821
+ `Imported ${result.added + result.updated} lesson(s) from ${result.dir}: ${result.added} added, ${result.updated} updated (old row superseded), ${result.unchanged} unchanged, ${result.skipped} skipped (empty), ${result.retired} retired (file deleted).\n`
821
822
  );
822
823
  }
823
824
  break;
@@ -75,7 +75,7 @@ Stay within the card's goal. A defect found on the way that the goal does not co
75
75
 
76
76
  With the commit sha (the merge sha for a swarmed card) and the wall clock since step 1 (or since 2b's launch, for a swarmed card):
77
77
 
78
- 1. Invoke the `run-report` skill. By hand: its hand-track-landing form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "typecheck 0; unit <n> passed"`, and `--note "<one line>"` when something about the build is worth keeping (what was harder than the goal said, what was left out and why). Swarmed: its workflow-run form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json>` (the file 2b saved, `.doug/.state/last-report.json`) `--commit <merge sha> --wall "<m> min" --cost <usd> --codex-cost <usd>` (add --codex-cost <usd> when cost.mjs printed a Codex line, next to the run total) with one `--adversary "<id>=<real|marginal|false>: <reason>"` per adversary block the report carries (run-report requires every block classified), then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --wall "<m> min" --commit <merge sha>` and put its three lines in the chat verbatim. Batch: the `run-report` skill's batch form — `cost.mjs <run-id> --by-card` for each card's cost with the shared integration agents counted once under the first, then one `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json> --commit <merge sha> --wall "<m> min" --cost <that card's usd> --shared-cost <shared usd> --adversary ...` per card in the plan's `cards` order, then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --card <id> --wall "<m> min" --commit <merge sha>` per card with each card's three lines in the chat verbatim.
78
+ 1. Invoke the `run-report` skill. By hand: its hand-track-landing form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>"` (the gate line is run-report's own rule: one `<command> <exit>` clause per entry of `.doug/config.json` `stopGate.commands`, joined by `; `; Doug's own is `typecheck 0; test:unit 0 (<n> passed)`), and `--note "<one line>"` when something about the build is worth keeping (what was harder than the goal said, what was left out and why). Swarmed: its workflow-run form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json>` (the file 2b saved, `.doug/.state/last-report.json`) `--commit <merge sha> --wall "<m> min" --cost <usd> --codex-cost <usd>` (add --codex-cost <usd> when cost.mjs printed a Codex line, next to the run total) with one `--adversary "<id>=<real|marginal|false>: <reason>"` per adversary block the report carries (run-report requires every block classified), then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --wall "<m> min" --commit <merge sha>` and put its three lines in the chat verbatim. Batch: the `run-report` skill's batch form — `cost.mjs <run-id> --by-card` for each card's cost with the shared integration agents counted once under the first, then one `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json> --commit <merge sha> --wall "<m> min" --cost <that card's usd> --shared-cost <shared usd> --adversary ...` per card in the plan's `cards` order, then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --card <id> --wall "<m> min" --commit <merge sha>` per card with each card's three lines in the chat verbatim.
79
79
  2. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> done --source "commit <sha>"`. Batch: once per card of the batch, same merge sha.
80
80
  3. `git add <record> docs/live-runs.md && git commit -m "Board: <id> done as <sha>"` (swarmed: add `.doug/plan.json` too); when step 1's `run-report` invocation printed that it promoted a research note, add `docs/research/<id>.md` too. No trailers. Batch: one commit `"Board: <id>, <id> done as <sha>"` including `.doug/plan.json`.
81
81
 
@@ -36,11 +36,11 @@ When `.doug/plan.json` carries `cards` (made by `plan.mjs merge`), the run is re
36
36
 
37
37
  ## A hand-track landing
38
38
 
39
- With the card id, the landing commit, the wall clock, and the gate line (`typecheck 0; unit <n> passed`):
39
+ With the card id, the landing commit, the wall clock, and the gate line — one `<command> <exit>` clause per entry of `.doug/config.json` `stopGate.commands`, in that order, joined by `; ` (a test command may add its pass count in parentheses; Doug's own is `typecheck 0; test:unit 0 (<n> passed)`; a pytest project's might be `pytest 0`):
40
40
 
41
- 1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "typecheck 0; unit <n> passed"`, with `--note "<one line>"` when something about the build is worth keeping. This appends the hand-track entry to `docs/live-runs.md`; its table is the summary. It also promotes `.doug/.state/research/<id>.md` to `docs/research/<id>.md` when that note exists, printing one line saying so; add that path to the landing commit alongside `docs/live-runs.md`.
41
+ 1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>"`, with `--note "<one line>"` when something about the build is worth keeping. This appends the hand-track entry to `docs/live-runs.md`; its table is the summary. It also promotes `.doug/.state/research/<id>.md` to `docs/research/<id>.md` when that note exists, printing one line saying so; add that path to the landing commit alongside `docs/live-runs.md`.
42
42
  2. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" import`, the same as above: its one-line output goes in the chat, an exit 1 naming Node is said in the chat and the landing goes on, and it is idempotent, supersedes a rewritten file, and never writes under the auto-memory directory.
43
- 3. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "typecheck 0; unit <n> passed" [--note "<one line>"]`, the same values step 1 used. It records one row in the local outcome log `.doug/.state/memory/memory.db` (uncommitted), keyed `hand:<sha>` so re-recording the same commit updates that row and track `hand`; a card that lands twice, in two commits, gets two rows. Its workflow-only columns (verified, review counts, adversary verdict, blocks, fix passes, budget, tokens, USD, the report itself) are null because they do not exist on this track, never because they measured zero: no independent verifier ran, and the gate's own text is the `gate` column instead. It needs Node >=22.16 (node:sqlite). When it exits 1 naming Node, say so in the chat and go on, since the record is a log, not a gate.
43
+ 3. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>" [--note "<one line>"]`, the same values step 1 used. It records one row in the local outcome log `.doug/.state/memory/memory.db` (uncommitted), keyed `hand:<sha>` so re-recording the same commit updates that row and track `hand`; a card that lands twice, in two commits, gets two rows. Its workflow-only columns (verified, review counts, adversary verdict, blocks, fix passes, budget, tokens, USD, the report itself) are null because they do not exist on this track, never because they measured zero: no independent verifier ran, and the gate's own text is the `gate` column instead. It needs Node >=22.16 (node:sqlite). When it exits 1 naming Node, say so in the chat and go on, since the record is a log, not a gate.
44
44
 
45
45
  `memory.mjs reflect` does not run on a hand-track landing: there is no report for the cheap-row pass to read.
46
46
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "doug-gates",
3
3
  "displayName": "Doug Gates",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "description": "Deterministic guardrails for Claude Code: protected paths, bash guard, secret scan on edits and commands, format on edit, edit-loop detection, a verification gate on Stop and on SubagentStop, a snapshot of the plan and session into the anchor before compaction and re-anchoring after it, and a run trace of subagents and tool calls. Configured by .doug/config.json.",
6
6
  "author": {
7
7
  "name": "Doug contributors"
@@ -93,6 +93,11 @@ export function emptyState() {
93
93
  // the Decide card stop-gate-honours-tester-claim's to add. Absent from an old state file loads as null
94
94
  // via the emptyState spread above.
95
95
  redByDesignClaim: null,
96
+ // Test totals per verification command, { [command]: { total, skipped, todo } } (card test-count-ratchet):
97
+ // copied from the last-green record (.doug/.state/test-totals.json) by session-start-baseline.mjs in the same
98
+ // write as `baseline`, never overwritten once non-null. The Stop gate compares against this, never against
99
+ // the file, so a session cannot lower its own bar by going green. null = no baseline, no count check.
100
+ testTotals: null,
96
101
  // context (card context-window-handoff): { pct, at, notifiedAt? } — absent until the status line first
97
102
  // records a reading (statusline.mjs), so not a default key here. pct and at are written by the status line
98
103
  // on every rounded-pct change; notifiedAt is written by the Stop gate (stop-gate.mjs) when it notices, and
@@ -0,0 +1,106 @@
1
+ // Test-count ratchet (card test-count-ratchet): pure logic for parsing a vitest summary, comparing it with a
2
+ // baseline, and matching a waiver. The Stop gate calls it; nothing here touches the filesystem or throws.
3
+ //
4
+ // Known limits, stated on purpose:
5
+ // - Counts cannot see delete-one-add-one: a session that removes a test and adds an unrelated one keeps
6
+ // every number level and passes.
7
+ // - The verified-unchanged skip runs no test command, so there is no output to count there; no count check
8
+ // happens on it. (The pre-commit credit path is counted from the totals the hook records in last-run.json.)
9
+ // - A runner that prints one summary per package (`pnpm -r`) reads as conflicting summaries and blocks.
10
+ // - Runners other than vitest print no `Tests` summary in this shape: no summary is parsed, no check runs.
11
+
12
+ const ANSI_RE = /\u001b\[[0-9;]*m/g;
13
+
14
+ export function stripAnsi(text) {
15
+ return String(text).replace(ANSI_RE, "");
16
+ }
17
+
18
+ const SEGMENT_RE = /^(\d+) (failed|passed|skipped|todo)$/;
19
+ const TESTS_LINE_RE = /^\s*Tests\s+(.+?)\s*$/;
20
+ const TOTAL_RE = /^(.*) \((\d+)\)$/;
21
+
22
+ // One line's value part ("1 failed | 2 passed | 3 skipped (6)") -> { total, skipped, todo } or null.
23
+ function parseSummaryValue(value) {
24
+ const m = TOTAL_RE.exec(value);
25
+ if (!m) return null;
26
+ const counts = { skipped: 0, todo: 0 };
27
+ for (const seg of m[1].split(" | ")) {
28
+ const s = SEGMENT_RE.exec(seg.trim());
29
+ if (!s) return null;
30
+ if (s[2] === "skipped" || s[2] === "todo") counts[s[2]] = Number(s[1]);
31
+ }
32
+ return { total: Number(m[2]), skipped: counts.skipped, todo: counts.todo };
33
+ }
34
+
35
+ // Every `Tests` summary line is collected (never "the last one": a test can print a byte-identical line).
36
+ // -> { status: "ok", totals } | { status: "none" } | { status: "conflict", summaries }
37
+ export function parseTotals(output) {
38
+ try {
39
+ if (typeof output !== "string") return { status: "none" };
40
+ const summaries = [];
41
+ for (const line of stripAnsi(output).split(/\r?\n/)) {
42
+ const m = TESTS_LINE_RE.exec(line);
43
+ if (!m) continue;
44
+ const parsed = parseSummaryValue(m[1]);
45
+ if (parsed) summaries.push(parsed);
46
+ }
47
+ if (summaries.length === 0) return { status: "none" };
48
+ const first = summaries[0];
49
+ const same = (s) => s.total === first.total && s.skipped === first.skipped && s.todo === first.todo;
50
+ if (summaries.every(same)) return { status: "ok", totals: { ...first } };
51
+ return { status: "conflict", summaries };
52
+ } catch {
53
+ return { status: "none" };
54
+ }
55
+ }
56
+
57
+ const isCount = (n) => Number.isInteger(n) && n >= 0;
58
+
59
+ // A baseline entry usable for comparison, or null.
60
+ export function validTotals(t) {
61
+ if (!t || typeof t !== "object") return null;
62
+ if (!isCount(t.total) || !isCount(t.skipped) || !isCount(t.todo)) return null;
63
+ return { total: t.total, skipped: t.skipped, todo: t.todo };
64
+ }
65
+
66
+ // Total dropped, or skipped/todo rose. A rise in total or a drop in skipped/todo is never a problem.
67
+ export function compareTotals(baseline, now) {
68
+ const out = [];
69
+ if (now.total < baseline.total) out.push({ kind: "total", baseline: baseline.total, now: now.total });
70
+ if (now.skipped > baseline.skipped) out.push({ kind: "skipped", baseline: baseline.skipped, now: now.skipped });
71
+ if (now.todo > baseline.todo) out.push({ kind: "todo", baseline: baseline.todo, now: now.todo });
72
+ return out;
73
+ }
74
+
75
+ export function deltaTotals(baseline, now) {
76
+ return { total: now.total - baseline.total, skipped: now.skipped - baseline.skipped, todo: now.todo - baseline.todo };
77
+ }
78
+
79
+ const WAIVER_RE = /^test-count-waiver: total=([+-]?\d+) skipped=([+-]?\d+) todo=([+-]?\d+);(.*)$/;
80
+
81
+ // One line -> { total, skipped, todo, reason } or null. The reason must be non-empty after trimming.
82
+ export function parseWaiver(line) {
83
+ if (typeof line !== "string") return null;
84
+ const m = WAIVER_RE.exec(line.trim());
85
+ if (!m) return null;
86
+ const reason = m[4].trim();
87
+ if (!reason) return null;
88
+ return { total: Number(m[1]) + 0, skipped: Number(m[2]) + 0, todo: Number(m[3]) + 0, reason };
89
+ }
90
+
91
+ // sources: strings, each possibly several lines. Covers only when all three deltas equal the actual ones.
92
+ export function waiverCovers(sources, delta) {
93
+ try {
94
+ if (!Array.isArray(sources)) return false;
95
+ for (const src of sources) {
96
+ if (typeof src !== "string") continue;
97
+ for (const line of src.split(/\r?\n/)) {
98
+ const w = parseWaiver(line);
99
+ if (w && w.total === delta.total && w.skipped === delta.skipped && w.todo === delta.todo) return true;
100
+ }
101
+ }
102
+ } catch {
103
+ // a broken source never covers
104
+ }
105
+ return false;
106
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougharness/gates",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "private": true,
5
5
  "description": "Deterministic Claude Code hooks: protected paths, bash guard, secret scan on edits and commands, format on edit, edit-loop detection, stop gate, re-anchor after compaction.",
6
6
  "type": "module",
@@ -22,8 +22,19 @@
22
22
  import { runHook, allow, sessionStartReason } from "../lib/io.mjs";
23
23
  import { projectDir, loadConfig } from "../lib/config.mjs";
24
24
  import { loadState, saveState } from "../lib/state.mjs";
25
+ import { readFileSync } from "node:fs";
26
+ import { join } from "node:path";
25
27
  import { changedFiles, captureBaseline, headAt, walkProposalPathFiles } from "../lib/baseline.mjs";
26
28
 
29
+ function readLastGreen(dir) {
30
+ try {
31
+ const parsed = JSON.parse(readFileSync(join(dir, ".doug/.state/test-totals.json"), "utf8"));
32
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
27
38
  runHook("session-start-baseline", async (input) => {
28
39
  const dir = projectDir(input);
29
40
  const state = loadState(dir, input.session_id);
@@ -52,6 +63,9 @@ runHook("session-start-baseline", async (input) => {
52
63
  // This capture always performs the proposalPaths walk above, so the baseline it just wrote can be trusted
53
64
  // for the stop gate's walk-based check (state.baselineWalked; see its definition in lib/state.mjs).
54
65
  fresh.baselineWalked = true;
66
+ // Card test-count-ratchet: copy the last-green totals record into state, same write as the baseline, never
67
+ // overwriting a non-null value. Missing or unreadable: stays null (no count check). Never throws.
68
+ if (fresh.testTotals === null) fresh.testTotals = readLastGreen(dir);
55
69
  saveState(dir, input.session_id, fresh);
56
70
  return allow();
57
71
  });
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // Claude Code status line: prints one line like `Doug 0.1.0 · Opus · main · ctx 8%`.
2
+ // Claude Code status line: prints one line like `Doug <version> · Opus · main · ctx 8%`.
3
3
  // Reads the status-line JSON Claude Code passes on stdin. Never prints an error, always exits 0;
4
4
  // each segment degrades on its own, and any thrown error falls back to the bare line `Doug`.
5
5
 
6
6
  import { readFileSync } from "node:fs";
7
- import { join } from "node:path";
7
+ import { dirname, join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
8
9
  import { spawnSync } from "node:child_process";
9
10
  import { DEFAULTS } from "../lib/config.mjs";
10
11
  import { loadState, saveState } from "../lib/state.mjs";
@@ -32,19 +33,44 @@ function nonEmpty(v) {
32
33
  return typeof v === "string" && v.trim() !== "";
33
34
  }
34
35
 
36
+ // Resolves the version to print, in order: the vendored VERSION record next to this script, this
37
+ // package's own package.json (only when it is @dougharness/gates, not a project's unrelated one),
38
+ // then the project's .doug/config.json doug.version fallback. Each step gets its own try/catch and
39
+ // only a non-empty trimmed value counts; the caller falls back to the bare name when this returns "".
40
+ function resolveVersion(here, projectDir) {
41
+ try {
42
+ const v = readFileSync(join(here, "..", "VERSION"), "utf8").trim();
43
+ if (v) return v;
44
+ } catch {
45
+ // no vendored VERSION file
46
+ }
47
+ try {
48
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
49
+ if (pkg && pkg.name === "@dougharness/gates" && nonEmpty(pkg.version)) return pkg.version.trim();
50
+ } catch {
51
+ // no package.json here, or it isn't @dougharness/gates
52
+ }
53
+ try {
54
+ const cfg = JSON.parse(readFileSync(join(projectDir, ".doug", "config.json"), "utf8"));
55
+ const doug = cfg && typeof cfg === "object" ? cfg.doug : null;
56
+ if (doug && typeof doug === "object" && nonEmpty(doug.version)) return doug.version.trim();
57
+ } catch {
58
+ // missing or unreadable config
59
+ }
60
+ return "";
61
+ }
62
+
35
63
  function productSegment(projectDir) {
36
64
  let name = "Doug";
37
- let version = "";
38
65
  try {
39
66
  const cfg = JSON.parse(readFileSync(join(projectDir, ".doug", "config.json"), "utf8"));
40
67
  const doug = cfg && typeof cfg === "object" ? cfg.doug : null;
41
- if (doug && typeof doug === "object") {
42
- if (nonEmpty(doug.name)) name = doug.name.trim();
43
- if (nonEmpty(doug.version)) version = doug.version.trim();
44
- }
68
+ if (doug && typeof doug === "object" && nonEmpty(doug.name)) name = doug.name.trim();
45
69
  } catch {
46
70
  // missing or unreadable config: bare product name
47
71
  }
72
+ const here = dirname(fileURLToPath(import.meta.url));
73
+ const version = resolveVersion(here, projectDir);
48
74
  return version ? `${name} ${version}` : name;
49
75
  }
50
76
 
@@ -67,6 +67,7 @@
67
67
  // unreadable, there is no evidence either way, so the gate runs as today.
68
68
 
69
69
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync, unlinkSync, openSync, closeSync, readSync } from "node:fs";
70
+ import { execFileSync } from "node:child_process";
70
71
  import { join } from "node:path";
71
72
  import { runHook, allow, blockStop, systemMessage } from "../lib/io.mjs";
72
73
  import { loadConfig, projectDir } from "../lib/config.mjs";
@@ -81,6 +82,7 @@ import { tracePath, readTrace, traceLine, appendTrace, agentLabel } from "../lib
81
82
  import { isAppliedContent } from "../lib/proposals.mjs";
82
83
  import { gitHead, mergeSnapshot, readDecisions, readPlanSummary, snapshotSection } from "../lib/anchor.mjs";
83
84
  import { contextReading, recordAgentContext } from "../lib/agent-context.mjs";
85
+ import { parseTotals, validTotals, compareTotals, deltaTotals, waiverCovers } from "../lib/test-totals.mjs";
84
86
  import { failingTestFilesFrom, classifyFinalBlock, appendBlockLine } from "../lib/block-ledger.mjs";
85
87
 
86
88
  // Only the last this many characters of last_assistant_message are ever scanned for a partial claim
@@ -517,6 +519,100 @@ function contextNotice(dir, cfg, state, sessionId, boundary) {
517
519
  }
518
520
  }
519
521
 
522
+ // ---- Test-count ratchet (card test-count-ratchet; the pure logic and its known limits are in lib/test-totals.mjs).
523
+ // Every reader below degrades to "no check" or "no waiver"; none may throw (runHook would fail open).
524
+ const LAST_GREEN_RELPATH = ".doug/.state/test-totals.json";
525
+ const WAIVER_FILE_RELPATH = ".doug/.state/test-count-waivers";
526
+
527
+ // Waiver sources: commit messages since the session's baseline HEAD, and the waiver file's text.
528
+ function waiverSources(dir, baselineHead) {
529
+ const sources = [];
530
+ try {
531
+ if (typeof baselineHead === "string" && /^[0-9a-f]{7,64}$/.test(baselineHead)) {
532
+ const out = execFileSync("git", ["log", "--format=%B%x00", `${baselineHead}..HEAD`], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
533
+ sources.push(...out.split("\0"));
534
+ }
535
+ } catch {
536
+ // no git, or a base git no longer knows: no commit waivers
537
+ }
538
+ try {
539
+ sources.push(readFileSync(join(dir, WAIVER_FILE_RELPATH), "utf8"));
540
+ } catch {
541
+ // no waiver file, or unreadable: no file waivers
542
+ }
543
+ return sources;
544
+ }
545
+
546
+ // The count check for the commands that ran green by exit code. Returns { problems, parsed }: `problems` are
547
+ // block reasons, `parsed` the per-command totals of every run with a clean single summary (for the last-green
548
+ // record). A missing or unparseable summary, or no baseline, is a trace line and no check.
549
+ function countCheck(dir, cfg, state, input, ran) {
550
+ const problems = [];
551
+ const parsed = {};
552
+ // The note carries its own event name, never the stop event's: a decision-less SubagentStop line would read to
553
+ // subagentsInFlight (more undecided stops than blocks) as "the agent is gone" (review B1). No reader keys on
554
+ // "CountCheck"; it only adds one to attribution's per-agent event count. Honours trace.enabled false (review m2).
555
+ const note = (reason) => {
556
+ try {
557
+ if (!cfg || !cfg.trace || cfg.trace.enabled === false) return;
558
+ appendTrace(dir, { ...traceLine(input, { now: new Date() }), event: "CountCheck", reason });
559
+ } catch {
560
+ // logging must never fail the gate
561
+ }
562
+ };
563
+ // Each entry is `{ command, stdout }` (a real run: parsed here) or `{ command, result }` (a credited pre-commit
564
+ // run: the parseTotals-shaped result the hook recorded). Both take the identical path below.
565
+ for (const { command, stdout, result } of ran) {
566
+ try {
567
+ const p = result !== undefined ? result : parseTotals(stdout);
568
+ if (p.status === "conflict") {
569
+ problems.push(`\`${command}\` printed conflicting test summaries (${p.summaries.map((x) => `${x.total} total, ${x.skipped} skipped, ${x.todo} todo`).join(" vs ")}); the test count cannot be trusted. A waiver does not cover this.`);
570
+ continue;
571
+ }
572
+ if (p.status !== "ok") {
573
+ note(`count check did not run for \`${command}\`: no test summary in its output`);
574
+ continue;
575
+ }
576
+ parsed[command] = p.totals;
577
+ const all = state.testTotals && typeof state.testTotals === "object" ? state.testTotals : null;
578
+ const base = all ? validTotals(all[command]) : null;
579
+ if (!base) {
580
+ note(`count check did not run for \`${command}\`: no baseline totals for it`);
581
+ continue;
582
+ }
583
+ const found = compareTotals(base, p.totals);
584
+ if (found.length === 0) continue;
585
+ const delta = deltaTotals(base, p.totals);
586
+ if (waiverCovers(waiverSources(dir, state.baselineHead), delta)) continue;
587
+ const lines = found.map((f) => `${f.kind} ${f.kind === "total" ? "dropped" : "rose"} from ${f.baseline} (baseline) to ${f.now} (now)`);
588
+ problems.push(
589
+ `\`${command}\` test count regressed: ${lines.join("; ")}. Deleting or skipping tests does not make a change green. Restore them, or waive it with a line \`test-count-waiver: total=${delta.total} skipped=${delta.skipped} todo=${delta.todo}; <reason>\` in the commit message or ${WAIVER_FILE_RELPATH}.`,
590
+ );
591
+ } catch {
592
+ note(`count check did not run for \`${command}\`: error while checking`);
593
+ }
594
+ }
595
+ return { problems, parsed };
596
+ }
597
+
598
+ // A green run's totals become the last-green record the next session starts from. Merged per command.
599
+ function writeLastGreen(dir, parsed) {
600
+ try {
601
+ if (Object.keys(parsed).length === 0) return;
602
+ let record = {};
603
+ try {
604
+ const old = JSON.parse(readFileSync(join(dir, LAST_GREEN_RELPATH), "utf8"));
605
+ if (old && typeof old === "object" && !Array.isArray(old)) record = old;
606
+ } catch {
607
+ // absent or unreadable: start a fresh record
608
+ }
609
+ mkdirSync(join(dir, ".doug/.state"), { recursive: true });
610
+ writeFileSync(join(dir, LAST_GREEN_RELPATH), JSON.stringify({ ...record, ...parsed }));
611
+ } catch {
612
+ // never fail the gate over the record
613
+ }
614
+ }
615
+
520
616
  function resolveCommands(cfg) {
521
617
  const out = [];
522
618
  for (const entry of cfg.stopGate.commands || []) {
@@ -527,6 +623,89 @@ function resolveCommands(cfg) {
527
623
  return out;
528
624
  }
529
625
 
626
+ // card stop-gate-credits-pre-commit: `git rev-parse HEAD^{tree}`, the tree sha of the current commit — what
627
+ // the pre-commit hook's own record (.doug/.state/pre-commit/last-run.json) compares its `tree` field against.
628
+ // Null on any failure (no git, no HEAD yet, ...): a reader must never throw.
629
+ function headTreeSha(dir) {
630
+ try {
631
+ const out = execFileSync("git", ["rev-parse", "HEAD^{tree}"], { cwd: dir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000 });
632
+ const sha = out.trim();
633
+ return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
634
+ } catch {
635
+ return null;
636
+ }
637
+ }
638
+
639
+ // card ratchet-pre-commit-credit: a recorded parseTotals result, normalised, or null when it is not readable.
640
+ function readableRecordedTotals(t) {
641
+ try {
642
+ if (!t || typeof t !== "object") return null;
643
+ if (t.status === "ok") {
644
+ const totals = validTotals(t.totals);
645
+ return totals ? { status: "ok", totals } : null;
646
+ }
647
+ if (t.status === "conflict") {
648
+ if (!Array.isArray(t.summaries) || t.summaries.length === 0) return null;
649
+ const summaries = t.summaries.map(validTotals);
650
+ return summaries.every((x) => x) ? { status: "conflict", summaries } : null;
651
+ }
652
+ return t.status === "none" ? { status: "none" } : null;
653
+ } catch {
654
+ return null;
655
+ }
656
+ }
657
+
658
+ // card stop-gate-credits-pre-commit: the pre-commit hook (.githooks/pre-commit) writes a machine-readable
659
+ // record of its own run to .doug/.state/pre-commit/last-run.json (version, tree, ok, at, commands: [{ name,
660
+ // command, exit, durationMs, totals? }], totals being the hook's parseTotals result for that command; a command with a baseline in state.testTotals is credited only if its totals are readable ok or conflict, else the commands run). When the working tree has no changes beyond HEAD (nothing left in `changed`
661
+ // once stopGate.ignoreChangedPaths paths are removed) and that record is a passing run, against HEAD's own
662
+ // tree, of exactly today's configured commands in order, the Stop gate credits it instead of re-running the
663
+ // same commands a second time — returning `{ name, command, ok: true, credited: "pre-commit" }` per command,
664
+ // same length and order as resolveCommands(cfg). Returns null (run as today) on a missing, unparsable,
665
+ // wrong-shape, failing, stale-tree, or mismatched-commands record, or a dirty tree — fail closed to running,
666
+ // never to passing. A reader must never throw: every path here is wrapped, so a corrupt record never crashes
667
+ // the hook (`runHook` fails open on a throw, which here would mean passing without running — forbidden).
668
+ function preCommitCredit(dir, cfg, changed, state) {
669
+ try {
670
+ if (!Array.isArray(changed)) return null;
671
+ const ignore = cfg.stopGate.ignoreChangedPaths || [];
672
+ const remaining = changed.filter((f) => !matchAny(ignore, f));
673
+ if (remaining.length > 0) return null;
674
+ const raw = readFileSync(join(dir, ".doug/.state/pre-commit/last-run.json"), "utf8");
675
+ const record = JSON.parse(raw);
676
+ if (!record || typeof record !== "object") return null;
677
+ if (record.version !== 1) return null;
678
+ if (record.ok !== true) return null;
679
+ if (typeof record.tree !== "string") return null;
680
+ if (!Array.isArray(record.commands)) return null;
681
+ const head = headTreeSha(dir);
682
+ if (!head || record.tree !== head) return null;
683
+ for (const c of record.commands) {
684
+ if (!c || typeof c !== "object" || c.exit !== 0) return null;
685
+ }
686
+ const configured = resolveCommands(cfg);
687
+ if (record.commands.length !== configured.length) return null;
688
+ for (let i = 0; i < configured.length; i++) {
689
+ if (!record.commands[i] || record.commands[i].command !== configured[i].command) return null;
690
+ }
691
+ // card ratchet-pre-commit-credit: a command with a baseline in state.testTotals is credited only when the
692
+ // record carries readable totals for it (ok with valid counts, or a conflict with valid summaries);
693
+ // otherwise null and the commands really run. `recorded` is the parseTotals-shaped result for the count check.
694
+ const all = state && state.testTotals && typeof state.testTotals === "object" ? state.testTotals : null;
695
+ const out = [];
696
+ for (let i = 0; i < configured.length; i++) {
697
+ const { name, command } = configured[i];
698
+ const recorded = readableRecordedTotals(record.commands[i].totals);
699
+ const hasBaseline = all ? validTotals(all[command]) !== null : false;
700
+ if (hasBaseline && (!recorded || recorded.status === "none")) return null;
701
+ out.push({ name, command, ok: true, credited: "pre-commit", ...(recorded ? { recorded } : {}) });
702
+ }
703
+ return out;
704
+ } catch {
705
+ return null;
706
+ }
707
+ }
708
+
530
709
  // The verified-tree fingerprint (card stop-gate-process-storm, goal 2): the HEAD sha plus the content
531
710
  // identity of every dirty path and every proposal-path file on disk, at the moment it is computed. Null
532
711
  // when `changed` is not the array shape changedFiles returns (no git, or git unavailable) — there is
@@ -1081,6 +1260,69 @@ runHook("stop-gate", async (input) => {
1081
1260
  // true for a SubagentStop; the main Stop always runs the full gate.
1082
1261
  const skipVerification = subagent ? agentMadeNoChanges(dir, cfg, input.session_id, subagent) : false;
1083
1262
 
1263
+ // card stop-gate-no-change-restop: a stop in the same scope on a tree unchanged since a red block whose
1264
+ // commands all really ran (state.lastRed, recorded at the final block below) repeats that block without
1265
+ // running the commands or touching gate.lock, counted against maxBlocks like any final block, logged with the
1266
+ // recorded block's class and carrying the deferral cap note like blockStopReleasing. One reuse per record: the
1267
+ // record is cleared by the reuse, so the next stop runs the commands (a new red records afresh). Anything
1268
+ // unreadable, different, or not plainly a command-failure red degrades to running (fail closed to running).
1269
+ if (!skipVerification) {
1270
+ let lastRed = null;
1271
+ try {
1272
+ const rec = state.lastRed;
1273
+ const scopeNow = subagent ? subagent.id : "main";
1274
+ const commandsNow = resolveCommands(cfg).map((c) => c.command);
1275
+ if (
1276
+ rec &&
1277
+ typeof rec === "object" &&
1278
+ !Array.isArray(rec) &&
1279
+ typeof rec.scope === "string" &&
1280
+ typeof rec.summary === "string" &&
1281
+ Array.isArray(rec.commands) &&
1282
+ rec.commands.every((c) => typeof c === "string") &&
1283
+ rec.scope === scopeNow &&
1284
+ rec.commands.length === commandsNow.length &&
1285
+ rec.commands.every((c, i) => c === commandsNow[i]) &&
1286
+ rec.fingerprint &&
1287
+ typeof rec.fingerprint === "object" &&
1288
+ currentFingerprint &&
1289
+ sameTree(rec.fingerprint, currentFingerprint) &&
1290
+ !(subagent && (redByDesignClaim(input.last_assistant_message) ?? redByDesignClaim(lastHandbackMessage(input.agent_transcript_path)))) &&
1291
+ !preCommitCredit(dir, cfg, changed, state)
1292
+ ) {
1293
+ lastRed = rec;
1294
+ }
1295
+ } catch {
1296
+ lastRed = null;
1297
+ }
1298
+ if (lastRed) {
1299
+ if (blocksSoFar >= maxBlocks) {
1300
+ const alreadyStoodDown = markStoodDown("block-cap");
1301
+ saveState(dir, input.session_id, state);
1302
+ const reason = who
1303
+ ? `[doug] ${who} was blocked ${blocksSoFar} times and the gate is standing down for it. Verification is still failing; do not treat its work as done.`
1304
+ : `[doug] Stop gate blocked ${state.stopBlocks} times this session and is standing down. Verification is still failing; do not treat this work as done.`;
1305
+ if (!alreadyStoodDown) logBlock(reason, "stood-down");
1306
+ return systemMessageDecided(reason);
1307
+ }
1308
+ const { problems: treeProblems } = scanTreeProblems();
1309
+ const blocks = blocksSoFar + 1;
1310
+ if (subagent) state.agentBlocks = { ...(state.agentBlocks || {}), [subagent.id]: blocks };
1311
+ else state.stopBlocks = blocks;
1312
+ state.lastRed = null; // one reuse per record: the next stop on this tree really runs
1313
+ saveState(dir, input.session_id, state);
1314
+ const reason =
1315
+ (who ? `[doug] ${who} cannot report done: verification failed (${blocks}/${maxBlocks}). Fix the following before finishing the task. ` : `[doug] Verification failed (${blocks}/${maxBlocks}). Fix the following before finishing. `) +
1316
+ `Do not claim the task is done while this fails.\n\n` +
1317
+ `Nothing changed since the last block, so the commands were not re-run. The last run's failure:\n\n` +
1318
+ [...treeProblems, lastRed.summary].join("\n\n");
1319
+ const reuseClass = typeof lastRed.class === "string" ? lastRed.class : "real";
1320
+ const capped = deferralCapNote && !subagent ? `${deferralCapNote}\n\n${reason}` : reason;
1321
+ logBlock(reason, reuseClass);
1322
+ return blockStopDecided(capped);
1323
+ }
1324
+ }
1325
+
1084
1326
  // card stop-gate-process-storm, case 3: only one gate runs the verification commands at a time per
1085
1327
  // project (the 2026-09-16 process storm: four concurrent SubagentStops each ran the full suite, each
1086
1328
  // 300s timeout orphaning ten vitest workers). A gate holds .doug/.state/gate.lock while its commands run;
@@ -1159,6 +1401,7 @@ runHook("stop-gate", async (input) => {
1159
1401
  const blocks = blocksSoFar + 1;
1160
1402
  if (subagent) state.agentBlocks = { ...(state.agentBlocks || {}), [subagent.id]: blocks };
1161
1403
  else state.stopBlocks = blocks;
1404
+ state.lastRed = null; // card stop-gate-no-change-restop: a lock refusal is load-caused, never reused
1162
1405
  saveState(dir, input.session_id, state);
1163
1406
  const reason =
1164
1407
  (who ? `[doug] ${who} cannot report done` : `[doug] Stop gate`) +
@@ -1257,7 +1500,17 @@ runHook("stop-gate", async (input) => {
1257
1500
  // One entry per failed command, `{ command, output }` (stderr + stdout, untruncated), for the
1258
1501
  // tests_red_by_design coverage check below (design point 2, card subagent-stop-gate-tester-red).
1259
1502
  const commandFailures = [];
1260
- if (!skipVerification) {
1503
+ const ranGreen = []; // count check input: { command, stdout } of each command that really ran and exited 0, plus { command, result } of each credited pre-commit command whose recorded totals are readable
1504
+ // card stop-gate-credits-pre-commit: a passing pre-commit record for this exact tree and command set
1505
+ // stands in for actually running them — see preCommitCredit above. Every other check below (evidence,
1506
+ // red-by-design, state.verified, the block ledger) runs exactly as it would after a real run.
1507
+ const credited = skipVerification ? null : preCommitCredit(dir, cfg, changed, state);
1508
+ if (!skipVerification && credited) {
1509
+ for (const { recorded, ...r } of credited) {
1510
+ results.push(r);
1511
+ if (recorded) ranGreen.push({ command: r.command, result: recorded });
1512
+ }
1513
+ } else if (!skipVerification) {
1261
1514
  for (const { name, command } of resolveCommands(cfg)) {
1262
1515
  // Card stop-gate-budget-under-hook-timeout: a command that cannot even start before the platform
1263
1516
  // would kill this hook's own process is dropped rather than let the platform cut it off mid-run,
@@ -1278,7 +1531,8 @@ runHook("stop-gate", async (input) => {
1278
1531
  // remaining budget outright. The timed-out message below reports this same value.
1279
1532
  const commandTimeoutMs = Math.min(normalizedTimeoutMs(cfg) ?? remaining, remaining);
1280
1533
  const r = runCommand(command, { cwd: dir, timeoutMs: commandTimeoutMs });
1281
- results.push({ name, command, ok: r.ok, durationMs: r.durationMs, timedOut: r.timedOut });
1534
+ results.push({ name, command, ok: r.ok, durationMs: r.durationMs, timedOut: r.timedOut, status: r.status });
1535
+ if (r.ok) ranGreen.push({ command, stdout: r.stdout });
1282
1536
  if (!r.ok) {
1283
1537
  const why = r.timedOut ? `timed out after ${commandTimeoutMs} ms` : `exit ${r.status}`;
1284
1538
  const output = r.stderr + "\n" + r.stdout;
@@ -1305,12 +1559,19 @@ runHook("stop-gate", async (input) => {
1305
1559
  }
1306
1560
  const evidenceProblemAdded = problems.length > postCommandProblems;
1307
1561
 
1562
+ // 3c. Test-count ratchet (card test-count-ratchet): its own kind of problem, not a failed command.
1563
+ const counted = countCheck(dir, cfg, state, input, ranGreen);
1564
+ problems.push(...counted.problems);
1565
+ const countProblemAdded = counted.problems.length > 0;
1566
+
1308
1567
  state.lastGate = { ok: problems.length === 0, at: Date.now(), results, scope, evidence, ...(subagent ? { subagent } : {}), ...(skipVerification ? { skipped: "no-agent-changes" } : {}) };
1309
1568
  // card stop-gate-process-storm, case 2: a green gate records the tree it just verified, so a later
1310
1569
  // Stop/SubagentStop whose tree still matches can skip re-running these commands (the sameTree check
1311
1570
  // above). A red gate never reaches here, so it records nothing (goal 2). Never on a no-agent-changes skip
1312
1571
  // (card stop-gate-read-only-subagent-skip): nothing was actually verified.
1572
+ state.lastRed = null; // card stop-gate-no-change-restop: re-set at the final block below when it qualifies
1313
1573
  if (problems.length === 0 && !skipVerification) state.verified = verifiedCandidate;
1574
+ if (problems.length === 0 && !skipVerification) writeLastGreen(dir, counted.parsed); // empty when nothing really ran
1314
1575
  if (problems.length === 0 && subagent) {
1315
1576
  // A subagent's green gate: its counter resets; the checkpoint, if any, belongs to the session's own Stop.
1316
1577
  state.agentBlocks = { ...(state.agentBlocks || {}), [subagent.id]: 0 };
@@ -1353,6 +1614,7 @@ runHook("stop-gate", async (input) => {
1353
1614
  } else if (
1354
1615
  preVerificationProblems === 0 &&
1355
1616
  !evidenceProblemAdded &&
1617
+ !countProblemAdded &&
1356
1618
  commandFailures.length > 0 &&
1357
1619
  // R1 (pass 2 review): a command dropped for budget also adds one problem in step 3 (line ~892),
1358
1620
  // but is not in commandFailures — that part of the verification never ran at all, so the claim
@@ -1414,6 +1676,33 @@ runHook("stop-gate", async (input) => {
1414
1676
  } catch {
1415
1677
  finalClass = "real";
1416
1678
  }
1679
+ // card stop-gate-no-change-restop: record this red for reuse only when every configured command really
1680
+ // ran (not credited, none dropped for budget or timed out) and at least one failed, every failure by a
1681
+ // numeric non-zero exit (a signal kill has status null and is load-suspect). Stored with the block's ledger
1682
+ // class so a reuse logs the same class. state.lastRed was cleared above, so every other path leaves none.
1683
+ try {
1684
+ const configured = resolveCommands(cfg);
1685
+ if (
1686
+ !skipVerification &&
1687
+ !credited &&
1688
+ commandFailures.length > 0 &&
1689
+ results.length === configured.length &&
1690
+ results.every((r) => !r.timedOut && !r.skipped && (r.ok || (typeof r.status === "number" && r.status !== 0))) &&
1691
+ postCommandProblems - preVerificationProblems === commandFailures.length &&
1692
+ verifiedCandidate
1693
+ ) {
1694
+ state.lastRed = {
1695
+ fingerprint: verifiedCandidate,
1696
+ scope: subagent ? subagent.id : "main",
1697
+ commands: configured.map((c) => c.command),
1698
+ summary: problems.slice(preVerificationProblems, postCommandProblems).join("\n\n"),
1699
+ class: finalClass,
1700
+ };
1701
+ saveState(dir, input.session_id, state);
1702
+ }
1703
+ } catch {
1704
+ state.lastRed = null;
1705
+ }
1417
1706
  logBlock(reason, finalClass);
1418
1707
  return blockStopReleasing(reason);
1419
1708
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougharness/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "doug: tailored, approval-gated harness install for Claude Code.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,8 +23,8 @@
23
23
  "node": ">=22.16"
24
24
  },
25
25
  "dependencies": {
26
- "@dougharness/flow": "1.0.1",
27
- "@dougharness/gates": "1.0.1"
26
+ "@dougharness/flow": "1.0.2",
27
+ "@dougharness/gates": "1.0.2"
28
28
  },
29
29
  "bundleDependencies": [
30
30
  "@dougharness/flow",