@windyroad/style-guide 0.6.1 → 0.6.2-preview.1167
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/hooks/hooks.json
CHANGED
|
@@ -47,6 +47,26 @@
|
|
|
47
47
|
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-slide-marker.sh\""
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"matcher": "collaborationspawn_agent|collaborationwait_agent|collaborationinterrupt_agent|spawn_agent|wait_agent|interrupt_agent|close_agent|multi_agent_v1__spawn_agent|multi_agent_v1__wait_agent|multi_agent_v1__close_agent",
|
|
53
|
+
"hooks": [
|
|
54
|
+
{
|
|
55
|
+
"type": "command",
|
|
56
|
+
"command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"SubagentStop": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "^wr-style-guide:agent$",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
|
|
68
|
+
}
|
|
69
|
+
]
|
|
50
70
|
}
|
|
51
71
|
]
|
|
52
72
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const role = "wr-style-guide:agent";
|
|
10
|
+
const writer = join(hookDir, "..", "hooks", "style-guide-mark-reviewed.sh");
|
|
11
|
+
const policy = "docs/STYLE-GUIDE.md";
|
|
12
|
+
const ttlSeconds = process.env.REVIEW_TTL ?? "3600";
|
|
13
|
+
const ttl = Number(ttlSeconds) * 1000;
|
|
14
|
+
|
|
15
|
+
function response(input) {
|
|
16
|
+
if (typeof input.tool_response === "object" && input.tool_response) return input.tool_response;
|
|
17
|
+
try { return JSON.parse(input.tool_response); } catch { return {}; }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stateDir(sessionId) {
|
|
21
|
+
return join(process.env.TMPDIR || "/tmp", `claude-risk-${sessionId}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function statePath(input, target, suffix = "") {
|
|
25
|
+
return join(stateDir(input.session_id), `codex-review-${Buffer.from(role + ":" + target).toString("base64url")}${suffix}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeTarget(target) {
|
|
29
|
+
if (typeof target !== "string") return "";
|
|
30
|
+
return target.startsWith("/root/") ? target.slice("/root/".length) : target;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function policyHash(root) {
|
|
34
|
+
const result = spawnSync("bash", ["-c", 'source "$1"; _substance_hash_path "$2"', "review-policy",
|
|
35
|
+
join(hookDir, "..", "hooks", "lib", "gate-helpers.sh"), policy], { cwd: root, encoding: "utf8" });
|
|
36
|
+
const hash = result.stdout?.trim();
|
|
37
|
+
return result.status === 0 && /^[a-f0-9]{64}$/.test(hash || "") ? hash : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function diagnostic(reason, input) {
|
|
41
|
+
const dir = process.env.TMPDIR || "/tmp";
|
|
42
|
+
const path = join(dir, "codex-review-completion-diagnostic.json");
|
|
43
|
+
const temporary = join(dir, `.codex-review-completion-diagnostic-${process.pid}.tmp`);
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
writeFileSync(temporary, JSON.stringify({
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
reason,
|
|
49
|
+
event: input?.hook_event_name === "SubagentStop" ? "SubagentStop" : input?.tool_name || "unknown",
|
|
50
|
+
role,
|
|
51
|
+
}), { mode: 0o600 });
|
|
52
|
+
renameSync(temporary, path);
|
|
53
|
+
} catch {
|
|
54
|
+
rmSync(temporary, { force: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkout(cwd) {
|
|
59
|
+
if (typeof cwd !== "string" || !cwd) return null;
|
|
60
|
+
let root;
|
|
61
|
+
try { root = realpathSync(cwd); } catch { return null; }
|
|
62
|
+
const git = spawnSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
63
|
+
if (git.status !== 0) return null;
|
|
64
|
+
let gitRoot;
|
|
65
|
+
try { gitRoot = realpathSync(git.stdout.trim()); } catch { return null; }
|
|
66
|
+
if (gitRoot !== root) return null;
|
|
67
|
+
const stat = statSync(root);
|
|
68
|
+
return { root, physical: `${stat.dev}:${stat.ino}` };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function targetFromSpawn(input) {
|
|
72
|
+
const result = response(input);
|
|
73
|
+
return result.agent_id ?? result.task_name;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function clear(input, target) {
|
|
77
|
+
for (const suffix of ["", ".claim", ".done"]) rmSync(statePath(input, target, suffix), { force: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function remember(input) {
|
|
81
|
+
const target = normalizeTarget(targetFromSpawn(input));
|
|
82
|
+
if (!target) return;
|
|
83
|
+
mkdirSync(stateDir(input.session_id), { recursive: true });
|
|
84
|
+
clear(input, target);
|
|
85
|
+
if (input.tool_input?.agent_type !== role) return;
|
|
86
|
+
const bound = checkout(input.cwd || process.cwd());
|
|
87
|
+
if (!bound) {
|
|
88
|
+
diagnostic("invalid-spawn-checkout", input);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const hash = policyHash(bound.root);
|
|
92
|
+
if (!hash) {
|
|
93
|
+
diagnostic("policy-hash-failed", input);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(statePath(input, target), JSON.stringify({ role, target, ...bound, policyHash: hash }), { mode: 0o600 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function claim(input, target) {
|
|
100
|
+
const path = statePath(input, target, ".claim");
|
|
101
|
+
const done = statePath(input, target, ".done");
|
|
102
|
+
if (existsSync(done)) return null;
|
|
103
|
+
try { writeFileSync(path, "", { flag: "wx", mode: 0o600 }); }
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error?.code === "EEXIST") return null;
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
return { path, done };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function complete(input, target, output) {
|
|
112
|
+
target = normalizeTarget(target);
|
|
113
|
+
if (!target || typeof output !== "string" || !output) return;
|
|
114
|
+
const path = statePath(input, target);
|
|
115
|
+
if (existsSync(statePath(input, target, ".done"))) return;
|
|
116
|
+
if (!existsSync(path)) {
|
|
117
|
+
diagnostic("missing-parent-registration", input);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let registered, age;
|
|
122
|
+
try {
|
|
123
|
+
registered = JSON.parse(readFileSync(path, "utf8"));
|
|
124
|
+
age = Date.now() - Math.floor(statSync(path).mtimeMs);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
diagnostic("malformed-registration", input);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (registered.role !== role || registered.target !== target) {
|
|
131
|
+
diagnostic("registration-mismatch", input);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!Number.isFinite(age) || age < 0) {
|
|
135
|
+
diagnostic("invalid-registration-age", input);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (age >= ttl) {
|
|
139
|
+
diagnostic("stale-registration", input);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const current = checkout(input.cwd || process.cwd());
|
|
144
|
+
if (!current || current.root !== registered.root || current.physical !== registered.physical) {
|
|
145
|
+
diagnostic("checkout-mismatch", input);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const hash = policyHash(registered.root);
|
|
150
|
+
if (!hash || hash !== registered.policyHash) {
|
|
151
|
+
diagnostic(hash ? "policy-changed" : "policy-hash-failed", input);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const claimed = claim(input, target);
|
|
156
|
+
if (!claimed) return;
|
|
157
|
+
const synthetic = {
|
|
158
|
+
...input,
|
|
159
|
+
cwd: registered.root,
|
|
160
|
+
tool_name: "Agent",
|
|
161
|
+
tool_input: { subagent_type: role, prompt: "" },
|
|
162
|
+
tool_response: { content: [{ type: "text", text: output }] },
|
|
163
|
+
};
|
|
164
|
+
const result = spawnSync(writer, {
|
|
165
|
+
cwd: registered.root,
|
|
166
|
+
env: process.env,
|
|
167
|
+
input: JSON.stringify(synthetic),
|
|
168
|
+
encoding: "utf8",
|
|
169
|
+
});
|
|
170
|
+
if (result.status === 0) {
|
|
171
|
+
renameSync(claimed.path, claimed.done);
|
|
172
|
+
rmSync(path, { force: true });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
rmSync(claimed.path, { force: true });
|
|
176
|
+
diagnostic("marker-writer-failed", input);
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function close(input) {
|
|
181
|
+
complete(input, input.tool_input?.target, response(input).previous_status?.completed);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function wait(input) {
|
|
185
|
+
const statuses = response(input).status;
|
|
186
|
+
if (!statuses || typeof statuses !== "object") return;
|
|
187
|
+
for (const [target, status] of Object.entries(statuses)) complete(input, target, status?.completed);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let body = "";
|
|
191
|
+
process.stdin.setEncoding("utf8");
|
|
192
|
+
for await (const chunk of process.stdin) body += chunk;
|
|
193
|
+
let input;
|
|
194
|
+
try { input = JSON.parse(body); } catch { process.exit(0); }
|
|
195
|
+
if (!/^[A-Za-z0-9-]+$/.test(input.session_id || "")) process.exit(0);
|
|
196
|
+
if (!/^[0-9]+$/.test(ttlSeconds) || !Number.isSafeInteger(ttl) || ttl <= 0) {
|
|
197
|
+
diagnostic("invalid-review-ttl", input);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (["collaborationspawn_agent", "spawn_agent", "multi_agent_v1__spawn_agent"].includes(input.tool_name)) remember(input);
|
|
202
|
+
if (["collaborationinterrupt_agent", "interrupt_agent", "close_agent", "multi_agent_v1__close_agent"].includes(input.tool_name)) close(input);
|
|
203
|
+
if (["collaborationwait_agent", "wait_agent", "multi_agent_v1__wait_agent"].includes(input.tool_name)) wait(input);
|
|
204
|
+
if (input.hook_event_name === "SubagentStop") {
|
|
205
|
+
if (input.agent_type !== role) diagnostic("unrelated-subagent-stop", input);
|
|
206
|
+
else complete(input, input.agent_id, input.last_assistant_message);
|
|
207
|
+
}
|
package/hooks-codex/hooks.json
CHANGED
|
@@ -47,6 +47,26 @@
|
|
|
47
47
|
"command": "bash \"${PLUGIN_ROOT}/hooks-codex/codex-adapter.sh\" \"${PLUGIN_ROOT}/hooks/style-guide-slide-marker.sh\""
|
|
48
48
|
}
|
|
49
49
|
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"matcher": "collaborationspawn_agent|collaborationwait_agent|collaborationinterrupt_agent|spawn_agent|wait_agent|interrupt_agent|close_agent|multi_agent_v1__spawn_agent|multi_agent_v1__wait_agent|multi_agent_v1__close_agent",
|
|
53
|
+
"hooks": [
|
|
54
|
+
{
|
|
55
|
+
"type": "command",
|
|
56
|
+
"command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"SubagentStop": [
|
|
62
|
+
{
|
|
63
|
+
"matcher": "^wr-style-guide:agent$",
|
|
64
|
+
"hooks": [
|
|
65
|
+
{
|
|
66
|
+
"type": "command",
|
|
67
|
+
"command": "node \"${PLUGIN_ROOT}/hooks-codex/codex-agent-completion.mjs\""
|
|
68
|
+
}
|
|
69
|
+
]
|
|
50
70
|
}
|
|
51
71
|
]
|
|
52
72
|
}
|