jfl 0.9.1 → 0.9.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/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +141 -3
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/ide.d.ts.map +1 -1
- package/dist/commands/ide.js +22 -0
- package/dist/commands/ide.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/linear.d.ts.map +1 -1
- package/dist/commands/linear.js +24 -0
- package/dist/commands/linear.js.map +1 -1
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +11 -15
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi.d.ts +3 -0
- package/dist/commands/pi.d.ts.map +1 -1
- package/dist/commands/pi.js +19 -0
- package/dist/commands/pi.js.map +1 -1
- package/dist/commands/pivot.d.ts.map +1 -1
- package/dist/commands/pivot.js +22 -25
- package/dist/commands/pivot.js.map +1 -1
- package/dist/commands/repair.d.ts.map +1 -1
- package/dist/commands/repair.js +26 -0
- package/dist/commands/repair.js.map +1 -1
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +39 -0
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +60 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +3 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/advanced-setup.js +7 -7
- package/dist/lib/advanced-setup.js.map +1 -1
- package/dist/lib/agent-session.d.ts.map +1 -1
- package/dist/lib/agent-session.js +6 -3
- package/dist/lib/agent-session.js.map +1 -1
- package/dist/lib/discovery-agent.js +1 -1
- package/dist/lib/discovery-agent.js.map +1 -1
- package/dist/lib/gtm-generator.js +7 -0
- package/dist/lib/gtm-generator.js.map +1 -1
- package/dist/lib/linear-webhook.d.ts +50 -0
- package/dist/lib/linear-webhook.d.ts.map +1 -0
- package/dist/lib/linear-webhook.js +92 -0
- package/dist/lib/linear-webhook.js.map +1 -0
- package/dist/lib/memory-db.d.ts +8 -0
- package/dist/lib/memory-db.d.ts.map +1 -1
- package/dist/lib/memory-db.js +24 -0
- package/dist/lib/memory-db.js.map +1 -1
- package/dist/lib/memory-indexer.d.ts +8 -0
- package/dist/lib/memory-indexer.d.ts.map +1 -1
- package/dist/lib/memory-indexer.js +30 -1
- package/dist/lib/memory-indexer.js.map +1 -1
- package/dist/lib/memory-search.d.ts.map +1 -1
- package/dist/lib/memory-search.js +2 -7
- package/dist/lib/memory-search.js.map +1 -1
- package/dist/lib/onboarding.js +1 -1
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/rl-manager.d.ts +1 -1
- package/dist/lib/rl-manager.d.ts.map +1 -1
- package/dist/lib/rl-manager.js +3 -3
- package/dist/lib/rl-manager.js.map +1 -1
- package/dist/lib/service-detector.js +2 -2
- package/dist/lib/service-detector.js.map +1 -1
- package/dist/lib/telemetry/physical-world-collector.js +1 -1
- package/dist/lib/telemetry/physical-world-collector.js.map +1 -1
- package/dist/lib/tool-schemas.d.ts +35 -0
- package/dist/lib/tool-schemas.d.ts.map +1 -0
- package/dist/lib/tool-schemas.js +246 -0
- package/dist/lib/tool-schemas.js.map +1 -0
- package/dist/lib/workspace/data-pipeline.d.ts.map +1 -1
- package/dist/lib/workspace/data-pipeline.js +29 -20
- package/dist/lib/workspace/data-pipeline.js.map +1 -1
- package/dist/lib/workspace/engine.d.ts +1 -0
- package/dist/lib/workspace/engine.d.ts.map +1 -1
- package/dist/lib/workspace/engine.js +10 -0
- package/dist/lib/workspace/engine.js.map +1 -1
- package/dist/mcp/context-hub-mcp.js +7 -1
- package/dist/mcp/context-hub-mcp.js.map +1 -1
- package/dist/types/telemetry.d.ts +1 -0
- package/dist/types/telemetry.d.ts.map +1 -1
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +9 -6
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/provenance.d.ts +65 -0
- package/dist/utils/provenance.d.ts.map +1 -0
- package/dist/utils/provenance.js +213 -0
- package/dist/utils/provenance.js.map +1 -0
- package/package.json +1 -1
- package/packages/pi/assets/boot.mp3 +0 -0
- package/packages/pi/extensions/autoresearch.ts +3 -2
- package/packages/pi/extensions/context.ts +38 -114
- package/packages/pi/extensions/eval.ts +2 -1
- package/packages/pi/extensions/header.ts +171 -0
- package/packages/pi/extensions/hub-tools.ts +31 -11
- package/packages/pi/extensions/hud-tool.ts +231 -70
- package/packages/pi/extensions/index.ts +65 -64
- package/packages/pi/extensions/jfl-resolve.ts +98 -0
- package/packages/pi/extensions/journal.ts +91 -6
- package/packages/pi/extensions/map-bridge.ts +31 -0
- package/packages/pi/extensions/memory-tool.ts +3 -3
- package/packages/pi/extensions/onboarding-v2.ts +263 -410
- package/packages/pi/extensions/onboarding-v3.ts +32 -21
- package/packages/pi/extensions/peter-parker.ts +2 -1
- package/packages/pi/extensions/policy-head-tool.ts +3 -2
- package/packages/pi/extensions/portfolio-bridge.ts +3 -4
- package/packages/pi/extensions/service-skills.ts +6 -1
- package/packages/pi/extensions/session.ts +97 -15
- package/packages/pi/extensions/startup-briefing.ts +313 -0
- package/packages/pi/extensions/stratus-bridge.ts +2 -1
- package/packages/pi/extensions/subway-mesh.ts +893 -0
- package/packages/pi/extensions/synopsis-tool.ts +6 -1
- package/packages/pi/extensions/training-buffer-tool.ts +3 -2
- package/packages/pi/extensions/types.ts +3 -0
- package/packages/pi/package.json +4 -1
- package/packages/pi/skills/viz/SKILL.md +204 -0
- package/scripts/pp-branch-pr.sh +24 -6
- package/scripts/pp-branch-pr.sh.bak +115 -0
- package/template/.pi/settings.json +5 -0
- package/template/CLAUDE.md +82 -1738
- package/template/CLAUDE.md.bak +0 -1187
|
@@ -1,516 +1,369 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Onboarding V2 —
|
|
2
|
+
* Onboarding V2 — Cinematic boot sequence
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Visual-only startup overlay. Shows the SATOR square animation and
|
|
5
|
+
* live system probes, then dismisses. No project data — that comes
|
|
6
|
+
* from the startup briefing steer which fires after this overlay closes.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* Flow: SATOR square → system probes → project name → dismiss
|
|
9
|
+
*
|
|
10
|
+
* The header (header.ts) owns persistent identity. This overlay owns
|
|
11
|
+
* the first-impression cinematic moment. The startup briefing (steer)
|
|
12
|
+
* owns the data-rich greeting that follows.
|
|
13
|
+
*
|
|
14
|
+
* @purpose Cinematic boot overlay — SATOR square + system health probes
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
|
-
import { existsSync, readFileSync, readdirSync
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from "fs"
|
|
12
18
|
import { join } from "path"
|
|
13
19
|
import { execSync } from "child_process"
|
|
14
20
|
import type { PiContext, PiTheme, JflConfig } from "./types.js"
|
|
21
|
+
import { setHeaderHubStatus, setHeaderAutoCommit, refreshHeaderData } from "./header.js"
|
|
22
|
+
|
|
23
|
+
// ─── Colors ──────────────────────────────────────────────────────────────────
|
|
15
24
|
|
|
16
25
|
const GOLD = "warning"
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
["
|
|
26
|
-
["
|
|
27
|
-
["
|
|
28
|
-
["
|
|
26
|
+
const DIM = "muted"
|
|
27
|
+
const WARM = "text"
|
|
28
|
+
const RED = "error"
|
|
29
|
+
const GREEN = "success"
|
|
30
|
+
|
|
31
|
+
// ─── Sator Square ────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const SATOR = [
|
|
34
|
+
["S","A","T","O","R"],
|
|
35
|
+
["A","R","E","P","O"],
|
|
36
|
+
["T","E","N","E","T"],
|
|
37
|
+
["O","P","E","R","A"],
|
|
38
|
+
["R","O","T","A","S"],
|
|
29
39
|
]
|
|
40
|
+
const TENET_ROW = 2
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
[2, 0], [2, 1], [2, 2], [2, 3], [2, 4],
|
|
33
|
-
]
|
|
42
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
34
43
|
|
|
35
|
-
interface
|
|
44
|
+
interface Sys {
|
|
36
45
|
name: string
|
|
37
|
-
|
|
38
|
-
status: "waiting" | "connecting" | "ready" | "error"
|
|
46
|
+
status: "wait" | "probe" | "ok" | "err"
|
|
39
47
|
detail: string
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
lastSessionSummary: string[]
|
|
44
|
-
pendingDecisions: number
|
|
45
|
-
currentPhase: string
|
|
46
|
-
projectName: string
|
|
47
|
-
projectType: string
|
|
48
|
-
branch: string
|
|
49
|
-
journalCount: number
|
|
50
|
-
activeAgents: number
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function gatherMissionData(root: string, config: JflConfig): MissionData {
|
|
54
|
-
const projectName = config.name ?? root.split("/").pop() ?? "TENET"
|
|
55
|
-
const projectType = config.type ?? "gtm"
|
|
56
|
-
|
|
57
|
-
let currentPhase = ""
|
|
58
|
-
const roadmapPath = join(root, "knowledge", "ROADMAP.md")
|
|
59
|
-
if (existsSync(roadmapPath)) {
|
|
60
|
-
try {
|
|
61
|
-
const content = readFileSync(roadmapPath, "utf-8")
|
|
62
|
-
const phaseMatch = content.match(/## (?:Phase|Current Phase)[:\s]*([^\n]+)/i)
|
|
63
|
-
if (phaseMatch) currentPhase = phaseMatch[1].trim()
|
|
64
|
-
} catch {}
|
|
65
|
-
}
|
|
50
|
+
// ─── Probes ──────────────────────────────────────────────────────────────────
|
|
66
51
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (existsSync(journalDir)) {
|
|
52
|
+
async function probeHub(root: string): Promise<{ok: boolean; detail: string}> {
|
|
53
|
+
const port = readPort(root)
|
|
54
|
+
for (let i = 0; i < 4; i++) {
|
|
71
55
|
try {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const lines = readFileSync(join(journalDir, f), "utf-8").trim().split("\n").filter(Boolean)
|
|
76
|
-
journalCount += lines.length
|
|
77
|
-
for (const l of lines.slice(-5)) {
|
|
78
|
-
try {
|
|
79
|
-
const e = JSON.parse(l)
|
|
80
|
-
lastSessionSummary.push(e.title ?? e.summary ?? "")
|
|
81
|
-
} catch {}
|
|
82
|
-
}
|
|
83
|
-
} catch {}
|
|
84
|
-
}
|
|
56
|
+
const r = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(2000) })
|
|
57
|
+
if (r.ok) { const d = await r.json() as any; if (d.status === "ok") return {ok:true, detail:`:${port}`} }
|
|
85
58
|
} catch {}
|
|
59
|
+
if (i < 3) await sleep(700)
|
|
86
60
|
}
|
|
61
|
+
return {ok:false, detail:"offline"}
|
|
62
|
+
}
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
64
|
+
async function probeContext(root: string): Promise<{ok: boolean; detail: string}> {
|
|
65
|
+
const port = readPort(root), token = readToken(root)
|
|
66
|
+
for (let i = 0; i < 3; i++) {
|
|
91
67
|
try {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
68
|
+
const r = await fetch(`http://localhost:${port}/api/context`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {"Content-Type":"application/json", ...(token ? {Authorization:`Bearer ${token}`} : {})},
|
|
71
|
+
body: JSON.stringify({maxItems:1}),
|
|
72
|
+
signal: AbortSignal.timeout(2000),
|
|
73
|
+
})
|
|
74
|
+
if (r.ok) return {ok:true, detail:"loaded"}
|
|
95
75
|
} catch {}
|
|
76
|
+
if (i < 2) await sleep(700)
|
|
96
77
|
}
|
|
78
|
+
return {ok:false, detail:"waiting…"}
|
|
79
|
+
}
|
|
97
80
|
|
|
98
|
-
|
|
81
|
+
async function probeMemory(root: string): Promise<{ok: boolean; detail: string}> {
|
|
82
|
+
const dir = join(root, ".jfl", "journal")
|
|
83
|
+
if (!existsSync(dir)) return {ok:false, detail:"none"}
|
|
99
84
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
let n = 0
|
|
86
|
+
for (const f of readdirSync(dir).filter(f => f.endsWith(".jsonl")))
|
|
87
|
+
n += readFileSync(join(dir, f), "utf-8").trim().split("\n").filter(Boolean).length
|
|
88
|
+
return {ok:true, detail:`${n} entries`}
|
|
89
|
+
} catch { return {ok:false, detail:"err"} }
|
|
90
|
+
}
|
|
103
91
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const now = Date.now()
|
|
92
|
+
async function probeAutoCommit(): Promise<{ok: boolean; detail: string}> {
|
|
93
|
+
for (let i = 0; i < 4; i++) {
|
|
107
94
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const stat = statSync(join(journalDir, f))
|
|
112
|
-
if (now - stat.mtimeMs < 300000) activeAgents++
|
|
113
|
-
} catch {}
|
|
114
|
-
}
|
|
95
|
+
if (execSync("ps aux", {timeout:2000, encoding:"utf-8", stdio:["pipe","pipe","ignore"]}).includes("auto-commit.sh"))
|
|
96
|
+
return {ok:true, detail:"watching"}
|
|
115
97
|
} catch {}
|
|
98
|
+
if (i < 3) await sleep(1000)
|
|
116
99
|
}
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
lastSessionSummary: lastSessionSummary.slice(-4),
|
|
120
|
-
pendingDecisions,
|
|
121
|
-
currentPhase,
|
|
122
|
-
projectName,
|
|
123
|
-
projectType,
|
|
124
|
-
branch,
|
|
125
|
-
journalCount,
|
|
126
|
-
activeAgents,
|
|
127
|
-
}
|
|
100
|
+
return {ok:false, detail:"–"}
|
|
128
101
|
}
|
|
129
102
|
|
|
130
|
-
async function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
): Promise<{ ok: boolean; detail: string }> {
|
|
103
|
+
async function probeEval(root: string): Promise<{ok: boolean; detail: string}> {
|
|
104
|
+
const p = join(root, ".jfl", "eval.jsonl")
|
|
105
|
+
if (!existsSync(p)) return {ok:false, detail:"—"}
|
|
134
106
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
107
|
+
const lines = readFileSync(p, "utf-8").trim().split("\n").filter(Boolean)
|
|
108
|
+
for (let i = lines.length-1; i >= Math.max(0, lines.length-20); i--) {
|
|
109
|
+
try { const e = JSON.parse(lines[i]); const s = e.composite ?? e.score; if (s != null) return {ok:true, detail:Number(s).toFixed(2)} } catch {}
|
|
110
|
+
}
|
|
111
|
+
return {ok:true, detail:`${lines.length} runs`}
|
|
112
|
+
} catch { return {ok:false, detail:"err"} }
|
|
140
113
|
}
|
|
141
114
|
|
|
142
|
-
async function
|
|
143
|
-
const
|
|
144
|
-
if (!existsSync(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
})
|
|
149
|
-
if (!resp.ok) throw new Error("unhealthy")
|
|
150
|
-
const data = await resp.json() as { status?: string; itemCount?: number }
|
|
151
|
-
const count = data.itemCount ?? 0
|
|
152
|
-
return count > 0 ? `${count} items` : "ready"
|
|
115
|
+
async function probeTraining(root: string): Promise<{ok: boolean; detail: string}> {
|
|
116
|
+
const p = join(root, ".jfl", "training-buffer.jsonl")
|
|
117
|
+
if (!existsSync(p)) return {ok:false, detail:"—"}
|
|
118
|
+
try {
|
|
119
|
+
return {ok:true, detail:`${readFileSync(p,"utf-8").trim().split("\n").filter(Boolean).length} tuples`}
|
|
120
|
+
} catch { return {ok:false, detail:"err"} }
|
|
153
121
|
}
|
|
154
122
|
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
const journalDir = join(root, ".jfl", "journal")
|
|
159
|
-
if (!existsSync(journalDir)) return "0 memories"
|
|
160
|
-
let count = 0
|
|
161
|
-
for (const f of readdirSync(journalDir).filter(f => f.endsWith(".jsonl"))) {
|
|
162
|
-
try {
|
|
163
|
-
count += readFileSync(join(journalDir, f), "utf-8").trim().split("\n").filter(Boolean).length
|
|
164
|
-
} catch {}
|
|
165
|
-
}
|
|
166
|
-
return `${count} memories`
|
|
123
|
+
function readPort(root: string): string {
|
|
124
|
+
const f = join(root, ".jfl", "context-hub.port")
|
|
125
|
+
return existsSync(f) ? readFileSync(f, "utf-8").trim() : "4360"
|
|
167
126
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!existsSync(envPath)) return "no key"
|
|
172
|
-
try {
|
|
173
|
-
const env = readFileSync(envPath, "utf-8")
|
|
174
|
-
if (env.includes("STRATUS_API_KEY") || env.includes("stratus_sk")) {
|
|
175
|
-
return "relay online"
|
|
176
|
-
}
|
|
177
|
-
} catch {}
|
|
178
|
-
return "no relay"
|
|
127
|
+
function readToken(root: string): string {
|
|
128
|
+
const f = join(root, ".jfl", "context-hub.token")
|
|
129
|
+
return existsSync(f) ? readFileSync(f, "utf-8").trim() : ""
|
|
179
130
|
}
|
|
131
|
+
function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) }
|
|
132
|
+
|
|
133
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
180
134
|
|
|
181
135
|
export async function setupOnboarding(ctx: PiContext, config: JflConfig): Promise<void> {
|
|
182
136
|
const root = ctx.session.projectRoot
|
|
137
|
+
if (!ctx.ui.hasUI) return
|
|
183
138
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
probeSubsystem("Subway", () => probeSubway(root)),
|
|
195
|
-
])
|
|
196
|
-
|
|
197
|
-
const subsystems: SubsystemState[] = [
|
|
198
|
-
{
|
|
199
|
-
name: "Context Hub",
|
|
200
|
-
icon: "◆",
|
|
201
|
-
status: "waiting",
|
|
202
|
-
detail: "",
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: "Memory",
|
|
206
|
-
icon: "◆",
|
|
207
|
-
status: "waiting",
|
|
208
|
-
detail: "",
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: "Subway",
|
|
212
|
-
icon: "◆",
|
|
213
|
-
status: "waiting",
|
|
214
|
-
detail: "",
|
|
215
|
-
},
|
|
139
|
+
const projectName = config.name ?? root.split("/").pop() ?? "TENET"
|
|
140
|
+
const branch = ctx.session.branch
|
|
141
|
+
|
|
142
|
+
const systems: Sys[] = [
|
|
143
|
+
{name:"Hub", status:"wait", detail:""},
|
|
144
|
+
{name:"Context", status:"wait", detail:""},
|
|
145
|
+
{name:"Memory", status:"wait", detail:""},
|
|
146
|
+
{name:"Auto-commit", status:"wait", detail:""},
|
|
147
|
+
{name:"Eval", status:"wait", detail:""},
|
|
148
|
+
{name:"Training", status:"wait", detail:""},
|
|
216
149
|
]
|
|
217
150
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
151
|
+
const probes = [
|
|
152
|
+
() => probeHub(root),
|
|
153
|
+
() => probeContext(root),
|
|
154
|
+
() => probeMemory(root),
|
|
155
|
+
() => probeAutoCommit(),
|
|
156
|
+
() => probeEval(root),
|
|
157
|
+
() => probeTraining(root),
|
|
158
|
+
]
|
|
221
159
|
|
|
222
160
|
await ctx.ui.custom<void>((tui: any, theme: PiTheme, _kb: any, done: (r: void) => void) => {
|
|
223
|
-
let phase: "square"
|
|
224
|
-
let
|
|
225
|
-
let
|
|
226
|
-
let
|
|
227
|
-
let
|
|
228
|
-
let
|
|
229
|
-
let
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
function
|
|
240
|
-
if (
|
|
241
|
-
|
|
242
|
-
if (disposed) return
|
|
243
|
-
fn()
|
|
244
|
-
tui.requestRender()
|
|
245
|
-
}, ms)
|
|
161
|
+
let phase: "square"|"glow"|"systems"|"ready" = "square"
|
|
162
|
+
let sqProg = 0
|
|
163
|
+
let glow = false
|
|
164
|
+
let sysRevealed = 0
|
|
165
|
+
let readyTick = 0
|
|
166
|
+
let timer: ReturnType<typeof setTimeout>|null = null
|
|
167
|
+
let dead = false
|
|
168
|
+
|
|
169
|
+
// ── Timing ──
|
|
170
|
+
const SQ_MS = 40 // per letter (faster)
|
|
171
|
+
const GLOW_MS = 500 // hold after square
|
|
172
|
+
const GLW2SYS = 300 // glow → systems
|
|
173
|
+
const SYS_MS = 180 // per system row reveal
|
|
174
|
+
const SETTLE = 150 // probe settle poll
|
|
175
|
+
const RDY_HOLD = 1200 // show ready then auto-dismiss
|
|
176
|
+
|
|
177
|
+
function tick(fn: () => void, ms: number) {
|
|
178
|
+
if (dead) return
|
|
179
|
+
timer = setTimeout(() => { if (!dead) { fn(); tui.requestRender() } }, ms)
|
|
246
180
|
}
|
|
247
181
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
182
|
+
// ── Color helpers ──
|
|
183
|
+
const g = (s: string) => theme.fg(GOLD, s)
|
|
184
|
+
const d = (s: string) => theme.fg(DIM, s)
|
|
185
|
+
const w = (s: string) => theme.fg(WARM, s)
|
|
186
|
+
const gr = (s: string) => theme.fg(GREEN, s)
|
|
187
|
+
const rd = (s: string) => theme.fg(RED, s)
|
|
188
|
+
|
|
189
|
+
// ── ANSI helpers ──
|
|
190
|
+
function stripLen(s: string) { return s.replace(/\x1b\[[0-9;]*m/g, "").length }
|
|
191
|
+
function pad(line: string, width: number) {
|
|
192
|
+
return line + " ".repeat(Math.max(0, width - stripLen(line)))
|
|
258
193
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
advanceSubsystem()
|
|
194
|
+
function center(text: string, width: number) {
|
|
195
|
+
const vis = stripLen(text)
|
|
196
|
+
const left = Math.max(0, Math.floor((width - vis) / 2))
|
|
197
|
+
return " ".repeat(left) + text
|
|
264
198
|
}
|
|
265
199
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
subsystems[subsystemIndex].detail = result.detail
|
|
271
|
-
subsystemIndex++
|
|
272
|
-
if (subsystemIndex < subsystems.length) {
|
|
273
|
-
scheduleNext(advanceSubsystem, SUBSYSTEM_FRAME_MS)
|
|
274
|
-
} else {
|
|
275
|
-
scheduleNext(startBriefing, SUBSYSTEM_FRAME_MS)
|
|
276
|
-
}
|
|
277
|
-
}
|
|
200
|
+
// ── Phase: Square ──
|
|
201
|
+
function doSquare() {
|
|
202
|
+
if (sqProg < 25) { sqProg++; tick(doSquare, SQ_MS) }
|
|
203
|
+
else tick(() => { glow = true; phase = "glow"; tick(doSystems, GLW2SYS) }, GLOW_MS)
|
|
278
204
|
}
|
|
279
205
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
206
|
+
// ── Phase: Systems ──
|
|
207
|
+
function doSystems() {
|
|
208
|
+
phase = "systems"
|
|
209
|
+
// Fire all probes in parallel
|
|
210
|
+
systems.forEach((s, i) => {
|
|
211
|
+
s.status = "probe"
|
|
212
|
+
probes[i]().then(r => {
|
|
213
|
+
s.status = r.ok ? "ok" : "err"
|
|
214
|
+
s.detail = r.detail
|
|
215
|
+
// Feed results back to the persistent header
|
|
216
|
+
if (i === 0) setHeaderHubStatus(r.ok) // Hub
|
|
217
|
+
if (i === 3) setHeaderAutoCommit(r.ok) // Auto-commit
|
|
218
|
+
tui.requestRender()
|
|
219
|
+
}).catch(() => {
|
|
220
|
+
s.status = "err"
|
|
221
|
+
s.detail = "timeout"
|
|
222
|
+
tui.requestRender()
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
revealSys()
|
|
284
226
|
}
|
|
285
227
|
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
briefingRevealed++
|
|
290
|
-
scheduleNext(advanceBriefing, BRIEFING_FRAME_MS)
|
|
291
|
-
} else {
|
|
292
|
-
scheduleNext(startReady, READY_DELAY_MS)
|
|
293
|
-
}
|
|
228
|
+
function revealSys() {
|
|
229
|
+
if (sysRevealed < systems.length) { sysRevealed++; tick(revealSys, SYS_MS) }
|
|
230
|
+
else waitProbes()
|
|
294
231
|
}
|
|
295
232
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
233
|
+
function waitProbes() {
|
|
234
|
+
if (systems.some(s => s.status === "probe")) tick(waitProbes, SETTLE)
|
|
235
|
+
else {
|
|
236
|
+
// Refresh header with final probe data
|
|
237
|
+
refreshHeaderData(root, config)
|
|
238
|
+
tick(doReady, 200)
|
|
302
239
|
}
|
|
303
|
-
if (mission.pendingDecisions > 0) {
|
|
304
|
-
lines.push(`${mission.pendingDecisions} tasks pending`)
|
|
305
|
-
}
|
|
306
|
-
if (mission.currentPhase) {
|
|
307
|
-
lines.push(mission.currentPhase)
|
|
308
|
-
}
|
|
309
|
-
if (lines.length === 0) {
|
|
310
|
-
lines.push("First session. No prior context.")
|
|
311
|
-
}
|
|
312
|
-
return lines
|
|
313
240
|
}
|
|
314
241
|
|
|
315
|
-
|
|
242
|
+
// ── Phase: Ready ──
|
|
243
|
+
function doReady() {
|
|
316
244
|
phase = "ready"
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
readyFade = 1
|
|
320
|
-
scheduleNext(() => {
|
|
321
|
-
if (!disposed) done(undefined)
|
|
322
|
-
}, 500)
|
|
323
|
-
}, 300)
|
|
245
|
+
readyTick = 0
|
|
246
|
+
tickReady()
|
|
324
247
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
function gold(t: PiTheme, text: string): string {
|
|
329
|
-
return t.fg(GOLD, text)
|
|
330
|
-
}
|
|
331
|
-
function dimGold(t: PiTheme, text: string): string {
|
|
332
|
-
return t.fg(GOLD_DIM, text)
|
|
333
|
-
}
|
|
334
|
-
function darkGold(t: PiTheme, text: string): string {
|
|
335
|
-
return t.fg(DARK_GOLD, text)
|
|
336
|
-
}
|
|
337
|
-
function warm(t: PiTheme, text: string): string {
|
|
338
|
-
return t.fg(WARM_WHITE, text)
|
|
248
|
+
function tickReady() {
|
|
249
|
+
if (readyTick < 3) { readyTick++; tick(tickReady, 250) }
|
|
250
|
+
else tick(() => { if (!dead) done(undefined) }, RDY_HOLD)
|
|
339
251
|
}
|
|
340
252
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const squareWidth = 5 * 4 - 1
|
|
344
|
-
const leftPad = Math.max(0, Math.floor((width - squareWidth) / 2))
|
|
345
|
-
const pad = " ".repeat(leftPad)
|
|
253
|
+
// Kick off
|
|
254
|
+
tick(doSquare, 100)
|
|
346
255
|
|
|
347
|
-
|
|
256
|
+
// ── Renderers ──
|
|
257
|
+
|
|
258
|
+
function renderSquare(W: number): string[] {
|
|
259
|
+
const out: string[] = []
|
|
260
|
+
const cw = 5 * 4 - 1 // "S A T O R"
|
|
261
|
+
const lp = " ".repeat(Math.max(0, Math.floor((W - cw) / 2)))
|
|
262
|
+
for (let r = 0; r < 5; r++) {
|
|
348
263
|
let line = ""
|
|
349
|
-
for (let
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
line += theme.bold(gold(theme, char))
|
|
357
|
-
} else if (tenetGlow) {
|
|
358
|
-
line += darkGold(theme, char)
|
|
359
|
-
} else {
|
|
360
|
-
line += dimGold(theme, char)
|
|
361
|
-
}
|
|
264
|
+
for (let c = 0; c < 5; c++) {
|
|
265
|
+
const idx = r * 5 + c
|
|
266
|
+
const ch = SATOR[r][c]
|
|
267
|
+
if (idx < sqProg) {
|
|
268
|
+
if (glow && r === TENET_ROW) line += theme.bold(g(ch))
|
|
269
|
+
else if (glow) line += d(ch)
|
|
270
|
+
else line += d(ch)
|
|
362
271
|
} else {
|
|
363
|
-
line +=
|
|
272
|
+
line += d("·")
|
|
364
273
|
}
|
|
365
|
-
|
|
366
|
-
if (col < 4) line += " "
|
|
367
|
-
}
|
|
368
|
-
lines.push(pad + line)
|
|
369
|
-
if (row < 4) lines.push("")
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return lines
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function renderSubsystems(theme: PiTheme, width: number): string[] {
|
|
376
|
-
const lines: string[] = []
|
|
377
|
-
const innerWidth = Math.min(width - 8, 50)
|
|
378
|
-
const leftPad = Math.max(0, Math.floor((width - innerWidth) / 2))
|
|
379
|
-
const pad = " ".repeat(leftPad)
|
|
380
|
-
|
|
381
|
-
for (let i = 0; i < subsystems.length; i++) {
|
|
382
|
-
const sys = subsystems[i]
|
|
383
|
-
let icon: string
|
|
384
|
-
let nameStr: string
|
|
385
|
-
let detailStr: string
|
|
386
|
-
|
|
387
|
-
if (i > subsystemIndex - 1 && i !== subsystemIndex) {
|
|
388
|
-
icon = darkGold(theme, "○")
|
|
389
|
-
nameStr = darkGold(theme, sys.name)
|
|
390
|
-
detailStr = darkGold(theme, "waiting...")
|
|
391
|
-
} else if (sys.status === "ready") {
|
|
392
|
-
icon = gold(theme, "●")
|
|
393
|
-
nameStr = warm(theme, sys.name)
|
|
394
|
-
detailStr = gold(theme, sys.detail)
|
|
395
|
-
} else if (sys.status === "error") {
|
|
396
|
-
icon = theme.fg(EMBER, "●")
|
|
397
|
-
nameStr = theme.fg(EMBER, sys.name)
|
|
398
|
-
detailStr = theme.fg(EMBER, sys.detail)
|
|
399
|
-
} else {
|
|
400
|
-
icon = dimGold(theme, "◌")
|
|
401
|
-
nameStr = dimGold(theme, sys.name)
|
|
402
|
-
detailStr = dimGold(theme, "connecting...")
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const namePadded = (sys.name + " ".repeat(Math.max(0, 16 - sys.name.length)))
|
|
406
|
-
if (sys.status === "ready") {
|
|
407
|
-
lines.push(pad + `${icon} ${warm(theme, namePadded)} ${detailStr}`)
|
|
408
|
-
} else if (sys.status === "error") {
|
|
409
|
-
lines.push(pad + `${icon} ${theme.fg(EMBER, namePadded)} ${detailStr}`)
|
|
410
|
-
} else if (i > subsystemIndex - 1) {
|
|
411
|
-
lines.push(pad + `${icon} ${darkGold(theme, namePadded)} ${detailStr}`)
|
|
412
|
-
} else {
|
|
413
|
-
lines.push(pad + `${icon} ${dimGold(theme, namePadded)} ${detailStr}`)
|
|
274
|
+
if (c < 4) line += " "
|
|
414
275
|
}
|
|
276
|
+
out.push(lp + line)
|
|
277
|
+
if (r < 4) out.push("") // spacing between rows
|
|
415
278
|
}
|
|
416
|
-
|
|
417
|
-
return lines
|
|
279
|
+
return out
|
|
418
280
|
}
|
|
419
281
|
|
|
420
|
-
function
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
282
|
+
function renderSystems(W: number): string[] {
|
|
283
|
+
const out: string[] = []
|
|
284
|
+
const inner = Math.min(W - 4, 48)
|
|
285
|
+
const lp = " ".repeat(Math.max(0, Math.floor((W - inner) / 2)))
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < Math.min(sysRevealed, systems.length); i++) {
|
|
288
|
+
const s = systems[i]
|
|
289
|
+
const nm = s.name.padEnd(13)
|
|
290
|
+
let icon: string, name: string, det: string
|
|
291
|
+
if (s.status === "ok") { icon = gr("✓"); name = w(nm); det = d(s.detail) }
|
|
292
|
+
else if (s.status === "err") { icon = rd("✗"); name = rd(nm); det = rd(s.detail) }
|
|
293
|
+
else if (s.status === "probe") { icon = d("◌"); name = d(nm); det = d("…") }
|
|
294
|
+
else { icon = d("○"); name = d(nm); det = d("") }
|
|
295
|
+
out.push(`${lp} ${icon} ${name} ${det}`)
|
|
435
296
|
}
|
|
436
|
-
|
|
437
|
-
return lines
|
|
297
|
+
return out
|
|
438
298
|
}
|
|
439
299
|
|
|
440
|
-
function renderReady(
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
if (
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
300
|
+
function renderReady(W: number): string[] {
|
|
301
|
+
const out: string[] = []
|
|
302
|
+
const sep = d(" · ")
|
|
303
|
+
out.push("")
|
|
304
|
+
if (readyTick >= 1) out.push(center(theme.bold(g(projectName.toUpperCase())), W))
|
|
305
|
+
if (readyTick >= 2) out.push(center(d(branch), W))
|
|
306
|
+
if (readyTick >= 3) {
|
|
307
|
+
// Count how many probes passed
|
|
308
|
+
const ok = systems.filter(s => s.status === "ok").length
|
|
309
|
+
const total = systems.length
|
|
310
|
+
const allGood = ok === total
|
|
311
|
+
const statusText = allGood
|
|
312
|
+
? gr("all systems nominal")
|
|
313
|
+
: g(`${ok}/${total} systems`)
|
|
314
|
+
out.push(center(statusText, W))
|
|
450
315
|
}
|
|
451
|
-
|
|
452
|
-
return lines
|
|
316
|
+
return out
|
|
453
317
|
}
|
|
454
318
|
|
|
319
|
+
// ── Main render ──
|
|
455
320
|
return {
|
|
456
321
|
handleInput(key: string) {
|
|
457
322
|
if (key === "\x1b" || key === "q" || key === " " || key === "\r") {
|
|
458
|
-
if (
|
|
459
|
-
|
|
323
|
+
if (timer) clearTimeout(timer)
|
|
324
|
+
dead = true
|
|
460
325
|
done(undefined)
|
|
461
326
|
}
|
|
462
327
|
},
|
|
463
328
|
|
|
464
329
|
render(width: number): string[] {
|
|
465
|
-
const
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
lines.push("")
|
|
469
|
-
lines.push("")
|
|
330
|
+
const W = width
|
|
331
|
+
const H = process.stdout?.rows || 40
|
|
332
|
+
const content: string[] = []
|
|
470
333
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
lines.push("")
|
|
477
|
-
|
|
478
|
-
if (phase === "subsystems" || phase === "briefing" || phase === "ready") {
|
|
479
|
-
const sysLines = renderSubsystems(theme, w)
|
|
480
|
-
lines.push(...sysLines)
|
|
481
|
-
lines.push("")
|
|
482
|
-
}
|
|
334
|
+
// Square
|
|
335
|
+
content.push(...renderSquare(W))
|
|
336
|
+
if (phase !== "square" && phase !== "glow") content.push("")
|
|
483
337
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
lines.push("")
|
|
338
|
+
// Systems
|
|
339
|
+
if (phase === "systems" || phase === "ready") {
|
|
340
|
+
content.push(...renderSystems(W))
|
|
488
341
|
}
|
|
489
342
|
|
|
343
|
+
// Ready
|
|
490
344
|
if (phase === "ready") {
|
|
491
|
-
|
|
492
|
-
lines.push(...readyLines)
|
|
493
|
-
lines.push("")
|
|
345
|
+
content.push(...renderReady(W))
|
|
494
346
|
}
|
|
495
347
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
lines.push("")
|
|
500
|
-
lines.push(hintPad + hint)
|
|
348
|
+
// Skip hint (during animation only)
|
|
349
|
+
if (phase === "square" || phase === "glow" || phase === "systems") {
|
|
350
|
+
content.push("", center(d("press any key to skip"), W))
|
|
501
351
|
}
|
|
502
352
|
|
|
503
|
-
|
|
353
|
+
// Vertically center, fill everything with blanks (covers Pi startup)
|
|
354
|
+
const topPad = Math.max(1, Math.floor((H - content.length) / 2))
|
|
355
|
+
const out: string[] = []
|
|
356
|
+
for (let i = 0; i < topPad; i++) out.push(" ".repeat(W))
|
|
357
|
+
for (const line of content) out.push(pad(line, W))
|
|
358
|
+
while (out.length < H) out.push(" ".repeat(W))
|
|
504
359
|
|
|
505
|
-
return
|
|
360
|
+
return out
|
|
506
361
|
},
|
|
507
362
|
|
|
508
363
|
invalidate() {},
|
|
509
364
|
}
|
|
510
365
|
}, {
|
|
511
366
|
overlay: true,
|
|
512
|
-
overlayOptions: {
|
|
513
|
-
dismissOnEscape: false,
|
|
514
|
-
},
|
|
367
|
+
overlayOptions: { dismissOnEscape: false },
|
|
515
368
|
})
|
|
516
369
|
}
|