@leing2021/super-pi 0.19.1 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,7 +35,7 @@ Super Pi's answers:
35
35
 
36
36
  Each step has a dedicated skill + tool pair. Not just prompts — structured toolchains.
37
37
 
38
- ### New: Stage model routing + optional auto-continue
38
+ ### New: Stage model routing
39
39
 
40
40
  Configure once in `.pi/settings.json`:
41
41
 
@@ -48,24 +48,22 @@ Configure once in `.pi/settings.json`:
48
48
  "04-review": "claude-sonnet-4-20250514",
49
49
  "05-learn": "claude-haiku-4-20250414",
50
50
  "default": "claude-sonnet-4-20250514"
51
- },
52
- "pipeline": {
53
- "autoContinue": false
54
51
  }
55
52
  }
56
53
  ```
57
54
 
58
55
  How it works:
59
- - Each stage picks `modelStrategy[stage]`, or `modelStrategy.default` as fallback.
56
+ - Model switching is handled automatically by the ce-core extension `input` hook — no manual `/model` needed.
57
+ - When you type `/skill:01-brainstorm` through `/skill:05-learn`, the extension reads `modelStrategy[stage]` (or `modelStrategy.default`) and switches before the skill runs.
58
+ - Supported formats: full reference (`"anthropic/claude-opus-4-1"`) or bare model id (`"claude-opus-4-1"`, reuses current provider).
60
59
  - Every stage prints a `📊 Pipeline Status` block with `Current / Output / Next`.
61
- - If `pipeline.autoContinue=true`, super-pi can trigger the next stage automatically.
62
- - Auto-continue is gate-aware: it will NOT skip required approvals (brainstorm approval, plan/review A/B/C choices).
60
+ - A `Switched model for <stage>: <provider>/<model>` notification appears when the model changes.
63
61
 
64
62
  Quick example:
65
- 1. Run `/skill:01-brainstorm`
63
+ 1. Run `/skill:01-brainstorm` — model auto-switches to the configured brainstorm model
66
64
  2. Approve the design
67
- 3. If `autoContinue=true`, it moves to `/skill:02-plan` automatically
68
- 4. If `autoContinue=false`, it stops after status output and waits for your command
65
+ 3. Run `/skill:02-plan` — model auto-switches to the configured plan model
66
+ 4. Continue through each stage model switches automatically at each step
69
67
 
70
68
  ---
71
69
 
@@ -180,7 +178,7 @@ Single `npm install` output filtered once pays for the entire overhead. Full eva
180
178
 
181
179
  ## Code Scale
182
180
 
183
- ~2500 lines of TypeScript implementing 15 tools, 22 Markdown reference files + 78 rule files driving 10 skills, 162 tests covering all tool logic.
181
+ ~2800 lines of TypeScript implementing 16 tools, 22 Markdown reference files + 78 rule files driving 10 skills, 170 tests covering all tool logic.
184
182
 
185
183
  Not a heavy framework. Each tool has a single responsibility, each skill works independently, and together they form a complete workflow.
186
184
 
@@ -383,6 +381,24 @@ Not a fork. Not a wrapper. Methodologies extracted and rebuilt with Pi's native
383
381
 
384
382
  ## Changelog
385
383
 
384
+ ### 0.19.3 — Terminate fix + runtime model routing + autoContinue removal
385
+ - Fixed 6 ce-core tools (`brainstorm_dialog`, `workflow_state`, `review_router`, `session_checkpoint`, `session_history`, `pattern_extractor`) incorrectly returning `terminate: true`, which caused agent turns to end prematurely (brainstorm questions not shown, "type continue to proceed" interruptions).
386
+ - Implemented runtime stage model routing via ce-core extension `input` hook: reads `.pi/settings.json` `modelStrategy`, auto-switches model before skill execution. Supports full reference (`anthropic/claude-opus-4-1`) and bare model id (`claude-opus-4-1`).
387
+ - Removed `pipeline.autoContinue` configuration (never had runtime implementation; Pi lacks `skill_end` event for auto-continue).
388
+ - Updated `skills/references/pipeline-config.md`, `README.md`, `README_CN.md` to reflect runtime model routing behavior.
389
+ - Added 4 new tests covering terminate regression, input hook model routing, and bare model id parsing.
390
+
391
+ ### 0.19.2 — Evidence-first handoff-lite + docs tracking rule
392
+ - Added `context_handoff` with evidence-first default handoff-lite generation when markdown is omitted.
393
+ - Standardized the shared handoff-lite template across 01-05 workflow handoffs via `skills/references/pipeline-config.md`.
394
+ - Added tests protecting default handoff generation and the shared handoff docs contract.
395
+ - Updated docs tracking so Git only uploads `docs/token-cost-evaluation.md` while other `docs/` artifacts stay local.
396
+
397
+ ### 0.19.1 — Pipeline config + typecheck baseline fix
398
+ - Added shared pipeline config (`skills/references/pipeline-config.md`) for stage model routing via `.pi/settings.json`.
399
+ - Added runtime stage model routing via ce-core extension `input` hook (reads `modelStrategy` from `.pi/settings.json`, auto-switches model before skill execution).
400
+ - Fixed TypeScript baseline issues so `bunx tsc --noEmit` passes.
401
+
386
402
  ### 0.19.0 — 0.69.0 alignment + learn rename
387
403
  - TypeBox migration: `@sinclair/typebox` → `typebox` (zero old-path imports)
388
404
  - Peer/dev dependency upgrade: pi-coding-agent `0.67.6` → `0.69.0`
@@ -1,3 +1,5 @@
1
+ import { readFile } from "node:fs/promises"
2
+ import path from "node:path"
1
3
  import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"
2
4
  import { Type } from "typebox"
3
5
  import { createArtifactHelperTool, type ArtifactType } from "./tools/artifact-helper"
@@ -20,6 +22,65 @@ import { AsyncMutex } from "./tools/async-mutex"
20
22
  import type { SubagentExecOptions, SubagentRunner } from "./tools/subagent"
21
23
 
22
24
  const _subagentEnvMutex = new AsyncMutex()
25
+ const PIPELINE_STAGE_KEYS = new Set([
26
+ "01-brainstorm",
27
+ "02-plan",
28
+ "03-work",
29
+ "04-review",
30
+ "05-learn",
31
+ ])
32
+
33
+ interface ModelStrategySettings {
34
+ modelStrategy?: Record<string, string>
35
+ }
36
+
37
+ async function readProjectSettings(cwd: string): Promise<ModelStrategySettings | null> {
38
+ const settingsPath = path.join(cwd, ".pi", "settings.json")
39
+ try {
40
+ const content = await readFile(settingsPath, "utf8")
41
+ return JSON.parse(content) as ModelStrategySettings
42
+ } catch {
43
+ return null
44
+ }
45
+ }
46
+
47
+ function parseStageSkillName(text: string): string | null {
48
+ const trimmed = text.trim()
49
+ const match = trimmed.match(/^\/skill:([^\s]+)/)
50
+ if (!match) {
51
+ return null
52
+ }
53
+
54
+ const skillName = match[1]
55
+ return PIPELINE_STAGE_KEYS.has(skillName) ? skillName : null
56
+ }
57
+
58
+ function parseModelRef(
59
+ modelRef: string,
60
+ currentProvider?: string,
61
+ ): { provider: string, id: string } | null {
62
+ const trimmed = modelRef.trim()
63
+ if (!trimmed) {
64
+ return null
65
+ }
66
+
67
+ const slashIndex = trimmed.indexOf("/")
68
+ if (slashIndex > 0 && slashIndex < trimmed.length - 1) {
69
+ return {
70
+ provider: trimmed.slice(0, slashIndex),
71
+ id: trimmed.slice(slashIndex + 1),
72
+ }
73
+ }
74
+
75
+ if (!currentProvider) {
76
+ return null
77
+ }
78
+
79
+ return {
80
+ provider: currentProvider,
81
+ id: trimmed,
82
+ }
83
+ }
23
84
 
24
85
  /**
25
86
  * Create a subagent runner that handles env injection with mutex protection
@@ -223,6 +284,62 @@ const patternExtractorParams = Type.Object({
223
284
  })
224
285
 
225
286
  export default function ceCoreExtension(pi: ExtensionAPI) {
287
+ pi.on("input", async (event, ctx) => {
288
+ if (event.source === "extension") {
289
+ return { action: "continue" as const }
290
+ }
291
+
292
+ const stageKey = parseStageSkillName(event.text)
293
+ if (!stageKey) {
294
+ return { action: "continue" as const }
295
+ }
296
+
297
+ const settings = await readProjectSettings(ctx.cwd)
298
+ const modelStrategy = settings?.modelStrategy
299
+ if (!modelStrategy) {
300
+ return { action: "continue" as const }
301
+ }
302
+
303
+ const targetModelRef = modelStrategy[stageKey] ?? modelStrategy.default
304
+ if (!targetModelRef) {
305
+ return { action: "continue" as const }
306
+ }
307
+
308
+ const parsed = parseModelRef(targetModelRef, ctx.model?.provider)
309
+ if (!parsed) {
310
+ if (ctx.hasUI) {
311
+ ctx.ui.notify(`Invalid modelStrategy entry for ${stageKey}: ${targetModelRef}`, "warning")
312
+ }
313
+ return { action: "continue" as const }
314
+ }
315
+
316
+ if (ctx.model?.provider === parsed.provider && ctx.model?.id === parsed.id) {
317
+ return { action: "continue" as const }
318
+ }
319
+
320
+ const model = ctx.modelRegistry.find(parsed.provider, parsed.id)
321
+ if (!model) {
322
+ if (ctx.hasUI) {
323
+ ctx.ui.notify(`Configured model not found for ${stageKey}: ${targetModelRef}`, "warning")
324
+ }
325
+ return { action: "continue" as const }
326
+ }
327
+
328
+ const switched = await pi.setModel(model)
329
+ if (switched) {
330
+ if (ctx.hasUI) {
331
+ ctx.ui.notify(`Switched model for ${stageKey}: ${model.provider}/${model.id}`, "info")
332
+ }
333
+ return { action: "continue" as const }
334
+ }
335
+
336
+ if (ctx.hasUI) {
337
+ ctx.ui.notify(`No API key available for configured model: ${model.provider}/${model.id}`, "warning")
338
+ }
339
+
340
+ return { action: "continue" as const }
341
+ })
342
+
226
343
  const artifactHelper = createArtifactHelperTool()
227
344
  const askUserQuestion = createAskUserQuestionTool()
228
345
  const subagent = createSubagentTool()
@@ -338,7 +455,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
338
455
  return {
339
456
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
340
457
  details: result,
341
- terminate: true,
342
458
  }
343
459
  },
344
460
  })
@@ -393,7 +509,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
393
509
  return {
394
510
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
395
511
  details: result,
396
- terminate: true,
397
512
  }
398
513
  },
399
514
  })
@@ -449,7 +564,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
449
564
  return {
450
565
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
451
566
  details: result,
452
- terminate: true,
453
567
  }
454
568
  },
455
569
  })
@@ -489,7 +603,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
489
603
  return {
490
604
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
491
605
  details: result,
492
- terminate: true,
493
606
  }
494
607
  },
495
608
  })
@@ -531,7 +644,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
531
644
  return {
532
645
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
533
646
  details: result,
534
- terminate: true,
535
647
  }
536
648
  },
537
649
  })
@@ -553,7 +665,6 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
553
665
  return {
554
666
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
555
667
  details: result,
556
- terminate: true,
557
668
  }
558
669
  },
559
670
  })
@@ -0,0 +1,357 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
2
+ import { existsSync } from "node:fs"
3
+ import path from "node:path"
4
+ import { normalizeSlug } from "../utils/name-utils"
5
+
6
+ export type ContextHealth = "good" | "watch" | "heavy" | "critical"
7
+
8
+ export interface ContextHandoffInput {
9
+ operation: "save" | "load" | "latest" | "status"
10
+ repoRoot: string
11
+ currentStage?: string
12
+ nextStage?: string
13
+ contextHealth?: ContextHealth
14
+ activeFiles?: string[]
15
+ blocker?: string
16
+ verification?: string
17
+ artifacts?: Record<string, string | undefined>
18
+ handoffMarkdown?: string
19
+ handoffPath?: string
20
+ }
21
+
22
+ export interface ContextStateEntry {
23
+ currentStage: string
24
+ nextStage?: string
25
+ contextHealth: ContextHealth
26
+ latestHandoffPath?: string
27
+ latestDatedHandoffPath?: string
28
+ activeFiles: string[]
29
+ blocker?: string
30
+ verification?: string
31
+ artifacts: Record<string, string | undefined>
32
+ recommendNewSession: boolean
33
+ updatedAt: string
34
+ }
35
+
36
+ export interface ContextHandoffResult {
37
+ operation: string
38
+ found?: boolean
39
+ path?: string
40
+ latestPath?: string
41
+ currentStage?: string
42
+ nextStage?: string
43
+ contextHealth?: ContextHealth
44
+ activeFiles?: string[]
45
+ blocker?: string
46
+ verification?: string
47
+ artifacts?: Record<string, string | undefined>
48
+ recommendNewSession?: boolean
49
+ handoffMarkdown?: string
50
+ updatedAt?: string
51
+ }
52
+
53
+ function ceDir(repoRoot: string): string {
54
+ return path.join(repoRoot, ".context", "compound-engineering")
55
+ }
56
+
57
+ function handoffDir(repoRoot: string): string {
58
+ return path.join(ceDir(repoRoot), "handoffs")
59
+ }
60
+
61
+ function stateFilePath(repoRoot: string): string {
62
+ return path.join(ceDir(repoRoot), "context-state.json")
63
+ }
64
+
65
+ function latestHandoffPath(repoRoot: string): string {
66
+ return path.join(handoffDir(repoRoot), "latest.md")
67
+ }
68
+
69
+ function toRepoRelative(repoRoot: string, filePath: string): string {
70
+ return path.relative(repoRoot, filePath)
71
+ }
72
+
73
+ function resolveRepoPath(repoRoot: string, filePath: string): string {
74
+ return path.isAbsolute(filePath) ? filePath : path.join(repoRoot, filePath)
75
+ }
76
+
77
+ function stageSlug(value?: string): string {
78
+ if (!value || value.trim().length === 0) return "unknown"
79
+ return normalizeSlug(value)
80
+ }
81
+
82
+ function buildDatedHandoffPath(repoRoot: string, currentStage?: string, nextStage?: string): string {
83
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
84
+ const fileName = `${timestamp}-${stageSlug(currentStage)}-to-${stageSlug(nextStage)}.md`
85
+ return path.join(handoffDir(repoRoot), fileName)
86
+ }
87
+
88
+ function computeRecommendNewSession(currentStage?: string, nextStage?: string, contextHealth: ContextHealth = "watch"): boolean {
89
+ const isCrossPhase = Boolean(currentStage && nextStage && currentStage !== nextStage)
90
+ const isHeavy = contextHealth === "heavy" || contextHealth === "critical"
91
+ return isCrossPhase && isHeavy
92
+ }
93
+
94
+ function formatBullets(items: string[]): string {
95
+ if (items.length === 0) return "- N/A"
96
+ return items.map(item => `- ${item}`).join("\n")
97
+ }
98
+
99
+ function formatArtifacts(artifacts: Record<string, string | undefined>): string {
100
+ const lines = Object.entries(artifacts)
101
+ .filter(([, value]) => Boolean(value && value.trim().length > 0))
102
+ .map(([key, value]) => `- ${key}: ${value}`)
103
+
104
+ if (lines.length === 0) return "- N/A"
105
+ return lines.join("\n")
106
+ }
107
+
108
+ function buildDefaultHandoffMarkdown(input: {
109
+ currentStage: string
110
+ nextStage?: string
111
+ activeFiles: string[]
112
+ artifacts: Record<string, string | undefined>
113
+ blocker?: string
114
+ verification?: string
115
+ }): string {
116
+ const currentTask = input.nextStage
117
+ ? `Continue from ${input.currentStage} to ${input.nextStage}.`
118
+ : `Continue ${input.currentStage}.`
119
+
120
+ const hotContext = formatBullets(input.activeFiles.slice(0, 5))
121
+
122
+ const verifiedFacts = formatBullets([
123
+ `Current stage: ${input.currentStage}`,
124
+ `Next stage: ${input.nextStage ?? "N/A"}`,
125
+ ])
126
+
127
+ const activeFiles = formatBullets(input.activeFiles.slice(0, 5))
128
+ const artifacts = formatArtifacts(input.artifacts)
129
+ const blocker = input.blocker ?? "N/A"
130
+ const verification = input.verification ?? "Not run"
131
+ const nextMinimalStep = input.nextStage ? `/skill:${input.nextStage}` : "N/A"
132
+
133
+ return [
134
+ "## Current Task",
135
+ currentTask,
136
+ "",
137
+ "## Hot Context",
138
+ hotContext,
139
+ "",
140
+ "## Verified Facts",
141
+ verifiedFacts,
142
+ "",
143
+ "## Active Files",
144
+ activeFiles,
145
+ "",
146
+ "## Artifacts",
147
+ artifacts,
148
+ "",
149
+ "## Current Blocker",
150
+ `- ${blocker}`,
151
+ "",
152
+ "## Verification",
153
+ `- ${verification}`,
154
+ "",
155
+ "## Do Not Repeat",
156
+ "- Do not reload full history unless the handoff lacks required evidence.",
157
+ "",
158
+ "## Next Minimal Step",
159
+ `- ${nextMinimalStep}`,
160
+ "",
161
+ ].join("\n")
162
+ }
163
+
164
+ async function readState(repoRoot: string): Promise<ContextStateEntry | null> {
165
+ const filePath = stateFilePath(repoRoot)
166
+ if (!existsSync(filePath)) return null
167
+
168
+ try {
169
+ const content = await readFile(filePath, "utf8")
170
+ return JSON.parse(content) as ContextStateEntry
171
+ } catch {
172
+ return null
173
+ }
174
+ }
175
+
176
+ async function writeState(repoRoot: string, state: ContextStateEntry): Promise<void> {
177
+ const filePath = stateFilePath(repoRoot)
178
+ await mkdir(path.dirname(filePath), { recursive: true })
179
+ await writeFile(filePath, JSON.stringify(state, null, 2), "utf8")
180
+ }
181
+
182
+ export function createContextHandoffTool() {
183
+ return {
184
+ name: "context_handoff",
185
+ async execute(input: ContextHandoffInput): Promise<ContextHandoffResult> {
186
+ switch (input.operation) {
187
+ case "save":
188
+ return save(input)
189
+ case "load":
190
+ return load(input)
191
+ case "latest":
192
+ return latest(input)
193
+ case "status":
194
+ return status(input)
195
+ default:
196
+ throw new Error(`Unknown operation: ${input.operation}`)
197
+ }
198
+ },
199
+ }
200
+ }
201
+
202
+ async function save(input: ContextHandoffInput): Promise<ContextHandoffResult> {
203
+ const currentStage = input.currentStage ?? "unknown"
204
+ const nextStage = input.nextStage
205
+ const contextHealth = input.contextHealth ?? "watch"
206
+ const activeFiles = input.activeFiles ?? []
207
+ const blocker = input.blocker
208
+ const verification = input.verification
209
+ const artifacts = input.artifacts ?? {}
210
+ const handoffMarkdown = input.handoffMarkdown?.trim().length
211
+ ? input.handoffMarkdown
212
+ : buildDefaultHandoffMarkdown({
213
+ currentStage,
214
+ nextStage,
215
+ activeFiles,
216
+ artifacts,
217
+ blocker,
218
+ verification,
219
+ })
220
+
221
+ const recommendNewSession = computeRecommendNewSession(currentStage, nextStage, contextHealth)
222
+ const latestPath = latestHandoffPath(input.repoRoot)
223
+ const datedPath = buildDatedHandoffPath(input.repoRoot, currentStage, nextStage)
224
+ const relativeLatestPath = toRepoRelative(input.repoRoot, latestPath)
225
+ const relativeDatedPath = toRepoRelative(input.repoRoot, datedPath)
226
+
227
+ await mkdir(path.dirname(latestPath), { recursive: true })
228
+ await writeFile(latestPath, handoffMarkdown, "utf8")
229
+ await writeFile(datedPath, handoffMarkdown, "utf8")
230
+
231
+ const state: ContextStateEntry = {
232
+ currentStage,
233
+ nextStage,
234
+ contextHealth,
235
+ latestHandoffPath: relativeLatestPath,
236
+ latestDatedHandoffPath: relativeDatedPath,
237
+ activeFiles,
238
+ blocker,
239
+ verification,
240
+ artifacts,
241
+ recommendNewSession,
242
+ updatedAt: new Date().toISOString(),
243
+ }
244
+
245
+ await writeState(input.repoRoot, state)
246
+
247
+ return {
248
+ operation: "save",
249
+ found: true,
250
+ path: relativeDatedPath,
251
+ latestPath: relativeLatestPath,
252
+ currentStage,
253
+ nextStage,
254
+ contextHealth,
255
+ activeFiles,
256
+ blocker,
257
+ verification,
258
+ artifacts,
259
+ recommendNewSession,
260
+ updatedAt: state.updatedAt,
261
+ }
262
+ }
263
+
264
+ async function load(input: ContextHandoffInput): Promise<ContextHandoffResult> {
265
+ const state = await readState(input.repoRoot)
266
+ if (!state) {
267
+ return {
268
+ operation: "load",
269
+ found: false,
270
+ contextHealth: "watch",
271
+ recommendNewSession: false,
272
+ }
273
+ }
274
+
275
+ const targetPath = input.handoffPath ?? state.latestHandoffPath ?? latestHandoffPath(input.repoRoot)
276
+ const absoluteTargetPath = resolveRepoPath(input.repoRoot, targetPath)
277
+ let markdown = ""
278
+ if (existsSync(absoluteTargetPath)) {
279
+ markdown = await readFile(absoluteTargetPath, "utf8")
280
+ }
281
+
282
+ return {
283
+ operation: "load",
284
+ found: true,
285
+ path: targetPath,
286
+ latestPath: state.latestHandoffPath,
287
+ currentStage: state.currentStage,
288
+ nextStage: state.nextStage,
289
+ contextHealth: state.contextHealth,
290
+ activeFiles: state.activeFiles,
291
+ blocker: state.blocker,
292
+ verification: state.verification,
293
+ artifacts: state.artifacts,
294
+ recommendNewSession: state.recommendNewSession,
295
+ handoffMarkdown: markdown,
296
+ updatedAt: state.updatedAt,
297
+ }
298
+ }
299
+
300
+ async function latest(input: ContextHandoffInput): Promise<ContextHandoffResult> {
301
+ const state = await readState(input.repoRoot)
302
+ if (!state || !state.latestHandoffPath) {
303
+ return {
304
+ operation: "latest",
305
+ found: false,
306
+ contextHealth: "watch",
307
+ recommendNewSession: false,
308
+ }
309
+ }
310
+
311
+ return {
312
+ operation: "latest",
313
+ found: true,
314
+ path: state.latestDatedHandoffPath,
315
+ latestPath: state.latestHandoffPath,
316
+ currentStage: state.currentStage,
317
+ nextStage: state.nextStage,
318
+ contextHealth: state.contextHealth,
319
+ activeFiles: state.activeFiles,
320
+ blocker: state.blocker,
321
+ verification: state.verification,
322
+ artifacts: state.artifacts,
323
+ recommendNewSession: state.recommendNewSession,
324
+ updatedAt: state.updatedAt,
325
+ }
326
+ }
327
+
328
+ async function status(input: ContextHandoffInput): Promise<ContextHandoffResult> {
329
+ const state = await readState(input.repoRoot)
330
+
331
+ if (!state) {
332
+ return {
333
+ operation: "status",
334
+ found: false,
335
+ contextHealth: "watch",
336
+ recommendNewSession: false,
337
+ activeFiles: [],
338
+ artifacts: {},
339
+ }
340
+ }
341
+
342
+ return {
343
+ operation: "status",
344
+ found: true,
345
+ path: state.latestDatedHandoffPath,
346
+ latestPath: state.latestHandoffPath,
347
+ currentStage: state.currentStage,
348
+ nextStage: state.nextStage,
349
+ contextHealth: state.contextHealth,
350
+ activeFiles: state.activeFiles,
351
+ blocker: state.blocker,
352
+ verification: state.verification,
353
+ artifacts: state.artifacts,
354
+ recommendNewSession: state.recommendNewSession,
355
+ updatedAt: state.updatedAt,
356
+ }
357
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leing2021/super-pi",
3
- "version": "0.19.1",
3
+ "version": "0.19.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Pi-native Compound Engineering package for iterative development workflows",
@@ -6,3 +6,6 @@ When the requirements artifact is ready:
6
6
  2. Summarize the recommended direction in 2-3 bullets.
7
7
  3. Recommend `02-plan` as the next step.
8
8
  4. If key ambiguity remains, say so before handing off.
9
+ 5. Provide `🧠 Context Status` (health, handoff path, active files, new-session recommendation).
10
+ 6. Save/mention handoff-lite path under `.context/compound-engineering/handoffs/` using the shared `Handoff-lite template` in `skills/references/pipeline-config.md`.
11
+ 7. Recommend new session only when cross-phase + health is heavy/critical, and include a copyable prompt.
@@ -6,3 +6,6 @@ When the plan is ready:
6
6
  2. Summarize the main implementation units.
7
7
  3. Recommend `03-work` as the next step.
8
8
  4. Call out any remaining assumptions or open risks.
9
+ 5. Provide `🧠 Context Status` (health, handoff path, active files, new-session recommendation).
10
+ 6. Save/mention handoff-lite path under `.context/compound-engineering/handoffs/` using the shared `Handoff-lite template` in `skills/references/pipeline-config.md`.
11
+ 7. Recommend new session only when cross-phase + health is heavy/critical, and include a copyable prompt.
@@ -6,3 +6,7 @@ When execution reaches a meaningful checkpoint:
6
6
  2. Report the latest verification results.
7
7
  3. Recommend `04-review` as the next step.
8
8
  4. Mention any remaining implementation risk.
9
+ 5. Include checkpoint fields: `activeFiles`, `currentUnit`, `blocker`, `verification`, `contextTiers`, `handoffPath`.
10
+ 6. Provide `🧠 Context Status` (health, handoff path, active files, new-session recommendation).
11
+ 7. Save/mention handoff-lite path under `.context/compound-engineering/handoffs/` using the shared `Handoff-lite template` in `skills/references/pipeline-config.md`.
12
+ 8. Recommend new session only when cross-phase + health is heavy/critical, and include a copyable prompt.
@@ -8,6 +8,8 @@ When the review is complete:
8
8
  4. After autofix, report what was changed and whether re-review confirms the fix.
9
9
  5. Recommend `05-learn` when the review surfaced a reusable learning or newly solved problem.
10
10
  6. Mention any relevant plan or solution artifacts referenced during review.
11
+ 7. Provide `🧠 Context Status` (health, handoff path, active files, new-session recommendation).
12
+ 8. Save/mention handoff-lite path under `.context/compound-engineering/handoffs/` using the shared `Handoff-lite template` in `skills/references/pipeline-config.md`.
11
13
 
12
14
  ## Autofix loop
13
15
 
@@ -32,5 +32,7 @@ See [shared pipeline instructions](../references/pipeline-config.md) for model r
32
32
  5. Choose the correct category using `references/category-map.md`.
33
33
  6. Write or update the solution artifact under `docs/solutions/<category>/`.
34
34
  7. Mention how future `02-plan` and `04-review` runs should benefit from the new learning.
35
+ 8. Include `🧠 Context Status` (health, handoff path, active files, new-session recommendation) for workflow closure.
36
+ 9. Save/mention handoff-lite path under `.context/compound-engineering/handoffs/` using the shared `Handoff-lite template` in `skills/references/pipeline-config.md`.
35
37
 
36
38
  Before finishing this skill, apply the completion checklist in [shared pipeline instructions](../references/pipeline-config.md).
@@ -4,23 +4,23 @@ Use these rules in all Phase 1 skills: `01-brainstorm` → `02-plan` → `03-wor
4
4
 
