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,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context Extension
|
|
3
3
|
*
|
|
4
|
-
* Ensures Context Hub is running
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Ensures Context Hub is running and registers the jfl_context tool.
|
|
5
|
+
* On first turn, injects a small amount of recent journal context so
|
|
6
|
+
* the model's greeting is contextual. The model reads CLAUDE.md itself
|
|
7
|
+
* via AGENTS.md instructions — no 45K system prompt injection needed.
|
|
7
8
|
*
|
|
8
|
-
* @purpose Context Hub +
|
|
9
|
+
* @purpose Context Hub startup + jfl_context tool registration
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { existsSync, readFileSync } from "fs"
|
|
@@ -18,7 +19,11 @@ import { readHubUrl, readToken } from "./hub-resolver.js"
|
|
|
18
19
|
let hubBaseUrl = "http://localhost:4242"
|
|
19
20
|
let hubToken: string | null = null
|
|
20
21
|
let projectRoot = ""
|
|
21
|
-
let
|
|
22
|
+
let promptInjected = false
|
|
23
|
+
|
|
24
|
+
export function resetPromptInjected(): void {
|
|
25
|
+
promptInjected = false
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
function refreshHubUrl(): void {
|
|
24
29
|
hubBaseUrl = readHubUrl(projectRoot)
|
|
@@ -28,10 +33,6 @@ function refreshHubUrl(): void {
|
|
|
28
33
|
async function fetchContext(query?: string, limit = 10): Promise<string> {
|
|
29
34
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
30
35
|
try {
|
|
31
|
-
const params = new URLSearchParams()
|
|
32
|
-
if (query) params.set("query", query)
|
|
33
|
-
params.set("limit", String(limit))
|
|
34
|
-
|
|
35
36
|
const body: Record<string, unknown> = { maxItems: limit }
|
|
36
37
|
if (query) body.query = query
|
|
37
38
|
|
|
@@ -67,81 +68,12 @@ async function fetchContext(query?: string, limit = 10): Promise<string> {
|
|
|
67
68
|
return ""
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Strip Claude-Code-specific instructions from CLAUDE.md prompt.
|
|
72
|
-
* Pi handles these via extensions (session.ts, map-bridge.ts, journal.ts)
|
|
73
|
-
* so the LLM shouldn't be told to do them manually.
|
|
74
|
-
*/
|
|
75
|
-
function stripClaudeCodeInstructions(prompt: string): string {
|
|
76
|
-
let cleaned = prompt
|
|
77
|
-
|
|
78
|
-
// Strip "Session Sync" section — Pi's session.ts handles via Hub API
|
|
79
|
-
cleaned = cleaned.replace(
|
|
80
|
-
/## CRITICAL: Session Sync[\s\S]*?(?=\n## (?!.*Session Sync))/,
|
|
81
|
-
"## Session Management\n\nSession sync, doctor checks, and branch management are handled automatically by Pi extensions. No manual action needed.\n\n"
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
// Strip MCP tool references — Pi has native tools instead
|
|
85
|
-
cleaned = cleaned.replace(/Call:\s*mcp__jfl-context__\w+/g, "Use the jfl_context tool")
|
|
86
|
-
cleaned = cleaned.replace(/mcp__jfl-context__context_get/g, "jfl_context")
|
|
87
|
-
cleaned = cleaned.replace(/mcp__jfl-context__context_search/g, "jfl_context (with query)")
|
|
88
|
-
cleaned = cleaned.replace(/mcp__jfl-context__memory_search/g, "jfl_memory_search")
|
|
89
|
-
|
|
90
|
-
// Strip .claude/settings.json references — Pi uses extensions
|
|
91
|
-
cleaned = cleaned.replace(/Hooks in `\.claude\/settings\.json`[^\n]*/g, "Hooks are managed by Pi extensions automatically.")
|
|
92
|
-
cleaned = cleaned.replace(/`\.claude\/settings\.json`[^`\n]*hooks[^.\n]*/gi, "Pi extension hooks")
|
|
93
|
-
|
|
94
|
-
// Strip session-sync.sh and jfl-doctor.sh instructions — session.ts handles
|
|
95
|
-
cleaned = cleaned.replace(/\n.*session-sync\.sh.*/g, "")
|
|
96
|
-
cleaned = cleaned.replace(/\n.*jfl-doctor\.sh.*/g, "")
|
|
97
|
-
cleaned = cleaned.replace(/\.\/scripts\/session\/session-sync\.sh/g, "(handled automatically)")
|
|
98
|
-
cleaned = cleaned.replace(/\.\/scripts\/session\/jfl-doctor\.sh/g, "(handled automatically)")
|
|
99
|
-
|
|
100
|
-
// Strip "Path B" sections (Claude Code manual startup)
|
|
101
|
-
const pathBPatterns = [
|
|
102
|
-
/### Path B: Claude Code \/ No Extension[\s\S]*?(?=\n### (?!.*Path B))/,
|
|
103
|
-
/### How to Tell Which Path You're On[\s\S]*?(?=\n###? )/,
|
|
104
|
-
]
|
|
105
|
-
for (const pattern of pathBPatterns) {
|
|
106
|
-
cleaned = cleaned.replace(pattern, "")
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Strip file structure references to .claude/
|
|
110
|
-
cleaned = cleaned.replace(/├── \.claude\/settings\.json[^\n]*/g, "")
|
|
111
|
-
cleaned = cleaned.replace(/- `\.claude\/settings\.json`[^\n]*/g, "")
|
|
112
|
-
cleaned = cleaned.replace(/- `\.claude\/skills\/`[^\n]*/g, "")
|
|
113
|
-
|
|
114
|
-
return cleaned
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function fetchPrompt(taskType?: string): Promise<string> {
|
|
118
|
-
try {
|
|
119
|
-
const resp = await fetch(`${hubBaseUrl}/api/prompt`, {
|
|
120
|
-
method: "POST",
|
|
121
|
-
headers: {
|
|
122
|
-
"Content-Type": "application/json",
|
|
123
|
-
...(hubToken ? { Authorization: `Bearer ${hubToken}` } : {}),
|
|
124
|
-
},
|
|
125
|
-
body: JSON.stringify({ taskType: taskType ?? "general", maxItems: 20 }),
|
|
126
|
-
signal: AbortSignal.timeout(10000),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
if (!resp.ok) return ""
|
|
130
|
-
const data = await resp.json() as { prompt?: string }
|
|
131
|
-
const raw = data.prompt ?? ""
|
|
132
|
-
|
|
133
|
-
// Clean CC-specific instructions — Pi handles these via extensions
|
|
134
|
-
return stripClaudeCodeInstructions(raw)
|
|
135
|
-
} catch {
|
|
136
|
-
return ""
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
71
|
export async function setupContext(ctx: PiContext, _config: JflConfig): Promise<void> {
|
|
141
72
|
const root = ctx.session.projectRoot
|
|
142
73
|
projectRoot = root
|
|
74
|
+
promptInjected = false // reset on each new session
|
|
143
75
|
|
|
144
|
-
// Start Context Hub
|
|
76
|
+
// Start Context Hub
|
|
145
77
|
try {
|
|
146
78
|
execSync("jfl context-hub ensure", { cwd: root, stdio: "pipe", timeout: 15000 })
|
|
147
79
|
ctx.log("Context Hub ensured", "debug")
|
|
@@ -150,15 +82,20 @@ export async function setupContext(ctx: PiContext, _config: JflConfig): Promise<
|
|
|
150
82
|
ctx.log(`Context Hub ensure failed: ${msg}`, "debug")
|
|
151
83
|
}
|
|
152
84
|
|
|
153
|
-
//
|
|
85
|
+
// Read the port hub wrote
|
|
154
86
|
hubBaseUrl = readHubUrl(root)
|
|
155
87
|
hubToken = readToken(root)
|
|
156
88
|
ctx.log(`Context Hub URL: ${hubBaseUrl}`, "debug")
|
|
157
89
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
90
|
+
// Wait for hub to actually accept connections (startDaemon only waits 800ms —
|
|
91
|
+
// not enough for the HTTP server to bind. Without this, session/init races and
|
|
92
|
+
// falls back to ctx.session.branch = "main".)
|
|
93
|
+
for (let i = 0; i < 20; i++) {
|
|
94
|
+
try {
|
|
95
|
+
const resp = await fetch(`${hubBaseUrl}/health`, { signal: AbortSignal.timeout(500) })
|
|
96
|
+
if (resp.ok) { ctx.log("Context Hub ready", "debug"); break }
|
|
97
|
+
} catch {}
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, 300))
|
|
162
99
|
}
|
|
163
100
|
|
|
164
101
|
ctx.registerTool({
|
|
@@ -189,15 +126,7 @@ export async function setupContext(ctx: PiContext, _config: JflConfig): Promise<
|
|
|
189
126
|
},
|
|
190
127
|
},
|
|
191
128
|
async handler(input) {
|
|
192
|
-
const { query, limit
|
|
193
|
-
|
|
194
|
-
// If asking for general context with no query, use the prompt endpoint
|
|
195
|
-
if (!query && taskType) {
|
|
196
|
-
const prompt = await fetchPrompt(taskType)
|
|
197
|
-
return prompt || "No context available."
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Otherwise use the search endpoint
|
|
129
|
+
const { query, limit } = input as { query?: string; limit?: number; taskType?: string }
|
|
201
130
|
const result = await fetchContext(query, limit ?? 30)
|
|
202
131
|
return result || "No relevant context found."
|
|
203
132
|
},
|
|
@@ -210,25 +139,20 @@ export async function injectContext(
|
|
|
210
139
|
_ctx: PiContext,
|
|
211
140
|
_event: AgentStartEvent
|
|
212
141
|
): Promise<{ systemPromptAddition?: string } | void> {
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (!
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
context,
|
|
229
|
-
].join("\n"),
|
|
230
|
-
}
|
|
142
|
+
// Only on first turn — inject recent journal context so greeting is contextual.
|
|
143
|
+
// The model reads CLAUDE.md itself (AGENTS.md tells it to). No 45K dump needed.
|
|
144
|
+
if (promptInjected) return
|
|
145
|
+
promptInjected = true
|
|
146
|
+
|
|
147
|
+
const context = await fetchContext(undefined, 10)
|
|
148
|
+
if (!context) return
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
systemPromptAddition: [
|
|
152
|
+
"## JFL Project Context",
|
|
153
|
+
"(Recent journal entries and project knowledge)",
|
|
154
|
+
"",
|
|
155
|
+
context,
|
|
156
|
+
].join("\n"),
|
|
231
157
|
}
|
|
232
|
-
|
|
233
|
-
return { systemPromptAddition: prompt }
|
|
234
158
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { randomUUID } from "crypto"
|
|
11
11
|
import type { PiContext, JflConfig, AgentEndEvent } from "./types.js"
|
|
12
|
+
import { jflImport } from "./jfl-resolve.js"
|
|
12
13
|
import { emitCustomEvent } from "./map-bridge.js"
|
|
13
14
|
|
|
14
15
|
interface EvalEntry {
|
|
@@ -50,7 +51,7 @@ export async function onAgentEnd(ctx: PiContext, event: AgentEndEvent): Promise<
|
|
|
50
51
|
|
|
51
52
|
try {
|
|
52
53
|
// @ts-ignore — resolved from jfl package at runtime
|
|
53
|
-
const { appendEval } = await
|
|
54
|
+
const { appendEval } = await jflImport("eval-store")
|
|
54
55
|
appendEval(entry as Parameters<typeof appendEval>[0], projectRoot)
|
|
55
56
|
} catch {
|
|
56
57
|
// eval-store may not be available in all contexts — non-fatal
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TENET Header
|
|
3
|
+
*
|
|
4
|
+
* Replaces Pi's default startup header (logo + keybinding hints) with a
|
|
5
|
+
* branded TENET identity line. Shows project name, branch, and key system
|
|
6
|
+
* indicators in a single compact row. This owns "above the fold" —
|
|
7
|
+
* everything the user sees before any messages.
|
|
8
|
+
*
|
|
9
|
+
* @purpose Branded header replacing Pi's default — owns above-the-fold identity
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, readdirSync } from "fs"
|
|
13
|
+
import { join } from "path"
|
|
14
|
+
import type { PiContext, PiTheme, JflConfig } from "./types.js"
|
|
15
|
+
|
|
16
|
+
// ─── Cached state ────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
let projectRoot = ""
|
|
19
|
+
let projectName = ""
|
|
20
|
+
let branch = ""
|
|
21
|
+
let journalCount = 0
|
|
22
|
+
let tupleCount = 0
|
|
23
|
+
let sessionCount = 0
|
|
24
|
+
let hubOnline = false
|
|
25
|
+
let autoCommitRunning = false
|
|
26
|
+
let evalScore: string | null = null
|
|
27
|
+
|
|
28
|
+
// ─── Data helpers ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function countJournals(root: string): { entries: number; sessions: number } {
|
|
31
|
+
const dir = join(root, ".jfl", "journal")
|
|
32
|
+
if (!existsSync(dir)) return { entries: 0, sessions: 0 }
|
|
33
|
+
try {
|
|
34
|
+
const files = readdirSync(dir).filter(f => f.endsWith(".jsonl"))
|
|
35
|
+
let entries = 0
|
|
36
|
+
for (const f of files) {
|
|
37
|
+
entries += readFileSync(join(dir, f), "utf-8").trim().split("\n").filter(Boolean).length
|
|
38
|
+
}
|
|
39
|
+
return { entries, sessions: files.length }
|
|
40
|
+
} catch {
|
|
41
|
+
return { entries: 0, sessions: 0 }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function countTuples(root: string): number {
|
|
46
|
+
const p = join(root, ".jfl", "training-buffer.jsonl")
|
|
47
|
+
if (!existsSync(p)) return 0
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(p, "utf-8").trim().split("\n").filter(Boolean).length
|
|
50
|
+
} catch {
|
|
51
|
+
return 0
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getLatestEval(root: string): string | null {
|
|
56
|
+
try {
|
|
57
|
+
const p = join(root, ".jfl", "eval.jsonl")
|
|
58
|
+
if (!existsSync(p)) return null
|
|
59
|
+
const lines = readFileSync(p, "utf-8").trim().split("\n").filter(Boolean)
|
|
60
|
+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 20); i--) {
|
|
61
|
+
try {
|
|
62
|
+
const e = JSON.parse(lines[i])
|
|
63
|
+
const s = e.composite ?? e.score
|
|
64
|
+
if (s != null) return Number(s).toFixed(2)
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
} catch {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Refresh on-demand (called from onboarding probes or session_start) ──────
|
|
74
|
+
|
|
75
|
+
export function refreshHeaderData(root: string, config: JflConfig): void {
|
|
76
|
+
projectRoot = root
|
|
77
|
+
projectName = config.name ?? root.split("/").pop() ?? "TENET"
|
|
78
|
+
const { entries, sessions } = countJournals(root)
|
|
79
|
+
journalCount = entries
|
|
80
|
+
sessionCount = sessions
|
|
81
|
+
tupleCount = countTuples(root)
|
|
82
|
+
evalScore = getLatestEval(root)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function setHeaderHubStatus(online: boolean): void {
|
|
86
|
+
hubOnline = online
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function setHeaderAutoCommit(running: boolean): void {
|
|
90
|
+
autoCommitRunning = running
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function setHeaderBranch(b: string): void {
|
|
94
|
+
branch = b
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── ANSI helpers ────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
function visLen(s: string): number {
|
|
100
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function truncAnsi(s: string, max: number): string {
|
|
104
|
+
let vis = 0
|
|
105
|
+
let i = 0
|
|
106
|
+
while (i < s.length && vis < max) {
|
|
107
|
+
if (s[i] === "\x1b") {
|
|
108
|
+
const end = s.indexOf("m", i)
|
|
109
|
+
if (end !== -1) { i = end + 1; continue }
|
|
110
|
+
}
|
|
111
|
+
vis++
|
|
112
|
+
i++
|
|
113
|
+
}
|
|
114
|
+
return vis >= max ? s.slice(0, i) : s
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Render ──────────────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
function renderHeader(width: number, theme: PiTheme): string[] {
|
|
120
|
+
// Line 1: ◆ TENET · project-name · branch
|
|
121
|
+
const diamond = theme.fg("warning", "◆")
|
|
122
|
+
const brand = theme.bold(theme.fg("accent", "TENET"))
|
|
123
|
+
const name = theme.fg("text", projectName)
|
|
124
|
+
const branchStr = theme.fg("muted", branch || "main")
|
|
125
|
+
|
|
126
|
+
// Line 2: compact system indicators
|
|
127
|
+
const hubIcon = hubOnline
|
|
128
|
+
? theme.fg("success", "✓") + theme.fg("dim", " Hub")
|
|
129
|
+
: theme.fg("error", "✗") + theme.fg("dim", " Hub")
|
|
130
|
+
|
|
131
|
+
const acIcon = autoCommitRunning
|
|
132
|
+
? theme.fg("success", "✓") + theme.fg("dim", " Auto-commit")
|
|
133
|
+
: theme.fg("dim", "– Auto-commit")
|
|
134
|
+
|
|
135
|
+
const parts: string[] = [hubIcon, acIcon]
|
|
136
|
+
|
|
137
|
+
if (journalCount > 0) {
|
|
138
|
+
parts.push(theme.fg("dim", `${journalCount} entries · ${sessionCount} sessions`))
|
|
139
|
+
}
|
|
140
|
+
if (tupleCount > 0) {
|
|
141
|
+
parts.push(theme.fg("dim", `${tupleCount} tuples`))
|
|
142
|
+
}
|
|
143
|
+
if (evalScore) {
|
|
144
|
+
parts.push(theme.fg("warning", evalScore) + theme.fg("dim", " eval"))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const sep = theme.fg("dim", " · ")
|
|
148
|
+
const line1 = truncAnsi(` ${diamond} ${brand}${sep}${name}${sep}${branchStr}`, width)
|
|
149
|
+
const line2 = truncAnsi(` ${parts.join(theme.fg("dim", " │ "))}`, width)
|
|
150
|
+
|
|
151
|
+
return [line1, line2]
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Setup ───────────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
export function setupHeader(ctx: PiContext, config: JflConfig): void {
|
|
157
|
+
if (!ctx.ui.hasUI) return
|
|
158
|
+
|
|
159
|
+
projectRoot = ctx.session.projectRoot
|
|
160
|
+
branch = ctx.session.branch
|
|
161
|
+
refreshHeaderData(projectRoot, config)
|
|
162
|
+
|
|
163
|
+
// Use the raw Pi setHeader API (available on latestPiCtx.ui)
|
|
164
|
+
// We access it through the shim's setHeader if available
|
|
165
|
+
ctx.ui.setHeader?.((tui: any, theme: PiTheme) => ({
|
|
166
|
+
render(width: number): string[] {
|
|
167
|
+
return renderHeader(width, theme)
|
|
168
|
+
},
|
|
169
|
+
invalidate() {},
|
|
170
|
+
}))
|
|
171
|
+
}
|
|
@@ -12,22 +12,42 @@
|
|
|
12
12
|
|
|
13
13
|
import type { PiContext, JflConfig } from "./types.js"
|
|
14
14
|
import { hubUrl, authToken } from "./map-bridge.js"
|
|
15
|
+
import { readHubUrl, readToken } from "./hub-resolver.js"
|
|
16
|
+
|
|
17
|
+
let projectRoot = ""
|
|
15
18
|
|
|
16
19
|
async function hubFetch(path: string, method: "GET" | "POST" = "GET", body?: unknown): Promise<any> {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
// Try current hubUrl, then fall back to fresh port file read
|
|
21
|
+
const urls = [hubUrl]
|
|
22
|
+
if (projectRoot) {
|
|
23
|
+
const fresh = readHubUrl(projectRoot)
|
|
24
|
+
if (fresh && fresh !== hubUrl) urls.push(fresh)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let lastErr: Error | null = null
|
|
28
|
+
for (const url of urls) {
|
|
29
|
+
const token = url === hubUrl ? authToken : (projectRoot ? readToken(projectRoot) : authToken)
|
|
30
|
+
try {
|
|
31
|
+
const resp = await fetch(`${url}${path}`, {
|
|
32
|
+
method,
|
|
33
|
+
headers: {
|
|
34
|
+
"Content-Type": "application/json",
|
|
35
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
36
|
+
},
|
|
37
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
38
|
+
signal: AbortSignal.timeout(5000),
|
|
39
|
+
})
|
|
40
|
+
if (!resp.ok) throw new Error(`Hub ${path}: ${resp.status}`)
|
|
41
|
+
return await resp.json()
|
|
42
|
+
} catch (err) {
|
|
43
|
+
lastErr = err as Error
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
throw lastErr ?? new Error(`Hub ${path}: unreachable`)
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
export async function setupHubTools(ctx: PiContext, _config: JflConfig): Promise<void> {
|
|
50
|
+
projectRoot = ctx.session.projectRoot
|
|
31
51
|
|
|
32
52
|
// ─── jfl_events_publish ──────────────────────────────────────────────────
|
|
33
53
|
ctx.registerTool({
|