@windyroad/architect 1.1.0 → 1.1.1-preview.1386
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/hooks/codex-agent-completion.mjs +15 -6
- package/hooks/lib/codex-completion-input.mjs +67 -1
- package/package.json +1 -1
- package/skills/capture-adr/SKILL.md +8 -1
- package/skills/create-adr/SKILL.md +8 -1
- package/skills/review-decisions/SKILL.md +8 -1
- package/skills/review-design/SKILL.md +9 -2
|
@@ -5,10 +5,13 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameS
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { CLOSE_TOOLS, SPAWN_TOOLS, WAIT_TOOLS, completionTarget, response } from "./lib/codex-completion-input.mjs";
|
|
8
|
+
import { CLOSE_TOOLS, SPAWN_TOOLS, WAIT_TOOLS, completionTarget, response, reviewerDispatch, reviewerInstructionsMatch, clearGeneralReview } from "./lib/codex-completion-input.mjs";
|
|
9
9
|
|
|
10
10
|
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const role = "wr-architect:agent";
|
|
12
|
+
function reviewerInstructions(candidate) {
|
|
13
|
+
return candidate === role ? readFileSync(join(hookDir, "..", "agents", "agent.md"), "utf8") : null;
|
|
14
|
+
}
|
|
12
15
|
const ttlText = process.env.REVIEW_TTL ?? "3600";
|
|
13
16
|
const ttl = Number(ttlText) * 1000;
|
|
14
17
|
|
|
@@ -112,24 +115,30 @@ function rememberNative(input) {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
function remember(input) {
|
|
115
|
-
|
|
118
|
+
const dispatch = reviewerDispatch(input, reviewerInstructions);
|
|
116
119
|
reapTransport();
|
|
117
120
|
const result = response(input);
|
|
118
121
|
const target = normalizeTarget(result.agent_id ?? result.task_name);
|
|
119
122
|
if (!target) return;
|
|
123
|
+
if (!dispatch?.reviewerTransport && existsSync(nativePath(input.session_id))) {
|
|
124
|
+
clearGeneralReview(input, role, target, { state: statePath(input.session_id, target),
|
|
125
|
+
registration: registrationPath(input.session_id, target), receipt: receiptPath(input.session_id, target) });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
120
128
|
mkdirSync(riskDir(input.session_id), { recursive: true });
|
|
121
129
|
clear(input.session_id, target);
|
|
122
|
-
if (
|
|
130
|
+
if (!dispatch) return;
|
|
123
131
|
const bound = checkout(input.cwd || process.cwd());
|
|
124
132
|
const hash = bound && policyHash(bound.root);
|
|
125
133
|
if (!bound || !hash) return diagnostic(bound ? "policy-hash-failed" : "invalid-spawn-checkout", input);
|
|
126
|
-
const registered = { parentSession: input.session_id, role, target, ...bound, policyHash: hash, createdAt: Date.now() };
|
|
134
|
+
const registered = { parentSession: input.session_id, role, target, reviewerTransport: dispatch.reviewerTransport, reviewerInstructionsHash: dispatch.reviewerInstructionsHash, ...bound, policyHash: hash, createdAt: Date.now() };
|
|
127
135
|
writeFileSync(statePath(input.session_id, target), JSON.stringify(registered), { mode: 0o600 });
|
|
128
136
|
mkdirSync(transportDir(), { recursive: true, mode: 0o700 });
|
|
129
137
|
writeFileSync(registrationPath(input.session_id, target), JSON.stringify(registered), { mode: 0o600 });
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
function valid(registered, input, target, age) {
|
|
141
|
+
if (!reviewerInstructionsMatch(registered, reviewerInstructions)) return diagnostic("reviewer-instructions-changed", input), false;
|
|
133
142
|
if (registered.role !== role || registered.target !== target) return diagnostic("registration-mismatch", input), false;
|
|
134
143
|
if (!Number.isFinite(age) || age < 0) return diagnostic("invalid-registration-age", input), false;
|
|
135
144
|
if (age >= ttl) return diagnostic("stale-registration", input), false;
|
|
@@ -159,7 +168,6 @@ function writeMarker(input, registered, target, output, completedAt, claim, done
|
|
|
159
168
|
}
|
|
160
169
|
|
|
161
170
|
function complete(input, rawTarget, output) {
|
|
162
|
-
if (existsSync(nativePath(input.session_id))) return;
|
|
163
171
|
const target = completionTarget(rawTarget, riskDir(input.session_id), "codex-architect-");
|
|
164
172
|
if (!target || typeof output !== "string" || !output) return;
|
|
165
173
|
const state = statePath(input.session_id, target);
|
|
@@ -167,6 +175,7 @@ function complete(input, rawTarget, output) {
|
|
|
167
175
|
let registered, age;
|
|
168
176
|
try { registered = JSON.parse(readFileSync(state, "utf8")); age = Date.now() - statSync(state).mtimeMs; }
|
|
169
177
|
catch { return diagnostic("malformed-registration", input); }
|
|
178
|
+
if (existsSync(nativePath(input.session_id)) && registered.reviewerTransport !== "general") return;
|
|
170
179
|
if (!valid(registered, input, target, age)) {
|
|
171
180
|
rmSync(state, { force: true });
|
|
172
181
|
rmSync(registrationPath(registered.parentSession || input.session_id, target), { force: true });
|
|
@@ -220,7 +229,7 @@ function consume(input) {
|
|
|
220
229
|
let pending;
|
|
221
230
|
try { pending = JSON.parse(readFileSync(path, "utf8")); } catch { diagnostic("malformed-receipt", input); continue; }
|
|
222
231
|
if (pending.parentSession !== input.session_id || pending.role !== role) continue;
|
|
223
|
-
if (existsSync(nativePath(input.session_id)) && pending.source !== "subagent-start") continue;
|
|
232
|
+
if (existsSync(nativePath(input.session_id)) && pending.source !== "subagent-start" && pending.reviewerTransport !== "general") continue;
|
|
224
233
|
if (!valid(pending, input, pending.target, Date.now() - pending.completedAt)) continue;
|
|
225
234
|
const claim = `${path}.claim`;
|
|
226
235
|
try { writeFileSync(claim, "", { flag: "wx", mode: 0o600 }); }
|
|
@@ -1,4 +1,59 @@
|
|
|
1
|
-
import { readdirSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export function reviewerFallbackPrompt(role, instructions, request) {
|
|
7
|
+
return `WR_REVIEWER_FALLBACK: ${role}\n<reviewer-instructions>\n${instructions}</reviewer-instructions>\n\n${request}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// A general reviewer is identified by the installed contract actually supplied
|
|
11
|
+
// at dispatch, never by its task name or its returned verdict.
|
|
12
|
+
export function reviewerDispatch(input, loadInstructions) {
|
|
13
|
+
const options = input.tool_input || {};
|
|
14
|
+
const prompt = options.message ?? options.prompt ?? "";
|
|
15
|
+
const named = options.agent_type;
|
|
16
|
+
try {
|
|
17
|
+
if (named && named !== "default") {
|
|
18
|
+
return loadInstructions(named) ? { role: named, prompt } : null;
|
|
19
|
+
}
|
|
20
|
+
if (options.fork_turns !== "none" || typeof prompt !== "string") return null;
|
|
21
|
+
const role = prompt.match(/^WR_REVIEWER_FALLBACK: ([a-z0-9-]+:[a-z0-9-]+)\n/)?.[1];
|
|
22
|
+
if (!role) return null;
|
|
23
|
+
const instructions = loadInstructions(role);
|
|
24
|
+
if (!instructions) return null;
|
|
25
|
+
const prefix = `WR_REVIEWER_FALLBACK: ${role}\n<reviewer-instructions>\n${instructions}</reviewer-instructions>\n\n`;
|
|
26
|
+
if (!prompt.startsWith(prefix) || !prompt.slice(prefix.length).trim()) return null;
|
|
27
|
+
return { role, prompt: prompt.slice(prefix.length), reviewerTransport: "general",
|
|
28
|
+
reviewerInstructionsHash: createHash("sha256").update(instructions).digest("hex") };
|
|
29
|
+
} catch { return null; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function reviewerInstructionsMatch(registered, loadInstructions) {
|
|
33
|
+
if (registered.reviewerTransport === undefined) return true;
|
|
34
|
+
if (registered.reviewerTransport !== "general") return false;
|
|
35
|
+
try {
|
|
36
|
+
const instructions = loadInstructions(registered.role);
|
|
37
|
+
return Boolean(instructions && registered.reviewerInstructionsHash ===
|
|
38
|
+
createHash("sha256").update(instructions).digest("hex"));
|
|
39
|
+
} catch { return false; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Native replay claims share a target key with legacy receipts. In native mode
|
|
43
|
+
// invalidate only a demonstrably owned general registration, never its claims.
|
|
44
|
+
export function clearGeneralReview(input, role, target, paths) {
|
|
45
|
+
const owned = value => value.reviewerTransport === "general" && value.role === role &&
|
|
46
|
+
value.parentSession === input.session_id && value.target === target;
|
|
47
|
+
try {
|
|
48
|
+
if (!owned(JSON.parse(readFileSync(paths.registration, "utf8")))) return false;
|
|
49
|
+
} catch { return false; }
|
|
50
|
+
rmSync(paths.state, { force: true });
|
|
51
|
+
rmSync(paths.registration, { force: true });
|
|
52
|
+
try {
|
|
53
|
+
if (owned(JSON.parse(readFileSync(paths.receipt, "utf8")))) rmSync(paths.receipt, { force: true });
|
|
54
|
+
} catch { /* No pending general receipt to invalidate. */ }
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
2
57
|
|
|
3
58
|
// Resolve only this session's state files, including completed claims so an
|
|
4
59
|
// ambiguous short name cannot consume a different reviewer after delivery.
|
|
@@ -63,3 +118,14 @@ export function parsedResponse(value) {
|
|
|
63
118
|
export function response(input) {
|
|
64
119
|
return parsedResponse(input?.tool_response);
|
|
65
120
|
}
|
|
121
|
+
|
|
122
|
+
// This only prepares a prompt. Hooks independently verify their own installed
|
|
123
|
+
// instruction source before registering or accepting a general review.
|
|
124
|
+
if (process.argv[2] === "--fallback-prompt" && process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
125
|
+
if (process.argv[2] !== "--fallback-prompt" || process.argv.length !== 5) {
|
|
126
|
+
console.error("Usage: codex-completion-input.mjs --fallback-prompt <role> <installed-agent.md> < request.txt");
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
} else {
|
|
129
|
+
process.stdout.write(reviewerFallbackPrompt(process.argv[3], readFileSync(process.argv[4], "utf8"), readFileSync(0, "utf8")));
|
|
130
|
+
}
|
|
131
|
+
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,14 @@ allowed-tools: Read, Write, Edit, Bash, Grep, Glob
|
|
|
12
12
|
> that token with the absolute path before running a bundled script; never
|
|
13
13
|
> search the adopter repository or rely on a `wr-architect-*` command being
|
|
14
14
|
> on `PATH`. Spawn the installed `wr-architect:agent` custom agent with the
|
|
15
|
-
> native Codex subagent tool
|
|
15
|
+
> native Codex subagent tool when available. Otherwise start an independent
|
|
16
|
+
> general/default subagent with `fork_turns: "none"`. Prepare its message with
|
|
17
|
+
> `node "<architect-plugin-root>/hooks/lib/codex-completion-input.mjs" --fallback-prompt wr-architect:agent "<architect-plugin-root>/agents/agent.md" < request.txt`.
|
|
18
|
+
> Pass that output verbatim, preserve read-only scope, wait, and call
|
|
19
|
+
> `interrupt_agent` once on the completed child. For cognitive review, supply
|
|
20
|
+
> the complete installed `agents/cog-a11y.md`, rubric and ADR to an independent
|
|
21
|
+
> general reviewer if its named reviewer is unavailable. A FAIL requires
|
|
22
|
+
> remediation, not fallback; never write markers or claim unverified completion.
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
# Capture ADR Skill
|
|
@@ -12,7 +12,14 @@ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, request_user_input, Agent
|
|
|
12
12
|
> that token with the absolute path before running a bundled script; never
|
|
13
13
|
> search the adopter repository or rely on a `wr-architect-*` command being
|
|
14
14
|
> on `PATH`. Spawn the installed `wr-architect:agent` custom agent with the
|
|
15
|
-
> native Codex subagent tool
|
|
15
|
+
> native Codex subagent tool when available. Otherwise start an independent
|
|
16
|
+
> general/default subagent with `fork_turns: "none"`. Prepare its message with
|
|
17
|
+
> `node "<architect-plugin-root>/hooks/lib/codex-completion-input.mjs" --fallback-prompt wr-architect:agent "<architect-plugin-root>/agents/agent.md" < request.txt`.
|
|
18
|
+
> Pass that output verbatim, preserve read-only scope, wait, and call
|
|
19
|
+
> `interrupt_agent` once on the completed child. For cognitive review, supply
|
|
20
|
+
> the complete installed `agents/cog-a11y.md`, rubric and ADR to an independent
|
|
21
|
+
> general reviewer if its named reviewer is unavailable. A FAIL requires
|
|
22
|
+
> remediation, not fallback; never write markers or claim unverified completion.
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
# Architecture Decision Record Generator
|
|
@@ -12,7 +12,14 @@ allowed-tools: Read, Glob, Grep, Bash, Edit, request_user_input, Agent
|
|
|
12
12
|
> that token with the absolute path before running a bundled script; never
|
|
13
13
|
> search the adopter repository or rely on a `wr-architect-*` command being
|
|
14
14
|
> on `PATH`. Spawn the installed `wr-architect:agent` custom agent with the
|
|
15
|
-
> native Codex subagent tool
|
|
15
|
+
> native Codex subagent tool when available. Otherwise start an independent
|
|
16
|
+
> general/default subagent with `fork_turns: "none"`. Prepare its message with
|
|
17
|
+
> `node "<architect-plugin-root>/hooks/lib/codex-completion-input.mjs" --fallback-prompt wr-architect:agent "<architect-plugin-root>/agents/agent.md" < request.txt`.
|
|
18
|
+
> Pass that output verbatim, preserve read-only scope, wait, and call
|
|
19
|
+
> `interrupt_agent` once on the completed child. For cognitive review, supply
|
|
20
|
+
> the complete installed `agents/cog-a11y.md`, rubric and ADR to an independent
|
|
21
|
+
> general reviewer if its named reviewer is unavailable. A FAIL requires
|
|
22
|
+
> remediation, not fallback; never write markers or claim unverified completion.
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
# Review Decisions — human-oversight drain
|
|
@@ -12,7 +12,14 @@ allowed-tools: Read, Glob, Grep, Bash, request_user_input, Skill
|
|
|
12
12
|
> that token with the absolute path before running a bundled script; never
|
|
13
13
|
> search the adopter repository or rely on a `wr-architect-*` command being
|
|
14
14
|
> on `PATH`. Spawn the installed `wr-architect:agent` custom agent with the
|
|
15
|
-
> native Codex subagent tool
|
|
15
|
+
> native Codex subagent tool when available. Otherwise start an independent
|
|
16
|
+
> general/default subagent with `fork_turns: "none"`. Prepare its message with
|
|
17
|
+
> `node "<architect-plugin-root>/hooks/lib/codex-completion-input.mjs" --fallback-prompt wr-architect:agent "<architect-plugin-root>/agents/agent.md" < request.txt`.
|
|
18
|
+
> Pass that output verbatim, preserve read-only scope, wait, and call
|
|
19
|
+
> `interrupt_agent` once on the completed child. For cognitive review, supply
|
|
20
|
+
> the complete installed `agents/cog-a11y.md`, rubric and ADR to an independent
|
|
21
|
+
> general reviewer if its named reviewer is unavailable. A FAIL requires
|
|
22
|
+
> remediation, not fallback; never write markers or claim unverified completion.
|
|
16
23
|
|
|
17
24
|
|
|
18
25
|
# Architecture Compliance Review Skill
|
|
@@ -110,7 +117,7 @@ agent: wr-architect:agent
|
|
|
110
117
|
prompt: <constructed review prompt from step 4>
|
|
111
118
|
```
|
|
112
119
|
|
|
113
|
-
The Codex installer generates `.codex/agents/wr-architect-agent.toml` from the installed plugin's `agents/agent.md` and registers the exact identity `wr-architect:agent`. SessionStart repairs the user-scoped registration when needed. Adopter projects do not need to ship their own agent configuration.
|
|
120
|
+
The Codex installer generates `.codex/agents/wr-architect-agent.toml` from the installed plugin's `agents/agent.md` and registers the exact identity `wr-architect:agent`. SessionStart repairs the user-scoped registration when needed. Adopter projects do not need to ship their own agent configuration. Prefer that named reviewer. If it is unavailable, or the native tool cannot select named reviewers, use an independent general/default subagent with `fork_turns: "none"`. Prepare its message with `node "<architect-plugin-root>/hooks/lib/codex-completion-input.mjs" --fallback-prompt wr-architect:agent "<architect-plugin-root>/agents/agent.md" < request.txt`, where `request.txt` contains the original review request and `<architect-plugin-root>` is the installed plugin root. Pass the output verbatim. Preserve read-only scope, wait for completion, then call `interrupt_agent` once on that child. The normal hook verifies the installed instructions and completed review before recording a result. A substantive FAIL still requires remediation; never write a marker manually. The repo-local `.codex/agents/wr-architect.toml`, kept in sync from `<architect-plugin-root>/agents/agent.md` by `scripts/sync-codex-agents.mjs`, remains the short-name `wr-architect` alias for source-repo dogfooding.
|
|
114
121
|
|
|
115
122
|
Wait for the agent to complete.
|
|
116
123
|
|