peaks-cli 1.2.7 → 1.2.9
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 +12 -0
- package/dist/src/cli/commands/core-artifact-commands.js +36 -1
- package/dist/src/cli/commands/perf-commands.d.ts +3 -0
- package/dist/src/cli/commands/perf-commands.js +41 -0
- package/dist/src/cli/commands/progress-close-kill.d.ts +51 -0
- package/dist/src/cli/commands/progress-close-kill.js +152 -0
- package/dist/src/cli/commands/progress-commands.d.ts +3 -0
- package/dist/src/cli/commands/progress-commands.js +348 -0
- package/dist/src/cli/commands/progress-start-spawn.d.ts +59 -0
- package/dist/src/cli/commands/progress-start-spawn.js +114 -0
- package/dist/src/cli/commands/progress-watch-render.d.ts +80 -0
- package/dist/src/cli/commands/progress-watch-render.js +308 -0
- package/dist/src/cli/commands/project-commands.js +1 -1
- package/dist/src/cli/commands/scan-commands.js +22 -0
- package/dist/src/cli/program.js +4 -0
- package/dist/src/services/config/config-types.d.ts +20 -0
- package/dist/src/services/config/config-types.js +5 -1
- package/dist/src/services/memory/project-memory-service.d.ts +1 -1
- package/dist/src/services/memory/project-memory-service.js +52 -23
- package/dist/src/services/perf/perf-baseline-service.d.ts +70 -0
- package/dist/src/services/perf/perf-baseline-service.js +213 -0
- package/dist/src/services/progress/progress-service.d.ts +179 -0
- package/dist/src/services/progress/progress-service.js +276 -0
- package/dist/src/services/scan/libraries-service.d.ts +24 -0
- package/dist/src/services/scan/libraries-service.js +419 -0
- package/dist/src/services/scan/libraries-types.d.ts +59 -0
- package/dist/src/services/scan/libraries-types.js +9 -0
- package/dist/src/services/session/index.d.ts +1 -1
- package/dist/src/services/session/index.js +1 -1
- package/dist/src/services/session/session-manager.d.ts +53 -8
- package/dist/src/services/session/session-manager.js +150 -3
- package/dist/src/services/skills/skill-presence-service.d.ts +27 -1
- package/dist/src/services/skills/skill-presence-service.js +112 -9
- package/dist/src/services/skills/skill-runbook-service.js +34 -1
- package/dist/src/services/workflow/autonomous-resume-writer.js +7 -7
- package/dist/src/shared/change-id.d.ts +30 -0
- package/dist/src/shared/change-id.js +40 -6
- package/dist/src/shared/paths.d.ts +1 -1
- package/dist/src/shared/paths.js +2 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +6 -2
- package/schemas/library-breaking-changes.data.json +141 -0
- package/schemas/library-breaking-changes.meta.json +6 -0
- package/schemas/library-breaking-changes.schema.json +50 -0
- package/skills/peaks-qa/SKILL.md +25 -0
- package/skills/peaks-rd/SKILL.md +221 -2
- package/skills/peaks-solo/SKILL.md +76 -316
- package/skills/peaks-solo/references/runbook.md +166 -0
- package/skills/peaks-solo/references/workflow-gates-and-types.md +177 -0
- package/skills/peaks-solo-resume/SKILL.md +81 -0
- package/skills/peaks-solo-status/SKILL.md +120 -0
- package/skills/peaks-solo-test/SKILL.md +84 -0
- package/skills/peaks-txt/SKILL.md +8 -5
package/README.md
CHANGED
|
@@ -13,6 +13,18 @@ npm install -g peaks-cli
|
|
|
13
13
|
|
|
14
14
|
安装后,Peaks 会把内置的 8 个 `peaks-*` 技能注册到 Claude Code,会话里直接通过技能名调用即可。
|
|
15
15
|
|
|
16
|
+
## 本地开发(从源码跑 CLI)
|
|
17
|
+
|
|
18
|
+
仓库自带 `peaks` CLI 源码。开发模式用 `tsx` 直接跑 `src/cli/index.ts`,所以**首次克隆后 `node_modules/` 里不会有 `chalk` / `ora` / `terminal-kit` 等运行时依赖**——直接 `tsx src/cli/index.ts` 会报 `ERR_MODULE_NOT_FOUND: chalk`。先执行一次 `pnpm install` 把依赖补齐,再验证:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm install
|
|
22
|
+
pnpm exec tsx src/cli/index.ts --version # 应打印 1.2.9
|
|
23
|
+
pnpm exec tsx src/cli/index.ts <cmd> # 与全局 `peaks <cmd>` 行为一致
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
热重载开发循环可用 `pnpm dev:watch`。
|
|
27
|
+
|
|
16
28
|
## 5 分钟上手
|
|
17
29
|
|
|
18
30
|
在 Claude Code 对话里,**直接对 Claude 说「用 X 技能做 Y」** 即可,技能会接管剩下的所有流程:
|
|
@@ -8,7 +8,8 @@ import { runDoctor } from '../../services/doctor/doctor-service.js';
|
|
|
8
8
|
import { listSkills } from '../../services/skills/skill-registry.js';
|
|
9
9
|
import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
|
|
10
10
|
import { setSkillPresence, clearSkillPresence, getSkillPresence, isSkillPresenceMode, touchSkillHeartbeat } from '../../services/skills/skill-presence-service.js';
|
|
11
|
-
import { ensureSession, getSessionMeta, setSessionMeta, setSessionTitle, listSessionMetas } from '../../services/session/session-manager.js';
|
|
11
|
+
import { ensureSession, getSessionMeta, rotateSessionBinding, setSessionMeta, setSessionTitle, listSessionMetas } from '../../services/session/session-manager.js';
|
|
12
|
+
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
12
13
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
13
14
|
import { generateProjectContext } from '../../services/memory/project-context-service.js';
|
|
14
15
|
import { fail, ok } from '../../shared/result.js';
|
|
@@ -214,6 +215,40 @@ export function registerCoreAndArtifactCommands(program, io) {
|
|
|
214
215
|
process.exitCode = 1;
|
|
215
216
|
}
|
|
216
217
|
});
|
|
218
|
+
addJsonOption(session
|
|
219
|
+
.command('rotate')
|
|
220
|
+
.description('Drop the project-level session binding so the next peaks call auto-generates a fresh session id. The on-disk session directory is left intact — only .peaks/.session.json is removed.')
|
|
221
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')
|
|
222
|
+
.option('--reason <text>', 'human-readable reason for the rotation, recorded in the response data')).action(async (options) => {
|
|
223
|
+
try {
|
|
224
|
+
// Canonicalise the project root before touching the binding.
|
|
225
|
+
// `peaks workspace init` writes the binding with the
|
|
226
|
+
// realpath-resolved projectRoot; if the caller passes a path
|
|
227
|
+
// through a symlink (notably /tmp on macOS, which is a
|
|
228
|
+
// symlink to /private/tmp) without canonicalising here,
|
|
229
|
+
// readSessionFile's strict projectRoot equality check fails
|
|
230
|
+
// and the rotate call reports "no prior binding" even
|
|
231
|
+
// though one exists. The same fix as `workspace init`
|
|
232
|
+
// (b193714): promote the path to the git root, falling back
|
|
233
|
+
// to the heuristic, falling back to cwd verbatim.
|
|
234
|
+
const projectRoot = options.project !== undefined
|
|
235
|
+
? options.project
|
|
236
|
+
: (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
237
|
+
const canonical = resolveCanonicalProjectRoot(projectRoot);
|
|
238
|
+
const previousSessionId = rotateSessionBinding(canonical);
|
|
239
|
+
printResult(io, ok('session.rotate', {
|
|
240
|
+
previousSessionId,
|
|
241
|
+
...(options.reason !== undefined ? { reason: options.reason } : {}),
|
|
242
|
+
note: previousSessionId === null
|
|
243
|
+
? 'No prior binding was present; the project is already unbound.'
|
|
244
|
+
: 'Next ensureSession() call will auto-generate a fresh id. The previous session directory is still on disk at .peaks/<previousSessionId>/.'
|
|
245
|
+
}), options.json);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
printResult(io, fail('session.rotate', 'SESSION_ROTATE_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Verify the project path exists and is writable']), options.json);
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
217
252
|
const profile = program.command('profile').description('Manage runtime profiles');
|
|
218
253
|
addJsonOption(profile.command('list').description('List available profiles')).action((options) => {
|
|
219
254
|
printResult(io, ok('profile.list', { profiles: listProfiles() }), options.json);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { executePerfBaselineInit, resolveProjectRootFromCwd } from '../../services/perf/perf-baseline-service.js';
|
|
2
|
+
import { fail, ok } from '../../shared/result.js';
|
|
3
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
4
|
+
export function registerPerfCommands(program, io) {
|
|
5
|
+
const perf = program.command('perf').description('Manage performance baseline scaffolding for the RD stage');
|
|
6
|
+
addJsonOption(perf
|
|
7
|
+
.command('baseline')
|
|
8
|
+
.description('Scaffold .peaks/<sid>/rd/perf-baseline.md so the RD can record the slice\'s perf numbers in a stable place that QA Gate A4 can diff against. Default dry-run; pass --apply to write.')
|
|
9
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')
|
|
10
|
+
.option('--apply', 'write the scaffold into the target project', false)
|
|
11
|
+
.option('--reason <text>', 'human-readable reason for the baseline (recorded in the response data)')).action(async (options) => {
|
|
12
|
+
try {
|
|
13
|
+
const projectRoot = options.project !== undefined
|
|
14
|
+
? options.project
|
|
15
|
+
: resolveProjectRootFromCwd(process.cwd());
|
|
16
|
+
const result = await executePerfBaselineInit({
|
|
17
|
+
projectRoot,
|
|
18
|
+
apply: options.apply === true,
|
|
19
|
+
...(options.reason !== undefined ? { reason: options.reason } : {})
|
|
20
|
+
});
|
|
21
|
+
const nextActions = [];
|
|
22
|
+
if (result.sessionId === null) {
|
|
23
|
+
nextActions.push('No peaks session is bound for this project yet. Run `peaks workspace init` (or any peaks skill) first so a session directory exists.');
|
|
24
|
+
}
|
|
25
|
+
else if (result.alreadyInitialized) {
|
|
26
|
+
nextActions.push(`perf-baseline.md already exists; no files were written. Re-run only after a re-measurement if you intend to overwrite.`);
|
|
27
|
+
}
|
|
28
|
+
else if (!result.apply) {
|
|
29
|
+
nextActions.push('Re-run with --apply to write the scaffold.');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
nextActions.push('Open the file and fill in the Results table — that is the input QA Gate A4 will diff against.');
|
|
33
|
+
}
|
|
34
|
+
printResult(io, ok('perf.baseline', result, [], nextActions), options.json);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
printResult(io, fail('perf.baseline', 'PERF_BASELINE_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Verify the project path exists and is writable']), options.json);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort close of a spawned `peaks progress watch`
|
|
3
|
+
* window. Used by `peaks progress close` (manual escape
|
|
4
|
+
* hatch) and by the watch-side auto-exit when the sub-agent
|
|
5
|
+
* hits a terminal phase.
|
|
6
|
+
*
|
|
7
|
+
* The close is best-effort by design: we never throw from
|
|
8
|
+
* individual signals. One failed close primitive is a UX
|
|
9
|
+
* paper cut, not a correctness bug — the caller still clears
|
|
10
|
+
* the spawn record after this returns.
|
|
11
|
+
*
|
|
12
|
+
* Cross-platform strategy:
|
|
13
|
+
*
|
|
14
|
+
* - macOS: pkill the watch process by command pattern
|
|
15
|
+
* (matches the project path, so we never close the
|
|
16
|
+
* wrong window), then send AppleScript to Terminal.app
|
|
17
|
+
* to close the window by `custom title`. Terminal.app
|
|
18
|
+
* is the dominant macOS terminal, and `custom title` is
|
|
19
|
+
* the only stable identifier we can target from outside
|
|
20
|
+
* the running shell.
|
|
21
|
+
* - Linux: pkill the watch process, then try `wmctrl -c
|
|
22
|
+
* peaks-cli-progress` to close the terminal window by
|
|
23
|
+
* WM class (set in `progress start` for alacritty /
|
|
24
|
+
* kitty; gnome-terminal / konsole / xfce4-terminal
|
|
25
|
+
* close on their own when the child exits). wmctrl is
|
|
26
|
+
* not always installed; we silently no-op on
|
|
27
|
+
* "command not found" (exit 127) and surface other
|
|
28
|
+
* errors as warnings.
|
|
29
|
+
* - Windows: `taskkill /F /FI "WINDOWTITLE eq
|
|
30
|
+
* peaks-cli:*"` to kill the cmd.exe wrapper. We use
|
|
31
|
+
* the title prefix because the exact title includes the
|
|
32
|
+
* `--reason` suffix which we do not know here.
|
|
33
|
+
*
|
|
34
|
+
* The kill is intentionally not a single primitive (e.g.
|
|
35
|
+
* `process.kill(-pid, 'SIGTERM')` on the process group).
|
|
36
|
+
* The launcher's PID is the spawn-time PID (osascript on
|
|
37
|
+
* macOS, gnome-terminal on Linux), not the long-lived
|
|
38
|
+
* watch process — and the long-lived process is the one we
|
|
39
|
+
* actually need to terminate to make the terminal close.
|
|
40
|
+
* Targeting by command pattern (pkill) + window title
|
|
41
|
+
* (AppleScript / wmctrl / taskkill) is more reliable than
|
|
42
|
+
* PID chasing across detached children.
|
|
43
|
+
*/
|
|
44
|
+
import type { ProgressSpawnRecord } from '../../services/progress/progress-service.js';
|
|
45
|
+
export type KillSpawnedTerminalResult = {
|
|
46
|
+
/** Each signal that was successfully sent. */
|
|
47
|
+
signals: string[];
|
|
48
|
+
/** Soft failures (e.g. pkill matched no process, wmctrl missing). */
|
|
49
|
+
warnings: string[];
|
|
50
|
+
};
|
|
51
|
+
export declare function killSpawnedTerminal(record: ProgressSpawnRecord, canonicalProjectRoot: string, currentPlatform: NodeJS.Platform): Promise<KillSpawnedTerminalResult>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort close of a spawned `peaks progress watch`
|
|
3
|
+
* window. Used by `peaks progress close` (manual escape
|
|
4
|
+
* hatch) and by the watch-side auto-exit when the sub-agent
|
|
5
|
+
* hits a terminal phase.
|
|
6
|
+
*
|
|
7
|
+
* The close is best-effort by design: we never throw from
|
|
8
|
+
* individual signals. One failed close primitive is a UX
|
|
9
|
+
* paper cut, not a correctness bug — the caller still clears
|
|
10
|
+
* the spawn record after this returns.
|
|
11
|
+
*
|
|
12
|
+
* Cross-platform strategy:
|
|
13
|
+
*
|
|
14
|
+
* - macOS: pkill the watch process by command pattern
|
|
15
|
+
* (matches the project path, so we never close the
|
|
16
|
+
* wrong window), then send AppleScript to Terminal.app
|
|
17
|
+
* to close the window by `custom title`. Terminal.app
|
|
18
|
+
* is the dominant macOS terminal, and `custom title` is
|
|
19
|
+
* the only stable identifier we can target from outside
|
|
20
|
+
* the running shell.
|
|
21
|
+
* - Linux: pkill the watch process, then try `wmctrl -c
|
|
22
|
+
* peaks-cli-progress` to close the terminal window by
|
|
23
|
+
* WM class (set in `progress start` for alacritty /
|
|
24
|
+
* kitty; gnome-terminal / konsole / xfce4-terminal
|
|
25
|
+
* close on their own when the child exits). wmctrl is
|
|
26
|
+
* not always installed; we silently no-op on
|
|
27
|
+
* "command not found" (exit 127) and surface other
|
|
28
|
+
* errors as warnings.
|
|
29
|
+
* - Windows: `taskkill /F /FI "WINDOWTITLE eq
|
|
30
|
+
* peaks-cli:*"` to kill the cmd.exe wrapper. We use
|
|
31
|
+
* the title prefix because the exact title includes the
|
|
32
|
+
* `--reason` suffix which we do not know here.
|
|
33
|
+
*
|
|
34
|
+
* The kill is intentionally not a single primitive (e.g.
|
|
35
|
+
* `process.kill(-pid, 'SIGTERM')` on the process group).
|
|
36
|
+
* The launcher's PID is the spawn-time PID (osascript on
|
|
37
|
+
* macOS, gnome-terminal on Linux), not the long-lived
|
|
38
|
+
* watch process — and the long-lived process is the one we
|
|
39
|
+
* actually need to terminate to make the terminal close.
|
|
40
|
+
* Targeting by command pattern (pkill) + window title
|
|
41
|
+
* (AppleScript / wmctrl / taskkill) is more reliable than
|
|
42
|
+
* PID chasing across detached children.
|
|
43
|
+
*/
|
|
44
|
+
import { execFile } from 'node:child_process';
|
|
45
|
+
import { promisify } from 'node:util';
|
|
46
|
+
import { getErrorMessage } from '../cli-helpers.js';
|
|
47
|
+
const execFileAsync = promisify(execFile);
|
|
48
|
+
export async function killSpawnedTerminal(record, canonicalProjectRoot, currentPlatform) {
|
|
49
|
+
const signals = [];
|
|
50
|
+
const warnings = [];
|
|
51
|
+
// The watch command we spawned, escaped for use as a pkill
|
|
52
|
+
// pattern. We anchor on `progress watch` (NOT `peaks progress
|
|
53
|
+
// watch`) because the actual cmdline is `.../peaks.js progress
|
|
54
|
+
// watch --project /path` — the literal substring
|
|
55
|
+
// `peaks progress watch` does NOT appear in the cmdline
|
|
56
|
+
// (there is a `.js` between `peaks` and `progress`).
|
|
57
|
+
// Anchoring on the verb + the project path is specific
|
|
58
|
+
// enough to not hit any user-owned `progress watch` process
|
|
59
|
+
// for a different project.
|
|
60
|
+
const watchPattern = `progress watch.*--project ${canonicalProjectRoot.replace(/[\\"\s]/g, '\\$&')}`;
|
|
61
|
+
if (currentPlatform === 'darwin') {
|
|
62
|
+
// pkill exit codes: 0 = matched & signalled, 1 = no processes
|
|
63
|
+
// matched (silent miss), 2 = syntax error (warning), 3 = fatal
|
|
64
|
+
// (warning). macOS pkill writes nothing to stderr on a clean
|
|
65
|
+
// miss, so the exit code is the only signal we have.
|
|
66
|
+
await trySignal('pkill', ['-f', watchPattern], signals, 'pkill-watch', warnings, /no.*process/i, new Set([1]));
|
|
67
|
+
// AppleScript to close the Terminal.app window by
|
|
68
|
+
// custom title. We use `every window whose custom title
|
|
69
|
+
// is` so we only close the right tab. AppleScript returns
|
|
70
|
+
// a non-zero exit when the window is already gone, the
|
|
71
|
+
// app is not running, or the title does not match — all
|
|
72
|
+
// of which are silent misses from the user's perspective
|
|
73
|
+
// (the user-facing outcome is identical to the success
|
|
74
|
+
// case: the window is no longer visible). Treat any
|
|
75
|
+
// non-zero exit as silent.
|
|
76
|
+
try {
|
|
77
|
+
const escapedTitle = record.windowTitle.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
78
|
+
await execFileAsync('osascript', [
|
|
79
|
+
'-e',
|
|
80
|
+
`tell application "Terminal" to close (every window whose custom title is "${escapedTitle}")`
|
|
81
|
+
]);
|
|
82
|
+
signals.push('osascript-close-window');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Silent miss. See comment above.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (currentPlatform === 'linux') {
|
|
89
|
+
// Same pkill exit code semantics as macOS.
|
|
90
|
+
await trySignal('pkill', ['-f', watchPattern], signals, 'pkill-watch', warnings, /no.*process/i, new Set([1]));
|
|
91
|
+
// wmctrl by WM class (set in `progress start`). Missing
|
|
92
|
+
// wmctrl is silent (exit 127) — most distros ship it but
|
|
93
|
+
// headless / minimal installs do not.
|
|
94
|
+
await trySignal('wmctrl', ['-c', 'peaks-cli-progress'], signals, 'wmctrl-close-class', warnings, /not found|No such file/i, new Set([127]));
|
|
95
|
+
}
|
|
96
|
+
else if (currentPlatform === 'win32') {
|
|
97
|
+
// Title prefix is set in `progress start` to `peaks-cli:`.
|
|
98
|
+
// We match the prefix because the full title includes
|
|
99
|
+
// the `--reason` suffix which we do not know here.
|
|
100
|
+
// taskkill exit codes: 0 = success, 1 = no tasks matched
|
|
101
|
+
// (silent miss — the window is already gone), 128 = error.
|
|
102
|
+
const titlePrefix = 'peaks-cli:';
|
|
103
|
+
await trySignal('taskkill', ['/F', '/FI', `WINDOWTITLE eq ${titlePrefix}*`], signals, 'taskkill-window-title', warnings, /no.*task/i, new Set([1]));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
warnings.push(`unsupported platform: ${currentPlatform}`);
|
|
107
|
+
}
|
|
108
|
+
return { signals, warnings };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Run a single close primitive. If it throws AND either
|
|
112
|
+
* (a) the error matches the "expected" stderr pattern
|
|
113
|
+
* (e.g. "no process matched" for pkill, "command not
|
|
114
|
+
* found" for wmctrl) — most platforms print this on
|
|
115
|
+
* stderr; or
|
|
116
|
+
* (b) the exit code is in `silentMissExitCodes` (pkill 1,
|
|
117
|
+
* wmctrl 127, taskkill 1) — the primitive ran, found
|
|
118
|
+
* nothing, and is not telling us via stderr,
|
|
119
|
+
* we silently no-op — that is the success case for the
|
|
120
|
+
* primitive. Other errors are appended to `warnings` for
|
|
121
|
+
* the caller to surface. On a clean resolve, the named
|
|
122
|
+
* signal is appended to `signals`.
|
|
123
|
+
*/
|
|
124
|
+
async function trySignal(command, args, signals, signal, warnings, expectedFailurePattern, silentMissExitCodes) {
|
|
125
|
+
try {
|
|
126
|
+
await execFileAsync(command, args);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// execFile's error object exposes `code` as either a
|
|
130
|
+
// numeric exit code (when the process ran) or a string
|
|
131
|
+
// system code like 'ENOENT' (when the binary itself
|
|
132
|
+
// is missing). Only numeric exit codes are candidates
|
|
133
|
+
// for silent-miss.
|
|
134
|
+
const execError = error;
|
|
135
|
+
if (typeof execError.code === 'number' && silentMissExitCodes.has(execError.code)) {
|
|
136
|
+
// Exit code says "ran, but found nothing to act on".
|
|
137
|
+
// The user-facing outcome is identical to the success
|
|
138
|
+
// case, so do not surface a warning.
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const message = getErrorMessage(error);
|
|
142
|
+
if (expectedFailurePattern.test(message)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
warnings.push(`${command}: ${message}`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// Reached only if execFile resolves (exit 0). All three
|
|
149
|
+
// primitives exit non-zero on a miss, so a clean resolve
|
|
150
|
+
// means the signal landed.
|
|
151
|
+
signals.push(signal);
|
|
152
|
+
}
|