@mobrienv/autoloop-harness 0.7.4 → 0.9.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.
Files changed (154) hide show
  1. package/dist/acceptance.d.ts +31 -0
  2. package/dist/acceptance.js +69 -0
  3. package/dist/acceptance.js.map +1 -0
  4. package/dist/ask.d.ts +16 -0
  5. package/dist/ask.js +47 -0
  6. package/dist/ask.js.map +1 -0
  7. package/dist/backend/types.d.ts +16 -0
  8. package/dist/backend/types.js +17 -0
  9. package/dist/backend/types.js.map +1 -0
  10. package/dist/circuit-breaker.d.ts +27 -0
  11. package/dist/circuit-breaker.js +41 -0
  12. package/dist/circuit-breaker.js.map +1 -0
  13. package/dist/completion-lint.d.ts +29 -0
  14. package/dist/completion-lint.js +51 -0
  15. package/dist/completion-lint.js.map +1 -0
  16. package/dist/config-helpers.d.ts +10 -2
  17. package/dist/config-helpers.js +272 -30
  18. package/dist/config-helpers.js.map +1 -1
  19. package/dist/control/acp-adapter.d.ts +12 -0
  20. package/dist/control/acp-adapter.js +54 -0
  21. package/dist/control/acp-adapter.js.map +1 -0
  22. package/dist/control/adapter.d.ts +12 -0
  23. package/dist/control/adapter.js +2 -0
  24. package/dist/control/adapter.js.map +1 -0
  25. package/dist/control/capabilities.d.ts +9 -0
  26. package/dist/control/capabilities.js +27 -0
  27. package/dist/control/capabilities.js.map +1 -0
  28. package/dist/control/claude-sdk-adapter.d.ts +15 -0
  29. package/dist/control/claude-sdk-adapter.js +79 -0
  30. package/dist/control/claude-sdk-adapter.js.map +1 -0
  31. package/dist/control/command-adapter.d.ts +19 -0
  32. package/dist/control/command-adapter.js +57 -0
  33. package/dist/control/command-adapter.js.map +1 -0
  34. package/dist/control/dispatch.d.ts +14 -0
  35. package/dist/control/dispatch.js +55 -0
  36. package/dist/control/dispatch.js.map +1 -0
  37. package/dist/control/index.d.ts +11 -0
  38. package/dist/control/index.js +9 -0
  39. package/dist/control/index.js.map +1 -0
  40. package/dist/control/kiro-adapter.d.ts +2 -0
  41. package/dist/control/kiro-adapter.js +2 -0
  42. package/dist/control/kiro-adapter.js.map +1 -0
  43. package/dist/control/paths.d.ts +19 -0
  44. package/dist/control/paths.js +40 -0
  45. package/dist/control/paths.js.map +1 -0
  46. package/dist/control/pi-adapter.d.ts +15 -0
  47. package/dist/control/pi-adapter.js +76 -0
  48. package/dist/control/pi-adapter.js.map +1 -0
  49. package/dist/control/queue.d.ts +14 -0
  50. package/dist/control/queue.js +82 -0
  51. package/dist/control/queue.js.map +1 -0
  52. package/dist/control/render.d.ts +10 -0
  53. package/dist/control/render.js +53 -0
  54. package/dist/control/render.js.map +1 -0
  55. package/dist/control/types.d.ts +56 -0
  56. package/dist/control/types.js +9 -0
  57. package/dist/control/types.js.map +1 -0
  58. package/dist/display.d.ts +9 -2
  59. package/dist/display.js +37 -1
  60. package/dist/display.js.map +1 -1
  61. package/dist/emit.d.ts +27 -0
  62. package/dist/emit.js +327 -6
  63. package/dist/emit.js.map +1 -1
  64. package/dist/events.d.ts +36 -3
  65. package/dist/fanout-runner.d.ts +25 -0
  66. package/dist/fanout-runner.js +54 -0
  67. package/dist/fanout-runner.js.map +1 -0
  68. package/dist/file-mod-audit.d.ts +29 -0
  69. package/dist/file-mod-audit.js +63 -0
  70. package/dist/file-mod-audit.js.map +1 -0
  71. package/dist/git-diff.d.ts +19 -0
  72. package/dist/git-diff.js +74 -0
  73. package/dist/git-diff.js.map +1 -0
  74. package/dist/guards.d.ts +37 -0
  75. package/dist/guards.js +78 -0
  76. package/dist/guards.js.map +1 -0
  77. package/dist/hooks.d.ts +78 -0
  78. package/dist/hooks.js +239 -0
  79. package/dist/hooks.js.map +1 -0
  80. package/dist/index.d.ts +15 -2
  81. package/dist/index.js +359 -26
  82. package/dist/index.js.map +1 -1
  83. package/dist/intent.d.ts +45 -0
  84. package/dist/intent.js +127 -0
  85. package/dist/intent.js.map +1 -0
  86. package/dist/iteration-diff.d.ts +32 -0
  87. package/dist/iteration-diff.js +92 -0
  88. package/dist/iteration-diff.js.map +1 -0
  89. package/dist/iteration.d.ts +20 -0
  90. package/dist/iteration.js +551 -30
  91. package/dist/iteration.js.map +1 -1
  92. package/dist/metareview.d.ts +9 -1
  93. package/dist/metareview.js +158 -34
  94. package/dist/metareview.js.map +1 -1
  95. package/dist/notify.d.ts +22 -0
  96. package/dist/notify.js +110 -0
  97. package/dist/notify.js.map +1 -0
  98. package/dist/parallel.d.ts +17 -0
  99. package/dist/parallel.js +38 -6
  100. package/dist/parallel.js.map +1 -1
  101. package/dist/postconditions.d.ts +25 -0
  102. package/dist/postconditions.js +107 -0
  103. package/dist/postconditions.js.map +1 -0
  104. package/dist/postfire-verify.d.ts +31 -0
  105. package/dist/postfire-verify.js +64 -0
  106. package/dist/postfire-verify.js.map +1 -0
  107. package/dist/premature-quit.d.ts +25 -0
  108. package/dist/premature-quit.js +54 -0
  109. package/dist/premature-quit.js.map +1 -0
  110. package/dist/progress.d.ts +20 -0
  111. package/dist/progress.js +56 -0
  112. package/dist/progress.js.map +1 -0
  113. package/dist/prompt.d.ts +4 -0
  114. package/dist/prompt.js +42 -2
  115. package/dist/prompt.js.map +1 -1
  116. package/dist/provisional.d.ts +42 -0
  117. package/dist/provisional.js +92 -0
  118. package/dist/provisional.js.map +1 -0
  119. package/dist/registry-bridge.js +32 -1
  120. package/dist/registry-bridge.js.map +1 -1
  121. package/dist/resume.d.ts +61 -0
  122. package/dist/resume.js +226 -0
  123. package/dist/resume.js.map +1 -0
  124. package/dist/scratchpad.js +27 -8
  125. package/dist/scratchpad.js.map +1 -1
  126. package/dist/stage.d.ts +21 -0
  127. package/dist/stage.js +131 -0
  128. package/dist/stage.js.map +1 -0
  129. package/dist/stop.d.ts +32 -2
  130. package/dist/stop.js +169 -0
  131. package/dist/stop.js.map +1 -1
  132. package/dist/suspend-state.d.ts +32 -0
  133. package/dist/suspend-state.js +104 -0
  134. package/dist/suspend-state.js.map +1 -0
  135. package/dist/tamper.d.ts +21 -0
  136. package/dist/tamper.js +88 -0
  137. package/dist/tamper.js.map +1 -0
  138. package/dist/types.d.ts +294 -3
  139. package/dist/types.js +91 -1
  140. package/dist/types.js.map +1 -1
  141. package/dist/wave/finalize-wave.d.ts +15 -5
  142. package/dist/wave/finalize-wave.js +56 -2
  143. package/dist/wave/finalize-wave.js.map +1 -1
  144. package/dist/wave/launch-branches.d.ts +23 -2
  145. package/dist/wave/launch-branches.js +58 -3
  146. package/dist/wave/launch-branches.js.map +1 -1
  147. package/dist/wave/stage-branch-runner.d.ts +12 -0
  148. package/dist/wave/stage-branch-runner.js +130 -0
  149. package/dist/wave/stage-branch-runner.js.map +1 -0
  150. package/dist/wave/types.d.ts +40 -2
  151. package/dist/wave.d.ts +32 -1
  152. package/dist/wave.js +163 -34
  153. package/dist/wave.js.map +1 -1
  154. package/package.json +131 -3
