peaks-loop 4.1.0 → 4.1.1
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 +25 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/dist/cli/commands/_register.js +2 -2
- package/dist/cli/commands/code-mode-gate-should-pause-command.js +1 -1
- package/dist/cli/commands/comments-commands.d.ts +14 -0
- package/dist/cli/commands/comments-commands.js +96 -0
- package/dist/cli/commands/core/memory-command.js +10 -0
- package/dist/cli/commands/core/skill-command.js +1 -1
- package/dist/cli/commands/core/standards-command.js +1 -1
- package/dist/cli/commands/ecc-commands.d.ts +17 -22
- package/dist/cli/commands/ecc-commands.js +38 -26
- package/dist/cli/commands/prd-commands.js +8 -2
- package/dist/cli/program.js +4 -9
- package/dist/services/code/mode-gate-types.d.ts +1 -1
- package/dist/services/code/mode-gate-types.js +0 -1
- package/dist/services/code/mode-gate.js +1 -9
- package/dist/services/code/user-touchpoint-classifier.js +0 -7
- package/dist/services/code-review/ecc-bridge.d.ts +6 -6
- package/dist/services/comments/citation-rules.d.ts +154 -0
- package/dist/services/comments/citation-rules.js +241 -0
- package/dist/services/comments/comment-audit.d.ts +57 -0
- package/dist/services/comments/comment-audit.js +100 -0
- package/dist/services/comments/comment-citations.d.ts +60 -0
- package/dist/services/comments/comment-citations.js +186 -0
- package/dist/services/comments/comment-hygiene.d.ts +79 -0
- package/dist/services/comments/comment-hygiene.js +133 -0
- package/dist/services/comments/comment-prune.d.ts +88 -0
- package/dist/services/comments/comment-prune.js +148 -0
- package/dist/services/comments/prune-apply.d.ts +60 -0
- package/dist/services/comments/prune-apply.js +150 -0
- package/dist/services/comments/repo-path-probe.d.ts +43 -0
- package/dist/services/comments/repo-path-probe.js +78 -0
- package/dist/services/log/retention.d.ts +0 -16
- package/dist/services/log/retention.js +0 -17
- package/dist/services/memory/project-memory-service/index.d.ts +1 -1
- package/dist/services/memory/project-memory-service/index.js +1 -1
- package/dist/services/memory/project-memory-service/store/atomic-write.d.ts +19 -7
- package/dist/services/memory/project-memory-service/store/atomic-write.js +120 -26
- package/dist/services/prd/handoff-frontmatter.js +61 -0
- package/dist/services/prd/handoff-gate-evidence.js +14 -10
- package/dist/services/prd/handoff-service.d.ts +11 -1
- package/dist/services/prd/handoff-service.js +11 -1
- package/dist/services/prd/handoff-types.d.ts +33 -1
- package/dist/services/recommendations/installed-capability-detector.d.ts +5 -5
- package/dist/services/recommendations/installed-capability-detector.js +11 -10
- package/dist/services/scan/archetype-detection.d.ts +37 -0
- package/dist/services/scan/archetype-detection.js +175 -2
- package/dist/services/scan/archetype-service.js +36 -22
- package/dist/services/scan/scan-types.d.ts +9 -0
- package/dist/services/workspace/generated-artifacts-stamp.d.ts +2 -2
- package/dist/services/workspace/generated-artifacts-stamp.js +2 -2
- package/dist/services/workspace/workspace-service.js +1 -1
- package/package.json +5 -5
- package/scripts/install-skills.mjs +0 -177
- package/skills/bee/peaks-qa/references/reading-handoff-frontmatter.md +3 -1
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +1 -1
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +3 -2
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +29 -7
- package/skills/peaks-code/references/frontend-only-mode.md +2 -2
- package/skills/peaks-code/references/startup-sequence.md +0 -4
- package/dist/cli/commands/upgrade-commands.d.ts +0 -25
- package/dist/cli/commands/upgrade-commands.js +0 -154
- package/dist/services/upgrade/1x-detector-service.d.ts +0 -7
- package/dist/services/upgrade/1x-detector-service.js +0 -96
- package/dist/services/upgrade/gitignore-migrate-service.d.ts +0 -56
- package/dist/services/upgrade/gitignore-migrate-service.js +0 -170
- package/dist/services/upgrade/upgrade-service.d.ts +0 -81
- package/dist/services/upgrade/upgrade-service.js +0 -428
- package/skills/peaks-code/references/step-0-55-1x-detection.md +0 -83
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* `.claude/settings.local.json` IS drift-checked against the current template
|
|
15
15
|
* — but only when something calls `initWorkspace`, which is precisely the
|
|
16
|
-
* event that stopped happening. The escape hatch (`peaks
|
|
17
|
-
*
|
|
16
|
+
* event that stopped happening. The escape hatch (`peaks workspace init`) is
|
|
17
|
+
* real and idempotent. What was missing is anything that
|
|
18
18
|
* tells the user — or the LLM driving them — that they need it. That is the
|
|
19
19
|
* gap this module closes: a stamp the generator writes, and a detector that
|
|
20
20
|
* can answer "the file on disk was produced by 4.0.40 while the installed
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*
|
|
14
14
|
* `.claude/settings.local.json` IS drift-checked against the current template
|
|
15
15
|
* — but only when something calls `initWorkspace`, which is precisely the
|
|
16
|
-
* event that stopped happening. The escape hatch (`peaks
|
|
17
|
-
*
|
|
16
|
+
* event that stopped happening. The escape hatch (`peaks workspace init`) is
|
|
17
|
+
* real and idempotent. What was missing is anything that
|
|
18
18
|
* tells the user — or the LLM driving them — that they need it. That is the
|
|
19
19
|
* gap this module closes: a stamp the generator writes, and a detector that
|
|
20
20
|
* can answer "the file on disk was produced by 4.0.40 while the installed
|
|
@@ -194,7 +194,7 @@ export async function initWorkspace(options) {
|
|
|
194
194
|
// is `$HOME`) resolved to `$HOME` and did exactly that — `.peaks/`,
|
|
195
195
|
// `.gitignore`, `.claude/settings.local.json` and a codegraph index, all in
|
|
196
196
|
// the user's home. Enforced here as well as at the CLI so a non-CLI caller
|
|
197
|
-
//
|
|
197
|
+
// cannot reach the same write by another door.
|
|
198
198
|
assertWritableProjectRoot(options.projectRoot);
|
|
199
199
|
// Phase 6 refactor (slice 2026-06-05-change-id-as-unit-of-work) +
|
|
200
200
|
// slice 006 (2026-06-06-change-folder-simplify-and-lazy-role-subdirs) +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-loop",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"keywords": [
|
|
@@ -117,10 +117,10 @@
|
|
|
117
117
|
"picomatch": "4.0.4",
|
|
118
118
|
"yaml": "^2.9.0",
|
|
119
119
|
"zod": "^4.4.3",
|
|
120
|
-
"peaks-loop-internal-runtime": "0.0.
|
|
121
|
-
"peaks-loop-mut": "0.1.
|
|
122
|
-
"peaks-loop-shared": "0.0.
|
|
123
|
-
"peaks-loop-shared-channel": "0.0.
|
|
120
|
+
"peaks-loop-internal-runtime": "0.0.41",
|
|
121
|
+
"peaks-loop-mut": "0.1.55",
|
|
122
|
+
"peaks-loop-shared": "0.0.90",
|
|
123
|
+
"peaks-loop-shared-channel": "0.0.58"
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
126
|
"@changesets/cli": "2.31.1",
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
unlinkSync,
|
|
18
18
|
writeFileSync
|
|
19
19
|
} from 'node:fs';
|
|
20
|
-
import { spawnSync } from 'node:child_process';
|
|
21
20
|
import { createHash, randomUUID } from 'node:crypto';
|
|
22
21
|
import { homedir } from 'node:os';
|
|
23
22
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -1409,162 +1408,6 @@ export function installBundledSkillsForAllPlatforms(options = {}) {
|
|
|
1409
1408
|
return perPlatform;
|
|
1410
1409
|
}
|
|
1411
1410
|
|
|
1412
|
-
/**
|
|
1413
|
-
* 1.x → 2.0 detection — sniff for legacy 1.x project state
|
|
1414
|
-
* in `cwd`. Returns a 1.x detection envelope with the
|
|
1415
|
-
* detected signals (so the postinstall can decide whether
|
|
1416
|
-
* to auto-upgrade).
|
|
1417
|
-
*
|
|
1418
|
-
* 1.x signals (any one fires the detection):
|
|
1419
|
-
* - `~/.peaks/config.json` exists with `version: '1.4.2'` (or
|
|
1420
|
-
* any '1.x' version that predates the 2.0 schema)
|
|
1421
|
-
* - `.claude/rules/common/dev-preference.md` exists and
|
|
1422
|
-
* references "peaks progress" (the 1.x CLI surface
|
|
1423
|
-
* removed in slice #014)
|
|
1424
|
-
* - `<cwd>/.peaks/preferences.json` missing OR has no
|
|
1425
|
-
* `schema_version: '2.0.0'` field
|
|
1426
|
-
*
|
|
1427
|
-
* Returns:
|
|
1428
|
-
* { isOneX: boolean, signals: string[], projectRoot: string|null,
|
|
1429
|
-
* configPath: string|null }
|
|
1430
|
-
*/
|
|
1431
|
-
export function detect1xProjectState(cwd = process.cwd()) {
|
|
1432
|
-
const home = homedir();
|
|
1433
|
-
const signals = [];
|
|
1434
|
-
let projectRoot = null;
|
|
1435
|
-
let configPath = null;
|
|
1436
|
-
|
|
1437
|
-
// Walk up from cwd looking for .peaks/_runtime (signals
|
|
1438
|
-
// we're inside a peaks project).
|
|
1439
|
-
let dir = cwd;
|
|
1440
|
-
for (let i = 0; i < 8; i += 1) {
|
|
1441
|
-
const peaksRuntime = join(dir, '.peaks', '_runtime');
|
|
1442
|
-
if (existsSync(peaksRuntime)) {
|
|
1443
|
-
projectRoot = dir;
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
const parent = dirname(dir);
|
|
1447
|
-
if (parent === dir) break;
|
|
1448
|
-
dir = parent;
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
// Signal 1: ~/.peaks/config.json with 1.x version
|
|
1452
|
-
const globalConfig = join(home, '.peaks', 'config.json');
|
|
1453
|
-
if (existsSync(globalConfig)) {
|
|
1454
|
-
try {
|
|
1455
|
-
const raw = JSON.parse(readFileSync(globalConfig, 'utf8'));
|
|
1456
|
-
if (typeof raw.version === 'string' && /^1\./.test(raw.version)) {
|
|
1457
|
-
signals.push(`global config at ${globalConfig} is 1.x (${raw.version})`);
|
|
1458
|
-
if (configPath === null) configPath = globalConfig;
|
|
1459
|
-
}
|
|
1460
|
-
} catch {
|
|
1461
|
-
// ignore parse error — the 1.x detection is best-effort
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
// Signal 2: .claude/rules/common/dev-preference.md with peaks progress
|
|
1466
|
-
if (projectRoot !== null) {
|
|
1467
|
-
const devPref = join(projectRoot, '.claude', 'rules', 'common', 'dev-preference.md');
|
|
1468
|
-
if (existsSync(devPref)) {
|
|
1469
|
-
try {
|
|
1470
|
-
const body = readFileSync(devPref, 'utf8');
|
|
1471
|
-
if (/peaks progress/i.test(body)) {
|
|
1472
|
-
signals.push(
|
|
1473
|
-
`${devPref} references "peaks progress" (1.x CLI surface, removed in slice #014)`
|
|
1474
|
-
);
|
|
1475
|
-
}
|
|
1476
|
-
} catch {
|
|
1477
|
-
// ignore
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
// Signal 3: project preferences.json missing or 1.x
|
|
1481
|
-
const prefs = join(projectRoot, '.peaks', 'preferences.json');
|
|
1482
|
-
if (!existsSync(prefs)) {
|
|
1483
|
-
signals.push(`${prefs} does not exist (1.x project never migrated)`);
|
|
1484
|
-
} else {
|
|
1485
|
-
try {
|
|
1486
|
-
const raw = JSON.parse(readFileSync(prefs, 'utf8'));
|
|
1487
|
-
if (raw.schema_version !== '2.0.0') {
|
|
1488
|
-
signals.push(
|
|
1489
|
-
`${prefs} has schema_version ${JSON.stringify(raw.schema_version)}, expected '2.0.0'`
|
|
1490
|
-
);
|
|
1491
|
-
}
|
|
1492
|
-
} catch {
|
|
1493
|
-
signals.push(`${prefs} exists but is not valid JSON`);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
return {
|
|
1499
|
-
isOneX: signals.length > 0,
|
|
1500
|
-
signals,
|
|
1501
|
-
projectRoot,
|
|
1502
|
-
configPath
|
|
1503
|
-
};
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
* Postinstall auto-upgrade — when the user just ran
|
|
1508
|
-
* `npm i -g peaks-loop@2.0` and `cwd` is a 1.x peaks-loop
|
|
1509
|
-
* project, this shells out to the installed `peaks`
|
|
1510
|
-
* binary to run the umbrella `peaks upgrade --to 2.0 --auto`.
|
|
1511
|
-
*
|
|
1512
|
-
* Per the "minimal-user-operation" tenet, the user should
|
|
1513
|
-
* never have to run a second command after `npm i -g`. The
|
|
1514
|
-
* upgrade CLI (if installed) is at the resolved `peaks`
|
|
1515
|
-
* binary path; if not, the user gets a hint to run it
|
|
1516
|
-
* manually.
|
|
1517
|
-
*
|
|
1518
|
-
* The auto-upgrade is opt-out via:
|
|
1519
|
-
* PEAKS_SKIP_AUTO_UPGRADE=1
|
|
1520
|
-
* (so a CI box that installs 2.0 but never wants the
|
|
1521
|
-
* project-level migration can suppress the auto-step).
|
|
1522
|
-
*/
|
|
1523
|
-
export async function autoUpgrade1xProjectIfPresent(options = {}) {
|
|
1524
|
-
if (process.env.PEAKS_SKIP_AUTO_UPGRADE === '1') {
|
|
1525
|
-
return { ran: false, reason: 'PEAKS_SKIP_AUTO_UPGRADE=1' };
|
|
1526
|
-
}
|
|
1527
|
-
const state = detect1xProjectState(options.cwd ?? process.cwd());
|
|
1528
|
-
if (!state.isOneX) {
|
|
1529
|
-
return { ran: false, reason: 'no 1.x project state detected' };
|
|
1530
|
-
}
|
|
1531
|
-
if (state.projectRoot === null) {
|
|
1532
|
-
return { ran: false, reason: 'cwd is not a peaks project (no .peaks/_runtime/)' };
|
|
1533
|
-
}
|
|
1534
|
-
// The peaks binary should be on PATH after `npm i -g`.
|
|
1535
|
-
// We shell out via spawnSync (synchronous; the postinstall
|
|
1536
|
-
// is already synchronous and the umbrella is fast).
|
|
1537
|
-
try {
|
|
1538
|
-
const result = spawnSync(
|
|
1539
|
-
'peaks',
|
|
1540
|
-
['upgrade', '--to', '2.0', '--auto', '--project', state.projectRoot],
|
|
1541
|
-
{
|
|
1542
|
-
encoding: 'utf8',
|
|
1543
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1544
|
-
timeout: 120_000,
|
|
1545
|
-
windowsHide: true
|
|
1546
|
-
}
|
|
1547
|
-
);
|
|
1548
|
-
return {
|
|
1549
|
-
ran: true,
|
|
1550
|
-
reason: 'auto-upgrade dispatched',
|
|
1551
|
-
signals: state.signals,
|
|
1552
|
-
projectRoot: state.projectRoot,
|
|
1553
|
-
exitCode: result.status,
|
|
1554
|
-
stdout: result.stdout ?? '',
|
|
1555
|
-
stderr: result.stderr ?? ''
|
|
1556
|
-
};
|
|
1557
|
-
} catch (err) {
|
|
1558
|
-
return {
|
|
1559
|
-
ran: true,
|
|
1560
|
-
reason: 'auto-upgrade dispatched but failed',
|
|
1561
|
-
signals: state.signals,
|
|
1562
|
-
projectRoot: state.projectRoot,
|
|
1563
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
1411
|
if (
|
|
1569
1412
|
process.argv[1] !== undefined &&
|
|
1570
1413
|
import.meta.url === pathToFileURL(resolve(process.argv[1])).href
|
|
@@ -1661,26 +1504,6 @@ if (
|
|
|
1661
1504
|
if (userConfigResult.created) {
|
|
1662
1505
|
process.stdout.write('Peaks user config created: ~/.peaks/config.json\n');
|
|
1663
1506
|
}
|
|
1664
|
-
|
|
1665
|
-
// 2.0 postinstall: auto-detect 1.x project state in cwd
|
|
1666
|
-
// and dispatch the upgrade umbrella. This makes the
|
|
1667
|
-
// user's `npm i -g peaks-loop@2.0` truly one-key.
|
|
1668
|
-
if (process.env.PEAKS_SKIP_AUTO_UPGRADE !== '1') {
|
|
1669
|
-
// Fire-and-forget; the upgrade is async by design so
|
|
1670
|
-
// the npm install output isn't blocked. We print a
|
|
1671
|
-
// one-line hint so the user knows the auto-step
|
|
1672
|
-
// happened.
|
|
1673
|
-
autoUpgrade1xProjectIfPresent().then((result) => {
|
|
1674
|
-
if (result.ran) {
|
|
1675
|
-
process.stdout.write(
|
|
1676
|
-
`\n✓ Detected 1.x peaks-loop project at ${result.projectRoot}\n` +
|
|
1677
|
-
` → auto-upgraded to 2.0 (${result.signals?.length ?? 0} signals resolved)\n` +
|
|
1678
|
-
` Run \`peaks audit red-lines --project .\` to verify.\n`
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
// When !result.ran we say nothing — silent on success.
|
|
1682
|
-
});
|
|
1683
|
-
}
|
|
1684
1507
|
if (userConfigResult.updated) {
|
|
1685
1508
|
process.stdout.write('Peaks user config updated: ~/.peaks/config.json\n');
|
|
1686
1509
|
}
|
|
@@ -4,10 +4,12 @@ QA reads the RD handoff's YAML frontmatter **before** reading the body prose. Th
|
|
|
4
4
|
|
|
5
5
|
## Required frontmatter fields (mirror RD writer)
|
|
6
6
|
|
|
7
|
-
`requestId`, `scope`, `files`, `decisions[]`, `risks[]`, `nextActions[]`, `gateEvidence`, `schemaVersion: '
|
|
7
|
+
`requestId`, `scope`, `files`, `decisions[]`, `risks[]`, `nextActions[]`, `gateEvidence`, `schemaVersion: 2` (written plain; `'2.0'` is not a valid value). Field schema: `../../peaks-rd/references/writing-handoff-frontmatter.md`.
|
|
8
8
|
|
|
9
9
|
## Mechanical cross-checks (run before body read)
|
|
10
10
|
|
|
11
|
+
Checks 1–3 and 5 are conditional on the capsule DECLARING the field: `scope`, `files`, `decisions[]`, `risks[]` and `nextActions[]` are authored by the writer, not derived, and the serializer writes no line for a field nobody filled in. An absent field is therefore NOT a pass — it means the writer made no claim to check, which is recorded as `not declared` rather than green. Only check 4 has a machine producer (`peaks prd handoff init` derives it from the request type).
|
|
12
|
+
|
|
11
13
|
1. **Decisions ↔ tests** — every `decisions[].id` (e.g. `D1`) implies a test under `tests/unit/`. Grep `tests/unit/` for the decision id (or its summary keywords). Missing test → Gate B2 partial.
|
|
12
14
|
2. **Risks ↔ security tests** — every `risks[].id` with `description` mentioning auth / input / boundary / path / external / crypto / payment MUST have a matching case under `tests/unit/security/`. Missing → Gate A3 partial.
|
|
13
15
|
3. **Files ↔ diff** — `files[]` must equal `git diff --name-only` against the parent commit. Any file in the diff not in `files[]` (or vice versa) → Gate B8 fail (out-of-scope or missing scope).
|
|
@@ -50,7 +50,7 @@ Note: sub-agent 1 (code-reviewer) and sub-agent 3 (karpathy-reviewer) write to `
|
|
|
50
50
|
- Required for Gate B3.
|
|
51
51
|
- **v2.11.0 Tier 7 (Group D) + 2026-09-09-ecc-dynamic:** the code-reviewer dispatch goes through the **ECC bridge** (`src/services/code-review/ecc-bridge.ts`). Fallback order is **native plugin → cache-backed generic agent → inline**:
|
|
52
52
|
1. **Native plugin** (`detectEcc` state `ready`): invoke the Agent tool with `subagent_type: "ecc:code-reviewer"` (plugin `ecc` + agent `code-reviewer`; see `DEFAULT_NATIVE_ECC_AGENT_ID`); it returns the structured envelope `{ passed, violations[], gateAction }`.
|
|
53
|
-
2. **Cache-backed generic agent** (`detectEcc` state `ready-via-cache` — plugin or its review agent absent, but a materialized ECC agent exists under `~/.peaks/agents/ecc/`): resolve the agent's REAL name first — `resolveMaterializedAgentName(['code-reviewer', 'code-review'])` from `peaks-loop-mut` (upstream ships `code-reviewer.md`; a hardcoded `code-review.md` never resolves). Read `~/.peaks/agents/ecc/<resolved>.md`, pass the name through as `detectEcc({ ..., cacheAgentName: resolved })`, build the prompt with `buildCacheBackedEccPrompt({ rid, instructions, diff })` (agent body + diff + `ECC_OUTPUT_CONTRACT`), dispatch a **generic** sub-agent, and validate its reply with `isEccEnvelope`. No ECC plugin required. If
|
|
53
|
+
2. **Cache-backed generic agent** (`detectEcc` state `ready-via-cache` — plugin or its review agent absent, but a materialized ECC agent exists under `~/.peaks/agents/ecc/`): resolve the agent's REAL name first — `resolveMaterializedAgentName(['code-reviewer', 'code-review'])` from `peaks-loop-mut` (upstream ships `code-reviewer.md`; a hardcoded `code-review.md` never resolves). Read `~/.peaks/agents/ecc/<resolved>.md`, pass the name through as `detectEcc({ ..., cacheAgentName: resolved })`, build the prompt with `buildCacheBackedEccPrompt({ rid, instructions, diff })` (agent body + diff + `ECC_OUTPUT_CONTRACT`), dispatch a **generic** sub-agent, and validate its reply with `isEccEnvelope`. No ECC plugin required. If no agent is materialized, run `peaks ecc install` first — it copies the agents out of the bundled `ecc-universal` dependency (local, no network); if that refuses with `ECC_PACKAGE_MISSING`, the dependency tree is broken and the only remedy is reinstalling peaks-loop, so degrade to inline review rather than retrying.
|
|
54
54
|
3. **Inline** (`detectEcc` states `plugin-missing` / `agent-missing` / `dispatch-failed` / `envelope-malformed`): fall back to inline review — TXT note `code-review-ecc-degraded-to-inline`.
|
|
55
55
|
|
|
56
56
|
In all dispatchable cases the envelope is rendered by the SAME bridge adapter (`adaptEccEnvelopeToRdCodeReview`) into the canonical `rd/code-review-<rid>.md` markdown shape that Gate B3 reads (`mustContain: ['## Findings', 'CRITICAL']`). The materialized copy lives under `~/.peaks/agents/ecc/` — peaks-loop NEVER writes into `~/.claude/`.
|
|
@@ -45,8 +45,9 @@ end of implementation, RD fires 3 sub-agents in parallel via
|
|
|
45
45
|
`detectEcc({ ..., cacheAgentName: resolved })`, compose the prompt with
|
|
46
46
|
`buildCacheBackedEccPrompt({ rid, instructions, diff })`,
|
|
47
47
|
dispatch a **generic** sub-agent, and validate the reply with `isEccEnvelope`.
|
|
48
|
-
If
|
|
49
|
-
|
|
48
|
+
If nothing is materialized, run `peaks ecc install` first — it copies the
|
|
49
|
+
agents from the bundled `ecc-universal` dependency (local, no network);
|
|
50
|
+
`ECC_PACKAGE_MISSING` → reinstall peaks-loop, and degrade to inline.
|
|
50
51
|
- Any other state (plugin-missing / agent-missing / dispatch-failed /
|
|
51
52
|
envelope-malformed) → inline review; the `code-review-ecc-degraded-to-inline`
|
|
52
53
|
TXT note records the fallback.
|
|
@@ -13,6 +13,18 @@ Every RD handoff artifact carries a **YAML frontmatter block** so peaks-qa (and
|
|
|
13
13
|
```yaml
|
|
14
14
|
---
|
|
15
15
|
requestId: 2026-06-25-slice-topology-multipass
|
|
16
|
+
sessionId: 2026-06-25-session-ee2aba
|
|
17
|
+
schemaVersion: 2
|
|
18
|
+
sha256: <64 lowercase hex of the body>
|
|
19
|
+
handoffHash: <the same 64 hex, no prefix>
|
|
20
|
+
writtenAt: 2026-06-25T03:05:30.000Z
|
|
21
|
+
goals:
|
|
22
|
+
- G1
|
|
23
|
+
- G2
|
|
24
|
+
acceptanceCriteria:
|
|
25
|
+
- AC-1
|
|
26
|
+
preservedBehavior:
|
|
27
|
+
- P1
|
|
16
28
|
scope:
|
|
17
29
|
- src/services/slice/schema-router.ts
|
|
18
30
|
- src/services/audit/audit-goal-service.ts
|
|
@@ -20,7 +32,6 @@ files:
|
|
|
20
32
|
- src/services/slice/schema-router.ts
|
|
21
33
|
- src/services/audit/audit-goal-service.ts
|
|
22
34
|
handoffPath: .peaks/_runtime/<sessionId>/prd/handoff-<rid>.md
|
|
23
|
-
handoffHash: sha256:<64 hex chars>
|
|
24
35
|
decisions:
|
|
25
36
|
- id: D1
|
|
26
37
|
summary: "Route v1/v2 envelopes via SchemaRouter instead of branching in prompts"
|
|
@@ -38,7 +49,6 @@ gateEvidence:
|
|
|
38
49
|
codeReview: .peaks/_runtime/<sessionId>/rd/code-review-<rid>.md
|
|
39
50
|
securityReview: .peaks/_runtime/<sessionId>/audit/security-<rid>.md
|
|
40
51
|
perfBaseline: .peaks/_runtime/<sessionId>/audit/perf-<rid>.md
|
|
41
|
-
schemaVersion: '2.0'
|
|
42
52
|
---
|
|
43
53
|
```
|
|
44
54
|
|
|
@@ -46,7 +56,11 @@ schemaVersion: '2.0'
|
|
|
46
56
|
## Field rules
|
|
47
57
|
|
|
48
58
|
- `requestId` — kebab-case; matches the PRD `requestId`.
|
|
49
|
-
- `
|
|
59
|
+
- `sessionId` — the session the capsule belongs to. **Required by `readHandoff`.**
|
|
60
|
+
- `writtenAt` — ISO 8601 at write time. **Required by `readHandoff`.**
|
|
61
|
+
- `goals` / `acceptanceCriteria` / `preservedBehavior` — the PRD IDs this slice binds to (`['G1','G2']`, `['AC-1']`, `['P1']`). Arrays, empty allowed, and **required by `readHandoff`** — omitting the line is a shape failure, not a declaration of nothing.
|
|
62
|
+
- `sha256` and `handoffHash` — the same 64 hex digits, twice, on purpose: `sha256` is the PLAIN field the gate and both independent audit loaders match at line start, `handoffHash` is the quoted field `readHandoff` reads. A capsule whose two values differ verifies as tampering.
|
|
63
|
+
- `scope` / `files` — repo-relative paths (`src/...`), sorted. Authored by the writer; the serializer writes them only when present (`handoff-frontmatter.ts`), so an absent field is no line rather than an empty claim.
|
|
50
64
|
- `handoffPath` + `handoffHash` — the immutable PRD handoff location and its sha256 hash. Sub-agents MUST verify `handoffHash` matches the file's actual sha256 before reading; mismatch → return `blocked`.
|
|
51
65
|
- `decisions[]` — every decision an LLM made that an implementer could question. `id` is local; `summary` is one line; `rationale` is ≤ 2 sentences.
|
|
52
66
|
- `risks[]` — same shape; `mitigation` is required. A risk without a mitigation is a red line (gate blocked).
|
|
@@ -56,16 +70,24 @@ schemaVersion: '2.0'
|
|
|
56
70
|
- `config` → **2 keys**: `projectScan`, `securityReview`. `CONFIG_TABLE['rd:qa-handoff']` is the security review alone, so a config slice has **no** `prdHandoff`; and its security evidence is the genuinely ridless `rd/security-review.md`, not `audit/security-<rid>.md`.
|
|
57
71
|
- `docs` / `chore` → **no `gateEvidence` block at all**. Those types have no `rd:qa-handoff` row: there is no evidence to declare and nothing for Gate C to check, so the block is omitted rather than filled with an unverified statement. Such a capsule is byte-identical to a pre-B2 one.
|
|
58
72
|
**The Gate C failure rule is scoped to the types above** — where that gate runs: a declared path that does not exist fails `peaks request transition --state qa-handoff` (`PREREQUISITES_MISSING`), naming the key. Two shapes are deliberately NOT failures: a capsule for a type with no `rd:qa-handoff` row (the check does not run), and an **empty** declaration (`gateEvidence: {}`) — a weak claim, not a false one, since the type's artifacts remain enforced by the table itself, so an empty map cannot open the gate. When the request type cannot be resolved at all, the block is omitted rather than half-declared.
|
|
59
|
-
- `schemaVersion:
|
|
73
|
+
- `schemaVersion: 2` — pinned, and written PLAIN (no quotes). The constant is `HANDOFF_SCHEMA_VERSION` in `src/services/prd/handoff-frontmatter-shape.ts` and its value is the string `'2'`; the serializer emits it unquoted, because `AUDIT_REQUIRES_HANDOFF` matches the substring `schemaVersion: 2` and a quoted `'2'` would not contain it. `readHandoff` accepts `'2'` or the bare number `2` and nothing else — **`'2.0'` is not a valid value here**, and this document said it was until it was measured against the serializer. Bump only when the field set changes.
|
|
60
74
|
|
|
61
|
-
> **v2.11.0 Group A change:** `schemaVersion`
|
|
75
|
+
> **v2.11.0 Group A change:** `schemaVersion` moved to version 2 (the shape that added `sha256` and AC/goal IDs). The consumers listed under §Validation anchor on it, so a file carrying anything else is rejected at read time.
|
|
62
76
|
|
|
63
77
|
## Validation
|
|
64
78
|
|
|
65
|
-
The frontmatter MUST parse as valid YAML. Two automated consumers read it, and both anchor on `schemaVersion` + `sha256`: the `AUDIT_REQUIRES_HANDOFF` prereq in `src/services/artifacts/artifact-prerequisites.ts` and the independent audit services under `src/services/audit-independent/`. The serializer that writes it is `src/services/prd/handoff-frontmatter.ts`.
|
|
79
|
+
The frontmatter MUST parse as valid YAML. Two automated consumers read it, and both anchor on `schemaVersion` + `sha256`: the `AUDIT_REQUIRES_HANDOFF` prereq in `src/services/artifacts/artifact-prerequisites.ts` and the independent audit services under `src/services/audit-independent/`. The serializer that writes it is `src/services/prd/handoff-frontmatter.ts`.
|
|
80
|
+
|
|
81
|
+
What is pinned now, and what is not:
|
|
82
|
+
|
|
83
|
+
- **Pinned** by `tests/unit/prd/handoff-frontmatter-doc-contract.test.ts`: the `gateEvidence` key set in the example above against `GATE_EVIDENCE_KEYS`, each request type's declared keys against `deriveGateEvidence`, and `schemaVersion` against what the serializer actually emits. If this document drifts from the code it describes, that test goes red.
|
|
84
|
+
- **NOT pinned — a recorded divergence**: `scope`, `files`, `decisions[]`, `risks[]` and `nextActions[]` are listed as required fields by this file and by `peaks-qa`'s reader doc, and the serializer emits none of them (it never has). `readHandoff` additionally REQUIRES `sessionId`, `writtenAt`, `goals`, `acceptanceCriteria` and `preservedBehavior`, which neither document lists. So a capsule hand-authored from this example is rejected, and four of peaks-qa's five mechanical cross-checks read fields that are not on disk. The contract test pins the gap in both directions so it cannot be quietly re-forgotten; closing it is an open decision (give the frontmatter those fields, or re-base the checks on inputs that exist).
|
|
66
85
|
|
|
67
86
|
```bash
|
|
68
|
-
#
|
|
87
|
+
# `peaks prd handoff init` prints the derived gateEvidence map (dry-run and
|
|
88
|
+
# applied), so the key set for a given slice is readable without opening the
|
|
89
|
+
# serializer. It does not validate an arbitrary handoff; that is
|
|
90
|
+
# `peaks prd handoff verify`.
|
|
69
91
|
```
|
|
70
92
|
|
|
71
93
|
## Body (prose, free-form)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
>
|
|
5
5
|
> **2026-09-12 (`rd-routing-by-integration-mode`):** this file is now the routing home for all three integration modes, not just `prd-only`. `### Integration-mode routing (RD)` was added and the boolean-era text in `### Mode determination` was retargeted onto `.integrationMode`; the mock table and placeholder layout below belong to `prd-only`.
|
|
6
6
|
|
|
7
|
-
`peaks scan archetype --json` classifies every project into exactly one of three integration modes (`.integrationMode`). Code and RD both branch on that value — the `frontendOnly` boolean is retained for back-compat only (the swarm plan still reads it) and is no longer a routing decision.
|
|
7
|
+
`peaks scan archetype --json` classifies every project into exactly one of three integration modes (`.integrationMode`). Code and RD both branch on that value — the `frontendOnly` boolean is retained for back-compat only (the swarm plan still reads it) and is no longer a routing decision. Both answers, and the `archetype`, come out of ONE backend predicate (`hasBackendEvidence` in `src/services/scan/archetype-service.ts`); before it was one predicate the three decision functions each re-spelled the same check inline, and a signal added for one of them left the others reading a different project.
|
|
8
8
|
|
|
9
9
|
### Mode determination (deterministic — CLI is the source of truth)
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@ Read `.integrationMode` and `.integrationModeReason` from the `peaks scan archet
|
|
|
12
12
|
|
|
13
13
|
| `.integrationMode` | `.integrationModeReason` | What is physically present |
|
|
14
14
|
|---|---|---|
|
|
15
|
-
| `full-stack` | `backend-detected` | a backend framework, Next API routes
|
|
15
|
+
| `full-stack` | `backend-detected` | a backend anywhere in this repo: a root framework, Next API routes **or server actions**, a backend dir, a workspace package's own manifest (`apps/gateway: express`), or a non-Node service (`go.mod`, `pom.xml`, `services/x/requirements.txt` naming a web framework) |
|
|
16
16
|
| `prd-plus-interface-doc` | `interface-doc-present` | an OpenAPI / proto document, and no backend here |
|
|
17
17
|
| `prd-only` | `no-backend-no-interface-doc` | neither |
|
|
18
18
|
|
|
@@ -49,10 +49,6 @@ Run `peaks workspace init` + `peaks skill presence:set peaks-code` BEFORE any an
|
|
|
49
49
|
|
|
50
50
|
After Step 0, run the resume-detection probe; surface via `AskUserQuestion` if a slice is in flight. **v3.1.2 resume rule:** if `.peaks/_runtime/<sessionId>/job/<jid>/progress.json` exists, read FIRST and surface `Next: slice #N of M`. **v2.11.0 D7 override:** if user just `/compact`ed, run `peaks code post-compact-detect --project <repo> --json` FIRST; `shouldAutoResume: true` skips AskUserQuestion. → `resume-detection.md`.
|
|
51
51
|
|
|
52
|
-
### Peaks-Loop Step 0.55: 1.x → 2.0 detection (BLOCKING on first invocation per session, when the project is not on a 2.0 layout)
|
|
53
|
-
|
|
54
|
-
After Step 0.7 returns "fresh", run `peaks upgrade --detect-1x --project <root> --json`. If `isOneX: true`, surface `AskUserQuestion`. → `step-0-55-1x-detection.md`.
|
|
55
|
-
|
|
56
52
|
### Peaks-Loop Step 0.8 — Job 启动 (BLOCKING on LLM judgement — v3.1.1 patch + v3.1.2 mechanical gates)
|
|
57
53
|
|
|
58
54
|
> **CLI reality check (D-001 sediment, 2026-07-09):** The CLI surface has changed since this section was last verified. The actual command is **`peaks job init --job-id <jid> --slice-list <list> --main-loop-strategy <single|rotating> [--parallelism-hint <serial|llm-decides>] [--exit-policy <strict|best-effort>] [--project <repo>]`**. The legacy `peaks code detect-job --is-job/--suggested-job-id/--confidence` form described below is **no longer present** in 4.0.0-beta.6. If you find `--is-job` rejected, fall back to `peaks job init`.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* peaks upgrade * CLI surface — Slice: 1.x → 2.0 umbrella +
|
|
3
|
-
* Slice 3: --detect-1x flag.
|
|
4
|
-
* Fix-4: reject dangerous probe names passed positionally and expose
|
|
5
|
-
* --gitignore-migrate as a read-only probe.
|
|
6
|
-
*
|
|
7
|
-
* Per the "one-key completion" + "minimal-user-operation" tenets
|
|
8
|
-
* (2026-06-11), the user's typical upgrade path is
|
|
9
|
-
* `npm i -g peaks-loop@2.0` (the postinstall does the upgrade).
|
|
10
|
-
*
|
|
11
|
-
* The `peaks upgrade --to 2.0` CLI is the manual fallback for
|
|
12
|
-
* when the postinstall is skipped (e.g. CI uses
|
|
13
|
-
* `--ignore-scripts`). The umbrella orchestrates 7 sub-commands:
|
|
14
|
-
* config-migrate / standards-migrate / memory-extract /
|
|
15
|
-
* hooks-install / skill-sync / audit-verify + write-upgrade-record.
|
|
16
|
-
*
|
|
17
|
-
* The `--detect-1x` flag (added in slice 3) is a read-only
|
|
18
|
-
* probe that the peaks-code skill calls to gate the
|
|
19
|
-
* AskUserQuestion that prompts the 1.x → 2.0 upgrade. The
|
|
20
|
-
* probe returns the JSON envelope from the
|
|
21
|
-
* 1x-detector-service; it does NOT modify any files.
|
|
22
|
-
*/
|
|
23
|
-
import type { Command } from 'commander';
|
|
24
|
-
import { type ProgramIO } from '../cli-helpers.js';
|
|
25
|
-
export declare function registerUpgradeCommands(program: Command, io: ProgramIO): void;
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { runUpgrade } from '../../services/upgrade/upgrade-service.js';
|
|
2
|
-
import { detect1xProjectState } from '../../services/upgrade/1x-detector-service.js';
|
|
3
|
-
import { migrateGitignoreFile } from '../../services/upgrade/gitignore-migrate-service.js';
|
|
4
|
-
import { initWorkspace } from '../../services/workspace/workspace-service.js';
|
|
5
|
-
import { ensureSessionWithRotation } from '../../services/session/session-manager.js';
|
|
6
|
-
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
7
|
-
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
8
|
-
import { fail, ok } from 'peaks-loop-shared/result';
|
|
9
|
-
const REJECTED_POSITIONALS = {
|
|
10
|
-
'1x-detector': '--detect-1x',
|
|
11
|
-
'gitignore-migrate': '--gitignore-migrate'
|
|
12
|
-
};
|
|
13
|
-
function isRejectedPositional(value) {
|
|
14
|
-
return value === '1x-detector' || value === 'gitignore-migrate';
|
|
15
|
-
}
|
|
16
|
-
export function registerUpgradeCommands(program, io) {
|
|
17
|
-
addJsonOption(program
|
|
18
|
-
.command('upgrade', { hidden: true })
|
|
19
|
-
.description('Upgrade a peaks-loop 1.x project to 2.0. Umbrella that orquestrates 7 sub-commands: config-migrate / standards-migrate / memory-extract / hooks-install / skill-sync / audit-verify + write-upgrade-record. Per the "one-key completion" tenet, prefer letting `npm i -g peaks-loop@2.0` postinstall run this for you. Use `--detect-1x` for a read-only probe (no file writes) that the peaks-code skill uses to gate the 1.x → 2.0 AskUserQuestion.')
|
|
20
|
-
.option('--to <version>', 'target version (only "2.0" supported)', '2.0')
|
|
21
|
-
.option('--project <path>', 'project root to upgrade (default: cwd)')
|
|
22
|
-
.option('--auto', 'non-interactive: accept soft-fail on any sub-step (used by the postinstall hook)')
|
|
23
|
-
.option('--detect-1x', 'read-only probe: returns the 1.x state as JSON (no file writes); consumed by peaks-code Step 0.55 to gate the AskUserQuestion')
|
|
24
|
-
.option('--gitignore-migrate', 'read-only probe: reports whether .gitignore needs the 1.x to 2.0 migration (no file writes)')
|
|
25
|
-
.option('--apply-init', 'slice 4 (slice 2026-06-13-selfheal-claude-settings-template): run initWorkspace so the drift-driven self-heal fires on the consumer-project .claude/settings.local.json and the offline .peaks/.claude-settings-template.json. Idempotent. Use after a peaks-loop version bump if you do not otherwise re-run init. Mutually exclusive with --detect-1x.')).action(async (options, command) => {
|
|
26
|
-
const projectRoot = options.project ?? process.cwd();
|
|
27
|
-
const positional = command.args[0];
|
|
28
|
-
if (isRejectedPositional(positional)) {
|
|
29
|
-
const replacementFlag = REJECTED_POSITIONALS[positional];
|
|
30
|
-
const message = `Positional \`${positional}\` is not supported because it could trigger the destructive upgrade umbrella. The documented read-only probe is \`${replacementFlag}\`.`;
|
|
31
|
-
printResult(io, fail('upgrade', 'UPGRADE_POSITIONAL_REJECTED', message, {}, [
|
|
32
|
-
`The LLM can coordinate the documented ${replacementFlag} read-only probe instead.`
|
|
33
|
-
]), options.json);
|
|
34
|
-
process.exitCode = 1;
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
// Branch 1: --detect-1x (read-only probe)
|
|
38
|
-
if (options.detect1x === true) {
|
|
39
|
-
try {
|
|
40
|
-
const state = detect1xProjectState(projectRoot);
|
|
41
|
-
const nextActions = [];
|
|
42
|
-
if (state.isOneX) {
|
|
43
|
-
nextActions.push(`Detected 1.x state. peaks-code Step 0.55 should present an AskUserQuestion to invoke \`peaks upgrade --to 2.0 --auto --project ${state.projectRoot ?? projectRoot}\`.`);
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
nextActions.push('No 1.x state detected. Proceed with the standing 2.0 layout.');
|
|
47
|
-
}
|
|
48
|
-
const envelope = ok('upgrade.detect-1x', state, [], nextActions);
|
|
49
|
-
printResult(io, envelope, options.json);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
const message = getErrorMessage(error);
|
|
53
|
-
printResult(io, fail('upgrade.detect-1x', 'DETECT_1X_FAILED', message, { isOneX: false, signals: [], projectRoot: null, configPath: null }, [message]), options.json);
|
|
54
|
-
process.exitCode = 1;
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
// Branch 2: --gitignore-migrate (read-only probe)
|
|
59
|
-
if (options.gitignoreMigrate === true) {
|
|
60
|
-
try {
|
|
61
|
-
const result = migrateGitignoreFile({ projectRoot, apply: false });
|
|
62
|
-
const envelope = ok('upgrade.gitignore-migrate', result, [], result.changed
|
|
63
|
-
? ['The upgrade umbrella can coordinate the required .gitignore migration.']
|
|
64
|
-
: ['No .gitignore migration is required.']);
|
|
65
|
-
printResult(io, envelope, options.json);
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
const message = getErrorMessage(error);
|
|
69
|
-
printResult(io, fail('upgrade.gitignore-migrate', 'GITIGNORE_MIGRATE_FAILED', message, { appliedWrite: false }, [message]), options.json);
|
|
70
|
-
process.exitCode = 1;
|
|
71
|
-
}
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Branch 3: --apply-init (slice 4 — slice 2026-06-13-selfheal-claude-settings-template).
|
|
75
|
-
//
|
|
76
|
-
// The drift-driven self-heal inside initWorkspace only fires when
|
|
77
|
-
// the user invokes init. After a peaks-loop version bump, users who
|
|
78
|
-
// never re-run init are stuck with stale templates until they do.
|
|
79
|
-
// This flag is the post-bump escape hatch: it triggers init for them.
|
|
80
|
-
//
|
|
81
|
-
// We do NOT pass --session-id (the CLI auto-generates / reuses an
|
|
82
|
-
// existing binding). We do NOT pass --no-claude-hooks (the goal is to
|
|
83
|
-
// bring the project to the current peaks-loop baseline, including the
|
|
84
|
-
// consumer-project hook).
|
|
85
|
-
if (options.applyInit === true) {
|
|
86
|
-
try {
|
|
87
|
-
const canonicalRoot = resolveCanonicalProjectRoot(projectRoot);
|
|
88
|
-
// Match the workspace-init CLI's pattern: resolve the session id
|
|
89
|
-
// (auto-generate / reuse binding / rotate on outer-mismatch)
|
|
90
|
-
// BEFORE calling initWorkspace. initWorkspace itself validates
|
|
91
|
-
// the session id and does NOT auto-generate, so we have to do
|
|
92
|
-
// the rotation-aware resolution here.
|
|
93
|
-
const sessionResolution = await ensureSessionWithRotation(canonicalRoot, {
|
|
94
|
-
skipRotateOnOuterMismatch: false
|
|
95
|
-
});
|
|
96
|
-
const result = await initWorkspace({
|
|
97
|
-
projectRoot: canonicalRoot,
|
|
98
|
-
sessionId: sessionResolution.sessionId,
|
|
99
|
-
allowSessionRebind: false
|
|
100
|
-
});
|
|
101
|
-
const nextActions = [];
|
|
102
|
-
// Surface the same self-heal messaging that workspace-init uses.
|
|
103
|
-
if (result.claudeSettings.offlineTemplate.action === 'refreshed') {
|
|
104
|
-
nextActions.push(`Self-healed .peaks/.claude-settings-template.json (action: refreshed) — ` +
|
|
105
|
-
'the offline recovery anchor now matches the current peaks-loop template.');
|
|
106
|
-
nextActions.push('⚠️ If you had manually edited .peaks/.claude-settings-template.json, ' +
|
|
107
|
-
'those edits have been overwritten by the self-heal.');
|
|
108
|
-
}
|
|
109
|
-
else if (result.claudeSettings.offlineTemplate.action === 'written') {
|
|
110
|
-
nextActions.push('Wrote .peaks/.claude-settings-template.json (action: written) — ' +
|
|
111
|
-
'the offline recovery anchor is now in place.');
|
|
112
|
-
}
|
|
113
|
-
if (result.claudeSettings.action === 'refreshed') {
|
|
114
|
-
nextActions.push(`Refreshed .claude/settings.local.json (action: refreshed) — ` +
|
|
115
|
-
'the consumer-project hook now matches the current peaks-loop template. ' +
|
|
116
|
-
'Restart Claude Code so the hooks take effect.');
|
|
117
|
-
}
|
|
118
|
-
else if (result.claudeSettings.action === 'written') {
|
|
119
|
-
nextActions.push('Wrote .claude/settings.local.json (action: written) — ' +
|
|
120
|
-
'the [Fact-Forcing Gate] bypass is now in effect. Restart Claude Code so the hooks take effect.');
|
|
121
|
-
}
|
|
122
|
-
const envelope = ok('upgrade.apply-init', result, [], nextActions);
|
|
123
|
-
printResult(io, envelope, options.json);
|
|
124
|
-
}
|
|
125
|
-
catch (error) {
|
|
126
|
-
const message = getErrorMessage(error);
|
|
127
|
-
printResult(io, fail('upgrade.apply-init', 'APPLY_INIT_FAILED', message, { applied: false }, [message]), options.json);
|
|
128
|
-
process.exitCode = 1;
|
|
129
|
-
}
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
// Branch 4: the umbrella (existing behavior)
|
|
133
|
-
try {
|
|
134
|
-
const result = runUpgrade({ projectRoot, auto: options.auto === true });
|
|
135
|
-
const nextActions = [...result.nextActions];
|
|
136
|
-
if (result.failedCount > 0) {
|
|
137
|
-
nextActions.unshift(`${result.failedCount} sub-step(s) failed. Re-run \`peaks upgrade --to 2.0\` to retry.`);
|
|
138
|
-
}
|
|
139
|
-
if (result.upgradeRecordPath !== null) {
|
|
140
|
-
nextActions.push(`Upgrade record written: ${result.upgradeRecordPath}`);
|
|
141
|
-
}
|
|
142
|
-
const envelope = ok('upgrade', result, [...result.warnings], nextActions);
|
|
143
|
-
printResult(io, envelope, options.json);
|
|
144
|
-
if (result.failedCount > 0) {
|
|
145
|
-
process.exitCode = 1;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
const message = getErrorMessage(error);
|
|
150
|
-
printResult(io, fail('upgrade', 'UPGRADE_FAILED', message, { applied: false }, [message]), options.json);
|
|
151
|
-
process.exitCode = 1;
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
}
|