opensquid 0.5.412 → 0.5.426

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 (93) hide show
  1. package/dist/cli.js +2 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/functions/event.d.ts.map +1 -1
  4. package/dist/functions/event.js +30 -0
  5. package/dist/functions/event.js.map +1 -1
  6. package/dist/functions/index.d.ts +2 -0
  7. package/dist/functions/index.d.ts.map +1 -1
  8. package/dist/functions/index.js +2 -0
  9. package/dist/functions/index.js.map +1 -1
  10. package/dist/functions/read_rubric.d.ts +27 -0
  11. package/dist/functions/read_rubric.d.ts.map +1 -0
  12. package/dist/functions/read_rubric.js +53 -0
  13. package/dist/functions/read_rubric.js.map +1 -0
  14. package/dist/functions/rubric_pre_inject.d.ts +22 -0
  15. package/dist/functions/rubric_pre_inject.d.ts.map +1 -0
  16. package/dist/functions/rubric_pre_inject.js +58 -0
  17. package/dist/functions/rubric_pre_inject.js.map +1 -0
  18. package/dist/functions/shell_parse.d.ts +41 -0
  19. package/dist/functions/shell_parse.d.ts.map +1 -0
  20. package/dist/functions/shell_parse.js +185 -0
  21. package/dist/functions/shell_parse.js.map +1 -0
  22. package/dist/mcp/server.js +14 -1
  23. package/dist/mcp/server.js.map +1 -1
  24. package/dist/mcp/tools/ralph.d.ts +21 -0
  25. package/dist/mcp/tools/ralph.d.ts.map +1 -0
  26. package/dist/mcp/tools/ralph.js +18 -0
  27. package/dist/mcp/tools/ralph.js.map +1 -0
  28. package/dist/mcp/tools/workgraph.d.ts +11 -0
  29. package/dist/mcp/tools/workgraph.d.ts.map +1 -1
  30. package/dist/mcp/tools/workgraph.js +7 -0
  31. package/dist/mcp/tools/workgraph.js.map +1 -1
  32. package/dist/runtime/bootstrap.d.ts.map +1 -1
  33. package/dist/runtime/bootstrap.js +4 -0
  34. package/dist/runtime/bootstrap.js.map +1 -1
  35. package/dist/runtime/ralph/decision_classifier.d.ts +18 -0
  36. package/dist/runtime/ralph/decision_classifier.d.ts.map +1 -0
  37. package/dist/runtime/ralph/decision_classifier.js +72 -0
  38. package/dist/runtime/ralph/decision_classifier.js.map +1 -0
  39. package/dist/runtime/ralph/escalate_lap.d.ts +31 -0
  40. package/dist/runtime/ralph/escalate_lap.d.ts.map +1 -0
  41. package/dist/runtime/ralph/escalate_lap.js +22 -0
  42. package/dist/runtime/ralph/escalate_lap.js.map +1 -0
  43. package/dist/runtime/ralph/escalator.d.ts +34 -0
  44. package/dist/runtime/ralph/escalator.d.ts.map +1 -0
  45. package/dist/runtime/ralph/escalator.js +19 -0
  46. package/dist/runtime/ralph/escalator.js.map +1 -0
  47. package/dist/runtime/ralph/lap_outcome.d.ts +43 -0
  48. package/dist/runtime/ralph/lap_outcome.d.ts.map +1 -0
  49. package/dist/runtime/ralph/lap_outcome.js +119 -0
  50. package/dist/runtime/ralph/lap_outcome.js.map +1 -0
  51. package/dist/runtime/ralph/orchestrator.d.ts +71 -0
  52. package/dist/runtime/ralph/orchestrator.d.ts.map +1 -0
  53. package/dist/runtime/ralph/orchestrator.js +74 -0
  54. package/dist/runtime/ralph/orchestrator.js.map +1 -0
  55. package/dist/runtime/ralph/ralph_template.d.ts +18 -0
  56. package/dist/runtime/ralph/ralph_template.d.ts.map +1 -0
  57. package/dist/runtime/ralph/ralph_template.js +61 -0
  58. package/dist/runtime/ralph/ralph_template.js.map +1 -0
  59. package/dist/runtime/ralph/supervisor.d.ts +30 -0
  60. package/dist/runtime/ralph/supervisor.d.ts.map +1 -0
  61. package/dist/runtime/ralph/supervisor.js +24 -0
  62. package/dist/runtime/ralph/supervisor.js.map +1 -0
  63. package/dist/setup/cli/ralph.d.ts +20 -0
  64. package/dist/setup/cli/ralph.d.ts.map +1 -0
  65. package/dist/setup/cli/ralph.js +168 -0
  66. package/dist/setup/cli/ralph.js.map +1 -0
  67. package/dist/setup/wizard/ralph_writer.d.ts +98 -0
  68. package/dist/setup/wizard/ralph_writer.d.ts.map +1 -0
  69. package/dist/setup/wizard/ralph_writer.js +114 -0
  70. package/dist/setup/wizard/ralph_writer.js.map +1 -0
  71. package/dist/workgraph/audience.d.ts +12 -0
  72. package/dist/workgraph/audience.d.ts.map +1 -0
  73. package/dist/workgraph/audience.js +10 -0
  74. package/dist/workgraph/audience.js.map +1 -0
  75. package/dist/workgraph/events.d.ts.map +1 -1
  76. package/dist/workgraph/events.js +34 -0
  77. package/dist/workgraph/events.js.map +1 -1
  78. package/dist/workgraph/store.d.ts.map +1 -1
  79. package/dist/workgraph/store.js +86 -12
  80. package/dist/workgraph/store.js.map +1 -1
  81. package/dist/workgraph/types.d.ts +33 -2
  82. package/dist/workgraph/types.d.ts.map +1 -1
  83. package/docs/rubric/author.md +17 -0
  84. package/docs/rubric/scope.md +16 -0
  85. package/package.json +3 -2
  86. package/packs/builtin/coding-flow/skills/entry-and-handoffs/skill.yaml +9 -0
  87. package/packs/builtin/coding-flow/skills/execute-gate/skill.yaml +9 -10
  88. package/packs/builtin/coding-flow/skills/scope-lifecycle/skill.yaml +67 -50
  89. package/packs/builtin/default-discipline/manifest.yaml +6 -3
  90. package/packs/builtin/default-discipline/skills/workflow/skill.yaml +7 -3
  91. package/packs/builtin/pack-architect/SKILL.md +10 -0
  92. package/packs/builtin/pack-architect/skills/skill-yaml-author-walkthrough/skill.yaml +7 -0
  93. package/packs/builtin/scope-architect/team.yaml +5 -2
