peaks-loop 3.0.3 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ /**
2
+ * v3.1.1 Step 0.8 — Job-shape decision recorder.
3
+ *
4
+ * The CLI is a *recorder and gate*, not a judge. The LLM makes the
5
+ * semantic call ("is this a Job?") and writes the verdict via
6
+ * `peaks solo detect-job`; this service persists the verdict to
7
+ * `.peaks/_runtime/<sessionId>/job-shape.json` and refuses downstream
8
+ * steps that need a decision when the file is missing.
9
+ *
10
+ * Karpathy §2 (Simplicity First): ~100 lines, pure validation + fs
11
+ * writes, NO keyword regex, NO LLM call inside. The LLM is the
12
+ * source of truth for whether the request is Job-shaped; this file
13
+ * just enforces that a decision was made at all.
14
+ *
15
+ * Decision file lives at `.peaks/_runtime/<sessionId>/job-shape.json`
16
+ * (sibling of `session.json`, NOT under `job/<jid>/` because no jid
17
+ * exists yet at the moment of Step 0.8).
18
+ */
19
+ import { createHash } from 'node:crypto';
20
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import { z } from 'zod';
23
+ export const JOB_SHAPE_SCHEMA_VERSION = 1;
24
+ export const JOB_SHAPE_FILE_NAME = 'job-shape.json';
25
+ export const JOB_SHAPE_ALREADY_DECIDED = 'JOB_SHAPE_ALREADY_DECIDED';
26
+ export const JOB_SHAPE_NOT_DECIDED = 'JOB_SHAPE_NOT_DECIDED';
27
+ export const JOB_SHAPE_INVALID = 'JOB_SHAPE_INVALID';
28
+ const SUGGESTED_JID_RE = /^[a-z0-9][a-z0-9-]{2,40}$/;
29
+ const JobShapeDecisionSchema = z.object({
30
+ isJob: z.boolean(),
31
+ rationale: z.string().min(1).max(2000),
32
+ suggestedJobId: z.string().regex(SUGGESTED_JID_RE, 'suggestedJobId must match /^[a-z0-9][a-z0-9-]{2,40}$/'),
33
+ suggestedStrategy: z.enum(['single', 'rotating']),
34
+ confidence: z.enum(['high', 'medium', 'low']),
35
+ decidedAt: z.string().datetime()
36
+ });
37
+ const JobShapeRecordSchema = z.object({
38
+ sessionId: z.string().min(1),
39
+ promptHash: z.string().regex(/^[a-f0-9]{16}$/, 'promptHash must be 16 hex chars'),
40
+ decision: JobShapeDecisionSchema,
41
+ schemaVersion: z.literal(JOB_SHAPE_SCHEMA_VERSION)
42
+ });
43
+ export class JobShapeDecisionError extends Error {
44
+ code;
45
+ details;
46
+ constructor(opts) {
47
+ super(opts.message);
48
+ this.name = 'JobShapeDecisionError';
49
+ this.code = opts.code;
50
+ if (opts.details !== undefined)
51
+ this.details = opts.details;
52
+ }
53
+ }
54
+ function runtimeDir(projectRoot, sessionId) {
55
+ return join(projectRoot, '.peaks', '_runtime', sessionId);
56
+ }
57
+ function decisionPath(projectRoot, sessionId) {
58
+ return join(runtimeDir(projectRoot, sessionId), JOB_SHAPE_FILE_NAME);
59
+ }
60
+ function sha1Prefix16(input) {
61
+ return createHash('sha1').update(input, 'utf8').digest('hex').slice(0, 16);
62
+ }
63
+ /** Pure validator; throws on bad input. No fs IO. */
64
+ export function validateJobShapeDecision(input) {
65
+ return JobShapeDecisionSchema.parse(input);
66
+ }
67
+ function validateRecord(input) {
68
+ return JobShapeRecordSchema.parse(input);
69
+ }
70
+ /** Read the canonical record; throws JOB_SHAPE_NOT_DECIDED if absent or malformed. */
71
+ export function readJobShapeDecision(projectRoot, sessionId) {
72
+ const path = decisionPath(projectRoot, sessionId);
73
+ if (!existsSync(path)) {
74
+ throw new JobShapeDecisionError({
75
+ code: JOB_SHAPE_NOT_DECIDED,
76
+ message: `No Job-shape decision recorded for session ${sessionId}. Run \`peaks solo detect-job\` to record a decision.`,
77
+ details: { path }
78
+ });
79
+ }
80
+ let raw;
81
+ try {
82
+ raw = readFileSync(path, 'utf8');
83
+ }
84
+ catch (err) {
85
+ throw new JobShapeDecisionError({
86
+ code: JOB_SHAPE_NOT_DECIDED,
87
+ message: `Job-shape decision file is unreadable for session ${sessionId}. Run \`peaks solo detect-job\` to re-record.`,
88
+ details: { path, cause: err instanceof Error ? err.message : String(err) }
89
+ });
90
+ }
91
+ let parsed;
92
+ try {
93
+ parsed = JSON.parse(raw);
94
+ }
95
+ catch (err) {
96
+ throw new JobShapeDecisionError({
97
+ code: JOB_SHAPE_NOT_DECIDED,
98
+ message: `Job-shape decision file is malformed JSON for session ${sessionId}. Run \`peaks solo detect-job --force\` to overwrite.`,
99
+ details: { path, cause: err instanceof Error ? err.message : String(err) }
100
+ });
101
+ }
102
+ try {
103
+ return validateRecord(parsed);
104
+ }
105
+ catch (err) {
106
+ throw new JobShapeDecisionError({
107
+ code: JOB_SHAPE_NOT_DECIDED,
108
+ message: `Job-shape decision file is schema-invalid for session ${sessionId}. Run \`peaks solo detect-job --force\` to overwrite.`,
109
+ details: { path, cause: err instanceof Error ? err.message : String(err) }
110
+ });
111
+ }
112
+ }
113
+ /**
114
+ * Persist a new decision; refuses overwrite unless `force: true`.
115
+ * Stamps `decidedAt` server-side via `now` (defaults to `new Date()`)
116
+ * so the LLM cannot back-date.
117
+ */
118
+ export function writeJobShapeDecision(projectRoot, sessionId, decision, opts = {}) {
119
+ const path = decisionPath(projectRoot, sessionId);
120
+ if (existsSync(path) && opts.force !== true) {
121
+ throw new JobShapeDecisionError({
122
+ code: JOB_SHAPE_ALREADY_DECIDED,
123
+ message: `Job-shape decision already exists for session ${sessionId}. Re-run with --force to overwrite.`,
124
+ details: { path }
125
+ });
126
+ }
127
+ // Validate the LLM-supplied fields first so a bad call fails loud
128
+ // before we touch the filesystem.
129
+ const partial = {
130
+ isJob: decision.isJob,
131
+ rationale: decision.rationale,
132
+ suggestedJobId: decision.suggestedJobId,
133
+ suggestedStrategy: decision.suggestedStrategy,
134
+ confidence: decision.confidence
135
+ };
136
+ // Validate the LLM-supplied shape (decidedAt is server-stamped).
137
+ JobShapeDecisionSchema.omit({ decidedAt: true }).parse(partial);
138
+ const now = opts.now ?? (() => new Date());
139
+ const stamped = { ...partial, decidedAt: now().toISOString() };
140
+ const promptHash = sha1Prefix16(decision.prompt);
141
+ const record = {
142
+ sessionId,
143
+ promptHash,
144
+ decision: stamped,
145
+ schemaVersion: JOB_SHAPE_SCHEMA_VERSION
146
+ };
147
+ const dir = runtimeDir(projectRoot, sessionId);
148
+ mkdirSync(dir, { recursive: true });
149
+ writeFileSync(path, JSON.stringify(record, null, 2) + '\n', 'utf8');
150
+ return record;
151
+ }
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "3.0.3";
1
+ export declare const CLI_VERSION = "3.1.1";
@@ -1 +1 @@
1
- export const CLI_VERSION = "3.0.3";
1
+ export const CLI_VERSION = "3.1.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -11,7 +11,7 @@ The `.peaks/` workspace has a **single scope axis** (session-id) plus a nested *
11
11
 
