@windyroad/risk-scorer 0.18.13-preview.1068 → 0.18.13-preview.1069
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.
|
@@ -33,6 +33,37 @@ function pendingDir() {
|
|
|
33
33
|
return join(process.env.TMPDIR || "/tmp", "claude-risk-pending");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function fieldType(input, field) {
|
|
37
|
+
if (!input || !Object.prototype.hasOwnProperty.call(input, field)) return "absent";
|
|
38
|
+
if (input[field] === null) return "null";
|
|
39
|
+
if (Array.isArray(input[field])) return "array";
|
|
40
|
+
return typeof input[field];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function diagnoseSubagentStop(input, outcome, reason) {
|
|
44
|
+
const dir = pendingDir();
|
|
45
|
+
const path = join(dir, "subagent-stop-diagnostic.json");
|
|
46
|
+
const temporary = join(dir, `.subagent-stop-diagnostic-${process.pid}.tmp`);
|
|
47
|
+
try {
|
|
48
|
+
mkdirSync(dir, { recursive: true });
|
|
49
|
+
writeFileSync(temporary, JSON.stringify({
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
outcome,
|
|
52
|
+
reason,
|
|
53
|
+
event: input?.hook_event_name === "SubagentStop" ? "SubagentStop" : "other",
|
|
54
|
+
fields: Object.fromEntries([
|
|
55
|
+
"session_id",
|
|
56
|
+
"agent_id",
|
|
57
|
+
"agent_type",
|
|
58
|
+
"last_assistant_message",
|
|
59
|
+
].map((field) => [field, fieldType(input, field)])),
|
|
60
|
+
}), { mode: 0o600 });
|
|
61
|
+
renameSync(temporary, path);
|
|
62
|
+
} catch {
|
|
63
|
+
rmSync(temporary, { force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
36
67
|
function statePath(input, target, suffix = "") {
|
|
37
68
|
return join(riskDir(input.session_id), `codex-agent-${Buffer.from(target).toString("base64url")}${suffix}`);
|
|
38
69
|
}
|
|
@@ -72,29 +103,47 @@ function claimTarget(input, target) {
|
|
|
72
103
|
return { claim, done };
|
|
73
104
|
}
|
|
74
105
|
|
|
75
|
-
function pipelineAssessment(output) {
|
|
106
|
+
function pipelineAssessment(output, reject = () => {}) {
|
|
107
|
+
if (typeof output !== "string" || !output) {
|
|
108
|
+
reject("missing-output");
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
76
111
|
const roots = [...output.matchAll(/^RISK_CWD:[ \t]*(.+)$/gm)];
|
|
77
|
-
if (roots.length !== 1)
|
|
112
|
+
if (roots.length !== 1) {
|
|
113
|
+
reject(roots.length ? "multiple-risk-cwd" : "missing-risk-cwd");
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
78
116
|
|
|
79
117
|
const declaredRoot = roots[0][1].trim();
|
|
80
|
-
if (!isAbsolute(declaredRoot))
|
|
118
|
+
if (!isAbsolute(declaredRoot)) {
|
|
119
|
+
reject("relative-risk-cwd");
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
81
122
|
|
|
82
123
|
let root;
|
|
83
124
|
try {
|
|
84
125
|
root = realpathSync(declaredRoot);
|
|
85
126
|
} catch {
|
|
127
|
+
reject("unreadable-risk-cwd");
|
|
86
128
|
return null;
|
|
87
129
|
}
|
|
88
130
|
const git = spawnSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
89
|
-
if (git.status !== 0)
|
|
131
|
+
if (git.status !== 0) {
|
|
132
|
+
reject("not-git-worktree");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
90
135
|
|
|
91
136
|
let gitRoot;
|
|
92
137
|
try {
|
|
93
138
|
gitRoot = realpathSync(git.stdout.trim());
|
|
94
139
|
} catch {
|
|
140
|
+
reject("unreadable-git-root");
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (gitRoot !== root) {
|
|
144
|
+
reject("risk-cwd-not-git-root");
|
|
95
145
|
return null;
|
|
96
146
|
}
|
|
97
|
-
if (gitRoot !== root) return null;
|
|
98
147
|
|
|
99
148
|
let sanitized = output.split(/\r?\n/).filter((line) => !line.startsWith("RISK_CWD:")).join("\n");
|
|
100
149
|
for (const privatePath of new Set([declaredRoot, root])) {
|
|
@@ -133,17 +182,39 @@ function freshReceipt(path) {
|
|
|
133
182
|
}
|
|
134
183
|
|
|
135
184
|
function persistPendingPipeline(input) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
185
|
+
diagnoseSubagentStop(input, "received", "pipeline-receipt-attempt");
|
|
186
|
+
if (input.agent_type !== "wr-risk-scorer:pipeline") {
|
|
187
|
+
diagnoseSubagentStop(input, "rejected", "unexpected-agent-type");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let rejection;
|
|
191
|
+
const assessment = pipelineAssessment(input.last_assistant_message, (reason) => { rejection = reason; });
|
|
192
|
+
if (!assessment) {
|
|
193
|
+
diagnoseSubagentStop(input, "rejected", rejection || "invalid-assessment");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
139
196
|
const id = checkoutId(assessment.root);
|
|
140
197
|
const hash = stateHash(assessment.root);
|
|
141
198
|
const completion = completionId(input, assessment.output);
|
|
142
|
-
if (!hash
|
|
199
|
+
if (!hash) {
|
|
200
|
+
diagnoseSubagentStop(input, "rejected", "state-hash-failed");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!completion) {
|
|
204
|
+
diagnoseSubagentStop(input, "rejected", "missing-completion-identity");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!/^RISK_SCORES: commit=\d+ push=\d+ release=\d+$/m.test(assessment.output)) {
|
|
208
|
+
diagnoseSubagentStop(input, "rejected", "missing-risk-scores");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
143
211
|
mkdirSync(pendingDir(), { recursive: true });
|
|
144
212
|
const path = pendingPath(id, hash, completion);
|
|
145
213
|
for (const candidate of [path, `${path}.done`]) {
|
|
146
|
-
if (freshReceipt(candidate))
|
|
214
|
+
if (freshReceipt(candidate)) {
|
|
215
|
+
diagnoseSubagentStop(input, "duplicate", "fresh-receipt-exists");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
147
218
|
rmSync(candidate, { force: true });
|
|
148
219
|
}
|
|
149
220
|
try {
|
|
@@ -155,8 +226,14 @@ function persistPendingPipeline(input) {
|
|
|
155
226
|
completionId: completion,
|
|
156
227
|
createdAt: Date.now(),
|
|
157
228
|
}), { flag: "wx", mode: 0o600 });
|
|
229
|
+
diagnoseSubagentStop(input, "receipt-written", "checkout-bound-receipt");
|
|
158
230
|
} catch (error) {
|
|
159
|
-
if (error?.code
|
|
231
|
+
if (error?.code === "EEXIST") {
|
|
232
|
+
diagnoseSubagentStop(input, "duplicate", "receipt-race");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
diagnoseSubagentStop(input, "rejected", "receipt-write-failed");
|
|
236
|
+
throw error;
|
|
160
237
|
}
|
|
161
238
|
}
|
|
162
239
|
|
|
@@ -306,6 +383,9 @@ let input;
|
|
|
306
383
|
try {
|
|
307
384
|
input = JSON.parse(body);
|
|
308
385
|
} catch {
|
|
386
|
+
if (process.argv.includes("--subagent-stop")) {
|
|
387
|
+
diagnoseSubagentStop({}, "rejected", "malformed-json");
|
|
388
|
+
}
|
|
309
389
|
process.exit(0);
|
|
310
390
|
}
|
|
311
391
|
|
|
@@ -314,7 +394,12 @@ if (process.argv.includes("--consume-pending")) {
|
|
|
314
394
|
process.exit(process.exitCode || 0);
|
|
315
395
|
}
|
|
316
396
|
|
|
317
|
-
if (!/^[A-Za-z0-9-]+$/.test(input.session_id || ""))
|
|
397
|
+
if (!/^[A-Za-z0-9-]+$/.test(input.session_id || "")) {
|
|
398
|
+
if (process.argv.includes("--subagent-stop")) {
|
|
399
|
+
diagnoseSubagentStop(input, "rejected", "invalid-session-id");
|
|
400
|
+
}
|
|
401
|
+
process.exit(0);
|
|
402
|
+
}
|
|
318
403
|
|
|
319
404
|
if (["collaborationspawn_agent", "spawn_agent", "multi_agent_v1__spawn_agent"].includes(input.tool_name)) {
|
|
320
405
|
rememberSpawn(input);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windyroad/risk-scorer",
|
|
3
|
-
"version": "0.18.13-preview.
|
|
3
|
+
"version": "0.18.13-preview.1069",
|
|
4
4
|
"description": "Pipeline risk scoring, commit/push gates, and secret leak detection",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepack": "node scripts/sync-codex-skills.mjs --pack",
|