peaks-loop 4.0.45 → 4.0.47

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
  5. package/dist/cli/commands/code-runtime-commands.js +128 -34
  6. package/dist/cli/commands/compact-command.js +129 -1
  7. package/dist/cli/commands/container-commands.js +3 -3
  8. package/dist/cli/commands/core/skill-command.js +45 -10
  9. package/dist/cli/commands/cron-commands.js +2 -1
  10. package/dist/cli/commands/e2e-verify.js +3 -3
  11. package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
  12. package/dist/cli/commands/hooks-commands.js +10 -1
  13. package/dist/cli/commands/loop-commands.js +1 -0
  14. package/dist/cli/commands/playwright-commands.js +2 -1
  15. package/dist/cli/commands/reinject-command.d.ts +72 -0
  16. package/dist/cli/commands/reinject-command.js +174 -0
  17. package/dist/cli/commands/request-commands.js +6 -3
  18. package/dist/cli/commands/shadcn-commands.js +1 -0
  19. package/dist/cli/commands/test-commands.js +2 -1
  20. package/dist/cli/commands/vm-commands.js +7 -7
  21. package/dist/cli/commands/workspace/init-command.js +24 -2
  22. package/dist/cli/commands/worktree-lease-commands.js +4 -4
  23. package/dist/cli/index.js +49 -2
  24. package/dist/cli/program.js +5 -0
  25. package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
  26. package/dist/services/adapter/adapter-registry.js +1 -1
  27. package/dist/services/artifacts/artifact-service.js +1 -1
  28. package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
  29. package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
  30. package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
  31. package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
  32. package/dist/services/code/auto-compact-lifecycle.d.ts +50 -4
  33. package/dist/services/code/auto-compact-lifecycle.js +52 -10
  34. package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
  35. package/dist/services/code/auto-compact-orchestrator.js +173 -43
  36. package/dist/services/code/orchestrator-can-do.d.ts +4 -2
  37. package/dist/services/code/orchestrator-can-do.js +37 -5
  38. package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
  39. package/dist/services/codegraph/codegraph-process-runner.js +3 -2
  40. package/dist/services/compact/request-transition-hook.js +5 -2
  41. package/dist/services/compact-history/compact-history-service.d.ts +61 -0
  42. package/dist/services/compact-history/compact-history-service.js +49 -0
  43. package/dist/services/compact-statusline/compact-lifecycle-store.d.ts +11 -2
  44. package/dist/services/compact-statusline/compact-lifecycle-store.js +19 -1
  45. package/dist/services/compact-statusline/compact-statusline-service.d.ts +1 -1
  46. package/dist/services/compact-statusline/compact-statusline-service.js +22 -0
  47. package/dist/services/config/config-safety.d.ts +52 -0
  48. package/dist/services/config/config-safety.js +75 -1
  49. package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
  50. package/dist/services/context/auto-compact-dispatcher.js +113 -40
  51. package/dist/services/context/auto-compact-reader.d.ts +68 -28
  52. package/dist/services/context/auto-compact-reader.js +155 -1
  53. package/dist/services/context/auto-compact-types.d.ts +89 -12
  54. package/dist/services/context/auto-compact-types.js +16 -32
  55. package/dist/services/context/harness-window-config.d.ts +412 -0
  56. package/dist/services/context/harness-window-config.js +607 -0
  57. package/dist/services/context/main-session-monitor.d.ts +27 -0
  58. package/dist/services/context/main-session-monitor.js +32 -1
  59. package/dist/services/context/post-compact-reinjection.d.ts +221 -0
  60. package/dist/services/context/post-compact-reinjection.js +491 -0
  61. package/dist/services/dispatch/merge-back-runner.js +5 -5
  62. package/dist/services/dispatch/service-shutdown.js +3 -3
  63. package/dist/services/doc/doc-generator.js +2 -1
  64. package/dist/services/env/shell-probe.js +1 -1
  65. package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
  66. package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
  67. package/dist/services/hooks/auto-compact-hook-install.js +8 -0
  68. package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
  69. package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
  70. package/dist/services/ide/ide-registry.d.ts +12 -0
  71. package/dist/services/ide/ide-registry.js +14 -0
  72. package/dist/services/ide/ide-types.d.ts +59 -0
  73. package/dist/services/lint/detect-eslint.js +2 -2
  74. package/dist/services/lint/eslint-runner.js +3 -1
  75. package/dist/services/loop/evaluator-dispatcher.js +2 -1
  76. package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
  77. package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
  78. package/dist/services/memory/project-memory-service/store/paths.js +15 -6
  79. package/dist/services/prd/best-practice-auto-trigger.js +1 -0
  80. package/dist/services/release/version-precheck-service.d.ts +2 -1
  81. package/dist/services/release/version-precheck-service.js +82 -12
  82. package/dist/services/runtime/vendor-adapter.d.ts +29 -4
  83. package/dist/services/runtime/vendors/claude-code.js +1 -1
  84. package/dist/services/runtime/vendors/codex.js +1 -1
  85. package/dist/services/runtime/vendors/copilot.js +1 -1
  86. package/dist/services/sc/sc-service.js +1 -1
  87. package/dist/services/scan/diff-scope-service.js +2 -2
  88. package/dist/services/scan/file-size-scan.js +2 -2
  89. package/dist/services/scan/orphan-service.js +2 -1
  90. package/dist/services/scan/type-sanity-service.js +2 -2
  91. package/dist/services/skillhub/tar-runtime.js +1 -0
  92. package/dist/services/skills/hooks-codegate-superpowers.d.ts +8 -0
  93. package/dist/services/skills/hooks-codegate-superpowers.js +40 -3
  94. package/dist/services/skills/hooks-settings-service.d.ts +12 -0
  95. package/dist/services/skills/hooks-settings-service.js +77 -10
  96. package/dist/services/skills/session-start-hook-constants.d.ts +41 -0
  97. package/dist/services/skills/session-start-hook-constants.js +41 -0
  98. package/dist/services/skills/skill-presence-service.js +9 -0
  99. package/dist/services/skills/skill-statusline-renderer.js +30 -5
  100. package/dist/services/skills/statusline-palette.d.ts +6 -0
  101. package/dist/services/skills/statusline-palette.js +4 -1
  102. package/dist/services/slice/slice-check-service.js +2 -1
  103. package/dist/services/slice/slice-decompose-runners.js +2 -1
  104. package/dist/services/upgrade/upgrade-service.js +1 -0
  105. package/dist/services/workflow/workflow-skip-service.js +2 -1
  106. package/dist/services/workspace/migrate-service.js +1 -1
  107. package/dist/services/workspace/workspace-claude-settings-materializer.js +51 -7
  108. package/dist/services/workspace/workspace-service.js +8 -0
  109. package/dist/services/worktree/host-worktree-reconciler.js +1 -0
  110. package/dist/services/worktree/long-path-cleanup.js +3 -2
  111. package/dist/shared/process.js +1 -1
  112. package/package.json +5 -5
  113. package/scripts/install-skills.mjs +1 -0
  114. package/scripts/watch.mjs +3 -1
  115. package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
  116. package/skills/bee/peaks-prd/SKILL.md +1 -1
  117. package/skills/bee/peaks-qa/SKILL.md +2 -2
  118. package/skills/bee/peaks-rd/SKILL.md +2 -2
  119. package/skills/bee/peaks-reviewer/SKILL.md +1 -1
  120. package/skills/bee/peaks-sc/SKILL.md +1 -1
  121. package/skills/bee/peaks-security-audit/SKILL.md +1 -1
  122. package/skills/bee/peaks-txt/SKILL.md +1 -1
  123. package/skills/bee/peaks-ui/SKILL.md +1 -1
  124. package/skills/peaks-audit/SKILL.md +1 -1
  125. package/skills/peaks-code/SKILL.md +4 -4
  126. package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
  127. package/skills/peaks-content/SKILL.md +1 -1
  128. package/skills/peaks-doctor/SKILL.md +1 -1
  129. package/skills/peaks-final-review/SKILL.md +1 -1
  130. package/skills/peaks-ide/SKILL.md +1 -1
  131. package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
  132. package/skills/peaks-resume/SKILL.md +1 -1
  133. package/skills/peaks-slice-decompose/SKILL.md +1 -1
  134. package/skills/peaks-solo/SKILL.md +1 -1
  135. package/skills/peaks-sop/SKILL.md +1 -1
  136. package/skills/peaks-status/SKILL.md +1 -1
  137. package/skills/peaks-test/SKILL.md +1 -1