12
12
  ## Karpathy guidance (Slice 1/6 — karpathy prompt-injection-lift)
13
13
 
14
- Every sub-agent dispatch (`peaks-prd`, `peaks-rd`, `peaks-qa`, `peaks-ui`, `peaks-sc`, `peaks-txt`) MUST receive the 4 Karpathy guidelines. Append the verbatim block from `peaks-rd/references/rd-sub-agent-dispatch.md` §"Karpathy-guidelines context" to the dispatch prompt; never silently drop it. Canonical skill id: `andrej-karpathy-skills:karpathy-guidelines`. Summary: **#1 Think Before Coding** (surface assumptions, name tradeoffs), **#2 Simplicity First** (min code, no speculative features, 800-line file cap), **#3 Surgical Changes** (touch only what the request requires), **#4 Goal-Driven Execution** (verifiable ACs, plan + verify checkpoints).
14
+ Every sub-agent dispatch (`peaks-prd`, `peaks-rd`, `peaks-qa`, `peaks-ui`, `peaks-sc`, `peaks-txt`) MUST receive the 4 Karpathy guidelines. Append the verbatim block from `peaks-rd/references/rd-sub-agent-dispatch.md` §"Karpathy-guidelines context" to the dispatch prompt. Canonical skill id: `andrej-karpathy-skills:karpathy-guidelines`. Summary: **#1 Think Before Coding**, **#2 Simplicity First** (min code, no speculative features, 800-line cap), **#3 Surgical Changes**, **#4 Goal-Driven Execution** (verifiable ACs, plan + verify checkpoints).
15
15
 
