peaks-loop 4.0.46 → 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 (130) hide show
  1. package/CHANGELOG.md +30 -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 +99 -16
  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 +11 -1
  33. package/dist/services/code/auto-compact-lifecycle.js +11 -4
  34. package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
  35. package/dist/services/code/auto-compact-orchestrator.js +153 -32
  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/config/config-safety.d.ts +52 -0
  44. package/dist/services/config/config-safety.js +75 -1
  45. package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
  46. package/dist/services/context/auto-compact-dispatcher.js +113 -40
  47. package/dist/services/context/auto-compact-reader.d.ts +68 -28
  48. package/dist/services/context/auto-compact-reader.js +155 -1
  49. package/dist/services/context/auto-compact-types.d.ts +89 -12
  50. package/dist/services/context/auto-compact-types.js +16 -32
  51. package/dist/services/context/harness-window-config.d.ts +412 -0
  52. package/dist/services/context/harness-window-config.js +607 -0
  53. package/dist/services/context/main-session-monitor.d.ts +27 -0
  54. package/dist/services/context/main-session-monitor.js +32 -1
  55. package/dist/services/context/post-compact-reinjection.d.ts +221 -0
  56. package/dist/services/context/post-compact-reinjection.js +491 -0
  57. package/dist/services/dispatch/merge-back-runner.js +5 -5
  58. package/dist/services/dispatch/service-shutdown.js +3 -3
  59. package/dist/services/doc/doc-generator.js +2 -1
  60. package/dist/services/env/shell-probe.js +1 -1
  61. package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
  62. package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
  63. package/dist/services/hooks/auto-compact-hook-install.js +8 -0
  64. package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
  65. package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
  66. package/dist/services/ide/ide-registry.d.ts +12 -0
  67. package/dist/services/ide/ide-registry.js +14 -0
  68. package/dist/services/ide/ide-types.d.ts +59 -0
  69. package/dist/services/lint/detect-eslint.js +2 -2
  70. package/dist/services/lint/eslint-runner.js +3 -1
  71. package/dist/services/loop/evaluator-dispatcher.js +2 -1
  72. package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
  73. package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
  74. package/dist/services/memory/project-memory-service/store/paths.js +15 -6
  75. package/dist/services/prd/best-practice-auto-trigger.js +1 -0
  76. package/dist/services/release/version-precheck-service.d.ts +2 -1
  77. package/dist/services/release/version-precheck-service.js +82 -12
  78. package/dist/services/runtime/vendor-adapter.d.ts +29 -4
  79. package/dist/services/runtime/vendors/claude-code.js +1 -1
  80. package/dist/services/runtime/vendors/codex.js +1 -1
  81. package/dist/services/runtime/vendors/copilot.js +1 -1
  82. package/dist/services/sc/sc-service.js +1 -1
  83. package/dist/services/scan/diff-scope-service.js +2 -2
  84. package/dist/services/scan/file-size-scan.js +2 -2
  85. package/dist/services/scan/orphan-service.js +2 -1
  86. package/dist/services/scan/type-sanity-service.js +2 -2
  87. package/dist/services/skillhub/tar-runtime.js +1 -0
  88. package/dist/services/skills/hooks-codegate-superpowers.d.ts +8 -0
  89. package/dist/services/skills/hooks-codegate-superpowers.js +40 -3
  90. package/dist/services/skills/hooks-settings-service.d.ts +12 -0
  91. package/dist/services/skills/hooks-settings-service.js +77 -10
  92. package/dist/services/skills/session-start-hook-constants.d.ts +41 -0
  93. package/dist/services/skills/session-start-hook-constants.js +41 -0
  94. package/dist/services/skills/skill-presence-service.js +9 -0
  95. package/dist/services/slice/slice-check-service.js +2 -1
  96. package/dist/services/slice/slice-decompose-runners.js +2 -1
  97. package/dist/services/upgrade/upgrade-service.js +1 -0
  98. package/dist/services/workflow/workflow-skip-service.js +2 -1
  99. package/dist/services/workspace/migrate-service.js +1 -1
  100. package/dist/services/workspace/workspace-claude-settings-materializer.js +51 -7
  101. package/dist/services/workspace/workspace-service.js +8 -0
  102. package/dist/services/worktree/host-worktree-reconciler.js +1 -0
  103. package/dist/services/worktree/long-path-cleanup.js +3 -2
  104. package/dist/shared/process.js +1 -1
  105. package/package.json +5 -5
  106. package/scripts/install-skills.mjs +1 -0
  107. package/scripts/watch.mjs +3 -1
  108. package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
  109. package/skills/bee/peaks-prd/SKILL.md +1 -1
  110. package/skills/bee/peaks-qa/SKILL.md +2 -2
  111. package/skills/bee/peaks-rd/SKILL.md +2 -2
  112. package/skills/bee/peaks-reviewer/SKILL.md +1 -1
  113. package/skills/bee/peaks-sc/SKILL.md +1 -1
  114. package/skills/bee/peaks-security-audit/SKILL.md +1 -1
  115. package/skills/bee/peaks-txt/SKILL.md +1 -1
  116. package/skills/bee/peaks-ui/SKILL.md +1 -1
  117. package/skills/peaks-audit/SKILL.md +1 -1
  118. package/skills/peaks-code/SKILL.md +2 -2
  119. package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
  120. package/skills/peaks-content/SKILL.md +1 -1
  121. package/skills/peaks-doctor/SKILL.md +1 -1
  122. package/skills/peaks-final-review/SKILL.md +1 -1
  123. package/skills/peaks-ide/SKILL.md +1 -1
  124. package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
  125. package/skills/peaks-resume/SKILL.md +1 -1
  126. package/skills/peaks-slice-decompose/SKILL.md +1 -1
  127. package/skills/peaks-solo/SKILL.md +1 -1
  128. package/skills/peaks-sop/SKILL.md +1 -1
  129. package/skills/peaks-status/SKILL.md +1 -1
  130. 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
