@unimatrix27/ralph-harness 1.0.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 (103) hide show
  1. package/CONTRIBUTING.md +89 -0
  2. package/README.md +401 -0
  3. package/dist/bin/ralph-bootstrap-aws.d.ts +3 -0
  4. package/dist/bin/ralph-bootstrap-aws.d.ts.map +1 -0
  5. package/dist/bin/ralph-bootstrap-aws.js +43 -0
  6. package/dist/bin/ralph-bootstrap-aws.js.map +1 -0
  7. package/dist/bin/ralph-fire.d.ts +3 -0
  8. package/dist/bin/ralph-fire.d.ts.map +1 -0
  9. package/dist/bin/ralph-fire.js +59 -0
  10. package/dist/bin/ralph-fire.js.map +1 -0
  11. package/dist/bin/ralph-gsm.d.ts +3 -0
  12. package/dist/bin/ralph-gsm.d.ts.map +1 -0
  13. package/dist/bin/ralph-gsm.js +93 -0
  14. package/dist/bin/ralph-gsm.js.map +1 -0
  15. package/dist/bin/ralph-orchestrate.d.ts +3 -0
  16. package/dist/bin/ralph-orchestrate.d.ts.map +1 -0
  17. package/dist/bin/ralph-orchestrate.js +20 -0
  18. package/dist/bin/ralph-orchestrate.js.map +1 -0
  19. package/dist/bin/ralph-sync-credential.d.ts +3 -0
  20. package/dist/bin/ralph-sync-credential.d.ts.map +1 -0
  21. package/dist/bin/ralph-sync-credential.js +44 -0
  22. package/dist/bin/ralph-sync-credential.js.map +1 -0
  23. package/dist/bin/ralph-sync-github-pat.d.ts +3 -0
  24. package/dist/bin/ralph-sync-github-pat.d.ts.map +1 -0
  25. package/dist/bin/ralph-sync-github-pat.js +93 -0
  26. package/dist/bin/ralph-sync-github-pat.js.map +1 -0
  27. package/dist/bin/ralph-tail-logs.d.ts +3 -0
  28. package/dist/bin/ralph-tail-logs.d.ts.map +1 -0
  29. package/dist/bin/ralph-tail-logs.js +72 -0
  30. package/dist/bin/ralph-tail-logs.js.map +1 -0
  31. package/dist/bin/ralph-validate-config.d.ts +3 -0
  32. package/dist/bin/ralph-validate-config.d.ts.map +1 -0
  33. package/dist/bin/ralph-validate-config.js +41 -0
  34. package/dist/bin/ralph-validate-config.js.map +1 -0
  35. package/dist/lib/aws-bootstrap.d.ts +53 -0
  36. package/dist/lib/aws-bootstrap.d.ts.map +1 -0
  37. package/dist/lib/aws-bootstrap.js +438 -0
  38. package/dist/lib/aws-bootstrap.js.map +1 -0
  39. package/dist/lib/aws-clients.d.ts +17 -0
  40. package/dist/lib/aws-clients.d.ts.map +1 -0
  41. package/dist/lib/aws-clients.js +25 -0
  42. package/dist/lib/aws-clients.js.map +1 -0
  43. package/dist/lib/claude-runner.d.ts +21 -0
  44. package/dist/lib/claude-runner.d.ts.map +1 -0
  45. package/dist/lib/claude-runner.js +101 -0
  46. package/dist/lib/claude-runner.js.map +1 -0
  47. package/dist/lib/credential-syncer.d.ts +27 -0
  48. package/dist/lib/credential-syncer.d.ts.map +1 -0
  49. package/dist/lib/credential-syncer.js +116 -0
  50. package/dist/lib/credential-syncer.js.map +1 -0
  51. package/dist/lib/ec2-orchestrator.d.ts +38 -0
  52. package/dist/lib/ec2-orchestrator.d.ts.map +1 -0
  53. package/dist/lib/ec2-orchestrator.js +469 -0
  54. package/dist/lib/ec2-orchestrator.js.map +1 -0
  55. package/dist/lib/env-loader.d.ts +18 -0
  56. package/dist/lib/env-loader.d.ts.map +1 -0
  57. package/dist/lib/env-loader.js +120 -0
  58. package/dist/lib/env-loader.js.map +1 -0
  59. package/dist/lib/fire-launcher.d.ts +59 -0
  60. package/dist/lib/fire-launcher.d.ts.map +1 -0
  61. package/dist/lib/fire-launcher.js +320 -0
  62. package/dist/lib/fire-launcher.js.map +1 -0
  63. package/dist/lib/gh-runner.d.ts +13 -0
  64. package/dist/lib/gh-runner.d.ts.map +1 -0
  65. package/dist/lib/gh-runner.js +50 -0
  66. package/dist/lib/gh-runner.js.map +1 -0
  67. package/dist/lib/github-state-mutator.d.ts +11 -0
  68. package/dist/lib/github-state-mutator.d.ts.map +1 -0
  69. package/dist/lib/github-state-mutator.js +179 -0
  70. package/dist/lib/github-state-mutator.js.map +1 -0
  71. package/dist/lib/phase-result-schemas.d.ts +88 -0
  72. package/dist/lib/phase-result-schemas.d.ts.map +1 -0
  73. package/dist/lib/phase-result-schemas.js +180 -0
  74. package/dist/lib/phase-result-schemas.js.map +1 -0
  75. package/dist/lib/post-hoc-agent-stuck-checker.d.ts +26 -0
  76. package/dist/lib/post-hoc-agent-stuck-checker.d.ts.map +1 -0
  77. package/dist/lib/post-hoc-agent-stuck-checker.js +142 -0
  78. package/dist/lib/post-hoc-agent-stuck-checker.js.map +1 -0
  79. package/dist/lib/prompt-renderer.d.ts +4 -0
  80. package/dist/lib/prompt-renderer.d.ts.map +1 -0
  81. package/dist/lib/prompt-renderer.js +30 -0
  82. package/dist/lib/prompt-renderer.js.map +1 -0
  83. package/dist/lib/security-runner.d.ts +7 -0
  84. package/dist/lib/security-runner.d.ts.map +1 -0
  85. package/dist/lib/security-runner.js +53 -0
  86. package/dist/lib/security-runner.js.map +1 -0
  87. package/dist/lib/structured-log-emitter.d.ts +53 -0
  88. package/dist/lib/structured-log-emitter.d.ts.map +1 -0
  89. package/dist/lib/structured-log-emitter.js +122 -0
  90. package/dist/lib/structured-log-emitter.js.map +1 -0
  91. package/dist/lib/target-config-schema.d.ts +28 -0
  92. package/dist/lib/target-config-schema.d.ts.map +1 -0
  93. package/dist/lib/target-config-schema.js +157 -0
  94. package/dist/lib/target-config-schema.js.map +1 -0
  95. package/dist/lib/user-data-renderer.d.ts +20 -0
  96. package/dist/lib/user-data-renderer.d.ts.map +1 -0
  97. package/dist/lib/user-data-renderer.js +75 -0
  98. package/dist/lib/user-data-renderer.js.map +1 -0
  99. package/lib/cloud-init/system-setup.sh +338 -0
  100. package/package.json +55 -0
  101. package/prompts/discovery.md +182 -0
  102. package/prompts/implementation.md +161 -0
  103. package/prompts/review.md +135 -0
