@bluewombat/implementer 0.1.0

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 (55) hide show
  1. package/README.md +91 -0
  2. package/SPECS.md +349 -0
  3. package/dist/args/parse-args.d.ts +6 -0
  4. package/dist/args/parse-args.d.ts.map +1 -0
  5. package/dist/args/parse-args.js +330 -0
  6. package/dist/args/parse-args.js.map +1 -0
  7. package/dist/child/parse-child-output.d.ts +11 -0
  8. package/dist/child/parse-child-output.d.ts.map +1 -0
  9. package/dist/child/parse-child-output.js +49 -0
  10. package/dist/child/parse-child-output.js.map +1 -0
  11. package/dist/child/run-builder.d.ts +24 -0
  12. package/dist/child/run-builder.d.ts.map +1 -0
  13. package/dist/child/run-builder.js +92 -0
  14. package/dist/child/run-builder.js.map +1 -0
  15. package/dist/child/run-child.d.ts +36 -0
  16. package/dist/child/run-child.d.ts.map +1 -0
  17. package/dist/child/run-child.js +141 -0
  18. package/dist/child/run-child.js.map +1 -0
  19. package/dist/child/run-gate.d.ts +14 -0
  20. package/dist/child/run-gate.d.ts.map +1 -0
  21. package/dist/child/run-gate.js +72 -0
  22. package/dist/child/run-gate.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +44 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/index.d.ts +12 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +9 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/loop/decide-after-attempt.d.ts +23 -0
  32. package/dist/loop/decide-after-attempt.d.ts.map +1 -0
  33. package/dist/loop/decide-after-attempt.js +26 -0
  34. package/dist/loop/decide-after-attempt.js.map +1 -0
  35. package/dist/progress/emit.d.ts +5 -0
  36. package/dist/progress/emit.d.ts.map +1 -0
  37. package/dist/progress/emit.js +24 -0
  38. package/dist/progress/emit.js.map +1 -0
  39. package/dist/run/run-implementer.d.ts +23 -0
  40. package/dist/run/run-implementer.d.ts.map +1 -0
  41. package/dist/run/run-implementer.js +274 -0
  42. package/dist/run/run-implementer.js.map +1 -0
  43. package/dist/status/announce.d.ts +14 -0
  44. package/dist/status/announce.d.ts.map +1 -0
  45. package/dist/status/announce.js +40 -0
  46. package/dist/status/announce.js.map +1 -0
  47. package/dist/status/make-status.d.ts +9 -0
  48. package/dist/status/make-status.d.ts.map +1 -0
  49. package/dist/status/make-status.js +38 -0
  50. package/dist/status/make-status.js.map +1 -0
  51. package/dist/types.d.ts +119 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +3 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +46 -0
