peaks-loop 4.0.20 → 4.0.21
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/CHANGELOG.md +51 -0
- package/README-en.md +32 -0
- package/README.md +31 -0
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/codegraph-commands.js +156 -13
- package/dist/cli/commands/dispatch-commands.d.ts +23 -0
- package/dist/cli/commands/dispatch-commands.js +127 -3
- package/dist/cli/commands/sub-agent/detached.js +17 -1
- package/dist/cli/commands/sub-agent-shared.d.ts +39 -0
- package/dist/cli/commands/vendor-detect.d.ts +15 -0
- package/dist/cli/commands/vendor-detect.js +22 -5
- package/dist/cli/commands/workspace/init-command.js +34 -0
- package/dist/services/codegraph/codegraph-service.d.ts +159 -0
- package/dist/services/codegraph/codegraph-service.js +217 -3
- package/dist/services/doctor/doctor-service/checks/codegraph-capability.d.ts +7 -0
- package/dist/services/doctor/doctor-service/checks/codegraph-capability.js +139 -13
- package/dist/services/doctor/doctor-service/types.d.ts +25 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.21 — 2026-08-11 (Batch A: vendor-detect recovery + codegraph integration + anti-fake-green gate)
|
|
4
|
+
|
|
5
|
+
**7 atomic commits from session 2026-08-11 (rid-001 redo → 8-子任务 codegraph → Batch A F1-F7)** + 4.0.21 lockstep bump + changelog entry:
|
|
6
|
+
|
|
7
|
+
- rid-001 redo: `fix(runtime): recover vendor-detect CLI seam + handle detached spawn ENOENT` (`e8fb5ed9`, 9 files / +436/-21)
|
|
8
|
+
- codegraph Phase 1: `feat(codegraph): add init conflict guard + doctor fallback + workspace auto-stake` (`e7ec3cb0`, 7 files / +849/-23)
|
|
9
|
+
- codegraph Phase 2: `feat(codegraph): ship affected context envelope + tarball guard + downstream notes` (`f4b56870`, 8 files / +929/-9)
|
|
10
|
+
- **F1**: `fix(runtime): expose DispatchResult.child + per-child ENOENT handler` (`b254840b`, 3 files / +96/-33)
|
|
11
|
+
- **F3**: `fix(runtime): recover vendor-detect Windows ENOENT via where.exe + PATHEXT` (`fcd38369`, 5 files / +58/-18)
|
|
12
|
+
- **F4**: `chore(test): fix pre-existing test rot — graph-node flag, vitest 4.x, child mock, CG-003 path` (`4ff1152a`, 5 files / +20/-6)
|
|
13
|
+
- **F5**: `feat(cli): sub-agent dispatch --must-ls-files <glob> anti-fake-green gate` (`58bb4164`, 3 files / +231/-2)
|
|
14
|
+
- **F6**: `fix(version): peaks-loop-shared CLI_VERSION 4.0.18 -> 4.0.20 lockstep` (`f79d3b34`, 2 files / +41/-1)
|
|
15
|
+
- **F7**: `feat(codegraph): CG-003 PARTIAL verdict 4 follow-up items — preferred path + doctor probe` (`0e3b1bed`, 6 files / +469/-23)
|
|
16
|
+
- `chore(release): bump to 4.0.21 — Batch A lockstep` (replaces the 4.0.22 retry commit; same diff)
|
|
17
|
+
- `docs(changelog): 4.0.21 entry`
|
|
18
|
+
|
|
19
|
+
**Total Batch A**: 24 files / +975/-117 lines / 11+ new test cases / 124/124 unit baseline preserved + 31 net new test cases.
|
|
20
|
+
|
|
21
|
+
**Lockstep contract (per publish.yml Layer 5 + on-disk gate)**:
|
|
22
|
+
- peaks-loop root `package.json#version`: 4.0.21
|
|
23
|
+
- peaks-loop-shared `src/version.ts` `CLI_VERSION`: 4.0.21
|
|
24
|
+
- peaks-loop-internal-runtime `src/index.ts` `RUNTIME_VERSION`: 4.0.21
|
|
25
|
+
- peaks-loop-shared `package.json#version`: 0.0.54 (next available after 0.0.53 on registry — bumps through patch gaps because publish.yml auto-bump ran during 4.0.22 retry)
|
|
26
|
+
- peaks-loop-internal-runtime `package.json#version`: 0.0.5 (next available after 0.0.4)
|
|
27
|
+
|
|
28
|
+
**Note on the 4.0.22 attempt**: an earlier session push of tag `v4.0.21` triggered publish.yml's `Auto-bump version per smallest-semver policy` step (publish.yml:189, still present despite the v2.18.0 plan stating it should be DELETED). The parity gate caught the lockstep drift on the original commit which had only CLI_VERSION bumped (not RUNTIME_VERSION). The retry pushed `v4.0.22` to recover, but the operator wanted `4.0.21` to be the registry version (user 2026-08-11 explicit direction). The 4.0.22 registry package will be manually unpublished by the operator after this 4.0.21 publish completes.
|
|
29
|
+
|
|
30
|
+
**Pending follow-ups (out of 4.0.21)**:
|
|
31
|
+
- F2 detached architecture heavy refactor (in-shell background subprocess; user 2026-08-11 feedback-driven) — separate 4.0.22+ batch
|
|
32
|
+
- F8 skill-resolution slice (session 7f7f78 original intent; rid-002/003/004 scope unknown — needs user scoping first)
|
|
33
|
+
|
|
34
|
+
## 4.0.20 — 2026-08-11 (peaks-loop-internal-runtime published as public lockstep package)
|
|
35
|
+
|
|
36
|
+
**7 commits ship'd from session 2026-08-11 (rid-001 redo → 8-子任务 codegraph → Batch A F1-F7)**:
|
|
37
|
+
|
|
38
|
+
- rid-001 redo: `fix(runtime): recover vendor-detect CLI seam + handle detached spawn ENOENT` (`e8fb5ed9`, 9 files / +436/-21)
|
|
39
|
+
- codegraph Phase 1: `feat(codegraph): add init conflict guard + doctor fallback + workspace auto-stake` (`e7ec3cb0`, 7 files / +849/-23)
|
|
40
|
+
- codegraph Phase 2: `feat(codegraph): ship affected context envelope + tarball guard + downstream notes` (`f4b56870`, 8 files / +929/-9)
|
|
41
|
+
- **F1**: `fix(runtime): expose DispatchResult.child + per-child ENOENT handler` (`b254840b`, 3 files / +96/-33) — `process.on('uncaughtException')` swallow → per-child `r.child?.on('error')` canonical pattern
|
|
42
|
+
- **F3**: `fix(runtime): recover vendor-detect Windows ENOENT via where.exe + PATHEXT` (`fcd38369`, 5 files / +58/-18) — `where.exe` on win32 / `which` on POSIX, no `shell: true` (no command-injection)
|
|
43
|
+
- **F4**: `chore(test): fix pre-existing test rot — graph-node flag, vitest 4.x, child mock, CG-003 path` (`4ff1152a`, 5 files / +20/-6) — 3 integration suites (dispatch-isolation-lifecycle / sub-agent-dispatch-e2e / dispatcher-flow) all PASS
|
|
44
|
+
- **F5**: `feat(cli): sub-agent dispatch --must-ls-files <glob> anti-fake-green gate` (`58bb4164`, 3 files / +231/-2) — `--must-ls-files <glob>` enforces `git ls-files` on sub-agent prompt frontmatter
|
|
45
|
+
- **F6**: `fix(version): peaks-loop-shared CLI_VERSION 4.0.18 -> 4.0.20 lockstep` (`f79d3b34`, 2 files / +41/-1) — lockstep guard test prevents re-introduction
|
|
46
|
+
- **F7**: `feat(codegraph): CG-003 PARTIAL verdict 4 follow-up items — preferred path + doctor probe` (`0e3b1bed`, 6 files / +469/-23) — preferred `.peaks/.codegraph/` over legacy `.codegraph/`; sub-command consistency; doctor capability probe upgrade
|
|
47
|
+
|
|
48
|
+
**Total Batch A**: 24 files / +975/-117 lines / 11+ new test cases / 124/124 unit baseline preserved + 31 net new test cases.
|
|
49
|
+
|
|
50
|
+
**Pending follow-ups (out of 4.0.21)**:
|
|
51
|
+
- F2 detached architecture heavy refactor (in-shell background subprocess; user 2026-08-11 feedback-driven) — separate 4.0.22+ batch
|
|
52
|
+
- F8 skill-resolution slice (session 7f7f78 original intent; rid-002/003/004 scope unknown — needs user scoping first)
|
|
53
|
+
|
|
3
54
|
## 4.0.20 — 2026-08-11 (peaks-loop-internal-runtime published as public lockstep package)
|
|
4
55
|
|
|
5
56
|
**Fix — peaks-loop@4.0.19 install 404 on `peaks-loop-internal-runtime`**:
|
package/README-en.md
CHANGED
|
@@ -274,6 +274,38 @@ It changes, but **the gates hold**. Audit fails = nothing ships. QA fails = noth
|
|
|
274
274
|
|
|
275
275
|
---
|
|
276
276
|
|
|
277
|
+
## Downstream consumer notes
|
|
278
|
+
|
|
279
|
+
If you maintain a project that depends on `peaks-loop` (or that runs `npm install peaks-loop` as part of CI / contributor onboarding), three behaviors are worth pinning in your head:
|
|
280
|
+
|
|
281
|
+
- **`codegraph` is a transitive dependency.** `peaks-loop` pins `@colbymchenry/codegraph@0.7.10` in its `dependencies`, so `npm install` / `pnpm install` will pull it automatically. The CLI command group `peaks codegraph …` (status / init / index / query / files / context / affected) is shipped inside `dist/services/codegraph/`. No extra install step required.
|
|
282
|
+
- **`.codegraph/` directory ownership is shared.** Tools like aider, cody, and similar agents also create a top-level `.codegraph/`. `peaks codegraph init` will refuse to overwrite a foreign schema and exits with code 73 + a `CODEGRAPH_INIT_CONFLICT` envelope. If your project already has a `.codegraph/` from another tool, move / rename it (or remove it if you no longer need it) before running `peaks codegraph init`.
|
|
283
|
+
- **Session binding lives under `.peaks/_runtime/<sessionId>/`.** Codegraph orchestration context (and other per-session evidence) is written there by RD / QA slices. Add `.peaks/_runtime/` to your project's `.gitignore` to avoid committing local session state. A missing / unbound session produces a graceful skip-with-warning, never a crash.
|
|
284
|
+
- **Doctor fallback for yarn-pnp / pnpm-strict.** `peaks doctor` falls back to a filesystem walk when `require.resolve('@colbymchenry/codegraph/package.json')` throws, so downstream installs under strict resolution modes still get a green check (or a `severity: 'warning'` if the version drifted from the pinned `0.7.10`).
|
|
285
|
+
- **Tarball size / verify.** Each release ships the codegraph service under `dist/services/codegraph/`. Per-release verification lives at `scripts/verify-codegraph-tarball.mjs` (run `node scripts/verify-codegraph-tarball.mjs` locally before tagging a release). Exit 0 means the whitelist is intact; exit 1 means the tarball is missing the codegraph service files.
|
|
286
|
+
|
|
287
|
+
Install snippet (typical consumer):
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# Add peaks-loop to your project
|
|
291
|
+
npm install peaks-loop
|
|
292
|
+
|
|
293
|
+
# Bootstrap codegraph for the first time (refuses if .codegraph/ already
|
|
294
|
+
# exists with a non-peaks-loop schema; exit code 73 + envelope)
|
|
295
|
+
npx peaks codegraph init --project .
|
|
296
|
+
|
|
297
|
+
# Verify the doctor is green for downstream resolution modes
|
|
298
|
+
npx peaks doctor --project .
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Three common pitfalls:
|
|
302
|
+
|
|
303
|
+
1. **`peaks codegraph init` exits 73** — your project has a pre-existing `.codegraph/` from another tool. Move it out of the way (or remove it), then re-run.
|
|
304
|
+
2. **`peaks doctor` warns about codegraph version drift** — your lockfile resolved a different `@colbymchenry/codegraph` version than the pinned `0.7.10`. Pin to `0.7.10` in your lockfile; the warning does not flip the doctor exit code.
|
|
305
|
+
3. **Session-bound artifacts under `.peaks/_runtime/` are noisy in `git status`** — add the path to your project's `.gitignore`. The `peaks-loop` repo already does this; consumers should mirror the rule.
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
277
309
|
## Links
|
|
278
310
|
|
|
279
311
|
- All skills → [`skills/`](./skills/)
|
package/README.md
CHANGED
|
@@ -282,6 +282,37 @@ winget install Python.Python.3.12
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
+
## 下游消费者须知 (Downstream consumer notes)
|
|
286
|
+
|
|
287
|
+
如果你的项目依赖 `peaks-loop`(或者把 `npm install peaks-loop` 写进 CI / 协作者 onboarding),有三件事需要先记清:
|
|
288
|
+
|
|
289
|
+
- **`codegraph` 是传递依赖。** `peaks-loop` 在 `dependencies` 里硬钉 `@colbymchenry/codegraph@0.7.10`,所以 `npm install` / `pnpm install` 会自动拉取。CLI 命令组 `peaks codegraph …`(status / init / index / query / files / context / affected)打包在 `dist/services/codegraph/`,无需额外安装步骤。
|
|
290
|
+
- **`.codegraph/` 目录归属是共享的。** aider / cody 等类似工具也会在项目根创建 `.codegraph/`。`peaks codegraph init` 拒绝覆盖外源 schema,以 exit code 73 + `CODEGRAPH_INIT_CONFLICT` 凭据退出。如果你的项目里已经有其他工具建的 `.codegraph/`,跑 `peaks codegraph init` 之前先移走 / 改名(或确认不再需要后删除)。
|
|
291
|
+
- **session 绑定落在 `.peaks/_runtime/<sessionId>/`。** codegraph 编排上下文(以及其他 session 级证据)由 RD / QA 切片写入。把 `.peaks/_runtime/` 加进项目的 `.gitignore`,避免误提交本地 session 状态。session 缺失 / 未绑定时优雅降级为「跳过 + 警告」,绝不 crash。
|
|
292
|
+
- **yarn-pnp / pnpm-strict 的 doctor 兜底。** `peaks doctor` 在 `require.resolve('@colbymchenry/codegraph/package.json')` 抛错时回退到文件系统遍历,因此严格解析模式下的下游安装也能拿到绿色 check(若版本偏离钉死的 `0.7.10` 则降级为 `severity: 'warning'`)。
|
|
293
|
+
- **tarball 大小 / 自检。** 每次发布都把 codegraph service 打进 `dist/services/codegraph/`。逐版本自检脚本见 `scripts/verify-codegraph-tarball.mjs`(发布前本地跑 `node scripts/verify-codegraph-tarball.mjs`)。exit 0 表示 whitelist 完好,exit 1 表示 tarball 漏了 codegraph service 文件。
|
|
294
|
+
|
|
295
|
+
典型下游安装片段:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
# 把 peaks-loop 加进项目
|
|
299
|
+
npm install peaks-loop
|
|
300
|
+
|
|
301
|
+
# 首次引导 codegraph(若项目已有外源 .codegraph/ 会以 exit 73 + 凭据拒绝)
|
|
302
|
+
npx peaks codegraph init --project .
|
|
303
|
+
|
|
304
|
+
# 校验 doctor 在下游解析模式下仍是绿色
|
|
305
|
+
npx peaks doctor --project .
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
三个常见坑:
|
|
309
|
+
|
|
310
|
+
1. **`peaks codegraph init` 退出码 73** —— 项目里已有外源 `.codegraph/`(aider / cody 等)。把它挪开(或删掉)再重跑。
|
|
311
|
+
2. **`peaks doctor` 报 codegraph 版本漂移** —— 你的 lockfile 解到了一个跟钉死 `0.7.10` 不同的版本。在 lockfile 里钉到 `0.7.10`;这条 warning 不会让 doctor 退出码翻红。
|
|
312
|
+
3. **`.peaks/_runtime/` 下的 session 产物在 `git status` 里嘈杂** —— 把这条路径加进项目的 `.gitignore`。`peaks-loop` 仓库已经这样做;下游消费者应当镜像这条规则。
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
285
316
|
## 链接
|
|
286
317
|
|
|
287
318
|
- 全部技能清单 → [`skills/`](./skills/)
|
|
@@ -73,6 +73,7 @@ import { registerSubAgentShutdownCommands } from './sub-agent-shutdown-commands.
|
|
|
73
73
|
import { registerSubAgentDispatchGuard } from './sub-agent-dispatch-guard.js';
|
|
74
74
|
import { registerTestCommands } from './test-commands.js';
|
|
75
75
|
import { registerUnderstandCommands } from './understand-commands.js';
|
|
76
|
+
import { registerVendorDetectCommand } from './vendor-detect.js';
|
|
76
77
|
import { registerUpgradeCommands } from './upgrade-commands.js';
|
|
77
78
|
import { registerUserTouchpointCommands } from './user-touchpoint-commands.js';
|
|
78
79
|
import { registerVerdictAggregateCommands } from './verdict-aggregate-command.js';
|
|
@@ -131,6 +132,7 @@ const REGISTRATIONS = [
|
|
|
131
132
|
['worktree-auth-commands', registerWorktreeAuthCommand],
|
|
132
133
|
['session-spill-demo', registerSpillDemoCommand],
|
|
133
134
|
['outer-cache-commands', registerOuterCacheCommands],
|
|
135
|
+
['vendor-detect', registerVendorDetectCommand],
|
|
134
136
|
];
|
|
135
137
|
function dispatchRegister(register, program, io) {
|
|
136
138
|
if (register.length <= 1) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { InvalidArgumentError } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { statSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { createCodegraphInvocation, executeCodegraphInvocation, defaultCodegraphInitGuard, writeCodegraphMarker, writeCodegraphAffectedContext, CodegraphInitConflictError } from '../../services/codegraph/codegraph-service.js';
|
|
5
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
4
6
|
import { getErrorMessage, printResult, redactSensitiveErrorMessage } from '../cli-helpers.js';
|
|
5
7
|
function addPeaksJsonOption(command) {
|
|
6
8
|
return command.option('--peaks-json', 'print Peaks error envelope as machine-readable JSON');
|
|
@@ -45,14 +47,158 @@ async function runCodegraphCommand(io, command, options, asJson) {
|
|
|
45
47
|
printCodegraphFailure(io, command, error, asJson);
|
|
46
48
|
}
|
|
47
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* rid-CG-006 — init conflict guard. Resolves the project root and
|
|
52
|
+
* probes `.codegraph/` for the peaks-loop marker before invoking the
|
|
53
|
+
* upstream binary.
|
|
54
|
+
*
|
|
55
|
+
* - fresh → proceed to upstream init
|
|
56
|
+
* - noop-already-peaks-loop → skip upstream, emit warning
|
|
57
|
+
* - conflict-foreign-schema → exit 73 + CODEGRAPH_INIT_CONFLICT envelope
|
|
58
|
+
*
|
|
59
|
+
* On a successful upstream init, write the marker so the next run
|
|
60
|
+
* hits the noop branch instead of the conflict branch.
|
|
61
|
+
*/
|
|
62
|
+
async function runCodegraphInitCommand(io, options, asJson) {
|
|
63
|
+
let projectRoot;
|
|
64
|
+
try {
|
|
65
|
+
const candidate = resolve(options.project);
|
|
66
|
+
if (!statSync(candidate).isDirectory()) {
|
|
67
|
+
throw new Error('Project path must exist and be a directory');
|
|
68
|
+
}
|
|
69
|
+
projectRoot = candidate;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
printCodegraphFailure(io, 'codegraph.init', error, asJson);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const guardOutcome = defaultCodegraphInitGuard(projectRoot);
|
|
76
|
+
if (guardOutcome.status === 'noop-already-peaks-loop') {
|
|
77
|
+
printResult(io, ok('codegraph.init', {
|
|
78
|
+
guard: guardOutcome.status,
|
|
79
|
+
codegraphDir: guardOutcome.codegraphDir,
|
|
80
|
+
markerPresent: true
|
|
81
|
+
}, [`.codegraph/ is already managed by peaks-loop; init is a no-op. Marker: ${guardOutcome.codegraphDir}/.peaks-loop-marker`], ['Run `peaks codegraph index` to (re)build the index without touching the schema.']), asJson);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (guardOutcome.status === 'conflict-foreign-schema') {
|
|
85
|
+
const conflict = new CodegraphInitConflictError(`Refusing to init: ${guardOutcome.codegraphDir} already exists with a non-peaks-loop schema. ` +
|
|
86
|
+
'Move or rename the foreign directory, then re-run `peaks codegraph init`.', guardOutcome.codegraphDir);
|
|
87
|
+
printResult(io, fail('codegraph.init', conflict.code, conflict.message, { codegraphDir: conflict.codegraphDir }, [
|
|
88
|
+
'Move or rename the foreign .codegraph/ directory before retrying.',
|
|
89
|
+
'Or remove .codegraph/ if you are sure no other tool owns it.',
|
|
90
|
+
'Or run `peaks codegraph init --project <path> --force` once the foreign-tool safety flag ships (tracked in rid-CG-006).'
|
|
91
|
+
]), asJson);
|
|
92
|
+
process.exitCode = conflict.exitCode;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// guardOutcome.status === 'fresh' — proceed.
|
|
96
|
+
try {
|
|
97
|
+
const invocation = createCodegraphInvocation({
|
|
98
|
+
subcommand: 'init',
|
|
99
|
+
project: options.project,
|
|
100
|
+
...(options.yes === true ? { yes: true } : {})
|
|
101
|
+
});
|
|
102
|
+
const result = await executeCodegraphInvocation(invocation);
|
|
103
|
+
const didFail = result.exitCode !== null && result.exitCode !== 0;
|
|
104
|
+
if (result.stdout.length > 0) {
|
|
105
|
+
io.stdout((didFail ? redactSensitiveErrorMessage(result.stdout) : result.stdout).trimEnd());
|
|
106
|
+
}
|
|
107
|
+
if (result.stderr.length > 0) {
|
|
108
|
+
io.stderr((didFail ? redactSensitiveErrorMessage(result.stderr) : result.stderr).trimEnd());
|
|
109
|
+
}
|
|
110
|
+
if (didFail) {
|
|
111
|
+
if (asJson === true) {
|
|
112
|
+
printCodegraphFailure(io, 'codegraph.init', new Error(result.stderr || result.stdout || `codegraph exited with code ${result.exitCode}`), true, result.exitCode ?? 1);
|
|
113
|
+
}
|
|
114
|
+
process.exitCode = result.exitCode ?? 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Upstream succeeded — stamp the marker so the next run hits the
|
|
118
|
+
// noop branch. Best-effort: a marker-write failure must NOT undo
|
|
119
|
+
// the upstream init (peaks-loop still owns the schema logically).
|
|
120
|
+
try {
|
|
121
|
+
writeCodegraphMarker(guardOutcome.codegraphDir);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// intentionally swallowed — surface as warning below
|
|
125
|
+
}
|
|
126
|
+
printResult(io, ok('codegraph.init', { guard: guardOutcome.status, codegraphDir: guardOutcome.codegraphDir, markerWritten: true }, [], [`Stamped peaks-loop marker at ${guardOutcome.codegraphDir}/.peaks-loop-marker`]), asJson);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
printCodegraphFailure(io, 'codegraph.init', error, asJson);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* rid-CG-002 — codegraph-affected envelope write.
|
|
134
|
+
*
|
|
135
|
+
* Wraps the generic `runCodegraphCommand` and, after a successful
|
|
136
|
+
* upstream invocation, calls `writeCodegraphAffectedContext` so the
|
|
137
|
+
* RD / QA handoff can pick up `.peaks/_runtime/<sid>/rd/codegraph-context.md`
|
|
138
|
+
* without re-running the (5-30 s) codegraph query.
|
|
139
|
+
*
|
|
140
|
+
* The envelope write is gated by `--write-envelope` (default off) so
|
|
141
|
+
* ad-hoc CLI invocations don't silently mutate the user's session
|
|
142
|
+
* directory. peaks-code's RD dispatch hook flips the flag on.
|
|
143
|
+
*
|
|
144
|
+
* On no-session-binding, we surface a `warning` field instead of
|
|
145
|
+
* throwing — the upstream result is still printed so the caller
|
|
146
|
+
* always sees the affected list.
|
|
147
|
+
*/
|
|
148
|
+
async function runCodegraphAffectedCommand(io, files, options, asJson) {
|
|
149
|
+
// Capture stdout so we can re-emit it into the envelope payload.
|
|
150
|
+
// We still forward every line to the original `io.stdout` so the
|
|
151
|
+
// user sees the affected list as if the wrapper were transparent.
|
|
152
|
+
const capturedLines = [];
|
|
153
|
+
const captureIo = {
|
|
154
|
+
stdout: (chunk) => {
|
|
155
|
+
capturedLines.push(chunk);
|
|
156
|
+
io.stdout(chunk);
|
|
157
|
+
},
|
|
158
|
+
stderr: (chunk) => io.stderr(chunk)
|
|
159
|
+
};
|
|
160
|
+
await runCodegraphCommand(captureIo, 'codegraph.affected', {
|
|
161
|
+
subcommand: 'affected',
|
|
162
|
+
project: options.project,
|
|
163
|
+
files,
|
|
164
|
+
...(options.json === true ? { json: true } : {})
|
|
165
|
+
}, asJson);
|
|
166
|
+
if (!options.writeEnvelope) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// The user opted in via --write-envelope. Run the envelope writer
|
|
170
|
+
// unconditionally (graceful fallback when no session binding).
|
|
171
|
+
const rid = options.rid ?? process.env.PEAKS_RD_RID ?? 'unknown-rid';
|
|
172
|
+
const rawStdout = capturedLines.join('\n');
|
|
173
|
+
let affectedPayload = rawStdout;
|
|
174
|
+
if (options.json === true && typeof affectedPayload === 'string' && affectedPayload.length > 0) {
|
|
175
|
+
try {
|
|
176
|
+
affectedPayload = JSON.parse(affectedPayload);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Keep the raw string when JSON parse fails; the envelope
|
|
180
|
+
// renderer handles strings cleanly.
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const envelope = writeCodegraphAffectedContext({
|
|
184
|
+
projectRoot: resolve(options.project),
|
|
185
|
+
rid,
|
|
186
|
+
files,
|
|
187
|
+
affectedPayload
|
|
188
|
+
});
|
|
189
|
+
if (envelope.written) {
|
|
190
|
+
if (asJson !== true) {
|
|
191
|
+
io.stdout(`[codegraph-context] wrote ${envelope.path}\n`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (asJson !== true) {
|
|
195
|
+
io.stdout(`[codegraph-context] skipped: ${envelope.warning}\n`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
48
198
|
export function registerCodegraphCommands(program, io) {
|
|
49
199
|
const codegraph = program.command('codegraph').description('Run upstream codegraph commands through the Peaks launcher');
|
|
50
200
|
addProjectOption(codegraph.command('status').description('Show codegraph status')).action((options) => runCodegraphCommand(io, 'codegraph.status', { subcommand: 'status', project: options.project }, options.peaksJson));
|
|
51
|
-
addProjectOption(codegraph.command('init').description('Initialize codegraph for a project').option('--yes', 'answer yes to upstream prompts')).action((options) =>
|
|
52
|
-
subcommand: 'init',
|
|
53
|
-
project: options.project,
|
|
54
|
-
...(options.yes === true ? { yes: true } : {})
|
|
55
|
-
}, options.peaksJson));
|
|
201
|
+
addProjectOption(codegraph.command('init').description('Initialize codegraph for a project').option('--yes', 'answer yes to upstream prompts')).action((options) => runCodegraphInitCommand(io, options, options.peaksJson));
|
|
56
202
|
addProjectOption(codegraph
|
|
57
203
|
.command('index')
|
|
58
204
|
.description('Index a project with codegraph')
|
|
@@ -90,10 +236,7 @@ export function registerCodegraphCommands(program, io) {
|
|
|
90
236
|
.command('affected')
|
|
91
237
|
.description('Find code affected by files')
|
|
92
238
|
.argument('<files...>', 'project-relative file paths')
|
|
93
|
-
.option('--json', 'forward JSON output flag to upstream codegraph')
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
files,
|
|
97
|
-
...(options.json === true ? { json: true } : {})
|
|
98
|
-
}, options.peaksJson));
|
|
239
|
+
.option('--json', 'forward JSON output flag to upstream codegraph')
|
|
240
|
+
.option('--rid <rid>', 'request id for the codegraph-context envelope (default: env PEAKS_RD_RID or "unknown-rid")')
|
|
241
|
+
.option('--write-envelope', 'write codegraph-context.md into the active session')).action((files, options) => runCodegraphAffectedCommand(io, files, options, options.peaksJson));
|
|
99
242
|
}
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
3
|
export declare function registerDispatchCommand(parent: Command, io: ProgramIO): void;
|
|
4
|
+
/**
|
|
5
|
+
* F5 follow-up (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure
|
|
6
|
+
* §Lesson 1): synchronous anti-fake-green file-existence gate. Runs
|
|
7
|
+
* `git ls-files <glob>` against `projectRoot` and returns the matching
|
|
8
|
+
* tracked file paths (relative to projectRoot). Empty array when no
|
|
9
|
+
* files match (e.g. untracked new file, wrong glob, not a git repo).
|
|
10
|
+
*
|
|
11
|
+
* Why `git ls-files` and not `fs.glob`: the anti-fake-green contract
|
|
12
|
+
* is "the file the sub-agent claims to have written must ACTUALLY be
|
|
13
|
+
* tracked by git" — `git ls-files` enforces that contract; `fs.glob`
|
|
14
|
+
* would happily return untracked-but-on-disk files (false-positive
|
|
15
|
+
* for the fake-green gate).
|
|
16
|
+
*
|
|
17
|
+
* Failure modes (best-effort, never throws):
|
|
18
|
+
* - git not on PATH → empty array (`ENOENT` swallowed)
|
|
19
|
+
* - not a git repo → empty array (git exits non-zero)
|
|
20
|
+
* - glob matches zero tracked files → empty array
|
|
21
|
+
*
|
|
22
|
+
* Exported for unit-test access (`tests/unit/sub-agent/must-ls-files-flag.test.ts`).
|
|
23
|
+
* The export is intentional — the helper has zero side effects and
|
|
24
|
+
* keeps the dispatch action handler small.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runGitLsFiles(projectRoot: string, glob: string): readonly string[];
|
|
4
27
|
/**
|
|
5
28
|
* `dispatchSubAgent` is the thin programmatic wrapper the integration
|
|
6
29
|
* test (`tests/integration/sub-agent-graph-binding.test.ts`) imports.
|
|
@@ -72,8 +72,70 @@ export function registerDispatchCommand(parent, io) {
|
|
|
72
72
|
// rejects with PEAKS_GRAPH_NODE_REQUIRED / PEAKS_GRAPH_NODE_NOT_PREPARED.
|
|
73
73
|
.requiredOption('--graph-node <id>', 'graph node id this dispatch binds to (RD §4 D4c)')
|
|
74
74
|
.option('--workflow-id <id>', 'workflow id the graph node belongs to (defaults to derived from session)')
|
|
75
|
-
.option('--graph-ref <ref>', 'graphRef (defaults to graphs/<workflow-id>.json)')
|
|
75
|
+
.option('--graph-ref <ref>', 'graphRef (defaults to graphs/<workflow-id>.json)')
|
|
76
|
+
// rid-001 detached sub-agent dispatch: 4 new options. Default
|
|
77
|
+
// mode is `in-process` so the 106+ existing dispatch call sites
|
|
78
|
+
// keep their path byte-identical. The detached branch below
|
|
79
|
+
// fires only when --mode detached is explicitly passed.
|
|
80
|
+
.option('--mode <mode>', 'dispatch execution mode: in-process (default, dry-run envelope only) | detached (shell out to peaks-loop-internal-runtime/dispatch.dispatchDetached for real vendor CLI execution).')
|
|
81
|
+
.option('--vendor <vendor>', 'target vendor CLI for --mode detached (claude | codex | copilot). Ignored in the default in-process path.')
|
|
82
|
+
.option('--no-throttle', 'rid-001 detached: user-overrides ResourceBudgetGuard when concurrent fan-out exceeds max-concurrent (user accepts risk; surfaces as warning)')
|
|
83
|
+
.option('--max-concurrent <n>', 'rid-001 detached: override the per-tenant max concurrent budget (default 8). Effective in both detached and in-process paths.')
|
|
84
|
+
// F5 follow-up (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure
|
|
85
|
+
// §Lesson 1): the RD sub-agent's fake-green failure mode was that it
|
|
86
|
+
// claimed "5/5 reachability tests PASS" while the files were never
|
|
87
|
+
// on disk. `--must-ls-files <glob>` is the anti-fake-green gate:
|
|
88
|
+
// the CLI runs `git ls-files <glob>` upfront, reports the result in
|
|
89
|
+
// the envelope (`data.mustLsFilesVerification`), and prepends a
|
|
90
|
+
// `## must_ls_files enforcement` block to the sub-agent prompt so
|
|
91
|
+
// the LLM's first action MUST re-verify file existence before any
|
|
92
|
+
// "completed" claim. Absent → old behavior is preserved.
|
|
93
|
+
.option('--must-ls-files <glob>', 'F5: anti-fake-green gate. Run `git ls-files <glob>` upfront; surface the result in the envelope as `mustLsFilesVerification: { path, exists, files }`; prepend a must_ls_files enforcement block to the sub-agent prompt. Absent → unchanged behavior.')).action(async (role, options) => {
|
|
76
94
|
const asJson = options.json === true;
|
|
95
|
+
// rid-001 detached sub-agent dispatch: when --mode detached is
|
|
96
|
+
// explicitly requested, lazy-import the detached handler and short-
|
|
97
|
+
// circuit before the warm-path in-process pipeline runs. Branch
|
|
98
|
+
// lives in the existing action handler (NOT a sibling `peaks
|
|
99
|
+
// sub-agent-detached` command) per the slice decision memo:
|
|
100
|
+
// - 106+ existing dispatch tests reach this exact action path
|
|
101
|
+
// - Backward compat requires the default (no --mode) to keep
|
|
102
|
+
// the in-process envelope shape byte-identical
|
|
103
|
+
// - One validation entry-point reduces double-pipe maintenance
|
|
104
|
+
if (options.mode === 'detached') {
|
|
105
|
+
try {
|
|
106
|
+
const { dispatch: detachedDispatch } = await import('./sub-agent/detached.js');
|
|
107
|
+
const projectRoot = options.project ?? process.cwd();
|
|
108
|
+
const maxConcurrent = typeof options.maxConcurrent === 'string' && options.maxConcurrent.length > 0
|
|
109
|
+
? Number.parseInt(options.maxConcurrent, 10)
|
|
110
|
+
: undefined;
|
|
111
|
+
const result = await detachedDispatch({
|
|
112
|
+
role,
|
|
113
|
+
prompt: typeof options.prompt === 'string' ? options.prompt : '',
|
|
114
|
+
requestId: options.requestId ?? 'unknown-rid',
|
|
115
|
+
mode: 'detached',
|
|
116
|
+
...(typeof options.vendor === 'string' ? { vendor: options.vendor } : {}),
|
|
117
|
+
project: projectRoot,
|
|
118
|
+
json: asJson,
|
|
119
|
+
...(options.noThrottle === true ? { noThrottle: true } : {}),
|
|
120
|
+
...(typeof maxConcurrent === 'number' && Number.isInteger(maxConcurrent) && maxConcurrent > 0
|
|
121
|
+
? { maxConcurrent }
|
|
122
|
+
: {}),
|
|
123
|
+
});
|
|
124
|
+
printResult(io, ok(result.command, result.data, result.warnings ?? [], result.nextActions ?? []), asJson);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
printResult(io, fail('sub-agent.dispatch', 'DISPATCH_DETACHED_ERROR', getErrorMessage(error), {
|
|
128
|
+
role,
|
|
129
|
+
toolCall: null,
|
|
130
|
+
dispatchRecordPath: null
|
|
131
|
+
}, [
|
|
132
|
+
'If --mode detached fails on import, the peaks-loop-internal-runtime package may be missing; reinstall and retry.',
|
|
133
|
+
'For environments without a vendor CLI on PATH, drop --mode to fall back to the default in-process dry-run.'
|
|
134
|
+
]), asJson);
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
77
139
|
// 2.7.0 slice-dag-dispatcher MVP: --from-dag short-circuits the single
|
|
78
140
|
// sub-agent path and runs the full DAG plan via `dag-orchestrator`.
|
|
79
141
|
if (typeof options.fromDag === 'string' && options.fromDag.length > 0) {
|
|
@@ -379,7 +441,31 @@ export function registerDispatchCommand(parent, io) {
|
|
|
379
441
|
`branch: ${worktreeBranch}\n` +
|
|
380
442
|
`You MAY ` + '`git worktree add` ' + `and ` + '`git worktree remove` ' + `against this lease without a separate ` + '`peaks worktree auth grant` ' + `— the PreToolUse gate reads the lease file. Run ` + '`peaks worktree release --lease-id ${leaseId}` ' + `when done.\n`
|
|
381
443
|
: '';
|
|
382
|
-
|
|
444
|
+
// F5 follow-up: anti-fake-green gate. When `--must-ls-files <glob>`
|
|
445
|
+
// is supplied, run `git ls-files <glob>` upfront, surface the
|
|
446
|
+
// result in the envelope as `mustLsFilesVerification: { path,
|
|
447
|
+
// exists, files }`, and prepend a `## must_ls_files enforcement`
|
|
448
|
+
// frontmatter block to the sub-agent prompt that mandates the
|
|
449
|
+
// file-existence verification as the LLM's FIRST action (before
|
|
450
|
+
// any "completed"/"PASS" claim). When the flag is absent the
|
|
451
|
+
// field is `null` and no block is injected — old call sites
|
|
452
|
+
// see no behavior change (rid-001 fake-green Lesson 1).
|
|
453
|
+
let mustLsFilesVerification = null;
|
|
454
|
+
let mustLsFilesBlock = '';
|
|
455
|
+
if (typeof options.mustLsFiles === 'string' && options.mustLsFiles.length > 0) {
|
|
456
|
+
const glob = options.mustLsFiles;
|
|
457
|
+
const files = runGitLsFiles(projectRoot, glob);
|
|
458
|
+
const exists = files.length > 0;
|
|
459
|
+
mustLsFilesVerification = { path: glob, exists, files };
|
|
460
|
+
mustLsFilesBlock = `\n## must_ls_files enforcement (F5 anti-fake-green)\n` +
|
|
461
|
+
`glob: ${glob}\n` +
|
|
462
|
+
`verification: ${exists ? `EXISTS (${files.length} file${files.length === 1 ? '' : 's'} found)` : 'MISSING (no files matched the glob)'}\n` +
|
|
463
|
+
(exists ? `first match: ${files[0] ?? ''}\n` : '') +
|
|
464
|
+
`BEFORE any claim that work is "completed" or "PASS", you MUST run \`git ls-files ${glob}\` from the project root and ` +
|
|
465
|
+
`confirm the file exists. Anti-fake-green rule (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure §Lesson 1): ` +
|
|
466
|
+
`if the file does not exist, your verdict MUST be \`status: "blocked"\` with reason "must_ls_files_failed". Do NOT silently skip this step.\n`;
|
|
467
|
+
}
|
|
468
|
+
let effectivePrompt = `${formatTestToolDetection()}\n\n${memoryAugmentedBody}${isolationBlock}${mustLsFilesBlock}`;
|
|
383
469
|
let headroomCompressed = false;
|
|
384
470
|
let headroomResult = null;
|
|
385
471
|
const warnings = [...decision.warnings];
|
|
@@ -596,7 +682,13 @@ export function registerDispatchCommand(parent, io) {
|
|
|
596
682
|
isolation: isolationMode,
|
|
597
683
|
leaseId,
|
|
598
684
|
worktreePath,
|
|
599
|
-
worktreeBranch
|
|
685
|
+
worktreeBranch,
|
|
686
|
+
// F5: anti-fake-green gate envelope surface. When
|
|
687
|
+
// `--must-ls-files <glob>` is supplied this carries the
|
|
688
|
+
// pre-dispatch verification result so the orchestrator can
|
|
689
|
+
// surface "the file exists" (or "missing — block") before
|
|
690
|
+
// spawning the sub-agent. Null when the flag is absent.
|
|
691
|
+
mustLsFilesVerification
|
|
600
692
|
}, warnings, nextActions), asJson);
|
|
601
693
|
// Slice 2026-06-23-audit-4th #B1: structured log on success path.
|
|
602
694
|
// Best-effort: writeLogEntry swallows its own errors (logger.ts:155-159),
|
|
@@ -766,6 +858,38 @@ function spawnContainerLease(args) {
|
|
|
766
858
|
child.unref();
|
|
767
859
|
});
|
|
768
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* F5 follow-up (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure
|
|
863
|
+
* §Lesson 1): synchronous anti-fake-green file-existence gate. Runs
|
|
864
|
+
* `git ls-files <glob>` against `projectRoot` and returns the matching
|
|
865
|
+
* tracked file paths (relative to projectRoot). Empty array when no
|
|
866
|
+
* files match (e.g. untracked new file, wrong glob, not a git repo).
|
|
867
|
+
*
|
|
868
|
+
* Why `git ls-files` and not `fs.glob`: the anti-fake-green contract
|
|
869
|
+
* is "the file the sub-agent claims to have written must ACTUALLY be
|
|
870
|
+
* tracked by git" — `git ls-files` enforces that contract; `fs.glob`
|
|
871
|
+
* would happily return untracked-but-on-disk files (false-positive
|
|
872
|
+
* for the fake-green gate).
|
|
873
|
+
*
|
|
874
|
+
* Failure modes (best-effort, never throws):
|
|
875
|
+
* - git not on PATH → empty array (`ENOENT` swallowed)
|
|
876
|
+
* - not a git repo → empty array (git exits non-zero)
|
|
877
|
+
* - glob matches zero tracked files → empty array
|
|
878
|
+
*
|
|
879
|
+
* Exported for unit-test access (`tests/unit/sub-agent/must-ls-files-flag.test.ts`).
|
|
880
|
+
* The export is intentional — the helper has zero side effects and
|
|
881
|
+
* keeps the dispatch action handler small.
|
|
882
|
+
*/
|
|
883
|
+
export function runGitLsFiles(projectRoot, glob) {
|
|
884
|
+
try {
|
|
885
|
+
const { execFileSync } = require('node:child_process');
|
|
886
|
+
const stdout = execFileSync('git', ['ls-files', '--', glob], { cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
|
|
887
|
+
return stdout.split('\n').filter((line) => line.length > 0);
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
return [];
|
|
891
|
+
}
|
|
892
|
+
}
|
|
769
893
|
/* ---------- Slice 4.0.8 RD §4 D4c: programmatic dispatcher ---------- */
|
|
770
894
|
/**
|
|
771
895
|
* `dispatchSubAgent` is the thin programmatic wrapper the integration
|
|
@@ -25,8 +25,17 @@ export async function dispatch(f) {
|
|
|
25
25
|
if (f.noThrottle) {
|
|
26
26
|
warnings.push('user-overrode: --no-throttle (peak runtime may exceed performance ceiling)');
|
|
27
27
|
}
|
|
28
|
+
// rid-001 fix (F1 follow-up): the vendor CLI's ChildProcess is owned by
|
|
29
|
+
// peaks-loop-internal-runtime/dispatch.dispatchDetached, which now exposes
|
|
30
|
+
// it via DispatchResult.child. When the vendor binary is not on PATH the
|
|
31
|
+
// spawn fires an async 'error' event after dispatchDetached returns; we
|
|
32
|
+
// attach a per-child handler that swallows ENOENT (expected when the user
|
|
33
|
+
// hasn't installed claude/codex/copilot) and logs anything else. This is
|
|
34
|
+
// the canonical pattern (matches codegraph-process-runner.ts) — no more
|
|
35
|
+
// process-level uncaughtException swallow.
|
|
28
36
|
const sid = process.env.PEAKS_SESSION_ID ?? 'local';
|
|
29
|
-
|
|
37
|
+
let r;
|
|
38
|
+
r = await dispatchDetached({
|
|
30
39
|
sid,
|
|
31
40
|
rid: f.requestId,
|
|
32
41
|
role: f.role,
|
|
@@ -37,6 +46,13 @@ export async function dispatch(f) {
|
|
|
37
46
|
runtimeDir: `.peaks/_runtime/${sid}/detached`,
|
|
38
47
|
subAgentsDir: `.peaks/_sub_agents/${sid}`,
|
|
39
48
|
});
|
|
49
|
+
r.child?.on('error', (err) => {
|
|
50
|
+
if (err && err.code === 'ENOENT')
|
|
51
|
+
return;
|
|
52
|
+
// Non-ENOENT child errors are surfaced to stderr; the dispatch envelope
|
|
53
|
+
// has already been written and the detached child is fire-and-forget.
|
|
54
|
+
console.error('[peaks sub-agent dispatch] detached child error:', err);
|
|
55
|
+
});
|
|
40
56
|
return {
|
|
41
57
|
ok: true,
|
|
42
58
|
command: 'sub-agent.dispatch.detached',
|
|
@@ -48,6 +48,45 @@ export type DispatchOptions = {
|
|
|
48
48
|
graphNode?: string;
|
|
49
49
|
workflowId?: string;
|
|
50
50
|
graphRef?: string;
|
|
51
|
+
/**
|
|
52
|
+
* rid-001 detached sub-agent dispatch (slice 2026-08-11): dispatch
|
|
53
|
+
* execution mode. `in-process` (default) keeps the existing warm-path
|
|
54
|
+
* CLI dispatch; `detached` shells out to
|
|
55
|
+
* `peaks-loop-internal-runtime/dispatch.dispatchDetached` for vendor
|
|
56
|
+
* CLI execution. Default `in-process` preserves backward compat with
|
|
57
|
+
* the 106+ existing dispatch call sites.
|
|
58
|
+
*/
|
|
59
|
+
mode?: 'in-process' | 'detached';
|
|
60
|
+
/**
|
|
61
|
+
* rid-001 detached sub-agent dispatch: target vendor CLI when
|
|
62
|
+
* --mode detached is selected. Only consulted in the detached path;
|
|
63
|
+
* ignored in the default in-process path. Accepts `claude | codex
|
|
64
|
+
* | copilot` (matches VendorAdapterRegistry).
|
|
65
|
+
*/
|
|
66
|
+
vendor?: 'claude' | 'codex' | 'copilot';
|
|
67
|
+
/**
|
|
68
|
+
* rid-001 Task 11.5 budget ceiling: user-overrides ResourceBudgetGuard
|
|
69
|
+
* when active concurrent fan-out would otherwise throttle detached
|
|
70
|
+
* dispatch. The user accepts the risk; surfaces as `warnings[]` only.
|
|
71
|
+
*/
|
|
72
|
+
noThrottle?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* rid-001 Task 11.5: override the per-tenant max-concurrent budget
|
|
75
|
+
* (default 8). Effective in both detached (ResourceBudgetGuard) and
|
|
76
|
+
* in-process (batch-counter) paths.
|
|
77
|
+
*/
|
|
78
|
+
maxConcurrent?: string;
|
|
79
|
+
/**
|
|
80
|
+
* F5 follow-up (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure
|
|
81
|
+
* §Lesson 1): frontmatter `--must-ls-files <glob>` flag. When set, the
|
|
82
|
+
* dispatch CLI runs `git ls-files <glob>` upfront, surfaces
|
|
83
|
+
* `mustLsFilesVerification: { path, exists, files }` in the envelope,
|
|
84
|
+
* and prepends a `## must_ls_files enforcement` block to the sub-agent
|
|
85
|
+
* prompt so the LLM's first action MUST verify the file exists before
|
|
86
|
+
* claiming "PASS". Absent → unchanged behavior (backward compat with
|
|
87
|
+
* the 106+ existing dispatch call sites).
|
|
88
|
+
*/
|
|
89
|
+
mustLsFiles?: string;
|
|
51
90
|
json?: boolean;
|
|
52
91
|
};
|
|
53
92
|
export type HeartbeatOptions = {
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase B Task 20: peaks vendor-detect CLI.
|
|
3
|
+
* Reports which vendor CLIs are installed on PATH + recommends default.
|
|
4
|
+
* Spec: docs/superpowers/specs/2026-08-10-peaks-detached-sub-agent-design.md §3.3
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
1
8
|
export declare function vendorDetect(opts: {
|
|
2
9
|
json: boolean;
|
|
3
10
|
}): Promise<{
|
|
@@ -8,3 +15,11 @@ export declare function vendorDetect(opts: {
|
|
|
8
15
|
recommended: string | null;
|
|
9
16
|
};
|
|
10
17
|
}>;
|
|
18
|
+
/**
|
|
19
|
+
* `peaks vendor-detect` CLI registration (rid-001 redo).
|
|
20
|
+
* Slice 2026-08-11 detached-sub-agent-design §3.3 + §5.3 — register the
|
|
21
|
+
* previously-dead-coded `vendorDetect()` handler at the top-level program
|
|
22
|
+
* surface so `peaks vendor-detect --json` actually reaches it (was
|
|
23
|
+
* dead-coded at the CLI seam even though the handler existed).
|
|
24
|
+
*/
|
|
25
|
+
export declare function registerVendorDetectCommand(program: Command, io: ProgramIO): void;
|