@imdeadpool/guardex 7.0.21 → 7.0.23
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/README.md +39 -29
- package/package.json +5 -1
- package/src/cli/args.js +89 -0
- package/src/cli/main.js +645 -2873
- package/src/context.js +195 -31
- package/src/core/stdin.js +52 -0
- package/src/core/versions.js +33 -0
- package/src/doctor/index.js +1071 -0
- package/src/finish/index.js +456 -358
- package/src/git/index.js +604 -1
- package/src/hooks/index.js +64 -0
- package/src/output/index.js +72 -5
- package/src/report/session-severity.js +213 -0
- package/src/sandbox/index.js +301 -52
- package/src/scaffold/index.js +627 -0
- package/src/toolchain/index.js +559 -179
- package/templates/AGENTS.multiagent-safety.md +25 -0
- package/templates/scripts/agent-branch-finish.sh +86 -6
- package/templates/scripts/agent-session-state.js +62 -1
- package/templates/scripts/agent-worktree-prune.sh +15 -1
- package/templates/scripts/codex-agent.sh +38 -0
- package/templates/scripts/install-vscode-active-agents-extension.js +38 -11
- package/templates/scripts/openspec/init-plan-workspace.sh +34 -3
- package/templates/vscode/guardex-active-agents/README.md +9 -6
- package/templates/vscode/guardex-active-agents/extension.js +805 -77
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/package.json +15 -3
- package/templates/vscode/guardex-active-agents/session-schema.js +311 -4
package/src/toolchain/index.js
CHANGED
|
@@ -1,223 +1,603 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
1
|
+
const {
|
|
2
|
+
fs,
|
|
3
|
+
path,
|
|
4
|
+
cp,
|
|
5
|
+
packageJson,
|
|
6
|
+
TOOL_NAME,
|
|
7
|
+
SHORT_TOOL_NAME,
|
|
8
|
+
OPENSPEC_PACKAGE,
|
|
9
|
+
NPX_BIN,
|
|
10
|
+
GUARDEX_HOME_DIR,
|
|
11
|
+
GLOBAL_TOOLCHAIN_SERVICES,
|
|
12
|
+
GLOBAL_TOOLCHAIN_PACKAGES,
|
|
13
|
+
OPTIONAL_LOCAL_COMPANION_TOOLS,
|
|
14
|
+
REQUIRED_SYSTEM_TOOLS,
|
|
15
|
+
NPM_BIN,
|
|
16
|
+
OPENSPEC_BIN,
|
|
17
|
+
envFlagIsTruthy,
|
|
18
|
+
} = require('../context');
|
|
19
|
+
const { run } = require('../core/runtime');
|
|
20
|
+
const {
|
|
21
|
+
parseVersionString,
|
|
22
|
+
compareParsedVersions,
|
|
23
|
+
isNewerVersion,
|
|
24
|
+
} = require('../core/versions');
|
|
25
|
+
const { readSingleLineFromStdin } = require('../core/stdin');
|
|
26
|
+
const { colorize } = require('../output');
|
|
27
|
+
|
|
28
|
+
function isInteractiveTerminal() {
|
|
29
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
30
|
+
}
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
function parseAutoApproval(name) {
|
|
33
|
+
const raw = process.env[name];
|
|
34
|
+
if (raw == null) return null;
|
|
35
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
36
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) return true;
|
|
37
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) return false;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
function parseNpmVersionOutput(stdout) {
|
|
42
|
+
const trimmed = String(stdout || '').trim();
|
|
43
|
+
if (!trimmed) return '';
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(trimmed);
|
|
47
|
+
if (Array.isArray(parsed)) {
|
|
48
|
+
return String(parsed[parsed.length - 1] || '').trim();
|
|
41
49
|
}
|
|
50
|
+
return String(parsed || '').trim();
|
|
51
|
+
} catch {
|
|
52
|
+
const firstLine = trimmed.split('\n').map((line) => line.trim()).find(Boolean);
|
|
53
|
+
return firstLine || '';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: autoApproval;
|
|
57
|
+
function checkForGuardexUpdate() {
|
|
58
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_UPDATE_CHECK)) {
|
|
59
|
+
return { checked: false, reason: 'disabled' };
|
|
60
|
+
}
|
|
48
61
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
const forceCheck = envFlagIsTruthy(process.env.GUARDEX_FORCE_UPDATE_CHECK);
|
|
63
|
+
if (!forceCheck && !isInteractiveTerminal()) {
|
|
64
|
+
return { checked: false, reason: 'non-interactive' };
|
|
65
|
+
}
|
|
53
66
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
67
|
+
const result = run(NPM_BIN, ['view', packageJson.name, 'version', '--json'], { timeout: 5000 });
|
|
68
|
+
if (result.status !== 0) {
|
|
69
|
+
return { checked: false, reason: 'lookup-failed' };
|
|
70
|
+
}
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
const latest = parseNpmVersionOutput(result.stdout);
|
|
73
|
+
if (!latest) {
|
|
74
|
+
return { checked: false, reason: 'invalid-latest-version' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
checked: true,
|
|
79
|
+
current: packageJson.version,
|
|
80
|
+
latest,
|
|
81
|
+
updateAvailable: isNewerVersion(latest, packageJson.version),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function printUpdateAvailableBanner(current, latest) {
|
|
86
|
+
const title = colorize('UPDATE AVAILABLE', '1;33');
|
|
87
|
+
console.log(`[${TOOL_NAME}] ${title}`);
|
|
88
|
+
console.log(`[${TOOL_NAME}] Current: ${current}`);
|
|
89
|
+
console.log(`[${TOOL_NAME}] Latest : ${latest}`);
|
|
90
|
+
console.log(`[${TOOL_NAME}] Command: ${NPM_BIN} i -g ${packageJson.name}@latest`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readInstalledGuardexVersion() {
|
|
94
|
+
const installInfo = readInstalledGuardexInstallInfo();
|
|
95
|
+
return installInfo ? installInfo.version : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readInstalledGuardexInstallInfo() {
|
|
99
|
+
try {
|
|
100
|
+
const rootResult = run(NPM_BIN, ['root', '-g'], { timeout: 5000 });
|
|
101
|
+
if (rootResult.status !== 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const globalRoot = String(rootResult.stdout || '').trim();
|
|
105
|
+
if (!globalRoot) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const installedPkgPath = path.join(globalRoot, packageJson.name, 'package.json');
|
|
109
|
+
if (!fs.existsSync(installedPkgPath)) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
const parsed = JSON.parse(fs.readFileSync(installedPkgPath, 'utf8'));
|
|
113
|
+
if (parsed && typeof parsed.version === 'string') {
|
|
114
|
+
let binRelative = null;
|
|
115
|
+
if (typeof parsed.bin === 'string') {
|
|
116
|
+
binRelative = parsed.bin;
|
|
117
|
+
} else if (parsed.bin && typeof parsed.bin === 'object') {
|
|
118
|
+
const invokedName = path.basename(process.argv[1] || '');
|
|
119
|
+
binRelative =
|
|
120
|
+
parsed.bin[invokedName] ||
|
|
121
|
+
parsed.bin[SHORT_TOOL_NAME] ||
|
|
122
|
+
Object.values(parsed.bin).find((value) => typeof value === 'string') ||
|
|
123
|
+
null;
|
|
84
124
|
}
|
|
125
|
+
const packageRoot = path.dirname(installedPkgPath);
|
|
126
|
+
const binPath = binRelative ? path.join(packageRoot, binRelative) : null;
|
|
127
|
+
return {
|
|
128
|
+
version: parsed.version,
|
|
129
|
+
packageRoot,
|
|
130
|
+
binPath,
|
|
131
|
+
};
|
|
85
132
|
}
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
86
138
|
|
|
87
|
-
|
|
88
|
-
|
|
139
|
+
function restartIntoUpdatedGuardex(expectedVersion) {
|
|
140
|
+
const installInfo = readInstalledGuardexInstallInfo();
|
|
141
|
+
if (!installInfo || installInfo.version !== expectedVersion || installInfo.version === packageJson.version) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!installInfo.binPath || !fs.existsSync(installInfo.binPath)) {
|
|
145
|
+
console.log(`[${TOOL_NAME}] Restart required to use ${installInfo.version}. Rerun ${SHORT_TOOL_NAME}.`);
|
|
146
|
+
return;
|
|
89
147
|
}
|
|
90
148
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
149
|
+
console.log(`[${TOOL_NAME}] Restarting into ${installInfo.version}…`);
|
|
150
|
+
const restartResult = cp.spawnSync(
|
|
151
|
+
process.execPath,
|
|
152
|
+
[installInfo.binPath, ...process.argv.slice(2)],
|
|
153
|
+
{
|
|
154
|
+
cwd: process.cwd(),
|
|
155
|
+
env: {
|
|
156
|
+
...process.env,
|
|
157
|
+
GUARDEX_SKIP_UPDATE_CHECK: '1',
|
|
158
|
+
},
|
|
159
|
+
stdio: 'inherit',
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
if (restartResult.error) {
|
|
163
|
+
console.log(
|
|
164
|
+
`[${TOOL_NAME}] Restart into ${installInfo.version} failed. Rerun ${SHORT_TOOL_NAME}.`,
|
|
165
|
+
);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
process.exit(restartResult.status == null ? 0 : restartResult.status);
|
|
169
|
+
}
|
|
96
170
|
|
|
97
|
-
|
|
171
|
+
function checkForOpenSpecPackageUpdate() {
|
|
172
|
+
if (envFlagIsTruthy(process.env.GUARDEX_SKIP_OPENSPEC_UPDATE_CHECK)) {
|
|
173
|
+
return { checked: false, reason: 'disabled' };
|
|
174
|
+
}
|
|
98
175
|
|
|
99
|
-
|
|
100
|
-
|
|
176
|
+
const forceCheck = envFlagIsTruthy(process.env.GUARDEX_FORCE_OPENSPEC_UPDATE_CHECK);
|
|
177
|
+
if (!forceCheck && !isInteractiveTerminal()) {
|
|
178
|
+
return { checked: false, reason: 'non-interactive' };
|
|
179
|
+
}
|
|
101
180
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
181
|
+
const detection = detectGlobalToolchainPackages();
|
|
182
|
+
if (!detection.ok) {
|
|
183
|
+
return { checked: false, reason: 'package-detect-failed' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const current = String((detection.installedVersions || {})[OPENSPEC_PACKAGE] || '').trim();
|
|
187
|
+
if (!current) {
|
|
188
|
+
return { checked: false, reason: 'not-installed' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const latestResult = run(NPM_BIN, ['view', OPENSPEC_PACKAGE, 'version', '--json'], { timeout: 5000 });
|
|
192
|
+
if (latestResult.status !== 0) {
|
|
193
|
+
return { checked: false, reason: 'lookup-failed' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const latest = parseNpmVersionOutput(latestResult.stdout);
|
|
197
|
+
if (!latest) {
|
|
198
|
+
return { checked: false, reason: 'invalid-latest-version' };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
checked: true,
|
|
203
|
+
current,
|
|
204
|
+
latest,
|
|
205
|
+
updateAvailable: isNewerVersion(latest, current),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function printOpenSpecUpdateAvailableBanner(current, latest) {
|
|
210
|
+
const title = colorize('OPENSPEC UPDATE AVAILABLE', '1;33');
|
|
211
|
+
console.log(`[${TOOL_NAME}] ${title}`);
|
|
212
|
+
console.log(`[${TOOL_NAME}] Current: ${current}`);
|
|
213
|
+
console.log(`[${TOOL_NAME}] Latest : ${latest}`);
|
|
214
|
+
console.log(`[${TOOL_NAME}] Command: ${NPM_BIN} i -g ${OPENSPEC_PACKAGE}@latest`);
|
|
215
|
+
console.log(`[${TOOL_NAME}] Then : ${OPENSPEC_BIN} update`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function promptYesNoStrict(question) {
|
|
219
|
+
while (true) {
|
|
220
|
+
process.stdout.write(`${question} [y/n] `);
|
|
221
|
+
const answer = readSingleLineFromStdin().trim().toLowerCase();
|
|
222
|
+
|
|
223
|
+
if (answer === 'y' || answer === 'yes') {
|
|
224
|
+
process.stdout.write('\n');
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (answer === 'n' || answer === 'no') {
|
|
228
|
+
process.stdout.write('\n');
|
|
229
|
+
return false;
|
|
105
230
|
}
|
|
106
231
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
: autoApproval;
|
|
232
|
+
process.stdout.write('Please answer with y or n.\n');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
112
235
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
236
|
+
function resolveGlobalInstallApproval(options) {
|
|
237
|
+
if (options.yesGlobalInstall && options.noGlobalInstall) {
|
|
238
|
+
throw new Error('Cannot use both --yes-global-install and --no-global-install');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (options.yesGlobalInstall) {
|
|
242
|
+
return { approved: true, source: 'flag' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (options.noGlobalInstall) {
|
|
246
|
+
return { approved: false, source: 'flag' };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!isInteractiveTerminal()) {
|
|
250
|
+
return { approved: false, source: 'non-interactive-default' };
|
|
251
|
+
}
|
|
252
|
+
return { approved: true, source: 'prompt' };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getGlobalToolchainService(packageName) {
|
|
256
|
+
const service = GLOBAL_TOOLCHAIN_SERVICES.find(
|
|
257
|
+
(candidate) => candidate.packageName === packageName,
|
|
258
|
+
);
|
|
259
|
+
return service || { name: packageName, packageName };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function formatGlobalToolchainServiceName(packageName) {
|
|
263
|
+
return getGlobalToolchainService(packageName).name;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function describeMissingGlobalDependencyWarnings(packageNames) {
|
|
267
|
+
return packageNames
|
|
268
|
+
.map((packageName) => getGlobalToolchainService(packageName))
|
|
269
|
+
.filter((service) => service.dependencyUrl)
|
|
270
|
+
.map(
|
|
271
|
+
(service) =>
|
|
272
|
+
`Guardex needs ${service.name} as a dependency: ${service.dependencyUrl}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function describeCompanionInstallCommands(missingPackages, missingLocalTools) {
|
|
277
|
+
const commands = [];
|
|
278
|
+
if (missingPackages.length > 0) {
|
|
279
|
+
commands.push(`${NPM_BIN} i -g ${missingPackages.join(' ')}`);
|
|
280
|
+
}
|
|
281
|
+
for (const tool of missingLocalTools) {
|
|
282
|
+
commands.push(tool.installCommand);
|
|
283
|
+
}
|
|
284
|
+
return commands;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function buildMissingCompanionInstallPrompt(missingPackages, missingLocalTools) {
|
|
288
|
+
const dependencyWarnings = describeMissingGlobalDependencyWarnings(missingPackages);
|
|
289
|
+
const installCommands = describeCompanionInstallCommands(missingPackages, missingLocalTools);
|
|
290
|
+
const dependencyPrefix = dependencyWarnings.length > 0
|
|
291
|
+
? `${dependencyWarnings.join(' ')} `
|
|
292
|
+
: '';
|
|
293
|
+
return `${dependencyPrefix}Install missing companion tools now? (${installCommands.join(' && ')})`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function detectGlobalToolchainPackages() {
|
|
297
|
+
const result = run(NPM_BIN, ['list', '-g', '--depth=0', '--json']);
|
|
298
|
+
if (result.status !== 0) {
|
|
299
|
+
const stderr = (result.stderr || '').trim();
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
error: stderr || 'Unable to detect globally installed npm packages',
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let parsed;
|
|
307
|
+
try {
|
|
308
|
+
parsed = JSON.parse(result.stdout || '{}');
|
|
309
|
+
} catch (error) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
error: `Failed to parse npm list output: ${error.message}`,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const dependencyMap = parsed && parsed.dependencies && typeof parsed.dependencies === 'object'
|
|
317
|
+
? parsed.dependencies
|
|
318
|
+
: {};
|
|
319
|
+
const installedSet = new Set(Object.keys(dependencyMap));
|
|
320
|
+
|
|
321
|
+
const installed = [];
|
|
322
|
+
const missing = [];
|
|
323
|
+
const installedVersions = {};
|
|
324
|
+
for (const pkg of GLOBAL_TOOLCHAIN_PACKAGES) {
|
|
325
|
+
if (installedSet.has(pkg)) {
|
|
326
|
+
installed.push(pkg);
|
|
327
|
+
const rawVersion = dependencyMap[pkg] && dependencyMap[pkg].version;
|
|
328
|
+
const version = String(rawVersion || '').trim();
|
|
329
|
+
if (version) {
|
|
330
|
+
installedVersions[pkg] = version;
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
missing.push(pkg);
|
|
116
334
|
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return { ok: true, installed, missing, installedVersions };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function detectRequiredSystemTools() {
|
|
341
|
+
const services = [];
|
|
342
|
+
for (const tool of REQUIRED_SYSTEM_TOOLS) {
|
|
343
|
+
const result = run(tool.command, ['--version']);
|
|
344
|
+
const active = result.status === 0;
|
|
345
|
+
const rawReason = result.error && result.error.code
|
|
346
|
+
? result.error.code
|
|
347
|
+
: (result.stderr || '').trim();
|
|
348
|
+
const reason = rawReason.split('\n')[0] || '';
|
|
349
|
+
services.push({
|
|
350
|
+
name: tool.name,
|
|
351
|
+
displayName: tool.displayName || tool.name,
|
|
352
|
+
command: tool.command,
|
|
353
|
+
installHint: tool.installHint,
|
|
354
|
+
status: active ? 'active' : 'inactive',
|
|
355
|
+
reason,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return services;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function detectOptionalLocalCompanionTools() {
|
|
362
|
+
return OPTIONAL_LOCAL_COMPANION_TOOLS.map((tool) => {
|
|
363
|
+
const detectedPath = tool.candidatePaths
|
|
364
|
+
.map((relativePath) => path.join(GUARDEX_HOME_DIR, relativePath))
|
|
365
|
+
.find((candidatePath) => fs.existsSync(candidatePath));
|
|
366
|
+
return {
|
|
367
|
+
name: tool.name,
|
|
368
|
+
displayName: tool.displayName || tool.name,
|
|
369
|
+
installCommand: tool.installCommand,
|
|
370
|
+
installArgs: [...tool.installArgs],
|
|
371
|
+
status: detectedPath ? 'active' : 'inactive',
|
|
372
|
+
detectedPath: detectedPath || null,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function askGlobalInstallForMissing(options, missingPackages, missingLocalTools) {
|
|
378
|
+
const approval = resolveGlobalInstallApproval(options);
|
|
379
|
+
if (!approval.approved) {
|
|
380
|
+
return approval;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (approval.source === 'prompt') {
|
|
384
|
+
const approved = promptYesNoStrict(
|
|
385
|
+
buildMissingCompanionInstallPrompt(missingPackages, missingLocalTools),
|
|
386
|
+
);
|
|
387
|
+
return { approved, source: 'prompt' };
|
|
388
|
+
}
|
|
117
389
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
390
|
+
return approval;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function maybeSelfUpdateBeforeStatus() {
|
|
394
|
+
const check = checkForGuardexUpdate();
|
|
395
|
+
if (!check.checked || !check.updateAvailable) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
printUpdateAvailableBanner(check.current, check.latest);
|
|
400
|
+
|
|
401
|
+
const autoApproval = parseAutoApproval('GUARDEX_AUTO_UPDATE_APPROVAL');
|
|
402
|
+
const interactive = isInteractiveTerminal();
|
|
403
|
+
|
|
404
|
+
if (!interactive && autoApproval == null) {
|
|
405
|
+
console.log(`[${TOOL_NAME}] Non-interactive shell; skipping auto-update prompt.`);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const shouldUpdate = interactive
|
|
410
|
+
? promptYesNoStrict(
|
|
411
|
+
`Update now? (${NPM_BIN} i -g ${packageJson.name}@latest)`,
|
|
412
|
+
)
|
|
413
|
+
: autoApproval;
|
|
414
|
+
|
|
415
|
+
if (!shouldUpdate) {
|
|
416
|
+
console.log(`[${TOOL_NAME}] Skipped update.`);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const installResult = run(NPM_BIN, ['i', '-g', `${packageJson.name}@latest`], { stdio: 'inherit' });
|
|
421
|
+
if (installResult.status !== 0) {
|
|
422
|
+
console.log(`[${TOOL_NAME}] Update failed. You can retry manually.`);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const postInstallVersion = readInstalledGuardexVersion();
|
|
427
|
+
if (postInstallVersion != null && postInstallVersion !== check.latest) {
|
|
428
|
+
console.log(
|
|
429
|
+
`[${TOOL_NAME}] Installed version is still ${postInstallVersion} (expected ${check.latest}). ` +
|
|
430
|
+
`Retrying with pinned version ${check.latest}...`,
|
|
431
|
+
);
|
|
432
|
+
const pinnedResult = run(
|
|
433
|
+
NPM_BIN,
|
|
434
|
+
['i', '-g', `${packageJson.name}@${check.latest}`],
|
|
435
|
+
{ stdio: 'inherit' },
|
|
436
|
+
);
|
|
437
|
+
if (pinnedResult.status !== 0) {
|
|
438
|
+
console.log(
|
|
439
|
+
`[${TOOL_NAME}] Pinned retry failed. Run manually: ${NPM_BIN} i -g ${packageJson.name}@${check.latest}`,
|
|
440
|
+
);
|
|
121
441
|
return;
|
|
122
442
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
443
|
+
const pinnedVersion = readInstalledGuardexVersion();
|
|
444
|
+
if (pinnedVersion != null && pinnedVersion !== check.latest) {
|
|
445
|
+
console.log(
|
|
446
|
+
`[${TOOL_NAME}] On-disk version still ${pinnedVersion} after pinned retry. ` +
|
|
447
|
+
`Investigate: ${NPM_BIN} root -g && ${NPM_BIN} cache verify`,
|
|
448
|
+
);
|
|
127
449
|
return;
|
|
128
450
|
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(`[${TOOL_NAME}] Updated to latest published version.`);
|
|
454
|
+
restartIntoUpdatedGuardex(check.latest);
|
|
455
|
+
}
|
|
129
456
|
|
|
130
|
-
|
|
457
|
+
function maybeOpenSpecUpdateBeforeStatus() {
|
|
458
|
+
const check = checkForOpenSpecPackageUpdate();
|
|
459
|
+
if (!check.checked || !check.updateAvailable) {
|
|
460
|
+
return;
|
|
131
461
|
}
|
|
132
462
|
|
|
133
|
-
|
|
134
|
-
const approval = resolveGlobalInstallApproval(options);
|
|
135
|
-
if (approval.source === 'flag' && !approval.approved) {
|
|
136
|
-
return {
|
|
137
|
-
status: 'skipped',
|
|
138
|
-
reason: approval.source,
|
|
139
|
-
missingPackages: [],
|
|
140
|
-
missingLocalTools: [],
|
|
141
|
-
};
|
|
142
|
-
}
|
|
463
|
+
printOpenSpecUpdateAvailableBanner(check.current, check.latest);
|
|
143
464
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
465
|
+
const autoApproval = parseAutoApproval('GUARDEX_AUTO_OPENSPEC_UPDATE_APPROVAL');
|
|
466
|
+
const interactive = isInteractiveTerminal();
|
|
147
467
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else {
|
|
153
|
-
if (detection.installed.length > 0) {
|
|
154
|
-
console.log(
|
|
155
|
-
`[${TOOL_NAME}] Already installed globally: ` +
|
|
156
|
-
`${detection.installed.map((pkg) => formatGlobalToolchainServiceName(pkg)).join(', ')}`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
const installedLocalTools = localCompanionTools
|
|
160
|
-
.filter((tool) => tool.status === 'active')
|
|
161
|
-
.map((tool) => tool.name);
|
|
162
|
-
if (installedLocalTools.length > 0) {
|
|
163
|
-
console.log(`[${TOOL_NAME}] Already installed locally: ${installedLocalTools.join(', ')}`);
|
|
164
|
-
}
|
|
165
|
-
if (detection.missing.length === 0 && localCompanionTools.every((tool) => tool.status === 'active')) {
|
|
166
|
-
return { status: 'already-installed' };
|
|
167
|
-
}
|
|
168
|
-
}
|
|
468
|
+
if (!interactive && autoApproval == null) {
|
|
469
|
+
console.log(`[${TOOL_NAME}] Non-interactive shell; skipping OpenSpec update prompt.`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
169
472
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
473
|
+
const shouldUpdate = interactive
|
|
474
|
+
? promptYesNoStrict(
|
|
475
|
+
`Update OpenSpec now? (${NPM_BIN} i -g ${OPENSPEC_PACKAGE}@latest && ${OPENSPEC_BIN} update)`,
|
|
476
|
+
)
|
|
477
|
+
: autoApproval;
|
|
478
|
+
|
|
479
|
+
if (!shouldUpdate) {
|
|
480
|
+
console.log(`[${TOOL_NAME}] Skipped OpenSpec update.`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
181
483
|
|
|
182
|
-
|
|
183
|
-
|
|
484
|
+
const installResult = run(NPM_BIN, ['i', '-g', `${OPENSPEC_PACKAGE}@latest`], { stdio: 'inherit' });
|
|
485
|
+
if (installResult.status !== 0) {
|
|
486
|
+
console.log(`[${TOOL_NAME}] OpenSpec npm install failed. You can retry manually.`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const toolUpdateResult = run(OPENSPEC_BIN, ['update'], { stdio: 'inherit' });
|
|
491
|
+
if (toolUpdateResult.status !== 0) {
|
|
492
|
+
console.log(`[${TOOL_NAME}] OpenSpec tool update failed. Run '${OPENSPEC_BIN} update' manually.`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
console.log(`[${TOOL_NAME}] OpenSpec updated to latest package and tool plugins refreshed.`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function installGlobalToolchain(options) {
|
|
500
|
+
const approval = resolveGlobalInstallApproval(options);
|
|
501
|
+
if (approval.source === 'flag' && !approval.approved) {
|
|
502
|
+
return {
|
|
503
|
+
status: 'skipped',
|
|
504
|
+
reason: approval.source,
|
|
505
|
+
missingPackages: [],
|
|
506
|
+
missingLocalTools: [],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (options.dryRun) {
|
|
511
|
+
return { status: 'dry-run-skip' };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const detection = detectGlobalToolchainPackages();
|
|
515
|
+
const localCompanionTools = detectOptionalLocalCompanionTools();
|
|
516
|
+
if (!detection.ok) {
|
|
517
|
+
console.log(`[${TOOL_NAME}] Could not detect global packages: ${detection.error}`);
|
|
518
|
+
} else {
|
|
519
|
+
if (detection.installed.length > 0) {
|
|
184
520
|
console.log(
|
|
185
|
-
`[${TOOL_NAME}]
|
|
521
|
+
`[${TOOL_NAME}] Already installed globally: ` +
|
|
522
|
+
`${detection.installed.map((pkg) => formatGlobalToolchainServiceName(pkg)).join(', ')}`,
|
|
186
523
|
);
|
|
187
|
-
const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
|
|
188
|
-
if (result.status !== 0) {
|
|
189
|
-
const stderr = (result.stderr || '').trim();
|
|
190
|
-
return {
|
|
191
|
-
status: 'failed',
|
|
192
|
-
reason: stderr || 'npm global install failed',
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
installed.push(...missingPackages);
|
|
196
524
|
}
|
|
525
|
+
const installedLocalTools = localCompanionTools
|
|
526
|
+
.filter((tool) => tool.status === 'active')
|
|
527
|
+
.map((tool) => tool.name);
|
|
528
|
+
if (installedLocalTools.length > 0) {
|
|
529
|
+
console.log(`[${TOOL_NAME}] Already installed locally: ${installedLocalTools.join(', ')}`);
|
|
530
|
+
}
|
|
531
|
+
if (detection.missing.length === 0 && localCompanionTools.every((tool) => tool.status === 'active')) {
|
|
532
|
+
return { status: 'already-installed' };
|
|
533
|
+
}
|
|
534
|
+
}
|
|
197
535
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
536
|
+
const missingPackages = detection.ok ? detection.missing : [...GLOBAL_TOOLCHAIN_PACKAGES];
|
|
537
|
+
const missingLocalTools = localCompanionTools.filter((tool) => tool.status !== 'active');
|
|
538
|
+
const installApproval = askGlobalInstallForMissing(options, missingPackages, missingLocalTools);
|
|
539
|
+
if (!installApproval.approved) {
|
|
540
|
+
return {
|
|
541
|
+
status: 'skipped',
|
|
542
|
+
reason: installApproval.source,
|
|
543
|
+
missingPackages,
|
|
544
|
+
missingLocalTools,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const installed = [];
|
|
549
|
+
if (missingPackages.length > 0) {
|
|
550
|
+
console.log(
|
|
551
|
+
`[${TOOL_NAME}] Installing global toolchain: npm i -g ${missingPackages.join(' ')}`,
|
|
552
|
+
);
|
|
553
|
+
const result = run(NPM_BIN, ['i', '-g', ...missingPackages], { stdio: 'inherit' });
|
|
554
|
+
if (result.status !== 0) {
|
|
555
|
+
const stderr = (result.stderr || '').trim();
|
|
556
|
+
return {
|
|
557
|
+
status: 'failed',
|
|
558
|
+
reason: stderr || 'npm global install failed',
|
|
559
|
+
};
|
|
209
560
|
}
|
|
561
|
+
installed.push(...missingPackages);
|
|
562
|
+
}
|
|
210
563
|
|
|
211
|
-
|
|
564
|
+
for (const tool of missingLocalTools) {
|
|
565
|
+
console.log(`[${TOOL_NAME}] Installing local companion tool: ${tool.installCommand}`);
|
|
566
|
+
const result = run(NPX_BIN, tool.installArgs, { stdio: 'inherit' });
|
|
567
|
+
if (result.status !== 0) {
|
|
568
|
+
const stderr = (result.stderr || '').trim();
|
|
569
|
+
return {
|
|
570
|
+
status: 'failed',
|
|
571
|
+
reason: stderr || `${tool.name} install failed`,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
installed.push(tool.name);
|
|
212
575
|
}
|
|
213
576
|
|
|
214
|
-
return {
|
|
215
|
-
maybeSelfUpdateBeforeStatus,
|
|
216
|
-
maybeOpenSpecUpdateBeforeStatus,
|
|
217
|
-
installGlobalToolchain,
|
|
218
|
-
};
|
|
577
|
+
return { status: 'installed', packages: installed };
|
|
219
578
|
}
|
|
220
579
|
|
|
221
580
|
module.exports = {
|
|
222
|
-
|
|
581
|
+
isInteractiveTerminal,
|
|
582
|
+
parseAutoApproval,
|
|
583
|
+
checkForGuardexUpdate,
|
|
584
|
+
printUpdateAvailableBanner,
|
|
585
|
+
readInstalledGuardexVersion,
|
|
586
|
+
readInstalledGuardexInstallInfo,
|
|
587
|
+
restartIntoUpdatedGuardex,
|
|
588
|
+
checkForOpenSpecPackageUpdate,
|
|
589
|
+
printOpenSpecUpdateAvailableBanner,
|
|
590
|
+
promptYesNoStrict,
|
|
591
|
+
resolveGlobalInstallApproval,
|
|
592
|
+
getGlobalToolchainService,
|
|
593
|
+
formatGlobalToolchainServiceName,
|
|
594
|
+
describeMissingGlobalDependencyWarnings,
|
|
595
|
+
describeCompanionInstallCommands,
|
|
596
|
+
detectGlobalToolchainPackages,
|
|
597
|
+
detectRequiredSystemTools,
|
|
598
|
+
detectOptionalLocalCompanionTools,
|
|
599
|
+
askGlobalInstallForMissing,
|
|
600
|
+
maybeSelfUpdateBeforeStatus,
|
|
601
|
+
maybeOpenSpecUpdateBeforeStatus,
|
|
602
|
+
installGlobalToolchain,
|
|
223
603
|
};
|