16
16
  ## Hard ban (effective 2.8.3 — read every session, no exceptions)
17
17
 
@@ -31,7 +31,7 @@ This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary**
31
31
 
32
32
  ## Code-Change Red Line (BLOCKING — read before ANY tool call)
33
33
 
34
- **Peaks-Loop Solo is an orchestrator, NOT an implementer. You MUST NOT write, edit, or modify any application source code directly.** Every code change goes through `peaks-solo → RD → QA → verdict`. `full-auto`/`swarm` use `peaks sub-agent dispatch <role>`; `assisted`/`strict`/inline-fallback executes inline. Both follow `peaks-rd` + `peaks-qa` contracts. **If you catch yourself about to write code, STOP.** Hand off to RD. Before declaring workflow complete, run `peaks workflow verify-pipeline --rid <rid> --project <repo> --json`.
34
+ **Peaks-Loop Solo is an orchestrator, NOT an implementer. You MUST NOT write, edit, or modify any application source code directly.** Every code change goes through `peaks-solo → RD → QA → verdict`. `full-auto`/`swarm` use `peaks sub-agent dispatch <role>`; `assisted`/`strict`/inline-fallback executes inline. **If you catch yourself about to write code, STOP.** Hand off to RD. Before declaring workflow complete, run `peaks workflow verify-pipeline --rid <rid> --project <repo> --json`.
35
35
 
36
36
  ## Peaks-Loop Startup sequence (MANDATORY — execute in order)
37
37
 
@@ -41,11 +41,11 @@ This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary**
41
41
 
42
42
  ### Peaks-Loop Step 0.75: Resume from checkpoint (BLOCKING on same-day re-invocation)
43
43
 
44
- When a NEW conversation opens on a session whose `lastActivity` is from today AND `.peaks/_runtime/<sessionId>/checkpoints/` contains a `*.json`, surface via `peaks session info --active --json` + `peaks session resume --from <path> --project <repo>`. Prompt via `AskUserQuestion` (resume / fresh); if "resume", prepend the block. Honors "fresh" — on-disk checkpoint stays untouched. Step 0.75 is no-op on precondition fail. See `references/checkpoint-resume.md`.
44
+ When a NEW conversation opens on a session whose `lastActivity` is from today AND `.peaks/_runtime/<sessionId>/checkpoints/` has `*.json`, surface via `peaks session info --active --json` + `peaks session resume --from <path> --project <repo>`. Prompt via `AskUserQuestion` (resume / fresh); if "resume", prepend the block. Step 0.75 is no-op on precondition fail. See `references/checkpoint-resume.md`.
45
45
 
46
46
  ### Peaks-Loop Step N: Periodic checkpoint (auto-fire, no user action)
47
47
 
48
- Proactive context-overflow defense. CLI: `peaks session checkpoint [--reason <r>] [...flags]`. Fire `--reason periodic` every 20 tool calls (G1 hard-coded), `--reason artifact-written` per PRD/RD/QA/TXT write, `--reason context-fill` / `user-pause` / `user-close` as appropriate. 20-call cadence locked; see `references/periodic-checkpoint.md` (otherwise `tests/unit/solo/checkpoint-periodic-frequency.test.ts` fails).
48
+ Proactive context-overflow defense. CLI: `peaks session checkpoint [--reason <r>] [...flags]`. Fire `--reason periodic` every 20 tool calls (G1 hard-coded), `--reason artifact-written` per PRD/RD/QA/TXT write, `--reason context-fill` / `user-pause` / `user-close` as appropriate. See `references/periodic-checkpoint.md` (otherwise `tests/unit/solo/checkpoint-periodic-frequency.test.ts` fails).
49
49
 