@@ -0,0 +1,63 @@
1
+ import { jsonField } from "@mobrienv/autoloop-core";
2
+ import { appendEvent } from "@mobrienv/autoloop-core/journal";
3
+ import { changedFiles, isGitRepo } from "./git-diff.js";
4
+ /**
5
+ * Ralph-parity emit-boundary audit (opt-in via `event_loop.audit_file_mods`).
6
+ * Compares the working tree against HEAD after every iteration; if the
7
+ * single acting role (`iter.allowedRoles[0]`, the same convention used for
8
+ * per-role backend overrides) declares `disallowedTools`/`readOnly` and
9
+ * files changed during the iteration, journals + emits a typed
10
+ * `policy.file_modification_violation` event carrying the role and file
11
+ * list. Purely observational: the harness itself never stops or rejects
12
+ * completion on this result — it exists for parent orchestrators to react to.
13
+ *
14
+ * No-ops (returns `{ ran: false, ... }`) when the policy is disabled, and
15
+ * `{ ran: true, violated: false, ... }` outside a git work tree, when no
16
+ * files changed, when the acting role is ambiguous/unknown, or when the
17
+ * acting role has no file-mutation restriction.
18
+ */
19
+ export function runFileModAudit(loop, iter, iteration) {
20
+ if (!loop.policy?.fileModAudit) {
21
+ return { ran: false, violated: false, violations: [] };
22
+ }
23
+ const workDir = loop.paths.workDir;
24
+ if (!isGitRepo(workDir)) {
25
+ return { ran: true, violated: false, violations: [] };
26
+ }
27
+ const files = changedFiles(workDir);
28
+ if (files.length === 0) {
29
+ return { ran: true, violated: false, violations: [] };
30
+ }
31
+ // Autoloop runs a single backend per iteration; allowedRoles[0] is the
32
+ // acting role for this turn (same convention prompt.ts uses to resolve
33
+ // per-role backend overrides). Ambiguous/empty routing is skipped rather
34
+ // than guessed, to avoid false-positive violations.
35
+ if (iter.allowedRoles.length !== 1) {
36
+ return { ran: true, violated: false, violations: [] };
37
+ }
38
+ const roleId = iter.allowedRoles[0];
39
+ const role = loop.topology.roles.find((r) => r.id === roleId);
40
+ if (!role) {
41
+ return { ran: true, violated: false, violations: [] };
42
+ }
43
+ const reason = role.readOnly
44
+ ? "read_only"
45
+ : (role.disallowedTools?.length ?? 0) > 0
46
+ ? "disallowed_tools"
47
+ : null;
48
+ if (!reason) {
49
+ return { ran: true, violated: false, violations: [] };
50
+ }
51
+ const violation = { role: roleId, files, reason };
52
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, String(iteration), "policy.file_modification_violation", `${jsonField("role", roleId)}, ${jsonField("files", files.join(","))}, ${jsonField("reason", reason)}`);
53
+ loop.onEvent?.({
54
+ type: "policy.file_modification_violation",
55
+ runId: loop.runtime.runId,
56
+ iteration,
57
+ role: roleId,
58
+ files,
59
+ reason,
60
+ });
61
+ return { ran: true, violated: true, violations: [violation] };
62
+ }
63
+ //# sourceMappingURL=file-mod-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-mod-audit.js","sourceRoot":"","sources":["../src/file-mod-audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface AddedLine {
2
+ file: string;
3
+ text: string;
4
+ }
5
+ export declare function isGitRepo(workDir: string): boolean;
6
+ /**
7
+ * Lines introduced this run: added (`+`) lines from `git diff HEAD` plus the
8
+ * full contents of untracked, non-ignored files (which a diff against HEAD does
9
+ * not include). This is the surface a reward-hack would land on.
10
+ */
11
+ export declare function collectAddedLines(workDir: string): AddedLine[];
12
+ /**
13
+ * Files changed this run: tracked files differing from HEAD plus untracked,
14
+ * non-ignored files. Used to detect whether a given class of file (e.g. tests)
15
+ * was touched during the run that claims completion.
16
+ */
17
+ export declare function changedFiles(workDir: string): string[];
18
+ /** Dirty working tree entries from `git status --porcelain`. */
19
+ export declare function porcelainStatus(workDir: string): string[];
@@ -0,0 +1,74 @@
1
+ import { spawnSync } from "node:child_process";
2
+ function git(workDir, args) {
3
+ const res = spawnSync("git", args, {
4
+ cwd: workDir,
5
+ encoding: "utf-8",
6
+ maxBuffer: 50 * 1024 * 1024,
7
+ });
8
+ return { ok: res.status === 0, stdout: res.stdout ?? "" };
9
+ }
10
+ export function isGitRepo(workDir) {
11
+ return git(workDir, ["rev-parse", "--is-inside-work-tree"]).ok;
12
+ }
13
+ /**
14
+ * Lines introduced this run: added (`+`) lines from `git diff HEAD` plus the
15
+ * full contents of untracked, non-ignored files (which a diff against HEAD does
16
+ * not include). This is the surface a reward-hack would land on.
17
+ */
18
+ export function collectAddedLines(workDir) {
19
+ const lines = [];
20
+ const diff = git(workDir, ["diff", "--unified=0", "--no-color", "HEAD"]);
21
+ if (diff.ok) {
22
+ let file = "";
23
+ for (const raw of diff.stdout.split("\n")) {
24
+ if (raw.startsWith("+++ b/")) {
25
+ file = raw.slice(6);
26
+ }
27
+ else if (raw.startsWith("+++ ")) {
28
+ file = raw.slice(4);
29
+ }
30
+ else if (raw.startsWith("+") && !raw.startsWith("+++")) {
31
+ lines.push({ file, text: raw.slice(1) });
32
+ }
33
+ }
34
+ }
35
+ for (const file of untrackedFiles(workDir)) {
36
+ const content = spawnSync("cat", [file], {
37
+ cwd: workDir,
38
+ encoding: "utf-8",
39
+ maxBuffer: 20 * 1024 * 1024,
40
+ });
41
+ if (content.status === 0) {
42
+ for (const text of (content.stdout ?? "").split("\n")) {
43
+ lines.push({ file, text });
44
+ }
45
+ }
46
+ }
47
+ return lines;
48
+ }
49
+ function untrackedFiles(workDir) {
50
+ const res = git(workDir, ["ls-files", "--others", "--exclude-standard"]);
51
+ return res.ok ? res.stdout.split("\n").filter(Boolean) : [];
52
+ }
53
+ /**
54
+ * Files changed this run: tracked files differing from HEAD plus untracked,
55
+ * non-ignored files. Used to detect whether a given class of file (e.g. tests)
56
+ * was touched during the run that claims completion.
57
+ */
58
+ export function changedFiles(workDir) {
59
+ const tracked = git(workDir, ["diff", "--name-only", "--no-color", "HEAD"]);
60
+ const files = new Set();
61
+ if (tracked.ok) {
62
+ for (const f of tracked.stdout.split("\n").filter(Boolean))
63
+ files.add(f);
64
+ }
65
+ for (const f of untrackedFiles(workDir))
66
+ files.add(f);
67
+ return [...files];
68
+ }
69
+ /** Dirty working tree entries from `git status --porcelain`. */
70
+ export function porcelainStatus(workDir) {
71
+ const res = git(workDir, ["status", "--porcelain"]);
72
+ return res.ok ? res.stdout.split("\n").filter(Boolean) : [];
73
+ }
74
+ //# sourceMappingURL=git-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../src/git-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ export interface StallCheck {
2
+ stalled: boolean;
3
+ repeats: number;
4
+ }
5
+ /**
6
+ * Detect a no-progress loop: `threshold` (or more) consecutive iterations
7
+ * whose backend output is byte-identical. A threshold of 0 disables the
8
+ * check. Empty outputs never count as a stall — backend failures and
9
+ * timeouts already have their own stop reasons.
10
+ */
11
+ export declare function detectStall(runLines: string[], threshold: number): StallCheck;
12
+ /**
13
+ * Epoch-ms start of the run, from the `created_at` field of the run's
14
+ * `loop.start` event. Null when absent or unparseable, in which case the
15
+ * runtime budget guard disables itself.
16
+ */
17
+ export declare function loopStartMs(runLines: string[]): number | null;
18
+ export interface RuntimeBudgetCheck {
19
+ exceeded: boolean;
20
+ elapsedMs: number;
21
+ }
22
+ /**
23
+ * Check elapsed wall-clock time (from the journaled `loop.start` event)
24
+ * against a runtime budget. A budget of 0 disables the check; so does a
25
+ * missing or unparseable start timestamp.
26
+ */
27
+ export declare function checkRuntimeBudget(runLines: string[], maxRuntimeMs: number, nowMs?: number): RuntimeBudgetCheck;
28
+ export interface CostBudgetCheck {
29
+ exceeded: boolean;
30
+ costUsd: number;
31
+ }
32
+ /**
33
+ * Check accumulated run cost (from `backend.usage` events) against a USD
34
+ * budget. A budget of 0 disables the check. Backends without usage telemetry
35
+ * report zero cost, so the budget never fires for them.
36
+ */
37
+ export declare function checkCostBudget(runLines: string[], maxCostUsd: number): CostBudgetCheck;
package/dist/guards.js ADDED
@@ -0,0 +1,78 @@
1
+ // Run-level guard checks evaluated between iterations.
2
+ //
3
+ // Both guards are journal-derived (no in-memory counters), so they survive
4
+ // context reloads and apply uniformly to every continue path — routed events,
5
+ // rejected emits, and plain continues alike.
6
+ import { collectUsage, decodeEvent } from "@mobrienv/autoloop-core";
7
+ /**
8
+ * Detect a no-progress loop: `threshold` (or more) consecutive iterations
9
+ * whose backend output is byte-identical. A threshold of 0 disables the
10
+ * check. Empty outputs never count as a stall — backend failures and
11
+ * timeouts already have their own stop reasons.
12
+ */
13
+ export function detectStall(runLines, threshold) {
14
+ if (threshold <= 0)
15
+ return { stalled: false, repeats: 0 };
16
+ const outputs = [];
17
+ for (const line of runLines) {
18
+ const event = decodeEvent(line);
19
+ if (!event || event.shape !== "fields")
20
+ continue;
21
+ if (event.topic !== "iteration.finish")
22
+ continue;
23
+ outputs.push((event.fields.output ?? "").trim());
24
+ }
25
+ if (outputs.length === 0)
26
+ return { stalled: false, repeats: 0 };
27
+ const last = outputs[outputs.length - 1];
28
+ if (last === "")
29
+ return { stalled: false, repeats: 0 };
30
+ let repeats = 1;
31
+ for (let i = outputs.length - 2; i >= 0 && outputs[i] === last; i--) {
32
+ repeats++;
33
+ }
34
+ return { stalled: repeats >= threshold, repeats };
35
+ }
36
+ /**
37
+ * Epoch-ms start of the run, from the `created_at` field of the run's
38
+ * `loop.start` event. Null when absent or unparseable, in which case the
39
+ * runtime budget guard disables itself.
40
+ */
41
+ export function loopStartMs(runLines) {
42
+ for (const line of runLines) {
43
+ const event = decodeEvent(line);
44
+ if (!event || event.shape !== "fields")
45
+ continue;
46
+ if (event.topic !== "loop.start")
47
+ continue;
48
+ const parsed = Date.parse(event.fields.created_at ?? "");
49
+ return Number.isNaN(parsed) ? null : parsed;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Check elapsed wall-clock time (from the journaled `loop.start` event)
55
+ * against a runtime budget. A budget of 0 disables the check; so does a
56
+ * missing or unparseable start timestamp.
57
+ */
58
+ export function checkRuntimeBudget(runLines, maxRuntimeMs, nowMs = Date.now()) {
59
+ if (maxRuntimeMs <= 0)
60
+ return { exceeded: false, elapsedMs: 0 };
61
+ const startMs = loopStartMs(runLines);
62
+ if (startMs === null)
63
+ return { exceeded: false, elapsedMs: 0 };
64
+ const elapsedMs = nowMs - startMs;
65
+ return { exceeded: elapsedMs >= maxRuntimeMs, elapsedMs };
66
+ }
67
+ /**
68
+ * Check accumulated run cost (from `backend.usage` events) against a USD
69
+ * budget. A budget of 0 disables the check. Backends without usage telemetry
70
+ * report zero cost, so the budget never fires for them.
71
+ */
72
+ export function checkCostBudget(runLines, maxCostUsd) {
73
+ if (maxCostUsd <= 0)
74
+ return { exceeded: false, costUsd: 0 };
75
+ const { totals } = collectUsage(runLines);
76
+ return { exceeded: totals.costUsd >= maxCostUsd, costUsd: totals.costUsd };
77
+ }
78
+ //# sourceMappingURL=guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { HookPhase } from "@mobrienv/autoloop-core/hooks-schema";
2
+ import type { LoopContext, StopReason } from "./types.js";
3
+ export interface HookEnv {
4
+ AUTOLOOP_PROJECT_DIR: string;
5
+ AUTOLOOP_RUN_ID: string;
6
+ AUTOLOOP_PRESET: string;
7
+ AUTOLOOP_TASKS_FILE: string;
8
+ AUTOLOOP_ITERATION?: string;
9
+ AUTOLOOP_GIT_SHA_BEFORE?: string;
10
+ AUTOLOOP_GIT_SHA_AFTER?: string;
11
+ /** Run stop reason, set on post_run (e.g. "completed" = success). */
12
+ AUTOLOOP_STOP_REASON?: string;
13
+ }
14
+ export declare function buildHookEnv(loop: LoopContext, extra?: {
15
+ iteration?: number;
16
+ gitShaBefore?: string;
17
+ gitShaAfter?: string;
18
+ stopReason?: StopReason;
19
+ }): HookEnv;
20
+ export declare function captureGitSha(cwd: string): string;
21
+ /**
22
+ * Legacy single-hook entry point (`runHook`). Kept for backward compatibility
23
+ * with any external callers/tests; internally the engine runs hooks through
24
+ * `runPhaseHooks` which layers policy + mutation on top of this same spawn +
25
+ * journal behavior.
26
+ */
27
+ export declare function runHook(loop: LoopContext, name: string, cmd: string, hookEnv: HookEnv, iteration?: string): void;
28
+ /**
29
+ * Parse a mutation directive from hook stdout, given which kind of mutation
30
+ * this hook is configured for:
31
+ *
32
+ * - `mutate="prompt"`: a fenced block (`<<<AUTOLOOP_MUTATE>>> ... <<<END>>>`)
33
+ * or a `{"prompt": "..."}` JSON line take precedence; otherwise the *entire*
34
+ * trimmed stdout becomes the new prompt (so a hook can be as simple as
35
+ * `echo "new prompt text"`).
36
+ * - `mutate="event"`: requires a JSON object with `topic` and/or `payload`
37
+ * (there is no plain-text form — an emitted event is structured, not free
38
+ * text).
39
+ *
40
+ * Empty/malformed stdout is not an error — it simply yields no mutation
41
+ * (returns null), since a hook may run for its side effects alone.
42
+ */
43
+ export declare function parseMutationDirective(stdout: string, mutate: "prompt" | "event"): {
44
+ prompt?: string;
45
+ topic?: string;
46
+ payload?: string;
47
+ } | null;
48
+ export interface PhaseHookResult {
49
+ /** Mutated prompt, when a `mutate="prompt"` hook produced one (pre_iteration). */
50
+ mutatedPrompt?: string;
51
+ /** Mutated event, when a `mutate="event"` hook produced one (pre_emit). */
52
+ mutatedEvent?: {
53
+ topic?: string;
54
+ payload?: string;
55
+ };
56
+ /** True when a `block` hook failed — caller must abort/fail. */
57
+ blocked: boolean;
58
+ /** Detail message for the first blocking failure, if any. */
59
+ blockedMessage?: string;
60
+ /** True when a `suspend` hook fired and the run should stop (out-of-process
61
+ * phases can't block; the caller is responsible for tearing down). */
62
+ suspended: boolean;
63
+ }
64
+ export interface RunPhaseHooksOptions {
65
+ iteration?: number;
66
+ gitShaBefore?: string;
67
+ gitShaAfter?: string;
68
+ stopReason?: string;
69
+ }
70
+ /**
71
+ * Run every hook configured for `phase`, in declaration order, applying each
72
+ * hook's error policy and (if configured) parsing a mutation directive from
73
+ * its stdout. Later hooks in the same phase see the not-yet-applied original
74
+ * inputs — mutation from multiple hooks in one phase is last-write-wins
75
+ * (whichever hook mutates last), which keeps the model simple and matches
76
+ * the common case of a single hook per phase.
77
+ */
78
+ export declare function runPhaseHooks(loop: LoopContext, phase: HookPhase, hookEnv: HookEnv, opts?: RunPhaseHooksOptions): Promise<PhaseHookResult>;
package/dist/hooks.js ADDED
@@ -0,0 +1,239 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { appendEvent } from "@mobrienv/autoloop-core/journal";
3
+ import { log, printHookOutput } from "./display.js";
4
+ import { writeSuspendState } from "./suspend-state.js";
5
+ export function buildHookEnv(loop, extra) {
6
+ const env = {
7
+ // The work dir is the repo being worked (where .autoloop/issue-sync.toml lives);
8
+ // projectDir can be the preset directory, which is not what hooks want.
9
+ AUTOLOOP_PROJECT_DIR: loop.paths.workDir,
10
+ AUTOLOOP_RUN_ID: loop.runtime.runId,
11
+ AUTOLOOP_PRESET: loop.launch.preset,
12
+ AUTOLOOP_TASKS_FILE: loop.paths.tasksFile,
13
+ };
14
+ if (extra?.iteration !== undefined) {
15
+ env.AUTOLOOP_ITERATION = String(extra.iteration);
16
+ }
17
+ if (extra?.gitShaBefore !== undefined) {
18
+ env.AUTOLOOP_GIT_SHA_BEFORE = extra.gitShaBefore;
19
+ }
20
+ if (extra?.gitShaAfter !== undefined) {
21
+ env.AUTOLOOP_GIT_SHA_AFTER = extra.gitShaAfter;
22
+ }
23
+ if (extra?.stopReason !== undefined) {
24
+ env.AUTOLOOP_STOP_REASON = extra.stopReason;
25
+ }
26
+ return env;
27
+ }
28
+ export function captureGitSha(cwd) {
29
+ try {
30
+ const result = spawnSync("git", ["rev-parse", "HEAD"], {
31
+ cwd,
32
+ encoding: "utf-8",
33
+ });
34
+ if (result.status === 0 && result.stdout) {
35
+ return result.stdout.trim();
36
+ }
37
+ }
38
+ catch {
39
+ /* not a git repo or git not available — return empty */
40
+ }
41
+ return "";
42
+ }
43
+ function spawnHook(loop, name, cmd, hookEnv) {
44
+ const env = { ...process.env };
45
+ for (const [k, v] of Object.entries(hookEnv)) {
46
+ if (v !== undefined)
47
+ env[k] = v;
48
+ }
49
+ const result = spawnSync(cmd, {
50
+ shell: true,
51
+ cwd: loop.paths.workDir,
52
+ encoding: "utf-8",
53
+ env,
54
+ });
55
+ const combined = (result.stdout ?? "") +
56
+ (result.stderr ? `\n[stderr]\n${result.stderr}` : "");
57
+ const failed = result.status !== 0 || Boolean(result.error);
58
+ if (failed && result.error) {
59
+ log(loop, "debug", `hook ${name} spawn error: ${result.error.message}`);
60
+ }
61
+ return { status: result.status ?? -1, combined, failed };
62
+ }
63
+ /**
64
+ * Legacy single-hook entry point (`runHook`). Kept for backward compatibility
65
+ * with any external callers/tests; internally the engine runs hooks through
66
+ * `runPhaseHooks` which layers policy + mutation on top of this same spawn +
67
+ * journal behavior.
68
+ */
69
+ export function runHook(loop, name, cmd, hookEnv, iteration) {
70
+ if (!cmd)
71
+ return;
72
+ log(loop, "debug", `hook ${name} start cmd=${JSON.stringify(cmd)}`);
73
+ const { status, combined, failed } = spawnHook(loop, name, cmd, hookEnv);
74
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, iteration ?? "", "hook.output", `"hook": ${JSON.stringify(name)}, "exit_code": ${status}, "output": ${JSON.stringify(combined.trim())}`);
75
+ printHookOutput(name, status, combined.trim(), failed);
76
+ if (failed) {
77
+ const msg = firstFailureLine(name, status, combined);
78
+ log(loop, "warn", msg);
79
+ if (loop.hooks.strict && name === "pre_run") {
80
+ throw new Error(`Aborting run: ${msg}`);
81
+ }
82
+ }
83
+ else {
84
+ log(loop, "debug", `hook ${name} ok`);
85
+ }
86
+ }
87
+ function firstFailureLine(name, status, combined) {
88
+ const stderrIdx = combined.indexOf("\n[stderr]\n");
89
+ const detail = (stderrIdx >= 0 ? combined.slice(stderrIdx + 10) : "").trim() ||
90
+ (stderrIdx >= 0 ? combined.slice(0, stderrIdx) : combined).trim();
91
+ const firstLine = detail
92
+ .split("\n")
93
+ .map((l) => l.trim())
94
+ .find((l) => l.length > 0) ?? "";
95
+ return `hook ${name} failed (exit ${status}): ${firstLine}`;
96
+ }
97
+ // --- Mutation directive parsing --------------------------------------------
98
+ const MUTATE_FENCE_RE = /<<<AUTOLOOP_MUTATE>>>\n?([\s\S]*?)\n?<<<END>>>/;
99
+ /**
100
+ * Parse a mutation directive from hook stdout, given which kind of mutation
101
+ * this hook is configured for:
102
+ *
103
+ * - `mutate="prompt"`: a fenced block (`<<<AUTOLOOP_MUTATE>>> ... <<<END>>>`)
104
+ * or a `{"prompt": "..."}` JSON line take precedence; otherwise the *entire*
105
+ * trimmed stdout becomes the new prompt (so a hook can be as simple as
106
+ * `echo "new prompt text"`).
107
+ * - `mutate="event"`: requires a JSON object with `topic` and/or `payload`
108
+ * (there is no plain-text form — an emitted event is structured, not free
109
+ * text).
110
+ *
111
+ * Empty/malformed stdout is not an error — it simply yields no mutation
112
+ * (returns null), since a hook may run for its side effects alone.
113
+ */
114
+ export function parseMutationDirective(stdout, mutate) {
115
+ if (!stdout?.trim())
116
+ return null;
117
+ const trimmed = stdout.trim();
118
+ const fenced = MUTATE_FENCE_RE.exec(stdout);
119
+ if (fenced && mutate === "prompt") {
120
+ return { prompt: fenced[1] };
121
+ }
122
+ if (trimmed.startsWith("{")) {
123
+ try {
124
+ const obj = JSON.parse(trimmed);
125
+ const out = {};
126
+ if (typeof obj.prompt === "string")
127
+ out.prompt = obj.prompt;
128
+ if (typeof obj.topic === "string")
129
+ out.topic = obj.topic;
130
+ if (typeof obj.payload === "string")
131
+ out.payload = obj.payload;
132
+ if (Object.keys(out).length > 0)
133
+ return out;
134
+ }
135
+ catch {
136
+ /* not JSON — fall through to the plain-text prompt case below */
137
+ }
138
+ }
139
+ if (mutate === "prompt")
140
+ return { prompt: trimmed };
141
+ return null;
142
+ }
143
+ /**
144
+ * Run every hook configured for `phase`, in declaration order, applying each
145
+ * hook's error policy and (if configured) parsing a mutation directive from
146
+ * its stdout. Later hooks in the same phase see the not-yet-applied original
147
+ * inputs — mutation from multiple hooks in one phase is last-write-wins
148
+ * (whichever hook mutates last), which keeps the model simple and matches
149
+ * the common case of a single hook per phase.
150
+ */
151
+ export async function runPhaseHooks(loop, phase, hookEnv, opts = {}) {
152
+ // Defensive default: hand-built test LoopContexts (and any older caller
153
+ // that predates `hooks.specs`) may omit it entirely.
154
+ const specs = (loop.hooks.specs ?? []).filter((s) => s.phase === phase);
155
+ const result = { blocked: false, suspended: false };
156
+ const iterationStr = opts.iteration !== undefined ? String(opts.iteration) : "";
157
+ for (const spec of specs) {
158
+ if (!spec.command)
159
+ continue;
160
+ log(loop, "debug", `hook ${phase} start cmd=${JSON.stringify(spec.command)} on_error=${spec.onError} mutate=${spec.mutate}`);
161
+ const { status, combined, failed } = spawnHook(loop, phase, spec.command, hookEnv);
162
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, iterationStr, "hook.output", `"hook": ${JSON.stringify(phase)}, "exit_code": ${status}, "output": ${JSON.stringify(combined.trim())}`);
163
+ printHookOutput(phase, status, combined.trim(), failed);
164
+ if (!failed && spec.mutate !== "none") {
165
+ const stdoutOnly = combined.split("\n[stderr]\n")[0];
166
+ const directive = parseMutationDirective(stdoutOnly, spec.mutate);
167
+ if (directive) {
168
+ if (spec.mutate === "prompt" && directive.prompt !== undefined) {
169
+ result.mutatedPrompt = directive.prompt;
170
+ }
171
+ else if (spec.mutate === "event") {
172
+ result.mutatedEvent = {
173
+ topic: directive.topic,
174
+ payload: directive.payload,
175
+ };
176
+ }
177
+ }
178
+ }
179
+ if (!failed) {
180
+ log(loop, "debug", `hook ${phase} ok`);
181
+ continue;
182
+ }
183
+ const msg = firstFailureLine(phase, status, combined);
184
+ switch (spec.onError) {
185
+ case "warn":
186
+ log(loop, "warn", msg);
187
+ break;
188
+ case "block":
189
+ log(loop, "error", `hook ${phase} blocked run: ${msg}`);
190
+ result.blocked = true;
191
+ result.blockedMessage = msg;
192
+ return result;
193
+ case "suspend": {
194
+ // Durable suspend: write versioned state and stop this call site
195
+ // immediately. The run process ends (see stopSuspended in the
196
+ // call sites); a later `autoloop resume` (a fresh process) reads
197
+ // suspend-state.json and continues from `resumeIteration`. This
198
+ // deliberately does NOT block in-process — emit's pre_emit/post_emit
199
+ // hooks run in a disposable subprocess and could never block the
200
+ // harness loop anyway, so in-process phases use the same simple,
201
+ // uniform "stop and let resume pick it back up" contract.
202
+ log(loop, "warn", `hook ${phase} suspended run: ${msg}`);
203
+ const resumeIteration = suspendResumeIteration(phase, opts.iteration);
204
+ writeSuspendState(loop.paths.stateDir, {
205
+ runId: loop.runtime.runId,
206
+ phase,
207
+ iteration: opts.iteration ?? 0,
208
+ reason: msg,
209
+ hookCommand: spec.command,
210
+ createdAt: new Date().toISOString(),
211
+ resumeIteration,
212
+ }, loop.paths.journalFile);
213
+ result.suspended = true;
214
+ result.blockedMessage = msg;
215
+ return result;
216
+ }
217
+ }
218
+ }
219
+ return result;
220
+ }
221
+ function suspendResumeIteration(phase, iteration) {
222
+ const iter = iteration ?? 0;
223
+ switch (phase) {
224
+ case "pre_run":
225
+ return 1;
226
+ case "pre_iteration":
227
+ return iter;
228
+ case "post_iteration":
229
+ case "post_emit":
230
+ return iter + 1;
231
+ case "pre_emit":
232
+ return iter;
233
+ case "post_run":
234
+ return iter + 1;
235
+ default:
236
+ return iter;
237
+ }
238
+ }
239
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,19 @@
1
+ import type { LiveControlAdapter } from "./control/adapter.js";
1
2
  import { emit as emitCmd } from "./emit.js";
