@dougharness/cli 1.0.2 → 1.1.1
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/bin.js +18 -3
- package/dist/bin.js.map +1 -1
- package/dist/board-serve.js +14 -7
- package/dist/board-serve.js.map +1 -1
- package/dist/board.d.ts +1 -1
- package/dist/board.js +42 -9
- package/dist/board.js.map +1 -1
- package/dist/detect/repo.js +14 -1
- package/dist/detect/repo.js.map +1 -1
- package/dist/detect/types.d.ts +2 -0
- package/dist/doctor.d.ts +46 -0
- package/dist/doctor.js +491 -0
- package/dist/doctor.js.map +1 -0
- package/dist/generate/agents.d.ts +7 -0
- package/dist/generate/agents.js +62 -12
- package/dist/generate/agents.js.map +1 -1
- package/dist/generate/claude-md.js +4 -0
- package/dist/generate/claude-md.js.map +1 -1
- package/dist/generate/config.d.ts +8 -0
- package/dist/generate/config.js +4 -1
- package/dist/generate/config.js.map +1 -1
- package/dist/generate/proposal.js +15 -6
- package/dist/generate/proposal.js.map +1 -1
- package/dist/generate/settings.d.ts +2 -0
- package/dist/generate/settings.js +17 -2
- package/dist/generate/settings.js.map +1 -1
- package/dist/init.d.ts +14 -0
- package/dist/init.js +94 -8
- package/dist/init.js.map +1 -1
- package/dist/memory-setup.d.ts +28 -0
- package/dist/memory-setup.js +326 -0
- package/dist/memory-setup.js.map +1 -0
- package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +16 -4
- package/node_modules/@dougharness/flow/agents/lead.md +1 -1
- package/node_modules/@dougharness/flow/agents/planner.md +3 -3
- package/node_modules/@dougharness/flow/agents/researcher.md +2 -2
- package/node_modules/@dougharness/flow/hooks/hooks.json +16 -0
- package/node_modules/@dougharness/flow/lib/board.mjs +171 -13
- package/node_modules/@dougharness/flow/lib/code-index.mjs +18 -3
- package/node_modules/@dougharness/flow/lib/context.mjs +57 -0
- package/node_modules/@dougharness/flow/lib/cost.mjs +155 -7
- package/node_modules/@dougharness/flow/lib/embeddings.mjs +188 -24
- package/node_modules/@dougharness/flow/lib/land.mjs +74 -10
- package/node_modules/@dougharness/flow/lib/learn.mjs +4 -4
- package/node_modules/@dougharness/flow/lib/memory.mjs +368 -50
- package/node_modules/@dougharness/flow/lib/plan.mjs +11 -1
- package/node_modules/@dougharness/flow/package.json +2 -1
- package/node_modules/@dougharness/flow/scripts/board.mjs +86 -25
- package/node_modules/@dougharness/flow/scripts/context.mjs +50 -0
- package/node_modules/@dougharness/flow/scripts/cost.mjs +15 -3
- package/node_modules/@dougharness/flow/scripts/memory.mjs +197 -37
- package/node_modules/@dougharness/flow/scripts/plan.mjs +54 -64
- package/node_modules/@dougharness/flow/scripts/session-recall.mjs +132 -0
- package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +105 -0
- package/node_modules/@dougharness/flow/skills/doug-learn/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +50 -14
- package/node_modules/@dougharness/flow/skills/doug-plan/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +7 -7
- package/node_modules/@dougharness/flow/skills/research/SKILL.md +4 -2
- package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +14 -13
- package/node_modules/@dougharness/flow/skills/swarm-launch/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/workflows/doug-implement.js +145 -86
- package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/gates/hooks/hooks.json +11 -0
- package/node_modules/@dougharness/gates/lib/baseline.mjs +166 -1
- package/node_modules/@dougharness/gates/lib/bash-rules.mjs +21 -14
- package/node_modules/@dougharness/gates/lib/block-ledger.mjs +58 -1
- package/node_modules/@dougharness/gates/lib/checkpoint.mjs +1 -1
- package/node_modules/@dougharness/gates/lib/config.mjs +20 -0
- package/node_modules/@dougharness/gates/lib/docs-gate.mjs +95 -0
- package/node_modules/@dougharness/gates/lib/evidence.mjs +2 -0
- package/node_modules/@dougharness/gates/lib/gate-lock.mjs +94 -0
- package/node_modules/@dougharness/gates/lib/run.mjs +4 -0
- package/node_modules/@dougharness/gates/lib/test-totals.mjs +40 -7
- package/node_modules/@dougharness/gates/lib/trace.mjs +24 -7
- package/node_modules/@dougharness/gates/lib/verified-tree.mjs +225 -0
- package/node_modules/@dougharness/gates/package.json +1 -1
- package/node_modules/@dougharness/gates/scripts/research-cap.mjs +133 -5
- package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +558 -197
- package/node_modules/@dougharness/gates/scripts/trace.mjs +5 -1
- package/node_modules/@dougharness/gates/scripts/turn-start.mjs +17 -0
- package/node_modules/@dougharness/gates/scripts/verify.mjs +201 -0
- package/package.json +4 -4
- package/templates/board-app.js +4 -3
- package/node_modules/@dougharness/flow/lib/rehearse.mjs +0 -825
- package/node_modules/@dougharness/flow/scripts/rehearse.mjs +0 -129
- package/node_modules/@dougharness/flow/skills/core-next/SKILL.md +0 -92
- package/node_modules/@dougharness/flow/skills/harness-fix/SKILL.md +0 -85
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// CLI for the development board, used by the /doug-next and
|
|
3
|
-
// board.mjs next [dir] [--track flow|hand] [--tag <tag>]
|
|
2
|
+
// CLI for the development board, used by the /doug-next and doug-hand skills. Deterministic; no agents.
|
|
3
|
+
// board.mjs next [dir] [--track flow|hand|any] [--tag <tag>]
|
|
4
4
|
// the first Ready card on that track (default flow) whose deps are
|
|
5
5
|
// Done, as JSON (exit 1 if none); --tag filters to cards carrying it
|
|
6
|
-
// board.mjs next --batch <n> [dir] [--track flow|hand] [--tag <tag>]
|
|
6
|
+
// board.mjs next --batch <n> [dir] [--track flow|hand|any] [--tag <tag>]
|
|
7
7
|
// the first n runnable Ready cards, in board order, as a JSON array
|
|
8
|
-
// (exit 1 if none): the cards /doug-next plans together
|
|
8
|
+
// (exit 1 if none): the cards /doug-next plans together; stderr also
|
|
9
|
+
// carries a `wall cap: <m> min` line (the cards' budgets from the size x class table)
|
|
10
|
+
// board.mjs budgets [dir] [--from <live-runs file>]
|
|
11
|
+
// without --from, the shipped wall budget table as `table: <JSON>`;
|
|
12
|
+
// with --from, recomputes it (p80 per size/class cell) from the file's
|
|
13
|
+
// Wall clock rows joined to the board, one line per cell and a final
|
|
14
|
+
// `table:` line; writes nothing (replace WALL_BUDGET_MIN by hand)
|
|
9
15
|
// board.mjs card <id> [dir] one card as JSON
|
|
10
16
|
// board.mjs reorder <id> [dir] --before <other> | --after <other> | --top | --bottom
|
|
11
17
|
// reorder a card within its column
|
|
@@ -21,26 +27,27 @@
|
|
|
21
27
|
// flow, or named by .doug/plan.json (each with --force removes it
|
|
22
28
|
// and prints a warning to stderr); prints the removed card as JSON
|
|
23
29
|
// to stdout so a mistaken removal can be re-added by hand
|
|
24
|
-
// board.mjs record <id> <report.json> [dir] [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary "<id>=<class>[: <reason>]"]... [--note <text>] [--rehearsal <scenario>]
|
|
30
|
+
// board.mjs record <id> <report.json> [dir] --summary "<one sentence>" [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary "<id>=<class>[: <reason>]"]... [--note <text>] [--rehearsal <scenario>] [--run <run-id>] [--claude-dir <dir>]
|
|
25
31
|
// append a run entry for the card to docs/live-runs.md; every adversary block
|
|
26
32
|
// in the report is classified real, marginal, or false (repeat the flag per block).
|
|
27
33
|
// When .doug/plan.json lists the card among its `cards` (a batch), the entry
|
|
28
34
|
// shows only that card's tasks, --cost is that card's tasks' cost, and
|
|
29
35
|
// --shared-cost <usd> the shared integration agents', counted once
|
|
30
|
-
// board.mjs record <id> --hand [dir] [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]
|
|
36
|
+
// board.mjs record <id> --hand [dir] --summary "<one sentence>" [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]
|
|
31
37
|
// append a hand-track entry (no report) for the card
|
|
32
38
|
// --rehearsal <scenario> (either form) marks the entry as a rehearse.mjs run on the ts-basic fixture: the first
|
|
33
39
|
// prose line and the commit sha (abbreviated to 7 characters) read as the fixture's, not this repository's.
|
|
34
40
|
// Either record form also promotes .doug/.state/research/<id>.md to docs/research/<id>.md when that note
|
|
35
41
|
// exists and differs from the docs copy, copying it and printing one line saying so; a card with no note, or
|
|
36
42
|
// whose docs copy is already byte-identical, is untouched, silently.
|
|
37
|
-
// board.mjs summary <report.json> [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>]
|
|
43
|
+
// board.mjs summary <report.json> --summary "<one sentence>" [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>] [--run <run-id>] [--claude-dir <dir>]
|
|
38
44
|
// the three-line chat summary of a report (--card: that card's tasks only)
|
|
39
45
|
|
|
40
46
|
import { readFileSync } from "node:fs";
|
|
41
47
|
import { relative, resolve } from "node:path";
|
|
42
|
-
import { loadBoard, saveBoard, findCard, nextReadyCard, nextReadyCards, moveCard, editCard, removeCard, reorderCard, runEntry, handEntry, runSummary, boardPath, parseAdversaryClasses, filterReportForCard, recordLanding } from "../lib/board.mjs";
|
|
43
|
-
import { loadPlan, unwrapReport } from "../lib/plan.mjs";
|
|
48
|
+
import { loadBoard, saveBoard, findCard, nextReadyCard, nextReadyCards, moveCard, editCard, removeCard, reorderCard, readyCapWarning, wallCapMinutes, WALL_BUDGET_MIN, WALL_BUDGET_MIN_N, parseLiveRunWalls, wallBudgetTable, runEntry, handEntry, handWallVerdict, runSummary, boardPath, parseAdversaryClasses, filterReportForCard, recordLanding } from "../lib/board.mjs";
|
|
49
|
+
import { loadPlan, unwrapReport, DEFAULT_BUDGET } from "../lib/plan.mjs";
|
|
50
|
+
import { wallForRun } from "../lib/cost.mjs";
|
|
44
51
|
|
|
45
52
|
const EDIT_FIELD_ORDER = ["title", "goal", "size", "component", "track", "class", "tags", "deps", "source"];
|
|
46
53
|
const EDIT_USAGE = "usage: board.mjs edit <id> [dir] [--title <t>] [--goal <g> | --goal-file <path>] [--size S|M|L] [--component <c>] [--track flow|hand] [--class <c>] [--tag a,b] [--deps a,b] [--source <s>] [--force]";
|
|
@@ -58,10 +65,11 @@ const SUBCOMMAND_FLAGS = {
|
|
|
58
65
|
edit: ["title", "goal", "goal-file", "size", "component", "track", "class", "deps", "tag", "source", "force"],
|
|
59
66
|
remove: ["force"],
|
|
60
67
|
reorder: ["before", "after", "top", "bottom"],
|
|
61
|
-
|
|
68
|
+
budgets: ["from"],
|
|
69
|
+
summary: ["summary", "cost", "codex-cost", "wall", "commit", "card", "run", "claude-dir"],
|
|
62
70
|
};
|
|
63
|
-
const RECORD_HAND_FLAGS = ["hand", "commit", "wall", "gate", "note", "rehearsal"];
|
|
64
|
-
const RECORD_REPORT_FLAGS = ["cost", "codex-cost", "wall", "commit", "adversary", "shared-cost", "note", "rehearsal"];
|
|
71
|
+
const RECORD_HAND_FLAGS = ["summary", "hand", "commit", "wall", "gate", "note", "rehearsal"];
|
|
72
|
+
const RECORD_REPORT_FLAGS = ["summary", "cost", "codex-cost", "wall", "commit", "adversary", "shared-cost", "note", "rehearsal", "run", "claude-dir"];
|
|
65
73
|
const opts = {};
|
|
66
74
|
const positional = [];
|
|
67
75
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -86,16 +94,17 @@ function fail(msg, code = 1) {
|
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
const USAGE_FOR = {
|
|
89
|
-
next: "usage: board.mjs next [dir] [--track flow|hand] [--batch <n>] [--tag <tag>]",
|
|
97
|
+
next: "usage: board.mjs next [dir] [--track flow|hand|any] [--batch <n>] [--tag <tag>]",
|
|
90
98
|
card: "usage: board.mjs card <id> [dir]",
|
|
91
99
|
move: "usage: board.mjs move <id> <column> [dir] [--source <text>]",
|
|
92
100
|
edit: EDIT_USAGE,
|
|
93
101
|
remove: "usage: board.mjs remove <id> [dir] [--force]",
|
|
94
102
|
reorder: "usage: board.mjs reorder <id> [dir] --before <other> | --after <other> | --top | --bottom",
|
|
95
|
-
|
|
103
|
+
budgets: "usage: board.mjs budgets [dir] [--from <live-runs file>]",
|
|
104
|
+
summary: "usage: board.mjs summary <report.json> --summary \"<one sentence>\" [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>] [--run <run-id>] [--claude-dir <dir>]",
|
|
96
105
|
};
|
|
97
|
-
const RECORD_HAND_USAGE = "usage: board.mjs record <id> --hand [dir] [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]";
|
|
98
|
-
const RECORD_REPORT_USAGE = "usage: board.mjs record <id> <report.json> [dir] [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary \"<id>=<class>[: <reason>]\"]... [--note <text>] [--rehearsal <scenario>]";
|
|
106
|
+
const RECORD_HAND_USAGE = "usage: board.mjs record <id> --hand [dir] --summary \"<one sentence>\" [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]";
|
|
107
|
+
const RECORD_REPORT_USAGE = "usage: board.mjs record <id> <report.json> [dir] --summary \"<one sentence>\" [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary \"<id>=<class>[: <reason>]\"]... [--note <text>] [--rehearsal <scenario>] [--run <run-id>] [--claude-dir <dir>]";
|
|
99
108
|
|
|
100
109
|
// An unknown --flag for the given subcommand: exit 2, name the flag and the subcommand's usage.
|
|
101
110
|
// Runs on flag names only (parse order doesn't matter), before any work or write.
|
|
@@ -119,22 +128,60 @@ if (cmd === "record" || Object.prototype.hasOwnProperty.call(SUBCOMMAND_FLAGS, c
|
|
|
119
128
|
}
|
|
120
129
|
}
|
|
121
130
|
|
|
131
|
+
// --summary: the lead's one sentence of what changed for the user, required by record and summary (card
|
|
132
|
+
// landing-one-sentence-summary). Trimmed, non-empty, one line, no sentence end followed by more text.
|
|
133
|
+
// Checked before anything is appended or promoted; printed as stdout's first line, never written to a file.
|
|
134
|
+
let summarySentence = null;
|
|
135
|
+
if (cmd === "record" || cmd === "summary") {
|
|
136
|
+
const text = typeof opts.summary === "string" ? opts.summary.trim() : "";
|
|
137
|
+
if (!text) fail(`--summary "<one sentence>" is required for board.mjs ${cmd}: one sentence, in plain words, of what changed for the user`, 2);
|
|
138
|
+
if (/[\r\n]/.test(text) || /[.!?]\s+\S/.test(text)) fail(`--summary must be one sentence for board.mjs ${cmd}: no newline, and no sentence end followed by more text`, 2);
|
|
139
|
+
summarySentence = text;
|
|
140
|
+
}
|
|
141
|
+
|
|
122
142
|
try {
|
|
123
143
|
switch (cmd) {
|
|
124
144
|
case "next": {
|
|
125
145
|
const dir = dirFrom(rest, 0);
|
|
126
|
-
const track = opts.track === "hand" ? "hand" : "flow";
|
|
127
|
-
if (opts.track !== undefined && opts.track !== "flow" && opts.track !== "hand") fail(`unknown track "${opts.track}"; tracks are flow, hand`, 2);
|
|
146
|
+
const track = opts.track === "hand" ? "hand" : opts.track === "any" ? "any" : "flow";
|
|
147
|
+
if (opts.track !== undefined && opts.track !== "flow" && opts.track !== "hand" && opts.track !== "any") fail(`unknown track "${opts.track}"; tracks are flow, hand, any`, 2);
|
|
128
148
|
const batch = opts.batch !== undefined ? Number(opts.batch) : null;
|
|
129
149
|
if (batch !== null && !(Number.isInteger(batch) && batch > 0)) fail(`--batch must be a positive integer, got ${JSON.stringify(opts.batch)}`, 2);
|
|
130
150
|
if (opts.tag !== undefined && opts.tag.includes(",")) fail(`--tag takes one tag here; got "${opts.tag}"`, 2);
|
|
131
151
|
const board = loadBoard(dir);
|
|
132
152
|
const { card, cards, skipped } = batch === null ? nextReadyCard(board, { track, tag: opts.tag }) : nextReadyCards(board, { track, batch, tag: opts.tag });
|
|
133
|
-
for (const s of skipped) process.stderr.write(s.hand ? `skipping ${s.id}: hand track (
|
|
153
|
+
for (const s of skipped) process.stderr.write(s.hand ? `skipping ${s.id}: hand track (a by-hand card; /doug-next ${s.id} takes it)\n` : s.flow ? `skipping ${s.id}: flow track (run it with /doug-next)\n` : `skipping ${s.id}: waiting on ${s.waitingOn.join(", ")}\n`);
|
|
154
|
+
const capLine = readyCapWarning(board);
|
|
155
|
+
if (capLine) process.stderr.write(capLine);
|
|
134
156
|
if (batch === null ? !card : !cards.length) fail(track === "hand" ? "No Ready hand-track card whose dependencies are Done." : "No Ready card whose dependencies are Done.");
|
|
157
|
+
if (batch !== null) process.stderr.write(`wall cap: ${wallCapMinutes(cards)} min (size x class table at p80, board.mjs budgets; no size counts as M)\n`);
|
|
135
158
|
process.stdout.write(JSON.stringify(batch === null ? card : cards, null, 2) + "\n");
|
|
136
159
|
break;
|
|
137
160
|
}
|
|
161
|
+
case "budgets": {
|
|
162
|
+
if (opts.from === undefined) {
|
|
163
|
+
process.stdout.write(`table: ${JSON.stringify(WALL_BUDGET_MIN)}\n`);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
const dir = dirFrom(rest, 0);
|
|
167
|
+
let text;
|
|
168
|
+
try {
|
|
169
|
+
text = readFileSync(resolve(opts.from), "utf8");
|
|
170
|
+
} catch (e) {
|
|
171
|
+
fail(`cannot read --from file ${opts.from}: ${e.message}`, 2);
|
|
172
|
+
}
|
|
173
|
+
const { table, cells } = wallBudgetTable(parseLiveRunWalls(text), loadBoard(dir));
|
|
174
|
+
const out = [];
|
|
175
|
+
for (const size of ["S", "M", "L"]) {
|
|
176
|
+
for (const c of cells.filter((x) => x.size === size)) {
|
|
177
|
+
if (c.cls === "all") out.push(c.n === 0 ? `${size}/all n=0 (no landings; keeps the shipped ${WALL_BUDGET_MIN[size].all})` : `${size}/all n=${c.n} p80 ${c.p80}`);
|
|
178
|
+
else out.push(`${size}/${c.cls} n=${c.n} p80 ${c.p80}${c.n < WALL_BUDGET_MIN_N ? ` (fewer than ${WALL_BUDGET_MIN_N}; falls back to ${size}/all ${table[size].all})` : ""}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
out.push(`table: ${JSON.stringify(table)}`);
|
|
182
|
+
process.stdout.write(out.join("\n") + "\n");
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
138
185
|
case "card": {
|
|
139
186
|
const dir = dirFrom(rest, 1);
|
|
140
187
|
process.stdout.write(JSON.stringify(findCard(loadBoard(dir), rest[0]), null, 2) + "\n");
|
|
@@ -144,8 +191,11 @@ try {
|
|
|
144
191
|
const [id, column] = rest;
|
|
145
192
|
if (!id || !column) fail("usage: board.mjs move <id> <column> [dir] [--source <text>]", 2);
|
|
146
193
|
const dir = dirFrom(rest, 2);
|
|
147
|
-
|
|
194
|
+
const moved = moveCard(loadBoard(dir), id, column, { source: opts.source });
|
|
195
|
+
saveBoard(dir, moved);
|
|
148
196
|
process.stdout.write(`Moved ${id} to ${column} in ${relative(dir, boardPath(dir))}.\n`);
|
|
197
|
+
const capLine = column === "ready" ? readyCapWarning(moved) : null;
|
|
198
|
+
if (capLine) process.stderr.write(capLine);
|
|
149
199
|
break;
|
|
150
200
|
}
|
|
151
201
|
case "edit": {
|
|
@@ -216,17 +266,22 @@ try {
|
|
|
216
266
|
case "record": {
|
|
217
267
|
if (opts.hand) {
|
|
218
268
|
const [id] = rest;
|
|
219
|
-
if (!id) fail("usage: board.mjs record <id> --hand [dir] [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]", 2);
|
|
269
|
+
if (!id) fail("usage: board.mjs record <id> --hand [dir] --summary \"<one sentence>\" [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>] [--rehearsal <scenario>]", 2);
|
|
220
270
|
const dir = dirFrom(rest, 1);
|
|
221
271
|
const card = findCard(loadBoard(dir), id);
|
|
222
272
|
const entry = handEntry({ card, commit: opts.commit ?? null, wallClock: opts.wall ?? null, gate: opts.gate ?? null, note: opts.note ?? null, record: relative(dir, boardPath(dir)), rehearsal: opts.rehearsal ?? null });
|
|
223
273
|
const { file, promoted } = recordLanding(dir, id, entry);
|
|
274
|
+
process.stdout.write(`${summarySentence}\n`);
|
|
275
|
+
if (!opts.rehearsal) {
|
|
276
|
+
const verdict = handWallVerdict(card, opts.wall ?? null);
|
|
277
|
+
if (verdict.over) process.stdout.write(`Wall budget: ${verdict.text}\n`);
|
|
278
|
+
}
|
|
224
279
|
if (promoted) process.stdout.write(`Promoted the research note for ${id} to ${promoted}.\n`);
|
|
225
280
|
process.stdout.write(`Appended a hand-track entry for ${id} to ${file}.\n`);
|
|
226
281
|
break;
|
|
227
282
|
}
|
|
228
283
|
const [id, reportFile] = rest;
|
|
229
|
-
if (!id || !reportFile) fail("usage: board.mjs record <id> <report.json> [dir] [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary \"<id>=<class>[: <reason>]\"]... [--note <text>] [--rehearsal <scenario>]", 2);
|
|
284
|
+
if (!id || !reportFile) fail("usage: board.mjs record <id> <report.json> [dir] --summary \"<one sentence>\" [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary \"<id>=<class>[: <reason>]\"]... [--note <text>] [--rehearsal <scenario>] [--run <run-id>] [--claude-dir <dir>]", 2);
|
|
230
285
|
const dir = dirFrom(rest, 2);
|
|
231
286
|
const card = findCard(loadBoard(dir), id);
|
|
232
287
|
const report = unwrapReport(JSON.parse(readFileSync(resolve(reportFile), "utf8")));
|
|
@@ -234,22 +289,28 @@ try {
|
|
|
234
289
|
const plan = loadPlan(dir);
|
|
235
290
|
const batch = plan && Array.isArray(plan.cards) && plan.cards.includes(id) ? plan.cards : null;
|
|
236
291
|
const taskIds = batch ? plan.tasks.filter((t) => t.card === id).map((t) => t.id) : [];
|
|
237
|
-
const
|
|
292
|
+
const wall = opts.run ? wallForRun({ runId: opts.run, claudeDir: opts["claude-dir"], projectDir: dir, wallMinutes: report.budget?.wallMinutes ?? DEFAULT_BUDGET.wallMinutes }) : null;
|
|
293
|
+
const entry = runEntry({ card, report, wall, cost: opts.cost !== undefined ? Number(opts.cost) : null, codexCost: opts["codex-cost"] !== undefined ? Number(opts["codex-cost"]) : null, wallClock: opts.wall ?? null, mergeCommit: opts.commit ?? null, record: relative(dir, boardPath(dir)), adversary: parseAdversaryClasses(opts.adversary || []), batch, taskIds, sharedCost: opts["shared-cost"] !== undefined ? Number(opts["shared-cost"]) : null, note: opts.note ?? null, rehearsal: opts.rehearsal ?? null });
|
|
238
294
|
const { file, promoted } = recordLanding(dir, id, entry);
|
|
295
|
+
process.stdout.write(`${summarySentence}\n`);
|
|
239
296
|
if (promoted) process.stdout.write(`Promoted the research note for ${id} to ${promoted}.\n`);
|
|
240
297
|
process.stdout.write(`Appended a run entry for ${id} to ${file}.\n`);
|
|
241
298
|
break;
|
|
242
299
|
}
|
|
243
300
|
case "summary": {
|
|
244
301
|
const [reportFile] = rest;
|
|
245
|
-
if (!reportFile) fail("usage: board.mjs summary <report.json> [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>]", 2);
|
|
302
|
+
if (!reportFile) fail("usage: board.mjs summary <report.json> --summary \"<one sentence>\" [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>] [--run <run-id>] [--claude-dir <dir>]", 2);
|
|
246
303
|
let report = unwrapReport(JSON.parse(readFileSync(resolve(reportFile), "utf8")));
|
|
247
304
|
if (opts.card !== undefined) {
|
|
248
305
|
// With the plan at hand the filter also covers a report whose tasks carry no card.
|
|
249
306
|
const plan = loadPlan(dirFrom(rest, 1));
|
|
250
307
|
report = filterReportForCard(report, opts.card, plan && Array.isArray(plan.tasks) ? plan.tasks.filter((t) => t.card === opts.card).map((t) => t.id) : []);
|
|
251
308
|
}
|
|
252
|
-
|
|
309
|
+
const wall = opts.run ? wallForRun({ runId: opts.run, claudeDir: opts["claude-dir"], projectDir: process.cwd(), wallMinutes: report.budget?.wallMinutes ?? DEFAULT_BUDGET.wallMinutes }) : null;
|
|
310
|
+
const sumPlan = opts.card !== undefined ? loadPlan(dirFrom(rest, 1)) : null;
|
|
311
|
+
const sumBatch = sumPlan && Array.isArray(sumPlan.cards) && sumPlan.cards.includes(opts.card) ? sumPlan.cards : null;
|
|
312
|
+
process.stdout.write(`${summarySentence}\n`);
|
|
313
|
+
process.stdout.write(runSummary({ report, wall, batch: sumBatch, card: opts.card ?? null, cost: opts.cost !== undefined ? Number(opts.cost) : null, codexCost: opts["codex-cost"] !== undefined ? Number(opts["codex-cost"]) : null, wallClock: opts.wall ?? null, mergeCommit: opts.commit ?? null }));
|
|
253
314
|
break;
|
|
254
315
|
}
|
|
255
316
|
default:
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The lead's own context percentage (card autopilot-unlimited), read from the session transcript.
|
|
3
|
+
// context.mjs [dir] [--session <id|latest>] [--claude-dir <path>]
|
|
4
|
+
// dir the project directory (default cwd); <dir>/.doug/config.json supplies autopilot.contextThreshold
|
|
5
|
+
// --session a session id or latest, as cost.mjs --session. Default: the CLAUDE_CODE_SESSION_ID environment
|
|
6
|
+
// variable (the lead's own session, so a newer non-lead transcript, such as a reflect pass, is not read);
|
|
7
|
+
// latest only when it is unset or empty. A variable naming a session with no transcript exits 1.
|
|
8
|
+
// --claude-dir where ~/.claude lives (tests point it at a fixture)
|
|
9
|
+
// Prints one line and exits 0; exits 1 with a stderr message when there is no transcript or no usage line.
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { resolve, join } from "node:path";
|
|
12
|
+
import { sessionTranscriptPath } from "../lib/cost.mjs";
|
|
13
|
+
import { atThreshold, contextReading, contextThreshold } from "../lib/context.mjs";
|
|
14
|
+
|
|
15
|
+
const argv = process.argv.slice(2);
|
|
16
|
+
const opts = {};
|
|
17
|
+
const positional = [];
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
if (!argv[i].startsWith("--")) {
|
|
20
|
+
positional.push(argv[i]);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
opts[argv[i].slice(2)] = argv[i + 1] ?? "";
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dir = resolve(positional[0] || process.cwd());
|
|
28
|
+
let config = {};
|
|
29
|
+
try {
|
|
30
|
+
config = JSON.parse(readFileSync(join(dir, ".doug/config.json"), "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
// absent or unreadable: defaults
|
|
33
|
+
}
|
|
34
|
+
const threshold = contextThreshold(config, (m) => process.stderr.write(`[doug] ${m}\n`));
|
|
35
|
+
|
|
36
|
+
let file;
|
|
37
|
+
try {
|
|
38
|
+
file = sessionTranscriptPath({ session: opts.session || process.env.CLAUDE_CODE_SESSION_ID || "latest", ...(opts["claude-dir"] ? { claudeDir: resolve(opts["claude-dir"]) } : {}), projectDir: dir });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
process.stderr.write(`context: ${err.message}\n`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const r = contextReading(file);
|
|
44
|
+
if (!r) {
|
|
45
|
+
process.stderr.write(`context: no usage line in ${file}\n`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
process.stdout.write(
|
|
49
|
+
`context: ${r.pct}% (${r.tokens} of ${r.window} tokens, ${r.model}); threshold ${threshold}% (autopilot.contextThreshold) -> ${atThreshold(r.pct, threshold) ? "stop" : "continue"}\n`,
|
|
50
|
+
);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Real cost of one doug-implement run from the local Claude Code transcripts; no network, nothing estimated.
|
|
3
|
+
// cost.mjs --session <id|latest> [dir] [--json] [--claude-dir <path>]
|
|
4
|
+
// --session price one Claude Code session: its transcript plus every subagent transcript under it
|
|
5
|
+
// (latest = the most recently modified one in the project); exit 1 when none is found
|
|
3
6
|
// cost.mjs <run-id> [dir] [--json] [--claude-dir <path>]
|
|
4
7
|
// <run-id> the Workflow run id (wf_...)
|
|
5
8
|
// [dir] the project directory whose ~/.claude/projects slug is searched first (default cwd)
|
|
@@ -15,7 +18,7 @@
|
|
|
15
18
|
// with the run total and any unpriced agent. An unreadable/invalid default report is ignored, not a usage error.
|
|
16
19
|
import { readFileSync } from "node:fs";
|
|
17
20
|
import { resolve, join } from "node:path";
|
|
18
|
-
import { costRun, renderCost, costByCard, renderCostByCard } from "../lib/cost.mjs";
|
|
21
|
+
import { costRun, costSession, renderSession, renderCost, costByCard, renderCostByCard } from "../lib/cost.mjs";
|
|
19
22
|
import { loadPlan, REPORT_RELPATH, unwrapReport } from "../lib/plan.mjs";
|
|
20
23
|
|
|
21
24
|
const argv = process.argv.slice(2);
|
|
@@ -35,13 +38,22 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
35
38
|
opts[name] = argv[i + 1] ?? "";
|
|
36
39
|
i++;
|
|
37
40
|
}
|
|
38
|
-
const [runId, dirArg] = positional;
|
|
39
|
-
if (
|
|
41
|
+
const [runId, dirArg] = opts.session !== undefined ? [null, positional[0]] : positional;
|
|
42
|
+
if (opts.session === "") {
|
|
43
|
+
process.stderr.write("usage: cost.mjs --session <id|latest> [dir] [--json] [--claude-dir <path>]\n");
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
if (!runId && opts.session === undefined) {
|
|
40
47
|
process.stderr.write("usage: cost.mjs <run-id> [dir] [--json] [--claude-dir <path>]\n");
|
|
41
48
|
process.exit(2);
|
|
42
49
|
}
|
|
43
50
|
try {
|
|
44
51
|
const projectDir = resolve(process.env.CLAUDE_PROJECT_DIR || dirArg || process.cwd());
|
|
52
|
+
if (opts.session !== undefined) {
|
|
53
|
+
const result = costSession({ session: opts.session, projectDir, claudeDir: opts["claude-dir"] ? resolve(opts["claude-dir"]) : undefined });
|
|
54
|
+
process.stdout.write(opts.json ? JSON.stringify(result, null, 2) + "\n" : renderSession(result));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
45
57
|
let reportPath = opts.report ? resolve(opts.report) : join(projectDir, REPORT_RELPATH);
|
|
46
58
|
let report = null;
|
|
47
59
|
if (opts.report) {
|