5
5
  ## Start of skill: model routing
6
6
 
7
- Run this checklist before normal skill workflow:
7
+ Model routing is handled automatically by the ce-core extension's `input` hook.
8
+ When a user types `/skill:01-brainstorm` through `/skill:05-learn`, the extension:
8
9
 
9
- 1. Read `.pi/settings.json` from the project root.
10
- 2. Parse `modelStrategy` (if missing, skip switching).
11
- 3. Resolve current stage key:
12
- - `01-brainstorm`
13
- - `02-plan`
14
- - `03-work`
15
- - `04-review`
16
- - `05-learn`
17
- 4. Pick `targetModel = modelStrategy[stageKey] ?? modelStrategy.default`.
18
- 5. If `targetModel` exists and differs from the current model, run `/model <targetModel>`.
19
- 6. If switching fails, continue with current model and mention the failure once.
10
+ 1. Reads `.pi/settings.json` from the project root.
11
+ 2. Parses `modelStrategy[stageKey]` or falls back to `modelStrategy.default`.
12
+ 3. If the target model differs from the current model, calls `pi.setModel()`.
13
+ 4. If switching fails, notifies the user and continues with the current model.
20
14
 
21
- ## End of skill: status + optional auto-continue
15
+ No manual `/model` command is needed. The skill itself does not need to handle model switching.
22
16
 
