peaks-cli 1.3.4 → 1.3.5
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/dist/src/cli/commands/hook-handle.d.ts +2 -2
- package/dist/src/cli/commands/hook-handle.js +5 -10
- package/dist/src/cli/commands/hooks-commands.js +44 -29
- package/dist/src/cli/commands/project-commands.js +7 -1
- package/dist/src/cli/commands/workspace-commands.js +1 -2
- package/dist/src/cli/program.js +3 -2
- package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +45 -40
- package/dist/src/services/dispatch/sub-agent-dispatcher.js +25 -20
- package/dist/src/services/ide/adapters/claude-code-adapter.js +0 -2
- package/dist/src/services/ide/adapters/trae-adapter.js +2 -4
- package/dist/src/services/ide/ide-types.d.ts +1 -16
- package/dist/src/services/progress/progress-service.d.ts +23 -103
- package/dist/src/services/progress/progress-service.js +24 -137
- package/dist/src/services/scan/file-size-scan.d.ts +4 -0
- package/dist/src/services/scan/file-size-scan.js +32 -3
- package/dist/src/services/skills/hooks-settings-service.d.ts +57 -5
- package/dist/src/services/skills/hooks-settings-service.js +153 -28
- package/dist/src/shared/incrementing-number.d.ts +0 -8
- package/dist/src/shared/incrementing-number.js +11 -1
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-qa/references/qa-fanout-contract.md +6 -6
- package/skills/peaks-rd/SKILL.md +0 -15
- package/skills/peaks-solo/references/runbook.md +21 -21
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +9 -9
- package/dist/src/cli/commands/progress-close-kill.d.ts +0 -51
- package/dist/src/cli/commands/progress-close-kill.js +0 -152
- package/dist/src/cli/commands/progress-commands.d.ts +0 -3
- package/dist/src/cli/commands/progress-commands.js +0 -379
- package/dist/src/cli/commands/progress-start-spawn.d.ts +0 -59
- package/dist/src/cli/commands/progress-start-spawn.js +0 -140
- package/dist/src/cli/commands/progress-watch-render.d.ts +0 -80
- package/dist/src/cli/commands/progress-watch-render.js +0 -308
|
@@ -10,36 +10,24 @@ import { getAdapter } from '../ide/ide-registry.js';
|
|
|
10
10
|
// values are computed lazily inside each public function call.
|
|
11
11
|
/** Sentinel substring identifying a Claude-Code gate-enforce hook entry. */
|
|
12
12
|
export const HOOK_ENFORCE_SENTINEL = 'peaks gate enforce';
|
|
13
|
-
/** Sentinel substring identifying a peaks-managed sub-agent-progress hook entry. */
|
|
14
|
-
export const HOOK_PROGRESS_SENTINEL = 'peaks progress start';
|
|
15
13
|
/** Default (claude-code) hook command — kept as a stable export for tests. */
|
|
16
14
|
export const HOOK_ENFORCE_COMMAND = `peaks gate enforce --project "\${CLAUDE_PROJECT_DIR}"`;
|
|
17
|
-
/** Default (claude-code) progress command — kept as a stable export for tests. */
|
|
18
|
-
export const HOOK_PROGRESS_COMMAND = `peaks progress start --project "\${CLAUDE_PROJECT_DIR}" --reason "auto-spawn for sub-agent Task" --quiet`;
|
|
19
15
|
function resolveHookSpec(ide) {
|
|
20
16
|
const adapter = getAdapter(ide);
|
|
21
17
|
if (ide === 'claude-code') {
|
|
22
18
|
return {
|
|
23
19
|
hookEnforceCommand: `peaks gate enforce --project "\${${adapter.envVar}}"`,
|
|
24
|
-
hookProgressCommand: `peaks progress start --project "\${${adapter.envVar}}" --reason "auto-spawn for sub-agent ${adapter.subAgentToolMatcher}" --quiet`,
|
|
25
20
|
hookEnforceSentinel: HOOK_ENFORCE_SENTINEL,
|
|
26
|
-
hookProgressSentinel: HOOK_PROGRESS_SENTINEL,
|
|
27
21
|
hookEnforceMatcher: adapter.toolMatcher, // 'Bash'
|
|
28
|
-
|
|
29
|
-
hookEnforceEvent: adapter.hookEvent, // 'PreToolUse'
|
|
30
|
-
hookProgressEvent: adapter.hookEvent // 'PreToolUse' for Claude
|
|
22
|
+
hookEnforceEvent: adapter.hookEvent // 'PreToolUse'
|
|
31
23
|
};
|
|
32
24
|
}
|
|
33
25
|
if (ide === 'trae') {
|
|
34
26
|
return {
|
|
35
27
|
hookEnforceCommand: `peaks hook handle --project "\${${adapter.envVar}}"`,
|
|
36
|
-
hookProgressCommand: `peaks progress start --project "\${${adapter.envVar}}" --reason "auto-spawn for sub-agent ${adapter.subAgentToolMatcher}" --quiet`,
|
|
37
28
|
hookEnforceSentinel: 'peaks hook handle',
|
|
38
|
-
hookProgressSentinel: HOOK_PROGRESS_SENTINEL,
|
|
39
29
|
hookEnforceMatcher: adapter.toolMatcher, // 'terminal'
|
|
40
|
-
|
|
41
|
-
hookEnforceEvent: adapter.hookEvent, // 'beforeToolCall'
|
|
42
|
-
hookProgressEvent: adapter.hookEvent // 'beforeToolCall' (no separate progress event yet for Trae)
|
|
30
|
+
hookEnforceEvent: adapter.hookEvent // 'beforeToolCall'
|
|
43
31
|
};
|
|
44
32
|
}
|
|
45
33
|
// Future adapters (codex, cursor, qoder, tongyi-lingma) — not yet registered.
|
|
@@ -51,6 +39,10 @@ function resolveHookSpec(ide) {
|
|
|
51
39
|
function resolveIde(options) {
|
|
52
40
|
return options?.ide ?? 'claude-code';
|
|
53
41
|
}
|
|
42
|
+
/** Slice #013: read the skipProgress opt-in flag. Slice #014: the underlying install no longer emits the progress-start entry, so the flag is effectively a no-op (kept for API stability). */
|
|
43
|
+
function resolveSkipProgress(options) {
|
|
44
|
+
return options?.skipProgress === true;
|
|
45
|
+
}
|
|
54
46
|
/** Resolve settings root dir for a scope. */
|
|
55
47
|
function resolveSettingsRoot(scope, projectRoot) {
|
|
56
48
|
if (scope === 'global')
|
|
@@ -101,24 +93,104 @@ function entryIsPeaksManaged(entry, sentinels) {
|
|
|
101
93
|
return sentinels.some((sentinel) => cmd.includes(sentinel));
|
|
102
94
|
});
|
|
103
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Slice #014: read the *actually-installed* peaks-managed hook entries
|
|
98
|
+
* from a settings object. Replaces the pre-#014 `listInstalledEntriesForIde`
|
|
99
|
+
* helper in `hooks-commands.ts`, which returned the IDE-EXPECTED list
|
|
100
|
+
* (a hardcoded 2-entry array per adapter) rather than what was on disk.
|
|
101
|
+
* That bug surfaced when slice #013's local cleanup installed
|
|
102
|
+
* `peaks hooks install --no-progress` (gate-enforce only), but the
|
|
103
|
+
* status command still reported `entries: [Bash, Task]` because the
|
|
104
|
+
* helper didn't read the file.
|
|
105
|
+
*
|
|
106
|
+
* The new helper:
|
|
107
|
+
* 1. reads each `hooks.<event>` array,
|
|
108
|
+
* 2. filters to entries that are peaks-managed for the given IDE
|
|
109
|
+
* (matches the legacy sentinel set: gate-enforce + the no-longer-
|
|
110
|
+
* installed progress-start),
|
|
111
|
+
* 3. returns one `{ matcher, sentinel }` row per entry, taking the
|
|
112
|
+
* FIRST matching sentinel per entry (entries have a single command
|
|
113
|
+
* handler in practice, but the loop tolerates multi-handler
|
|
114
|
+
* entries by taking the first match).
|
|
115
|
+
*
|
|
116
|
+
* Pre-#014 settings.json files that have a stale progress-start entry
|
|
117
|
+
* will see it surface in the result. This is intentional: the status
|
|
118
|
+
* command is the user's tool for "what is on disk right now", and
|
|
119
|
+
* surfacing a stale entry is the only way the user can know to run
|
|
120
|
+
* `peaks hooks install` (which now strips it) or `peaks hooks
|
|
121
|
+
* uninstall` (which removes it).
|
|
122
|
+
*/
|
|
123
|
+
export function readInstalledEntriesFromSettings(settings, ide) {
|
|
124
|
+
const sentinels = resolveLegacySentinels(ide);
|
|
125
|
+
// Walk every event key the settings file has, not just the
|
|
126
|
+
// adapter-declared one. A pre-#014 install could have left a
|
|
127
|
+
// progress-start entry on a different event than the gate-enforce
|
|
128
|
+
// entry (Trae: both on beforeToolCall, but a stale install on a
|
|
129
|
+
// future IDE could split them).
|
|
130
|
+
const hooksRoot = settings.hooks;
|
|
131
|
+
if (!hooksRoot || typeof hooksRoot !== 'object' || Array.isArray(hooksRoot))
|
|
132
|
+
return [];
|
|
133
|
+
const result = [];
|
|
134
|
+
for (const eventKey of Object.keys(hooksRoot)) {
|
|
135
|
+
const entries = readHookEntriesFromHooks(hooksRoot, eventKey);
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (!entryIsPeaksManaged(entry, sentinels))
|
|
138
|
+
continue;
|
|
139
|
+
const matcher = typeof entry.matcher === 'string' ? entry.matcher : '';
|
|
140
|
+
// Find the first matching sentinel inside the entry's command
|
|
141
|
+
// handlers. For each handler, find the first sentinel substring
|
|
142
|
+
// it contains. We pick the first handler's first matching
|
|
143
|
+
// sentinel (entries have a single command in practice).
|
|
144
|
+
const firstHandler = Array.isArray(entry.hooks) ? entry.hooks[0] : undefined;
|
|
145
|
+
const cmd = typeof firstHandler?.command === 'string' ? firstHandler.command : '';
|
|
146
|
+
const sentinel = sentinels.find((s) => cmd.includes(s));
|
|
147
|
+
if (matcher === '' || sentinel === undefined)
|
|
148
|
+
continue;
|
|
149
|
+
result.push({ matcher, sentinel });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
104
154
|
/**
|
|
105
155
|
* Compute the per-IDE peaks hook entries to merge into the settings file.
|
|
106
156
|
* Replaces the slice #1 hardcoded `PEAKS_HOOK_ENTRIES` constant; the constant
|
|
107
157
|
* remains exported (computed for claude-code) for backward compat.
|
|
158
|
+
*
|
|
159
|
+
* Slice #013 (`--no-progress` flag): when `skipProgress` is true, the
|
|
160
|
+
* progress-start entry is omitted from the returned list. Install with this
|
|
161
|
+
* flag will (a) NOT emit the progress hook entry, and (b) will idempotently
|
|
162
|
+
* remove any previously-installed progress entry (sentinel-based merge).
|
|
163
|
+
*
|
|
164
|
+
* Slice #014 (refactor — full removal): only the gate-enforce entry is
|
|
165
|
+
* ever emitted. The `skipProgress` parameter is kept for API stability
|
|
166
|
+
* but is a no-op — the returned list is always single-entry. The progress
|
|
167
|
+
* entry's sentinel is included in the legacy sentinel set so uninstall
|
|
168
|
+
* can find + remove any stale progress-start entry that an older
|
|
169
|
+
* `peaks hooks install` may have written before this slice.
|
|
108
170
|
*/
|
|
109
|
-
function resolveHookEntries(ide) {
|
|
171
|
+
function resolveHookEntries(ide, _skipProgress = false) {
|
|
110
172
|
const spec = resolveHookSpec(ide);
|
|
111
173
|
return [
|
|
112
|
-
{ sentinel: spec.hookEnforceSentinel, matcher: spec.hookEnforceMatcher, command: spec.hookEnforceCommand, event: spec.hookEnforceEvent }
|
|
113
|
-
{ sentinel: spec.hookProgressSentinel, matcher: spec.hookProgressMatcher, command: spec.hookProgressCommand, event: spec.hookProgressEvent }
|
|
174
|
+
{ sentinel: spec.hookEnforceSentinel, matcher: spec.hookEnforceMatcher, command: spec.hookEnforceCommand, event: spec.hookEnforceEvent }
|
|
114
175
|
];
|
|
115
176
|
}
|
|
116
|
-
/**
|
|
177
|
+
/**
|
|
178
|
+
* Legacy sentinel set used by uninstall + status to find and remove stale
|
|
179
|
+
* progress-start entries written by pre-#014 installs. The progress-start
|
|
180
|
+
* sentinel is the literal substring that older installs emitted.
|
|
181
|
+
*/
|
|
182
|
+
const LEGACY_PROGRESS_START_SENTINEL = 'peaks progress start';
|
|
183
|
+
function resolveLegacySentinels(ide) {
|
|
184
|
+
if (ide === 'trae') {
|
|
185
|
+
return ['peaks hook handle', LEGACY_PROGRESS_START_SENTINEL];
|
|
186
|
+
}
|
|
187
|
+
return [HOOK_ENFORCE_SENTINEL, LEGACY_PROGRESS_START_SENTINEL];
|
|
188
|
+
}
|
|
189
|
+
/** Default (claude-code) peaks-managed hook entries — kept as a stable export for tests. Slice #014: only the gate-enforce entry. */
|
|
117
190
|
export const PEAKS_HOOK_ENTRIES = (() => {
|
|
118
191
|
const spec = resolveHookSpec('claude-code');
|
|
119
192
|
return [
|
|
120
|
-
{ sentinel: spec.hookEnforceSentinel, matcher: spec.hookEnforceMatcher, command: spec.hookEnforceCommand, event: spec.hookEnforceEvent }
|
|
121
|
-
{ sentinel: spec.hookProgressSentinel, matcher: spec.hookProgressMatcher, command: spec.hookProgressCommand, event: spec.hookProgressEvent }
|
|
193
|
+
{ sentinel: spec.hookEnforceSentinel, matcher: spec.hookEnforceMatcher, command: spec.hookEnforceCommand, event: spec.hookEnforceEvent }
|
|
122
194
|
];
|
|
123
195
|
})();
|
|
124
196
|
function isInstalledForIde(settings, ide) {
|
|
@@ -133,8 +205,43 @@ function isInstalledForIde(settings, ide) {
|
|
|
133
205
|
}
|
|
134
206
|
return false;
|
|
135
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Slice #014: detect the "stale progress entry after pre-#014 install"
|
|
210
|
+
* case. The desired shape is gate-enforce-only. If the file currently
|
|
211
|
+
* has a peaks-managed progress-start entry (left behind by a pre-#014
|
|
212
|
+
* install), the install is NOT a no-op — it must strip the stale
|
|
213
|
+
* entry. This helper returns true exactly when the desired shape is
|
|
214
|
+
* fully reflected on disk: gate-enforce present AND no legacy
|
|
215
|
+
* progress-start present.
|
|
216
|
+
*/
|
|
217
|
+
function shapeMatchesDesired(settings, ide) {
|
|
218
|
+
const desiredEntries = resolveHookEntries(ide);
|
|
219
|
+
const desiredSentinels = new Set(desiredEntries.map((e) => e.sentinel));
|
|
220
|
+
const allPeaksSentinels = resolveLegacySentinels(ide);
|
|
221
|
+
const eventKeys = new Set(resolveHookEntries(ide).map((e) => e.event));
|
|
222
|
+
for (const eventKey of eventKeys) {
|
|
223
|
+
const present = readHookEventEntries(settings, eventKey);
|
|
224
|
+
const peaksPresent = present.filter((e) => entryIsPeaksManaged(e, allPeaksSentinels));
|
|
225
|
+
// (a) every peaks-managed entry currently on disk must match the
|
|
226
|
+
// desired sentinel set (no stale entries the caller wants removed).
|
|
227
|
+
for (const entry of peaksPresent) {
|
|
228
|
+
const entrySentinels = (entry.hooks ?? []).map((h) => allPeaksSentinels.find((s) => String(h.command ?? '').includes(s))).filter((s) => Boolean(s));
|
|
229
|
+
if (entrySentinels.some((s) => !desiredSentinels.has(s))) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// (b) every desired entry must be on disk.
|
|
234
|
+
for (const sentinel of desiredSentinels) {
|
|
235
|
+
const has = peaksPresent.some((entry) => (entry.hooks ?? []).some((h) => String(h.command ?? '').includes(sentinel)));
|
|
236
|
+
if (!has)
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
136
242
|
export function planHookInstall(scope, projectRoot, options) {
|
|
137
243
|
const ide = resolveIde(options);
|
|
244
|
+
const _skipProgress = resolveSkipProgress(options);
|
|
138
245
|
const root = resolveSettingsRoot(scope, projectRoot);
|
|
139
246
|
const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
|
|
140
247
|
assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
|
|
@@ -152,24 +259,29 @@ export function planHookInstall(scope, projectRoot, options) {
|
|
|
152
259
|
};
|
|
153
260
|
}
|
|
154
261
|
/** Merge all peaks-managed hook entries into settings, preserving all other keys and hooks. */
|
|
155
|
-
function withHooksInstalledForIde(settings, ide) {
|
|
262
|
+
function withHooksInstalledForIde(settings, ide, _skipProgress = false) {
|
|
156
263
|
const existingHooks = (settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks))
|
|
157
264
|
? settings.hooks
|
|
158
265
|
: {};
|
|
159
|
-
// Per-IDE entries may map to different events (Trae:
|
|
160
|
-
// Claude:
|
|
161
|
-
// independently merged.
|
|
266
|
+
// Per-IDE entries may map to different events (Trae: gate-enforce on
|
|
267
|
+
// beforeToolCall; Claude: gate-enforce on PreToolUse). Group by event
|
|
268
|
+
// so each event array is independently merged.
|
|
162
269
|
const ourByEvent = new Map();
|
|
163
270
|
for (const spec of resolveHookEntries(ide)) {
|
|
164
271
|
const list = ourByEvent.get(spec.event) ?? [];
|
|
165
272
|
list.push(spec);
|
|
166
273
|
ourByEvent.set(spec.event, list);
|
|
167
274
|
}
|
|
168
|
-
|
|
275
|
+
// Slice #014: the legacy sentinel set includes the progress-start
|
|
276
|
+
// sentinel so a pre-#014 install's stale progress-start entry is
|
|
277
|
+
// stripped by the filter (the file converges on the new
|
|
278
|
+
// gate-enforce-only shape, idempotently). The desired set (passed
|
|
279
|
+
// in below) only contains the gate-enforce sentinel.
|
|
280
|
+
const allSentinels = resolveLegacySentinels(ide);
|
|
169
281
|
const nextHooks = { ...existingHooks };
|
|
170
282
|
for (const [eventKey, ourEntries] of ourByEvent) {
|
|
171
283
|
const existing = readHookEntriesFromHooks(nextHooks, eventKey);
|
|
172
|
-
const nonPeaks = existing.filter((entry) => !entryIsPeaksManaged(entry,
|
|
284
|
+
const nonPeaks = existing.filter((entry) => !entryIsPeaksManaged(entry, allSentinels));
|
|
173
285
|
const ourFormatted = ourEntries.map((spec) => ({
|
|
174
286
|
matcher: spec.matcher,
|
|
175
287
|
hooks: [{ type: 'command', command: spec.command }]
|
|
@@ -189,17 +301,25 @@ function withHooksInstalledForIde(settings, ide) {
|
|
|
189
301
|
}
|
|
190
302
|
export function applyHookInstall(scope, projectRoot, options) {
|
|
191
303
|
const ide = resolveIde(options);
|
|
304
|
+
const _skipProgress = resolveSkipProgress(options);
|
|
192
305
|
const root = resolveSettingsRoot(scope, projectRoot);
|
|
193
306
|
const settingsPath = resolveSettingsPath(scope, ide, projectRoot);
|
|
194
307
|
assertSafeSettingsPathCompat(scope, ide, root, settingsPath);
|
|
195
308
|
const exists = existsSync(settingsPath);
|
|
196
309
|
const settings = exists ? readJsonObjectFile(settingsPath) : {};
|
|
197
310
|
const spec = resolveHookSpec(ide);
|
|
311
|
+
// Slice #014: `alreadyInstalled` reflects the FULL desired shape
|
|
312
|
+
// (gate-enforce-only + no stale progress-start entry). Pre-#014
|
|
313
|
+
// installs that left a progress-start entry behind will be treated
|
|
314
|
+
// as not-yet-installed, so the merge strips the stale entry on the
|
|
315
|
+
// next install call. This is the only path that converges the file
|
|
316
|
+
// on the new shape; pure presence-checks are insufficient.
|
|
317
|
+
const alreadyInstalled = shapeMatchesDesired(settings, ide);
|
|
198
318
|
const baseResult = {
|
|
199
319
|
scope,
|
|
200
320
|
settingsPath,
|
|
201
321
|
exists,
|
|
202
|
-
alreadyInstalled
|
|
322
|
+
alreadyInstalled,
|
|
203
323
|
desiredCommand: spec.hookEnforceCommand,
|
|
204
324
|
sentinel: spec.hookEnforceSentinel,
|
|
205
325
|
matcher: spec.hookEnforceMatcher
|
|
@@ -220,7 +340,12 @@ export function removeHookInstall(scope, projectRoot, options) {
|
|
|
220
340
|
}
|
|
221
341
|
const settings = readJsonObjectFile(settingsPath);
|
|
222
342
|
const existingHooks = settings.hooks ?? {};
|
|
223
|
-
|
|
343
|
+
// Slice #014: uninstall must remove the gate-enforce entry AND any
|
|
344
|
+
// legacy progress-start entry that a pre-#014 install left behind.
|
|
345
|
+
// The legacy sentinel set covers both shapes so the uninstall
|
|
346
|
+
// converges the file on "no peaks-managed entries", regardless of
|
|
347
|
+
// what shape the file was in when the user ran uninstall.
|
|
348
|
+
const sentinels = resolveLegacySentinels(ide);
|
|
224
349
|
const eventKeys = new Set(resolveHookEntries(ide).map((e) => e.event));
|
|
225
350
|
let removedAny = false;
|
|
226
351
|
const nextHooks = { ...existingHooks };
|
|
@@ -11,14 +11,6 @@
|
|
|
11
11
|
* @returns Next available number (1, 2, 3, ...)
|
|
12
12
|
*/
|
|
13
13
|
export declare function getNextNumber(dirPath: string): number;
|
|
14
|
-
/**
|
|
15
|
-
* Build a numbered filename from a number and description.
|
|
16
|
-
* Format: 001-description-slug.md
|
|
17
|
-
*
|
|
18
|
-
* @param number - The file number (will be zero-padded to 3 digits)
|
|
19
|
-
* @param description - Human-readable description (converted to kebab-case slug)
|
|
20
|
-
* @returns Formatted filename like "001-feature-name.md"
|
|
21
|
-
*/
|
|
22
14
|
export declare function buildNumberedFilename(number: number, description: string): string;
|
|
23
15
|
/**
|
|
24
16
|
* Get the next numbered file path in a directory.
|
|
@@ -34,13 +34,23 @@ export function getNextNumber(dirPath) {
|
|
|
34
34
|
* @param description - Human-readable description (converted to kebab-case slug)
|
|
35
35
|
* @returns Formatted filename like "001-feature-name.md"
|
|
36
36
|
*/
|
|
37
|
+
// Windows supports up to 255 chars per filename component (and 260 for the
|
|
38
|
+
// full path). Pre-#015 the slug was silently truncated to 50 chars, which
|
|
39
|
+
// produced orphaned artefacts (the on-disk file no longer matched the
|
|
40
|
+
// request-id and the state machine could not find it). 255 is the OS-level
|
|
41
|
+
// ceiling; if a requestId exceeds that, `mkdir` / `writeFile` will surface a
|
|
42
|
+
// real ENAMETOOLONG error instead of a silent mismatch. We reserve 4 chars
|
|
43
|
+
// for the `<NNN>-` numeric prefix and 3 chars for the `.md` suffix, so
|
|
44
|
+
// the slug may be at most 248 chars (giving 4 + 248 + 3 = 255 total).
|
|
45
|
+
const MAX_FILENAME_LENGTH = 255;
|
|
46
|
+
const MAX_FILENAME_SLUG_LENGTH = MAX_FILENAME_LENGTH - 7;
|
|
37
47
|
export function buildNumberedFilename(number, description) {
|
|
38
48
|
const padded = String(number).padStart(3, '0');
|
|
39
49
|
const slug = description
|
|
40
50
|
.toLowerCase()
|
|
41
51
|
.replace(/[^a-z0-9]+/g, '-')
|
|
42
52
|
.replace(/^-|-$/g, '')
|
|
43
|
-
.slice(0,
|
|
53
|
+
.slice(0, MAX_FILENAME_SLUG_LENGTH);
|
|
44
54
|
return `${padded}-${slug}.md`;
|
|
45
55
|
}
|
|
46
56
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.3.
|
|
1
|
+
export declare const CLI_VERSION = "1.3.5";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.3.
|
|
1
|
+
export const CLI_VERSION = "1.3.5";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Cross-AI-IDE workflow-gating CLI + skill family (Claude Code shipped, Trae in progress; Codex / Cursor / Qoder / Tongyi Lingma on the roadmap).",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@ issues 3 dispatch calls in a **single message**:
|
|
|
40
40
|
peaks sub-agent dispatch qa-business \
|
|
41
41
|
--prompt "<qa-business contract below, plus runtime args: project=<repo>,
|
|
42
42
|
session-id=<sid>, request-id=<rid>.
|
|
43
|
-
Write your evidence at .peaks/<sid>/qa/test-reports/<rid>.md
|
|
43
|
+
Write your evidence at .peaks/_runtime/<sid>/qa/test-reports/<rid>.md
|
|
44
44
|
and return ONLY the path. While running, call
|
|
45
45
|
peaks sub-agent heartbeat --record <dispatchRecordPath>
|
|
46
46
|
--status running --progress <pct> --note '<text>' at least every 30s;
|
|
@@ -48,11 +48,11 @@ peaks sub-agent dispatch qa-business \
|
|
|
48
48
|
--request-id <rid> --session-id <sid> --project <repo> --json
|
|
49
49
|
|
|
50
50
|
peaks sub-agent dispatch qa-perf \
|
|
51
|
-
--prompt "<qa-perf contract below, plus runtime args; output .peaks/<sid>/qa/performance-findings.md>" \
|
|
51
|
+
--prompt "<qa-perf contract below, plus runtime args; output .peaks/_runtime/<sid>/qa/performance-findings.md>" \
|
|
52
52
|
--request-id <rid> --session-id <sid> --project <repo> --json
|
|
53
53
|
|
|
54
54
|
peaks sub-agent dispatch qa-security \
|
|
55
|
-
--prompt "<qa-security contract below, plus runtime args; output .peaks/<sid>/qa/security-findings.md>" \
|
|
55
|
+
--prompt "<qa-security contract below, plus runtime args; output .peaks/_runtime/<sid>/qa/security-findings.md>" \
|
|
56
56
|
--request-id <rid> --session-id <sid> --project <repo> --json
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -80,9 +80,9 @@ string. The recommended names above are hints, not a hard list.
|
|
|
80
80
|
|
|
81
81
|
| Sub-agent | Reads | Writes | Must not depend on |
|
|
82
82
|
|---|---|---|---|
|
|
83
|
-
| `qa-business` (or subdivisions) | PRD body, RD planning, codegraph, project scan, existing system |
|
|
84
|
-
| `qa-perf` | RD planning, codegraph, perf baselines from prior slices |
|
|
85
|
-
| `qa-security` | PRD body (trust model), codegraph, RD planning, existing security notes |
|
|
83
|
+
| `qa-business` (or subdivisions) | PRD body, RD planning, codegraph, project scan, existing system | `.peaks/_runtime/<sid>/qa/test-reports/<rid>.md` | perf / security output (run in parallel) |
|
|
84
|
+
| `qa-perf` | RD planning, codegraph, perf baselines from prior slices | `.peaks/_runtime/<sid>/qa/performance-findings.md` | business / security output |
|
|
85
|
+
| `qa-security` | PRD body (trust model), codegraph, RD planning, existing security notes | `.peaks/_runtime/<sid>/qa/security-findings.md` | business / perf output |
|
|
86
86
|
|
|
87
87
|
The reducer merges all 3 outputs into the final QA verdict. None of
|
|
88
88
|
the 3 sub-agents reads another sub-agent's output (no peer-to-peer
|
package/skills/peaks-rd/SKILL.md
CHANGED
|
@@ -119,21 +119,6 @@ On the first presence:set in a project, ensure the out-of-band status bar is ins
|
|
|
119
119
|
peaks statusline install --project <repo> # idempotent; skips if already installed
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
**Auto-spawn a progress watch terminal once per slice (BLOCKING on the first phase transition).** The user opens a fresh VSCode window per slice, not per Bash call. Without a separate progress terminal the user has no live signal that the sub-agent is alive — the only signal they have is the static statusline. So at the first phase transition of every slice, fire `peaks progress start` ONCE. The CLI auto-spawns a new terminal tab running `peaks progress watch` and the user can close the new tab at any time. Do NOT re-invoke on every phase change — one per slice is the contract. The LLM-side cost of this one invocation is one Bash call plus a small JSON envelope; the watch side is a 1s file poll that does not consume LLM tokens.
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
# At the first phase transition of a slice (after the first
|
|
126
|
-
# peaks progress step), fire the watch:
|
|
127
|
-
peaks progress start --project <repo> --reason "rd-implementing for <rid>"
|
|
128
|
-
|
|
129
|
-
# On every subsequent phase transition, only update the
|
|
130
|
-
# progress file — the watch is already running in another tab:
|
|
131
|
-
peaks progress step --project <repo> --request-id <rid> --role rd \
|
|
132
|
-
--step "running pnpm test" --phase running
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
If `peaks progress start` is unsupported on the current platform (no terminal emulator, headless container, etc.) it returns a recoverable error envelope. Surface that in the RD handoff so the user knows the auto-spawn failed; the sub-agent can still emit `peaks progress step` writes that the user reads from the on-disk file. The auto-spawn is convenience, not a gate.
|
|
136
|
-
|
|
137
122
|
Read persistent project memory via CLI (durable, LLM-authored memories):
|
|
138
123
|
|
|
139
124
|
```bash
|
|
@@ -20,12 +20,12 @@ peaks skill runbook peaks-solo --json
|
|
|
20
20
|
peaks workspace init --project <repo> --json
|
|
21
21
|
peaks workspace reconcile --project <repo> --json
|
|
22
22
|
peaks scan archetype --project <repo> --json
|
|
23
|
-
# → copy archetype, frontendOnly, signals into .peaks/<session-id>/rd/project-scan.md (Peaks-Cli Gate A)
|
|
24
|
-
# → copy libraries[] into .peaks/<session-id>/rd/project-scan.md under `## Library versions`
|
|
23
|
+
# → copy archetype, frontendOnly, signals into .peaks/_runtime/<session-id>/rd/project-scan.md (Peaks-Cli Gate A)
|
|
24
|
+
# → copy libraries[] into .peaks/_runtime/<session-id>/rd/project-scan.md under `## Library versions`
|
|
25
25
|
peaks scan libraries --project <repo> --json
|
|
26
26
|
# → if archetype != greenfield AND archetype != unknown:
|
|
27
27
|
peaks scan existing-system --project <repo> --json
|
|
28
|
-
# → copy tokens, sources, conventions, inconsistencies into .peaks/<session-id>/system/existing-system.md (Peaks-Cli Gate A.5)
|
|
28
|
+
# → copy tokens, sources, conventions, inconsistencies into .peaks/_runtime/<session-id>/system/existing-system.md (Peaks-Cli Gate A.5)
|
|
29
29
|
|
|
30
30
|
# 1. Peaks-Cli Standards preflight + apply
|
|
31
31
|
# Run dry-run first to inspect deltas, then APPLY. In full-auto and swarm modes,
|
|
@@ -55,7 +55,7 @@ peaks request transition <rid> --role prd --state handed-off --project <repo> --
|
|
|
55
55
|
|
|
56
56
|
# 3. Peaks-Cli Swarm parallel — sub-agent fan-out (peaks sub-agent dispatch, NOT Skill tool)
|
|
57
57
|
# Solo computes the swarm plan from --type + frontendOnly + frontend-keyword scan,
|
|
58
|
-
# writes it to .peaks/<sid>/sc/swarm-plan.json, then launches one
|
|
58
|
+
# writes it to .peaks/_runtime/<sid>/sc/swarm-plan.json, then launches one
|
|
59
59
|
# `peaks sub-agent dispatch <role>` call per sub-agent in the same message.
|
|
60
60
|
# See "Peaks-Cli Swarm parallel phase" above for the full decision table and the
|
|
61
61
|
# prompt template; the role's required artefact paths are listed there.
|
|
@@ -80,36 +80,36 @@ peaks skill presence:set peaks-solo --project <repo> --mode <mode> --gate swarm-
|
|
|
80
80
|
# Step 0 / presence, plus the runtime args (rid / sid / mode / type / paths).
|
|
81
81
|
# 3c. After fan-out, Solo restores presence once and runs Gate B (ls checks):
|
|
82
82
|
peaks skill presence:set peaks-solo --project <repo> --mode <mode> --gate swarm-converged
|
|
83
|
-
ls .peaks/<sid>/prd/requests/<rid>.md # PRD artefact must exist (Gate B hard)
|
|
84
|
-
# feature / refactor → ls .peaks/<sid>/rd/tech-doc.md
|
|
85
|
-
# bugfix → ls .peaks/<sid>/rd/bug-analysis.md
|
|
86
|
-
ls .peaks/<sid>/qa/test-cases/<rid>.md # QA test-cases (skipped for docs|chore)
|
|
83
|
+
ls .peaks/_runtime/<sid>/prd/requests/<rid>.md # PRD artefact must exist (Gate B hard)
|
|
84
|
+
# feature / refactor → ls .peaks/_runtime/<sid>/rd/tech-doc.md
|
|
85
|
+
# bugfix → ls .peaks/_runtime/<sid>/rd/bug-analysis.md
|
|
86
|
+
ls .peaks/_runtime/<sid>/qa/test-cases/<rid>.md # QA test-cases (skipped for docs|chore)
|
|
87
87
|
# ui (only when in plan):
|
|
88
|
-
ls .peaks/<sid>/ui/design-draft.md 2>&1 # non-blocking (Gate B info)
|
|
88
|
+
ls .peaks/_runtime/<sid>/ui/design-draft.md 2>&1 # non-blocking (Gate B info)
|
|
89
89
|
# Apply the degradation rules in the main SKILL.md if any artefact is missing.
|
|
90
90
|
# → Peaks-Cli Gate B convergence check. Assisted/Strict: [CONFIRM]
|
|
91
91
|
|
|
92
92
|
# 4. Peaks-Cli RD planning artifact (the file required by the prerequisite gate)
|
|
93
|
-
# feature / refactor → write .peaks/<id>/rd/tech-doc.md
|
|
94
|
-
# bugfix → write .peaks/<id>/rd/bug-analysis.md
|
|
93
|
+
# feature / refactor → write .peaks/_runtime/<id>/rd/tech-doc.md
|
|
94
|
+
# bugfix → write .peaks/_runtime/<id>/rd/bug-analysis.md
|
|
95
95
|
# config → no planning artifact required at this state
|
|
96
96
|
# docs / chore → no planning artifact required
|
|
97
97
|
peaks request transition <rid> --role rd --state implemented --project <repo> --json
|
|
98
98
|
|
|
99
99
|
# 5. Peaks-Cli Code review + security review BEFORE qa-handoff transition.
|
|
100
100
|
# Produce the evidence files the CLI gate enforces:
|
|
101
|
-
# - .peaks/<id>/rd/code-review.md (CRITICAL/HIGH findings + fixes; required for feature/bugfix/refactor)
|
|
102
|
-
# - .peaks/<id>/rd/security-review.md (required for feature/bugfix/refactor/config)
|
|
101
|
+
# - .peaks/_runtime/<id>/rd/code-review.md (CRITICAL/HIGH findings + fixes; required for feature/bugfix/refactor)
|
|
102
|
+
# - .peaks/_runtime/<id>/rd/security-review.md (required for feature/bugfix/refactor/config)
|
|
103
103
|
# Then transition. If --type is docs/chore the gate is empty and the transition is unguarded.
|
|
104
104
|
peaks request transition <rid> --role rd --state qa-handoff --project <repo> --json
|
|
105
105
|
|
|
106
106
|
# 6. Peaks-Cli QA validation (AUTO-PROCEED from RD in full-auto)
|
|
107
107
|
# Before each QA transition, produce the evidence files the CLI gate enforces:
|
|
108
|
-
# Before qa:running → .peaks/<id>/qa/test-cases/<rid>.md
|
|
108
|
+
# Before qa:running → .peaks/_runtime/<id>/qa/test-cases/<rid>.md
|
|
109
109
|
peaks request transition <rid> --role qa --state running --project <repo> --json
|
|
110
|
-
# Before qa:verdict-issued → .peaks/<id>/qa/test-reports/<rid>.md
|
|
111
|
-
# + .peaks/<id>/qa/security-findings.md
|
|
112
|
-
# + .peaks/<id>/qa/performance-findings.md (feature/refactor only)
|
|
110
|
+
# Before qa:verdict-issued → .peaks/_runtime/<id>/qa/test-reports/<rid>.md
|
|
111
|
+
# + .peaks/_runtime/<id>/qa/security-findings.md
|
|
112
|
+
# + .peaks/_runtime/<id>/qa/performance-findings.md (feature/refactor only)
|
|
113
113
|
peaks request transition <rid> --role qa --state verdict-issued --project <repo> --json
|
|
114
114
|
# → Peaks-Cli Gate D check. Assisted/Strict: [CONFIRM]
|
|
115
115
|
|
|
@@ -135,12 +135,12 @@ peaks openspec archive <cid> --project <repo> --apply --json
|
|
|
135
135
|
peaks workspace reconcile --project <repo> --apply --older-than 7
|
|
136
136
|
|
|
137
137
|
# 10. Peaks-Cli TXT handoff — invoke peaks-txt which embeds memory markers and extracts
|
|
138
|
-
# peaks-txt writes the handoff capsule to .peaks/<id>/txt/handoff.md. Inside the
|
|
138
|
+
# peaks-txt writes the handoff capsule to .peaks/_runtime/<id>/txt/handoff.md. Inside the
|
|
139
139
|
# capsule body, peaks-txt embeds <!-- peaks-memory:start --> blocks for every
|
|
140
140
|
# stable project fact surfaced this session.
|
|
141
141
|
#
|
|
142
142
|
# 10a. Skill-side scan (do this BEFORE the AskUserQuestion below):
|
|
143
|
-
# grep -n "peaks-memory:start" .peaks/<id>/txt/handoff.md
|
|
143
|
+
# grep -n "peaks-memory:start" .peaks/_runtime/<id>/txt/handoff.md
|
|
144
144
|
# Record the count. This is the skill doing the work, not a CLI command —
|
|
145
145
|
# we deliberately do not ship a `peaks memory scan` because the LLM is
|
|
146
146
|
# the only consumer and the LLM has grep.
|
|
@@ -148,7 +148,7 @@ peaks workspace reconcile --project <repo> --apply --older-than 7
|
|
|
148
148
|
# 10b. AskUserQuestion (only if 10a returned count >= 1):
|
|
149
149
|
# "The TXT handoff has N peaks-memory:start blocks. Persist to .peaks/memory/?
|
|
150
150
|
# (a) Apply all — `peaks memory extract --project <repo>
|
|
151
|
-
# --artifact .peaks/<id>/txt/handoff.md --apply --json`
|
|
151
|
+
# --artifact .peaks/_runtime/<id>/txt/handoff.md --apply --json`
|
|
152
152
|
# (b) Apply selectively — re-edit handoff.md first, then re-apply
|
|
153
153
|
# (c) Skip for now — blocks stay in the handoff only, no .peaks/memory/ write"
|
|
154
154
|
# If 10a returned 0 AND the session surfaced a stable project fact
|
|
@@ -156,7 +156,7 @@ peaks workspace reconcile --project <repo> --apply --older-than 7
|
|
|
156
156
|
# back and embed at least one block before Solo can advance.
|
|
157
157
|
|
|
158
158
|
# 10c. After the user picks (a) or (b), run:
|
|
159
|
-
peaks memory extract --project <repo> --artifact .peaks/<id>/txt/handoff.md --apply --json
|
|
159
|
+
peaks memory extract --project <repo> --artifact .peaks/_runtime/<id>/txt/handoff.md --apply --json
|
|
160
160
|
# --apply is REQUIRED to write .peaks/memory/; without it the command only
|
|
161
161
|
# previews. The extract regenerates index.json in the same call.
|
|
162
162
|
|
|
@@ -16,7 +16,7 @@ Return a compact JSON envelope — do not write prose.
|
|
|
16
16
|
## Hard prohibitions
|
|
17
17
|
- Do NOT call Skill(skill="..."). You are the role.
|
|
18
18
|
- Do NOT call `peaks skill presence:set` — the main loop owns .peaks/.active-skill.json.
|
|
19
|
-
If you need to record state, write to .peaks/<session-id>/system/sub-agent-<role>.json.
|
|
19
|
+
If you need to record state, write to .peaks/_runtime/<session-id>/system/sub-agent-<role>.json.
|
|
20
20
|
- Do NOT commit, push, install hooks, or apply settings.json mutations.
|
|
21
21
|
- Do NOT ask the user interactive questions. If you need clarification, return
|
|
22
22
|
{"status":"blocked","blockedReason":"<text>"} and let the main loop handle it.
|
|
@@ -47,14 +47,14 @@ Steps:
|
|
|
47
47
|
3. Read <project-scan-path> for component library / CSS framework.
|
|
48
48
|
4. Run the prototype fidelity gate: Figma file? PRD visuals? Headed browser?
|
|
49
49
|
5. Write TWO artefacts:
|
|
50
|
-
- .peaks/<sid>/ui/design-draft.md
|
|
51
|
-
- .peaks/<sid>/ui/requests/<rid>.md
|
|
50
|
+
- .peaks/_runtime/<sid>/ui/design-draft.md
|
|
51
|
+
- .peaks/_runtime/<sid>/ui/requests/<rid>.md
|
|
52
52
|
6. Return:
|
|
53
53
|
{
|
|
54
54
|
"role": "ui",
|
|
55
55
|
"rid": "<rid>",
|
|
56
56
|
"status": "ok" | "blocked" | "skipped",
|
|
57
|
-
"artefacts": [".peaks/<sid>/ui/design-draft.md", ".peaks/<sid>/ui/requests/<rid>.md"],
|
|
57
|
+
"artefacts": [".peaks/_runtime/<sid>/ui/design-draft.md", ".peaks/_runtime/<sid>/ui/requests/<rid>.md"],
|
|
58
58
|
"warnings": [],
|
|
59
59
|
"blockedReason": null
|
|
60
60
|
}
|
|
@@ -80,15 +80,15 @@ Steps:
|
|
|
80
80
|
into rd/project-scan.md.
|
|
81
81
|
5. Read <existing-system-path> if archetype is legacy-*.
|
|
82
82
|
6. Write the type-appropriate planning artefact:
|
|
83
|
-
- feature | refactor → .peaks/<sid>/rd/tech-doc.md
|
|
84
|
-
- bugfix → .peaks/<sid>/rd/bug-analysis.md
|
|
83
|
+
- feature | refactor → .peaks/_runtime/<sid>/rd/tech-doc.md
|
|
84
|
+
- bugfix → .peaks/_runtime/<sid>/rd/bug-analysis.md
|
|
85
85
|
- config | docs | chore → no planning artefact required. Return skipped.
|
|
86
86
|
7. Return:
|
|
87
87
|
{
|
|
88
88
|
"role": "rd-planning",
|
|
89
89
|
"rid": "<rid>",
|
|
90
90
|
"status": "ok" | "blocked" | "skipped",
|
|
91
|
-
"artefacts": [".peaks/<sid>/rd/tech-doc.md"], // or [] when skipped
|
|
91
|
+
"artefacts": [".peaks/_runtime/<sid>/rd/tech-doc.md"], // or [] when skipped
|
|
92
92
|
"warnings": [],
|
|
93
93
|
"blockedReason": null
|
|
94
94
|
}
|
|
@@ -106,14 +106,14 @@ Steps:
|
|
|
106
106
|
2. peaks request show <rid> --role prd --project <repo> --json
|
|
107
107
|
3. peaks request show <rid> --role rd --project <repo> --json
|
|
108
108
|
4. Read <project-scan-path>.
|
|
109
|
-
5. Write .peaks/<sid>/qa/test-cases/<rid>.md with test cases linked to PRD
|
|
109
|
+
5. Write .peaks/_runtime/<sid>/qa/test-cases/<rid>.md with test cases linked to PRD
|
|
110
110
|
acceptance items (use **Acceptance:** A1, A2 style).
|
|
111
111
|
6. Return:
|
|
112
112
|
{
|
|
113
113
|
"role": "qa-test-cases",
|
|
114
114
|
"rid": "<rid>",
|
|
115
115
|
"status": "ok" | "blocked" | "skipped",
|
|
116
|
-
"artefacts": [".peaks/<sid>/qa/test-cases/<rid>.md"],
|
|
116
|
+
"artefacts": [".peaks/_runtime/<sid>/qa/test-cases/<rid>.md"],
|
|
117
117
|
"warnings": [],
|
|
118
118
|
"blockedReason": null
|
|
119
119
|
}
|
|
@@ -1,51 +0,0 @@
|
|
|
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>;
|