@windyroad/architect 1.0.0-preview.1297 → 1.0.1-preview.1299

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.
@@ -123,5 +123,5 @@
123
123
  }
124
124
  },
125
125
  "name": "wr-architect",
126
- "version": "1.0.0"
126
+ "version": "1.0.1"
127
127
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wr-architect",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "author": {
6
6
  "name": "Windy Road Technology",
@@ -63,7 +63,7 @@ if messages:
63
63
  [ -z "$detect_output" ] || printf '%s\n' "$detect_output"
64
64
  [ -z "$stale_output" ] || printf '%s\n' "$stale_output"
65
65
  ;;
66
- subagent-stop)
66
+ subagent-start|subagent-stop)
67
67
  printf '%s' "$INPUT" | node "$SCRIPT_DIR/codex-agent-completion.mjs"
68
68
  ;;
69
69
  pre-tool)
@@ -23,6 +23,8 @@ function statePath(sessionId, target, suffix = "") {
23
23
  return join(riskDir(sessionId), `codex-architect-${Buffer.from(target).toString("base64url")}${suffix}`);
24
24
  }
25
25
  function registrationPath(sessionId, target) { return join(transportDir(), `registration-${id(sessionId, target)}.json`); }
26
+ function registeredPath(value) { return registrationPath(value.parentSession, value.source === "subagent-start" ? `native:${value.target}` : value.target); }
27
+ function nativePath(sessionId) { return join(riskDir(sessionId), "native-architect-transport"); }
26
28
  function receiptPath(sessionId, target, suffix = "") { return join(transportDir(), `receipt-${id(sessionId, target)}.json${suffix}`); }
27
29
 
