@leing2021/super-pi 0.23.2 → 0.23.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.
|
@@ -310,6 +310,7 @@ const contextHandoffParams = Type.Object({
|
|
|
310
310
|
Type.Literal("load"),
|
|
311
311
|
Type.Literal("latest"),
|
|
312
312
|
Type.Literal("status"),
|
|
313
|
+
Type.Literal("validate"),
|
|
313
314
|
], { description: "Handoff operation" }),
|
|
314
315
|
repoRoot: Type.String({ description: "Repository root" }),
|
|
315
316
|
currentStage: Type.Optional(Type.String({ description: "Current pipeline stage (e.g. 02-plan)" })),
|
|
@@ -393,18 +394,21 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
|
|
|
393
394
|
if (thinkingStrategy) {
|
|
394
395
|
const targetThinking = thinkingStrategy[stageKey] ?? thinkingStrategy.default
|
|
395
396
|
if (targetThinking) {
|
|
396
|
-
const levelMap: Record<string, "
|
|
397
|
+
const levelMap: Record<string, ReturnType<ExtensionAPI["getThinkingLevel"]>> = {
|
|
398
|
+
off: "off",
|
|
399
|
+
minimal: "minimal",
|
|
397
400
|
low: "low",
|
|
398
401
|
medium: "medium",
|
|
399
402
|
high: "high",
|
|
403
|
+
xhigh: "xhigh",
|
|
400
404
|
"0": "low",
|
|
401
405
|
"1": "medium",
|
|
402
406
|
"2": "high",
|
|
403
407
|
}
|
|
404
408
|
const normalized = levelMap[targetThinking.toLowerCase()] ?? "medium"
|
|
405
|
-
const currentLevel =
|
|
409
|
+
const currentLevel = pi.getThinkingLevel()
|
|
406
410
|
if (currentLevel !== normalized) {
|
|
407
|
-
|
|
411
|
+
pi.setThinkingLevel(normalized)
|
|
408
412
|
if (ctx.hasUI) {
|
|
409
413
|
ctx.ui.notify(`Switched thinking level for ${stageKey}: ${normalized}`, "info")
|
|
410
414
|
}
|
|
@@ -748,7 +752,7 @@ export default function ceCoreExtension(pi: ExtensionAPI) {
|
|
|
748
752
|
pi.registerTool({
|
|
749
753
|
name: contextHandoff.name,
|
|
750
754
|
label: "Context Handoff",
|
|
751
|
-
description: "Manage cross-stage context handoffs with evidence-first templates. Supports save (write handoff + state), load (read handoff + state), latest (read latest dated handoff),
|
|
755
|
+
description: "Manage cross-stage context handoffs with evidence-first templates. Supports save (write handoff + state), load (read handoff + state), latest (read latest dated handoff), status (read current state), and validate (check continuation readiness with deterministic probes).",
|
|
752
756
|
parameters: contextHandoffParams,
|
|
753
757
|
async execute(_toolCallId, params) {
|
|
754
758
|
const result = await contextHandoff.execute({
|
|
@@ -5,8 +5,23 @@ import { normalizeSlug } from "../utils/name-utils"
|
|
|
5
5
|
|
|
6
6
|
export type ContextHealth = "good" | "watch" | "heavy" | "critical"
|
|
7
7
|
|
|
8
|
+
export type ContextHandoffRecommendedAction = "continue" | "save_handoff" | "fill_required_context"
|
|
9
|
+
|
|
10
|
+
export interface ContextHandoffValidationCheck {
|
|
11
|
+
name: string
|
|
12
|
+
passed: boolean
|
|
13
|
+
reason: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ContextHandoffValidationProbes {
|
|
17
|
+
recall: boolean
|
|
18
|
+
continuation: boolean
|
|
19
|
+
artifact: boolean
|
|
20
|
+
decision: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
8
23
|
export interface ContextHandoffInput {
|
|
9
|
-
operation: "save" | "load" | "latest" | "status"
|
|
24
|
+
operation: "save" | "load" | "latest" | "status" | "validate"
|
|
10
25
|
repoRoot: string
|
|
11
26
|
currentStage?: string
|
|
12
27
|
nextStage?: string
|
|
@@ -63,6 +78,13 @@ export interface ContextHandoffResult {
|
|
|
63
78
|
recentlyAccessedFiles?: string[]
|
|
64
79
|
compressionRisk?: string[]
|
|
65
80
|
updatedAt?: string
|
|
81
|
+
// Validation fields
|
|
82
|
+
ok?: boolean
|
|
83
|
+
probes?: ContextHandoffValidationProbes
|
|
84
|
+
checks?: ContextHandoffValidationCheck[]
|
|
85
|
+
missing?: string[]
|
|
86
|
+
warnings?: string[]
|
|
87
|
+
recommendedAction?: ContextHandoffRecommendedAction
|
|
66
88
|
}
|
|
67
89
|
|
|
68
90
|
function ceDir(repoRoot: string): string {
|
|
@@ -268,6 +290,8 @@ export function createContextHandoffTool() {
|
|
|
268
290
|
return latest(input)
|
|
269
291
|
case "status":
|
|
270
292
|
return status(input)
|
|
293
|
+
case "validate":
|
|
294
|
+
return validate(input)
|
|
271
295
|
default:
|
|
272
296
|
throw new Error(`Unknown operation: ${input.operation}`)
|
|
273
297
|
}
|
|
@@ -434,6 +458,272 @@ async function latest(input: ContextHandoffInput): Promise<ContextHandoffResult>
|
|
|
434
458
|
}
|
|
435
459
|
}
|
|
436
460
|
|
|
461
|
+
const PLACEHOLDER_VALUES = new Set(["n/a", "na", "not run", "none", "", "-"])
|
|
462
|
+
const PLACEHOLDER_PREFIXES = ["- n/a", "- na", "- not run", "- none", "- "]
|
|
463
|
+
|
|
464
|
+
function isPlaceholder(text: string): boolean {
|
|
465
|
+
const trimmed = text.trim().toLowerCase()
|
|
466
|
+
if (PLACEHOLDER_VALUES.has(trimmed)) return true
|
|
467
|
+
for (const prefix of PLACEHOLDER_PREFIXES) {
|
|
468
|
+
if (trimmed === prefix) return true
|
|
469
|
+
}
|
|
470
|
+
return false
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function isMeaningfulText(value?: string): boolean {
|
|
474
|
+
return Boolean(value && !isPlaceholder(value))
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function toPublicHandoffPath(repoRoot: string, filePath: string): string {
|
|
478
|
+
return path.isAbsolute(filePath) ? toRepoRelative(repoRoot, filePath) : filePath
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function extractSection(markdown: string, heading: string): string {
|
|
482
|
+
const lines = markdown.split("\n")
|
|
483
|
+
const sectionLines: string[] = []
|
|
484
|
+
let inSection = false
|
|
485
|
+
|
|
486
|
+
for (const line of lines) {
|
|
487
|
+
if (line.startsWith("## ")) {
|
|
488
|
+
if (inSection) break
|
|
489
|
+
if (line.slice(3).trim() === heading) {
|
|
490
|
+
inSection = true
|
|
491
|
+
}
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
if (inSection) {
|
|
495
|
+
sectionLines.push(line)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return sectionLines.join("\n").trim()
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function sectionHasMeaningfulContent(markdown: string, heading: string): boolean {
|
|
503
|
+
const section = extractSection(markdown, heading)
|
|
504
|
+
if (!section) return false
|
|
505
|
+
|
|
506
|
+
const lines = section.split("\n")
|
|
507
|
+
for (const line of lines) {
|
|
508
|
+
const trimmed = line.trim()
|
|
509
|
+
if (!trimmed) continue
|
|
510
|
+
if (!isPlaceholder(trimmed)) return true
|
|
511
|
+
}
|
|
512
|
+
return false
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function hasMeaningfulArray(arr: string[]): boolean {
|
|
516
|
+
return arr.some(item => isMeaningfulText(item))
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function hasMeaningfulRecord(rec: Record<string, string | undefined>): boolean {
|
|
520
|
+
return Object.values(rec).some(isMeaningfulText)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function isMeaningfulStage(value?: string): boolean {
|
|
524
|
+
return Boolean(value && value.trim().length > 0 && value !== "unknown" && !isPlaceholder(value))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function validate(input: ContextHandoffInput): Promise<ContextHandoffResult> {
|
|
528
|
+
const checks: ContextHandoffValidationCheck[] = []
|
|
529
|
+
const missing: string[] = []
|
|
530
|
+
const warnings: string[] = []
|
|
531
|
+
|
|
532
|
+
// Read state
|
|
533
|
+
const state = await readState(input.repoRoot)
|
|
534
|
+
const hasState = state !== null
|
|
535
|
+
|
|
536
|
+
// Read markdown
|
|
537
|
+
let markdown = ""
|
|
538
|
+
let hasMarkdown = false
|
|
539
|
+
let handoffFile = ""
|
|
540
|
+
|
|
541
|
+
if (input.handoffPath) {
|
|
542
|
+
const absolutePath = resolveRepoPath(input.repoRoot, input.handoffPath)
|
|
543
|
+
if (existsSync(absolutePath)) {
|
|
544
|
+
markdown = await readFile(absolutePath, "utf8")
|
|
545
|
+
hasMarkdown = markdown.trim().length > 0
|
|
546
|
+
handoffFile = toPublicHandoffPath(input.repoRoot, input.handoffPath)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (!hasMarkdown && state?.latestHandoffPath) {
|
|
551
|
+
const absolutePath = resolveRepoPath(input.repoRoot, state.latestHandoffPath)
|
|
552
|
+
if (existsSync(absolutePath)) {
|
|
553
|
+
markdown = await readFile(absolutePath, "utf8")
|
|
554
|
+
hasMarkdown = markdown.trim().length > 0
|
|
555
|
+
handoffFile = toPublicHandoffPath(input.repoRoot, state.latestHandoffPath)
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (!hasMarkdown) {
|
|
560
|
+
const latestPath = latestHandoffPath(input.repoRoot)
|
|
561
|
+
if (existsSync(latestPath)) {
|
|
562
|
+
markdown = await readFile(latestPath, "utf8")
|
|
563
|
+
hasMarkdown = markdown.trim().length > 0
|
|
564
|
+
handoffFile = toRepoRelative(input.repoRoot, latestPath)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const found = hasState || hasMarkdown
|
|
569
|
+
|
|
570
|
+
checks.push({
|
|
571
|
+
name: "state_exists",
|
|
572
|
+
passed: hasState,
|
|
573
|
+
reason: hasState ? "Found context-state.json" : "No context-state.json found",
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
checks.push({
|
|
577
|
+
name: "handoff_exists",
|
|
578
|
+
passed: hasMarkdown,
|
|
579
|
+
reason: hasMarkdown ? "Found handoff markdown" : "No handoff markdown found",
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
// --- Recall probe ---
|
|
583
|
+
const hasCurrentTruth = state ? hasMeaningfulArray(state.currentTruth) : false
|
|
584
|
+
const hasCurrentStage = state ? isMeaningfulStage(state.currentStage) : false
|
|
585
|
+
const hasCurrentTaskSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Current Task")
|
|
586
|
+
|
|
587
|
+
const recallPass = hasCurrentTruth || (hasCurrentStage && (hasMarkdown || (state?.nextStage !== undefined))) || hasCurrentTaskSection
|
|
588
|
+
|
|
589
|
+
checks.push({
|
|
590
|
+
name: "recall_current_truth",
|
|
591
|
+
passed: hasCurrentTruth,
|
|
592
|
+
reason: hasCurrentTruth ? `Found ${state!.currentTruth.length} current truth entries` : "No current truth entries",
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
checks.push({
|
|
596
|
+
name: "recall_current_task",
|
|
597
|
+
passed: hasCurrentTaskSection,
|
|
598
|
+
reason: hasCurrentTaskSection ? "Current Task section has meaningful content" : "Current Task section is missing or placeholder",
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
checks.push({
|
|
602
|
+
name: "recall_current_stage",
|
|
603
|
+
passed: hasCurrentStage,
|
|
604
|
+
reason: hasCurrentStage ? `Current stage: ${state!.currentStage}` : "No meaningful current stage",
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
if (!recallPass) {
|
|
608
|
+
missing.push("recall: current task or goal evidence")
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// --- Continuation probe ---
|
|
612
|
+
// Tightened: only actionable next-step evidence passes
|
|
613
|
+
const hasNextStage = state ? isMeaningfulStage(state.nextStage) : false
|
|
614
|
+
const hasNextMinimalStep = hasMarkdown && sectionHasMeaningfulContent(markdown, "Next Minimal Step")
|
|
615
|
+
|
|
616
|
+
const continuationPass = hasNextMinimalStep || hasNextStage
|
|
617
|
+
|
|
618
|
+
checks.push({
|
|
619
|
+
name: "continuation_next_stage",
|
|
620
|
+
passed: hasNextStage,
|
|
621
|
+
reason: hasNextStage ? `Next stage: ${state!.nextStage}` : "No meaningful next stage",
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
checks.push({
|
|
625
|
+
name: "continuation_next_minimal_step",
|
|
626
|
+
passed: hasNextMinimalStep,
|
|
627
|
+
reason: hasNextMinimalStep ? "Next Minimal Step has meaningful content" : "Next Minimal Step is missing or placeholder",
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
// Diagnostic checks (do not affect continuation pass)
|
|
631
|
+
const hasBlocker = state ? Boolean(state.blocker && !isPlaceholder(state.blocker)) : false
|
|
632
|
+
const hasVerification = state ? Boolean(state.verification && !isPlaceholder(state.verification)) : false
|
|
633
|
+
const hasVerificationSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Verification")
|
|
634
|
+
|
|
635
|
+
checks.push({
|
|
636
|
+
name: "continuation_blocker",
|
|
637
|
+
passed: hasBlocker,
|
|
638
|
+
reason: hasBlocker ? `Blocker: ${state!.blocker}` : "No blocker information",
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
checks.push({
|
|
642
|
+
name: "continuation_verification",
|
|
643
|
+
passed: hasVerification || hasVerificationSection,
|
|
644
|
+
reason: (hasVerification || hasVerificationSection) ? "Verification evidence present" : "No verification information",
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
if (!continuationPass) {
|
|
648
|
+
missing.push("continuation: next minimal step or next stage evidence")
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// --- Artifact probe ---
|
|
652
|
+
const hasActiveFiles = state ? hasMeaningfulArray(state.activeFiles) : false
|
|
653
|
+
const hasRecentlyAccessed = state ? hasMeaningfulArray(state.recentlyAccessedFiles) : false
|
|
654
|
+
const hasArtifacts = state ? hasMeaningfulRecord(state.artifacts) : false
|
|
655
|
+
const hasActiveFilesSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Active Files")
|
|
656
|
+
const hasRecentlyAccessedSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Recently Accessed Files")
|
|
657
|
+
const hasArtifactsSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Artifacts")
|
|
658
|
+
|
|
659
|
+
const artifactPass = hasActiveFiles || hasRecentlyAccessed || hasArtifacts
|
|
660
|
+
|| hasActiveFilesSection || hasRecentlyAccessedSection || hasArtifactsSection
|
|
661
|
+
|
|
662
|
+
checks.push({
|
|
663
|
+
name: "artifact_active_files",
|
|
664
|
+
passed: hasActiveFiles || hasActiveFilesSection,
|
|
665
|
+
reason: (hasActiveFiles || hasActiveFilesSection) ? "Active files present" : "No active files",
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
if (!artifactPass) {
|
|
669
|
+
warnings.push("artifact: active files or artifacts are missing")
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// --- Decision probe ---
|
|
673
|
+
const hasOpenDecisions = state ? hasMeaningfulArray(state.openDecisions) : false
|
|
674
|
+
const hasInvalidatedAssumptions = state ? hasMeaningfulArray(state.invalidatedAssumptions) : false
|
|
675
|
+
const hasOpenDecisionsSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Open Decisions")
|
|
676
|
+
const hasInvalidatedSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Invalidated Assumptions")
|
|
677
|
+
const hasCurrentTruthSection = hasMarkdown && sectionHasMeaningfulContent(markdown, "Current Truth")
|
|
678
|
+
|
|
679
|
+
const decisionPass = hasOpenDecisions || hasInvalidatedAssumptions || hasCurrentTruth
|
|
680
|
+
|| hasOpenDecisionsSection || hasInvalidatedSection || hasCurrentTruthSection
|
|
681
|
+
|
|
682
|
+
checks.push({
|
|
683
|
+
name: "decision_open_decisions",
|
|
684
|
+
passed: hasOpenDecisions || hasOpenDecisionsSection,
|
|
685
|
+
reason: (hasOpenDecisions || hasOpenDecisionsSection) ? "Open decisions present" : "No open decisions",
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
if (!decisionPass) {
|
|
689
|
+
warnings.push("decision: decisions or invalidated assumptions are missing")
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// --- ok derivation ---
|
|
693
|
+
const ok = recallPass && continuationPass
|
|
694
|
+
|
|
695
|
+
// --- recommended action ---
|
|
696
|
+
let recommendedAction: ContextHandoffRecommendedAction
|
|
697
|
+
if (!found) {
|
|
698
|
+
recommendedAction = "save_handoff"
|
|
699
|
+
} else if (!ok) {
|
|
700
|
+
recommendedAction = "fill_required_context"
|
|
701
|
+
} else {
|
|
702
|
+
recommendedAction = "continue"
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
operation: "validate",
|
|
707
|
+
found,
|
|
708
|
+
ok,
|
|
709
|
+
path: handoffFile || undefined,
|
|
710
|
+
probes: {
|
|
711
|
+
recall: recallPass,
|
|
712
|
+
continuation: continuationPass,
|
|
713
|
+
artifact: artifactPass,
|
|
714
|
+
decision: decisionPass,
|
|
715
|
+
},
|
|
716
|
+
checks,
|
|
717
|
+
missing,
|
|
718
|
+
warnings,
|
|
719
|
+
recommendedAction,
|
|
720
|
+
currentStage: state?.currentStage,
|
|
721
|
+
nextStage: state?.nextStage,
|
|
722
|
+
contextHealth: state?.contextHealth,
|
|
723
|
+
updatedAt: state?.updatedAt,
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
437
727
|
async function status(input: ContextHandoffInput): Promise<ContextHandoffResult> {
|
|
438
728
|
const state = await readState(input.repoRoot)
|
|
439
729
|
|