@windyroad/architect 1.1.0 → 1.1.1-preview.1385
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 +11 -6
- package/hooks/lib/codex-completion-input.mjs +51 -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 } 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,26 @@ function rememberNative(input) {
|
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
function remember(input) {
|
|
115
|
-
|
|
118
|
+
const dispatch = reviewerDispatch(input, reviewerInstructions);
|
|
119
|
+
if (!dispatch) return;
|
|
120
|
+
if (!dispatch.reviewerTransport && existsSync(nativePath(input.session_id))) return;
|
|
116
121
|
reapTransport();
|
|
117
122
|
const result = response(input);
|
|
118
123
|
const target = normalizeTarget(result.agent_id ?? result.task_name);
|
|
119
124
|
if (!target) return;
|
|
120
125
|
mkdirSync(riskDir(input.session_id), { recursive: true });
|
|
121
126
|
clear(input.session_id, target);
|
|
122
|
-
if (input.tool_input?.agent_type !== role) return;
|
|
123
127
|
const bound = checkout(input.cwd || process.cwd());
|
|
124
128
|
const hash = bound && policyHash(bound.root);
|
|
125
129
|
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() };
|
|
130
|
+
const registered = { parentSession: input.session_id, role, target, reviewerTransport: dispatch.reviewerTransport, reviewerInstructionsHash: dispatch.reviewerInstructionsHash, ...bound, policyHash: hash, createdAt: Date.now() };
|
|
127
131
|
writeFileSync(statePath(input.session_id, target), JSON.stringify(registered), { mode: 0o600 });
|
|
128
132
|
mkdirSync(transportDir(), { recursive: true, mode: 0o700 });
|
|
129
133
|
writeFileSync(registrationPath(input.session_id, target), JSON.stringify(registered), { mode: 0o600 });
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
function valid(registered, input, target, age) {
|
|
137
|
+
if (!reviewerInstructionsMatch(registered, reviewerInstructions)) return diagnostic("reviewer-instructions-changed", input), false;
|
|
133
138
|
if (registered.role !== role || registered.target !== target) return diagnostic("registration-mismatch", input), false;
|
|
134
139
|
if (!Number.isFinite(age) || age < 0) return diagnostic("invalid-registration-age", input), false;
|
|
135
140
|
if (age >= ttl) return diagnostic("stale-registration", input), false;
|
|
@@ -159,7 +164,6 @@ function writeMarker(input, registered, target, output, completedAt, claim, done
|
|
|
159
164
|
}
|
|
160
165
|
|
|
161
166
|
function complete(input, rawTarget, output) {
|
|
162
|
-
if (existsSync(nativePath(input.session_id))) return;
|
|
163
167
|
const target = completionTarget(rawTarget, riskDir(input.session_id), "codex-architect-");
|
|
164
168
|
if (!target || typeof output !== "string" || !output) return;
|
|
165
169
|
const state = statePath(input.session_id, target);
|
|
@@ -167,6 +171,7 @@ function complete(input, rawTarget, output) {
|
|
|
167
171
|
let registered, age;
|
|
168
172
|
try { registered = JSON.parse(readFileSync(state, "utf8")); age = Date.now() - statSync(state).mtimeMs; }
|
|
169
173
|
catch { return diagnostic("malformed-registration", input); }
|
|
174
|
+
if (existsSync(nativePath(input.session_id)) && registered.reviewerTransport !== "general") return;
|
|
170
175
|
if (!valid(registered, input, target, age)) {
|
|
171
176
|
rmSync(state, { force: true });
|
|
172
177
|
rmSync(registrationPath(registered.parentSession || input.session_id, target), { force: true });
|
|
@@ -220,7 +225,7 @@ function consume(input) {
|
|
|
220
225
|
let pending;
|
|
221
226
|
try { pending = JSON.parse(readFileSync(path, "utf8")); } catch { diagnostic("malformed-receipt", input); continue; }
|
|
222
227
|
if (pending.parentSession !== input.session_id || pending.role !== role) continue;
|
|
223
|
-
if (existsSync(nativePath(input.session_id)) && pending.source !== "subagent-start") continue;
|
|
228
|
+
if (existsSync(nativePath(input.session_id)) && pending.source !== "subagent-start" && pending.reviewerTransport !== "general") continue;
|
|
224
229
|
if (!valid(pending, input, pending.target, Date.now() - pending.completedAt)) continue;
|
|
225
230
|
const claim = `${path}.claim`;
|
|
226
231
|
try { writeFileSync(claim, "", { flag: "wx", mode: 0o600 }); }
|
|
@@ -1,4 +1,43 @@
|
|
|
1
|
-
import { readdirSync } from "node:fs";
|
|
1
|
+
import { readdirSync, readFileSync } 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
|
+
}
|
|
2
41
|
|
|
3
42
|
// Resolve only this session's state files, including completed claims so an
|
|
4
43
|
// ambiguous short name cannot consume a different reviewer after delivery.
|
|
@@ -63,3 +102,14 @@ export function parsedResponse(value) {
|
|
|
63
102
|
export function response(input) {
|
|
64
103
|
return parsedResponse(input?.tool_response);
|
|
65
104
|
}
|
|
105
|
+
|
|
106
|
+
// This only prepares a prompt. Hooks independently verify their own installed
|
|
107
|
+
// instruction source before registering or accepting a general review.
|
|
108
|
+
if (process.argv[2] === "--fallback-prompt" && process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
109
|
+
if (process.argv[2] !== "--fallback-prompt" || process.argv.length !== 5) {
|
|
110
|
+
console.error("Usage: codex-completion-input.mjs --fallback-prompt <role> <installed-agent.md> < request.txt");
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
} else {
|
|
113
|
+
process.stdout.write(reviewerFallbackPrompt(process.argv[3], readFileSync(process.argv[4], "utf8"), readFileSync(0, "utf8")));
|
|
114
|
+
}
|
|
115
|
+
}
|
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
|
|