@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
package/dist/intent.js ADDED
@@ -0,0 +1,127 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { jsonBool, jsonField, jsonFieldRaw } from "@mobrienv/autoloop-core";
3
+ import { appendEvent, appendOperatorEvent, } from "@mobrienv/autoloop-core/journal";
4
+ const CHECK_SEP = " :: ";
5
+ /** Parse one criterion line into text + optional bound check. */
6
+ export function parseCriterion(line) {
7
+ const idx = line.indexOf(CHECK_SEP);
8
+ if (idx === -1)
9
+ return { text: line.trim() };
10
+ return {
11
+ text: line.slice(0, idx).trim(),
12
+ check: line.slice(idx + CHECK_SEP.length).trim() || undefined,
13
+ };
14
+ }
15
+ /**
16
+ * Extract acceptance criteria from an objective's "Acceptance criteria" (or
17
+ * "Acceptance Criteria") section: the bullet/numbered lines following the
18
+ * heading, up to the next blank line or heading. Returns [] when absent.
19
+ */
20
+ export function parseObjectiveCriteria(objective) {
21
+ const lines = objective.split("\n");
22
+ const out = [];
23
+ let inSection = false;
24
+ for (const raw of lines) {
25
+ const line = raw.trim();
26
+ if (/^#{0,6}\s*acceptance\s+criteria\b/i.test(line.replace(/[:*_]/g, ""))) {
27
+ inSection = true;
28
+ continue;
29
+ }
30
+ if (!inSection)
31
+ continue;
32
+ if (line === "")
33
+ break;
34
+ // A new markdown heading ends the section.
35
+ if (/^#{1,6}\s/.test(line))
36
+ break;
37
+ const bullet = line.match(/^(?:[-*+]|\d+[.)])\s+(.*)$/);
38
+ if (bullet)
39
+ out.push(bullet[1].trim());
40
+ else
41
+ break; // non-bullet line ends a bullet list
42
+ }
43
+ return out;
44
+ }
45
+ /**
46
+ * Capture the intent-binding contract at loop start: config criteria first,
47
+ * then any parsed from the objective (deduped by text). This is the bound
48
+ * record of "what was asked" the deterministic gate keys off.
49
+ */
50
+ export function captureAcceptanceContract(loop) {
51
+ const raw = [
52
+ ...(loop.acceptance.criteria ?? []),
53
+ ...parseObjectiveCriteria(loop.objective ?? ""),
54
+ ];
55
+ const seen = new Set();
56
+ const criteria = [];
57
+ for (const line of raw) {
58
+ const c = parseCriterion(line);
59
+ if (!c.text || seen.has(c.text))
60
+ continue;
61
+ seen.add(c.text);
62
+ criteria.push(c);
63
+ }
64
+ return criteria;
65
+ }
66
+ /** Journal the bound acceptance contract once at loop start (audit/durability). */
67
+ export function journalAcceptanceContract(loop) {
68
+ const criteria = captureAcceptanceContract(loop);
69
+ if (criteria.length === 0)
70
+ return;
71
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, "", "acceptance.contract", jsonField("count", String(criteria.length)) +
72
+ ", " +
73
+ jsonField("criteria", criteria.map((c) => c.text).join(" | ")) +
74
+ ", " +
75
+ jsonFieldRaw("checked", String(criteria.filter((c) => c.check).length)));
76
+ }
77
+ /**
78
+ * Evaluate the intent-binding criteria at the acceptance gate. Criteria that
79
+ * bind a deterministic check (`text :: cmd`) are run HARNESS-side in the work
80
+ * dir; a non-zero exit fails acceptance so a run that passes its tests but does
81
+ * not satisfy the stated intent is still rejected. Criteria without a check are
82
+ * advisory (recorded in the contract, not a hard blocker).
83
+ */
84
+ export function runIntentCriteria(loop, iteration) {
85
+ const checked = captureAcceptanceContract(loop).filter((c) => c.check);
86
+ if (checked.length === 0)
87
+ return { ran: false, passed: true, failures: [] };
88
+ const iter = String(iteration);
89
+ const failures = [];
90
+ for (const c of checked) {
91
+ const check = c.check;
92
+ const res = spawnSync(check, {
93
+ shell: "/bin/sh",
94
+ cwd: loop.paths.workDir,
95
+ encoding: "utf-8",
96
+ timeout: loop.acceptance.timeoutMs,
97
+ maxBuffer: 50 * 1024 * 1024,
98
+ });
99
+ const timedOut = res.signal === "SIGTERM" ||
100
+ res.error?.code === "ETIMEDOUT";
101
+ const exitCode = timedOut ? 124 : (res.status ?? 1);
102
+ const tail = `${res.stdout ?? ""}${res.stderr ?? ""}`.slice(-1000);
103
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, iter, "acceptance.criterion", jsonField("text", c.text) +
104
+ ", " +
105
+ jsonField("check", check) +
106
+ ", " +
107
+ jsonField("exit_code", String(exitCode)));
108
+ if (exitCode !== 0)
109
+ failures.push({ text: c.text, check, exitCode, tail });
110
+ }
111
+ const passed = failures.length === 0;
112
+ appendEvent(loop.paths.journalFile, loop.runtime.runId, iter, "acceptance.intent_result", jsonFieldRaw("passed", jsonBool(passed)) +
113
+ ", " +
114
+ jsonField("failed", String(failures.length)));
115
+ return { ran: true, passed, failures };
116
+ }
117
+ /** Re-inject failed intent criteria as operator guidance. */
118
+ export function reinjectIntentFailure(loop, iteration, result) {
119
+ const detail = result.failures
120
+ .map((f) => `- "${f.text}" (check \`${f.check}\` exited ${f.exitCode}):\n${f.tail}`)
121
+ .join("\n\n");
122
+ const message = "Completion was blocked: the build does not satisfy the stated acceptance " +
123
+ "criteria (intent-binding), even if tests pass. Satisfy these before " +
124
+ `claiming completion:\n\n${detail}`;
125
+ appendOperatorEvent(loop.paths.journalFile, loop.runtime.runId, String(iteration), "operator.guidance", message);
126
+ }
127
+ //# sourceMappingURL=intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent.js","sourceRoot":"","sources":["../src/intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface TextDelta {
2
+ aChars: number;
3
+ bChars: number;
4
+ charDelta: number;
5
+ linesAdded: number;
6
+ linesRemoved: number;
7
+ changed: boolean;
8
+ }
9
+ export interface IterationDiff {
10
+ iterA: number;
11
+ iterB: number;
12
+ prompt: TextDelta;
13
+ output: TextDelta;
14
+ cost: {
15
+ aUsd: number;
16
+ bUsd: number;
17
+ deltaUsd: number;
18
+ };
19
+ /** Event topics emitted in one iteration but not the other (structural). */
20
+ events: {
21
+ onlyInA: string[];
22
+ onlyInB: string[];
23
+ };
24
+ }
25
+ /**
26
+ * Structural diff between two journaled iterations of a run: prompt (assembled
27
+ * context), backend output, cost, and the emitted-event topic set. Pure over
28
+ * the run's journal lines — the durable journal is the trustworthy source.
29
+ */
30
+ export declare function diffIterations(runLines: string[], iterA: number, iterB: number): IterationDiff;
31
+ /** Render a structural iteration diff as a human-readable report. */
32
+ export declare function renderIterationDiff(runId: string, d: IterationDiff): string;
@@ -0,0 +1,92 @@
1
+ import { extractField, extractIteration, extractTopic, } from "@mobrienv/autoloop-core/journal";
2
+ function linesOf(runLines, iter) {
3
+ const want = String(iter);
4
+ return runLines.filter((l) => extractIteration(l) === want);
5
+ }
6
+ function fieldFor(lines, topic, field) {
7
+ // Last matching line wins (the final value journaled for that iteration).
8
+ for (let i = lines.length - 1; i >= 0; i--) {
9
+ if (extractTopic(lines[i]) === topic)
10
+ return extractField(lines[i], field);
11
+ }
12
+ return "";
13
+ }
14
+ function topicsOf(lines) {
15
+ return new Set(lines.map((l) => extractTopic(l)).filter(Boolean));
16
+ }
17
+ function costOf(lines) {
18
+ let sum = 0;
19
+ for (const l of lines) {
20
+ if (extractTopic(l) !== "backend.usage")
21
+ continue;
22
+ const v = Number(extractField(l, "cost_usd"));
23
+ if (Number.isFinite(v))
24
+ sum += v;
25
+ }
26
+ return Math.round(sum * 1e6) / 1e6;
27
+ }
28
+ /** Multiset line diff: counts of lines present in b-not-a (added) / a-not-b (removed). */
29
+ function textDelta(a, b) {
30
+ const aLines = a ? a.split("\n") : [];
31
+ const bLines = b ? b.split("\n") : [];
32
+ const count = (xs) => {
33
+ const m = new Map();
34
+ for (const x of xs)
35
+ m.set(x, (m.get(x) ?? 0) + 1);
36
+ return m;
37
+ };
38
+ const am = count(aLines);
39
+ const bm = count(bLines);
40
+ let added = 0;
41
+ let removed = 0;
42
+ for (const [line, n] of bm)
43
+ added += Math.max(0, n - (am.get(line) ?? 0));
44
+ for (const [line, n] of am)
45
+ removed += Math.max(0, n - (bm.get(line) ?? 0));
46
+ return {
47
+ aChars: a.length,
48
+ bChars: b.length,
49
+ charDelta: b.length - a.length,
50
+ linesAdded: added,
51
+ linesRemoved: removed,
52
+ changed: a !== b,
53
+ };
54
+ }
55
+ /**
56
+ * Structural diff between two journaled iterations of a run: prompt (assembled
57
+ * context), backend output, cost, and the emitted-event topic set. Pure over
58
+ * the run's journal lines — the durable journal is the trustworthy source.
59
+ */
60
+ export function diffIterations(runLines, iterA, iterB) {
61
+ const a = linesOf(runLines, iterA);
62
+ const b = linesOf(runLines, iterB);
63
+ const aTopics = topicsOf(a);
64
+ const bTopics = topicsOf(b);
65
+ const aUsd = costOf(a);
66
+ const bUsd = costOf(b);
67
+ return {
68
+ iterA,
69
+ iterB,
70
+ prompt: textDelta(fieldFor(a, "iteration.start", "prompt"), fieldFor(b, "iteration.start", "prompt")),
71
+ output: textDelta(fieldFor(a, "iteration.finish", "output"), fieldFor(b, "iteration.finish", "output")),
72
+ cost: { aUsd, bUsd, deltaUsd: Math.round((bUsd - aUsd) * 1e6) / 1e6 },
73
+ events: {
74
+ onlyInA: [...aTopics].filter((t) => !bTopics.has(t)).sort(),
75
+ onlyInB: [...bTopics].filter((t) => !aTopics.has(t)).sort(),
76
+ },
77
+ };
78
+ }
79
+ /** Render a structural iteration diff as a human-readable report. */
80
+ export function renderIterationDiff(runId, d) {
81
+ const t = (label, x) => `${label}: ${x.changed ? "changed" : "identical"} (${x.aChars}→${x.bChars} chars, Δ${x.charDelta >= 0 ? "+" : ""}${x.charDelta}; +${x.linesAdded}/-${x.linesRemoved} lines)`;
82
+ return [
83
+ `## Diff ${runId}: iter ${d.iterA} → ${d.iterB}`,
84
+ "",
85
+ t("prompt/context", d.prompt),
86
+ t("output", d.output),
87
+ `cost: $${d.cost.aUsd} → $${d.cost.bUsd} (Δ${d.cost.deltaUsd >= 0 ? "+" : ""}${d.cost.deltaUsd})`,
88
+ `events only in ${d.iterA}: ${d.events.onlyInA.join(", ") || "(none)"}`,
89
+ `events only in ${d.iterB}: ${d.events.onlyInB.join(", ") || "(none)"}`,
90
+ ].join("\n");
91
+ }
92
+ //# sourceMappingURL=iteration-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iteration-diff.js","sourceRoot":"","sources":["../src/iteration-diff.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,27 @@ export declare function resolveOutcome(ctx: {
10
10
  completionEvent: string;
11
11
  requiredEvents: string[];
12
12
  completionPromise: string;
13
+ hasBlockingTasks: boolean;
14
+ /**
15
+ * Ralph-parity ordering policy. When true, a completion claim is rejected
16
+ * (falls through to the routed/promise/continue branches) unless
17
+ * `completionOrderOk` confirms no other event followed completionEvent in
18
+ * its own turn. Optional/false = order-insensitive set-membership check
19
+ * (unchanged default behavior).
20
+ */
21
+ mustBeLast?: boolean;
22
+ /** Precomputed by the caller from full journal ordering (see completionEmittedLast). */
23
+ completionOrderOk?: boolean;
13
24
  }): {
14
25
  action: string;
15
26
  outcome: string;
16
27
  };
28
+ /**
29
+ * True iff `completionEvent`, wherever it was last emitted in the run, was
30
+ * the last non-system topic emitted in that same turn (iteration). Used to
31
+ * enforce `completion.must_be_last`: ralph rejects completion if other
32
+ * events follow it within the same turn, rather than autoloop's default
33
+ * order-insensitive set-membership check. Returns true (not applicable) if
34
+ * the completion event has not been emitted at all.
35
+ */
36
+ export declare function completionEmittedLast(runLines: string[], completionEvent: string): boolean;