28
30
  function checkout(cwd) {
@@ -87,7 +89,30 @@ function clear(sessionId, target) {
87
89
  for (const suffix of ["", ".claim", ".done"]) rmSync(receiptPath(sessionId, target, suffix), { force: true });
88
90
  }
89
91
 
92
+ function rememberNative(input) {
93
+ if (input.agent_type !== role || typeof input.agent_id !== "string" || !/^[A-Za-z0-9-]+$/.test(input.agent_id)) return;
94
+ const bound = checkout(input.cwd);
95
+ const hash = bound && policyHash(bound.root);
96
+ if (!bound || !hash) return diagnostic("invalid-native-binding", input);
97
+ const target = input.agent_id;
98
+ if (existsSync(receiptPath(input.session_id, target, ".done")) || existsSync(statePath(input.session_id, target, ".done"))) return;
99
+ const registered = { source: "subagent-start", parentSession: input.session_id, role, target, ...bound, policyHash: hash, createdAt: Date.now() };
100
+ mkdirSync(transportDir(), { recursive: true, mode: 0o700 });
101
+ try { writeFileSync(registeredPath(registered), JSON.stringify(registered), { flag: "wx", mode: 0o600 }); }
102
+ catch (error) {
103
+ if (error?.code !== "EEXIST") throw error;
104
+ const previous = JSON.parse(readFileSync(registeredPath(registered), "utf8"));
105
+ for (const key of ["source", "parentSession", "role", "target", "root", "physical", "policyHash"]) {
106
+ if (previous[key] !== registered[key]) return diagnostic("native-registration-conflict", input);
107
+ }
108
+ }
109
+ mkdirSync(riskDir(input.session_id), { recursive: true });
110
+ try { writeFileSync(nativePath(input.session_id), "", { flag: "wx", mode: 0o600 }); }
111
+ catch (error) { if (error?.code !== "EEXIST") throw error; }
112
+ }
113
+
90
114
  function remember(input) {
115
+ if (existsSync(nativePath(input.session_id))) return;
91
116
  reapTransport();
92
117
  const result = response(input);
93
118
  const target = normalizeTarget(result.agent_id ?? result.task_name);
@@ -125,7 +150,7 @@ function writeMarker(input, registered, target, output, completedAt, claim, done
125
150
  }
126
151
  renameSync(claim, done);
127
152
  rmSync(statePath(registered.parentSession, target), { force: true });
128
- rmSync(registrationPath(registered.parentSession, target), { force: true });
153
+ rmSync(registeredPath(registered), { force: true });
129
154
  const assessedAt = new Date(completedAt);
130
155
  for (const marker of [`/tmp/architect-reviewed-${registered.parentSession}`, `/tmp/architect-reviewed-${registered.parentSession}.hash`, `/tmp/architect-plan-reviewed-${registered.parentSession}`]) {
131
156
  if (existsSync(marker)) utimesSync(marker, assessedAt, assessedAt);
@@ -134,6 +159,7 @@ function writeMarker(input, registered, target, output, completedAt, claim, done
134
159
  }
135
160
 
136
161
  function complete(input, rawTarget, output) {
162
+ if (existsSync(nativePath(input.session_id))) return;
137
163
  const target = completionTarget(rawTarget, riskDir(input.session_id), "codex-architect-");
138
164
  if (!target || typeof output !== "string" || !output) return;
139
165
  const state = statePath(input.session_id, target);
@@ -171,8 +197,11 @@ function persist(input) {
171
197
  if (!target || typeof output !== "string" || !output) return;
172
198
  if (parsedVerdict(output) !== "PASS") return diagnostic("non-pass-output", input);
173
199
  const registrationsForCheckout = registrations(input, target);
174
- const exact = registrationsForCheckout.filter(({ registered }) => registered.target === target);
175
- const candidates = exact.length ? exact : registrationsForCheckout.filter(({ registered }) => registered.parentSession === input.session_id);
200
+ const native = registrationsForCheckout.filter(({ registered }) => registered.source === "subagent-start");
201
+ const nativeExact = native.filter(({ registered }) => registered.parentSession === input.session_id && registered.target === input.agent_id);
202
+ const legacy = registrationsForCheckout.filter(({ registered }) => registered.source !== "subagent-start");
203
+ const exact = legacy.filter(({ registered }) => registered.target === target);
204
+ const candidates = existsSync(nativePath(input.session_id)) || nativeExact.length ? nativeExact : exact.length ? exact : legacy.filter(({ registered }) => registered.parentSession === input.session_id);
176
205
  if (candidates.length !== 1) return diagnostic(candidates.length ? "ambiguous-parent-registration" : "missing-parent-registration", input);
177
206
  const { registered, age } = candidates[0];
178
207
  target = registered.target;
@@ -191,6 +220,7 @@ function consume(input) {
191
220
  let pending;
192
221
  try { pending = JSON.parse(readFileSync(path, "utf8")); } catch { diagnostic("malformed-receipt", input); continue; }
193
222
  if (pending.parentSession !== input.session_id || pending.role !== role) continue;
223
+ if (existsSync(nativePath(input.session_id)) && pending.source !== "subagent-start") continue;
194
224
  if (!valid(pending, input, pending.target, Date.now() - pending.completedAt)) continue;
195
225
  const claim = `${path}.claim`;
196
226
  try { writeFileSync(claim, "", { flag: "wx", mode: 0o600 }); }
@@ -215,13 +245,14 @@ if (!/^[A-Za-z0-9-]+$/.test(input.session_id || "")) process.exit(0);
215
245
  if (!/^[0-9]+$/.test(ttlText) || !Number.isSafeInteger(ttl) || ttl <= 0) process.exit(0);
216
246
 
217
247
  try {
248
+ if (input.hook_event_name === "SubagentStart") rememberNative(input);
218
249
  if (SPAWN_TOOLS.has(input.tool_name)) remember(input);
219
250
  if (CLOSE_TOOLS.has(input.tool_name)) close(input);
220
251
  if (WAIT_TOOLS.has(input.tool_name)) wait(input);
221
252
  if (input.hook_event_name === "UserPromptSubmit" || input.hook_event_name === "PreToolUse" || ["Bash", "Edit", "Write", "ExitPlanMode"].includes(input.tool_name)) consume(input);
222
253
  if (input.hook_event_name === "SubagentStop") {
223
254
  if (input.agent_type !== role) diagnostic("unrelated-subagent-stop", input);
224
- else if (existsSync(statePath(input.session_id, normalizeTarget(input.agent_id)))) complete(input, input.agent_id, input.last_assistant_message);
255
+ else if (!existsSync(nativePath(input.session_id)) && existsSync(statePath(input.session_id, normalizeTarget(input.agent_id)))) complete(input, input.agent_id, input.last_assistant_message);
225
256
  else persist(input);
226
257
  }
227
258
  } catch {
package/hooks/hooks.json CHANGED
@@ -1,21 +1,69 @@
1
1
  {
2
2
  "hooks": {
3
3
  "SessionStart": [
4
- { "matcher": "startup", "hooks": [
5
- { "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh session-start" }
6
- ] }
4
+ {
5
+ "matcher": "startup",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh session-start"
10
+ }
11
+ ]
12
+ }
7
13
  ],
8
14
  "UserPromptSubmit": [
9
- { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh user-prompt" }] }
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh user-prompt"
20
+ }
21
+ ]
22
+ }
10
23
  ],
11
24
  "PreToolUse": [
12
- { "matcher": "Bash|Edit|Write|ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh pre-tool" }] }
25
+ {
26
+ "matcher": "Bash|Edit|Write|ExitPlanMode",
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh pre-tool"
31
+ }
32
+ ]
33
+ }
13
34
  ],
14
35
  "PostToolUse": [
15
- { "matcher": "Agent|Bash|Edit|Skill|Write|collaboration.spawn_agent|collaboration.wait_agent|collaboration.interrupt_agent|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", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh post-tool" }] }
36
+ {
37
+ "matcher": "Agent|Bash|Edit|Skill|Write|collaboration.spawn_agent|collaboration.wait_agent|collaboration.interrupt_agent|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",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh post-tool"
42
+ }
43
+ ]
44
+ }
16
45
  ],
17
46
  "SubagentStop": [
18
- { "matcher": "^wr-architect:agent$", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh subagent-stop" }] }
47
+ {
48
+ "matcher": "^wr-architect:agent$",
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh subagent-stop"
53
+ }
54
+ ]
55
+ }
56
+ ],
57
+ "SubagentStart": [
58
+ {
59
+ "matcher": "^wr-architect:agent$",
60
+ "hooks": [
61
+ {
62
+ "type": "command",
63
+ "command": "${CLAUDE_PLUGIN_ROOT}/hooks/architect-dispatch.sh subagent-start"
64
+ }
65
+ ]
66
+ }
19
67
  ]
20
68
  }
21
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/architect",
3
- "version": "1.0.0-preview.1297",
3
+ "version": "1.0.1-preview.1299",
4
4
  "description": "Architecture decision enforcement for AI coding agents",
5
5
  "bin": {
6
6
  "windyroad-architect": "./bin/install.mjs"