2
- import type { LoopContext, RunOptions, RunSummary } from "./types.js";
3
- export type { LoopContext, RunOptions, RunSummary };
3
+ import type { LoopContext, RunOptions, RunSummary, StopReason } from "./types.js";
4
+ export { STOP_REASONS } from "./types.js";
5
+ export type { LoopContext, RunOptions, RunSummary, StopReason };
4
6
  export declare function run(projectDir: string, promptOverride: string | null, selfCommand: string, runOptions: RunOptions): Promise<RunSummary>;
7
+ /**
8
+ * Drive the iteration loop for an already-built, already-registered
9
+ * LoopContext, starting at `startIteration`. Owns abort/teardown wiring,
10
+ * the tracked-iterate recursion, session cleanup, post-run worktree
11
+ * lifecycle, and finish notifications. Shared by `run()` and `resume()` so
12
+ * both paths exercise identical signal handlers, stop handlers, and
13
+ * registry/journal behavior.
14
+ */
15
+ export declare function driveLoop(loop: LoopContext, runOptions: RunOptions, startIteration: number): Promise<RunSummary>;
16
+ export { buildResumeContext, determineResumeIteration, type ResumeOptions, type ResumeResult, resume, } from "./resume.js";
5
17
  export { emitCmd as emit };
6
18
  export declare function runParallelBranchCli(projectDir: string, branchDir: string, selfCommand: string, onEvent?: (event: import("./events.js").LoopEvent) => void): Promise<void>;
19
+ export declare function buildControlAdapter(loop: LoopContext): LiveControlAdapter | undefined;