23
- Before final completion, always output this block (replace placeholders with real values, never output angle-bracket placeholders literally):
17
+ Supported `modelStrategy` formats in `.pi/settings.json`:
18
+ - Full reference: `"02-plan": "anthropic/claude-opus-4-1"`
19
+ - Bare model id (reuses current provider): `"02-plan": "claude-opus-4-1"`
20
+
21
+ ## End of skill: status + context
22
+
23
+ Before final completion, always output these blocks (replace placeholders with real values, never output angle-bracket placeholders literally):
24
24
 
25
25
  ```
26
26
  ---
@@ -29,6 +29,13 @@ Before final completion, always output this block (replace placeholders with rea
29
29
  - Output: <main artifact path or N/A>
30
30
  - Next: <next skill command or Completed>
31
31
  ---
32
+
33
+ 🧠 Context Status
34
+ - Health: good | watch | heavy | critical
35
+ - Handoff: <path or N/A>
36
+ - Active: <1-5 active files or N/A>
37
+ - New session: recommended | not needed
38
+ ---
32
39
  ```
33
40
 
34
41
  Next step mapping:
@@ -38,12 +45,83 @@ Next step mapping:
38
45
  - `04-review` → `/skill:05-learn`
39
46
  - `05-learn` → `Completed`
40
47
 