@@ -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.46",
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-internal-runtime": "0.0.31",
106
- "peaks-loop-shared": "0.0.80",
107
- "peaks-loop-shared-channel": "0.0.48",
108
- "peaks-loop-mut": "0.1.44"
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)
@@ -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
 
@@ -3,7 +3,7 @@ name: peaks-resume
3
3
  description: Universal resume primitive for any in-flight Peaks-Loop workflow (orchestrator-agnostic). Detects the current session's deepest completed gate and surfaces a resume option via AskUserQuestion. Use when ANY bee (peaks-code, future peaks-research, future peaks-content, …) needs to recover from /compact or session interruption. Triggers on "/peaks-resume", "continue the unfinished work", "继续完成", "把刚才没做完的收尾". (Replaces peaks-resume as a top-level primitive.)
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-slice-decompose
3
3
  description: Run multi-pass slice decomposition on a PRD-ready request and emit a v2 envelope with passes, cross-pass edges, and an LLM-arbitration trace. Use when a PRD is ready and the caller needs service-level and/or file-level cuts in one artifact.
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
 
@@ -14,7 +14,7 @@ metadata:
14
14
  red_lines: [RL-1, RL-8, HC-7, HC-8, HC-9, HC-10, HC-11]
15
15
  ---
16
16
 
17
- <!-- peaks:loop-hygiene — generated block; keep byte-identical in every SKILL.md -->
17
+ <!-- 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. -->
18
18
 
19
19
  ## Loop hygiene (every turn — MANDATORY)
20
20
 
@@ -3,7 +3,7 @@ name: peaks-sop
3
3
  description: 'Authoring skill for user-defined SOPs (standard operating procedures) in Peaks. Use when a user wants to create, edit, debug, or register their own gated workflow — ordered phases plus gates that block advancement until conditions are met — by describing it in natural language instead of hand-writing JSON or memorizing CLI commands. DOMAIN-AGNOSTIC: not just software/release flows — equally for content publishing, compliance and approval checklists, data pipelines, onboarding, ops runbooks, or any personal repeatable procedure, wherever "don''t enter the next stage until X is true" applies and X is checkable via a file, file content, or a command.'
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-status
3
3
  description: Universal status primitive for any in-flight Peaks-Loop workflow (orchestrator-agnostic). Reads the existing CLI primitives (peaks skill presence, peaks session list, peaks project dashboard, peaks request show) and renders a 1-screen status table. Use when the user asks "where are we", "现在到哪了", "what's the current state" for ANY bee (peaks-code, future peaks-research, …). Triggers on "/peaks-status", "现在到哪了", "what is the current state", "show me the dashboard". (Replaces peaks-status as a top-level primitive.)
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-test
3
3
  description: Universal test-runner primitive for any in-flight Peaks-Loop workflow (orchestrator-agnostic). Runs the project's test suite on the current repo and reports results. Use when the user asks "run the tests", "跑一下 test", "跑测试" for ANY bee (peaks-code, future peaks-research, …). Triggers on "/peaks-test", "跑一下 test", "跑测试", "run the tests", "test now". (Replaces peaks-test as a top-level primitive.)
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