50
50
  ### Peaks-Loop Step 0.5: OpenSpec first-run opt-in (conditional)
51
51
 
@@ -67,7 +67,7 @@ After autonomous work (RD, QA, security, perf), invoke peaks-final-review for 4-
67
67
 
68
68
  After every 4th tool call, probe via `peaks solo context-now --project <repo> --json`. Thresholds: 50% / **0.85 pre-compact / 0.95 red-line** (v2.13.0).
69
69
 
70
- **≥ 0.85**, LLM MUST fire `peaks solo auto-compact --execute --project <repo>` autonomously (zero-human-intervention). **≥ 0.95** red line: next Bash/Task tool call fires `peaks session auto-compact-hook` (PreToolUse), which in-band spawns `claude --compact` against the **current** runner.
70
+ **≥ 0.85**, LLM MUST fire `peaks solo auto-compact --execute --project <repo>` autonomously. **≥ 0.95** red line: next Bash/Task tool call fires `peaks session auto-compact-hook` (PreToolUse), which in-band spawns `claude --compact` against the current runner.
71
71
 
72
72
  **Karpathy §4 exception**: compact red line keeps runner alive → zero-intervention wins. LLM does NOT ask user to run `/compact`. Honor `--in-flight-batch` (D6.e).
73
73
 
@@ -87,19 +87,56 @@ Run `peaks workspace init` + `peaks skill presence:set peaks-solo` BEFORE any an
87
87
 
88
88
  After Step 0, run the resume-detection probe; surface via `AskUserQuestion` if a slice is in flight.
89
89
 
90
- **v2.11.0 D7 override:** if the user just `/compact`ed, run `peaks solo post-compact-detect --project <repo> --json` FIRST. If `shouldAutoResume: true`, skip the AskUserQuestion (D7.b — pre-compact approval carries forward). Log to `.peaks/_runtime/<sessionId>/txt/auto-decisions.md`. Applies to all 4 modes; cross-day / cross-machine resume NOT in scope (D7.g).
90
+ **v2.11.0 D7 override:** if the user just `/compact`ed, run `peaks solo post-compact-detect --project <repo> --json` FIRST. If `shouldAutoResume: true`, skip AskUserQuestion (D7.b). Log to `.peaks/_runtime/<sessionId>/txt/auto-decisions.md`. Cross-day / cross-machine resume NOT in scope (D7.g).
91
91
 
92
92
  → see `references/resume-detection.md` for the full detection algorithm + classification table.
93
93
 
94
94
  ### Peaks-Loop Step 0.55: 1.x → 2.0 detection (BLOCKING on first invocation per session, when the project is not on a 2.0 layout)
95
95
 
96
- Per the "one-key completion" tenet, peaks-loop 2.0 detects 1.x consumers and prompts upgrade. After Step 0.7 returns "fresh", run `peaks upgrade --detect-1x --project <root> --json`. If `isOneX: true`, surface `AskUserQuestion`. Persist to `.peaks/preferences.json` (`autoUpgradePrompt`: opt-in / skip-this-session / skip-forever).
96
+ Per the "one-key completion" tenet, peaks-loop 2.0 detects 1.x consumers and prompts upgrade. After Step 0.7 returns "fresh", run `peaks upgrade --detect-1x --project <root> --json`. If `isOneX: true`, surface `AskUserQuestion`. Persist to `.peaks/preferences.json`.
97
97
 
98
98
  → see `references/step-0-55-1x-detection.md` for the detection algorithm + AskUserQuestion options + persistence contract.
99
99
 
