@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.
- package/catalog/AGENTS.md.tpl +7 -11
- package/catalog/agent/commands/verify.md +2 -2
- package/catalog/agent/rules/changeset-release.md +4 -2
- package/catalog/agent/rules/ci-cost-local-first.md +139 -0
- package/catalog/agent/rules/ci-test-perf.md +5 -1
- package/catalog/agent/rules/cmd-execution.md +9 -0
- package/catalog/agent/rules/pre-implementation.md +2 -1
- package/catalog/agent/rules/test-scan-perf.md +80 -0
- package/catalog/agent/rules/workflow-skills-routing.md +9 -4
- package/catalog/agent/skills/autopilot/SKILL.md +48 -11
- package/catalog/agent/skills/claude/SKILL.md +7 -0
- package/catalog/agent/skills/codex/SKILL.md +7 -0
- package/catalog/agent/skills/fix/SKILL.md +36 -8
- package/catalog/agent/skills/grok/SKILL.md +7 -0
- package/catalog/agent/skills/investigate/SKILL.md +12 -1
- package/catalog/agent/skills/pll/SKILL.md +53 -48
- package/catalog/agent/skills/team/SKILL.md +26 -9
- package/catalog/agent/skills/ultragoal/SKILL.md +147 -19
- package/catalog/agent/skills/verify/SKILL.md +89 -51
- package/dist/esm/audit/blueprint-lifecycle-sql.d.ts +8 -0
- package/dist/esm/audit/blueprint-lifecycle-sql.js +25 -1
- package/dist/esm/audit/blueprint-pr-coverage.js +68 -7
- package/dist/esm/audit/registry.d.ts +4 -4
- package/dist/esm/audit/registry.js +3 -0
- package/dist/esm/audit/roadmap-links.js +6 -0
- package/dist/esm/audit/test-scan-perf.d.ts +18 -0
- package/dist/esm/audit/test-scan-perf.js +275 -0
- package/dist/esm/blueprint/lifecycle/engine.d.ts +9 -0
- package/dist/esm/blueprint/lifecycle/engine.js +53 -6
- package/dist/esm/blueprint/lifecycle/review-provenance.d.ts +6 -0
- package/dist/esm/blueprint/lifecycle/review-provenance.js +93 -12
- package/dist/esm/blueprint/markdown/helpers.js +6 -2
- package/dist/esm/ci/act-replay.js +110 -7
- package/dist/esm/ci/act-runner.d.ts +4 -0
- package/dist/esm/ci/act-runner.js +85 -26
- package/dist/esm/ci/act-worktree-git.d.ts +11 -0
- package/dist/esm/ci/act-worktree-git.js +219 -0
- package/dist/esm/ci/native-session-memory-cache.d.ts +9 -1
- package/dist/esm/ci/native-session-memory-cache.js +9 -1
- package/dist/esm/ci/vitest-ci-shards.d.ts +6 -4
- package/dist/esm/ci/vitest-ci-shards.js +6 -4
- package/dist/esm/cli/auto-update/guard-tombstone.d.ts +25 -0
- package/dist/esm/cli/auto-update/guard-tombstone.js +67 -0
- package/dist/esm/cli/auto-update/installer.d.ts +4 -17
- package/dist/esm/cli/auto-update/installer.js +6 -57
- package/dist/esm/cli/auto-update/self-invocation.d.ts +37 -0
- package/dist/esm/cli/auto-update/self-invocation.js +62 -0
- package/dist/esm/cli/commands/audit.js +4 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +35 -18
- package/dist/esm/cli/commands/blueprint/router.js +27 -2
- package/dist/esm/cli/commands/ci.d.ts +8 -1
- package/dist/esm/cli/commands/ci.js +80 -21
- package/dist/esm/cli/commands/dash/index.js +3 -3
- package/dist/esm/cli/commands/dash/keymap.js +1 -0
- package/dist/esm/cli/commands/dash/launch-preferences.d.ts +15 -0
- package/dist/esm/cli/commands/dash/launch-preferences.js +46 -17
- package/dist/esm/cli/commands/dash/plan-once-attention.d.ts +32 -0
- package/dist/esm/cli/commands/dash/plan-once-attention.js +50 -0
- package/dist/esm/cli/commands/dash/plan-once-label.d.ts +7 -0
- package/dist/esm/cli/commands/dash/plan-once-label.js +5 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.d.ts +67 -0
- package/dist/esm/cli/commands/dash/plan-once-membership.js +151 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.d.ts +143 -0
- package/dist/esm/cli/commands/dash/plan-once-merge.js +238 -0
- package/dist/esm/cli/commands/dash/plan-once-schema.d.ts +73 -0
- package/dist/esm/cli/commands/dash/plan-once-schema.js +119 -0
- package/dist/esm/cli/commands/dash/plan-once.d.ts +142 -0
- package/dist/esm/cli/commands/dash/plan-once.js +261 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.d.ts +9 -0
- package/dist/esm/cli/commands/dash/tui/attention-hud.js +9 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/chat-bridge.js +73 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.d.ts +47 -0
- package/dist/esm/cli/commands/dash/tui/chat-mode.js +98 -0
- package/dist/esm/cli/commands/dash/tui/frame.d.ts +3 -0
- package/dist/esm/cli/commands/dash/tui/frame.js +3 -1
- package/dist/esm/cli/commands/dash/tui/ipc-protocol.d.ts +87 -0
- package/dist/esm/cli/commands/dash/tui/ipc-protocol.js +128 -0
- package/dist/esm/cli/commands/dash/tui/native-host.d.ts +34 -0
- package/dist/esm/cli/commands/dash/tui/native-host.js +256 -0
- package/dist/esm/cli/commands/dash/tui/native-path.d.ts +11 -0
- package/dist/esm/cli/commands/dash/tui/native-path.js +41 -0
- package/dist/esm/cli/commands/dash/tui/palette.d.ts +1 -1
- package/dist/esm/cli/commands/dash/tui/palette.js +14 -0
- package/dist/esm/cli/commands/dash/tui/runtime.js +311 -4
- package/dist/esm/cli/commands/dash/tui/state.d.ts +41 -0
- package/dist/esm/cli/commands/dash/tui/state.js +257 -4
- package/dist/esm/cli/commands/format.js +0 -2
- package/dist/esm/cli/commands/init/config.d.ts +0 -1
- package/dist/esm/cli/commands/init/config.js +1 -4
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.d.ts +5 -6
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/emitters/claude.js +6 -13
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.d.ts +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/ir.js +0 -2
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.d.ts +6 -3
- package/dist/esm/cli/commands/init/scaffolders/agent-hooks/merge.js +39 -26
- package/dist/esm/cli/commands/init/scaffolders/agent-kit-global/index.js +1 -1
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +61 -23
- package/dist/esm/cli/commands/logs.d.ts +15 -0
- package/dist/esm/cli/commands/logs.js +192 -3
- package/dist/esm/cli/commands/package-manager.js +2 -1
- package/dist/esm/cli/commands/quality-log-store.d.ts +1 -0
- package/dist/esm/cli/commands/quality-log-store.js +187 -51
- package/dist/esm/cli/commands/review.js +5 -8
- package/dist/esm/cli/commands/secrets.d.ts +1 -0
- package/dist/esm/cli/commands/secrets.js +4 -1
- package/dist/esm/cli/commands/self-install-guard.d.ts +14 -32
- package/dist/esm/cli/commands/self-install-guard.js +17 -63
- package/dist/esm/cli/direct-provider-launch.js +25 -1
- package/dist/esm/hooks/doctor.d.ts +15 -0
- package/dist/esm/hooks/doctor.js +56 -0
- package/dist/esm/hooks/guard-switch/index.js +8 -4
- package/dist/esm/hooks/post-tool/lint-after-edit.js +6 -4
- package/dist/esm/hooks/precompact/index.js +5 -3
- package/dist/esm/hooks/pretool-guard/validators/forbidden-commands.js +4 -4
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.d.ts +0 -1
- package/dist/esm/hooks/pretool-guard/validators/mcp-redirect.js +8 -5
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.d.ts +9 -7
- package/dist/esm/hooks/pretool-guard/validators/worktree-discipline.js +126 -14
- package/dist/esm/hooks/sessionstart/index.d.ts +10 -0
- package/dist/esm/hooks/sessionstart/index.js +61 -4
- package/dist/esm/hooks/shared/types.d.ts +11 -0
- package/dist/esm/hooks/shared/types.js +21 -0
- package/dist/esm/mcp/tools/_shared/audit-kinds.d.ts +1 -1
- package/dist/esm/mcp/tools/audit.d.ts +2 -1
- package/dist/esm/mcp/tools/audit.js +10 -0
- package/dist/esm/mcp/tools/audits.d.ts +1 -0
- package/dist/esm/mcp/tools/ci-act.js +9 -11
- package/dist/esm/paths/state-root.js +9 -0
- package/dist/esm/review/authority.js +17 -2
- package/dist/esm/review/availability.js +18 -5
- package/dist/esm/review/subject.d.ts +15 -0
- package/dist/esm/review/subject.js +105 -10
- package/dist/esm/runtime/executor.d.ts +1 -0
- package/dist/esm/runtime/executor.js +2 -1
- package/dist/esm/secret-gate/runner.d.ts +1 -0
- package/dist/esm/secret-gate/runner.js +4 -1
- package/dist/esm/session-memory/current-session.js +2 -0
- package/dist/esm/session-memory/native-runtime.js +5 -3
- package/dist/esm/session-memory/native-warm-markers.d.ts +18 -0
- package/dist/esm/session-memory/native-warm-markers.js +22 -0
- package/dist/esm/session-memory/sync/types.d.ts +4 -0
- package/dist/esm/session-memory/types.d.ts +3 -1
- package/dist/esm/session-memory/types.js +2 -0
- package/dist/esm/ultragoal/runtime.js +44 -2
- package/dist/esm/worktrees/current.d.ts +1 -0
- package/dist/esm/worktrees/current.js +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.d.ts +1 -0
- package/dist/esm/worktrees/project-agent-surfaces.js +2 -1
- package/package.json +17 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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
|
|
260
|
-
return join(
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
-
//
|
|
255
|
-
|
|
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;
|