jfl 0.9.0 → 0.9.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.
Files changed (136) hide show
  1. package/README.md +35 -4
  2. package/dist/commands/context-hub.d.ts.map +1 -1
  3. package/dist/commands/context-hub.js +118 -2
  4. package/dist/commands/context-hub.js.map +1 -1
  5. package/dist/commands/digest.d.ts +6 -0
  6. package/dist/commands/digest.d.ts.map +1 -1
  7. package/dist/commands/digest.js +70 -69
  8. package/dist/commands/digest.js.map +1 -1
  9. package/dist/commands/eval.d.ts +40 -0
  10. package/dist/commands/eval.d.ts.map +1 -1
  11. package/dist/commands/eval.js +8 -8
  12. package/dist/commands/eval.js.map +1 -1
  13. package/dist/commands/findings.d.ts +7 -0
  14. package/dist/commands/findings.d.ts.map +1 -1
  15. package/dist/commands/findings.js +4 -4
  16. package/dist/commands/findings.js.map +1 -1
  17. package/dist/commands/ide.d.ts.map +1 -1
  18. package/dist/commands/ide.js +25 -2
  19. package/dist/commands/ide.js.map +1 -1
  20. package/dist/commands/kanban.js +6 -6
  21. package/dist/commands/kanban.js.map +1 -1
  22. package/dist/commands/linear.d.ts.map +1 -1
  23. package/dist/commands/linear.js +24 -0
  24. package/dist/commands/linear.js.map +1 -1
  25. package/dist/commands/pi.d.ts +3 -0
  26. package/dist/commands/pi.d.ts.map +1 -1
  27. package/dist/commands/pi.js +19 -0
  28. package/dist/commands/pi.js.map +1 -1
  29. package/dist/commands/portfolio.d.ts +5 -0
  30. package/dist/commands/portfolio.d.ts.map +1 -1
  31. package/dist/commands/portfolio.js +193 -203
  32. package/dist/commands/portfolio.js.map +1 -1
  33. package/dist/commands/predict.d.ts +19 -0
  34. package/dist/commands/predict.d.ts.map +1 -1
  35. package/dist/commands/predict.js +4 -4
  36. package/dist/commands/predict.js.map +1 -1
  37. package/dist/commands/setup.d.ts.map +1 -1
  38. package/dist/commands/setup.js +106 -7
  39. package/dist/commands/setup.js.map +1 -1
  40. package/dist/commands/start.d.ts +25 -0
  41. package/dist/commands/start.d.ts.map +1 -0
  42. package/dist/commands/start.js +191 -0
  43. package/dist/commands/start.js.map +1 -0
  44. package/dist/commands/tenet-agents.js +2 -2
  45. package/dist/commands/tenet-agents.js.map +1 -1
  46. package/dist/commands/tenet-setup.d.ts +2 -1
  47. package/dist/commands/tenet-setup.d.ts.map +1 -1
  48. package/dist/commands/tenet-setup.js +22 -18
  49. package/dist/commands/tenet-setup.js.map +1 -1
  50. package/dist/commands/viz.d.ts +33 -0
  51. package/dist/commands/viz.d.ts.map +1 -1
  52. package/dist/commands/viz.js +9 -9
  53. package/dist/commands/viz.js.map +1 -1
  54. package/dist/index.js +97 -43
  55. package/dist/index.js.map +1 -1
  56. package/dist/lib/advanced-setup.d.ts +1 -1
  57. package/dist/lib/advanced-setup.d.ts.map +1 -1
  58. package/dist/lib/advanced-setup.js +22 -22
  59. package/dist/lib/advanced-setup.js.map +1 -1
  60. package/dist/lib/discovery-agent.js +4 -4
  61. package/dist/lib/discovery-agent.js.map +1 -1
  62. package/dist/lib/linear-id-map.d.ts.map +1 -1
  63. package/dist/lib/linear-id-map.js +2 -0
  64. package/dist/lib/linear-id-map.js.map +1 -1
  65. package/dist/lib/linear-webhook.d.ts +50 -0
  66. package/dist/lib/linear-webhook.d.ts.map +1 -0
  67. package/dist/lib/linear-webhook.js +92 -0
  68. package/dist/lib/linear-webhook.js.map +1 -0
  69. package/dist/lib/onboarding.d.ts +1 -1
  70. package/dist/lib/onboarding.js +14 -14
  71. package/dist/lib/onboarding.js.map +1 -1
  72. package/dist/lib/rl-manager.d.ts +1 -1
  73. package/dist/lib/rl-manager.d.ts.map +1 -1
  74. package/dist/lib/rl-manager.js +5 -4
  75. package/dist/lib/rl-manager.js.map +1 -1
  76. package/dist/lib/setup/starter-intelligence.d.ts +25 -0
  77. package/dist/lib/setup/starter-intelligence.d.ts.map +1 -0
  78. package/dist/lib/setup/starter-intelligence.js +309 -0
  79. package/dist/lib/setup/starter-intelligence.js.map +1 -0
  80. package/dist/lib/tool-schemas.d.ts +35 -0
  81. package/dist/lib/tool-schemas.d.ts.map +1 -0
  82. package/dist/lib/tool-schemas.js +246 -0
  83. package/dist/lib/tool-schemas.js.map +1 -0
  84. package/dist/lib/workspace/data-pipeline.d.ts.map +1 -1
  85. package/dist/lib/workspace/data-pipeline.js +29 -20
  86. package/dist/lib/workspace/data-pipeline.js.map +1 -1
  87. package/dist/lib/workspace/engine.d.ts +1 -0
  88. package/dist/lib/workspace/engine.d.ts.map +1 -1
  89. package/dist/lib/workspace/engine.js +10 -0
  90. package/dist/lib/workspace/engine.js.map +1 -1
  91. package/dist/lib/workspace/surface-registry.d.ts.map +1 -1
  92. package/dist/lib/workspace/surface-registry.js +5 -0
  93. package/dist/lib/workspace/surface-registry.js.map +1 -1
  94. package/dist/lib/workspace/surfaces/sidebar.d.ts.map +1 -1
  95. package/dist/lib/workspace/surfaces/sidebar.js +5 -1
  96. package/dist/lib/workspace/surfaces/sidebar.js.map +1 -1
  97. package/dist/lib/workspace/tmux-adapter.d.ts +8 -5
  98. package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -1
  99. package/dist/lib/workspace/tmux-adapter.js +38 -7
  100. package/dist/lib/workspace/tmux-adapter.js.map +1 -1
  101. package/dist/lib/workspace/tmux-sidebar.d.ts +14 -0
  102. package/dist/lib/workspace/tmux-sidebar.d.ts.map +1 -0
  103. package/dist/lib/workspace/tmux-sidebar.js +230 -0
  104. package/dist/lib/workspace/tmux-sidebar.js.map +1 -0
  105. package/dist/mcp/context-hub-mcp.js +7 -1
  106. package/dist/mcp/context-hub-mcp.js.map +1 -1
  107. package/dist/types/telemetry.d.ts +1 -0
  108. package/dist/types/telemetry.d.ts.map +1 -1
  109. package/dist/utils/jfl-config.d.ts +7 -2
  110. package/dist/utils/jfl-config.d.ts.map +1 -1
  111. package/dist/utils/jfl-config.js +14 -4
  112. package/dist/utils/jfl-config.js.map +1 -1
  113. package/package.json +1 -1
  114. package/packages/pi/assets/boot.mp3 +0 -0
  115. package/packages/pi/extensions/autoresearch.ts +3 -2
  116. package/packages/pi/extensions/context.ts +29 -66
  117. package/packages/pi/extensions/eval.ts +2 -1
  118. package/packages/pi/extensions/hub-tools.ts +267 -0
  119. package/packages/pi/extensions/hud-tool.ts +230 -69
  120. package/packages/pi/extensions/index.ts +43 -63
  121. package/packages/pi/extensions/jfl-resolve.ts +98 -0
  122. package/packages/pi/extensions/journal.ts +91 -6
  123. package/packages/pi/extensions/map-bridge.ts +31 -0
  124. package/packages/pi/extensions/memory-tool.ts +84 -4
  125. package/packages/pi/extensions/onboarding-v2.ts +367 -399
  126. package/packages/pi/extensions/peter-parker.ts +2 -1
  127. package/packages/pi/extensions/policy-head-tool.ts +3 -2
  128. package/packages/pi/extensions/portfolio-bridge.ts +3 -4
  129. package/packages/pi/extensions/service-skills.ts +214 -0
  130. package/packages/pi/extensions/session.ts +91 -15
  131. package/packages/pi/extensions/stratus-bridge.ts +2 -1
  132. package/packages/pi/extensions/synopsis-tool.ts +6 -1
  133. package/packages/pi/extensions/training-buffer-tool.ts +3 -2
  134. package/packages/pi/extensions/types.ts +2 -0
  135. package/packages/pi/package.json +3 -1
  136. package/packages/pi/skills/viz/SKILL.md +204 -0
