@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.
- package/dist/generate/config.d.ts +0 -1
- package/dist/generate/config.js +1 -11
- package/dist/generate/config.js.map +1 -1
- package/dist/generate/proposal.js +9 -0
- package/dist/generate/proposal.js.map +1 -1
- package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/flow/lib/data/pairs.json +103 -0
- package/node_modules/@dougharness/flow/lib/decisions.mjs +2 -2
- package/node_modules/@dougharness/flow/lib/embeddings.mjs +4 -4
- package/node_modules/@dougharness/flow/lib/learn.mjs +58 -1
- package/node_modules/@dougharness/flow/lib/memory-import.mjs +22 -4
- package/node_modules/@dougharness/flow/package.json +1 -1
- package/node_modules/@dougharness/flow/scripts/memory.mjs +6 -5
- package/node_modules/@dougharness/flow/skills/core-next/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +3 -3
- package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/gates/lib/state.mjs +5 -0
- package/node_modules/@dougharness/gates/lib/test-totals.mjs +106 -0
- package/node_modules/@dougharness/gates/package.json +1 -1
- package/node_modules/@dougharness/gates/scripts/session-start-baseline.mjs +14 -0
- package/node_modules/@dougharness/gates/scripts/statusline.mjs +33 -7
- package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +291 -2
- package/package.json +3 -3
package/dist/generate/config.js
CHANGED
|
@@ -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"
|
|
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":"AAAA,OAAO,EAAE,
|
|
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.
|
|
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
|
|
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 = `${
|
|
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
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
export const DEFAULT_SANITY_PAIRS = join(dirname(fileURLToPath(import.meta.url)), "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
132
|
-
// pairs.json (or --pairs) against the
|
|
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 (
|
|
473
|
-
//
|
|
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
|
|
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 "
|
|
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 "
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
27
|
-
"@dougharness/gates": "1.0.
|
|
26
|
+
"@dougharness/flow": "1.0.2",
|
|
27
|
+
"@dougharness/gates": "1.0.2"
|
|
28
28
|
},
|
|
29
29
|
"bundleDependencies": [
|
|
30
30
|
"@dougharness/flow",
|