@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.
- package/CONTRIBUTING.md +89 -0
- package/README.md +401 -0
- package/dist/bin/ralph-bootstrap-aws.d.ts +3 -0
- package/dist/bin/ralph-bootstrap-aws.d.ts.map +1 -0
- package/dist/bin/ralph-bootstrap-aws.js +43 -0
- package/dist/bin/ralph-bootstrap-aws.js.map +1 -0
- package/dist/bin/ralph-fire.d.ts +3 -0
- package/dist/bin/ralph-fire.d.ts.map +1 -0
- package/dist/bin/ralph-fire.js +59 -0
- package/dist/bin/ralph-fire.js.map +1 -0
- package/dist/bin/ralph-gsm.d.ts +3 -0
- package/dist/bin/ralph-gsm.d.ts.map +1 -0
- package/dist/bin/ralph-gsm.js +93 -0
- package/dist/bin/ralph-gsm.js.map +1 -0
- package/dist/bin/ralph-orchestrate.d.ts +3 -0
- package/dist/bin/ralph-orchestrate.d.ts.map +1 -0
- package/dist/bin/ralph-orchestrate.js +20 -0
- package/dist/bin/ralph-orchestrate.js.map +1 -0
- package/dist/bin/ralph-sync-credential.d.ts +3 -0
- package/dist/bin/ralph-sync-credential.d.ts.map +1 -0
- package/dist/bin/ralph-sync-credential.js +44 -0
- package/dist/bin/ralph-sync-credential.js.map +1 -0
- package/dist/bin/ralph-sync-github-pat.d.ts +3 -0
- package/dist/bin/ralph-sync-github-pat.d.ts.map +1 -0
- package/dist/bin/ralph-sync-github-pat.js +93 -0
- package/dist/bin/ralph-sync-github-pat.js.map +1 -0
- package/dist/bin/ralph-tail-logs.d.ts +3 -0
- package/dist/bin/ralph-tail-logs.d.ts.map +1 -0
- package/dist/bin/ralph-tail-logs.js +72 -0
- package/dist/bin/ralph-tail-logs.js.map +1 -0
- package/dist/bin/ralph-validate-config.d.ts +3 -0
- package/dist/bin/ralph-validate-config.d.ts.map +1 -0
- package/dist/bin/ralph-validate-config.js +41 -0
- package/dist/bin/ralph-validate-config.js.map +1 -0
- package/dist/lib/aws-bootstrap.d.ts +53 -0
- package/dist/lib/aws-bootstrap.d.ts.map +1 -0
- package/dist/lib/aws-bootstrap.js +438 -0
- package/dist/lib/aws-bootstrap.js.map +1 -0
- package/dist/lib/aws-clients.d.ts +17 -0
- package/dist/lib/aws-clients.d.ts.map +1 -0
- package/dist/lib/aws-clients.js +25 -0
- package/dist/lib/aws-clients.js.map +1 -0
- package/dist/lib/claude-runner.d.ts +21 -0
- package/dist/lib/claude-runner.d.ts.map +1 -0
- package/dist/lib/claude-runner.js +101 -0
- package/dist/lib/claude-runner.js.map +1 -0
- package/dist/lib/credential-syncer.d.ts +27 -0
- package/dist/lib/credential-syncer.d.ts.map +1 -0
- package/dist/lib/credential-syncer.js +116 -0
- package/dist/lib/credential-syncer.js.map +1 -0
- package/dist/lib/ec2-orchestrator.d.ts +38 -0
- package/dist/lib/ec2-orchestrator.d.ts.map +1 -0
- package/dist/lib/ec2-orchestrator.js +469 -0
- package/dist/lib/ec2-orchestrator.js.map +1 -0
- package/dist/lib/env-loader.d.ts +18 -0
- package/dist/lib/env-loader.d.ts.map +1 -0
- package/dist/lib/env-loader.js +120 -0
- package/dist/lib/env-loader.js.map +1 -0
- package/dist/lib/fire-launcher.d.ts +59 -0
- package/dist/lib/fire-launcher.d.ts.map +1 -0
- package/dist/lib/fire-launcher.js +320 -0
- package/dist/lib/fire-launcher.js.map +1 -0
- package/dist/lib/gh-runner.d.ts +13 -0
- package/dist/lib/gh-runner.d.ts.map +1 -0
- package/dist/lib/gh-runner.js +50 -0
- package/dist/lib/gh-runner.js.map +1 -0
- package/dist/lib/github-state-mutator.d.ts +11 -0
- package/dist/lib/github-state-mutator.d.ts.map +1 -0
- package/dist/lib/github-state-mutator.js +179 -0
- package/dist/lib/github-state-mutator.js.map +1 -0
- package/dist/lib/phase-result-schemas.d.ts +88 -0
- package/dist/lib/phase-result-schemas.d.ts.map +1 -0
- package/dist/lib/phase-result-schemas.js +180 -0
- package/dist/lib/phase-result-schemas.js.map +1 -0
- package/dist/lib/post-hoc-agent-stuck-checker.d.ts +26 -0
- package/dist/lib/post-hoc-agent-stuck-checker.d.ts.map +1 -0
- package/dist/lib/post-hoc-agent-stuck-checker.js +142 -0
- package/dist/lib/post-hoc-agent-stuck-checker.js.map +1 -0
- package/dist/lib/prompt-renderer.d.ts +4 -0
- package/dist/lib/prompt-renderer.d.ts.map +1 -0
- package/dist/lib/prompt-renderer.js +30 -0
- package/dist/lib/prompt-renderer.js.map +1 -0
- package/dist/lib/security-runner.d.ts +7 -0
- package/dist/lib/security-runner.d.ts.map +1 -0
- package/dist/lib/security-runner.js +53 -0
- package/dist/lib/security-runner.js.map +1 -0
- package/dist/lib/structured-log-emitter.d.ts +53 -0
- package/dist/lib/structured-log-emitter.d.ts.map +1 -0
- package/dist/lib/structured-log-emitter.js +122 -0
- package/dist/lib/structured-log-emitter.js.map +1 -0
- package/dist/lib/target-config-schema.d.ts +28 -0
- package/dist/lib/target-config-schema.d.ts.map +1 -0
- package/dist/lib/target-config-schema.js +157 -0
- package/dist/lib/target-config-schema.js.map +1 -0
- package/dist/lib/user-data-renderer.d.ts +20 -0
- package/dist/lib/user-data-renderer.d.ts.map +1 -0
- package/dist/lib/user-data-renderer.js +75 -0
- package/dist/lib/user-data-renderer.js.map +1 -0
- package/lib/cloud-init/system-setup.sh +338 -0
- package/package.json +55 -0
- package/prompts/discovery.md +182 -0
- package/prompts/implementation.md +161 -0
- 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 @@
|
|
|
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
|