@webpresso/agent-kit 3.1.26 → 3.1.30

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.
Files changed (150) hide show
  1. package/catalog/AGENTS.md.tpl +7 -11
  2. package/catalog/agent/commands/verify.md +2 -2
  3. package/catalog/agent/rules/changeset-release.md +4 -2
  4. package/catalog/agent/rules/ci-cost-local-first.md +139 -0
  5. package/catalog/agent/rules/ci-test-perf.md +5 -1
  6. package/catalog/agent/rules/cmd-execution.md +9 -0
  7. package/catalog/agent/rules/pre-implementation.md +2 -1
  8. package/catalog/agent/rules/test-scan-perf.md +80 -0
  9. package/catalog/agent/rules/workflow-skills-routing.md +9 -4
  10. package/catalog/agent/skills/autopilot/SKILL.md +48 -11
  11. package/catalog/agent/skills/claude/SKILL.md +7 -0
  12. package/catalog/agent/skills/codex/SKILL.md +7 -0
  13. package/catalog/agent/skills/fix/SKILL.md +36 -8
  14. package/catalog/agent/skills/grok/SKILL.md +7 -0
  15. package/catalog/agent/skills/investigate/SKILL.md +12 -1
  16. package/catalog/agent/skills/pll/SKILL.md +53 -48
  17. package/catalog/agent/skills/team/SKILL.md +26 -9
  18. package/catalog/agent/skills/ultragoal/SKILL.md +147 -19
  19. package/catalog/agent/skills/verify/SKILL.md +89 -51
  20. package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +8 -0
  21. package/dist/esm/audit/blueprint-lifecycle-sql.js +25 -1
  22. package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
  23. package/dist/esm/audit/registry.d.ts +4 -4
  24. package/dist/esm/audit/registry.js +3 -0
  25. package/dist/esm/audit/roadmap-links.js +6 -0
  26. package/dist/esm/audit/test-scan-perf.d.ts +18 -0
  27. package/dist/esm/audit/test-scan-perf.js +275 -0
  28. package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
  29. package/dist/esm/blueprint/lifecycle/engine.js +53 -6
  30. package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
  31. package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
  32. package/dist/esm/blueprint/markdown/helpers.js +6 -2
  33. package/dist/esm/ci/act-replay.js +110 -7
  34. package/dist/esm/ci/act-runner.d.ts +4 -0
  35. package/dist/esm/ci/act-runner.js +85 -26
  36. package/dist/esm/ci/act-worktree-git.d.ts +11 -0
  37. package/dist/esm/ci/act-worktree-git.js +219 -0
  38. package/dist/esm/ci/native-session-memory-cache.d.ts +9 -1
  39. package/dist/esm/ci/native-session-memory-cache.js +9 -1
  40. package/dist/esm/ci/vitest-ci-shards.d.ts +6 -4
  41. package/dist/esm/ci/vitest-ci-shards.js +6 -4
  42. package/dist/esm/cli/auto-update/guard-tombstone.d.ts +25 -0
  43. package/dist/esm/cli/auto-update/guard-tombstone.js +67 -0
  44. package/dist/esm/cli/auto-update/installer.d.ts +4 -17
  45. package/dist/esm/cli/auto-update/installer.js +6 -57
  46. package/dist/esm/cli/auto-update/self-invocation.d.ts +37 -0
  47. package/dist/esm/cli/auto-update/self-invocation.js +62 -0
  48. package/dist/esm/cli/commands/audit.js +4 -0
  49. package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
  50. package/dist/esm/cli/commands/blueprint/router.js +27 -2
  51. package/dist/esm/cli/commands/ci.d.ts +8 -1
  52. package/dist/esm/cli/commands/ci.js +80 -21
  53. package/dist/esm/cli/commands/dash/index.js +3 -3
  54. package/dist/esm/cli/commands/dash/keymap.js +1 -0
  55. package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
  56. package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
  57. package/dist/esm/cli/commands/dash/plan-once-attention.d.ts +32 -0
  58. package/dist/esm/cli/commands/dash/plan-once-attention.js +50 -0
  59. package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
  60. package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
  61. package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
  62. package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
  63. package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
  64. package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
  65. package/dist/esm/cli/commands/dash/plan-once-schema.d.ts +73 -0
  66. package/dist/esm/cli/commands/dash/plan-once-schema.js +119 -0
  67. package/dist/esm/cli/commands/dash/plan-once.d.ts +142 -0
  68. package/dist/esm/cli/commands/dash/plan-once.js +261 -0
  69. package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
  70. package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
  71. package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
  72. package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
  73. package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
  74. package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
  75. package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
  76. package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
  77. package/dist/esm/cli/commands/dash/tui/ipc-protocol.d.ts +87 -0
  78. package/dist/esm/cli/commands/dash/tui/ipc-protocol.js +128 -0
  79. package/dist/esm/cli/commands/dash/tui/native-host.d.ts +34 -0
  80. package/dist/esm/cli/commands/dash/tui/native-host.js +256 -0
  81. package/dist/esm/cli/commands/dash/tui/native-path.d.ts +11 -0
  82. package/dist/esm/cli/commands/dash/tui/native-path.js +41 -0
  83. package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
  84. package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
  85. package/dist/esm/cli/commands/dash/tui/runtime.js +311 -4
  86. package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
  87. package/dist/esm/cli/commands/dash/tui/state.js +257 -4
  88. package/dist/esm/cli/commands/format.js +0 -2
  89. package/dist/esm/cli/commands/init/config.d.ts +0 -1
  90. package/dist/esm/cli/commands/init/config.js +1 -4
  91. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
  92. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
  93. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
  94. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
  95. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
  96. package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
  97. package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +1 -1
  98. package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
  99. package/dist/esm/cli/commands/logs.d.ts +15 -0
  100. package/dist/esm/cli/commands/logs.js +192 -3
  101. package/dist/esm/cli/commands/package-manager.js +2 -1
  102. package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
  103. package/dist/esm/cli/commands/quality-log-store.js +187 -51
  104. package/dist/esm/cli/commands/review.js +5 -8
  105. package/dist/esm/cli/commands/secrets.d.ts +1 -0
  106. package/dist/esm/cli/commands/secrets.js +4 -1
  107. package/dist/esm/cli/commands/self-install-guard.d.ts +14 -32
  108. package/dist/esm/cli/commands/self-install-guard.js +17 -63
  109. package/dist/esm/cli/direct-provider-launch.js +25 -1
  110. package/dist/esm/hooks/doctor.d.ts +15 -0
  111. package/dist/esm/hooks/doctor.js +56 -0
  112. package/dist/esm/hooks/guard-switch/index.js +8 -4
  113. package/dist/esm/hooks/post-tool/lint-after-edit.js +6 -4
  114. package/dist/esm/hooks/precompact/index.js +5 -3
  115. package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
  116. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
  117. package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
  118. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
  119. package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
  120. package/dist/esm/hooks/sessionstart/index.d.ts +10 -0
  121. package/dist/esm/hooks/sessionstart/index.js +61 -4
  122. package/dist/esm/hooks/shared/types.d.ts +11 -0
  123. package/dist/esm/hooks/shared/types.js +21 -0
  124. package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
  125. package/dist/esm/mcp/tools/audit.d.ts +2 -1
  126. package/dist/esm/mcp/tools/audit.js +10 -0
  127. package/dist/esm/mcp/tools/audits.d.ts +1 -0
  128. package/dist/esm/mcp/tools/ci-act.js +9 -11
  129. package/dist/esm/paths/state-root.js +9 -0
  130. package/dist/esm/review/authority.js +17 -2
  131. package/dist/esm/review/availability.js +18 -5
  132. package/dist/esm/review/subject.d.ts +15 -0
  133. package/dist/esm/review/subject.js +105 -10
  134. package/dist/esm/runtime/executor.d.ts +1 -0
  135. package/dist/esm/runtime/executor.js +2 -1
  136. package/dist/esm/secret-gate/runner.d.ts +1 -0
  137. package/dist/esm/secret-gate/runner.js +4 -1
  138. package/dist/esm/session-memory/current-session.js +2 -0
  139. package/dist/esm/session-memory/native-runtime.js +5 -3
  140. package/dist/esm/session-memory/native-warm-markers.d.ts +18 -0
  141. package/dist/esm/session-memory/native-warm-markers.js +22 -0
  142. package/dist/esm/session-memory/sync/types.d.ts +4 -0
  143. package/dist/esm/session-memory/types.d.ts +3 -1
  144. package/dist/esm/session-memory/types.js +2 -0
  145. package/dist/esm/ultragoal/runtime.js +44 -2
  146. package/dist/esm/worktrees/current.d.ts +1 -0
  147. package/dist/esm/worktrees/current.js +1 -0
  148. package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
  149. package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
  150. package/package.json +17 -14