@@ -0,0 +1,119 @@
1
+ /**
2
+ * GR.2 — the typed-exit contract for a gated-ralph lap (Inv 5: ONE typed exit).
3
+ *
4
+ * A lap is a headless `claude -p … --output-format json` run of `RALPH.md`. Its envelope is the
5
+ * empirically-verified shape `{ result, is_error, subtype, total_cost_usd, … }` (verified 2026-06-13).
6
+ * The lap signals its outcome by emitting a single greppable line in `result`:
7
+ *
8
+ * RALPH-EXIT: {"kind":"HUMAN_REQUIRED","reason":"SCOPE_FORK","payload":{…}}
9
+ *
10
+ * `parseLapOutcome` is a TOTAL mapping of every envelope shape to a `LapOutcome` — it never throws and
11
+ * never returns a false `SHIPPED`: an unparseable envelope or `is_error` becomes `CRASH`; a clean
12
+ * envelope with no tag becomes `SHIPPED`. The orchestrator (GR.4) switches on the result.
13
+ *
14
+ * Imported by: src/runtime/ralph/orchestrator.ts (GR.4), src/runtime/ralph/supervisor.ts (GR.3).
15
+ */
16
+ const HUMAN_REQUIRED_REASONS = [
17
+ 'IRREVERSIBLE_BOUNDARY',
18
+ 'SCOPE_FORK',
19
+ 'UNRECOVERABLE_WEDGE',
20
+ 'BUDGET',
21
+ 'RATE_BUDGET',
22
+ 'BOARD_EMPTY',
23
+ ];
24
+ const isReason = (v) => typeof v === 'string' && HUMAN_REQUIRED_REASONS.includes(v);
25
+ const TAG = 'RALPH-EXIT:';
26
+ /**
27
+ * Scan the lap's free-text result for the LAST `RALPH-EXIT: {json}` line and validate it into a
28
+ * `LapOutcome`. Returns null when there is no well-formed tag (caller defaults to SHIPPED on a clean
29
+ * exit). Defensive by construction — a malformed tag is treated as "no tag".
30
+ */
31
+ export function extractTypedExit(resultText) {
32
+ const idx = resultText.lastIndexOf(TAG);
33
+ if (idx === -1)
34
+ return null;
35
+ const after = resultText.slice(idx + TAG.length).trimStart();
36
+ const json = sliceFirstJsonObject(after);
37
+ if (json === null)
38
+ return null;
39
+ let obj;
40
+ try {
41
+ obj = JSON.parse(json);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ if (obj === null || typeof obj !== 'object')
47
+ return null;
48
+ const rec = obj;
49
+ switch (rec.kind) {
50
+ case 'SHIPPED':
51
+ return { kind: 'SHIPPED' };
52
+ case 'WEDGE':
53
+ return { kind: 'WEDGE' };
54
+ case 'TIMEOUT':
55
+ return { kind: 'TIMEOUT' };
56
+ case 'CRASH':
57
+ return { kind: 'CRASH' };
58
+ case 'HUMAN_REQUIRED':
59
+ if (!isReason(rec.reason))
60
+ return null; // a HUMAN_REQUIRED without a valid reason is malformed
61
+ return {
62
+ kind: 'HUMAN_REQUIRED',
63
+ reason: rec.reason,
64
+ ...(typeof rec.item === 'string' ? { item: rec.item } : {}),
65
+ ...(rec.payload === undefined ? {} : { payload: rec.payload }),
66
+ };
67
+ default:
68
+ return null;
69
+ }
70
+ }
71
+ /** Parse the headless JSON envelope into a total `LapOutcome` + the lap's cost. Never throws. */
72
+ export function parseLapOutcome(stdout) {
73
+ let env;
74
+ try {
75
+ const parsed = JSON.parse(stdout);
76
+ if (parsed === null || typeof parsed !== 'object')
77
+ return { outcome: { kind: 'CRASH' }, costUsd: 0 };
78
+ env = parsed;
79
+ }
80
+ catch {
81
+ return { outcome: { kind: 'CRASH' }, costUsd: 0 }; // unparseable envelope = CRASH, never SHIPPED
82
+ }
83
+ const costUsd = typeof env.total_cost_usd === 'number' ? env.total_cost_usd : 0;
84
+ if (env.is_error === true)
85
+ return { outcome: { kind: 'CRASH' }, costUsd };
86
+ const tagged = extractTypedExit(typeof env.result === 'string' ? env.result : '');
87
+ return { outcome: tagged ?? { kind: 'SHIPPED' }, costUsd }; // clean exit, no tag = SHIPPED
88
+ }
89
+ /** Return the first balanced `{…}` JSON object substring starting at `s[0]`, or null. */
90
+ function sliceFirstJsonObject(s) {
91
+ if (!s.startsWith('{'))
92
+ return null;
93
+ let depth = 0;
94
+ let inStr = false;
95
+ let esc = false;
96
+ for (let i = 0; i < s.length; i++) {
97
+ const c = s[i];
98
+ if (inStr) {
99
+ if (esc)
100
+ esc = false;
101
+ else if (c === '\\')
102
+ esc = true;
103
+ else if (c === '"')
104
+ inStr = false;
105
+ continue;
106
+ }
107
+ if (c === '"')
108
+ inStr = true;
109
+ else if (c === '{')
110
+ depth++;
111
+ else if (c === '}') {
112
+ depth--;
113
+ if (depth === 0)
114
+ return s.slice(0, i + 1);
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ //# sourceMappingURL=lap_outcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lap_outcome.js","sourceRoot":"","sources":["../../../src/runtime/ralph/lap_outcome.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * GR.4 — the gated-ralph orchestrator: a thin, non-LLM loop that composes GR.1–3.
3
+ *
4
+ * One pass: read the oldest ready item (GR.1 `listReady`) → atomically claim it (GR.1 CAS) → run a
5
+ * supervised lap (GR.3 `superviseLap` over an injected lap-runner that wraps `claude -p RALPH.md` +
6
+ * GR.2 `parseLapOutcome`) → act on the typed `LapOutcome` (GR.2) → repeat until BOARD_EMPTY or a
7
+ * stopping escalation. No new state store (claim/wedge are work-graph ops); no new gate logic (the lap
8
+ * is gated by the same coding-flow); ONE uniform escalation (GR.3 `escalateLap`).
9
+ *
10
+ * Dependencies are INJECTED (`RalphDeps`) so the loop is pure composition and unit-testable without the
11
+ * notification stack or a real `claude` subprocess — the CLI (`ralph.ts`) wires the real store, the
12
+ * `claude -p` lap-runner, and `escalateSeverity`.
13
+ *
14
+ * The S1 spike PROVED spawn-a-lap is safe (a nested `--dangerously-skip-permissions` lap's ungated
15
+ * commit was blocked by the gate), so this loop spawns laps directly rather than via Alternative F.
16
+ */
17
+ import type { Issue, WorkGraphStore, ClaimAudience } from '../../workgraph/types.js';
18
+ import type { HumanRequiredReason } from './lap_outcome.js';
19
+ import type { LapResult, SuperviseOpts } from './supervisor.js';
20
+ import { type LapEscalator } from './escalate_lap.js';
21
+ import type { DecisionVerdict } from './decision_classifier.js';
22
+ export interface RalphConfig {
23
+ /** Auth mode from CONFIG (Inv 11; no runtime auto-detect). API → dollar budget; subscription → W. */
24
+ authMode: 'api' | 'subscription';
25
+ /** API dollar bound — the running sum of the verified `total_cost_usd` (Inv 11). API mode only. */
26
+ maxBudgetUsd: number;
27
+ /** TTL for the per-item claim (GR.1). */
28
+ claimTtlSec: number;
29
+ /** Stop after one item (the `--once` flag). */
30
+ once: boolean;
31
+ /** GR.3 supervisor opts (retry cap, backoff, heartbeat). */
32
+ supervise: SuperviseOpts;
33
+ }
34
+ /** Why the loop stopped. NB: the per-item residual reasons (IRREVERSIBLE_BOUNDARY / SCOPE_FORK /
35
+ * UNRECOVERABLE_WEDGE) never STOP the loop — they park the item and the loop takes the next. Only the
36
+ * RESOURCE pauses + an explicit `--once` end the run. */
37
+ export type RalphStop = 'BOARD_EMPTY' | 'BUDGET' | 'RATE_BUDGET' | 'once';
38
+ export interface RalphResult {
39
+ stopped: RalphStop;
40
+ spent: number;
41
+ closed: string[];
42
+ parked: {
43
+ id: string;
44
+ reason: HumanRequiredReason;
45
+ }[];
46
+ }
47
+ export interface RalphDeps {
48
+ wg: WorkGraphStore;
49
+ /** Env-derived claim audience (GR.1 — never caller input). */
50
+ claimAudience: () => ClaimAudience;
51
+ /** Run ONE lap for an item → its typed outcome + cost. The CLI wraps `claude -p RALPH.md` + parseLapOutcome. */
52
+ runLap: (item: Issue) => Promise<LapResult>;
53
+ /** The undroppable escalation transport (GR.3) — the CLI wires `escalateSeverity`. */
54
+ escalate: LapEscalator;
55
+ }
56
+ export declare function runRalphLoop(cfg: RalphConfig, deps: RalphDeps): Promise<RalphResult>;
57
+ /** GR.2's recordMisclassification, injected (kept testable / decoupled). */
58
+ export type RecordMisclassification = (sessionId: string, expected: DecisionVerdict, got: DecisionVerdict, decision: string, nowIso: string) => Promise<void>;
59
+ /**
60
+ * `opensquid loop resolve <itemId> --misclassified` — the human-override INPUT (post-hoc residual-shrink
61
+ * path; NOT the hot loop). The human marks a parked escalation as principle-settleable: we record the
62
+ * misclassification (the heuristic said ESCALATE, the correct verdict was DECIDE) and un-wedge the item so
63
+ * it re-enters `ready` for another lap. The SOLE caller of GR.2's recordMisclassification.
64
+ */
65
+ export declare function resolveParked(itemId: string, deps: {
66
+ wg: WorkGraphStore;
67
+ recordMisclassification: RecordMisclassification;
68
+ sessionId: string;
69
+ nowIso: string;
70
+ }): Promise<void>;
71
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,WAAW;IAC1B,qGAAqG;IACrG,QAAQ,EAAE,KAAK,GAAG,cAAc,CAAC;IACjC,mGAAmG;IACnG,YAAY,EAAE,MAAM,CAAC;IACrB,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAC;IACd,4DAA4D;IAC5D,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED;;yDAEyD;AACzD,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,QAAQ,GAAG,aAAa,GAAG,MAAM,CAAC;AAE1E,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,SAAS,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAA;KAAE,EAAE,CAAC;CACvD;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,cAAc,CAAC;IACnB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,aAAa,CAAC;IACnC,gHAAgH;IAChH,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IAC5C,sFAAsF;IACtF,QAAQ,EAAE,YAAY,CAAC;CACxB;AAOD,wBAAsB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CA0D1F;AAED,4EAA4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,eAAe,EACzB,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IACJ,EAAE,EAAE,cAAc,CAAC;IACnB,uBAAuB,EAAE,uBAAuB,CAAC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -0,0 +1,74 @@
1
+ import { superviseLap } from './supervisor.js';
2
+ import { escalateLap } from './escalate_lap.js';
3
+ /** Resource pauses END the run; everything else is per-item decision-residual that parks + continues. */
4
+ const RESOURCE_PAUSES = ['BUDGET', 'RATE_BUDGET', 'BOARD_EMPTY'];
5
+ const isResourcePause = (r) => RESOURCE_PAUSES.includes(r);
6
+ export async function runRalphLoop(cfg, deps) {
7
+ const { wg } = deps;
8
+ const closed = [];
9
+ const parked = [];
10
+ let spent = 0;
11
+ // THE single uniform stop-layer (Inv 5): every reason chat-escalates through ONE path — no per-trigger
12
+ // code paths (rejects Alt D). Two SEPARATE, explicit decisions ride alongside it: (a) wedge-mark ONLY the
13
+ // per-item residual (IRREVERSIBLE_BOUNDARY/SCOPE_FORK/UNRECOVERABLE_WEDGE) — a resource pause
14
+ // (BUDGET/RATE_BUDGET/BOARD_EMPTY) is TRANSIENT, so the item stays in `ready` to retry after the
15
+ // window/budget resets (wedge-marking it would PERMANENTLY park healthy work); (b) continue-vs-stop is
16
+ // isResourcePause at the call sites.
17
+ const parkAndEscalate = async (reason, item) => {
18
+ if (item !== undefined && !isResourcePause(reason)) {
19
+ await wg.wedgeMark(item.id, reason); // a marked item SKIPS on re-attempt (residual only — GR.3)
20
+ parked.push({ id: item.id, reason });
21
+ }
22
+ // GR.3 — undroppable. Omit `item` entirely when absent (exactOptionalPropertyTypes: no explicit undefined).
23
+ await escalateLap(reason, item === undefined ? { escalate: deps.escalate } : { item: item.id, escalate: deps.escalate });
24
+ };
25
+ for (;;) {
26
+ const ready = await wg.listReady(); // GR.1 ordering, claim+wedge aware (live-claimed items excluded)
27
+ if (ready.length === 0) {
28
+ await parkAndEscalate('BOARD_EMPTY'); // resource pause → escalate + STOP
29
+ return { stopped: 'BOARD_EMPTY', spent, closed, parked };
30
+ }
31
+ const item = ready[0]; // oldest-first (shipped listReady ORDER BY created_at; no priority column)
32
+ if (item === undefined)
33
+ continue; // unreachable (length checked) — narrows for strict indexing
34
+ const { won } = await wg.claimIssue(item.id, deps.claimAudience(), cfg.claimTtlSec); // GR.1 atomic CAS
35
+ if (!won)
36
+ continue; // another runner/harness won it — it now carries a live claim, excluded next pass
37
+ const outcome = await superviseLap(() => deps.runLap(item), cfg.supervise); // GR.3 → LapResult
38
+ spent += outcome.costUsd; // GR.3 propagates costUsd across retries
39
+ if (outcome.kind === 'SHIPPED') {
40
+ await wg.updateIssue(item.id, { status: 'closed' });
41
+ closed.push(item.id);
42
+ }
43
+ else {
44
+ // Non-SHIPPED → the ONE uniform path. HUMAN_REQUIRED carries its reason; WEDGE (and the
45
+ // post-supervision CRASH/TIMEOUT the type allows but superviseLap never returns — it exhausts
46
+ // them to HUMAN_REQUIRED{UNRECOVERABLE_WEDGE}) map to UNRECOVERABLE_WEDGE.
47
+ const reason = outcome.kind === 'HUMAN_REQUIRED' ? outcome.reason : 'UNRECOVERABLE_WEDGE';
48
+ await parkAndEscalate(reason, item);
49
+ if (isResourcePause(reason))
50
+ return { stopped: reason, spent, closed, parked }; // e.g. lap-emitted RATE_BUDGET
51
+ // else (IRREVERSIBLE_BOUNDARY / SCOPE_FORK / UNRECOVERABLE_WEDGE): parked, take the next item
52
+ }
53
+ if (cfg.authMode === 'api' && spent > cfg.maxBudgetUsd) {
54
+ await parkAndEscalate('BUDGET'); // Inv 11 API bound (verified total_cost_usd) → resource pause → STOP
55
+ return { stopped: 'BUDGET', spent, closed, parked };
56
+ }
57
+ if (cfg.once)
58
+ return { stopped: 'once', spent, closed, parked };
59
+ }
60
+ }
61
+ /**
62
+ * `opensquid loop resolve <itemId> --misclassified` — the human-override INPUT (post-hoc residual-shrink
63
+ * path; NOT the hot loop). The human marks a parked escalation as principle-settleable: we record the
64
+ * misclassification (the heuristic said ESCALATE, the correct verdict was DECIDE) and un-wedge the item so
65
+ * it re-enters `ready` for another lap. The SOLE caller of GR.2's recordMisclassification.
66
+ */
67
+ export async function resolveParked(itemId, deps) {
68
+ const item = await deps.wg.getIssue(itemId);
69
+ if (item?.wedgeReason === undefined)
70
+ throw new Error(`resolveParked: not a parked item: ${itemId}`);
71
+ await deps.recordMisclassification(deps.sessionId, 'DECIDE', 'ESCALATE', item.title, deps.nowIso);
72
+ await deps.wg.clearWedge(itemId); // un-wedge → re-enters listReady
73
+ }
74
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../../src/runtime/ralph/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * GR.4 — the RALPH.md per-lap directive (the "Ralph constant").
3
+ *
4
+ * This is the STABLE prompt every lap runs (Brodie's Ralph: the same prompt, fresh context, every
5
+ * iteration — Inv 1 dumb-loop + Inv 2 fresh-context). It is NOT the orchestrator's logic; it is the
6
+ * instruction the spawned `claude -p` lap reads. The orchestrator stays a thin non-LLM loop; ALL the
7
+ * intelligence lives in the lap running this directive against the reloaded wedge-gated lessons.
8
+ *
9
+ * The one hard contract between lap and orchestrator: the lap MUST end by emitting a single greppable
10
+ * `RALPH-EXIT: {json}` line whose JSON is a `LapOutcome` (parsed by GR.2 `extractTypedExit`). Everything
11
+ * else (resume, lean load, the 7-phase flow, DECIDE-vs-ESCALATE) is the same discipline a human-driven
12
+ * session follows — the lap is gated identically (the gate is harness-agnostic, GDC).
13
+ *
14
+ * Exported as a string constant (not a shipped file) so the wizard (`ralph_writer.ts`) writes it to
15
+ * `~/.opensquid/RALPH.md` idempotently — no build-time file-copy-to-dist concern.
16
+ */
17
+ export declare const RALPH_MD = "# RALPH.md \u2014 the gated-ralph per-lap directive\n\nYou are ONE lap of an autonomous gated loop. A thin orchestrator handed you exactly one already-claimed\nwork-item id via `--item <id>`. Your context is FRESH \u2014 nothing carried over from the previous lap\nexcept the durable disk (the work-graph + the wedge-gated lesson store). That is by design: you are the\ndumb loop's smart body (Inv 1/2). Do the item, then exit with a typed verdict. Do not try to do the\nwhole board \u2014 the orchestrator takes the next item.\n\n## What to do\n\n1. **Resume + load lean.** Read the item (`workgraph_get`) and recall only the lessons/memories relevant\n to it (scoped recall). Do NOT bulk-load the whole store \u2014 fresh context is the point.\n2. **Run the flow.** Drive SCOPE \u2192 AUTHOR \u2192 the 7-phase exactly as an interactive session would. The gate\n is identical for you (it is harness-agnostic). You CANNOT route around it \u2014 `--no-verify` is futile\n (the PreToolUse + git-owned gates both hold), and that is the safety floor that lets you run unattended.\n3. **DECIDE, don't ask.** Surface decisions are yours to settle by the locked principles (rename, format,\n file location, refactor \u2014 Simplicity). Decide and proceed. Permission-fishing is drift, not diligence.\n4. **ESCALATE only the genuine residual.** Stop and emit `HUMAN_REQUIRED` ONLY for: an irreversible /\n outward boundary you cannot cross (npm publish, OTP, force-push, prod deploy, drop table) \u2192\n `IRREVERSIBLE_BOUNDARY`; a genuine product/UX fork the principles cannot settle \u2192 `SCOPE_FORK`. These\n are the things only the human can own. Everything else, you own.\n5. **Ship gated.** Land the work only through the flow (tests + gates green, commit, push if configured).\n Flush any durable lessons learned.\n\n## How to exit (the ONE hard contract)\n\nEnd your run by printing EXACTLY ONE line of the form:\n\n```\nRALPH-EXIT: {\"kind\":\"SHIPPED\"}\n```\n\nThe JSON must be one of:\n\n- `{\"kind\":\"SHIPPED\"}` \u2014 the item is done, gated, committed.\n- `{\"kind\":\"HUMAN_REQUIRED\",\"reason\":\"IRREVERSIBLE_BOUNDARY\",\"payload\":{...}}` \u2014 parked for the human;\n `reason` is one of IRREVERSIBLE_BOUNDARY | SCOPE_FORK | UNRECOVERABLE_WEDGE | BUDGET | RATE_BUDGET |\n BOARD_EMPTY (you will normally only emit the first two; the orchestrator owns the resource reasons).\n- `{\"kind\":\"WEDGE\"}` \u2014 you are genuinely stuck and a fresh lap on the SAME item would not help (the\n orchestrator wedge-marks it so it is not re-attempted, and escalates UNRECOVERABLE_WEDGE).\n\nIf you crash or time out, say nothing special \u2014 the orchestrator's supervisor treats a missing/erroring\nexit as a transient CRASH/TIMEOUT and bounds the retries itself. A clean run with NO tag = `SHIPPED`.\n";
18
+ //# sourceMappingURL=ralph_template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph_template.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/ralph_template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,QAAQ,ixFA2CpB,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * GR.4 — the RALPH.md per-lap directive (the "Ralph constant").
3
+ *
4
+ * This is the STABLE prompt every lap runs (Brodie's Ralph: the same prompt, fresh context, every
5
+ * iteration — Inv 1 dumb-loop + Inv 2 fresh-context). It is NOT the orchestrator's logic; it is the
6
+ * instruction the spawned `claude -p` lap reads. The orchestrator stays a thin non-LLM loop; ALL the
7
+ * intelligence lives in the lap running this directive against the reloaded wedge-gated lessons.
8
+ *
9
+ * The one hard contract between lap and orchestrator: the lap MUST end by emitting a single greppable
10
+ * `RALPH-EXIT: {json}` line whose JSON is a `LapOutcome` (parsed by GR.2 `extractTypedExit`). Everything
11
+ * else (resume, lean load, the 7-phase flow, DECIDE-vs-ESCALATE) is the same discipline a human-driven
12
+ * session follows — the lap is gated identically (the gate is harness-agnostic, GDC).
13
+ *
14
+ * Exported as a string constant (not a shipped file) so the wizard (`ralph_writer.ts`) writes it to
15
+ * `~/.opensquid/RALPH.md` idempotently — no build-time file-copy-to-dist concern.
16
+ */
17
+ export const RALPH_MD = `# RALPH.md — the gated-ralph per-lap directive
18
+
19
+ You are ONE lap of an autonomous gated loop. A thin orchestrator handed you exactly one already-claimed
20
+ work-item id via \`--item <id>\`. Your context is FRESH — nothing carried over from the previous lap
21
+ except the durable disk (the work-graph + the wedge-gated lesson store). That is by design: you are the
22
+ dumb loop's smart body (Inv 1/2). Do the item, then exit with a typed verdict. Do not try to do the
23
+ whole board — the orchestrator takes the next item.
24
+
25
+ ## What to do
26
+
27
+ 1. **Resume + load lean.** Read the item (\`workgraph_get\`) and recall only the lessons/memories relevant
28
+ to it (scoped recall). Do NOT bulk-load the whole store — fresh context is the point.
29
+ 2. **Run the flow.** Drive SCOPE → AUTHOR → the 7-phase exactly as an interactive session would. The gate
30
+ is identical for you (it is harness-agnostic). You CANNOT route around it — \`--no-verify\` is futile
31
+ (the PreToolUse + git-owned gates both hold), and that is the safety floor that lets you run unattended.
32
+ 3. **DECIDE, don't ask.** Surface decisions are yours to settle by the locked principles (rename, format,
33
+ file location, refactor — Simplicity). Decide and proceed. Permission-fishing is drift, not diligence.
34
+ 4. **ESCALATE only the genuine residual.** Stop and emit \`HUMAN_REQUIRED\` ONLY for: an irreversible /
35
+ outward boundary you cannot cross (npm publish, OTP, force-push, prod deploy, drop table) →
36
+ \`IRREVERSIBLE_BOUNDARY\`; a genuine product/UX fork the principles cannot settle → \`SCOPE_FORK\`. These
37
+ are the things only the human can own. Everything else, you own.
38
+ 5. **Ship gated.** Land the work only through the flow (tests + gates green, commit, push if configured).
39
+ Flush any durable lessons learned.
40
+
41
+ ## How to exit (the ONE hard contract)
42
+
43
+ End your run by printing EXACTLY ONE line of the form:
44
+
45
+ \`\`\`
46
+ RALPH-EXIT: {"kind":"SHIPPED"}
47
+ \`\`\`
48
+
49
+ The JSON must be one of:
50
+
51
+ - \`{"kind":"SHIPPED"}\` — the item is done, gated, committed.
52
+ - \`{"kind":"HUMAN_REQUIRED","reason":"IRREVERSIBLE_BOUNDARY","payload":{...}}\` — parked for the human;
53
+ \`reason\` is one of IRREVERSIBLE_BOUNDARY | SCOPE_FORK | UNRECOVERABLE_WEDGE | BUDGET | RATE_BUDGET |
54
+ BOARD_EMPTY (you will normally only emit the first two; the orchestrator owns the resource reasons).
55
+ - \`{"kind":"WEDGE"}\` — you are genuinely stuck and a fresh lap on the SAME item would not help (the
56
+ orchestrator wedge-marks it so it is not re-attempted, and escalates UNRECOVERABLE_WEDGE).
57
+
58
+ If you crash or time out, say nothing special — the orchestrator's supervisor treats a missing/erroring
59
+ exit as a transient CRASH/TIMEOUT and bounds the retries itself. A clean run with NO tag = \`SHIPPED\`.
60
+ `;
61
+ //# sourceMappingURL=ralph_template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph_template.js","sourceRoot":"","sources":["../../../src/runtime/ralph/ralph_template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CvB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * GR.3 — the lap-supervisor: bounded respawn + observable heartbeat (Inv 6: no silent death, no
3
+ * unbounded retry).
4
+ *
5
+ * `superviseLap` runs a lap, retrying ONLY on CRASH/TIMEOUT (every other outcome — SHIPPED,
6
+ * HUMAN_REQUIRED, WEDGE — is terminal and returned as-is). Retries are BOUNDED by `maxRetries`; on
7
+ * exhaustion it returns a typed `HUMAN_REQUIRED{UNRECOVERABLE_WEDGE}` rather than looping forever or
8
+ * dying quietly. The `heartbeat` callback is the OBSERVABLE liveness tick (the orchestrator passes a
9
+ * lease-refresh, the `live_session_lease` freshness model) — liveness is a signal, not a hope.
10
+ *
11
+ * Cost propagates: each attempt bills `costUsd`, accumulated across retries, so GR.4 can sum the Inv 11
12
+ * running budget even when a crash-then-ship spent on both attempts.
13
+ *
14
+ * Imported by: src/runtime/ralph/orchestrator.ts (GR.4).
15
+ */
16
+ import type { LapOutcome } from './lap_outcome.js';
17
+ export type LapResult = LapOutcome & {
18
+ costUsd: number;
19
+ };
20
+ export interface SuperviseOpts {
21
+ maxRetries: number;
22
+ /** Backoff before the n-th retry (n is 0-based attempt index just completed). */
23
+ backoffMs: (attempt: number) => number;
24
+ /** Observable liveness tick, fired once per attempt (e.g. refreshLease). */
25
+ heartbeat: () => void;
26
+ /** Injectable sleep (tests pass a no-op); defaults to a real timer. */
27
+ sleep?: (ms: number) => Promise<void>;
28
+ }
29
+ export declare function superviseLap(run: () => Promise<LapResult>, opts: SuperviseOpts): Promise<LapResult>;
30
+ //# sourceMappingURL=supervisor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.d.ts","sourceRoot":"","sources":["../../../src/runtime/ralph/supervisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACvC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,uEAAuE;IACvE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAID,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,EAC7B,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,SAAS,CAAC,CAmBpB"}
@@ -0,0 +1,24 @@
1
+ const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
2
+ export async function superviseLap(run, opts) {
3
+ const sleep = opts.sleep ?? realSleep;
4
+ let costUsd = 0;
5
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
6
+ opts.heartbeat();
7
+ let out = null;
8
+ try {
9
+ out = await run();
10
+ costUsd += out.costUsd;
11
+ }
12
+ catch {
13
+ out = null; // a thrown run is treated as CRASH (retryable)
14
+ }
15
+ if (out !== null && out.kind !== 'CRASH' && out.kind !== 'TIMEOUT') {
16
+ return { ...out, costUsd }; // terminal: SHIPPED / HUMAN_REQUIRED / WEDGE
17
+ }
18
+ if (attempt < opts.maxRetries)
19
+ await sleep(opts.backoffMs(attempt));
20
+ }
21
+ // bounded → escalate, never silent
22
+ return { kind: 'HUMAN_REQUIRED', reason: 'UNRECOVERABLE_WEDGE', costUsd };
23
+ }
24
+ //# sourceMappingURL=supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../../src/runtime/ralph/supervisor.ts"],"names":[],"mappings":"AA6BA,MAAM,SAAS,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAEvF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAA6B,EAC7B,IAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,CAAC;IACtC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,IAAI,GAAG,GAAqB,IAAI,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC;YAClB,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,IAAI,CAAC,CAAC,+CAA+C;QAC7D,CAAC;QACD,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACnE,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,6CAA6C;QAC3E,CAAC;QACD,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU;YAAE,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IACD,mCAAmC;IACnC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { Command } from 'commander';
2
+ import { runOneShotCli } from '../../runtime/spawn_lifecycle.js';
3
+ import type { Issue } from '../../workgraph/types.js';
4
+ import { type RalphConfig } from '../../runtime/ralph/orchestrator.js';
5
+ import type { LapResult } from '../../runtime/ralph/supervisor.js';
6
+ import { type ChatSend } from '../../runtime/ralph/escalator.js';
7
+ import { type RalphConfigFile } from '../wizard/ralph_writer.js';
8
+ /** Hydrate the persisted scalar config into the orchestrator's runtime `RalphConfig` (closures rebuilt). */
9
+ export declare function buildRalphConfig(file: RalphConfigFile, opts: {
10
+ once: boolean;
11
+ maxBudgetUsd?: number;
12
+ }): RalphConfig;
13
+ /** Build the per-lap runner: spawn `claude -p RALPH.md --item <id>`, parse the typed exit. A deadline
14
+ * overrun (group-SIGKILL) becomes the typed TIMEOUT, NOT a CRASH; a genuine spawn failure rethrows → CRASH. */
15
+ export declare function makeSpawnLap(cfg: RalphConfig, file: RalphConfigFile, runCli?: typeof runOneShotCli): (item: Issue) => Promise<LapResult>;
16
+ /** The real chat transport: a one-shot UDS JSON-RPC `send` to the chat-daemon (the live Telegram path
17
+ * `chat_send` uses). `ok:false` on any unreachable/error so the escalator stays undroppable. */
18
+ export declare const daemonChatSend: ChatSend;
19
+ export declare function registerRalph(program: Command): Command;
20
+ //# sourceMappingURL=ralph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralph.d.ts","sourceRoot":"","sources":["../../../src/setup/cli/ralph.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAGjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAGnE,OAAO,EAAiB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAChF,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAElF,4GAA4G;AAC5G,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,WAAW,CAYb;AAED;+GAC+G;AAC/G,wBAAgB,YAAY,CAC1B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,eAAe,EACrB,MAAM,GAAE,OAAO,aAA6B,GAC3C,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,SAAS,CAAC,CA6BrC;AAED;gGACgG;AAChG,eAAO,MAAM,cAAc,EAAE,QAwCzB,CAAC;AAEL,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAqEvD"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * GR.4 — the `opensquid loop` CLI: the user-invoked entry that ASSEMBLES the orchestrator's injected deps
3
+ * and runs the gated-ralph loop. A thin wire — all logic lives in the unit-tested pieces it composes
4
+ * (runRalphLoop, chatEscalator, parseLapOutcome, the GR.1–3 store ops). The loop is a CLI COMMAND, not a
5
+ * daemon (opensquid stays an MCP server / tool provider; the agent loop runs inside the spawned harness).
6
+ *
7
+ * Commands:
8
+ * opensquid loop [--once] [--max-budget-usd <n>] — run the loop (read ready → claim → lap → repeat)
9
+ * opensquid loop resolve <itemId> --misclassified — the human-override residual-shrink path (GR.4)
10
+ *
11
+ * Imports from: commander, node:net, ../../runtime/paths.js, ../../runtime/spawn_lifecycle.js,
12
+ * ../../runtime/ralph/*, ../../workgraph/*, ../wizard/ralph_writer.js.
13
+ */
14
+ import { connect } from 'node:net';
15
+ import { join } from 'node:path';
16
+ import { OPENSQUID_HOME, chatDaemonSockPath } from '../../runtime/paths.js';
17
+ import { runOneShotCli } from '../../runtime/spawn_lifecycle.js';
18
+ import { workGraphStore } from '../../workgraph/store.js';
19
+ import { claimAudience } from '../../workgraph/audience.js';
20
+ import { runRalphLoop, resolveParked } from '../../runtime/ralph/orchestrator.js';
21
+ import { parseLapOutcome } from '../../runtime/ralph/lap_outcome.js';
22
+ import { recordMisclassification } from '../../runtime/ralph/decision_classifier.js';
23
+ import { chatEscalator } from '../../runtime/ralph/escalator.js';
24
+ import { readRalphConfig } from '../wizard/ralph_writer.js';
25
+ /** Hydrate the persisted scalar config into the orchestrator's runtime `RalphConfig` (closures rebuilt). */
26
+ export function buildRalphConfig(file, opts) {
27
+ return {
28
+ authMode: file.authMode,
29
+ maxBudgetUsd: opts.maxBudgetUsd ?? file.maxBudgetUsd,
30
+ claimTtlSec: file.claimTtlSec,
31
+ once: opts.once,
32
+ supervise: {
33
+ maxRetries: file.maxRetries,
34
+ backoffMs: (attempt) => file.backoffBaseMs * 2 ** attempt, // exponential from the base
35
+ heartbeat: () => undefined, // a future lease-refresh; liveness tick is a no-op for the CLI run
36
+ },
37
+ };
38
+ }
39
+ /** Build the per-lap runner: spawn `claude -p RALPH.md --item <id>`, parse the typed exit. A deadline
40
+ * overrun (group-SIGKILL) becomes the typed TIMEOUT, NOT a CRASH; a genuine spawn failure rethrows → CRASH. */
41
+ export function makeSpawnLap(cfg, file, runCli = runOneShotCli) {
42
+ return async (item) => {
43
+ let stdout;
44
+ try {
45
+ stdout = await runCli({
46
+ cli: file.harness.cli, // Inv 10 — harness is a parameter
47
+ args: [
48
+ '-p',
49
+ file.harness.ralphMdPath,
50
+ '--item',
51
+ item.id,
52
+ '--output-format',
53
+ 'json',
54
+ '--max-budget-usd',
55
+ String(cfg.maxBudgetUsd),
56
+ '--dangerously-skip-permissions',
57
+ ],
58
+ prompt: '',
59
+ timeoutMs: file.wallClockMs,
60
+ markSubagent: true,
61
+ timeoutError: () => Object.assign(new Error('lap timeout'), { __timeout: true }),
62
+ });
63
+ }
64
+ catch (e) {
65
+ if (e.__timeout === true)
66
+ return { kind: 'TIMEOUT', costUsd: 0 };
67
+ throw e; // genuine spawn/IO failure → superviseLap maps it to CRASH
68
+ }
69
+ const { outcome, costUsd } = parseLapOutcome(stdout);
70
+ return { ...outcome, costUsd };
71
+ };
72
+ }
73
+ /** The real chat transport: a one-shot UDS JSON-RPC `send` to the chat-daemon (the live Telegram path
74
+ * `chat_send` uses). `ok:false` on any unreachable/error so the escalator stays undroppable. */
75
+ export const daemonChatSend = (params) => new Promise((resolve) => {
76
+ const sock = connect(chatDaemonSockPath());
77
+ let buf = '';
78
+ const done = (r) => {
79
+ try {
80
+ sock.end();
81
+ }
82
+ catch {
83
+ /* already closed */
84
+ }
85
+ resolve(r);
86
+ };
87
+ const timer = setTimeout(() => done({ ok: false, reason: 'chat-daemon RPC timeout (5s)' }), 5000);
88
+ sock.on('error', (e) => {
89
+ clearTimeout(timer);
90
+ done({ ok: false, reason: `chat-daemon unreachable: ${e.message}` });
91
+ });
92
+ sock.on('data', (d) => {
93
+ buf += d.toString('utf8');
94
+ const nl = buf.indexOf('\n');
95
+ if (nl === -1)
96
+ return;
97
+ clearTimeout(timer);
98
+ try {
99
+ const res = JSON.parse(buf.slice(0, nl));
100
+ done(res.error ? { ok: false, reason: res.error.message ?? 'send error' } : { ok: true });
101
+ }
102
+ catch (e) {
103
+ done({ ok: false, reason: e instanceof Error ? e.message : 'bad RPC response' });
104
+ }
105
+ });
106
+ sock.write(JSON.stringify({
107
+ jsonrpc: '2.0',
108
+ id: `ralph-${String(Date.now())}`,
109
+ method: 'send',
110
+ params: { channel: params.channel, text: params.text },
111
+ }) + '\n');
112
+ });
113
+ export function registerRalph(program) {
114
+ const loop = program
115
+ .command('loop')
116
+ .description('Run the gated-ralph autonomous loop (composes the work-graph + the coding-flow gate)');
117
+ loop
118
+ .option('--once', 'process a single ready item then stop', false)
119
+ .option('--max-budget-usd <n>', 'API-mode dollar budget for this run (overrides config)')
120
+ .action(async (opts) => {
121
+ const file = await readRalphConfig();
122
+ if (file === null) {
123
+ process.stderr.write('🦑 loop OFF: no ~/.opensquid/ralph.config.json. Run the wizard first.\n');
124
+ process.exitCode = 1;
125
+ return;
126
+ }
127
+ const cfg = buildRalphConfig(file, {
128
+ once: opts.once === true,
129
+ ...(opts.maxBudgetUsd === undefined ? {} : { maxBudgetUsd: Number(opts.maxBudgetUsd) }),
130
+ });
131
+ const wg = workGraphStore({
132
+ dbUrl: `file:${join(OPENSQUID_HOME(), 'workgraph.db')}`,
133
+ sourceDir: join(OPENSQUID_HOME(), 'store', 'issues'),
134
+ });
135
+ await wg.init();
136
+ const result = await runRalphLoop(cfg, {
137
+ wg,
138
+ claimAudience,
139
+ runLap: makeSpawnLap(cfg, file),
140
+ escalate: chatEscalator({ send: daemonChatSend, channel: 'project:telegram' }),
141
+ });
142
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
143
+ });
144
+ loop
145
+ .command('resolve <itemId>')
146
+ .description('Resolve a parked HUMAN_REQUIRED item (the human-override residual-shrink path)')
147
+ .option('--misclassified', 'the escalation was principle-settleable — record + un-wedge for another lap', false)
148
+ .action(async (itemId, opts) => {
149
+ if (opts.misclassified !== true) {
150
+ process.stderr.write('Nothing to do: pass --misclassified to record + un-wedge the item.\n');
151
+ return;
152
+ }
153
+ const wg = workGraphStore({
154
+ dbUrl: `file:${join(OPENSQUID_HOME(), 'workgraph.db')}`,
155
+ sourceDir: join(OPENSQUID_HOME(), 'store', 'issues'),
156
+ });
157
+ await wg.init();
158
+ await resolveParked(itemId, {
159
+ wg,
160
+ recordMisclassification,
161
+ sessionId: process.env.CLAUDE_SESSION_ID ?? '<cli>',
162
+ nowIso: new Date().toISOString(),
163
+ });
164
+ process.stdout.write(`resolved ${itemId}: misclassification recorded, item un-wedged → back in ready.\n`);
165
+ });
166
+ return loop;
167
+ }
168
+ //# sourceMappingURL=ralph.js.map