@@ -0,0 +1,142 @@
1
+ // post-hoc-agent-stuck-checker — slice-9 contract, ported to TS.
2
+ //
3
+ // After the EC2 instance terminates, the launcher checks whether the
4
+ // implementation call left behind a marker:
5
+ //
6
+ // - PR on the target repo carrying `<!-- ralph-launch: <tag> -->` in body
7
+ // - or, if no such PR, a `PICKED_ISSUE=<n>` line in the per-instance
8
+ // CloudWatch stream
9
+ //
10
+ // Outcomes:
11
+ // - PR found → ClearTermination (the run produced output)
12
+ // - No PR, no picked issue → NoPickedIssueRecoverable (e.g. discovery
13
+ // returned NONE/ALL_BLOCKED before any work)
14
+ // - No PR, picked issue → AgentStuckLabelApplied (the launcher labels
15
+ // the source issue `agent-stuck`)
16
+ //
17
+ // Composes:
18
+ // gh-runner — list PRs, edit issue label
19
+ // aws-clients — CloudWatch FilterLogEventsCommand
20
+ // structured-log-emitter — info-line sink for the diagnostic trail
21
+ //
22
+ // Failure mode: any read failure (gh auth, CloudWatch transient, missing
23
+ // log stream) is treated as "no signal recovered" and the check returns
24
+ // without applying a label. The cost of a missed label is a stale
25
+ // `ready-for-agent` issue that the next iteration can re-evaluate; the
26
+ // cost of a wrong label is operator confusion. Asymmetric, so we err on
27
+ // the side of silence.
28
+ import { FilterLogEventsCommand } from "@aws-sdk/client-cloudwatch-logs";
29
+ import { GhRunnerError, runGh } from "./gh-runner.js";
30
+ export const MODULE_PREFIX = "post-hoc-agent-stuck-checker";
31
+ const noop = () => { };
32
+ // findPrWithLaunchTag — list recent PRs (any state) on the target repo
33
+ // whose body contains the `ralph-launch: <tag>` marker. Returns the first
34
+ // match's PR number, or null. Network/auth failures return null.
35
+ export async function findPrWithLaunchTag(repo, launchTag) {
36
+ let parsed;
37
+ try {
38
+ const r = runGh([
39
+ "pr",
40
+ "list",
41
+ "--repo",
42
+ repo,
43
+ "--state",
44
+ "all",
45
+ "--limit",
46
+ "50",
47
+ "--json",
48
+ "number,body",
49
+ ]);
50
+ const data = JSON.parse(r.stdout);
51
+ if (!Array.isArray(data))
52
+ return null;
53
+ parsed = data
54
+ .filter((i) => !!i &&
55
+ typeof i === "object" &&
56
+ typeof i.number === "number")
57
+ .map((i) => ({
58
+ number: i.number,
59
+ body: typeof i.body === "string"
60
+ ? i.body
61
+ : null,
62
+ }));
63
+ }
64
+ catch (err) {
65
+ if (err instanceof GhRunnerError)
66
+ return null;
67
+ return null;
68
+ }
69
+ const marker = `ralph-launch: ${launchTag}`;
70
+ for (const pr of parsed) {
71
+ if (pr.body && pr.body.includes(marker))
72
+ return pr.number;
73
+ }
74
+ return null;
75
+ }
76
+ // fetchPickedIssue — query CloudWatch for the orchestrator's
77
+ // `PICKED_ISSUE=<n>` marker on the per-instance log stream. Returns the
78
+ // integer or null on any failure.
79
+ export async function fetchPickedIssue(clients, logGroup, instanceId) {
80
+ try {
81
+ const r = await clients.logs.send(new FilterLogEventsCommand({
82
+ logGroupName: logGroup,
83
+ logStreamNames: [instanceId],
84
+ filterPattern: '"PICKED_ISSUE="',
85
+ }));
86
+ for (const ev of r.events ?? []) {
87
+ const msg = ev.message ?? "";
88
+ const m = msg.match(/PICKED_ISSUE=(\d+)/);
89
+ if (m && m[1]) {
90
+ const n = Number.parseInt(m[1], 10);
91
+ if (Number.isFinite(n) && n > 0)
92
+ return n;
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ }
101
+ // applyAgentStuckLabel — best-effort `gh issue edit --add-label`. We never
102
+ // throw here: a label that was already applied returns non-zero with a
103
+ // useful message, but for our purposes the post-condition (label present)
104
+ // is what matters, and that's the case whether or not we re-applied.
105
+ export function applyAgentStuckLabel(repo, issue, label, info) {
106
+ try {
107
+ runGh([
108
+ "issue",
109
+ "edit",
110
+ String(issue),
111
+ "--repo",
112
+ repo,
113
+ "--add-label",
114
+ label,
115
+ ]);
116
+ return true;
117
+ }
118
+ catch (err) {
119
+ if (err instanceof GhRunnerError) {
120
+ info(`${MODULE_PREFIX}: gh issue edit returned ${err.exitCode} (label may already be present)`);
121
+ return false;
122
+ }
123
+ throw err;
124
+ }
125
+ }
126
+ export async function postHocCheck(opts) {
127
+ const info = opts.info ?? noop;
128
+ const prNumber = await findPrWithLaunchTag(opts.targetRepo, opts.launchTag);
129
+ if (prNumber !== null) {
130
+ info(`${MODULE_PREFIX}: PR #${prNumber} carries launch tag ${opts.launchTag} — clean termination`);
131
+ return { kind: "ClearTermination", prNumber };
132
+ }
133
+ const issue = await fetchPickedIssue(opts.clients, opts.logGroup, opts.instanceId);
134
+ if (issue === null) {
135
+ info(`${MODULE_PREFIX}: no PR for launch ${opts.launchTag} and no picked issue recoverable from CloudWatch — nothing to label`);
136
+ return { kind: "NoPickedIssueRecoverable" };
137
+ }
138
+ info(`${MODULE_PREFIX}: no PR for launch ${opts.launchTag} — applying ${opts.agentStuckLabel} to ${opts.targetRepo}#${issue}`);
139
+ applyAgentStuckLabel(opts.targetRepo, issue, opts.agentStuckLabel, info);
140
+ return { kind: "AgentStuckLabelApplied", issue };
141
+ }
142
+ //# sourceMappingURL=post-hoc-agent-stuck-checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-hoc-agent-stuck-checker.js","sourceRoot":"","sources":["../../src/lib/post-hoc-agent-stuck-checker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const MODULE_PREFIX = "prompt-renderer";
2
+ export type PromptContext = Readonly<Record<string, string>>;
3
+ export declare function render(template: string, context: PromptContext): string;
4
+ //# sourceMappingURL=prompt-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-renderer.d.ts","sourceRoot":"","sources":["../../src/lib/prompt-renderer.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAI7D,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAQvE"}
@@ -0,0 +1,30 @@
1
+ // prompt-renderer — pure {{...}} substitution for the harness prompt
2
+ // templates. Replaces the bash parameter-expansion blocks in iteration-1's
3
+ // ec2-orchestrator.sh.
4
+ //
5
+ // Contract:
6
+ // render(template, context) — returns a new string with every `{{KEY}}`
7
+ // occurrence replaced by `context[KEY]`. Keys are matched literally (no
8
+ // surrounding whitespace allowed inside the braces — the legacy bash port
9
+ // never produced any). Missing keys are left as the literal placeholder
10
+ // `{{KEY}}`; the call does NOT throw, mirroring the bash behaviour where an
11
+ // unset variable expanded to empty *or* untouched depending on quoting. We
12
+ // pick "leave untouched" so a missing-key bug is grep-visible in the
13
+ // rendered output rather than silently swallowed.
14
+ //
15
+ // Escape: a `{{KEY}}` placeholder preceded by a backslash (`\{{KEY}}`) is
16
+ // emitted as the literal `{{KEY}}` with the backslash stripped — escape
17
+ // hatch for documentation that has to mention placeholder syntax verbatim.
18
+ export const MODULE_PREFIX = "prompt-renderer";
19
+ const PLACEHOLDER_RE = /(\\)?\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g;
20
+ export function render(template, context) {
21
+ return template.replace(PLACEHOLDER_RE, (match, escape, key) => {
22
+ if (escape)
23
+ return `{{${key}}}`;
24
+ if (Object.prototype.hasOwnProperty.call(context, key)) {
25
+ return context[key] ?? "";
26
+ }
27
+ return match;
28
+ });
29
+ }
30
+ //# sourceMappingURL=prompt-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-renderer.js","sourceRoot":"","sources":["../../src/lib/prompt-renderer.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,2EAA2E;AAC3E,uBAAuB;AACvB,EAAE;AACF,YAAY;AACZ,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,0EAA0E;AAC1E,8EAA8E;AAC9E,6EAA6E;AAC7E,uEAAuE;AACvE,oDAAoD;AACpD,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,6EAA6E;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAI/C,MAAM,cAAc,GAAG,wCAAwC,CAAC;AAEhE,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,OAAsB;IAC7D,OAAO,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,EAAE,MAA0B,EAAE,GAAW,EAAE,EAAE;QACzF,IAAI,MAAM;YAAE,OAAO,KAAK,GAAG,IAAI,CAAC;QAChC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;YACvD,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare class SecurityRunnerError extends Error {
2
+ readonly exitCode: number;
3
+ readonly stderr: string;
4
+ constructor(exitCode: number, stderr: string, message: string);
5
+ }
6
+ export declare function readGenericPassword(service: string): string;
7
+ //# sourceMappingURL=security-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-runner.d.ts","sourceRoot":"","sources":["../../src/lib/security-runner.ts"],"names":[],"mappings":"AAuBA,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,QAAQ,EAAE,MAAM;aAChB,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAKlB;AAKD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiC3D"}
@@ -0,0 +1,53 @@
1
+ // security-runner — thin subprocess wrapper around macOS `security` for the
2
+ // one operation credential-syncer needs: read a generic-password Keychain
3
+ // entry by service name.
4
+ //
5
+ // Exists for the same reason gh-runner does — so credential-syncer can be
6
+ // unit-tested by mocking exactly one module instead of stubbing a binary on
7
+ // PATH.
8
+ //
9
+ // Contract:
10
+ // readGenericPassword(service)
11
+ // - exit 0 → resolve with the stdout bytes (the password value)
12
+ // - exit ≠ 0 → throw SecurityRunnerError (the Keychain entry is missing
13
+ // or the service is mistyped)
14
+ //
15
+ // Security: the password value is only ever returned from this function as
16
+ // the resolved string. It is never passed on argv to any subsequent process,
17
+ // never logged, and never included in any error message thrown by this
18
+ // module.
19
+ import { spawnSync } from "node:child_process";
20
+ const MODULE_PREFIX = "security-runner";
21
+ export class SecurityRunnerError extends Error {
22
+ exitCode;
23
+ stderr;
24
+ constructor(exitCode, stderr, message) {
25
+ super(message);
26
+ this.exitCode = exitCode;
27
+ this.stderr = stderr;
28
+ this.name = "SecurityRunnerError";
29
+ }
30
+ }
31
+ // readGenericPassword — runs `security find-generic-password -s <service> -w`
32
+ // and returns the raw password bytes (no trailing newline trimming — the
33
+ // caller decides). Throws SecurityRunnerError on non-zero exit.
34
+ export function readGenericPassword(service) {
35
+ const r = spawnSync("security", ["find-generic-password", "-s", service, "-w"], { encoding: "utf8" });
36
+ if (r.error) {
37
+ throw new SecurityRunnerError(-1, "", `${MODULE_PREFIX}: failed to spawn security: ${r.error.message}`);
38
+ }
39
+ const exitCode = r.status ?? -1;
40
+ const stderr = r.stderr ?? "";
41
+ if (exitCode !== 0) {
42
+ throw new SecurityRunnerError(exitCode, stderr, `${MODULE_PREFIX}: security find-generic-password -s '${service}' failed (exit ${exitCode})`);
43
+ }
44
+ // `security -w` emits the password on stdout followed by a single newline.
45
+ // Strip exactly one trailing newline so the caller gets the bytes the user
46
+ // stored. (Some Keychain entries legitimately contain trailing newlines;
47
+ // matches the `out=$(security ...)` stripping done by the bash port.)
48
+ let stdout = r.stdout ?? "";
49
+ if (stdout.endsWith("\n"))
50
+ stdout = stdout.slice(0, -1);
51
+ return stdout;
52
+ }
53
+ //# sourceMappingURL=security-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-runner.js","sourceRoot":"","sources":["../../src/lib/security-runner.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,yBAAyB;AACzB,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,QAAQ;AACR,EAAE;AACF,YAAY;AACZ,iCAAiC;AACjC,sEAAsE;AACtE,4EAA4E;AAC5E,+CAA+C;AAC/C,EAAE;AACF,2EAA2E;AAC3E,6EAA6E;AAC7E,uEAAuE;AACvE,UAAU;AAEV,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAExC,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAE1B;IACA;IAFlB,YACkB,QAAgB,EAChB,MAAc,EAC9B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,aAAQ,GAAR,QAAQ,CAAQ;QAChB,WAAM,GAAN,MAAM,CAAQ;QAI9B,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,8EAA8E;AAC9E,yEAAyE;AACzE,gEAAgE;AAChE,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,CAAC,GAAG,SAAS,CACjB,UAAU,EACV,CAAC,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAC9C,EAAE,QAAQ,EAAE,MAAM,EAAE,CACrB,CAAC;IAEF,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,IAAI,mBAAmB,CAC3B,CAAC,CAAC,EACF,EAAE,EACF,GAAG,aAAa,+BAA+B,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CACjE,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;IAE9B,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,mBAAmB,CAC3B,QAAQ,EACR,MAAM,EACN,GAAG,aAAa,wCAAwC,OAAO,kBAAkB,QAAQ,GAAG,CAC7F,CAAC;IACJ,CAAC;IAED,2EAA2E;IAC3E,2EAA2E;IAC3E,yEAAyE;IACzE,sEAAsE;IACtE,IAAI,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;IAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,53 @@
1
+ export declare const MODULE_PREFIX = "structured-log-emitter";
2
+ export type Phase = "discovery" | "implementation" | "review";
3
+ export type Outcome = {
4
+ kind: "no_work";
5
+ } | {
6
+ kind: "all_blocked";
7
+ } | {
8
+ kind: "agent_stuck";
9
+ issue: number;
10
+ } | {
11
+ kind: "pr_opened";
12
+ issue: number;
13
+ pr: number;
14
+ review: "none" | "revised";
15
+ };
16
+ export type ImplementationStatus = "PR_OPENED" | "AGENT_STUCK" | "unknown";
17
+ export type ReviewStatus = "NO_REVIEW" | "REVISION_APPLIED" | "unknown";
18
+ export type Clock = () => Date;
19
+ export declare const realClock: Clock;
20
+ export declare function nowUtc(clock?: Clock): string;
21
+ export interface PhaseStartArgs {
22
+ phase: Phase;
23
+ ts: string;
24
+ target?: string;
25
+ issue?: number;
26
+ pr?: number;
27
+ }
28
+ export declare function phaseStart(args: PhaseStartArgs): string;
29
+ export interface PhaseEndArgs {
30
+ phase: Phase;
31
+ durationSec: number;
32
+ ts: string;
33
+ issue?: number;
34
+ pr?: number;
35
+ status?: string;
36
+ }
37
+ export declare function phaseEnd(args: PhaseEndArgs): string;
38
+ export declare function pickedIssue(issue: number): string;
39
+ export declare function outcome(o: Outcome): string;
40
+ export type Sink = (line: string) => void;
41
+ export declare const stdoutSink: Sink;
42
+ export declare class Emitter {
43
+ private readonly sink;
44
+ private readonly clock;
45
+ constructor(sink?: Sink, clock?: Clock);
46
+ ts(): string;
47
+ emit(line: string): void;
48
+ start(args: Omit<PhaseStartArgs, "ts">): string;
49
+ end(args: Omit<PhaseEndArgs, "ts">): string;
50
+ picked(issue: number): string;
51
+ outcome(o: Outcome): string;
52
+ }
53
+ //# sourceMappingURL=structured-log-emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-log-emitter.d.ts","sourceRoot":"","sources":["../../src/lib/structured-log-emitter.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,aAAa,2BAA2B,CAAC;AAEtD,MAAM,MAAM,KAAK,GAAG,WAAW,GAAG,gBAAgB,GAAG,QAAQ,CAAC;AAE9D,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACtC;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC;AAEN,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,aAAa,GAAG,SAAS,CAAC;AAC3E,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,kBAAkB,GAAG,SAAS,CAAC;AAExE,MAAM,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;AAE/B,eAAO,MAAM,SAAS,EAAE,KAAwB,CAAC;AAMjD,wBAAgB,MAAM,CAAC,KAAK,GAAE,KAAiB,GAAG,MAAM,CASvD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CASvD;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAoBnD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAW1C;AAID,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1C,eAAO,MAAM,UAAU,EAAE,IAAkD,CAAC;AAE5E,qBAAa,OAAO;IAEhB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,IAAI,GAAE,IAAiB,EACvB,KAAK,GAAE,KAAiB;IAG3C,EAAE,IAAI,MAAM;IAIZ,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxB,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,MAAM;IAM/C,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,MAAM;IAM3C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAM7B,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM;CAK5B"}
@@ -0,0 +1,122 @@
1
+ // structured-log-emitter — formats the CloudWatch markers the harness emits
2
+ // at every phase boundary. The wire format is byte-identical to iteration 1's
3
+ // ec2-orchestrator.sh output so existing CloudWatch Insights queries / smoke-
4
+ // test grep lines keep working unchanged.
5
+ //
6
+ // Markers:
7
+ // PHASE_START phase=<discovery> ts=<iso> target=<repo>
8
+ // PHASE_START phase=<implementation> ts=<iso> issue=<n>
9
+ // PHASE_START phase=<review> ts=<iso> issue=<n> pr=<m>
10
+ // PHASE_END phase=<discovery> duration_s=<n> ts=<iso>
11
+ // PHASE_END phase=<implementation> duration_s=<n> issue=<n> status=<s> ts=<iso>
12
+ // PHASE_END phase=<review> duration_s=<n> issue=<n> pr=<m> status=<s> ts=<iso>
13
+ // PICKED_ISSUE=<n>
14
+ // OUTCOME=no_work
15
+ // OUTCOME=all_blocked
16
+ // OUTCOME=agent_stuck issue=<n>
17
+ // OUTCOME=pr_opened issue=<n> pr=<m> review=<none|revised>
18
+ //
19
+ // The iteration-1 timestamps come from `date -u +%FT%TZ`, e.g.
20
+ // `2026-05-04T11:43:57Z`. We reproduce that via `nowUtc()` rather than
21
+ // taking it as a free parameter — keeps callers honest and tests
22
+ // deterministic via the injectable `clock` argument.
23
+ export const MODULE_PREFIX = "structured-log-emitter";
24
+ export const realClock = () => new Date();
25
+ // nowUtc — formats a Date as `YYYY-MM-DDTHH:MM:SSZ`, matching iteration-1's
26
+ // `date -u +%FT%TZ`. We can NOT use `Date.toISOString()` directly: it emits
27
+ // fractional milliseconds (`.123Z`), which would break byte-identity with
28
+ // the bash port.
29
+ export function nowUtc(clock = realClock) {
30
+ const d = clock();
31
+ const yyyy = d.getUTCFullYear().toString().padStart(4, "0");
32
+ const mm = (d.getUTCMonth() + 1).toString().padStart(2, "0");
33
+ const dd = d.getUTCDate().toString().padStart(2, "0");
34
+ const hh = d.getUTCHours().toString().padStart(2, "0");
35
+ const mi = d.getUTCMinutes().toString().padStart(2, "0");
36
+ const ss = d.getUTCSeconds().toString().padStart(2, "0");
37
+ return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}Z`;
38
+ }
39
+ export function phaseStart(args) {
40
+ switch (args.phase) {
41
+ case "discovery":
42
+ return `PHASE_START phase=discovery ts=${args.ts} target=${args.target ?? "unknown"}`;
43
+ case "implementation":
44
+ return `PHASE_START phase=implementation ts=${args.ts} issue=${requireNumber(args.issue, "issue")}`;
45
+ case "review":
46
+ return `PHASE_START phase=review ts=${args.ts} issue=${requireNumber(args.issue, "issue")} pr=${requireNumber(args.pr, "pr")}`;
47
+ }
48
+ }
49
+ export function phaseEnd(args) {
50
+ switch (args.phase) {
51
+ case "discovery":
52
+ return `PHASE_END phase=discovery duration_s=${args.durationSec} ts=${args.ts}`;
53
+ case "implementation":
54
+ return (`PHASE_END phase=implementation duration_s=${args.durationSec}` +
55
+ ` issue=${requireNumber(args.issue, "issue")}` +
56
+ ` status=${args.status ?? "unknown"}` +
57
+ ` ts=${args.ts}`);
58
+ case "review":
59
+ return (`PHASE_END phase=review duration_s=${args.durationSec}` +
60
+ ` issue=${requireNumber(args.issue, "issue")}` +
61
+ ` pr=${requireNumber(args.pr, "pr")}` +
62
+ ` status=${args.status ?? "unknown"}` +
63
+ ` ts=${args.ts}`);
64
+ }
65
+ }
66
+ export function pickedIssue(issue) {
67
+ return `PICKED_ISSUE=${issue}`;
68
+ }
69
+ export function outcome(o) {
70
+ switch (o.kind) {
71
+ case "no_work":
72
+ return "OUTCOME=no_work";
73
+ case "all_blocked":
74
+ return "OUTCOME=all_blocked";
75
+ case "agent_stuck":
76
+ return `OUTCOME=agent_stuck issue=${o.issue}`;
77
+ case "pr_opened":
78
+ return `OUTCOME=pr_opened issue=${o.issue} pr=${o.pr} review=${o.review}`;
79
+ }
80
+ }
81
+ export const stdoutSink = (line) => process.stdout.write(`${line}\n`);
82
+ export class Emitter {
83
+ sink;
84
+ clock;
85
+ constructor(sink = stdoutSink, clock = realClock) {
86
+ this.sink = sink;
87
+ this.clock = clock;
88
+ }
89
+ ts() {
90
+ return nowUtc(this.clock);
91
+ }
92
+ emit(line) {
93
+ this.sink(line);
94
+ }
95
+ start(args) {
96
+ const line = phaseStart({ ...args, ts: this.ts() });
97
+ this.sink(line);
98
+ return line;
99
+ }
100
+ end(args) {
101
+ const line = phaseEnd({ ...args, ts: this.ts() });
102
+ this.sink(line);
103
+ return line;
104
+ }
105
+ picked(issue) {
106
+ const line = pickedIssue(issue);
107
+ this.sink(line);
108
+ return line;
109
+ }
110
+ outcome(o) {
111
+ const line = outcome(o);
112
+ this.sink(line);
113
+ return line;
114
+ }
115
+ }
116
+ function requireNumber(v, name) {
117
+ if (typeof v !== "number" || !Number.isFinite(v)) {
118
+ throw new Error(`${MODULE_PREFIX}: ${name} is required`);
119
+ }
120
+ return v;
121
+ }
122
+ //# sourceMappingURL=structured-log-emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-log-emitter.js","sourceRoot":"","sources":["../../src/lib/structured-log-emitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ export type ValidateExitCode = 3 | 4 | 5 | 6;
3
+ export declare class ValidateError extends Error {
4
+ readonly code: ValidateExitCode;
5
+ constructor(code: ValidateExitCode, message: string);
6
+ }
7
+ export declare const TargetConfigSchema: z.ZodObject<{
8
+ build_cmd: z.ZodString;
9
+ test_cmd: z.ZodString;
10
+ branch_prefix: z.ZodString;
11
+ review_bot: z.ZodObject<{
12
+ username: z.ZodString;
13
+ source: z.ZodEnum<{
14
+ review: "review";
15
+ comment: "comment";
16
+ }>;
17
+ }, z.core.$strict>;
18
+ agent_stuck_label: z.ZodOptional<z.ZodString>;
19
+ prompt_extensions: z.ZodOptional<z.ZodObject<{
20
+ discovery: z.ZodOptional<z.ZodString>;
21
+ implementation: z.ZodOptional<z.ZodString>;
22
+ review: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strict>>;
24
+ }, z.core.$strict>;
25
+ export type TargetConfig = z.infer<typeof TargetConfigSchema>;
26
+ export declare function validate(yamlString: string): TargetConfig;
27
+ export declare function moduleErr(message: string): string;
28
+ //# sourceMappingURL=target-config-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-config-schema.d.ts","sourceRoot":"","sources":["../../src/lib/target-config-schema.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE7C,qBAAa,aAAc,SAAQ,KAAK;aAEpB,IAAI,EAAE,gBAAgB;gBAAtB,IAAI,EAAE,gBAAgB,EACtC,OAAO,EAAE,MAAM;CAKlB;AAwBD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;kBASpB,CAAC;AAEZ,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAqBzD;AAmGD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -0,0 +1,157 @@
1
+ // target-config-schema — load + validate a target repo's .ralph/config.yaml.
2
+ //
3
+ // Pure module: YAML string in / typed config out, or throws ValidateError
4
+ // with a numbered exit code that the CLI surfaces as its own exit code.
5
+ //
6
+ // Exit codes (must match lib/target-config-schema.sh — bash module is still
7
+ // sourced by the EC2 orchestrator until slice 5 cuts it over):
8
+ // 0 valid (no error thrown)
9
+ // 2 usage / file not found (CLI only — see ralph-validate-config)
10
+ // 3 malformed yaml
11
+ // 4 missing required field
12
+ // 5 unknown field
13
+ // 6 type error or invalid value
14
+ // 7 reserved (was: missing yq dependency in the bash port; not used here)
15
+ //
16
+ // Schema: see docs/config-schema.md.
17
+ import { parse as parseYaml } from "yaml";
18
+ import { z } from "zod";
19
+ const MODULE_PREFIX = "target-config-schema";
20
+ export class ValidateError extends Error {
21
+ code;
22
+ constructor(code, message) {
23
+ super(message);
24
+ this.code = code;
25
+ this.name = "ValidateError";
26
+ }
27
+ }
28
+ const NonEmptyString = z.string().min(1);
29
+ const ReviewBotSchema = z
30
+ .object({
31
+ username: NonEmptyString,
32
+ source: z.enum(["comment", "review"]),
33
+ })
34
+ .strict();
35
+ const PromptExtensionsSchema = z
36
+ .object({
37
+ discovery: NonEmptyString.optional(),
38
+ implementation: NonEmptyString.optional(),
39
+ review: NonEmptyString.optional(),
40
+ })
41
+ .strict();
42
+ const BranchPrefixSchema = NonEmptyString.refine((v) => !v.includes("/") && !/\s/.test(v), { message: "branch_prefix must not contain '/' or whitespace" });
43
+ export const TargetConfigSchema = z
44
+ .object({
45
+ build_cmd: NonEmptyString,
46
+ test_cmd: NonEmptyString,
47
+ branch_prefix: BranchPrefixSchema,
48
+ review_bot: ReviewBotSchema,
49
+ agent_stuck_label: NonEmptyString.optional(),
50
+ prompt_extensions: PromptExtensionsSchema.optional(),
51
+ })
52
+ .strict();
53
+ export function validate(yamlString) {
54
+ let parsed;
55
+ try {
56
+ parsed = parseYaml(yamlString, { prettyErrors: false });
57
+ }
58
+ catch (err) {
59
+ const detail = err instanceof Error ? err.message : String(err);
60
+ throw new ValidateError(3, `malformed yaml: ${detail}`);
61
+ }
62
+ if (parsed === null || parsed === undefined) {
63
+ throw new ValidateError(6, "top-level must be a mapping, got empty");
64
+ }
65
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
66
+ const got = Array.isArray(parsed) ? "array" : typeof parsed;
67
+ throw new ValidateError(6, `top-level must be a mapping, got ${got}`);
68
+ }
69
+ const result = TargetConfigSchema.safeParse(parsed);
70
+ if (result.success)
71
+ return result.data;
72
+ throw issueToValidateError(result.error, parsed);
73
+ }
74
+ function issueToValidateError(err, input) {
75
+ // Prefer issues that map to the most specific exit code so a multi-issue
76
+ // failure surfaces the same code the bash module would have surfaced
77
+ // (which checks unknown-field rejection BEFORE per-field type checks, and
78
+ // missing-field BEFORE wrong-type).
79
+ const ranked = [...err.issues].sort((a, b) => issueRank(a, input) - issueRank(b, input));
80
+ const issue = ranked[0];
81
+ const path = issue.path.map(String);
82
+ const pathStr = path.length > 0 ? path.join(".") : "<root>";
83
+ if (issue.code === "unrecognized_keys") {
84
+ const keys = issue.keys ?? [];
85
+ const key = keys[0] ?? "<unknown>";
86
+ const fullKey = path.length > 0 ? `${pathStr}.${key}` : key;
87
+ return new ValidateError(5, `unknown field: ${fullKey}`);
88
+ }
89
+ if (issue.code === "invalid_type") {
90
+ const expected = issue.expected ?? "valid value";
91
+ if (!pathExists(input, issue.path)) {
92
+ return new ValidateError(4, `missing required field: ${pathStr}`);
93
+ }
94
+ const got = actualTypeAt(input, issue.path);
95
+ return new ValidateError(6, `${pathStr} must be a ${expected}, got ${got}`);
96
+ }
97
+ if (issue.code === "too_small" || issue.code === "too_big") {
98
+ return new ValidateError(6, `${pathStr} must be a non-empty string`);
99
+ }
100
+ // Zod 4 names enum mismatches "invalid_value"; Zod 3 used "invalid_enum_value".
101
+ if (issue.code === "invalid_value" ||
102
+ issue.code === "invalid_enum_value") {
103
+ const opts = issue.options ??
104
+ issue.values ??
105
+ [];
106
+ const optList = opts.map((o) => `'${String(o)}'`).join(" or ");
107
+ const received = issue.received;
108
+ return new ValidateError(6, `${pathStr} must be ${optList || "a valid value"}, got '${formatReceived(received)}'`);
109
+ }
110
+ if (issue.code === "custom") {
111
+ return new ValidateError(6, `${pathStr}: ${issue.message}`);
112
+ }
113
+ return new ValidateError(6, `${pathStr}: ${issue.message}`);
114
+ }
115
+ function issueRank(issue, input) {
116
+ // Lower wins. Order: unknown field (5) → missing field (4) → everything else (6).
117
+ if (issue.code === "unrecognized_keys")
118
+ return 0;
119
+ if (issue.code === "invalid_type" && !pathExists(input, issue.path))
120
+ return 1;
121
+ return 2;
122
+ }
123
+ function pathExists(input, path) {
124
+ let cur = input;
125
+ for (const p of path) {
126
+ if (cur === null || cur === undefined || typeof cur !== "object")
127
+ return false;
128
+ if (!Object.prototype.hasOwnProperty.call(cur, p))
129
+ return false;
130
+ cur = cur[String(p)];
131
+ }
132
+ return true;
133
+ }
134
+ function actualTypeAt(input, path) {
135
+ let cur = input;
136
+ for (const p of path) {
137
+ if (cur === null || cur === undefined || typeof cur !== "object")
138
+ return "undefined";
139
+ cur = cur[String(p)];
140
+ }
141
+ if (Array.isArray(cur))
142
+ return "array";
143
+ if (cur === null)
144
+ return "null";
145
+ return typeof cur;
146
+ }
147
+ function formatReceived(received) {
148
+ if (received === undefined)
149
+ return "undefined";
150
+ if (typeof received === "string")
151
+ return received;
152
+ return String(received);
153
+ }
154
+ export function moduleErr(message) {
155
+ return `${MODULE_PREFIX}: error: ${message}`;
156
+ }
157
+ //# sourceMappingURL=target-config-schema.js.map