@leing2021/super-pi 0.23.1 → 0.23.2

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.
@@ -326,6 +326,11 @@ const contextHandoffParams = Type.Object({
326
326
  artifacts: Type.Optional(Type.Record(Type.String(), Type.Optional(Type.String()), { description: "Artifact paths (requirements, plan, checkpoint, proof)" })),
327
327
  handoffMarkdown: Type.Optional(Type.String({ description: "Custom handoff markdown content" })),
328
328
  handoffPath: Type.Optional(Type.String({ description: "Specific handoff file path to load" })),
329
+ currentTruth: Type.Optional(Type.Array(Type.String(), { description: "Known true statements validated during session" })),
330
+ invalidatedAssumptions: Type.Optional(Type.Array(Type.String(), { description: "Assumptions proven wrong during session" })),
331
+ openDecisions: Type.Optional(Type.Array(Type.String(), { description: "Pending decisions that affect next steps" })),
332
+ recentlyAccessedFiles: Type.Optional(Type.Array(Type.String(), { description: "Files recently read or edited (defaults to activeFiles)" })),
333
+ compressionRisk: Type.Optional(Type.Array(Type.String(), { description: "Context compression risks to watch for" })),
329
334
  })
330
335
 
331
336
  const patternExtractorParams = Type.Object({
@@ -758,6 +763,11 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
758
763
  artifacts: params.artifacts as Record<string, string | undefined> | undefined,
759
764
  handoffMarkdown: params.handoffMarkdown,
760
765
  handoffPath: params.handoffPath,
766
+ currentTruth: params.currentTruth,
767
+ invalidatedAssumptions: params.invalidatedAssumptions,
768
+ openDecisions: params.openDecisions,
769
+ recentlyAccessedFiles: params.recentlyAccessedFiles,
770
+ compressionRisk: params.compressionRisk,
761
771
  })
762
772
 