@@ -1,7 +1,7 @@
1
- import { randomUUID } from "node:crypto";
2
- import { closeSync, createWriteStream, mkdirSync, openSync, readdirSync, readFileSync, statSync, renameSync, utimesSync, rmSync, writeFileSync, } from "node:fs";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { createWriteStream, mkdirSync, openSync, readdirSync, readFileSync, statSync, renameSync, utimesSync, rmSync, writeFileSync, } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
- import { dirname, join } from "node:path";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
5
  import { getSurfacePath } from "#paths/state-root.js";
6
6
  import { readTrustedJsonFile } from "#shared-utils/read-json-file.js";
7
7
  import { shortId } from "#shared-utils/short-id.js";
@@ -16,6 +16,7 @@ export const CLI_LOG_COMMANDS = [
16
16
  "format",
17
17
  ];
18
18
  const ORPHAN_LOG_GRACE_MS = 60_000;
19
+ const INDEX_LOCK_STALE_MS = 5_000;
19
20
  export function isCliLogCommandName(value) {
20
21
  return CLI_LOG_COMMANDS.includes(value);
21
22
  }
@@ -63,39 +64,39 @@ function openCliLogSink(commandDir, command, cwd) {
63
64
  stream.write(chunk);
64
65
  },
65
66
  async finalize(metadata) {
66
- await new Promise((resolve, reject) => {
67
- if (streamError) {
68
- reject(streamError);
69
- return;
70
- }
71
- rejectPendingFinalize = reject;
72
- stream.end(() => {
73
- rejectPendingFinalize = undefined;
67
+ try {
68
+ await new Promise((resolve, reject) => {
74
69
  if (streamError) {
75
70
  reject(streamError);
76
71
  return;
77
72
  }
78
- resolve();
73
+ rejectPendingFinalize = reject;
74
+ stream.end(() => {
75
+ rejectPendingFinalize = undefined;
76
+ if (streamError) {
77
+ reject(streamError);
78
+ return;
79
+ }
80
+ resolve();
81
+ });
79
82
  });
80
- });
81
- const entry = {
82
- id,
83
- command,
84
- timestamp: metadata.timestamp ?? new Date().toISOString(),
85
- exitCode: metadata.exitCode,
86
- logPath: absoluteLogPath,
87
- ...(metadata.options ? { options: metadata.options } : {}),
88
- ...(metadata.summary ? { summary: metadata.summary } : {}),
89
- };
90
- try {
83
+ const entry = {
84
+ id,
85
+ command,
86
+ timestamp: metadata.timestamp ?? new Date().toISOString(),
87
+ exitCode: metadata.exitCode,
88
+ logPath: absoluteLogPath,
89
+ ...(metadata.options ? { options: metadata.options } : {}),
90
+ ...(metadata.summary ? { summary: metadata.summary } : {}),
91
+ };
91
92
  markLogRecentlyFinalized(absoluteLogPath);
92
93
  // Index/lock live next to the log file so fallback dirs stay consistent.
93
94
  writeLogEntryInDir(entry, commandDir);
95
+ return entry;
94
96
  }
95
97
  finally {
96
98
  rmSync(activeMarkerPath, { force: true });
97
99
  }
98
- return entry;
99
100
  },
100
101
  };
101
102
  }
@@ -113,7 +114,7 @@ export function createCliLogSink(command, cwd = process.cwd()) {
113
114
  catch (error) {
114
115
  if (!isUnwritableFsError(error))
115
116
  throw error;
116
- const fallbackDir = join(tmpdir(), "webpresso", "cli-logs", command);
117
+ const fallbackDir = getFallbackCommandLogDir(command, cwd);
117
118
  const code = typeof error === "object" && error !== null && "code" in error
118
119
  ? String(error.code)
119
120
  : "unknown";
@@ -122,13 +123,37 @@ export function createCliLogSink(command, cwd = process.cwd()) {
122
123
  }
123
124
  }
124
125
  export function readCliLogEntries(command, cwd = process.cwd()) {
125
- const indexPath = getCommandIndexPath(command, cwd);
126
+ const entries = [
127
+ ...readCliLogEntriesFromCommandDir(command, getCommandLogDir(command, cwd)),
128
+ ...readCliLogEntriesFromCommandDir(command, getFallbackCommandLogDir(command, cwd)),
129
+ ];
130
+ const byLogPath = new Map();
131
+ for (const entry of entries) {
132
+ byLogPath.set(entry.logPath, entry);
133
+ }
134
+ return [...byLogPath.values()]
135
+ .sort((a, b) => {
136
+ if (a.active === true && b.active !== true)
137
+ return -1;
138
+ if (b.active === true && a.active !== true)
139
+ return 1;
140
+ return b.timestamp.localeCompare(a.timestamp);
141
+ })
142
+ .slice(0, 10);
143
+ }
144
+ function readCliLogEntriesFromCommandDir(command, commandDir) {
145
+ const activeEntries = readActiveCliLogEntriesFromDir(command, commandDir);
146
+ const indexPath = join(commandDir, "index.json");
126
147
  try {
127
148
  const parsed = readTrustedJsonFile(indexPath);
128
- return Array.isArray(parsed.entries) ? parsed.entries : [];
149
+ const finalizedEntries = Array.isArray(parsed.entries) ? parsed.entries : [];
150
+ return [
151
+ ...activeEntries,
152
+ ...finalizedEntries.filter((entry) => !activeEntries.some((active) => active.id === entry.id)),
153
+ ].slice(0, 10);
129
154
  }
130
155
  catch {
131
- return [];
156
+ return activeEntries;
132
157
  }
133
158
  }
134
159
  export function readCliLogEntry(command, ordinal = 1, cwd = process.cwd()) {
@@ -146,6 +171,45 @@ function readCliLogEntriesFromDir(commandDir) {
146
171
  return [];
147
172
  }
148
173
  }
174
+ function readActiveCliLogEntriesFromDir(command, commandDir) {
175
+ let files;
176
+ try {
177
+ files = readdirSync(commandDir);
178
+ }
179
+ catch {
180
+ return [];
181
+ }
182
+ return files
183
+ .filter((file) => file.endsWith(".log.active"))
184
+ .map((file) => readActiveCliLogEntryFromMarker(command, join(commandDir, file)))
185
+ .filter((entry) => entry !== null)
186
+ .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
187
+ }
188
+ function readActiveCliLogEntryFromMarker(command, markerPath) {
189
+ try {
190
+ const markerStat = statSync(markerPath);
191
+ const marker = readFileSync(markerPath, "utf8");
192
+ const id = basename(markerPath).slice(0, -".log.active".length);
193
+ if (!id)
194
+ return null;
195
+ const markerLines = marker.split("\n");
196
+ const live = isActiveMarkerSnapshotLive(marker, markerStat.mtimeMs);
197
+ return {
198
+ id,
199
+ command,
200
+ timestamp: markerLines[1]?.trim() || new Date(markerStat.mtimeMs).toISOString(),
201
+ exitCode: -1,
202
+ logPath: markerPath.slice(0, -".active".length),
203
+ ...(live ? { active: true } : {}),
204
+ summary: live ? "active log" : "orphaned log from exited process",
205
+ };
206
+ }
207
+ catch (error) {
208
+ if (isMissingFileError(error))
209
+ return null;
210
+ throw error;
211
+ }
212
+ }
149
213
  function writeLogEntryInDir(entry, commandDir) {
150
214
  withCommandIndexLockInDir(commandDir, () => {
151
215
  const indexPath = join(commandDir, "index.json");
@@ -169,28 +233,86 @@ function writeLogEntryInDir(entry, commandDir) {
169
233
  }
170
234
  function withCommandIndexLockInDir(commandDir, fn) {
171
235
  mkdirSync(commandDir, { recursive: true });
172
- const lockPath = join(commandDir, "index.lock");
236
+ const lockDir = join(commandDir, "index.lock.d");
237
+ mkdirSync(lockDir, { recursive: true });
238
+ const ownerPath = join(lockDir, `${Date.now()}-${process.pid}-${randomUUID()}.owner`);
239
+ writeFileSync(ownerPath, `${process.pid}\n${new Date().toISOString()}\n`, {
240
+ encoding: "utf8",
241
+ flag: "wx",
242
+ });
173
243
  const started = Date.now();
174
- let fd;
175
- while (fd === undefined) {
176
- try {
177
- fd = openSync(lockPath, "wx");
178
- }
179
- catch (error) {
180
- if (!isFileExistsError(error))
181
- throw error;
182
- if (Date.now() - started > 5_000) {
183
- throw new Error(`Timed out waiting for CLI log index lock: ${lockPath}`);
244
+ try {
245
+ while (!isCurrentIndexLockOwner(lockDir, ownerPath)) {
246
+ if (Date.now() - started > INDEX_LOCK_STALE_MS) {
247
+ throw new Error(`Timed out waiting for CLI log index lock: ${lockDir}`);
184
248
  }
185
249
  sleepSync(10);
186
250
  }
187
- }
188
- try {
189
251
  return fn();
190
252
  }
191
253
  finally {
192
- closeSync(fd);
193
- rmSync(lockPath, { force: true });
254
+ rmSync(ownerPath, { force: true });
255
+ }
256
+ }
257
+ function isCurrentIndexLockOwner(lockDir, ownerPath) {
258
+ const liveOwners = readLiveIndexLockOwners(lockDir);
259
+ const firstOwner = liveOwners[0];
260
+ if (firstOwner === undefined)
261
+ return false;
262
+ return firstOwner.path === ownerPath;
263
+ }
264
+ function readLiveIndexLockOwners(lockDir) {
265
+ let files;
266
+ try {
267
+ files = readdirSync(lockDir);
268
+ }
269
+ catch (error) {
270
+ if (isMissingFileError(error))
271
+ return [];
272
+ throw error;
273
+ }
274
+ return files
275
+ .filter((file) => file.endsWith(".owner"))
276
+ .map((file) => readIndexLockOwner(join(lockDir, file)))
277
+ .filter((owner) => owner !== null)
278
+ .sort((a, b) => a.timestampMs - b.timestampMs || a.path.localeCompare(b.path));
279
+ }
280
+ function readIndexLockOwner(ownerPath) {
281
+ let marker = "";
282
+ let markerMtimeMs = 0;
283
+ try {
284
+ const markerStat = statSync(ownerPath);
285
+ markerMtimeMs = markerStat.mtimeMs;
286
+ marker = readFileSync(ownerPath, "utf8");
287
+ }
288
+ catch (error) {
289
+ if (isMissingFileError(error))
290
+ return null;
291
+ throw error;
292
+ }
293
+ const markerLines = marker.split("\n");
294
+ const pid = Number(markerLines[0]);
295
+ const hasOwner = Number.isInteger(pid) && pid > 0;
296
+ const timestamp = Date.parse(markerLines[1] ?? "");
297
+ const timestampMs = Number.isFinite(timestamp) ? timestamp : markerMtimeMs;
298
+ const stale = (hasOwner && !isProcessLive(pid)) ||
299
+ (!hasOwner && Date.now() - markerMtimeMs > INDEX_LOCK_STALE_MS);
300
+ if (stale) {
301
+ rmSync(ownerPath, { force: true });
302
+ return null;
303
+ }
304
+ return { path: ownerPath, timestampMs };
305
+ }
306
+ function isProcessLive(pid) {
307
+ try {
308
+ process.kill(pid, 0);
309
+ return true;
310
+ }
311
+ catch (error) {
312
+ if (typeof error === "object" && error !== null && "code" in error) {
313
+ return error.code === "EPERM";
314
+ }
315
+ return false;
194
316
  }
195
317
  }
196
318
  function writeIndexAtomically(indexPath, index) {
@@ -233,31 +355,45 @@ function canPruneLogPath(logPath) {
233
355
  }
234
356
  }
235
357
  function isActiveLogPath(logPath) {
358
+ const activeMarkerPath = getActiveMarkerPath(logPath);
236
359
  try {
237
- readFileSync(getActiveMarkerPath(logPath), "utf8");
238
- return true;
360
+ const markerStat = statSync(activeMarkerPath);
361
+ const marker = readFileSync(activeMarkerPath, "utf8");
362
+ const live = isActiveMarkerSnapshotLive(marker, markerStat.mtimeMs);
363
+ if (!live)
364
+ rmSync(activeMarkerPath, { force: true });
365
+ return live;
239
366
  }
240
- catch {
367
+ catch (error) {
368
+ if (!isMissingFileError(error))
369
+ throw error;
241
370
  return false;
242
371
  }
243
372
  }
373
+ function isActiveMarkerSnapshotLive(marker, markerMtimeMs) {
374
+ const pid = Number(marker.split("\n")[0]);
375
+ if (Number.isInteger(pid) && pid > 0) {
376
+ return isProcessLive(pid);
377
+ }
378
+ return Date.now() - markerMtimeMs <= ORPHAN_LOG_GRACE_MS;
379
+ }
244
380
  function getActiveMarkerPath(logPath) {
245
381
  return `${logPath}.active`;
246
382
  }
247
383
  function sleepSync(ms) {
248
384
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
249
385
  }
250
- function isFileExistsError(error) {
251
- return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
252
- }
253
386
  function isMissingFileError(error) {
254
387
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
255
388
  }
256
389
  function getCommandLogDir(command, cwd) {
257
390
  return getSurfacePath(join("cli-logs", command), "repo", cwd);
258
391
  }
259
- function getCommandIndexPath(command, cwd) {
260
- return join(getCommandLogDir(command, cwd), "index.json");
392
+ function getFallbackCommandLogDir(command, cwd) {
393
+ return join(tmpdir(), "webpresso", "cli-logs", fallbackScopeId(cwd), command);
394
+ }
395
+ function fallbackScopeId(cwd) {
396
+ return createHash("sha256").update(resolve(cwd)).digest("hex").slice(0, 12);
261
397
  }
262
398
  function createLogId(now = new Date()) {
263
399
  const iso = now.toISOString().replaceAll(":", "-").replaceAll(".", "-");
@@ -7,6 +7,7 @@ import { z } from "zod";
7
7
  import { MAX_PORTABLE_REPOSITORY_PATH_LENGTH } from "#audit/absolute-path-policy.js";
8
8
  import { getProjectRoot } from "#cli/utils";
9
9
  import { relativeBlueprintSlug, resolveBlueprintFile } from "#lifecycle/local.js";
10
+ import { readBlueprintOverviewAtRef } from "#lifecycle/review-provenance.js";
10
11
  import { scanBlueprintDirectory } from "#service/scanner.js";
11
12
  import { resolveBlueprintRoot } from "#utils/blueprint-root.js";
12
13
  import { CLAUDE_DEFAULT_REVIEW_MODEL, CODEX_DEFAULT_REVIEW_MODEL, GROK_DEFAULT_REVIEW_MODEL, DEFAULT_REVIEW_MAX_PROGRESS_EVENTS, runReviewExecution, } from "#review/execution/index.js";
@@ -860,14 +861,10 @@ function readBlueprintAtTarget(projectRoot, blueprintPath, targetSha) {
860
861
  throw new Error(`Blueprint path is outside Git toplevel: ${blueprintPath}`);
861
862
  }
862
863
  const relative = gitRelativePath.replace(/\\/gu, "/");
863
- try {
864
- return runBoundedReviewGit(gitRoot, ["show", `${targetSha}:${relative}`]);
865
- }
866
- catch (error) {
867
- throw new Error(`Blueprint ${relative} is not tracked at target ${targetSha}.`, {
868
- cause: error,
869
- });
870
- }
864
+ const content = readBlueprintOverviewAtRef(gitRoot, targetSha, absoluteBlueprintPath);
865
+ if (content)
866
+ return content.toString("utf8");
867
+ throw new Error(`Blueprint ${relative} is not tracked at target ${targetSha}.`);
871
868
  }
872
869
  function blueprintPathAtGitRoot(projectRoot, blueprintPath) {
873
870
  const gitRoot = resolveReviewGitToplevel(projectRoot);
@@ -2,6 +2,7 @@ import type { CAC } from "cac";
2
2
  import { spawnRuntimeCommandSync } from "#runtime/executor.js";
3
3
  export interface SecretsCommandOptions {
4
4
  readonly cwd?: string;
5
+ readonly commandCwd?: string;
5
6
  readonly profile?: string;
6
7
  readonly sink?: string;
7
8
  readonly json?: boolean;
@@ -14,6 +14,7 @@ export function registerSecretsCommand(cli) {
14
14
  .command("secrets <action> [target]", "Secret orchestration commands (doctor, bootstrap github)")
15
15
  .option("--profile <name>", "Secret profile name", { default: "preview" })
16
16
  .option("--sink <id>", "Secret sink id")
17
+ .option("--command-cwd <dir>", "Run the scoped command from this directory")
17
18
  .option("--json", "Emit machine-readable JSON")
18
19
  .option("--lane <lane>", "Lane to bootstrap (repeatable)", { default: [] })
19
20
  .option("--apply", "Apply GitHub bootstrap changes instead of dry-run planning")
@@ -22,6 +23,7 @@ export function registerSecretsCommand(cli) {
22
23
  cwd: process.cwd(),
23
24
  profile: flags.profile,
24
25
  sink: flags.sink,
26
+ commandCwd: flags.commandCwd,
25
27
  json: Boolean(flags.json),
26
28
  lanes: toArray(flags.lane),
27
29
  apply: Boolean(flags.apply),
@@ -109,7 +111,8 @@ export function runSecretsRun(options = {}, deps = {}) {
109
111
  const result = (deps.runSecretScopedCommand ?? spawnRuntimeCommandSync)({
110
112
  command,
111
113
  args,
112
- cwd: options.cwd,
114
+ cwd: options.commandCwd ?? options.cwd,
115
+ envCwd: options.cwd,
113
116
  profile: plan.runtimeProfile,
114
117
  secretProfile: plan.profile,
115
118
  environment: plan.environment,
@@ -30,13 +30,17 @@
30
30
  * chain unresolvable after repair; 12 lease held; 13 ownership lost.
31
31
  * 7. Release the lease owner-checked in a finally.
32
32
  *
33
- * Post-swap import rule: the child `vp update -g @webpresso/agent-kit` may
34
- * delete this process's on-disk package tree (old installId) before probe/
35
- * repair run. Post-child helpers MUST be static imports loaded at process
36
- * start never `await import("#cli/…")` after the child exits, or Node
37
- * re-resolves against a removed path (`Cannot find module …/global-vp.js`).
38
- * Probe/repair logic itself reads the vite-plus store SSOT + PATH, not this
39
- * process's dying package root.
33
+ * Survival layers (in-repo SOTA bar):
34
+ * 1. Prefer a **stable** non-versioned launcher when re-execing the guard
35
+ * (`#cli/auto-update/self-invocation.js`) primary defense.
36
+ * 2. Self-package helpers (probe, repair, tombstone) MUST be static imports
37
+ * loaded at process start never package-local dynamic import, or Node
38
+ * re-resolves against a removed installId path after `vp update -g`.
39
+ * 3. Argument builders depend on cycle-free leaves (self-invocation,
40
+ * guard-tombstone), never on this command module solely for argv.
41
+ *
42
+ * Probe/repair reads the vite-plus store SSOT + PATH, not this process's
43
+ * dying package root.
40
44
  *
41
45
  * Custody boundary (stated exactly, mirrored from install-lock.ts): a stale,
42
46
  * OS-suspended holder whose child completes a mutation AFTER the successor
@@ -45,6 +49,9 @@
45
49
  * resource-side fencing.
46
50
  */
47
51
  import type { CAC } from "cac";
52
+ import { buildSelfInvocationCommand, isVersionedAgentKitPayloadPath, resolveStableWpLauncher } from "#cli/auto-update/self-invocation.js";
53
+ /** Re-export leaf helpers for public ergonomics / existing imports. */
54
+ export { buildSelfInvocationCommand, isVersionedAgentKitPayloadPath, resolveStableWpLauncher };
48
55
  export declare function registerSelfInstallGuardCommand(cli: CAC): void;
49
56
  export declare const GUARD_EXIT_CHILD_FAILED = 10;
50
57
  export declare const GUARD_EXIT_UNRESOLVABLE = 11;
@@ -85,28 +92,3 @@ export interface SelfInstallGuardDeps {
85
92
  * Testable core. The CLI action wires `process.exit(await runSelfInstallGuard(...))`.
86
93
  */
87
94
  export declare function runSelfInstallGuard(rawArgs: readonly string[], deps?: SelfInstallGuardDeps): Promise<number>;
88
- /**
89
- * How wp re-invokes itself:
90
- * - Prefer a **stable** global launcher (`~/.vite-plus/bin/wp` or PATH `wp`
91
- * outside an installId package tree). Re-execing `node …/agent-kit#id/…/cli.js`
92
- * dies after `vp update -g` deletes the pre-swap tree mid-guard.
93
- * - Bun-compiled binary: process.execPath IS the binary (argv[1] is /$bunfs).
94
- * - Source/dev lane without a stable launcher: `<runtime> <script>`.
95
- */
96
- export declare function buildSelfInvocationCommand(args: readonly string[], options?: {
97
- readonly execPath?: string;
98
- readonly argv1?: string;
99
- /** Injectable stable launcher for tests; `null` forces legacy argv1 path. */
100
- readonly stableWp?: string | null;
101
- }): string[];
102
- /**
103
- * A launcher that survives installId package-tree deletion during global update.
104
- * Rejects paths under `agent-kit#<uuid>/` (versioned store payload).
105
- */
106
- export declare function resolveStableWpLauncher(options?: {
107
- readonly home?: string;
108
- readonly pathEnv?: string;
109
- readonly existsSync?: (path: string) => boolean;
110
- }): string | null;
111
- /** True when path lives inside a vite-plus installId package tree. */
112
- export declare function isVersionedAgentKitPayloadPath(filePath: string): boolean;
@@ -30,13 +30,17 @@
30
30
  * chain unresolvable after repair; 12 lease held; 13 ownership lost.
31
31
  * 7. Release the lease owner-checked in a finally.
32
32
  *
33
- * Post-swap import rule: the child `vp update -g @webpresso/agent-kit` may
34
- * delete this process's on-disk package tree (old installId) before probe/
35
- * repair run. Post-child helpers MUST be static imports loaded at process
36
- * start never `await import("#cli/…")` after the child exits, or Node
37
- * re-resolves against a removed path (`Cannot find module …/global-vp.js`).
38
- * Probe/repair logic itself reads the vite-plus store SSOT + PATH, not this
39
- * process's dying package root.
33
+ * Survival layers (in-repo SOTA bar):
34
+ * 1. Prefer a **stable** non-versioned launcher when re-execing the guard
35
+ * (`#cli/auto-update/self-invocation.js`) primary defense.
36
+ * 2. Self-package helpers (probe, repair, tombstone) MUST be static imports
37
+ * loaded at process start never package-local dynamic import, or Node
38
+ * re-resolves against a removed installId path after `vp update -g`.
39
+ * 3. Argument builders depend on cycle-free leaves (self-invocation,
40
+ * guard-tombstone), never on this command module solely for argv.
41
+ *
42
+ * Probe/repair reads the vite-plus store SSOT + PATH, not this process's
43
+ * dying package root.
40
44
  *
41
45
  * Custody boundary (stated exactly, mirrored from install-lock.ts): a stale,
42
46
  * OS-suspended holder whose child completes a mutation AFTER the successor
@@ -45,13 +49,15 @@
45
49
  * resource-side fencing.
46
50
  */
47
51
  import { spawn } from "node:child_process";
48
- import { existsSync } from "node:fs";
49
- import path from "node:path";
50
52
  import { buildVpGlobalInstallCommand, resolveBundledVpCommand, } from "#cli/auto-update/detect-pm.js";
53
+ import { writeGuardTombstone } from "#cli/auto-update/guard-tombstone.js";
51
54
  import { INSTALL_LOCK_HEARTBEAT_MS, claimInstallLock, refreshInstallLock, releaseInstallLock, resolveInstallLockPath, } from "#cli/auto-update/install-lock.js";
55
+ import { buildSelfInvocationCommand, isVersionedAgentKitPayloadPath, resolveStableWpLauncher, } from "#cli/auto-update/self-invocation.js";
52
56
  import { probeGlobalWpResolvable } from "#cli/commands/init/package-root.js";
53
57
  import { resolveGlobalCapableVpCommand } from "#cli/global-vp.js";
54
58
  import { resolveEffectiveRunnerRoot } from "#tool-runtime";
59
+ /** Re-export leaf helpers for public ergonomics / existing imports. */
60
+ export { buildSelfInvocationCommand, isVersionedAgentKitPayloadPath, resolveStableWpLauncher };
55
61
  export function registerSelfInstallGuardCommand(cli) {
56
62
  cli
57
63
  .command("self-install-guard [...args]", "Serialize and verify a global agent-kit self-install (internal)")
@@ -147,56 +153,6 @@ function extractChildCommand(rawArgs) {
147
153
  return [];
148
154
  return rawArgs.slice(separator + 1);
149
155
  }
150
- /**
151
- * How wp re-invokes itself:
152
- * - Prefer a **stable** global launcher (`~/.vite-plus/bin/wp` or PATH `wp`
153
- * outside an installId package tree). Re-execing `node …/agent-kit#id/…/cli.js`
154
- * dies after `vp update -g` deletes the pre-swap tree mid-guard.
155
- * - Bun-compiled binary: process.execPath IS the binary (argv[1] is /$bunfs).
156
- * - Source/dev lane without a stable launcher: `<runtime> <script>`.
157
- */
158
- export function buildSelfInvocationCommand(args, options = {}) {
159
- const stableWp = options.stableWp !== undefined ? options.stableWp : resolveStableWpLauncher();
160
- if (typeof stableWp === "string" && stableWp.length > 0) {
161
- return [stableWp, ...args];
162
- }
163
- const execPath = options.execPath ?? process.execPath;
164
- const argv1 = options.argv1 ?? process.argv[1] ?? "";
165
- if (argv1.startsWith("/$bunfs"))
166
- return [execPath, ...args];
167
- return [execPath, argv1, ...args];
168
- }
169
- /**
170
- * A launcher that survives installId package-tree deletion during global update.
171
- * Rejects paths under `agent-kit#<uuid>/` (versioned store payload).
172
- */
173
- export function resolveStableWpLauncher(options = {}) {
174
- const exists = options.existsSync ?? existsSync;
175
- const home = options.home ?? process.env.HOME ?? process.env.USERPROFILE ?? "";
176
- const candidates = [];
177
- if (home) {
178
- candidates.push(path.join(home, ".vite-plus", "bin", "wp"));
179
- }
180
- const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
181
- for (const dir of pathEnv.split(path.delimiter)) {
182
- if (!dir)
183
- continue;
184
- candidates.push(path.join(dir, "wp"));
185
- }
186
- for (const candidate of candidates) {
187
- if (!exists(candidate))
188
- continue;
189
- if (isVersionedAgentKitPayloadPath(candidate))
190
- continue;
191
- return candidate;
192
- }
193
- return null;
194
- }
195
- /** True when path lives inside a vite-plus installId package tree. */
196
- export function isVersionedAgentKitPayloadPath(filePath) {
197
- const normalized = filePath.replaceAll("\\", "/");
198
- return /\/@webpresso\/agent-kit#[^/]+\//u.test(normalized);
199
- }
200
156
  function defaultSpawnChild(command) {
201
157
  const [executable, ...args] = command;
202
158
  // detached: own process group, so lost-ownership kills reap descendants.
@@ -251,8 +207,6 @@ async function defaultRunRepair() {
251
207
  return child.exited;
252
208
  }
253
209
  function writeObservabilityTombstone() {
254
- // Lazy import keeps the guard's cold path minimal; best-effort by contract.
255
- void import("#cli/auto-update/installer.js")
256
- .then(({ writeGuardTombstone }) => writeGuardTombstone())
257
- .catch(() => undefined);
210
+ // Static import only leaf module; see survival layers in the file header.
211
+ writeGuardTombstone();
258
212
  }
@@ -11,6 +11,7 @@ import { canonicalizeWorktreePath } from "#worktrees/identity.js";
11
11
  import { dashProviderOwnershipName, parseDashPermissionMode, } from "#cli/commands/dash/provider-capabilities.js";
12
12
  import { optionalToolCanonicalCommand } from "#cli/optional-tools.js";
13
13
  import { commandExists } from "#runtime/command-exists.js";
14
+ import { ensureCodexProjectMcpServers, ensureCodexWebpressoMcp, } from "#cli/commands/init/scaffolders/codex-mcp/index.js";
14
15
  export class DirectProviderLaunchPreparationError extends Error {
15
16
  action;
16
17
  constructor(message, action) {
@@ -90,6 +91,25 @@ function validateDirectProviderLaunch(provider, cwd) {
90
91
  vpCommand,
91
92
  };
92
93
  }
94
+ function ensureCodexSessionTooling(repoRoot) {
95
+ const options = { dryRun: false, overwrite: false };
96
+ const webpresso = ensureCodexWebpressoMcp({ options });
97
+ if (webpresso.kind === "codex-webpresso-mcp-not-installed") {
98
+ throw new DirectProviderLaunchPreparationError(`Codex webpresso MCP is not configured because no usable agent-kit install root was found. Checked: ${webpresso.checked.join(", ") || "(none)"}`, "Run `wp setup --yes-global` from this repo, then retry `wp codex`.");
99
+ }
100
+ const project = ensureCodexProjectMcpServers({ repoRoot, options });
101
+ if (project.kind === "codex-project-mcp-invalid") {
102
+ throw new DirectProviderLaunchPreparationError(`Codex project MCP config is invalid: ${project.reason}`, "Fix .mcp.json or run `wp setup --yes-global` for detailed setup diagnostics.");
103
+ }
104
+ if (project.kind === "codex-project-mcp-unavailable") {
105
+ throw new DirectProviderLaunchPreparationError(`Codex project MCP server ${project.server} is unavailable: ${project.reason}`, "Run `wp setup --yes-global` from this repo to provision project MCP servers, then retry.");
106
+ }
107
+ }
108
+ function ensureProviderSessionTooling(provider, cwd) {
109
+ if (provider !== "codex")
110
+ return;
111
+ ensureCodexSessionTooling(cwd);
112
+ }
93
113
  export function isBareInteractiveProviderLaunch(args) {
94
114
  return parseDashPermissionMode(args).args.length === 0;
95
115
  }
@@ -136,14 +156,17 @@ export async function prepareDirectProviderLaunch(provider, args, cwd) {
136
156
  return { cwd, warnings: [] };
137
157
  }
138
158
  const { managedCwd, warnings } = await prepareLaunchEnvironment(provider, cwd);
139
- if (managedCwd)
159
+ if (managedCwd) {
160
+ ensureProviderSessionTooling(provider, managedCwd);
140
161
  return { cwd: managedCwd, warnings };
162
+ }
141
163
  const created = await createManagedWorktree({
142
164
  cwd,
143
165
  prefix: provider,
144
166
  base: "origin/main",
145
167
  dependencyHydration: dependencyHydrationForProvider(provider),
146
168
  });
169
+ ensureProviderSessionTooling(provider, created.path);
147
170
  return { cwd: created.path, warnings };
148
171
  }
149
172
  /**
@@ -163,5 +186,6 @@ export function dependencyHydrationForProvider(provider) {
163
186
  */
164
187
  export async function prepareDirectProviderLaunchAt(provider, cwd) {
165
188
  const { warnings } = await prepareLaunchEnvironment(provider, cwd);
189
+ ensureProviderSessionTooling(provider, cwd);
166
190
  return { cwd, warnings };
167
191
  }
@@ -116,6 +116,21 @@ export declare function checkCodexConfigKeys(configFilePath?: string, deps?: Hos
116
116
  * (exact `vp dlx` pins, launcher rules) at doctor time instead of only at setup.
117
117
  */
118
118
  export declare function checkProjectMcpPins(cwd?: string, deps?: HostConfigDoctorDeps): DoctorCheck;
119
+ export interface ClaudeMcpDuplicateRegistrationOptions {
120
+ readonly home?: string;
121
+ readonly exists?: (path: string) => boolean;
122
+ readonly readFile?: (path: string) => string;
123
+ }
124
+ /**
125
+ * `claude-mcp-duplicate-registration` — advisory. A machine can register the
126
+ * webpresso MCP server twice for the same Claude Code session: a manual
127
+ * `claude mcp add webpresso` entry in `~/.claude.json` (user scope or a
128
+ * project scope) plus the agent-kit marketplace plugin's bundled server
129
+ * (plugin cache under `~/.claude/plugins/`). Both spawn `wp mcp`, so affected
130
+ * sessions run two server processes and carry a duplicated wp_* tool list.
131
+ * Degrades to ok on missing/unreadable config — loud only on real duplication.
132
+ */
133
+ export declare function checkClaudeMcpDuplicateRegistration(options?: ClaudeMcpDuplicateRegistrationOptions): DoctorCheck;
119
134
  export interface DanglingHostPathsOptions {
120
135
  readonly configFilePath?: string;
121
136
  readonly home?: string;