@jphutchins/code-review 0.1.0-alpha.18 → 0.1.0-alpha.19
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/index.js +46 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { defineCommand, runMain } from 'citty';
|
|
3
|
-
import { readFileSync, writeFileSync, readdirSync } from 'fs';
|
|
3
|
+
import { readFileSync, writeFileSync, statSync, readdirSync } from 'fs';
|
|
4
4
|
import { resolve as resolve$1, join, dirname, basename } from 'path';
|
|
5
5
|
import { Eta } from 'eta';
|
|
6
6
|
import parseDiff from 'parse-diff';
|
|
@@ -765,7 +765,7 @@ var timeClause = (i) => i.elapsedMs === null ? null : i.wallMs !== null && i.wal
|
|
|
765
765
|
var directive = (phase, draftPath, isSubagent) => {
|
|
766
766
|
if (isSubagent)
|
|
767
767
|
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now and report the findings you have back to the main agent in your reply. Do not write ${draftPath} yourself; only the main agent writes it.` : `Wind down investigation and report the findings you have back to the main agent in your reply \u2014 do not write ${draftPath} yourself; the main agent writes it, and you may run out of budget otherwise.`;
|
|
768
|
-
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Write your COMPLETE findings to ${draftPath} and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 you may run out of budget before you finish otherwise.`;
|
|
768
|
+
return phase.kind === "hard" ? `Budget nearly exhausted \u2014 STOP all new investigation now. Do not wait for subagents still running in the background. Write your COMPLETE findings from what you already have to ${draftPath} and run \`code-review validate ${draftPath} --explain\` until it passes (--explain prints the exact schema when the shape is wrong). Other tools are blocked until that draft is written.` : `Wind down investigation and write your COMPLETE findings to ${draftPath} now, then run \`code-review validate ${draftPath} --explain\` (it prints the exact schema if the shape is wrong) \u2014 fold in the subagent reports you already have rather than waiting on stragglers; you may run out of budget before you finish otherwise.`;
|
|
769
769
|
};
|
|
770
770
|
var budgetMessage = (i, phase, draftPath, isSubagent) => {
|
|
771
771
|
const status = [spendClause(i), timeClause(i)].filter((c) => c !== null).join(" \xB7 ");
|
|
@@ -804,6 +804,16 @@ var writesToDraft = (toolName, toolInput, draftPath) => {
|
|
|
804
804
|
return false;
|
|
805
805
|
};
|
|
806
806
|
var singleWriterMessage = (draftPath) => `Only the main agent may write ${draftPath}. When a subagent writes it too, the concurrent writers clobber each other and the review comes out empty. Do NOT write, edit, or redirect into ${draftPath} \u2014 instead, return the findings you discovered in your reply (the field names are in the schema); the main agent collects every subagent's reported findings and writes the draft itself.`;
|
|
807
|
+
var seedMarkerPath = (draftPath) => `${draftPath}.seed`;
|
|
808
|
+
var mainHasWrittenDraft = (draftMtimeMs, seedMarkerMtimeMs) => draftMtimeMs !== null && (seedMarkerMtimeMs === null || draftMtimeMs > seedMarkerMtimeMs);
|
|
809
|
+
var spawnFloorMessage = (draftPath) => `Write your own first-pass findings to ${draftPath} before spawning subagents \u2014 a review must never depend on subagents alone, and a pre-seeded draft does not count until you have revised it yourself this run. Write ${draftPath} from what you have read so far (preliminary findings are fine), run \`code-review validate ${draftPath} --explain\` until it passes, then fan out; your subagents run in the background, so keep refining the draft as their reports arrive.`;
|
|
810
|
+
var forceBackgroundSpawn = (toolInput) => ({
|
|
811
|
+
hookSpecificOutput: {
|
|
812
|
+
hookEventName: "PreToolUse",
|
|
813
|
+
permissionDecision: "allow",
|
|
814
|
+
updatedInput: { ...asRecord(toolInput) ?? {}, run_in_background: true }
|
|
815
|
+
}
|
|
816
|
+
});
|
|
807
817
|
var denyPreTool = (reason) => ({
|
|
808
818
|
hookSpecificOutput: {
|
|
809
819
|
hookEventName: "PreToolUse",
|
|
@@ -833,11 +843,16 @@ var evaluateBudgetHook = (input, params) => {
|
|
|
833
843
|
};
|
|
834
844
|
case "PreToolUse": {
|
|
835
845
|
const toolName = rec["tool_name"];
|
|
836
|
-
if (
|
|
846
|
+
if (typeof toolName !== "string") return {};
|
|
847
|
+
if (isSubagent && writesToDraft(toolName, rec["tool_input"], params.draftPath))
|
|
837
848
|
return denyPreTool(singleWriterMessage(params.draftPath));
|
|
838
|
-
if (phase.kind
|
|
839
|
-
if (typeof toolName === "string" && blockedDuringConvergence(toolName, rec["tool_input"]))
|
|
849
|
+
if (phase.kind === "hard" && blockedDuringConvergence(toolName, rec["tool_input"]))
|
|
840
850
|
return denyPreTool(budgetMessage(inputs, phase, params.draftPath, isSubagent));
|
|
851
|
+
if (SPAWN_TOOLS.has(toolName)) {
|
|
852
|
+
if (!isSubagent && !params.mainDraftWritten)
|
|
853
|
+
return denyPreTool(spawnFloorMessage(params.draftPath));
|
|
854
|
+
return forceBackgroundSpawn(rec["tool_input"]);
|
|
855
|
+
}
|
|
841
856
|
return {};
|
|
842
857
|
}
|
|
843
858
|
default:
|
|
@@ -2231,6 +2246,13 @@ var parseBudgetUsd = (raw) => {
|
|
|
2231
2246
|
const n = Number.parseFloat(raw);
|
|
2232
2247
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
|
2233
2248
|
};
|
|
2249
|
+
var mtimeMsOf = (path) => {
|
|
2250
|
+
try {
|
|
2251
|
+
return statSync(path).mtimeMs;
|
|
2252
|
+
} catch {
|
|
2253
|
+
return null;
|
|
2254
|
+
}
|
|
2255
|
+
};
|
|
2234
2256
|
var transcriptPathOf = (input) => {
|
|
2235
2257
|
const tp = (typeof input === "object" && input !== null ? input : {})["transcript_path"];
|
|
2236
2258
|
return typeof tp === "string" ? tp : void 0;
|
|
@@ -2238,7 +2260,7 @@ var transcriptPathOf = (input) => {
|
|
|
2238
2260
|
var budgetHookCmd = defineCommand({
|
|
2239
2261
|
meta: {
|
|
2240
2262
|
name: "budget-hook",
|
|
2241
|
-
description: "Self-dispatching Claude Code hook for budget discipline (issue #38): on PostToolBatch, steer the agent to converge once spend or wall-clock enters its soft wind-down reserve; on PreToolUse, inside the hard reserve, deny the budget-burning tools (subagent spawns, arbitrary shell, web) while leaving the draft-delivery path open. Reads the hook payload on stdin, measures spend from its transcript, and decides on $ and/or time. Degrades to a no-op on any error."
|
|
2263
|
+
description: "Self-dispatching Claude Code hook for budget discipline (issue #38): on PostToolBatch, steer the agent to converge once spend or wall-clock enters its soft wind-down reserve; on PreToolUse, inside the hard reserve, deny the budget-burning tools (subagent spawns, arbitrary shell, web) while leaving the draft-delivery path open. At every phase, the main agent's subagent spawns are denied until it has written its own first-pass draft (seed-draft's sidecar marker tells the seed apart), and permitted spawns are rewritten to run in the background so no batch join can block the spawner (issue #73). Reads the hook payload on stdin, measures spend from its transcript, and decides on $ and/or time. Degrades to a no-op on any error."
|
|
2242
2264
|
},
|
|
2243
2265
|
args: {
|
|
2244
2266
|
draft: {
|
|
@@ -2301,7 +2323,11 @@ var budgetHookCmd = defineCommand({
|
|
|
2301
2323
|
flatUsd: parseBudgetUsd(args["reserve-usd"]) ?? DEFAULT_RESERVE.flatUsd,
|
|
2302
2324
|
flatMs: args["reserve-wall"] ? parseWallMs(args["reserve-wall"]) ?? DEFAULT_RESERVE.flatMs : DEFAULT_RESERVE.flatMs
|
|
2303
2325
|
},
|
|
2304
|
-
draftPath
|
|
2326
|
+
draftPath,
|
|
2327
|
+
mainDraftWritten: mainHasWrittenDraft(
|
|
2328
|
+
mtimeMsOf(draftPath),
|
|
2329
|
+
mtimeMsOf(seedMarkerPath(draftPath))
|
|
2330
|
+
)
|
|
2305
2331
|
});
|
|
2306
2332
|
process.stdout.write(`${JSON.stringify(output)}
|
|
2307
2333
|
`);
|
|
@@ -2484,7 +2510,7 @@ ${printableSchema(schemaPath)}
|
|
|
2484
2510
|
var seedDraftCmd = defineCommand({
|
|
2485
2511
|
meta: {
|
|
2486
2512
|
name: "seed-draft",
|
|
2487
|
-
description: "Write a valid findings $DRAFT before the review runs: the decoded findings from a prior review when one exists and still validates (incremental re-review), else an empty-but-valid scaffold \u2014 so a valid draft exists from turn 0 (issues #52, #53). Prints the mode chosen (prior|empty|none \u2014 none when even the scaffold write failed) to stdout; always exits 0"
|
|
2513
|
+
description: "Write a valid findings $DRAFT before the review runs: the decoded findings from a prior review when one exists and still validates (incremental re-review), else an empty-but-valid scaffold \u2014 so a valid draft exists from turn 0 (issues #52, #53). Also drops a sidecar marker beside the seed so the budget hook can tell the untouched seed from a draft the agent wrote itself (issue #73). Prints the mode chosen (prior|empty|none \u2014 none when even the scaffold write failed) to stdout; always exits 0"
|
|
2488
2514
|
},
|
|
2489
2515
|
args: {
|
|
2490
2516
|
prior: {
|
|
@@ -2519,10 +2545,21 @@ var seedDraftCmd = defineCommand({
|
|
|
2519
2545
|
`
|
|
2520
2546
|
);
|
|
2521
2547
|
}
|
|
2548
|
+
const writeSeedMarker = () => {
|
|
2549
|
+
try {
|
|
2550
|
+
writeFileSync(seedMarkerPath(outPath), "code-review seed marker\n");
|
|
2551
|
+
} catch (err) {
|
|
2552
|
+
process.stderr.write(
|
|
2553
|
+
`Warning: could not write the seed marker beside ${outPath} (${err instanceof Error ? err.message : String(err)}) \u2014 the seeded draft will count as agent-written
|
|
2554
|
+
`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2522
2558
|
const writeEmptyScaffold = () => {
|
|
2523
2559
|
try {
|
|
2524
2560
|
writeFileSync(outPath, `${JSON.stringify(noticeFindings(""), null, 2)}
|
|
2525
2561
|
`);
|
|
2562
|
+
writeSeedMarker();
|
|
2526
2563
|
process.stderr.write(
|
|
2527
2564
|
`Seeded ${outPath} with an empty valid scaffold \u2014 no decodable prior findings to build on
|
|
2528
2565
|
`
|
|
@@ -2558,6 +2595,7 @@ var seedDraftCmd = defineCommand({
|
|
|
2558
2595
|
if (!validateAgainstSchema(priorFindings, schemaPath).valid) return false;
|
|
2559
2596
|
writeFileSync(outPath, `${JSON.stringify(priorFindings, null, 2)}
|
|
2560
2597
|
`);
|
|
2598
|
+
writeSeedMarker();
|
|
2561
2599
|
return true;
|
|
2562
2600
|
} catch (err) {
|
|
2563
2601
|
process.stderr.write(
|