41
- Then read `.pi/settings.json` → `pipeline.autoContinue`:
42
- - If `false` or missing, stop after the status block and wait for user input.
43
- - If current stage is `05-learn`, stop after status block.
44
- - If `true` and current stage is not `05-learn`, auto-continue is allowed only after stage-specific gates are satisfied:
45
- - `01-brainstorm`: user has explicitly approved the design handoff.
46
- - `02-plan`: review choice is resolved (A/B/C flow completed) and user confirmed to proceed.
47
- - `04-review`: optional QA choice is resolved (A/B/C flow completed) and user confirmed to proceed.
48
- - Any unclear/ambiguous state: do not auto-continue; stop and ask.
49
- - When gates are satisfied, automatically trigger the mapped next skill command.
48
+ ### Handoff-lite template
49
+
50
+ When a stage produces or updates handoff-lite, use this evidence-first structure and keep it concise (target <= 1500 tokens):
51
+
52
+ ```md
53
+ ## Current Task
54
+
55
+ ## Hot Context
56
+ - 1-5 must-know facts for the next step
57
+
58
+ ## Verified Facts
59
+ - already validated facts (do not re-prove)
60
+
61
+ ## Active Files
62
+ - 1-5 file paths only
63
+
64
+ ## Artifacts
65
+ - requirements: <path or N/A>
66
+ - plan: <path or N/A>
67
+ - checkpoint: <path or N/A>
68
+ - proof: <path or N/A>
69
+
70
+ ## Current Blocker
71
+ - <blocker or N/A>
72
+
73
+ ## Verification
74
+ - <latest command + result or Not run>
75
+
76
+ ## Do Not Repeat
77
+ - what should not be re-read/re-run unless needed
78
+
79
+ ## Next Minimal Step
80
+ - exact next command/action
81
+ ```
82
+
83
+ Rules:
84
+ - Use `N/A` instead of inventing facts.
85
+ - Keep broad history in artifact paths, not expanded narrative.
86
+ - If `context_handoff` is unavailable, manually write this shape to `.context/compound-engineering/handoffs/latest.md` and mention the path.
87
+
88
+ ### New-session recommendation rule
89
+
90
+ Recommend a new session only when both are true:
91
+
92
+ 1. Phase is changing (`Current` != next stage)
93
+ 2. Context health is `heavy` or `critical`
94
+
95
+ When recommending a new session, include a directly copyable prompt:
96
+
97
+ ```md
98
+ ## 建议新开 Session
99
+
100
+ 原因:当前 `<current stage>` 已完成,下一步将进入 `<next stage>`。当前窗口已经包含较多已完成阶段上下文,继续执行会降低 Token ROI,并增加旧上下文干扰后续判断的风险。
101
+
102
+ 建议:新开一个窗口,把下面这段 Prompt 复制进去即可继续。
103
+
104
+ ```text
105
+ 继续这个 Super Pi workflow,不要重新开始。
106
+
107
+ Repo: <repo path>
108
+
109
+ 请先读取:
110
+ - <latest plan/requirements artifact>
111
+ - <latest handoff-lite path>
112
+ - <latest checkpoint path or summary>
113
+
114
+ 然后继续:
115
+ - 执行 <next skill command>
116
+
117
+ 上下文策略:
118
+ - hot: 仅保留当前执行必须文件(1-5)
119
+ - warm: 通过 artifact path 按需回读
120
+ - cold: 不进入当前窗口,除非明确需要
121
+
122
+ 核心原则:
123
+ - 不重复已完成阶段
124
+ - 优先验证当前阶段输出
125
+ - 控制 token,保持高 ROI
126
+ ```
127
+ ```