100
+ ### Peaks-Loop Step 0.8 — Job 启动 (BLOCKING on LLM judgement — v3.1.1 patch)
101
+
102
+ The CLI does NOT judge whether the request is Job-shaped — that's the LLM's job. Keyword regexes are wrong here (too brittle, miss natural-language variants, shift the LLM's semantic understanding into code). The CLI is a **recorder and gate**: the LLM makes the judgement, writes it via `peaks solo detect-job`, and downstream steps refuse to proceed until the decision file exists.
103
+
104
+ LLM judgement criteria (read once, then call the recorder):
105
+ - N parallel targets (`app/components/*`, `app/modules/*`, `for each subdir`, "以目录为维度", "把剩下的也跑了").
106
+ - Completion phrase implying batch continuation (`until all done`, "继续执行下个 slice", "全部添加完", "all of them").
107
+ - Cost / length disavowal (`不用考虑费用`, "don't worry about cost", "一直跑").
108
+ - Explicit slice count (`25 slices`, "切分成 N 段", "拆成 N 块").
109
+
110
+ Action:
111
+ 1. LLM reads the user prompt and applies judgement → `isJob: boolean + rationale + suggestedJobId + suggestedStrategy + confidence`.
112
+ 2. LLM calls `peaks solo detect-job --is-job <bool> --rationale <text> --suggested-job-id <jid> --suggested-strategy <single|rotating> --confidence <high|medium|low> [--force]`.
113
+ 3. CLI records to `.peaks/_runtime/<sessionId>/job-shape.json`.
114
+ 4. If `isJob: true` → `peaks job init --job-id <jid> --slice-list <...> --main-loop-strategy <strategy> --rotate-every 3` (BEFORE Step 1).
115
+ 5. Downstream steps (Step 1, Step 0.81, etc.) call `read-job-shape` to refuse if missing.
116
+
117
+ **Hard rule (v3.1.1 red-line #10):** LLM MUST NOT skip `peaks solo detect-job` even when the trigger is obvious from context. If the next step's `read-job-shape` throws `JOB_SHAPE_NOT_DECIDED`, Solo MUST record a decision before proceeding. Keyword-based "I already know it's a Job" is not a substitute.
118
+
119
+ ### Peaks-Loop Step 0.81 — per-slice 收尾
120
+
121
+ After commit: `peaks job checkpoint --slice-id <rid> --state done --commit-sha $(git rev-parse HEAD)` → `peaks job status` → loop (remaining>0 → Step 1; ==0 → Step 8/9/10/11; blocked → Step 0.85).
122
+
123
+ ### Peaks-Loop Step 0.85 — slice 阻塞处理
124
+
125
+ Trigger: `repair-status` atCap=true, context-now red-line ≥5min, or `subagent-cleanup` fails twice. Action: `peaks job block --slice-id <rid> --reason "<reason>"` then STOP with TXT handoff.
126
+
127
+ ### Peaks-Loop Step 0.86 — main session rotation
128
+
129
+ Active in rotating mode; fires every `rotateEvery` slices or on-demand. Sequence: cycle-summary → checkpoint rotate-marker → `peaks session rotate` → next turn resumes via `peaks session resume --job-id <jid>`.
130
+
131
+ ### Peaks-Loop Step 0.87 — sub-agent cleanup gate
132
+
133
+ After every `peaks sub-agent dispatch --batch-id <id>` inside a Job, BEFORE next slice checkpoint: `peaks job subagent-cleanup --job-id <jid> --batch-id <id> --force`. Non-zero → block.
134
+
135
+ → see `references/job-loop.md` for state machine, visibility table, rotation cadence, cleanup gate, cross-day recovery, 9 red lines.
136
+
100
137
  ### Peaks-Loop Step 1: Mode selection
101
138
 
102
- Use `AskUserQuestion` with `Full auto (Recommended)` as the first option when EITHER the user did not name a profile OR the recorded skill presence is stale (v2.15.0 slice 002 AC-2: run `peaks skill presence:check-stale --project <path> --json` first; `stale: true` ⇒ re-ask. `peaks solo should-pause --step step-1-mode-select` returns `reason: 'stale-presence'` in this case).
139
+ Use `AskUserQuestion` with `Full auto (Recommended)` first when user did not name a profile OR skill presence is stale (run `peaks skill presence:check-stale --project <path> --json` first; `stale: true` ⇒ re-ask).
103
140
 
104
141
  → see `references/mode-selection.md`, `references/fast-mode.md`, `references/mode-selection-with-stale-presence.md`.
105
142
 
@@ -153,7 +190,7 @@ When the project has no live backend (no swagger.json, no API server), Solo must
153
190
 
154
191
  ## Peaks-Loop Request type classification + Workflow order + Transition verification gates
155
192
 
156
- The contract for the 6-type classification table, the 11-step workflow order, and the 7 transition verification gates (A through G with their `ls` / `grep` shell snippets) lives in `references/workflow-gates-and-types.md`. The peaks-solo narrative in this SKILL.md references those gate numbers (Gate A through Gate G) — keep both files in lockstep when adding or renaming a gate. The reference file is the canonical contract; SKILL.md keeps the prose.
193
+ The contract for the 6-type classification table, the 11-step workflow order, and the 7 transition verification gates (A through G with their `ls` / `grep` shell snippets) lives in `references/workflow-gates-and-types.md`. The peaks-solo narrative references those gate numbers (Gate A through Gate G) — keep both files in lockstep when adding or renaming a gate.
157
194
 
158
195
  ## Peaks-Loop Default sub-agent fan-out (≥ 2 leaves/topological level → `--from-dag`)
159
196
 
@@ -212,7 +249,7 @@ After final validation, refresh project-local standards via `peaks standards ini
212
249
 
213
250
  ## Peaks-Loop Step 11: Memory sediment (BLOCKING on workflow complete)
214
251
 
215
- > **Hard rule.** Solo MUST NOT declare a workflow complete until Step 11 has produced ≥ 1 file in `.peaks/memory/` (the durable, LLM-authored memory store) OR the user has explicitly approved a no-sediment outcome via AskUserQuestion. This applies to **all modes** including `assisted` and `strict` — `assisted` previously skipped Step 10 because there was no `[CONFIRM]` gate; Step 11 fixes that.
252
+ > **Hard rule.** Solo MUST NOT declare a workflow complete until Step 11 has produced ≥ 1 file in `.peaks/memory/` (the durable, LLM-authored memory store) OR the user has explicitly approved a no-sediment outcome via AskUserQuestion. Applies to **all modes** including `assisted` and `strict` — `assisted` previously skipped Step 10 because there was no `[CONFIRM]` gate; Step 11 fixes that.
216
253
 
217
254
  ### Substeps (BLOCKING)
218
255
 
@@ -246,15 +283,15 @@ peaks memory extract --project <repo> --artifact .peaks/_runtime/<sessionId>/txt
246
283
 
247
284
  Default option = (a). Solo MUST NOT silently accept (b) without user pick.
248
285
 
249
- **Why this step exists:** audit 2026-07-03 confirmed 2 consecutive sessions produced zero `.peaks/memory/` files despite completing RD + QA + handoff artifacts. Root cause: `assisted` mode silently skipped runbook Step 10 (no STOP condition). Step 11 makes the BLOCKING semantics explicit. **Why `peaks memory extract` (not `peaks project memories:extract`):** the artifact-scoped extract is the canonical command next to its capsule; the batch-scoped sibling is for non-handoff flows (out of scope). For Step 11, **always** use `peaks memory extract --apply`.
286
+ **Why this step exists:** audit 2026-07-03 confirmed 2 consecutive sessions produced zero `.peaks/memory/` files despite completing RD + QA + handoff artifacts. `assisted` mode silently skipped runbook Step 10 (no STOP condition). Step 11 makes the BLOCKING semantics explicit. **Why `peaks memory extract` (not `peaks project memories:extract`):** artifact-scoped extract is canonical; the batch-scoped sibling is for non-handoff flows. Always use `peaks memory extract --apply`.
250
287
 
251
288
  ## Peaks-Loop External references and lifecycle
252
289
 
253
- 3rd-party integrations (codegraph, mattpocock/skills, shadcn/ui, MCPs, Context7) follow Discovery → Reference → Side effect through Peaks CLI only. Run `peaks capabilities` for capability discovery; treat external skills as reference material only. MCP servers (Playwright / Chrome DevTools / Figma) are user-installed — check the tool list for `mcp__<server>__*` entries. → `references/external-references.md` + `references/external-skill-invocation.md`.
290
+ 3rd-party integrations (codegraph, mattpocock/skills, shadcn/ui, MCPs, Context7) follow Discovery → Reference → Side effect through Peaks CLI only. Run `peaks capabilities` for capability discovery; treat external skills as reference material only. MCP servers (Playwright / Chrome DevTools / Figma) are user-installed — check the tool list for `mcp__<server>__*` entries. Do not execute upstream installer / run upstream commands / persist sensitive examples / install upstream resources directly — funnel side effects through the Peaks CLI surface. Peaks-Loop Solo gates and artifacts remain authoritative. → `references/external-references.md` + `references/external-skill-invocation.md`.
254
291
 
255
292
  ## Codegraph orchestration context
256
293
 
257
- `peaks codegraph affected` is an optional project-analysis enhancement (untrusted supporting evidence) used during role handoff. Solo must not treat codegraph output as approval for scope, design, or QA verdict. Never mutate agent settings / Claude settings / hooks from codegraph; do not commit `.codegraph/` artifacts. RD writes `.peaks/_runtime/<sessionId>/rd/codegraph-context.md`; QA / TXT consume the same envelope. → `references/codegraph-orchestration.md`.
294
+ `peaks codegraph affected` is an optional project-analysis enhancement (untrusted supporting evidence) used during role handoff. Solo must not treat codegraph output as approval for scope, design, or QA verdict. Never mutate agent settings / hooks from codegraph; do not commit `.codegraph/` artifacts. RD writes `.peaks/_runtime/<sessionId>/rd/codegraph-context.md`; QA / TXT consume the same envelope. → `references/codegraph-orchestration.md`.
258
295
 
259
296
  ## Sub-agent context governance (G7 + G7.7 + G8 + G9 — slice #010)
260
297
 
@@ -297,6 +334,7 @@ Index of every `references/` file. Read on demand.
297
334
  | `references/refactor-mode.md` | Refactor mode + red lines. |
298
335
  | `references/resume-detection.md` | Step 0.7 unfinished-work. |
299
336
  | `references/runbook.md` | End-to-end CLI sequence. |
337
+ | `references/job-loop.md` | Step 0.8 / 0.81 / 0.85 / 0.86 / 0.87 deep-dive. |
300
338
  | `references/skill-presence-and-title.md` | Step 2 + Step 2.5. |
301
339
  | `references/standards-preflight.md` | Standards preflight. |
302
340
  | `references/sub-agent-dispatch.md` | IDE-agnostic dispatch. |
@@ -0,0 +1,68 @@
1
+ # Peaks-Loop Solo — Job Loop Deep-Dive
2
+
3
+ > Read alongside `SKILL.md` Steps 0.8 / 0.81 / 0.85 / 0.86 / 0.87.
4
+
5
+ ## State machine
6
+
7
+ ```
8
+ init ─────────► pending ─► in-progress ─┐
9
+ │ │
10
+ ▼ ▼
11
+ blocked (one slice) done ──► (next slice)
12
+ │ ▲
13
+ ▼ │
14
+ (strict: STOP, │
15
+ best-effort: skipped) │
16
+ ```
17
+
18
+ ## Visibility
19
+
20
+ | Layer | Tool | Cost | User reads |
21
+ |---|---|---|---|
22
+ | LLM-runner transcript | (the chat) | free | always |
23
+ | `--watch` poll | peaks job status --watch | 1% CPU | when in a 2nd terminal pane |
24
+ | Statusline | peaks statusline install | ambient | in IDE bottom bar |
25
+
26
+ ## Rotation cadence
27
+
28
+ - `len(slices) ≤ 2` → single-mode (auto-compact passively).
29
+ - `len(slices) ≥ 3` → rotating-mode, K=3 default.
30
+ - LLM-initiated override (rotating→single) is **strongly discouraged**; if used, must record `mainLoopOverride` in state.json with `reason ≥ 10 chars` and predicted wall-time ≤ 30 min.
31
+
32
+ ## Cleanup gate
33
+
34
+ Every `peaks sub-agent dispatch` inside a Job scope MUST be matched by a `peaks job subagent-cleanup --force` BEFORE the next slice checkpoint. The wrapper (M5) refuses dispatch return-success without matching cleanup.
35
+
36
+ ## Cross-day recovery
37
+
38
+ `peaks session resume --job-id <jid>` reads:
39
+ 1. `job/<jid>/state.json` — current job state
40
+ 2. `session/cycle-<n>.md` — main-session cycle summary (rotating mode)
41
+ 3. `session/auto-decisions.md` — auto-compact history
42
+ 4. `session/checkpoints/*.json` — generic session checkpoints
43
+
44
+ The resume endpoint cross-checks all four before offering continuation; if any layer is stale, the user gets an AskUserQuestion (resume / restart / skip).
45
+
46
+ ## Visibility prose
47
+
48
+ The Job loop is foreground. Three visibility layers, all on by default:
49
+ 1. **LLM-runner transcript** — primary surface; user reads the chat to see active step.
50
+ 2. **`peaks job status --watch`** — terminal poll, ANSI bar, refresh every 3 s.
51
+ 3. **Statusline** — ambient `job: <jid> [done/total] currentSlice ETA m:s context main%. cycle`.
52
+
53
+ No detached workers, no `nohup`, no `disown`. Any attempt to spawn a background job → red line violation → block.
54
+
55
+ ## Red lines (9 hard rules)
56
+
57
+ The LLM-runner MUST NOT:
58
+ 1. Enter Step 11 / write final handoff while job has remaining slices.
59
+ 2. Re-ask the user about cost / length / context.
60
+ 3. Coalesce multiple slices into one rid.
61
+ 4. Modify a committed slice (`git commit --amend` on `done`).
62
+ 5. Fake completion (CLI verifies commit-sha exists in git log).
63
+ 6. Use detached / background / daemon-mode sub-agents inside a Job.
64
+ 7. Skip `peaks job subagent-cleanup` between dispatch and slice checkpoint.
65
+ 8. Skip or postpone a scheduled `peaks session rotate`.
66
+ 9. Suppress visibility — no silencing statusline / `--watch`.
67
+
68
+ Violations trigger a `peaks job block` event with the specific red-line number.
@@ -187,3 +187,43 @@ peaks skill doctor --json
187
187
  ```
188
188
 
189
189
  Repair loop details: see `## Mandatory RD QA repair loop` in SKILL.md for the full 5-step procedure and the 3-cycle cap. Append transition notes via `--reason` rather than rewriting artifacts during repair cycles.
190
+
191
+ ```bash
192
+
193
+ # Peaks-Loop Default runbook — Job path (excerpt; full flow in references/job-loop.md)
194
+
195
+ # Step 0.8 (BLOCKING): LLM judges Job-shape, CLI records it.
196
+ # This is a RECORDER, not a detector. The LLM supplies --is-job + --rationale;
197
+ # the CLI writes .peaks/_runtime/<sid>/job-shape.json. Downstream steps call
198
+ # `peaks solo read-job-shape` to enforce the decision exists.
199
+
200
+ # (The LLM does the semantic judgement, e.g.:)
201
+ # "The user named N=35 parallel app/ subdirs, said 'continue until all done',
202
+ # and disavowed cost. isJob=true, rationale=..., suggestedJobId=app-ut-batch,
203
+ # suggestedStrategy=rotating, confidence=high."
204
+ peaks solo detect-job \
205
+ --is-job true \
206
+ --rationale "35 parallel app/ subdirs + 'until all done' + '不用考虑费用'" \
207
+ --suggested-job-id app-ut-batch \
208
+ --suggested-strategy rotating \
209
+ --confidence high
210
+
211
+ # Step 0.81-init: if Job-shaped, init BEFORE Step 1.
212
+ DECISION=$(peaks solo read-job-shape --json)
213
+ if [ "$(echo "$DECISION" | jq -r '.data.decision.isJob')" = "true" ]; then
214
+ JID=$(echo "$DECISION" | jq -r '.data.decision.suggestedJobId')
215
+ STRATEGY=$(echo "$DECISION" | jq -r '.data.decision.suggestedStrategy')
216
+ # LLM-derived slice list (not CLI-derived).
217
+ peaks job init --job-id "$JID" --slice-list "<LLM_DERIVED>" --main-loop-strategy "$STRATEGY" --rotate-every 3 --json
218
+ fi
219
+
220
+ # After Step 7 (RD+QA commit) lands AND Step 0.8 fired (state.json exists):
221
+ peaks job checkpoint --slice-id <rid> --state done --commit-sha $(git rev-parse HEAD)
222
+ peaks job status --job-id <jid> --json
223
+ peaks job subagent-cleanup --job-id <jid> --batch-id <bid> --force # Step 0.87 gate
224
+ # Loop control:
225
+ # remaining > 0 → return to Step 1 (next slice)
226
+ # remaining == 0 → Step 8/9/10/11 (original tail)
227
+ # blocked (strict) → peaks job block + STOP
228
+ # Rotating-mode: every rotateEvery slices → Step 0.86 (peaks session rotate + resume)
229
+ ```