@@ -0,0 +1,141 @@
1
+ import { spawn } from "node:child_process";
2
+ // Two bounds, for two different things.
3
+ //
4
+ // The first protects this process: a child that writes without end is not one
5
+ // whose answer can be read, so past this much it is killed and reported as
6
+ // broken. No sane child comes near it, and nothing a child says is cut by it.
7
+ const OUTPUT_LIMIT_CHARS = 8 * 1024 * 1024;
8
+ // The second protects the ledger: when a child left no contract line, its
9
+ // stdout and stderr become the report, and only their end — where a crash says
10
+ // why — is kept. Same size as the tracker keeps of a report.
11
+ const REPORT_KEEP_CHARS = 8_192;
12
+ function keepEnd(text) {
13
+ if (text.length <= REPORT_KEEP_CHARS) {
14
+ return text;
15
+ }
16
+ return `[…${text.length - REPORT_KEEP_CHARS} characters cut]${text.slice(-REPORT_KEEP_CHARS)}`;
17
+ }
18
+ function killProcessTree(child) {
19
+ try {
20
+ child.kill("SIGKILL");
21
+ }
22
+ catch {
23
+ // already gone
24
+ }
25
+ }
26
+ /**
27
+ * Run one child with a wall-clock timeout. Does not inherit Implementer stdin.
28
+ */
29
+ export function runChild(request) {
30
+ return new Promise((resolve) => {
31
+ const [file, ...args] = request.argv;
32
+ if (file === undefined) {
33
+ resolve({ kind: "spawn_error", detail: "Empty command." });
34
+ return;
35
+ }
36
+ let settled = false;
37
+ let timedOut = false;
38
+ let interrupted = false;
39
+ let stdout = "";
40
+ let stderr = "";
41
+ let timer;
42
+ let interruptPoll;
43
+ const finish = (outcome) => {
44
+ if (settled) {
45
+ return;
46
+ }
47
+ settled = true;
48
+ if (timer !== undefined) {
49
+ clearTimeout(timer);
50
+ }
51
+ if (interruptPoll !== undefined) {
52
+ clearInterval(interruptPoll);
53
+ }
54
+ resolve(outcome);
55
+ };
56
+ let child;
57
+ try {
58
+ child = spawn(file, args, {
59
+ cwd: request.cwd,
60
+ env: request.env,
61
+ stdio: ["ignore", "pipe", "pipe"],
62
+ });
63
+ }
64
+ catch (error) {
65
+ const detail = error instanceof Error ? error.message : String(error);
66
+ finish({ kind: "spawn_error", detail });
67
+ return;
68
+ }
69
+ if (!child.stdout || !child.stderr) {
70
+ finish({ kind: "spawn_error", detail: "Child stdout/stderr pipes unavailable." });
71
+ return;
72
+ }
73
+ child.stdout.setEncoding("utf8");
74
+ child.stderr.setEncoding("utf8");
75
+ let overflowed = false;
76
+ const take = (chunk) => {
77
+ if (overflowed) {
78
+ return false;
79
+ }
80
+ if (stdout.length + stderr.length + chunk.length > OUTPUT_LIMIT_CHARS) {
81
+ overflowed = true;
82
+ killProcessTree(child);
83
+ return false;
84
+ }
85
+ return true;
86
+ };
87
+ child.stdout.on("data", (chunk) => {
88
+ if (take(chunk)) {
89
+ stdout += chunk;
90
+ }
91
+ });
92
+ child.stderr.on("data", (chunk) => {
93
+ if (take(chunk)) {
94
+ stderr += chunk;
95
+ }
96
+ });
97
+ child.on("error", (error) => {
98
+ finish({ kind: "spawn_error", detail: error.message });
99
+ });
100
+ timer = setTimeout(() => {
101
+ timedOut = true;
102
+ killProcessTree(child);
103
+ }, Math.max(1, request.timeoutMs));
104
+ interruptPoll = setInterval(() => {
105
+ if (request.shouldInterrupt()) {
106
+ interrupted = true;
107
+ killProcessTree(child);
108
+ }
109
+ }, 20);
110
+ if (request.shouldInterrupt()) {
111
+ interrupted = true;
112
+ killProcessTree(child);
113
+ }
114
+ child.on("close", (exitCode, signal) => {
115
+ const out = stdout;
116
+ const err = stderr;
117
+ if (overflowed) {
118
+ finish({
119
+ kind: "spawn_error",
120
+ detail: `Child wrote more than ${OUTPUT_LIMIT_CHARS} characters; its answer cannot be read.`,
121
+ });
122
+ return;
123
+ }
124
+ if (interrupted || request.shouldInterrupt()) {
125
+ finish({ kind: "interrupted", stdout: out, stderr: err });
126
+ return;
127
+ }
128
+ if (timedOut) {
129
+ finish({ kind: "timed_out", stdout: out, stderr: err });
130
+ return;
131
+ }
132
+ finish({ kind: "exited", exitCode, signal, stdout: out, stderr: err });
133
+ });
134
+ });
135
+ }
136
+ /** The fallback report of a child that left no contract line: its last words. */
137
+ export function combinedOutput(stdout, stderr) {
138
+ const parts = [stdout, stderr].filter((p) => p.length > 0);
139
+ return keepEnd(parts.join("\n"));
140
+ }
141
+ //# sourceMappingURL=run-child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-child.js","sourceRoot":"","sources":["../../src/child/run-child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { GateSpec, GateTraceEntry, Invocation } from "../types.js";
2
+ export type GateRunResult = {
3
+ entry: GateTraceEntry;
4
+ /** When not pass, the Attempt ends with this (or interrupted). */
5
+ stop?: "fail-retryable" | "fail-blocking" | "interrupted";
6
+ };
7
+ export declare function runGate(input: {
8
+ invocation: Invocation;
9
+ attempt: number;
10
+ gate: GateSpec;
11
+ timeoutMs: number;
12
+ shouldInterrupt: () => boolean;
13
+ }): Promise<GateRunResult>;
14
+ //# sourceMappingURL=run-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-gate.d.ts","sourceRoot":"","sources":["../../src/child/run-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIxE,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,kEAAkE;IAClE,IAAI,CAAC,EAAE,gBAAgB,GAAG,eAAe,GAAG,aAAa,CAAC;CAC3D,CAAC;AAEF,wBAAsB,OAAO,CAAC,KAAK,EAAE;IACnC,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,OAAO,CAAC;CAChC,GAAG,OAAO,CAAC,aAAa,CAAC,CA2BzB"}
@@ -0,0 +1,72 @@
1
+ import { parseGateVerdictJson } from "./parse-child-output.js";
2
+ import { runChild } from "./run-child.js";
3
+ export async function runGate(input) {
4
+ const { invocation, attempt, gate, timeoutMs, shouldInterrupt } = input;
5
+ const argv = [
6
+ ...gate.argv,
7
+ "--id",
8
+ invocation.id,
9
+ "--attempt",
10
+ String(attempt),
11
+ "--gate-id",
12
+ gate.id,
13
+ "--intention",
14
+ invocation.intention,
15
+ "--definition-of-done",
16
+ invocation.definitionOfDone,
17
+ "--stage",
18
+ invocation.stage ?? "unit",
19
+ ];
20
+ const outcome = await runChild({
21
+ argv,
22
+ cwd: invocation.workspace,
23
+ timeoutMs,
24
+ shouldInterrupt,
25
+ });
26
+ return interpretGateOutcome(gate.id, outcome, timeoutMs);
27
+ }
28
+ function interpretGateOutcome(gateId, outcome, timeoutMs) {
29
+ if (outcome.kind === "spawn_error") {
30
+ return {
31
+ entry: { id: gateId, verdict: "fail-blocking", report: outcome.detail },
32
+ stop: "fail-blocking",
33
+ };
34
+ }
35
+ if (outcome.kind === "interrupted") {
36
+ return {
37
+ entry: { id: gateId, verdict: "fail-blocking", report: "stop signal" },
38
+ stop: "interrupted",
39
+ };
40
+ }
41
+ if (outcome.kind === "timed_out") {
42
+ const report = `killed after ${timeoutMs}ms, its own ceiling`;
43
+ return {
44
+ entry: { id: gateId, verdict: "fail-retryable", report },
45
+ stop: "fail-retryable",
46
+ };
47
+ }
48
+ const parsed = parseGateVerdictJson(outcome.stdout);
49
+ if (!parsed) {
50
+ return {
51
+ entry: {
52
+ id: gateId,
53
+ verdict: "fail-blocking",
54
+ report: "Gate stdout is not a JSON object with a known verdict.",
55
+ },
56
+ stop: "fail-blocking",
57
+ };
58
+ }
59
+ const entry = {
60
+ id: gateId,
61
+ verdict: parsed.verdict,
62
+ report: parsed.report,
63
+ };
64
+ if (parsed.verdict === "pass") {
65
+ return { entry };
66
+ }
67
+ return {
68
+ entry,
69
+ stop: parsed.verdict,
70
+ };
71
+ }
72
+ //# sourceMappingURL=run-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-gate.js","sourceRoot":"","sources":["../../src/child/run-gate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAqB,MAAM,gBAAgB,CAAC;AAQ7D,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAM7B;IACC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IAExE,MAAM,IAAI,GAAG;QACX,GAAG,IAAI,CAAC,IAAI;QACZ,MAAM;QACN,UAAU,CAAC,EAAE;QACb,WAAW;QACX,MAAM,CAAC,OAAO,CAAC;QACf,WAAW;QACX,IAAI,CAAC,EAAE;QACP,aAAa;QACb,UAAU,CAAC,SAAS;QACpB,sBAAsB;QACtB,UAAU,CAAC,gBAAgB;QAC3B,SAAS;QACT,UAAU,CAAC,KAAK,IAAI,MAAM;KAC3B,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC;QAC7B,IAAI;QACJ,GAAG,EAAE,UAAU,CAAC,SAAS;QACzB,SAAS;QACT,eAAe;KAChB,CAAC,CAAC;IAEH,OAAO,oBAAoB,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,oBAAoB,CAC3B,MAAc,EACd,OAAqB,EACrB,SAAiB;IAEjB,IAAI,OAAO,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACnC,OAAO;YACL,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YACvE,IAAI,EAAE,eAAe;SACtB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QACnC,OAAO;YACL,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE;YACtE,IAAI,EAAE,aAAa;SACpB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,gBAAgB,SAAS,qBAAqB,CAAC;QAC9D,OAAO;YACL,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE;YACxD,IAAI,EAAE,gBAAgB;SACvB,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,KAAK,EAAE;gBACL,EAAE,EAAE,MAAM;gBACV,OAAO,EAAE,eAAe;gBACxB,MAAM,EAAE,wDAAwD;aACjE;YACD,IAAI,EAAE,eAAe;SACtB,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAmB;QAC5B,EAAE,EAAE,MAAM;QACV,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;IAEF,IAAI,MAAM,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,OAAO;QACL,KAAK;QACL,IAAI,EAAE,MAAM,CAAC,OAAO;KACrB,CAAC;AACJ,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "./args/parse-args.js";
3
+ import { createStdoutProgressWriter } from "./progress/emit.js";
4
+ import { invalidInvocationResult, runImplementer } from "./run/run-implementer.js";
5
+ /**
6
+ * Process signals belong to the binary, not to the Transformer: a Transformer imported into
7
+ * a host must not hijack the host's SIGINT. The CLI owns the flag it passes in.
8
+ */
9
+ function installSignalHandlers(state) {
10
+ const onSignal = () => {
11
+ state.interrupted = true;
12
+ };
13
+ process.on("SIGINT", onSignal);
14
+ process.on("SIGTERM", onSignal);
15
+ return () => {
16
+ process.off("SIGINT", onSignal);
17
+ process.off("SIGTERM", onSignal);
18
+ };
19
+ }
20
+ async function main(argv) {
21
+ const write = createStdoutProgressWriter();
22
+ const parsed = parseArgs(argv);
23
+ if (!parsed.ok) {
24
+ const result = invalidInvocationResult(undefined, write, parsed.reason);
25
+ return result.exitCode;
26
+ }
27
+ const interruptFlag = { interrupted: false };
28
+ const detachSignals = installSignalHandlers(interruptFlag);
29
+ try {
30
+ const result = await runImplementer({
31
+ invocation: parsed.invocation,
32
+ write,
33
+ interruptFlag,
34
+ cwd: process.cwd(),
35
+ });
36
+ return result.exitCode;
37
+ }
38
+ finally {
39
+ detachSignals();
40
+ }
41
+ }
42
+ const exitCode = await main(process.argv.slice(2));
43
+ process.exitCode = exitCode;
44
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEnF;;;GAGG;AACH,SAAS,qBAAqB,CAAC,KAA+B;IAC5D,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3B,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,KAAK,GAAG,0BAA0B,EAAE,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACxE,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,MAAM,aAAa,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC7C,MAAM,aAAa,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;YAClC,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,KAAK;YACL,aAAa;YACb,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;SACnB,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;YAAS,CAAC;QACT,aAAa,EAAE,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Import entry point for this Transformer.
3
+ *
4
+ * The Transformer is a function. `cli.ts` is a thin adapter over this surface: it
5
+ * reads argv and stdin, calls runImplementer, and maps the outcome to an exit code.
6
+ * Everything a caller needs in process is reachable from here.
7
+ */
8
+ export type { ProgressWriter } from "./progress/emit.js";
9
+ export type { RunOptions, RunResult } from "./run/run-implementer.js";
10
+ export { runImplementer } from "./run/run-implementer.js";
11
+ export type * from "./types.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,mBAAmB,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Import entry point for this Transformer.
3
+ *
4
+ * The Transformer is a function. `cli.ts` is a thin adapter over this surface: it
5
+ * reads argv and stdin, calls runImplementer, and maps the outcome to an exit code.
6
+ * Everything a caller needs in process is reachable from here.
7
+ */
8
+ export { runImplementer } from "./run/run-implementer.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { AttemptEnded, RunOutcome } from "../types.js";
2
+ export type LoopInput = {
3
+ ended: AttemptEnded;
4
+ /** Attempts started so far, including the one that just ended. */
5
+ attemptsStarted: number;
6
+ maxAttempts: number;
7
+ };
8
+ export type LoopDecision = {
9
+ action: "finish";
10
+ outcome: Exclude<RunOutcome, "invalid-invocation">;
11
+ } | {
12
+ action: "retry";
13
+ };
14
+ /**
15
+ * Pure loop rule: next action from the Attempt verdict and the count.
16
+ *
17
+ * No clock enters here any more. Every child of an Attempt carries its own
18
+ * ceiling, so a slow one ends its Attempt as any other failure does, and the
19
+ * budget of a whole Task is what the Project wrote: `maxAttempts` times the
20
+ * producer and its Gates.
21
+ */
22
+ export declare function decideAfterAttempt(input: LoopInput): LoopDecision;
23
+ //# sourceMappingURL=decide-after-attempt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide-after-attempt.d.ts","sourceRoot":"","sources":["../../src/loop/decide-after-attempt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,YAAY,CAAC;IACpB,kEAAkE;IAClE,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GACpB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAA;CAAE,GACxE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,CAkBjE"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Pure loop rule: next action from the Attempt verdict and the count.
3
+ *
4
+ * No clock enters here any more. Every child of an Attempt carries its own
5
+ * ceiling, so a slow one ends its Attempt as any other failure does, and the
6
+ * budget of a whole Task is what the Project wrote: `maxAttempts` times the
7
+ * producer and its Gates.
8
+ */
9
+ export function decideAfterAttempt(input) {
10
+ const { ended, attemptsStarted, maxAttempts } = input;
11
+ if (ended === "validated") {
12
+ return { action: "finish", outcome: "validated" };
13
+ }
14
+ if (ended === "fail-blocking") {
15
+ return { action: "finish", outcome: "escalated" };
16
+ }
17
+ if (ended === "interrupted") {
18
+ return { action: "finish", outcome: "interrupted" };
19
+ }
20
+ // fail-retryable
21
+ if (attemptsStarted >= maxAttempts) {
22
+ return { action: "finish", outcome: "escalated" };
23
+ }
24
+ return { action: "retry" };
25
+ }
26
+ //# sourceMappingURL=decide-after-attempt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decide-after-attempt.js","sourceRoot":"","sources":["../../src/loop/decide-after-attempt.ts"],"names":[],"mappings":"AAaA;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAgB;IACjD,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;IAEtD,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;QAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,KAAK,KAAK,eAAe,EAAE,CAAC;QAC9B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IACtD,CAAC;IAED,iBAAiB;IACjB,IAAI,eAAe,IAAI,WAAW,EAAE,CAAC;QACnC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Status } from "../types.js";
2
+ export type ProgressWriter = (line: Record<string, unknown>) => void;
3
+ export declare function createStdoutProgressWriter(): ProgressWriter;
4
+ export declare function emitStatusEvent(write: ProgressWriter, taskId: string, status: Status): void;
5
+ //# sourceMappingURL=emit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/progress/emit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;AAErE,wBAAgB,0BAA0B,IAAI,cAAc,CAI3D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAiB3F"}
@@ -0,0 +1,24 @@
1
+ export function createStdoutProgressWriter() {
2
+ return (line) => {
3
+ process.stdout.write(`${JSON.stringify(line)}\n`);
4
+ };
5
+ }
6
+ export function emitStatusEvent(write, taskId, status) {
7
+ const line = {
8
+ event: "status",
9
+ task_id: taskId,
10
+ phase: status.phase,
11
+ label: status.label,
12
+ };
13
+ if (status.attempt !== undefined) {
14
+ line.attempt = status.attempt;
15
+ }
16
+ if (status.max_attempts !== undefined) {
17
+ line.max_attempts = status.max_attempts;
18
+ }
19
+ if (status.gate_id !== undefined) {
20
+ line.gate_id = status.gate_id;
21
+ }
22
+ write(line);
23
+ }
24
+ //# sourceMappingURL=emit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/progress/emit.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,0BAA0B;IACxC,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAqB,EAAE,MAAc,EAAE,MAAc;IACnF,MAAM,IAAI,GAA4B;QACpC,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,MAAM;QACf,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;IACF,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,CAAC;IACD,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,CAAC;IAC1C,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ProgressWriter } from "../progress/emit.js";
2
+ import type { Invocation, RunOutcome, Trace } from "../types.js";
3
+ export type RunResult = {
4
+ outcome: RunOutcome;
5
+ traces: Trace[];
6
+ exitCode: number;
7
+ };
8
+ export type RunOptions = {
9
+ invocation: Invocation;
10
+ write?: ProgressWriter;
11
+ /** When set, polled for interrupt; otherwise process signals are used. */
12
+ interruptFlag?: {
13
+ interrupted: boolean;
14
+ };
15
+ /** Working directory for spawned children. Undefined lets them inherit. */
16
+ cwd?: string;
17
+ };
18
+ /**
19
+ * Run one Implementer invocation to a run outcome.
20
+ */
21
+ export declare function runImplementer(options: RunOptions): Promise<RunResult>;
22
+ export declare function invalidInvocationResult(invocationId: string | undefined, write: ProgressWriter, reason: string): RunResult;
23
+ //# sourceMappingURL=run-implementer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-implementer.d.ts","sourceRoot":"","sources":["../../src/run/run-implementer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,OAAO,KAAK,EAA8B,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE7F,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,UAAU,CAAC;IACvB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,0EAA0E;IAC1E,aAAa,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,CAAC;IACzC,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAuCF;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAQ5E;AAuPD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,MAAM,GACb,SAAS,CAqBX"}