@@ -12,7 +12,7 @@ import { existsSync, readFileSync, appendFileSync, mkdirSync } from "fs"
12
12
  import { join } from "path"
13
13
  import { execSync } from "child_process"
14
14
  import type { PiContext, PiTheme, JflConfig, AgentEndEvent, ToolExecutionEvent } from "./types.js"
15
- import { getCurrentBranch } from "./session.js"
15
+ import { getCurrentBranch, getSessionBranch } from "./session.js"
16
16
  import { emitCustomEvent } from "./map-bridge.js"
17
17
 
18
18
  let projectRoot = ""
@@ -33,7 +33,9 @@ interface JournalEntry {
33
33
  }
34
34
 
35
35
  function getJournalPath(root: string): string {
36
- const branch = getCurrentBranch(root)
36
+ // Prefer the session branch tracked by session.ts — git HEAD may differ
37
+ // if checkout failed due to dirty working tree
38
+ const branch = getSessionBranch() || getCurrentBranch(root)
37
39
  return join(root, ".jfl", "journal", `${branch}.jsonl`)
38
40
  }
39
41
 
@@ -57,7 +59,7 @@ function readRecentEntries(root: string, count = 5): JournalEntry[] {
57
59
  }
58
60
  }
59
61
 
60
- function hasJournalEntryForSession(root: string, sessionBranch: string): boolean {
62
+ export function hasJournalEntryForSession(root: string, sessionBranch: string): boolean {
61
63
  const journalPath = join(root, ".jfl", "journal", `${sessionBranch}.jsonl`)
62
64
  if (!existsSync(journalPath)) return false
63
65
  const content = readFileSync(journalPath, "utf-8").trim()
@@ -117,7 +119,7 @@ export async function setupJournal(ctx: PiContext, _config: JflConfig): Promise<
117
119
  const entry: JournalEntry = {
118
120
  v: 1,
119
121
  ts: new Date().toISOString(),
120
- session: getCurrentBranch(projectRoot),
122
+ session: getSessionBranch() || getCurrentBranch(projectRoot),
121
123
  type,
122
124
  status: "complete",
123
125
  title: title.trim(),
@@ -134,7 +136,7 @@ export async function setupJournal(ctx: PiContext, _config: JflConfig): Promise<
134
136
  }
135
137
 
136
138
  // Fallback: raw JSON input for non-interactive mode
137
- const branch = getCurrentBranch(projectRoot)
139
+ const branch = getSessionBranch() || getCurrentBranch(projectRoot)
138
140
  const template = JSON.stringify({
139
141
  v: 1,
140
142
  ts: new Date().toISOString(),
@@ -172,6 +174,21 @@ export async function onToolExecutionEnd(
172
174
  event: ToolExecutionEvent
173
175
  ): Promise<void> {
174
176
  const toolName = event.toolName ?? event.tool ?? ""
177
+
178
+ // TaskUpdate completed → journal nudge (parity with CC PostToolUse hook)
179
+ if (toolName === "TaskUpdate") {
180
+ const input = event.input ?? event.args ?? {}
181
+ const inputStr = typeof input === "string" ? input : JSON.stringify(input)
182
+ if (/"status"\s*:\s*"completed"/.test(inputStr)) {
183
+ const branch = getCurrentBranch(projectRoot)
184
+ ctx.ui.notify(
185
+ `Task completed — write journal entry\nFile: .jfl/journal/${branch}.jsonl`,
186
+ { level: "info" }
187
+ )
188
+ }
189
+ return
190
+ }
191
+
175
192
  if (toolName.toLowerCase() !== "bash") return
176
193
 
177
194
  const result = String(event.result ?? "")
@@ -202,10 +219,78 @@ export async function onJournalAgentEnd(
202
219
  // Removed: "Journal entry recommended" nudge (noisy)
203
220
  }
204
221
 
222
+ /**
223
+ * Check @purpose header on Write/Edit to code files.
224
+ * Equivalent to Claude Code's PostToolUse(Write|Edit) hook.
225
+ */
226
+ export function checkPurposeHeader(
227
+ ctx: PiContext,
228
+ event: ToolExecutionEvent
229
+ ): void {
230
+ const toolName = (event.toolName ?? event.tool ?? "").toLowerCase()
231
+ if (toolName !== "write" && toolName !== "edit") return
232
+
233
+ // Extract file path from tool input
234
+ const input = event.input ?? event.args ?? {}
235
+ const filePath = typeof input === "string"
236
+ ? input
237
+ : (input as any).path ?? (input as any).file_path ?? ""
238
+
239
+ if (!filePath) return
240
+ if (!/\.(ts|tsx|js|jsx)$/.test(filePath)) return
241
+
242
+ const fullPath = filePath.startsWith("/") ? filePath : join(projectRoot, filePath)
243
+ if (!existsSync(fullPath)) return
244
+
245
+ try {
246
+ const head = readFileSync(fullPath, "utf-8").slice(0, 800)
247
+ if (!head.includes("@purpose")) {
248
+ ctx.ui.notify(
249
+ `⚠️ Missing @purpose header in ${filePath}\n Add: /** @purpose One-line description */`,
250
+ { level: "warn" }
251
+ )
252
+ }
253
+ } catch {}
254
+ }
255
+
256
+ /**
257
+ * Auto-commit changes before context compaction.
258
+ * Equivalent to Claude Code's PreCompact hook that runs:
259
+ * git add knowledge/ previews/ content/ suggestions/ .jfl/ && git commit
260
+ */
261
+ function autoCommitBeforeCompact(ctx: PiContext): void {
262
+ const trackedDirs = ["knowledge", "previews", "content", "suggestions", ".jfl", "CLAUDE.md"]
263
+ const addPaths = trackedDirs
264
+ .filter((d) => existsSync(join(projectRoot, d)))
265
+ .join(" ")
266
+
267
+ if (!addPaths) return
268
+
269
+ try {
270
+ const status = execSync(`git status --porcelain -- ${addPaths}`, {
271
+ cwd: projectRoot,
272
+ encoding: "utf-8",
273
+ }).trim()
274
+
275
+ if (!status) return // nothing to commit
276
+
277
+ execSync(`git add ${addPaths} && git commit -m "auto-commit: pre-compact save"`, {
278
+ cwd: projectRoot,
279
+ stdio: "pipe",
280
+ })
281
+ ctx.log("Pre-compact auto-commit saved changes", "debug")
282
+ } catch {
283
+ // Not critical — auto-commit daemon is the primary safety net
284
+ }
285
+ }
286
+
205
287
  export async function checkJournalBeforeCompact(
206
288
  ctx: PiContext
207
289
  ): Promise<{ cancel: true } | void> {
208
- const branch = getCurrentBranch(projectRoot)
290
+ // Auto-commit changes before compaction (safety net)
291
+ autoCommitBeforeCompact(ctx)
292
+
293
+ const branch = getSessionBranch() || getCurrentBranch(projectRoot)
209
294
  if (!hasJournalEntryForSession(projectRoot, branch)) {
210
295
  if (ctx.ui.hasUI) {
211
296
  const ok = await ctx.ui.confirm(
@@ -143,6 +143,37 @@ export async function setupMapBridge(ctx: PiContext, config: JflConfig): Promise
143
143
  if (port) hubUrl = `http://localhost:${port}`
144
144
  }
145
145
 
146
+ // Verify the hub is actually reachable on this port.
147
+ // If not, try discovering the real port from the running process.
148
+ try {
149
+ const healthResp = await fetch(`${hubUrl}/health`, { signal: AbortSignal.timeout(3000) })
150
+ if (!healthResp.ok) throw new Error("unhealthy")
151
+ } catch {
152
+ ctx.log(`Hub unreachable at ${hubUrl}, attempting port discovery...`, "warn")
153
+ // Try to find the actual port from the running hub process
154
+ try {
155
+ const { execSync } = await import("child_process")
156
+ const psOut = execSync(
157
+ `ps aux | grep "context-hub serve.*--project-root ${root.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}" | grep -v grep`,
158
+ { encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
159
+ ).trim()
160
+ const portMatch = psOut.match(/--port\s+(\d+)/)
161
+ if (portMatch) {
162
+ const discoveredPort = portMatch[1]
163
+ const testResp = await fetch(`http://localhost:${discoveredPort}/health`, { signal: AbortSignal.timeout(3000) })
164
+ if (testResp.ok) {
165
+ hubUrl = `http://localhost:${discoveredPort}`
166
+ // Fix the stale port file
167
+ const { writeFileSync } = await import("fs")
168
+ writeFileSync(join(root, ".jfl", "context-hub.port"), discoveredPort)
169
+ ctx.log(`Hub discovered on port ${discoveredPort} — fixed stale port file`, "warn")
170
+ }
171
+ }
172
+ } catch {
173
+ ctx.log("Hub port discovery failed — hub tools will be unavailable", "warn")
174
+ }
175
+ }
176
+
146
177
  ctx.on("hook:session-start", (data) => postToHub({ type: "hook:session-start", source: "pi-session", data, ts: new Date().toISOString() }))
147
178
  ctx.on("hook:session-end", (data) => postToHub({ type: "hook:session-end", source: "pi-session", data, ts: new Date().toISOString() }))
148
179
 
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Memory Tool Extension
3
3
  *
4
- * Registers jfl_memory_search tool with custom TUI rendering.
5
- * Queries Context Hub memory API and renders results with
6
- * type-colored headers and collapsible sections.
4
+ * Registers jfl_memory_search, jfl_memory_add, and jfl_memory_status tools.
5
+ * Full parity with the Context Hub MCP server's memory tools.
7
6
  *
8
- * @purpose jfl_memory_search toolthemed semantic memory search results
7
+ * @purpose Memory toolssearch, add, and status for project memory
9
8
  */
10
9
 
11
10
  import type { PiContext } from "./types.js"
@@ -13,6 +12,7 @@ import { hubUrl, authToken } from "./map-bridge.js"
13
12
  import { memoryRenderCall, memoryRenderResult } from "./tool-renderers.js"
14
13
 
15
14
  export function setupMemoryTool(ctx: PiContext): void {
15
+ // ─── jfl_memory_search ───────────────────────────────────────────────────
16
16
  ctx.registerTool({
17
17
  name: "jfl_memory_search",
18
18
  description: "Search JFL project memory — find past decisions, learnings, and patterns across all sessions",
@@ -70,4 +70,84 @@ export function setupMemoryTool(ctx: PiContext): void {
70
70
  renderCall: memoryRenderCall,
71
71
  renderResult: memoryRenderResult,
72
72
  })
73
+
74
+ // ─── jfl_memory_add ──────────────────────────────────────────────────────
75
+ ctx.registerTool({
76
+ name: "jfl_memory_add",
77
+ description: "Manually add a memory or note to the project memory system. Use to capture insights, decisions, or important context that should persist across sessions.",
78
+ promptSnippet: "Add a memory/note to project memory",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ title: {
83
+ type: "string",
84
+ description: "Title for the memory entry",
85
+ },
86
+ content: {
87
+ type: "string",
88
+ description: "Content of the memory — the insight, decision, or note",
89
+ },
90
+ type: {
91
+ type: "string",
92
+ description: "Type of memory entry",
93
+ enum: ["decision", "discovery", "insight", "note"],
94
+ },
95
+ },
96
+ required: ["title", "content"],
97
+ },
98
+ async handler(input) {
99
+ const { title, content, type } = input as { title: string; content: string; type?: string }
100
+
101
+ try {
102
+ const resp = await fetch(`${hubUrl}/api/memory/add`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
107
+ },
108
+ body: JSON.stringify({ title, content, type: type ?? "note" }),
109
+ signal: AbortSignal.timeout(5000),
110
+ })
111
+
112
+ if (!resp.ok) return "Failed to add memory — hub returned error."
113
+ const data = await resp.json() as { ok?: boolean; id?: string }
114
+ return data.ok
115
+ ? `Memory added: "${title}" (${type ?? "note"})`
116
+ : "Memory add returned unexpected response."
117
+ } catch {
118
+ return "Memory add unavailable — Context Hub may not be running."
119
+ }
120
+ },
121
+ })
122
+
123
+ // ─── jfl_memory_status ───────────────────────────────────────────────────
124
+ ctx.registerTool({
125
+ name: "jfl_memory_status",
126
+ description: "Get memory system statistics and health — total entries, indexed count, embedding status.",
127
+ promptSnippet: "Check memory system health and stats",
128
+ inputSchema: {
129
+ type: "object",
130
+ properties: {},
131
+ },
132
+ async handler() {
133
+ try {
134
+ const resp = await fetch(`${hubUrl}/api/memory/status`, {
135
+ method: "GET",
136
+ headers: {
137
+ ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
138
+ },
139
+ signal: AbortSignal.timeout(5000),
140
+ })
141
+
142
+ if (!resp.ok) return "Memory status unavailable."
143
+ const data = await resp.json() as Record<string, unknown>
144
+
145
+ return Object.entries(data)
146
+ .map(([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`)
147
+ .join("\n")
148
+ } catch {
149
+ return "Memory status unavailable — Context Hub may not be running."
150
+ }
151
+ },
152
+ })
73
153
  }