jfl 0.9.6 → 0.9.7
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/agents.js +1 -1
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +77 -18
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/feedback.js +1 -1
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/hud.js +1 -1
- package/dist/commands/hud.js.map +1 -1
- package/dist/commands/init-from-service.js +1 -1
- package/dist/commands/init-from-service.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts +2 -13
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +6 -44
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/organize.d.ts +16 -0
- package/dist/commands/organize.d.ts.map +1 -0
- package/dist/commands/organize.js +334 -0
- package/dist/commands/organize.js.map +1 -0
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +273 -0
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/pi.d.ts.map +1 -1
- package/dist/commands/pi.js +8 -2
- package/dist/commands/pi.js.map +1 -1
- package/dist/commands/repair.js +1 -1
- package/dist/commands/repair.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +113 -45
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +4 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -19
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-config.d.ts.map +1 -1
- package/dist/lib/agent-config.js +24 -2
- package/dist/lib/agent-config.js.map +1 -1
- package/dist/lib/hub-health.d.ts.map +1 -1
- package/dist/lib/hub-health.js +14 -2
- package/dist/lib/hub-health.js.map +1 -1
- package/dist/lib/resource-optimizer-middleware.d.ts.map +1 -1
- package/dist/lib/resource-optimizer-middleware.js +8 -2
- package/dist/lib/resource-optimizer-middleware.js.map +1 -1
- package/dist/lib/telemetry.d.ts.map +1 -1
- package/dist/lib/telemetry.js +13 -12
- package/dist/lib/telemetry.js.map +1 -1
- package/dist/utils/auth-status.d.ts +21 -0
- package/dist/utils/auth-status.d.ts.map +1 -0
- package/dist/utils/auth-status.js +53 -0
- package/dist/utils/auth-status.js.map +1 -0
- package/package.json +3 -2
- package/packages/pi/dist/agent-grid.d.ts +24 -0
- package/packages/pi/dist/agent-grid.d.ts.map +1 -0
- package/packages/pi/dist/agent-grid.js +162 -0
- package/packages/pi/dist/agent-grid.js.map +1 -0
- package/packages/pi/dist/agent-names.d.ts +43 -0
- package/packages/pi/dist/agent-names.d.ts.map +1 -0
- package/packages/pi/dist/agent-names.js +156 -0
- package/packages/pi/dist/agent-names.js.map +1 -0
- package/packages/pi/dist/autoresearch.d.ts +15 -0
- package/packages/pi/dist/autoresearch.d.ts.map +1 -0
- package/packages/pi/dist/autoresearch.js +372 -0
- package/packages/pi/dist/autoresearch.js.map +1 -0
- package/packages/pi/dist/bookmarks.d.ts +15 -0
- package/packages/pi/dist/bookmarks.d.ts.map +1 -0
- package/packages/pi/dist/bookmarks.js +77 -0
- package/packages/pi/dist/bookmarks.js.map +1 -0
- package/packages/pi/dist/context.d.ts +17 -0
- package/packages/pi/dist/context.d.ts.map +1 -0
- package/packages/pi/dist/context.js +152 -0
- package/packages/pi/dist/context.js.map +1 -0
- package/packages/pi/dist/crm-tool.d.ts +12 -0
- package/packages/pi/dist/crm-tool.d.ts.map +1 -0
- package/packages/pi/dist/crm-tool.js +58 -0
- package/packages/pi/dist/crm-tool.js.map +1 -0
- package/packages/pi/dist/eval-tool.d.ts +11 -0
- package/packages/pi/dist/eval-tool.d.ts.map +1 -0
- package/packages/pi/dist/eval-tool.js +188 -0
- package/packages/pi/dist/eval-tool.js.map +1 -0
- package/packages/pi/dist/eval.d.ts +12 -0
- package/packages/pi/dist/eval.d.ts.map +1 -0
- package/packages/pi/dist/eval.js +43 -0
- package/packages/pi/dist/eval.js.map +1 -0
- package/packages/pi/dist/footer.d.ts +20 -0
- package/packages/pi/dist/footer.d.ts.map +1 -0
- package/packages/pi/dist/footer.js +222 -0
- package/packages/pi/dist/footer.js.map +1 -0
- package/packages/pi/dist/header.d.ts +17 -0
- package/packages/pi/dist/header.d.ts.map +1 -0
- package/packages/pi/dist/header.js +156 -0
- package/packages/pi/dist/header.js.map +1 -0
- package/packages/pi/dist/hub-resolver.d.ts +11 -0
- package/packages/pi/dist/hub-resolver.d.ts.map +1 -0
- package/packages/pi/dist/hub-resolver.js +58 -0
- package/packages/pi/dist/hub-resolver.js.map +1 -0
- package/packages/pi/dist/hub-tools.d.ts +14 -0
- package/packages/pi/dist/hub-tools.d.ts.map +1 -0
- package/packages/pi/dist/hub-tools.js +266 -0
- package/packages/pi/dist/hub-tools.js.map +1 -0
- package/packages/pi/dist/hud-tool.d.ts +17 -0
- package/packages/pi/dist/hud-tool.d.ts.map +1 -0
- package/packages/pi/dist/hud-tool.js +297 -0
- package/packages/pi/dist/hud-tool.js.map +1 -0
- package/packages/pi/dist/index.d.ts +12 -0
- package/packages/pi/dist/index.d.ts.map +1 -0
- package/packages/pi/dist/index.js +436 -0
- package/packages/pi/dist/index.js.map +1 -0
- package/packages/pi/dist/jfl-resolve.d.ts +29 -0
- package/packages/pi/dist/jfl-resolve.d.ts.map +1 -0
- package/packages/pi/dist/jfl-resolve.js +89 -0
- package/packages/pi/dist/jfl-resolve.js.map +1 -0
- package/packages/pi/dist/journal.d.ts +23 -0
- package/packages/pi/dist/journal.d.ts.map +1 -0
- package/packages/pi/dist/journal.js +250 -0
- package/packages/pi/dist/journal.js.map +1 -0
- package/packages/pi/dist/map-bridge.d.ts +20 -0
- package/packages/pi/dist/map-bridge.d.ts.map +1 -0
- package/packages/pi/dist/map-bridge.js +181 -0
- package/packages/pi/dist/map-bridge.js.map +1 -0
- package/packages/pi/dist/memory-tool.d.ts +11 -0
- package/packages/pi/dist/memory-tool.d.ts.map +1 -0
- package/packages/pi/dist/memory-tool.js +148 -0
- package/packages/pi/dist/memory-tool.js.map +1 -0
- package/packages/pi/dist/notifications.d.ts +15 -0
- package/packages/pi/dist/notifications.d.ts.map +1 -0
- package/packages/pi/dist/notifications.js +65 -0
- package/packages/pi/dist/notifications.js.map +1 -0
- package/packages/pi/dist/onboarding-v1.d.ts +15 -0
- package/packages/pi/dist/onboarding-v1.d.ts.map +1 -0
- package/packages/pi/dist/onboarding-v1.js +417 -0
- package/packages/pi/dist/onboarding-v1.js.map +1 -0
- package/packages/pi/dist/onboarding-v2.d.ts +18 -0
- package/packages/pi/dist/onboarding-v2.d.ts.map +1 -0
- package/packages/pi/dist/onboarding-v2.js +402 -0
- package/packages/pi/dist/onboarding-v2.js.map +1 -0
- package/packages/pi/dist/onboarding-v3.d.ts +13 -0
- package/packages/pi/dist/onboarding-v3.d.ts.map +1 -0
- package/packages/pi/dist/onboarding-v3.js +581 -0
- package/packages/pi/dist/onboarding-v3.js.map +1 -0
- package/packages/pi/dist/peter-parker.d.ts +12 -0
- package/packages/pi/dist/peter-parker.d.ts.map +1 -0
- package/packages/pi/dist/peter-parker.js +162 -0
- package/packages/pi/dist/peter-parker.js.map +1 -0
- package/packages/pi/dist/pivot-tool.d.ts +11 -0
- package/packages/pi/dist/pivot-tool.d.ts.map +1 -0
- package/packages/pi/dist/pivot-tool.js +56 -0
- package/packages/pi/dist/pivot-tool.js.map +1 -0
- package/packages/pi/dist/policy-head-tool.d.ts +15 -0
- package/packages/pi/dist/policy-head-tool.d.ts.map +1 -0
- package/packages/pi/dist/policy-head-tool.js +220 -0
- package/packages/pi/dist/policy-head-tool.js.map +1 -0
- package/packages/pi/dist/portfolio-bridge.d.ts +12 -0
- package/packages/pi/dist/portfolio-bridge.d.ts.map +1 -0
- package/packages/pi/dist/portfolio-bridge.js +81 -0
- package/packages/pi/dist/portfolio-bridge.js.map +1 -0
- package/packages/pi/dist/service-skills.d.ts +15 -0
- package/packages/pi/dist/service-skills.d.ts.map +1 -0
- package/packages/pi/dist/service-skills.js +198 -0
- package/packages/pi/dist/service-skills.js.map +1 -0
- package/packages/pi/dist/session.d.ts +24 -0
- package/packages/pi/dist/session.d.ts.map +1 -0
- package/packages/pi/dist/session.js +394 -0
- package/packages/pi/dist/session.js.map +1 -0
- package/packages/pi/dist/shortcuts.d.ts +11 -0
- package/packages/pi/dist/shortcuts.d.ts.map +1 -0
- package/packages/pi/dist/shortcuts.js +231 -0
- package/packages/pi/dist/shortcuts.js.map +1 -0
- package/packages/pi/dist/startup-briefing.d.ts +13 -0
- package/packages/pi/dist/startup-briefing.d.ts.map +1 -0
- package/packages/pi/dist/startup-briefing.js +271 -0
- package/packages/pi/dist/startup-briefing.js.map +1 -0
- package/packages/pi/dist/stratus-bridge.d.ts +14 -0
- package/packages/pi/dist/stratus-bridge.d.ts.map +1 -0
- package/packages/pi/dist/stratus-bridge.js +104 -0
- package/packages/pi/dist/stratus-bridge.js.map +1 -0
- package/packages/pi/dist/subway-mesh.d.ts +88 -0
- package/packages/pi/dist/subway-mesh.d.ts.map +1 -0
- package/packages/pi/dist/subway-mesh.js +813 -0
- package/packages/pi/dist/subway-mesh.js.map +1 -0
- package/packages/pi/dist/synopsis-tool.d.ts +12 -0
- package/packages/pi/dist/synopsis-tool.d.ts.map +1 -0
- package/packages/pi/dist/synopsis-tool.js +84 -0
- package/packages/pi/dist/synopsis-tool.js.map +1 -0
- package/packages/pi/dist/tool-renderers.d.ts +55 -0
- package/packages/pi/dist/tool-renderers.d.ts.map +1 -0
- package/packages/pi/dist/tool-renderers.js +349 -0
- package/packages/pi/dist/tool-renderers.js.map +1 -0
- package/packages/pi/dist/training-buffer-tool.d.ts +16 -0
- package/packages/pi/dist/training-buffer-tool.d.ts.map +1 -0
- package/packages/pi/dist/training-buffer-tool.js +319 -0
- package/packages/pi/dist/training-buffer-tool.js.map +1 -0
- package/packages/pi/dist/types.d.ts +195 -0
- package/packages/pi/dist/types.d.ts.map +1 -0
- package/packages/pi/dist/types.js +11 -0
- package/packages/pi/dist/types.js.map +1 -0
- package/packages/pi/extensions/session.ts +115 -8
- package/packages/pi/extensions/training-buffer-tool.ts +15 -8
- package/packages/pi/extensions/types.ts +1 -0
- package/packages/pi/package.json +2 -3
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { execSync } from "child_process";
|
|
16
|
+
function parseFrontmatter(content) {
|
|
17
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
18
|
+
if (!match)
|
|
19
|
+
return {};
|
|
20
|
+
const fm = {};
|
|
21
|
+
for (const line of match[1].split("\n")) {
|
|
22
|
+
const [key, ...rest] = line.split(":");
|
|
23
|
+
if (key && rest.length) {
|
|
24
|
+
fm[key.trim()] = rest.join(":").trim();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return fm;
|
|
28
|
+
}
|
|
29
|
+
function discoverServiceSkills(projectRoot) {
|
|
30
|
+
const claudeSkillsDir = join(projectRoot, ".claude", "skills");
|
|
31
|
+
if (!existsSync(claudeSkillsDir))
|
|
32
|
+
return [];
|
|
33
|
+
const skills = [];
|
|
34
|
+
try {
|
|
35
|
+
const entries = readdirSync(claudeSkillsDir, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (!entry.isDirectory())
|
|
38
|
+
continue;
|
|
39
|
+
const skillPath = join(claudeSkillsDir, entry.name, "SKILL.md");
|
|
40
|
+
if (!existsSync(skillPath))
|
|
41
|
+
continue;
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
44
|
+
const fm = parseFrontmatter(content);
|
|
45
|
+
if (fm.type !== "service")
|
|
46
|
+
continue;
|
|
47
|
+
// Extract available commands from the markdown table
|
|
48
|
+
const commands = [];
|
|
49
|
+
const cmdMatch = content.match(/\| `(\w+)` \|/g);
|
|
50
|
+
if (cmdMatch) {
|
|
51
|
+
for (const m of cmdMatch) {
|
|
52
|
+
const cmd = m.match(/`(\w+)`/);
|
|
53
|
+
if (cmd)
|
|
54
|
+
commands.push(cmd[1]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Fallback defaults
|
|
58
|
+
if (commands.length === 0) {
|
|
59
|
+
commands.push("status", "logs", "start", "stop", "restart", "health", "recent");
|
|
60
|
+
}
|
|
61
|
+
skills.push({
|
|
62
|
+
name: entry.name,
|
|
63
|
+
description: fm.description || `${entry.name} service agent`,
|
|
64
|
+
servicePath: fm.service_path || "",
|
|
65
|
+
commands,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Skip unparseable skills
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// .claude/skills not readable
|
|
75
|
+
}
|
|
76
|
+
return skills;
|
|
77
|
+
}
|
|
78
|
+
function runServiceCommand(serviceName, command, servicePath, projectRoot) {
|
|
79
|
+
// Try jfl service-agent first
|
|
80
|
+
try {
|
|
81
|
+
const result = execSync(`jfl service-agent run ${serviceName} ${command}`, { cwd: projectRoot, encoding: "utf-8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] });
|
|
82
|
+
return result.trim();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Fallback: direct execution based on command type
|
|
86
|
+
}
|
|
87
|
+
if (!servicePath || !existsSync(servicePath)) {
|
|
88
|
+
return `Service path not found: ${servicePath}`;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
switch (command) {
|
|
92
|
+
case "status": {
|
|
93
|
+
const branch = execSync("git branch --show-current", { cwd: servicePath, encoding: "utf-8" }).trim();
|
|
94
|
+
const lastCommit = execSync("git log -1 --pretty='%h %s (%cr)'", { cwd: servicePath, encoding: "utf-8" }).trim();
|
|
95
|
+
const dirty = execSync("git status --porcelain", { cwd: servicePath, encoding: "utf-8" }).trim();
|
|
96
|
+
return [
|
|
97
|
+
`Service: ${serviceName}`,
|
|
98
|
+
`Path: ${servicePath}`,
|
|
99
|
+
`Branch: ${branch}`,
|
|
100
|
+
`Last commit: ${lastCommit}`,
|
|
101
|
+
`Working tree: ${dirty ? `${dirty.split("\n").length} changes` : "clean"}`,
|
|
102
|
+
].join("\n");
|
|
103
|
+
}
|
|
104
|
+
case "logs": {
|
|
105
|
+
return execSync("git log --oneline -10", { cwd: servicePath, encoding: "utf-8" }).trim();
|
|
106
|
+
}
|
|
107
|
+
case "recent": {
|
|
108
|
+
const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
109
|
+
try {
|
|
110
|
+
return execSync(`git log --oneline --since="${since}"`, { cwd: servicePath, encoding: "utf-8" }).trim() || "No changes in last 24h";
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return "No changes in last 24h";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
case "health": {
|
|
117
|
+
// Check if package.json exists and has a test script
|
|
118
|
+
const pkgPath = join(servicePath, "package.json");
|
|
119
|
+
if (existsSync(pkgPath)) {
|
|
120
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
121
|
+
const hasTests = !!pkg.scripts?.test;
|
|
122
|
+
const hasBuild = !!pkg.scripts?.build;
|
|
123
|
+
return [
|
|
124
|
+
`Service: ${serviceName}`,
|
|
125
|
+
`Has tests: ${hasTests ? "yes" : "no"}`,
|
|
126
|
+
`Has build: ${hasBuild ? "yes" : "no"}`,
|
|
127
|
+
`Dependencies: ${Object.keys(pkg.dependencies || {}).length}`,
|
|
128
|
+
`Dev dependencies: ${Object.keys(pkg.devDependencies || {}).length}`,
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
return `No package.json found at ${servicePath}`;
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
return `Unknown command: ${command}. Available: status, logs, recent, health`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return `Error running ${command} on ${serviceName}: ${err}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function setupServiceSkills(ctx, _config) {
|
|
142
|
+
const root = ctx.session.projectRoot;
|
|
143
|
+
const services = discoverServiceSkills(root);
|
|
144
|
+
if (services.length === 0)
|
|
145
|
+
return;
|
|
146
|
+
ctx.log(`Discovered ${services.length} service skill(s): ${services.map(s => s.name).join(", ")}`, "debug");
|
|
147
|
+
// Register a unified service tool
|
|
148
|
+
ctx.registerTool({
|
|
149
|
+
name: "jfl_service",
|
|
150
|
+
description: `Query registered service agents. Available services: ${services.map(s => s.name).join(", ")}. Commands: status, logs, recent, health, start, stop, restart.`,
|
|
151
|
+
promptSnippet: `Service agents: ${services.map(s => s.name).join(", ")}`,
|
|
152
|
+
promptGuidelines: [
|
|
153
|
+
`Use this tool to interact with registered services: ${services.map(s => s.name).join(", ")}`,
|
|
154
|
+
"Common commands: status (git info), logs (recent commits), recent (24h changes), health (package info)",
|
|
155
|
+
],
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
service: {
|
|
160
|
+
type: "string",
|
|
161
|
+
description: `Service name: ${services.map(s => s.name).join(", ")}`,
|
|
162
|
+
enum: services.map(s => s.name),
|
|
163
|
+
},
|
|
164
|
+
command: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Command to run on the service",
|
|
167
|
+
enum: ["status", "logs", "recent", "health", "start", "stop", "restart"],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
required: ["service", "command"],
|
|
171
|
+
},
|
|
172
|
+
async handler(input) {
|
|
173
|
+
const { service, command } = input;
|
|
174
|
+
const svc = services.find(s => s.name === service);
|
|
175
|
+
if (!svc)
|
|
176
|
+
return `Unknown service: ${service}. Available: ${services.map(s => s.name).join(", ")}`;
|
|
177
|
+
return runServiceCommand(service, command, svc.servicePath, root);
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
// Register per-service commands (e.g., /subclaw status, /subway-fe logs)
|
|
181
|
+
// Skip "subway" — /subway is owned by the mesh command (subway-mesh.ts or standalone ext).
|
|
182
|
+
// The jfl_service tool still covers all services including subway.
|
|
183
|
+
const reservedNames = new Set(["subway"]);
|
|
184
|
+
for (const svc of services) {
|
|
185
|
+
if (reservedNames.has(svc.name))
|
|
186
|
+
continue;
|
|
187
|
+
ctx.registerCommand({
|
|
188
|
+
name: svc.name,
|
|
189
|
+
description: `${svc.description} — commands: ${svc.commands.join(", ")}`,
|
|
190
|
+
async handler(args, _ctx) {
|
|
191
|
+
const command = args.trim().split(/\s+/)[0] || "status";
|
|
192
|
+
const result = runServiceCommand(svc.name, command, svc.servicePath, root);
|
|
193
|
+
ctx.ui.notify(result, { level: "info" });
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=service-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-skills.js","sourceRoot":"","sources":["../extensions/service-skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JFL Session Extension
|
|
3
|
+
*
|
|
4
|
+
* Pi-native session lifecycle via Context Hub API.
|
|
5
|
+
* Calls POST /api/session/init on start and POST /api/session/end on shutdown.
|
|
6
|
+
* Hub handles sync, branch creation, doctor — single source of truth.
|
|
7
|
+
* Pi only manages the auto-commit daemon locally (needs a detached process).
|
|
8
|
+
*
|
|
9
|
+
* @purpose Pi session lifecycle — Hub API for init/end, local auto-commit daemon
|
|
10
|
+
*/
|
|
11
|
+
import type { PiContext, JflConfig } from "./types.js";
|
|
12
|
+
export declare function setupSession(ctx: PiContext, _config: JflConfig): Promise<void>;
|
|
13
|
+
export declare function getSessionBranch(): string;
|
|
14
|
+
export declare function pivot(ctx: PiContext, summary?: string): Promise<{
|
|
15
|
+
ok: boolean;
|
|
16
|
+
pivotNumber: number;
|
|
17
|
+
committed: boolean;
|
|
18
|
+
commitHash: string | null;
|
|
19
|
+
filesChanged: number;
|
|
20
|
+
indexed: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function onShutdown(ctx: PiContext): Promise<void>;
|
|
23
|
+
export declare function getCurrentBranch(root: string): string;
|
|
24
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../extensions/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAmHtD,wBAAsB,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEpF;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAsB,KAAK,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACrE,EAAE,EAAE,OAAO,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;CACjB,CAAC,CAiDD;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiI9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWrD"}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JFL Session Extension
|
|
3
|
+
*
|
|
4
|
+
* Pi-native session lifecycle via Context Hub API.
|
|
5
|
+
* Calls POST /api/session/init on start and POST /api/session/end on shutdown.
|
|
6
|
+
* Hub handles sync, branch creation, doctor — single source of truth.
|
|
7
|
+
* Pi only manages the auto-commit daemon locally (needs a detached process).
|
|
8
|
+
*
|
|
9
|
+
* @purpose Pi session lifecycle — Hub API for init/end, local auto-commit daemon
|
|
10
|
+
*/
|
|
11
|
+
import { execSync, spawn } from "child_process";
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
13
|
+
import { join } from "path";
|
|
14
|
+
import { hubUrl, authToken } from "./map-bridge.js";
|
|
15
|
+
let autoCommitProcess = null;
|
|
16
|
+
let sessionBranch = "";
|
|
17
|
+
/**
|
|
18
|
+
* Create a session branch locally without the Hub.
|
|
19
|
+
* Parity with scripts/session/session-init.sh:
|
|
20
|
+
* 1. Commit or stash dirty state
|
|
21
|
+
* 2. If on a stale session branch, try to switch to working branch
|
|
22
|
+
* 3. Create new session branch from clean base
|
|
23
|
+
* 4. Pop stash if needed
|
|
24
|
+
*/
|
|
25
|
+
function createSessionBranchLocally(root, ctx) {
|
|
26
|
+
// Generate session branch name
|
|
27
|
+
const user = (() => {
|
|
28
|
+
try {
|
|
29
|
+
return execSync("git config user.name", { cwd: root, stdio: ["pipe", "pipe", "pipe"] })
|
|
30
|
+
.toString().trim().replace(/\s+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 30) || "user";
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return "user";
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
38
|
+
const timeStr = now.toISOString().slice(11, 16).replace(":", "");
|
|
39
|
+
const randomId = Math.random().toString(16).slice(2, 8);
|
|
40
|
+
const branchName = `session-${user}-${dateStr}-${timeStr}-${randomId}`;
|
|
41
|
+
// Get working branch from config
|
|
42
|
+
let workingBranch = "main";
|
|
43
|
+
const configPath = join(root, ".jfl", "config.json");
|
|
44
|
+
if (existsSync(configPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
47
|
+
if (cfg.working_branch)
|
|
48
|
+
workingBranch = cfg.working_branch;
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
// Step 1: Commit or stash dirty state so checkout doesn't fail
|
|
53
|
+
let stashed = false;
|
|
54
|
+
try {
|
|
55
|
+
const status = execSync("git status --porcelain", { cwd: root, timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
56
|
+
if (status) {
|
|
57
|
+
try {
|
|
58
|
+
execSync("git add -A && git commit -m 'auto: session-init save before branch switch' --no-verify", { cwd: root, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Commit failed — stash instead
|
|
62
|
+
try {
|
|
63
|
+
execSync("git stash push -m 'jfl-session-init-stash' --include-untracked", { cwd: root, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
|
|
64
|
+
stashed = true;
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
// Step 2: If on a stale session branch, try to switch to working branch
|
|
72
|
+
const currentBranch = getCurrentBranch(root);
|
|
73
|
+
if (currentBranch.startsWith("session-")) {
|
|
74
|
+
try {
|
|
75
|
+
execSync(`git checkout "${workingBranch}"`, { cwd: root, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
|
|
76
|
+
ctx.log(`Switched from stale session branch ${currentBranch} → ${workingBranch}`, "info");
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
ctx.log(`Could not switch from ${currentBranch} — branching from it`, "warn");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Step 3: Create new session branch
|
|
83
|
+
let finalBranch = branchName;
|
|
84
|
+
try {
|
|
85
|
+
execSync(`git checkout -b "${branchName}"`, { cwd: root, stdio: ["pipe", "pipe", "pipe"] });
|
|
86
|
+
ctx.ui.notify(`Session branch: ${branchName}`, { level: "info" });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Branch name collision — retry with longer ID
|
|
90
|
+
const fallbackId = Math.random().toString(16).slice(2, 14);
|
|
91
|
+
const fallbackBranch = `session-${user}-${dateStr}-${timeStr}-${fallbackId}`;
|
|
92
|
+
try {
|
|
93
|
+
execSync(`git checkout -b "${fallbackBranch}"`, { cwd: root, stdio: ["pipe", "pipe", "pipe"] });
|
|
94
|
+
finalBranch = fallbackBranch;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Give up — use current branch
|
|
98
|
+
finalBranch = getCurrentBranch(root);
|
|
99
|
+
ctx.log(`Could not create session branch — using ${finalBranch}`, "warn");
|
|
100
|
+
ctx.ui.notify(`Branch creation failed — using ${finalBranch}`, { level: "warn" });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Step 4: Pop stash if we stashed
|
|
104
|
+
if (stashed) {
|
|
105
|
+
try {
|
|
106
|
+
execSync("git stash pop", { cwd: root, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
ctx.log("Could not pop stash — run 'git stash pop' manually", "warn");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Save session info
|
|
113
|
+
try {
|
|
114
|
+
const jflDir = join(root, ".jfl");
|
|
115
|
+
writeFileSync(join(jflDir, "current-session-branch.txt"), finalBranch);
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
return finalBranch;
|
|
119
|
+
}
|
|
120
|
+
function findScript(root, scriptName) {
|
|
121
|
+
const candidates = [
|
|
122
|
+
join(root, "scripts", "session", scriptName),
|
|
123
|
+
join(root, "scripts", scriptName),
|
|
124
|
+
];
|
|
125
|
+
for (const candidate of candidates) {
|
|
126
|
+
if (existsSync(candidate))
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
export async function setupSession(ctx, _config) {
|
|
132
|
+
const root = ctx.session.projectRoot;
|
|
133
|
+
// Call Hub session init — handles sync, doctor, branch creation
|
|
134
|
+
try {
|
|
135
|
+
const resp = await fetch(`${hubUrl}/api/session/init`, {
|
|
136
|
+
method: "POST",
|
|
137
|
+
headers: {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
140
|
+
},
|
|
141
|
+
body: JSON.stringify({ runtime: "pi" }),
|
|
142
|
+
signal: AbortSignal.timeout(30000),
|
|
143
|
+
});
|
|
144
|
+
if (resp.ok) {
|
|
145
|
+
const data = await resp.json();
|
|
146
|
+
if (data.warnings?.length) {
|
|
147
|
+
for (const w of data.warnings)
|
|
148
|
+
ctx.log(w, "warn");
|
|
149
|
+
}
|
|
150
|
+
if (data.doctor?.errors) {
|
|
151
|
+
ctx.ui.notify(`Doctor: ${data.doctor.errors} errors`, { level: "warn" });
|
|
152
|
+
}
|
|
153
|
+
// Hub may return 200 OK but fail to create a branch (dirty state, etc).
|
|
154
|
+
// If branch is missing or is the working branch (main), fall back to local creation.
|
|
155
|
+
const hubBranch = data.branch ?? "";
|
|
156
|
+
if (hubBranch.startsWith("session-")) {
|
|
157
|
+
sessionBranch = hubBranch;
|
|
158
|
+
ctx.log(`Session init via Hub: branch=${sessionBranch}, sync=${data.syncOk}`, "debug");
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
ctx.log(`Hub returned non-session branch "${hubBranch}" — creating locally`, "warn");
|
|
162
|
+
sessionBranch = createSessionBranchLocally(root, ctx);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const errText = await resp.text().catch(() => "unknown");
|
|
167
|
+
ctx.log(`Hub session init failed (${resp.status}): ${errText}`, "warn");
|
|
168
|
+
sessionBranch = createSessionBranchLocally(root, ctx);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
// Hub not available — create session branch locally (parity with session-init.sh)
|
|
173
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
174
|
+
ctx.log(`Hub unavailable for session init: ${msg}`, "warn");
|
|
175
|
+
ctx.ui.notify(`Hub unreachable — creating session branch locally`, { level: "warn" });
|
|
176
|
+
sessionBranch = createSessionBranchLocally(root, ctx);
|
|
177
|
+
}
|
|
178
|
+
// Start auto-commit daemon — must be local (detached process)
|
|
179
|
+
const autoCommitScript = findScript(root, "auto-commit.sh");
|
|
180
|
+
if (autoCommitScript) {
|
|
181
|
+
autoCommitProcess = spawn("bash", [autoCommitScript, "start", "120"], {
|
|
182
|
+
cwd: root,
|
|
183
|
+
detached: true,
|
|
184
|
+
stdio: "ignore",
|
|
185
|
+
});
|
|
186
|
+
autoCommitProcess.unref();
|
|
187
|
+
ctx.log("Auto-commit daemon started (120s interval)", "debug");
|
|
188
|
+
}
|
|
189
|
+
ctx.emit("hook:session-start", {
|
|
190
|
+
session: ctx.session.id,
|
|
191
|
+
branch: sessionBranch,
|
|
192
|
+
projectRoot: root,
|
|
193
|
+
ts: new Date().toISOString(),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
export function getSessionBranch() {
|
|
197
|
+
return sessionBranch;
|
|
198
|
+
}
|
|
199
|
+
export async function pivot(ctx, summary) {
|
|
200
|
+
const root = ctx.session.projectRoot;
|
|
201
|
+
try {
|
|
202
|
+
const resp = await fetch(`${hubUrl}/api/session/pivot`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
"Content-Type": "application/json",
|
|
206
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify({ summary }),
|
|
209
|
+
signal: AbortSignal.timeout(30000),
|
|
210
|
+
});
|
|
211
|
+
if (resp.ok) {
|
|
212
|
+
const data = await resp.json();
|
|
213
|
+
ctx.emit("hook:session-pivot", {
|
|
214
|
+
session: ctx.session.id,
|
|
215
|
+
branch: sessionBranch,
|
|
216
|
+
pivotNumber: data.pivotNumber,
|
|
217
|
+
summary: summary || "",
|
|
218
|
+
ts: new Date().toISOString(),
|
|
219
|
+
});
|
|
220
|
+
ctx.log(`Pivot #${data.pivotNumber} via Hub`, "debug");
|
|
221
|
+
return data;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
ctx.log("Hub unavailable for pivot, falling back to CLI", "debug");
|
|
226
|
+
}
|
|
227
|
+
// Fallback: shell out to jfl pivot
|
|
228
|
+
try {
|
|
229
|
+
const result = execSync(`jfl pivot --json${summary ? ` --summary "${summary.replace(/"/g, '\\"')}"` : ""}`, { cwd: root, encoding: "utf-8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
230
|
+
const parsed = JSON.parse(result);
|
|
231
|
+
ctx.emit("hook:session-pivot", {
|
|
232
|
+
session: ctx.session.id,
|
|
233
|
+
branch: sessionBranch,
|
|
234
|
+
pivotNumber: parsed.pivotNumber,
|
|
235
|
+
summary: summary || "",
|
|
236
|
+
ts: new Date().toISOString(),
|
|
237
|
+
});
|
|
238
|
+
return parsed;
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
ctx.log(`Pivot fallback failed: ${err}`, "warn");
|
|
242
|
+
return { ok: false, pivotNumber: 0, committed: false, commitHash: null, filesChanged: 0, indexed: false };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
export async function onShutdown(ctx) {
|
|
246
|
+
const root = ctx.session.projectRoot;
|
|
247
|
+
const branch = getSessionBranch() || getCurrentBranch(root);
|
|
248
|
+
ctx.ui.notify("Shutting down session…", { level: "info" });
|
|
249
|
+
// ── 1. Journal check (parity with CC Stop hook) ──────────────────────
|
|
250
|
+
const journalPath = join(root, ".jfl", "journal", `${branch}.jsonl`);
|
|
251
|
+
const hasJournal = existsSync(journalPath) && readFileSync(journalPath, "utf-8").trim().length > 0;
|
|
252
|
+
if (!hasJournal) {
|
|
253
|
+
ctx.ui.notify("⚠️ No journal entry for this session. Context will be lost.\n" +
|
|
254
|
+
` File: .jfl/journal/${branch}.jsonl`, { level: "warn" });
|
|
255
|
+
}
|
|
256
|
+
// ── 2. Kill auto-commit daemon ───────────────────────────────────────
|
|
257
|
+
if (autoCommitProcess) {
|
|
258
|
+
try {
|
|
259
|
+
autoCommitProcess.kill();
|
|
260
|
+
}
|
|
261
|
+
catch { }
|
|
262
|
+
autoCommitProcess = null;
|
|
263
|
+
ctx.ui.notify(" ✓ Auto-commit stopped", { level: "info" });
|
|
264
|
+
}
|
|
265
|
+
// ── 3. Auto-commit any uncommitted changes ───────────────────────────
|
|
266
|
+
try {
|
|
267
|
+
const status = execSync("git status --porcelain", { cwd: root, timeout: 3000, encoding: "utf-8" }).trim();
|
|
268
|
+
if (status) {
|
|
269
|
+
execSync("git add -A && git commit -m 'auto: session-end save'", { cwd: root, timeout: 5000, stdio: "pipe" });
|
|
270
|
+
ctx.ui.notify(" ✓ Uncommitted changes saved", { level: "info" });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch { }
|
|
274
|
+
// ── 4. Call Hub session end ──────────────────────────────────────────
|
|
275
|
+
try {
|
|
276
|
+
await fetch(`${hubUrl}/api/session/end`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
"Content-Type": "application/json",
|
|
280
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
281
|
+
},
|
|
282
|
+
body: JSON.stringify({ runtime: "pi" }),
|
|
283
|
+
signal: AbortSignal.timeout(5000),
|
|
284
|
+
});
|
|
285
|
+
ctx.ui.notify(" ✓ Hub session closed", { level: "info" });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
ctx.log("Hub session end failed — running local cleanup", "debug");
|
|
289
|
+
}
|
|
290
|
+
// ── 5. Merge session branch back (parity with CC session-cleanup.sh) ─
|
|
291
|
+
if (branch.startsWith("session-")) {
|
|
292
|
+
// Guard: if git is already on a DIFFERENT branch (e.g. a new session started),
|
|
293
|
+
// don't merge — the old session branch may already be gone.
|
|
294
|
+
const currentGitBranch = getCurrentBranch(root);
|
|
295
|
+
if (currentGitBranch !== branch) {
|
|
296
|
+
ctx.log(`Skipping merge — git is on ${currentGitBranch}, not ${branch} (new session likely started)`, "debug");
|
|
297
|
+
ctx.ui.notify(` ⚠ Skipped merge — already on ${currentGitBranch}`, { level: "info" });
|
|
298
|
+
}
|
|
299
|
+
else
|
|
300
|
+
try {
|
|
301
|
+
// Get working branch from config or default to main
|
|
302
|
+
let workingBranch = "main";
|
|
303
|
+
const configPath = join(root, ".jfl", "config.json");
|
|
304
|
+
if (existsSync(configPath)) {
|
|
305
|
+
try {
|
|
306
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
307
|
+
if (config.working_branch)
|
|
308
|
+
workingBranch = config.working_branch;
|
|
309
|
+
}
|
|
310
|
+
catch { }
|
|
311
|
+
}
|
|
312
|
+
// Remove stale lock files that block git operations
|
|
313
|
+
const lockFile = join(root, ".git", "index.lock");
|
|
314
|
+
if (existsSync(lockFile)) {
|
|
315
|
+
try {
|
|
316
|
+
require("fs").unlinkSync(lockFile);
|
|
317
|
+
}
|
|
318
|
+
catch { }
|
|
319
|
+
}
|
|
320
|
+
// Commit any dirty tracked files before switching branches
|
|
321
|
+
try {
|
|
322
|
+
execSync("git add -A && git commit -m 'session: auto-commit before merge' --no-verify", {
|
|
323
|
+
cwd: root, timeout: 10000, stdio: "pipe",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch { } // OK if nothing to commit
|
|
327
|
+
// Push session branch first
|
|
328
|
+
try {
|
|
329
|
+
execSync(`git push origin ${branch}`, { cwd: root, timeout: 15000, stdio: "pipe" });
|
|
330
|
+
ctx.ui.notify(" ✓ Session branch pushed", { level: "info" });
|
|
331
|
+
}
|
|
332
|
+
catch { }
|
|
333
|
+
// Merge session into working branch
|
|
334
|
+
execSync(`git checkout ${workingBranch}`, { cwd: root, timeout: 5000, stdio: "pipe" });
|
|
335
|
+
execSync(`git merge ${branch} --no-edit`, { cwd: root, timeout: 10000, stdio: "pipe" });
|
|
336
|
+
ctx.ui.notify(` ✓ Merged ${branch} → ${workingBranch}`, { level: "info" });
|
|
337
|
+
// Push merged working branch
|
|
338
|
+
try {
|
|
339
|
+
execSync(`git push origin ${workingBranch}`, { cwd: root, timeout: 15000, stdio: "pipe" });
|
|
340
|
+
}
|
|
341
|
+
catch { }
|
|
342
|
+
// Delete session branch (local + remote)
|
|
343
|
+
try {
|
|
344
|
+
execSync(`git branch -d ${branch}`, { cwd: root, timeout: 5000, stdio: "pipe" });
|
|
345
|
+
execSync(`git push origin --delete ${branch}`, { cwd: root, timeout: 10000, stdio: "pipe" });
|
|
346
|
+
ctx.ui.notify(" ✓ Session branch cleaned up", { level: "info" });
|
|
347
|
+
}
|
|
348
|
+
catch { }
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
352
|
+
if (msg.includes("CONFLICT") || msg.includes("conflict")) {
|
|
353
|
+
ctx.ui.notify(` ⚠ Merge conflict — session branch ${branch} preserved`, { level: "warn" });
|
|
354
|
+
// Switch back to session branch so user can resolve
|
|
355
|
+
try {
|
|
356
|
+
execSync(`git checkout ${branch}`, { cwd: root, timeout: 5000, stdio: "pipe" });
|
|
357
|
+
}
|
|
358
|
+
catch { }
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
ctx.ui.notify(` ⚠ Branch merge failed: ${msg.slice(0, 80)}`, { level: "warn" });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ── 6. Fallback cleanup script ───────────────────────────────────────
|
|
366
|
+
const cleanupScript = findScript(root, "session-cleanup.sh");
|
|
367
|
+
if (cleanupScript && !branch.startsWith("session-")) {
|
|
368
|
+
// Only run cleanup script if we didn't already handle merge above
|
|
369
|
+
try {
|
|
370
|
+
execSync(`bash "${cleanupScript}"`, { cwd: root, timeout: 10000, stdio: "pipe" });
|
|
371
|
+
}
|
|
372
|
+
catch { }
|
|
373
|
+
}
|
|
374
|
+
ctx.emit("hook:session-end", {
|
|
375
|
+
session: ctx.session.id,
|
|
376
|
+
branch,
|
|
377
|
+
ts: new Date().toISOString(),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
export function getCurrentBranch(root) {
|
|
381
|
+
try {
|
|
382
|
+
return execSync("git branch --show-current", { cwd: root }).toString().trim();
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
try {
|
|
386
|
+
const saved = readFileSync(join(root, ".jfl", "current-session-branch.txt"), "utf-8").trim();
|
|
387
|
+
return saved;
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return "main";
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
//# sourceMappingURL=session.js.map
|