763
773
  return {
@@ -17,6 +17,11 @@ export interface ContextHandoffInput {
17
17
  artifacts?: Record<string, string | undefined>
18
18
  handoffMarkdown?: string
19
19
  handoffPath?: string
20
+ currentTruth?: string[]
21
+ invalidatedAssumptions?: string[]
22
+ openDecisions?: string[]
23
+ recentlyAccessedFiles?: string[]
24
+ compressionRisk?: string[]
20
25
  }
21
26
 
22
27
  export interface ContextStateEntry {
@@ -29,6 +34,11 @@ export interface ContextStateEntry {
29
34
  blocker?: string
30
35
  verification?: string
31
36
  artifacts: Record<string, string | undefined>
37
+ currentTruth: string[]
38
+ invalidatedAssumptions: string[]
39
+ openDecisions: string[]
40
+ recentlyAccessedFiles: string[]
41
+ compressionRisk: string[]
32
42
  recommendNewSession: boolean
33
43
  updatedAt: string
34
44
  }
@@ -47,6 +57,11 @@ export interface ContextHandoffResult {
47
57
  artifacts?: Record<string, string | undefined>
48
58
  recommendNewSession?: boolean
49
59
  handoffMarkdown?: string
60
+ currentTruth?: string[]
61
+ invalidatedAssumptions?: string[]
62
+ openDecisions?: string[]
63
+ recentlyAccessedFiles?: string[]
64
+ compressionRisk?: string[]
50
65
  updatedAt?: string
51
66
  }
52
67
 
@@ -112,6 +127,11 @@ function buildDefaultHandoffMarkdown(input: {
112
127
  artifacts: Record<string, string | undefined>
113
128
  blocker?: string
114
129
  verification?: string
130
+ currentTruth: string[]
131
+ invalidatedAssumptions: string[]
132
+ openDecisions: string[]
133
+ recentlyAccessedFiles: string[]
134
+ compressionRisk: string[]
115
135
  }): string {
116
136
  const currentTask = input.nextStage
117
137
  ? `Continue from ${input.currentStage} to ${input.nextStage}.`
@@ -137,12 +157,24 @@ function buildDefaultHandoffMarkdown(input: {
137
157
  "## Hot Context",
138
158
  hotContext,
139
159
  "",
160
+ "## Current Truth",
161
+ formatBullets(input.currentTruth),
162
+ "",
140
163
  "## Verified Facts",
141
164
  verifiedFacts,
142
165
  "",
166
+ "## Invalidated Assumptions",
167
+ formatBullets(input.invalidatedAssumptions),
168
+ "",
169
+ "## Open Decisions",
170
+ formatBullets(input.openDecisions),
171
+ "",
143
172
  "## Active Files",
144
173
  activeFiles,
145
174
  "",
175
+ "## Recently Accessed Files",
176
+ formatBullets(input.recentlyAccessedFiles),
177
+ "",
146
178
  "## Artifacts",
147
179
  artifacts,
148
180
  "",
@@ -152,6 +184,9 @@ function buildDefaultHandoffMarkdown(input: {
152
184
  "## Verification",
153
185
  `- ${verification}`,
154
186
  "",
187
+ "## Compression Risk",
188
+ formatBullets(input.compressionRisk),
189
+ "",
155
190
  "## Do Not Repeat",
156
191
  "- Do not reload full history unless the handoff lacks required evidence.",
157
192
  "",
@@ -161,13 +196,54 @@ function buildDefaultHandoffMarkdown(input: {
161
196
  ].join("\n")
162
197
  }
163
198
 
199
+ function toStringArray(value: unknown): string[] {
200
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : []
201
+ }
202
+
203
+ function normalizeStateEntry(raw: unknown): ContextStateEntry | null {
204
+ if (!raw || typeof raw !== "object") return null
205
+
206
+ const state = raw as Record<string, unknown>
207
+ const activeFiles = toStringArray(state.activeFiles)
208
+
209
+ return {
210
+ currentStage: typeof state.currentStage === "string" ? state.currentStage : "unknown",
211
+ nextStage: typeof state.nextStage === "string" ? state.nextStage : undefined,
212
+ contextHealth: isContextHealth(state.contextHealth) ? state.contextHealth : "watch",
213
+ latestHandoffPath: typeof state.latestHandoffPath === "string" ? state.latestHandoffPath : undefined,
214
+ latestDatedHandoffPath: typeof state.latestDatedHandoffPath === "string" ? state.latestDatedHandoffPath : undefined,
215
+ activeFiles,
216
+ blocker: typeof state.blocker === "string" ? state.blocker : undefined,
217
+ verification: typeof state.verification === "string" ? state.verification : undefined,
218
+ artifacts: isStringRecord(state.artifacts) ? state.artifacts : {},
219
+ currentTruth: toStringArray(state.currentTruth),
220
+ invalidatedAssumptions: toStringArray(state.invalidatedAssumptions),
221
+ openDecisions: toStringArray(state.openDecisions),
222
+ recentlyAccessedFiles: toStringArray(state.recentlyAccessedFiles).length > 0
223
+ ? toStringArray(state.recentlyAccessedFiles)
224
+ : activeFiles.slice(0, 5),
225
+ compressionRisk: toStringArray(state.compressionRisk),
226
+ recommendNewSession: typeof state.recommendNewSession === "boolean" ? state.recommendNewSession : false,
227
+ updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : new Date(0).toISOString(),
228
+ }
229
+ }
230
+
231
+ function isContextHealth(value: unknown): value is ContextHealth {
232
+ return value === "good" || value === "watch" || value === "heavy" || value === "critical"
233
+ }
234
+
235
+ function isStringRecord(value: unknown): value is Record<string, string | undefined> {
236
+ if (!value || typeof value !== "object") return false
237
+ return Object.values(value).every(item => item === undefined || typeof item === "string")
238
+ }
239
+
164
240
  async function readState(repoRoot: string): Promise<ContextStateEntry | null> {
165
241
  const filePath = stateFilePath(repoRoot)
166
242
  if (!existsSync(filePath)) return null
167
243
 
168
244
  try {
169
245
  const content = await readFile(filePath, "utf8")
170
- return JSON.parse(content) as ContextStateEntry
246
+ return normalizeStateEntry(JSON.parse(content))
171
247
  } catch {
172
248
  return null
173
249
  }
@@ -207,6 +283,14 @@ async function save(input: ContextHandoffInput): Promise<ContextHandoffResult> {
207
283
  const blocker = input.blocker
208
284
  const verification = input.verification
209
285
  const artifacts = input.artifacts ?? {}
286
+ const currentTruth = input.currentTruth ?? []
287
+ const invalidatedAssumptions = input.invalidatedAssumptions ?? []
288
+ const openDecisions = input.openDecisions ?? []
289
+ const recentlyAccessedFiles = input.recentlyAccessedFiles?.length
290
+ ? input.recentlyAccessedFiles
291
+ : activeFiles.slice(0, 5)
292
+ const compressionRisk = input.compressionRisk ?? []
293
+
210
294
  const handoffMarkdown = input.handoffMarkdown?.trim().length
211
295
  ? input.handoffMarkdown
212
296
  : buildDefaultHandoffMarkdown({
@@ -216,6 +300,11 @@ async function save(input: ContextHandoffInput): Promise<ContextHandoffResult> {
216
300
  artifacts,
217
301
  blocker,
218
302
  verification,
303
+ currentTruth,
304
+ invalidatedAssumptions,
305
+ openDecisions,
306
+ recentlyAccessedFiles,
307
+ compressionRisk,
219
308
  })
220
309
 
221
310
  const recommendNewSession = computeRecommendNewSession(currentStage, nextStage, contextHealth)
@@ -238,6 +327,11 @@ async function save(input: ContextHandoffInput): Promise<ContextHandoffResult> {
238
327
  blocker,
239
328
  verification,
240
329
  artifacts,
330
+ currentTruth,
331
+ invalidatedAssumptions,
332
+ openDecisions,
333
+ recentlyAccessedFiles,
334
+ compressionRisk,
241
335
  recommendNewSession,
242
336
  updatedAt: new Date().toISOString(),
243
337
  }
@@ -256,6 +350,11 @@ async function save(input: ContextHandoffInput): Promise<ContextHandoffResult> {
256
350
  blocker,
257
351
  verification,
258
352
  artifacts,
353
+ currentTruth,
354
+ invalidatedAssumptions,
355
+ openDecisions,
356
+ recentlyAccessedFiles,
357
+ compressionRisk,
259
358
  recommendNewSession,
260
359
  updatedAt: state.updatedAt,
261
360
  }
@@ -291,6 +390,11 @@ async function load(input: ContextHandoffInput): Promise<ContextHandoffResult> {
291
390
  blocker: state.blocker,
292
391
  verification: state.verification,
293
392
  artifacts: state.artifacts,
393
+ currentTruth: state.currentTruth,
394
+ invalidatedAssumptions: state.invalidatedAssumptions,
395
+ openDecisions: state.openDecisions,
396
+ recentlyAccessedFiles: state.recentlyAccessedFiles,
397
+ compressionRisk: state.compressionRisk,
294
398
  recommendNewSession: state.recommendNewSession,
295
399
  handoffMarkdown: markdown,
296
400
  updatedAt: state.updatedAt,
@@ -320,6 +424,11 @@ async function latest(input: ContextHandoffInput): Promise<ContextHandoffResult>
320
424
  blocker: state.blocker,
321
425
  verification: state.verification,
322
426
  artifacts: state.artifacts,
427
+ currentTruth: state.currentTruth,
428
+ invalidatedAssumptions: state.invalidatedAssumptions,
429
+ openDecisions: state.openDecisions,
430
+ recentlyAccessedFiles: state.recentlyAccessedFiles,
431
+ compressionRisk: state.compressionRisk,
323
432
  recommendNewSession: state.recommendNewSession,
324
433
  updatedAt: state.updatedAt,
325
434
  }
@@ -351,6 +460,11 @@ async function status(input: ContextHandoffInput): Promise<ContextHandoffResult>
351
460
  blocker: state.blocker,
352
461
  verification: state.verification,
353
462
  artifacts: state.artifacts,
463
+ currentTruth: state.currentTruth,
464
+ invalidatedAssumptions: state.invalidatedAssumptions,
465
+ openDecisions: state.openDecisions,
466
+ recentlyAccessedFiles: state.recentlyAccessedFiles,
467
+ compressionRisk: state.compressionRisk,
354
468
  recommendNewSession: state.recommendNewSession,
355
469
  updatedAt: state.updatedAt,
356
470
  }
@@ -1,4 +1,4 @@
1
- import { readdirSync, statSync, existsSync } from "node:fs"
1
+ import { readdirSync, existsSync, readFileSync } from "node:fs"
2
2
  import path from "node:path"
3
3
 
4
4
  export interface WorkflowCategoryState {
@@ -6,6 +6,25 @@ export interface WorkflowCategoryState {
6
6
  latest: string | null
7
7
  }
8
8
 
9
+ export interface WorkflowContextState {
10
+ found: boolean
11
+ currentStage?: string
12
+ nextStage?: string
13
+ contextHealth?: string
14
+ latestHandoffPath?: string
15
+ latestDatedHandoffPath?: string
16
+ activeFiles: string[]
17
+ recentlyAccessedFiles: string[]
18
+ blocker?: string
19
+ verification?: string
20
+ currentTruth: string[]
21
+ invalidatedAssumptions: string[]
22
+ openDecisions: string[]
23
+ compressionRisk: string[]
24
+ recommendNewSession?: boolean
25
+ updatedAt?: string
26
+ }
27
+
9
28
  export interface WorkflowStateInput {
10
29
  repoRoot: string
11
30
  }
@@ -15,6 +34,7 @@ export interface WorkflowStateResult {
15
34
  plans: WorkflowCategoryState
16
35
  solutions: WorkflowCategoryState
17
36
  runs: WorkflowCategoryState
37
+ context: WorkflowContextState
18
38
  }
19
39
 
20
40
  function emptyCategory(): WorkflowCategoryState {
@@ -63,6 +83,52 @@ function collectFiles(dirPath: string): string[] {
63
83
  return results
64
84
  }
65
85
 
86
+ function emptyContext(): WorkflowContextState {
87
+ return {
88
+ found: false,
89
+ activeFiles: [],
90
+ recentlyAccessedFiles: [],
91
+ currentTruth: [],
92
+ invalidatedAssumptions: [],
93
+ openDecisions: [],
94
+ compressionRisk: [],
95
+ }
96
+ }
97
+
98
+ function toStringArray(value: unknown): string[] {
99
+ return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : []
100
+ }
101
+
102
+ function readContextState(repoRoot: string): WorkflowContextState {
103
+ const statePath = path.join(repoRoot, ".context", "compound-engineering", "context-state.json")
104
+ if (!existsSync(statePath)) return emptyContext()
105
+
106
+ try {
107
+ const raw = readFileSync(statePath, "utf8")
108
+ const state = JSON.parse(raw) as Record<string, unknown>
109
+ return {
110
+ found: true,
111
+ currentStage: typeof state.currentStage === "string" ? state.currentStage : undefined,
112
+ nextStage: typeof state.nextStage === "string" ? state.nextStage : undefined,
113
+ contextHealth: typeof state.contextHealth === "string" ? state.contextHealth : undefined,
114
+ latestHandoffPath: typeof state.latestHandoffPath === "string" ? state.latestHandoffPath : undefined,
115
+ latestDatedHandoffPath: typeof state.latestDatedHandoffPath === "string" ? state.latestDatedHandoffPath : undefined,
116
+ activeFiles: toStringArray(state.activeFiles),
117
+ recentlyAccessedFiles: toStringArray(state.recentlyAccessedFiles),
118
+ blocker: typeof state.blocker === "string" ? state.blocker : undefined,
119
+ verification: typeof state.verification === "string" ? state.verification : undefined,
120
+ currentTruth: toStringArray(state.currentTruth),
121
+ invalidatedAssumptions: toStringArray(state.invalidatedAssumptions),
122
+ openDecisions: toStringArray(state.openDecisions),
123
+ compressionRisk: toStringArray(state.compressionRisk),
124
+ recommendNewSession: typeof state.recommendNewSession === "boolean" ? state.recommendNewSession : undefined,
125
+ updatedAt: typeof state.updatedAt === "string" ? state.updatedAt : undefined,
126
+ }
127
+ } catch {
128
+ return emptyContext()
129
+ }
130
+ }
131
+
66
132
  export function createWorkflowStateTool() {
67
133
  return {
68
134
  name: "workflow_state",
@@ -74,6 +140,7 @@ export function createWorkflowStateTool() {
74
140
  plans: scanDir(path.join(repoRoot, "docs", "plans")),
75
141
  solutions: scanDir(path.join(repoRoot, "docs", "solutions")),
76
142
  runs: scanDir(path.join(repoRoot, ".context", "compound-engineering")),
143
+ context: readContextState(repoRoot),
77
144
  }
78
145
  },
79
146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leing2021/super-pi",
3
- "version": "0.23.1",
3
+ "version": "0.23.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Pi-native Compound Engineering package for iterative development workflows",
@@ -55,12 +55,24 @@ When a stage produces or updates handoff-lite, use this evidence-first structure
55
55
  ## Hot Context
56
56
  - 1-5 must-know facts for the next step
57
57
 
58
+ ## Current Truth
59
+ - validated truths that must survive compression
60
+
58
61
  ## Verified Facts
59
62
  - already validated facts (do not re-prove)
60
63
 
64
+ ## Invalidated Assumptions
65
+ - assumptions proven wrong this session
66
+
67
+ ## Open Decisions
68
+ - pending decisions that affect next steps
69
+
61
70
  ## Active Files
62
71
  - 1-5 file paths only
63
72
 
73
+ ## Recently Accessed Files
74
+ - files recently read or edited
75
+
64
76
  ## Artifacts
65
77
  - requirements: <path or N/A>
66
78
  - plan: <path or N/A>
@@ -73,6 +85,9 @@ When a stage produces or updates handoff-lite, use this evidence-first structure
73
85
  ## Verification
74
86
  - <latest command + result or Not run>
75
87
 
88
+ ## Compression Risk
89
+ - context compression risks to watch for
90
+
76
91
  ## Do Not Repeat
77
92
  - what should not be re-read/re-run unless needed
78
93