jfl 0.9.0 → 0.9.1
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/README.md +35 -4
- package/dist/commands/digest.d.ts +6 -0
- package/dist/commands/digest.d.ts.map +1 -1
- package/dist/commands/digest.js +70 -69
- package/dist/commands/digest.js.map +1 -1
- package/dist/commands/eval.d.ts +40 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +8 -8
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/findings.d.ts +7 -0
- package/dist/commands/findings.d.ts.map +1 -1
- package/dist/commands/findings.js +4 -4
- package/dist/commands/findings.js.map +1 -1
- package/dist/commands/ide.js +3 -2
- package/dist/commands/ide.js.map +1 -1
- package/dist/commands/kanban.js +6 -6
- package/dist/commands/kanban.js.map +1 -1
- package/dist/commands/portfolio.d.ts +5 -0
- package/dist/commands/portfolio.d.ts.map +1 -1
- package/dist/commands/portfolio.js +193 -203
- package/dist/commands/portfolio.js.map +1 -1
- package/dist/commands/predict.d.ts +19 -0
- package/dist/commands/predict.d.ts.map +1 -1
- package/dist/commands/predict.js +4 -4
- package/dist/commands/predict.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +106 -7
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/start.d.ts +25 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +191 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/tenet-agents.js +2 -2
- package/dist/commands/tenet-agents.js.map +1 -1
- package/dist/commands/tenet-setup.d.ts +2 -1
- package/dist/commands/tenet-setup.d.ts.map +1 -1
- package/dist/commands/tenet-setup.js +22 -18
- package/dist/commands/tenet-setup.js.map +1 -1
- package/dist/commands/viz.d.ts +33 -0
- package/dist/commands/viz.d.ts.map +1 -1
- package/dist/commands/viz.js +9 -9
- package/dist/commands/viz.js.map +1 -1
- package/dist/index.js +94 -43
- package/dist/index.js.map +1 -1
- package/dist/lib/advanced-setup.d.ts +1 -1
- package/dist/lib/advanced-setup.d.ts.map +1 -1
- package/dist/lib/advanced-setup.js +15 -15
- package/dist/lib/advanced-setup.js.map +1 -1
- package/dist/lib/discovery-agent.js +3 -3
- package/dist/lib/discovery-agent.js.map +1 -1
- package/dist/lib/linear-id-map.d.ts.map +1 -1
- package/dist/lib/linear-id-map.js +2 -0
- package/dist/lib/linear-id-map.js.map +1 -1
- package/dist/lib/onboarding.d.ts +1 -1
- package/dist/lib/onboarding.js +13 -13
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/rl-manager.d.ts.map +1 -1
- package/dist/lib/rl-manager.js +2 -1
- package/dist/lib/rl-manager.js.map +1 -1
- package/dist/lib/setup/starter-intelligence.d.ts +25 -0
- package/dist/lib/setup/starter-intelligence.d.ts.map +1 -0
- package/dist/lib/setup/starter-intelligence.js +309 -0
- package/dist/lib/setup/starter-intelligence.js.map +1 -0
- package/dist/lib/workspace/surface-registry.d.ts.map +1 -1
- package/dist/lib/workspace/surface-registry.js +5 -0
- package/dist/lib/workspace/surface-registry.js.map +1 -1
- package/dist/lib/workspace/surfaces/sidebar.d.ts.map +1 -1
- package/dist/lib/workspace/surfaces/sidebar.js +5 -1
- package/dist/lib/workspace/surfaces/sidebar.js.map +1 -1
- package/dist/lib/workspace/tmux-adapter.d.ts +8 -5
- package/dist/lib/workspace/tmux-adapter.d.ts.map +1 -1
- package/dist/lib/workspace/tmux-adapter.js +38 -7
- package/dist/lib/workspace/tmux-adapter.js.map +1 -1
- package/dist/lib/workspace/tmux-sidebar.d.ts +14 -0
- package/dist/lib/workspace/tmux-sidebar.d.ts.map +1 -0
- package/dist/lib/workspace/tmux-sidebar.js +230 -0
- package/dist/lib/workspace/tmux-sidebar.js.map +1 -0
- package/dist/utils/jfl-config.d.ts +7 -2
- package/dist/utils/jfl-config.d.ts.map +1 -1
- package/dist/utils/jfl-config.js +14 -4
- package/dist/utils/jfl-config.js.map +1 -1
- package/package.json +1 -1
- package/packages/pi/extensions/context.ts +51 -1
- package/packages/pi/extensions/hub-tools.ts +247 -0
- package/packages/pi/extensions/index.ts +4 -0
- package/packages/pi/extensions/memory-tool.ts +84 -4
- package/packages/pi/extensions/service-skills.ts +214 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory Tool Extension
|
|
3
3
|
*
|
|
4
|
-
* Registers jfl_memory_search
|
|
5
|
-
*
|
|
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
|
|
7
|
+
* @purpose Memory tools — search, 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
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Skills Bridge
|
|
3
|
+
*
|
|
4
|
+
* Scans .claude/skills/ for service-type skills (generated during jfl init
|
|
5
|
+
* service onboarding) and registers them as Pi commands + tools.
|
|
6
|
+
* This gives Pi parity with Claude Code's service agent access.
|
|
7
|
+
*
|
|
8
|
+
* Service skills in .claude/skills/ have frontmatter `type: service` and
|
|
9
|
+
* include commands like status, logs, start, stop, restart, health.
|
|
10
|
+
*
|
|
11
|
+
* @purpose Bridge .claude/skills/ service agents into Pi sessions
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync } from "fs"
|
|
15
|
+
import { join } from "path"
|
|
16
|
+
import { execSync } from "child_process"
|
|
17
|
+
import type { PiContext, JflConfig } from "./types.js"
|
|
18
|
+
|
|
19
|
+
interface ServiceSkill {
|
|
20
|
+
name: string
|
|
21
|
+
description: string
|
|
22
|
+
servicePath: string
|
|
23
|
+
commands: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
27
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/)
|
|
28
|
+
if (!match) return {}
|
|
29
|
+
const fm: Record<string, string> = {}
|
|
30
|
+
for (const line of match[1].split("\n")) {
|
|
31
|
+
const [key, ...rest] = line.split(":")
|
|
32
|
+
if (key && rest.length) {
|
|
33
|
+
fm[key.trim()] = rest.join(":").trim()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return fm
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function discoverServiceSkills(projectRoot: string): ServiceSkill[] {
|
|
40
|
+
const claudeSkillsDir = join(projectRoot, ".claude", "skills")
|
|
41
|
+
if (!existsSync(claudeSkillsDir)) return []
|
|
42
|
+
|
|
43
|
+
const skills: ServiceSkill[] = []
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const entries = readdirSync(claudeSkillsDir, { withFileTypes: true })
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.isDirectory()) continue
|
|
50
|
+
|
|
51
|
+
const skillPath = join(claudeSkillsDir, entry.name, "SKILL.md")
|
|
52
|
+
if (!existsSync(skillPath)) continue
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const content = readFileSync(skillPath, "utf-8")
|
|
56
|
+
const fm = parseFrontmatter(content)
|
|
57
|
+
|
|
58
|
+
if (fm.type !== "service") continue
|
|
59
|
+
|
|
60
|
+
// Extract available commands from the markdown table
|
|
61
|
+
const commands: string[] = []
|
|
62
|
+
const cmdMatch = content.match(/\| `(\w+)` \|/g)
|
|
63
|
+
if (cmdMatch) {
|
|
64
|
+
for (const m of cmdMatch) {
|
|
65
|
+
const cmd = m.match(/`(\w+)`/)
|
|
66
|
+
if (cmd) commands.push(cmd[1])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Fallback defaults
|
|
70
|
+
if (commands.length === 0) {
|
|
71
|
+
commands.push("status", "logs", "start", "stop", "restart", "health", "recent")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
skills.push({
|
|
75
|
+
name: entry.name,
|
|
76
|
+
description: fm.description || `${entry.name} service agent`,
|
|
77
|
+
servicePath: fm.service_path || "",
|
|
78
|
+
commands,
|
|
79
|
+
})
|
|
80
|
+
} catch {
|
|
81
|
+
// Skip unparseable skills
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// .claude/skills not readable
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return skills
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runServiceCommand(serviceName: string, command: string, servicePath: string, projectRoot: string): string {
|
|
92
|
+
// Try jfl service-agent first
|
|
93
|
+
try {
|
|
94
|
+
const result = execSync(
|
|
95
|
+
`jfl service-agent run ${serviceName} ${command}`,
|
|
96
|
+
{ cwd: projectRoot, encoding: "utf-8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] }
|
|
97
|
+
)
|
|
98
|
+
return result.trim()
|
|
99
|
+
} catch {
|
|
100
|
+
// Fallback: direct execution based on command type
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!servicePath || !existsSync(servicePath)) {
|
|
104
|
+
return `Service path not found: ${servicePath}`
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
switch (command) {
|
|
109
|
+
case "status": {
|
|
110
|
+
const branch = execSync("git branch --show-current", { cwd: servicePath, encoding: "utf-8" }).trim()
|
|
111
|
+
const lastCommit = execSync("git log -1 --pretty='%h %s (%cr)'", { cwd: servicePath, encoding: "utf-8" }).trim()
|
|
112
|
+
const dirty = execSync("git status --porcelain", { cwd: servicePath, encoding: "utf-8" }).trim()
|
|
113
|
+
return [
|
|
114
|
+
`Service: ${serviceName}`,
|
|
115
|
+
`Path: ${servicePath}`,
|
|
116
|
+
`Branch: ${branch}`,
|
|
117
|
+
`Last commit: ${lastCommit}`,
|
|
118
|
+
`Working tree: ${dirty ? `${dirty.split("\n").length} changes` : "clean"}`,
|
|
119
|
+
].join("\n")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case "logs": {
|
|
123
|
+
return execSync("git log --oneline -10", { cwd: servicePath, encoding: "utf-8" }).trim()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case "recent": {
|
|
127
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()
|
|
128
|
+
try {
|
|
129
|
+
return execSync(`git log --oneline --since="${since}"`, { cwd: servicePath, encoding: "utf-8" }).trim() || "No changes in last 24h"
|
|
130
|
+
} catch {
|
|
131
|
+
return "No changes in last 24h"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
case "health": {
|
|
136
|
+
// Check if package.json exists and has a test script
|
|
137
|
+
const pkgPath = join(servicePath, "package.json")
|
|
138
|
+
if (existsSync(pkgPath)) {
|
|
139
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
|
|
140
|
+
const hasTests = !!pkg.scripts?.test
|
|
141
|
+
const hasBuild = !!pkg.scripts?.build
|
|
142
|
+
return [
|
|
143
|
+
`Service: ${serviceName}`,
|
|
144
|
+
`Has tests: ${hasTests ? "yes" : "no"}`,
|
|
145
|
+
`Has build: ${hasBuild ? "yes" : "no"}`,
|
|
146
|
+
`Dependencies: ${Object.keys(pkg.dependencies || {}).length}`,
|
|
147
|
+
`Dev dependencies: ${Object.keys(pkg.devDependencies || {}).length}`,
|
|
148
|
+
].join("\n")
|
|
149
|
+
}
|
|
150
|
+
return `No package.json found at ${servicePath}`
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
default:
|
|
154
|
+
return `Unknown command: ${command}. Available: status, logs, recent, health`
|
|
155
|
+
}
|
|
156
|
+
} catch (err) {
|
|
157
|
+
return `Error running ${command} on ${serviceName}: ${err}`
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function setupServiceSkills(ctx: PiContext, _config: JflConfig): Promise<void> {
|
|
162
|
+
const root = ctx.session.projectRoot
|
|
163
|
+
const services = discoverServiceSkills(root)
|
|
164
|
+
|
|
165
|
+
if (services.length === 0) return
|
|
166
|
+
|
|
167
|
+
ctx.log(`Discovered ${services.length} service skill(s): ${services.map(s => s.name).join(", ")}`, "debug")
|
|
168
|
+
|
|
169
|
+
// Register a unified service tool
|
|
170
|
+
ctx.registerTool({
|
|
171
|
+
name: "jfl_service",
|
|
172
|
+
description: `Query registered service agents. Available services: ${services.map(s => s.name).join(", ")}. Commands: status, logs, recent, health, start, stop, restart.`,
|
|
173
|
+
promptSnippet: `Service agents: ${services.map(s => s.name).join(", ")}`,
|
|
174
|
+
promptGuidelines: [
|
|
175
|
+
`Use this tool to interact with registered services: ${services.map(s => s.name).join(", ")}`,
|
|
176
|
+
"Common commands: status (git info), logs (recent commits), recent (24h changes), health (package info)",
|
|
177
|
+
],
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: "object",
|
|
180
|
+
properties: {
|
|
181
|
+
service: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: `Service name: ${services.map(s => s.name).join(", ")}`,
|
|
184
|
+
enum: services.map(s => s.name),
|
|
185
|
+
},
|
|
186
|
+
command: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Command to run on the service",
|
|
189
|
+
enum: ["status", "logs", "recent", "health", "start", "stop", "restart"],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
required: ["service", "command"],
|
|
193
|
+
},
|
|
194
|
+
async handler(input) {
|
|
195
|
+
const { service, command } = input as { service: string; command: string }
|
|
196
|
+
const svc = services.find(s => s.name === service)
|
|
197
|
+
if (!svc) return `Unknown service: ${service}. Available: ${services.map(s => s.name).join(", ")}`
|
|
198
|
+
return runServiceCommand(service, command, svc.servicePath, root)
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// Register per-service commands (e.g., /jfl-cli status, /jfl-platform logs)
|
|
203
|
+
for (const svc of services) {
|
|
204
|
+
ctx.registerCommand({
|
|
205
|
+
name: svc.name,
|
|
206
|
+
description: `${svc.description} — commands: ${svc.commands.join(", ")}`,
|
|
207
|
+
async handler(args: string, _ctx: PiContext) {
|
|
208
|
+
const command = args.trim().split(/\s+/)[0] || "status"
|
|
209
|
+
const result = runServiceCommand(svc.name, command, svc.servicePath, root)
|
|
210
|
+
ctx.ui.notify(result, { level: "info" })
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
}
|