@@ -604,6 +604,15 @@ export function clearSkillPresence(projectRootOverride) {
604
604
  // shim; the LLM runner calls `peaks workflow terminalize` or
605
605
  // `terminalizePresenceLease` directly).
606
606
  //
607
+ // That rule is the surrounding DESIGN, not a description of this
608
+ // function: nothing below routes anywhere. This shim has no session
609
+ // lock, no lease argument and no workflow id, so it cannot route —
610
+ // it cleans up deprecated files and returns whether it removed any.
611
+ // The truthful report of what remains is the caller's job
612
+ // (`skill-command.ts` re-reads the live projection and emits
613
+ // `cleared` / `reason`). Do not read the paragraph above as a
614
+ // promise about this function.
615
+ //
607
616
  // The shim still removes the legacy `active-skill.json` /
608
617
  // `.peaks/.active-skill.json` so a stale marker from a prior CLI
609
618
  // version cannot resurrect after a fresh `clear`. The canonical
@@ -1,4 +1,5 @@
1
1
  import { basename } from 'node:path';
2
+ import { AUTO_COMPACT_RED_LINE_RATIO } from '../context/auto-compact-types.js';
2
3
  import { computeRootSuffix as computeRootSuffixImpl, } from './skill-statusline-sid-suffix.js';
3
4
  // Re-export so existing test imports
4
5
  // (`import { formatShortSid, computeRootSuffix } from '.../skill-statusline-renderer'`)
@@ -181,6 +182,10 @@ function formatRatio(value) {
181
182
  *
182
183
  * <stage-glyph> <bar> <label>[ · <before>%][ → <after>%]
183
184
  *
185
+ * `armed` is the one exception: it renders WITHOUT the bar
186
+ * (`<glyph> armed · <before>% · fires at <redLine>%`) because a bar is
187
+ * a progress claim and a registered-but-unfired trigger has no progress.
188
+ *
184
189
  * Failed states additionally suffix the stage at which the compact failed.
185
190
  * Stalled states keep the active-stage cell count and render a plain
186
191
  * "stalled" label. Invalid states surface the read-reason verbatim as a
@@ -202,6 +207,16 @@ function renderCompact(state, palette) {
202
207
  return `${palette.compact.compacting} ${renderCompactBar(4, palette)} compacting${typeof state.triggerRatio === 'number'
203
208
  ? `${palette.inlineSeparator}${formatRatio(state.triggerRatio)}`
204
209
  : ''}`;
210
+ case 'armed': {
211
+ // Slice 2026-09-12-compact-band-policy: NO bar. A bar is a
212
+ // progress claim, and a registered-but-unfired trigger has no
213
+ // progress to report — it is waiting for the ratio to reach the
214
+ // red line on its own. Say exactly that instead.
215
+ const now = typeof state.triggerRatio === 'number'
216
+ ? `${palette.inlineSeparator}${formatRatio(state.triggerRatio)}`
217
+ : '';
218
+ return `${palette.compact.armed} armed${now}${palette.inlineSeparator}fires at ${formatRatio(AUTO_COMPACT_RED_LINE_RATIO)}`;
219
+ }
205
220
  case 'verifying':
206
221
  return `${palette.compact.verifying} ${renderCompactBar(6, palette)} verifying`;
207
222
  case 'completed':
@@ -518,7 +533,14 @@ export function renderStatusLine(model, options, env) {
518
533
  const compactSegment = renderCompact(model.compact, palette);
519
534
  let line;
520
535
  const hasCompact = compactSegment.length > 0;
521
- if (hasCompact) {
536
+ // Slice 2026-09-12-compact-band-policy: `armed` is a RESTING state, not
537
+ // an in-flight compact. It can hold for the whole band between the
538
+ // auto-fire ratio and the red line, so it is appended to the normal
539
+ // line instead of REPLACING the skill token — hiding which skill is
540
+ // active for many turns would be a regression the user never asked
541
+ // for. Every other (genuinely in-flight) stage keeps replacing it.
542
+ const armedOnly = model.compact.kind === 'armed';
543
+ if (hasCompact && !armedOnly) {
522
544
  // Compact state replaces the active / stale / idle skill content.
523
545
  // `invalid-presence` still surfaces its own diagnostic when compact
524
546
  // is also `invalid` (the compact diagnostic wins, since it's the
@@ -526,21 +548,24 @@ export function renderStatusLine(model, options, env) {
526
548
  line = `${brand} ${compactSegment}${rootSuffix}`;
527
549
  }
528
550
  else {
551
+ let base;
529
552
  switch (model.state) {
530
553
  case 'active':
531
- line = `${brand} ${renderActive(model.presence, palette, nowMs, capability, noColor, model.activeLeaf, model.twentyFourHourState)}${rootSuffix}`;
554
+ base = renderActive(model.presence, palette, nowMs, capability, noColor, model.activeLeaf, model.twentyFourHourState);
532
555
  break;
533
556
  case 'stale':
534
- line = `${brand} ${renderStale(model.presence, model.ageMs, palette, capability, noColor)}${rootSuffix}`;
557
+ base = renderStale(model.presence, model.ageMs, palette, capability, noColor);
535
558
  break;
536
559
  case 'invalid-presence':
537
- line = `${brand} ${renderInvalid(palette)}${rootSuffix}`;
560
+ base = renderInvalid(palette);
538
561
  break;
539
562
  case 'idle':
540
563
  default:
541
- line = `${brand} ${renderIdle(palette)}${rootSuffix}`;
564
+ base = renderIdle(palette);
542
565
  break;
543
566
  }
567
+ const armedSuffix = armedOnly ? `${palette.inlineSeparator}${compactSegment}` : '';
568
+ line = `${brand} ${base}${armedSuffix}${rootSuffix}`;
544
569
  }
545
570
  // Marquee is OFF for idle (and only idle). Compact states always
546
571
  // carry the band because the compact bar IS the headline. NO_COLOR
@@ -35,6 +35,12 @@ interface CompactPalette {
35
35
  readonly queued: string;
36
36
  readonly preparing: string;
37
37
  readonly compacting: string;
38
+ /**
39
+ * Slice 2026-09-12-compact-band-policy: a trigger is registered but no
40
+ * compaction is running. Rendered WITHOUT a bar — see
41
+ * `renderCompact` in skill-statusline-renderer.ts.
42
+ */
43
+ readonly armed: string;
38
44
  readonly verifying: string;
39
45
  readonly completed: string;
40
46
  readonly failed: string;
@@ -117,7 +117,7 @@ function buildPalette(capability, noColor) {
117
117
  idleLabel: 'empty',
118
118
  invalidMessage: 'presence unreadable',
119
119
  compact: {
120
- queued: '[', preparing: '+', compacting: '+', verifying: '+',
120
+ queued: '[', preparing: '+', compacting: '+', armed: '~', verifying: '+',
121
121
  completed: '*', failed,
122
122
  },
123
123
  barFilled: '#',
@@ -142,6 +142,9 @@ function buildPalette(capability, noColor) {
142
142
  queued: brandGlyph('◐'),
143
143
  preparing: brandGlyph('◑'),
144
144
  compacting: brandGlyph('◒'),
145
+ // Distinct from `compacting` on purpose: "waiting for the trigger"
146
+ // must not wear the same face as "a compact is in flight".
147
+ armed: brandGlyph('◔'),
145
148
  verifying: brandGlyph('◓'),
146
149
  completed: brandGlyph('✓'),
147
150
  failed,
@@ -96,7 +96,8 @@ function runCommand(command, args, cwd, timeoutMs) {
96
96
  cwd,
97
97
  stdio: ['ignore', 'pipe', 'pipe'],
98
98
  timeout: timeoutMs,
99
- maxBuffer: EXEC_MAX_BUFFER_BYTES
99
+ maxBuffer: EXEC_MAX_BUFFER_BYTES,
100
+ windowsHide: true
100
101
  }).toString('utf8');
101
102
  return {
102
103
  status: 'pass',
@@ -98,7 +98,8 @@ function runCodegraph(args, projectRoot) {
98
98
  cwd: projectRoot,
99
99
  stdio: ['ignore', 'pipe', 'pipe'],
100
100
  timeout: 60_000,
101
- maxBuffer: 32 * 1024 * 1024
101
+ maxBuffer: 32 * 1024 * 1024,
102
+ windowsHide: true
102
103
  };
103
104
  // Use `peaks codegraph` (the peaks wrapper), which adds --project support.
104
105
  // 2026-09-10: no shell. `peaks` on Windows is a `.cmd` shim, which Node >= 20
@@ -47,6 +47,7 @@ export const defaultSubstepExecutor = (command, args, timeoutMs) => {
47
47
  encoding: 'utf8',
48
48
  stdio: ['ignore', 'pipe', 'pipe'],
49
49
  timeout: timeoutMs,
50
+ windowsHide: true,
50
51
  });
51
52
  return {
52
53
  status: result.status,
@@ -186,7 +186,8 @@ function resolveSkipAppliedBy() {
186
186
  const { execFileSync } = require('node:child_process');
187
187
  const out = execFileSync('git', ['config', '--global', 'user.name'], {
188
188
  encoding: 'utf8',
189
- stdio: ['ignore', 'pipe', 'ignore']
189
+ stdio: ['ignore', 'pipe', 'ignore'],
190
+ windowsHide: true
190
191
  });
191
192
  const trimmed = out.trim();
192
193
  if (trimmed.length > 0) {
@@ -426,7 +426,7 @@ async function gitMv(from, to, projectRoot) {
426
426
  // command must be run from inside the project so it can locate
427
427
  // .git/ (the migrate target may be a temp dir created by tests).
428
428
  try {
429
- execFileSync('git', ['mv', from, to], { cwd: projectRoot, stdio: 'pipe' });
429
+ execFileSync('git', ['mv', from, to], { cwd: projectRoot, stdio: 'pipe', windowsHide: true });
430
430
  }
431
431
  catch {
432
432
  throw error;
@@ -24,22 +24,56 @@ function readTextIfPresent(filePath) {
24
24
  }
25
25
  }
26
26
  /**
27
- * The `env` object of a serialized settings file, or `undefined` when the file
28
- * is malformed or has no `env` object. Tolerant on purpose: a bad on-disk file
29
- * must not stop the materialization.
27
+ * The parsed top-level object of a serialized settings file, or `undefined`
28
+ * when the file is malformed or is not a JSON object. Tolerant on purpose: a
29
+ * bad on-disk file must not stop the materialization.
30
30
  */
31
- function readEnvObject(serialized) {
31
+ function readSettingsObject(serialized) {
32
32
  try {
33
33
  const parsed = JSON.parse(serialized);
34
34
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
35
35
  return undefined;
36
- const env = parsed.env;
37
- return typeof env === 'object' && env !== null && !Array.isArray(env) ? env : undefined;
36
+ return parsed;
38
37
  }
39
38
  catch {
40
39
  return undefined;
41
40
  }
42
41
  }
42
+ /** The `env` object of a serialized settings file, or `undefined`. */
43
+ function readEnvObject(serialized) {
44
+ const env = readSettingsObject(serialized)?.env;
45
+ return typeof env === 'object' && env !== null && !Array.isArray(env) ? env : undefined;
46
+ }
47
+ /**
48
+ * The top-level keys this function's template is allowed to DECIDE. Every other
49
+ * key on disk belongs to whoever put it there and is carried across verbatim.
50
+ *
51
+ * - `hooks` — the tree this function exists to keep in sync. `peaks workspace
52
+ * init` is the writer that converges a consumer's file on the current
53
+ * release's handler set, and `templateContentMatches` (the drift detector
54
+ * that decides whether to rewrite at all) compares exactly this tree. Letting
55
+ * the disk win here would make the rewrite a no-op that reports `refreshed`
56
+ * forever.
57
+ * - `env` — jointly owned with `peaks hooks install`, which unions the user's
58
+ * exemption globs into it. Handled as a union below, not by either side
59
+ * winning outright.
60
+ *
61
+ * This is deliberately an owned-key LIST and not a preserved-key whitelist: the
62
+ * direction matters. A whitelist drops every key it was not told about — which
63
+ * is how `permissions` was lost — whereas anything absent from this list is
64
+ * preserved by default, including keys no release of peaks-loop knows about.
65
+ */
66
+ const TEMPLATE_OWNED_KEYS = new Set(['hooks', 'env']);
67
+ /** `template`'s own keys, then every on-disk key the template does not own. */
68
+ function carryUserOwnedKeys(onDisk, template) {
69
+ const merged = { ...template };
70
+ for (const [key, value] of Object.entries(onDisk)) {
71
+ if (TEMPLATE_OWNED_KEYS.has(key))
72
+ continue;
73
+ merged[key] = value;
74
+ }
75
+ return merged;
76
+ }
43
77
  /**
44
78
  * The peaks-managed snippet appended to the consumer project's
45
79
  * `.peaks/.gitignore` so the local-only settings file never lands
@@ -102,8 +136,18 @@ export async function materializeClaudeSettingsLocal(projectRoot, noClaudeHooks)
102
136
  // the rewrite this function is about to do, or a refresh would silently
103
137
  // drop someone else's exemptions. The template's own row is added on top, so
104
138
  // the result is a union either way.
139
+ //
140
+ // The same argument applies to every OTHER top-level key on disk, which the
141
+ // rewrite used to drop wholesale: `{ ...template }` emitted only the keys the
142
+ // template declares, so a user's `permissions.allow`, `statusLine`, `model`,
143
+ // … were deleted by an init that had no opinion about them. The user's
144
+ // permission rules are not this function's to remove, and the removal was
145
+ // silent — the envelope reported only `refreshed`. See `carryUserOwnedKeys`
146
+ // for which keys the template still decides.
147
+ const onDisk = existing === undefined ? undefined : readSettingsObject(existing);
105
148
  const onDiskEnv = existing === undefined ? undefined : readEnvObject(existing);
106
- const serialized = JSON.stringify(withExternalGateExemptions(onDiskEnv === undefined ? template : { ...template, env: onDiskEnv }), null, 2) + '\n';
149
+ const merged = onDisk === undefined ? template : carryUserOwnedKeys(onDisk, template);
150
+ const serialized = JSON.stringify(withExternalGateExemptions(onDiskEnv === undefined ? merged : { ...merged, env: onDiskEnv }), null, 2) + '\n';
107
151
  // Always drop (or self-heal) a copy of the template under .peaks/
108
152
  // so the --no-claude-hooks recovery flow has a known source-of-truth
109
153
  // on disk. The file is gitignored by the snippet below.
@@ -6,6 +6,7 @@ import { getSessionIdCanonical, setCurrentSessionBinding, setSessionMeta } from
6
6
  import { updateCallerBindingSessionId } from '../session/caller-binding-service.js';
7
7
  import { resolveCallerProjection } from '../session/resolve-caller-id.js';
8
8
  import { normalizePath } from '../../shared/path-utils.js';
9
+ import { assertWritableProjectRoot } from '../config/config-safety.js';
9
10
  /**
10
11
  * Slice 2026-09-10 (rid=rebind-must-update-caller-binding): repoint the
11
12
  * calling process's own per-caller binding at the session id this init just
@@ -180,6 +181,13 @@ export function validateSessionId(sessionId) {
180
181
  }
181
182
  export async function initWorkspace(options) {
182
183
  validateSessionId(options.sessionId);
184
+ // Before anything is created: a project tree must never be materialized into
185
+ // the user's home directory. `--project .` from a fresh terminal (whose cwd
186
+ // is `$HOME`) resolved to `$HOME` and did exactly that — `.peaks/`,
187
+ // `.gitignore`, `.claude/settings.local.json` and a codegraph index, all in
188
+ // the user's home. Enforced here as well as at the CLI so a non-CLI caller
189
+ // (`peaks upgrade`) cannot reach the same write by another door.
190
+ assertWritableProjectRoot(options.projectRoot);
183
191
  // Phase 6 refactor (slice 2026-06-05-change-id-as-unit-of-work) +
184
192
  // slice 006 (2026-06-06-change-folder-simplify-and-lazy-role-subdirs) +
185
193
  // slice 2026-06-22-top-level-change-id-cleanup (2.8.3):
@@ -11,6 +11,7 @@ export function reconcileHostWorktrees(input) {
11
11
  cwd: input.projectRoot,
12
12
  encoding: 'utf8',
13
13
  stdio: ['ignore', 'pipe', 'pipe'],
14
+ windowsHide: true,
14
15
  });
15
16
  records = parseGitWorktreePorcelain(raw);
16
17
  }
@@ -14,8 +14,9 @@ export function removeRegisteredWorktree(input) {
14
14
  execFileSync('git', ['worktree', 'remove', '--force', input.worktreePath], {
15
15
  cwd: input.projectRoot,
16
16
  stdio: ['ignore', 'pipe', 'pipe'],
17
+ windowsHide: true,
17
18
  });
18
- execFileSync('git', ['worktree', 'prune'], { cwd: input.projectRoot, stdio: 'ignore' });
19
+ execFileSync('git', ['worktree', 'prune'], { cwd: input.projectRoot, stdio: 'ignore', windowsHide: true });
19
20
  return { removed: !existsSync(input.worktreePath), strategy: 'git' };
20
21
  }
21
22
  catch (error) {
@@ -24,7 +25,7 @@ export function removeRegisteredWorktree(input) {
24
25
  }
25
26
  try {
26
27
  rmSync(windowsLongPath(input.worktreePath), { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
27
- execFileSync('git', ['worktree', 'prune'], { cwd: input.projectRoot, stdio: 'ignore' });
28
+ execFileSync('git', ['worktree', 'prune'], { cwd: input.projectRoot, stdio: 'ignore', windowsHide: true });
28
29
  return { removed: !existsSync(input.worktreePath), strategy: 'windows-long-path' };
29
30
  }
30
31
  catch (fallbackError) {
@@ -1,7 +1,7 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  export async function execCommand(command, args, options) {
3
3
  return new Promise((resolve, reject) => {
4
- execFile(command, args, { cwd: options?.cwd, env: options?.env }, (error, stdout) => {
4
+ execFile(command, args, { cwd: options?.cwd, env: options?.env, windowsHide: true }, (error, stdout) => {
5
5
  if (error) {
6
6
  reject(error);
7
7
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.45",
3
+ "version": "4.0.47",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "keywords": [
@@ -102,10 +102,10 @@
102
102
  "picomatch": "4.0.4",
103
103
  "yaml": "^2.9.0",
104
104
  "zod": "^4.4.3",
105
- "peaks-loop-mut": "0.1.43",
106
- "peaks-loop-shared-channel": "0.0.47",
107
- "peaks-loop-internal-runtime": "0.0.30",
108
- "peaks-loop-shared": "0.0.79"
105
+ "peaks-loop-internal-runtime": "0.0.32",
106
+ "peaks-loop-shared": "0.0.81",
107
+ "peaks-loop-mut": "0.1.45",
108
+ "peaks-loop-shared-channel": "0.0.49"
109
109
  },
110
110
  "devDependencies": {
111
111
  "@changesets/cli": "2.31.1",
@@ -1273,6 +1273,7 @@ export async function autoUpgrade1xProjectIfPresent(options = {}) {
1273
1273
  encoding: 'utf8',
1274
1274
  stdio: ['ignore', 'pipe', 'pipe'],
1275
1275
  timeout: 120_000,
1276
+ windowsHide: true,
1276
1277
  });
1277
1278
  return {
1278
1279
  ran: true,
package/scripts/watch.mjs CHANGED
@@ -30,7 +30,9 @@ export function runCommand(command, args, options = {}) {
30
30
  let child;
31
31
 
32
32
  try {
33
- child = spawn(command, args, { stdio: 'inherit', ...options });
33
+ // `windowsHide` is a default, not a lock: a caller that passes its own
34
+ // value in `options` still wins, because the spread comes after it.
35
+ child = spawn(command, args, { stdio: 'inherit', windowsHide: true, ...options });
34
36
  } catch (error) {
35
37
  rejectPromise(error);
36
38
  return;
@@ -12,7 +12,7 @@ metadata:
12
12
  - schema: .peaks/project-scan/audit-output-schema.md
13
13
  ---
14
14
 
15
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
15
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
16
16
 
17
17
  ## Loop hygiene (every turn — MANDATORY)
18
18
 
@@ -8,7 +8,7 @@ description: |
8
8
  visibility: internal
9
9
  ---
10
10
 
11
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
11
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
12
12
 
13
13
  ## Loop hygiene (every turn — MANDATORY)
14
14
 
@@ -8,7 +8,7 @@ description: |
8
8
  visibility: internal
9
9
  ---
10
10
 
11
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
11
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
12
12
 
13
13
  ## Loop hygiene (every turn — MANDATORY)
14
14
 
@@ -298,4 +298,4 @@ QA contracts to assert on the L2 surface (a minimal acceptance test set):
298
298
 
299
299
  ## Sub-role detached mode (Phase C, slice 2026-08-10)
300
300
 
301
- QA sub-roles (`qa-business`, `qa-perf`, `qa-security`, `qa-business-api`, `qa-business-frontend`, `qa-business-regression`) accept `--mode detached --vendor <vendor>` for parallel test-case execution. Detached mode spawns a real OS process via `peaks sub-agent dispatch --mode detached`, isolated from the orchestrator session. Use detached mode when the test-case writer's expected runtime exceeds 60s OR it processes ≥ 20 source files. The default remains `in-process` (backward compat; existing 106+ dispatch tests untouched). When `--mode detached` is used, the child QA agent receives the G8 infinite-context auto-compact marker (0.85 / 0.95 thresholds; unlimited spend authorized). See `peaks-code/references/sub-agent-dispatch.md` §"Detached Mode" for the full contract.
301
+ QA sub-roles (`qa-business`, `qa-perf`, `qa-security`, `qa-business-api`, `qa-business-frontend`, `qa-business-regression`) accept `--mode detached --vendor <vendor>` for parallel test-case execution. Detached mode spawns a real OS process via `peaks sub-agent dispatch --mode detached`, isolated from the orchestrator session. Use detached mode when the test-case writer's expected runtime exceeds 60s OR it processes ≥ 20 source files. The default remains `in-process` (backward compat; existing 106+ dispatch tests untouched). When `--mode detached` is used, the child QA agent receives the `<peaks-auto-compact>` marker (corrected 2026-09-13, rid `2026-09-13-defects-e`): it asks the child to persist durable state to `.peaks/_runtime/<sid>/detached/<rid>/compact/<n>.json` before its own harness compacts the session — the child cannot compact itself, and nothing is spliced back into its prompt. See `peaks-code/references/sub-agent-dispatch.md` §"Detached Mode" for the full contract.
@@ -9,7 +9,7 @@ description: |
9
9
  visibility: internal
10
10
  ---
11
11
 
12
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
12
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
13
13
 
14
14
  ## Loop hygiene (every turn — MANDATORY)
15
15
 
@@ -187,7 +187,7 @@ Full dispatch contract (when-to-fan-out rules, dispatch template, prereq gates)
187
187
 
188
188
  ## Reviewer fan-out detached mode (Phase C, slice 2026-08-10)
189
189
 
190
- Reviewer fan-out may run in detached mode for any of the 3-way fan-out roles (`code-reviewer`, `qa-test-cases-writer`, `karpathy-reviewer`). Detached mode spawns a real OS process via `peaks sub-agent dispatch --mode detached --vendor <vendor>`, isolated from the orchestrator session. Use detached mode when the reviewer's expected runtime exceeds 60s OR processes ≥ 20 source files. Pass `--mode detached` explicitly per reviewer role; the default remains `in-process` (backward compat). The karpathy-reviewer's G8 infinite-context marker (auto-compact at 0.85/0.95, unlimited spend) is honored automatically when `--mode detached` is used. See `peaks-code/references/sub-agent-dispatch.md` §"Detached Mode" for the full contract.
190
+ Reviewer fan-out may run in detached mode for any of the 3-way fan-out roles (`code-reviewer`, `qa-test-cases-writer`, `karpathy-reviewer`). Detached mode spawns a real OS process via `peaks sub-agent dispatch --mode detached --vendor <vendor>`, isolated from the orchestrator session. Use detached mode when the reviewer's expected runtime exceeds 60s OR processes ≥ 20 source files. Pass `--mode detached` explicitly per reviewer role; the default remains `in-process` (backward compat). The karpathy-reviewer's `<peaks-auto-compact>` marker is honored automatically when `--mode detached` is used. Note what that marker does and does not do (corrected 2026-09-13, rid `2026-09-13-defects-e`): it asks the child to persist durable state to `.peaks/_runtime/<sid>/detached/<rid>/compact/<n>.json` before its own harness compacts the session — the child cannot compact itself, and nothing is spliced back into its prompt. See `peaks-code/references/sub-agent-dispatch.md` §"Detached Mode" for the full contract.
191
191
 
192
192
  ## Refactor hard gates
193
193
 
@@ -4,7 +4,7 @@ visibility: internal
4
4
  description: Third-party independent code-review skill for Peaks. Use as a parallel reviewer to karpathy-reviewer when a workflow needs an out-of-band (non-RD-model) review pass to mitigate single-LLM self-dogfood blind spots. Emits a schema-validated ReviewerEnvelope (no free-form LLM JSON). v2.14.0 G4.
5
5
  ---
6
6
 
7
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
7
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
8
8
 
9
9
  ## Loop hygiene (every turn — MANDATORY)
10
10
 
@@ -8,7 +8,7 @@ description: |
8
8
  visibility: internal
9
9
  ---
10
10
 
11
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
11
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
12
12
 
13
13
  ## Loop hygiene (every turn — MANDATORY)
14
14
 
@@ -12,7 +12,7 @@ metadata:
12
12
  - schema: .peaks/project-scan/audit-output-schema.md
13
13
  ---
14
14
 
15
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
15
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
16
16
 
17
17
  ## Loop hygiene (every turn — MANDATORY)
18
18
 
@@ -8,7 +8,7 @@ description: |
8
8
  visibility: internal
9
9
  ---
10
10
 
11
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
11
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
12
12
 
13
13
  ## Loop hygiene (every turn — MANDATORY)
14
14
 
@@ -8,7 +8,7 @@ description: |
8
8
  visibility: internal
9
9
  ---
10
10
 
11
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
11
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
12
12
 
13
13
  ## Loop hygiene (every turn — MANDATORY)
14
14
 
@@ -3,7 +3,7 @@ name: peaks-audit
3
3
  description: Gate autonomous LLM work on a 6-dimension audit + user-accepted goal. Use IMMEDIATELY after a need is expressed, BEFORE any PRD/RD/QA work starts.
4
4
  ---
5
5
 
6
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
6
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
7
7
 
8
8
  ## Loop hygiene (every turn — MANDATORY)
9
9
 
@@ -3,7 +3,7 @@ name: peaks-code
3
3
  description: Code-domain loop engineering orchestrator for the Peaks-Loop skill family. Use when the user asks Peaks-Loop to handle a code-repo workflow end-to-end (端到端/全流程/需求开发), especially from a product document (PRD/飞书文档/Feishu doc) through implementation and validation. Coordinates peaks-prd, peaks-rd, peaks-qa, peaks-ui, peaks-sc, and peaks-txt while preserving user confirmation gates. Triggers on `/peaks-code`, "peaks code", "全流程开发", "端到端迭代". General primitives (peaks-resume / peaks-status / peaks-test) are sibling skills, not children.
4
4
  ---
5
5
 
6
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
6
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
7
7
 
8
8
  ## Loop hygiene (every turn — MANDATORY)
9
9
 
@@ -37,7 +37,7 @@ of ~30 minutes clears the gate's "already passed" list, so a file you cleared ea
37
37
  denied again after a long pause. That is the gate resetting, not you regressing.
38
38
  <!-- /peaks:loop-hygiene -->
39
39
 
40
- > **Detached sub-agent mode (Phase A, slice 2026-08-10).** When the orchestrator requires true parallelism with isolated context windows and survives orchestrator session exit, dispatch sub-agents with `--mode detached --vendor <claude|codex|copilot>`. The CLI spawns a real OS process via `ProcessSupervisor` (Windows `DETACHED_PROCESS` + `CREATE_NEW_PROCESS_GROUP`, POSIX `setsid` + `nohup`); the child vendor LLM receives a 5–8KB minimum prompt slice (no orchestrator session history) and self-compacts at 0.85 / 0.95 against the vendor window via the `<peaks-auto-compact>` marker (G8 — unlimited spend authorized). Orchestrator MUST emit one line of prose before every detached dispatch: `⏳ Spawning detached sub-agent via <vendor>: rid=<rid> (ETA ~60s)`. Status is read from `.peaks/_runtime/<sid>/detached/<rid>/status.json`; `LifecycleOwner` enforces 100% cleanup of `pid` / `log.txt` / `status.json` / `owner-session` on every exit path. `--no-throttle --max-concurrent <N>` bypasses `ResourceBudgetGuard` (user accepts risk; default max=8). See `references/sub-agent-dispatch.md` §"Detached Mode" for the full contract. Default mode remains `in-process` for backward compat (existing 106+ dispatch tests untouched).
40
+ > **Detached sub-agent mode (Phase A, slice 2026-08-10).** When the orchestrator requires true parallelism with isolated context windows and survives orchestrator session exit, dispatch sub-agents with `--mode detached --vendor <claude|codex|copilot>`. The CLI spawns a real OS process via `ProcessSupervisor` (Windows `DETACHED_PROCESS` + `CREATE_NEW_PROCESS_GROUP`, POSIX `setsid` + `nohup`); the child vendor LLM receives a 5–8KB minimum prompt slice (no orchestrator session history) and, via the `<peaks-auto-compact>` marker, is told to persist durable state to `.peaks/_runtime/<sid>/detached/<rid>/compact/<n>.json` before its own harness compacts the session (G8, corrected 2026-09-13 rid `2026-09-13-defects-e`: the child cannot compact itself and nothing is spliced back into its prompt; peaks runtime simply has no token-cost throttle, which is not the same as a granted budget). Orchestrator MUST emit one line of prose before every detached dispatch: `⏳ Spawning detached sub-agent via <vendor>: rid=<rid> (ETA ~60s)`. Status is read from `.peaks/_runtime/<sid>/detached/<rid>/status.json`; `LifecycleOwner` enforces 100% cleanup of `pid` / `log.txt` / `status.json` / `owner-session` on every exit path. `--no-throttle --max-concurrent <N>` bypasses `ResourceBudgetGuard` (user accepts risk; default max=8). See `references/sub-agent-dispatch.md` §"Detached Mode" for the full contract. Default mode remains `in-process` for backward compat (existing 106+ dispatch tests untouched).
41
41
  ---
42
42
 
43
43
  ## Scope (RL-8 — red line, locked 2026-07-08)
@@ -185,7 +185,7 @@ Before the first planning action, run `peaks fresh-context preflight --prompt "<
185
185
  1. **PreToolUse hook — `peaks code gate-step-08`.** Installed by `peaks workspace init` on the `Bash` matcher; checks `job-shape.json` presence + fail-closed backup regex. If `job-shape.json` AND `progress.json` exist, surfaces `Next: slice #N of M (<currentSlice>)` so the LLM cannot wake up cold.
186
186
  2. **Size-fear ban — `peaks code emit-handoff`.** Refuses to emit a final handoff while `remaining > 0` under Job mode. Pass `--force-under-job` only with explicit user approval.
187
187
  3. **On-disk slice progress — `peaks job progress`.** `peaks job checkpoint --state done` writes `progress.json`. `peaks job progress --job-id <jid> [--allow-missing]` is the canonical reader.
188
- 4. **Forced auto-compact — `--enforce-job-mode`.** `peaks code context-now --enforce-job-mode` returns `action: 'auto-compact-now'` at ≥ 0.85. **Job mode at ≥ 0.85 is MANDATORY auto-compact** — Code MUST call `peaks code auto-compact` without confirmation.
188
+ 4. **Forced auto-compact — `peaks code context-now`.** It returns `action: 'auto-compact-now'` at ≥ 0.85. **≥ 0.85 is MANDATORY auto-compact in every mode (single-rid included)** — Code MUST call `peaks code auto-compact` without confirmation. `--enforce-job-mode` (v3.1.2) only labels the run `jobMode=true`; the ≥ 0.85 downgrade that used to apply to single-rid sessions was removed 2026-09-12.
189
189
 
190
190
  **Step 0.7 resume rule (read-FIRST):** on resume, `peaks code gate-step-08` reads `progress.json` first and surfaces `Next: slice #N of M (<currentSlice>)` so the orchestrator picks up at the right slice without re-reading the artifact tree.
191
191
 
@@ -209,7 +209,7 @@ Before the first planning action, run `peaks fresh-context preflight --prompt "<
209
209
 
210
210
  **Enforcement layers (defense in depth):**
211
211
  1. `src/services/code/auto-compact-orchestrator.ts` — `evaluateAutoCompactDecision` default-returns `shouldCompact: true` for both `pre-compact` and `red-line`. Only deferral is `inFlightBatch.hasInFlightBatch` (D6.e); no LLM/human approval branch.
212
- 2. `--enforce-job-mode` (v3.1.2) — Job mode elevates ≥0.85 to MANDATORY regardless of in-flight batch.
212
+ 2. `peaks code context-now` — ≥ 0.85 is MANDATORY (`auto-compact-now`) in every mode; `--enforce-job-mode` no longer gates that (2026-09-12). Only an in-flight sub-agent batch defers it.
213
213
  3. `peaks code gate-step-08` (PreToolUse hook) — surfaces `auto-compact-now` on every Bash call when ratio is in the zone, so the LLM cannot wake up cold and forget.
214
214
  4. Karpathy §4 exception: `peaks code auto-compact` is fired *by the orchestrator*, not by the user. If you find yourself about to write "ask the user to compact" / "prompt the user to run `/compact`", STOP — that is the regression.
215
215
 
@@ -449,7 +449,7 @@ peaks skill presence:set peaks-code --project <repo> --mode <mode> --gate swarm-
449
449
 
450
450
  - **Cross-platform spawn**: Windows uses `DETACHED_PROCESS` + `CREATE_NEW_PROCESS_GROUP`; POSIX uses `setsid` + `nohup`. Implementation: `packages/peaks-loop-internal-runtime/src/process-supervisor.ts`.
451
451
  - **Minimum-context prompt**: `PromptBuilder` emits a 5–8KB slice `{rid, role, vendor, files, refs}` plus the verbatim `<peaks-auto-compact>` marker. The forbidden marker `@@@ORCHESTRATOR_SESSION_HISTORY_BOUNDARY@@@` MUST NOT appear in any prompt (unit-tested; regression fails vitest).
452
- - **G8 infinite context**: the child LLM self-monitors context; on ≥0.85 it compacts and writes `.peaks/_runtime/<sid>/detached/<rid>/compact/<n>.json`; on ≥0.95 it writes `status.json` note `'compact-emergency'`. peaks runtime does NOT throttle on token cost — user authorized unlimited spend for G8 (recorded as `tokenUsage` on the dispatch record for audit visibility only).
452
+ - **G8 infinite context (corrected 2026-09-13, rid `2026-09-13-defects-e`)**: the child does **not** and **cannot** compact its own session. `/compact` is not model-invokable, hooks may only observe or veto, there is no `--compact` flag, and no external process can reach a running session's memory — compaction is the child's own harness acting on its own window. What the `<peaks-auto-compact>` marker actually asks for is the reachable half: write the durable state (summary / task state / decisions kept / options discarded) to `.peaks/_runtime/<sid>/detached/<rid>/compact/<n>.json` and heartbeat to `status.json`, so nothing load-bearing lives only in a conversation that will be summarised. Nothing flows back into the child's context — its prompt is the `-p` argv string fixed at spawn. Token cost: peaks runtime has no token-cost throttle to relax (the only guard, `ResourceBudgetGuard`, throttles RSS / CPU / fan-out, never spend), so there is no cost cap — but that is an absence of throttling, not a granted permission with a flag behind it.
453
453
  - **Lifecycle closure invariant**: `pid` / `log.txt` / `status.json` / `owner-session` 100% cleaned on every exit path (success / crash / OOM / SIGTERM). `peaks sub-agent cleanup --orphan` is the only orphan-killing path (RL-15: user-only decision; default is "do nothing").
454
454
  - **Resource budget guard**: `ResourceBudgetGuard` enforces `runtime RSS ≤ 200MB / idle CPU ≤ 5% / fan-out ≤ 8 (default)`. `--no-throttle` bypasses (user accepts risk; adds warning to envelope). `--max-concurrent <N>` overrides the 8 default.
455
455
  - **Vendor-neutral**: `VendorAdapterRegistry` registers `ClaudeAdapter` (Phase A) / `CodexAdapter` + `CopilotAdapter` (Phase B). Missing vendor CLI → fallback to default with `dispatchRecord.warning = 'vendor fallback: <id>→claude'`.
@@ -3,7 +3,7 @@ name: peaks-content
3
3
  description: Non-code orchestrator for content workflows (draft / edit / publish / archive). Use when a user wants to drive an end-to-end content-production loop (blog post, newsletter, marketing copy, social thread, doc page) where the LLM must coordinate draft / edit / tone / publish / archive stages with explicit gates between them, and where the user only wants to speak natural language and pick options. NOT for free-form chatting; NOT for code review. Reuses the peaks-loop Loop Engineering primitives (Loop Engineering Asset, Bee Asset, Workflow Trace, Evolution Evaluation) but does NOT import peaks-code internals. Triggers on `/peaks-content`, "peaks content", "content workflow", "publish this to <channel>", "edit the draft", "archive the previous version".
4
4
  ---
5
5
 
6
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
6
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
7
7
 
8
8
  ## Loop hygiene (every turn — MANDATORY)
9
9
 
@@ -4,7 +4,7 @@ description: Orchestrate peaks-loop's L3 doctor (peaks audit + peaks doctor + pe
4
4
  visibility: internal
5
5
  ---
6
6
 
7
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
7
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
8
8
 
9
9
  ## Loop hygiene (every turn — MANDATORY)
10
10
 
@@ -3,7 +3,7 @@ name: peaks-final-review
3
3
  description: Final-review skill for Peaks. Use when a workflow needs to assemble 4-dimension business-review evidence (functional-completeness, problem-resolution, no-new-bugs, existing-functionality-intact) for human acceptance at the end of an autonomous slice. Triggers on "/peaks-final-review", "prepare final review", "4-dim review", or peaks-code end-of-workflow handoff.
4
4
  ---
5
5
 
6
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
6
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
7
7
 
8
8
  ## Loop hygiene (every turn — MANDATORY)
9
9
 
@@ -4,7 +4,7 @@ description: Orchestrate peaks-loop's IDE-aware behavior (hooks + statusline + h
4
4
  visibility: internal
5
5
  ---
6
6
 
7
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
7
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
8
8
 
9
9
  ## Loop hygiene (every turn — MANDATORY)
10
10
 
@@ -3,7 +3,7 @@ name: peaks-issue-fix-orchestrator
3
3
  description: 'End-to-end orchestrator that surveys open issues in a target repo, classifies them by difficulty, mines reference merged-PRs, fixes each issue with real commits (with Repository + AI-modified declaration), writes normalized PR description files, and emits a one-line submit script. Use when the user wants to drive a "fix N open issues and prepare PRs" run on an upstream repo (any language; primarily Python + TypeScript). Reuses Loop Engineering primitives: 4-layer asset model, Darwin ratchet, karpathy-engineered red lines, Human-NL-Choice-Only. Triggers on `/peaks-issue-fix-orchestrator`, "fix 30 issues", "dogfood on <repo>", "open-issue sweep on <repo>".'
4
4
  ---
5
5
 
6
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
6
+ <!-- peaks:loop-hygiene — HAND-MAINTAINED block, NOT generated; byte-identical in every SKILL.md, enforced by tests/unit/skills/loop-hygiene-block.test.ts. Edit one, edit all 22. -->
7
7
 
8
8
  ## Loop hygiene (every turn — MANDATORY)
9
9