jfl 0.5.0 → 0.6.0
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 +1 -0
- package/dist/commands/context-hub.d.ts.map +1 -1
- package/dist/commands/context-hub.js +246 -2
- package/dist/commands/context-hub.js.map +1 -1
- package/dist/commands/peter.d.ts +2 -0
- package/dist/commands/peter.d.ts.map +1 -1
- package/dist/commands/peter.js +242 -52
- package/dist/commands/peter.js.map +1 -1
- package/dist/commands/setup.d.ts +12 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +322 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/train.d.ts +33 -0
- package/dist/commands/train.d.ts.map +1 -0
- package/dist/commands/train.js +510 -0
- package/dist/commands/train.js.map +1 -0
- package/dist/commands/verify.d.ts +14 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +276 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/dashboard-static/assets/index-CW9ZxqX8.css +1 -0
- package/dist/dashboard-static/assets/index-DNN__p4K.js +121 -0
- package/dist/dashboard-static/index.html +2 -2
- package/dist/index.js +99 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-session.d.ts.map +1 -1
- package/dist/lib/agent-session.js +12 -4
- package/dist/lib/agent-session.js.map +1 -1
- package/dist/lib/eval-snapshot.js +1 -1
- package/dist/lib/eval-snapshot.js.map +1 -1
- package/dist/lib/pi-sky/bridge.d.ts +55 -0
- package/dist/lib/pi-sky/bridge.d.ts.map +1 -0
- package/dist/lib/pi-sky/bridge.js +264 -0
- package/dist/lib/pi-sky/bridge.js.map +1 -0
- package/dist/lib/pi-sky/cost-monitor.d.ts +21 -0
- package/dist/lib/pi-sky/cost-monitor.d.ts.map +1 -0
- package/dist/lib/pi-sky/cost-monitor.js +126 -0
- package/dist/lib/pi-sky/cost-monitor.js.map +1 -0
- package/dist/lib/pi-sky/eval-sweep.d.ts +27 -0
- package/dist/lib/pi-sky/eval-sweep.d.ts.map +1 -0
- package/dist/lib/pi-sky/eval-sweep.js +141 -0
- package/dist/lib/pi-sky/eval-sweep.js.map +1 -0
- package/dist/lib/pi-sky/event-router.d.ts +32 -0
- package/dist/lib/pi-sky/event-router.d.ts.map +1 -0
- package/dist/lib/pi-sky/event-router.js +176 -0
- package/dist/lib/pi-sky/event-router.js.map +1 -0
- package/dist/lib/pi-sky/experiment.d.ts +9 -0
- package/dist/lib/pi-sky/experiment.d.ts.map +1 -0
- package/dist/lib/pi-sky/experiment.js +83 -0
- package/dist/lib/pi-sky/experiment.js.map +1 -0
- package/dist/lib/pi-sky/index.d.ts +16 -0
- package/dist/lib/pi-sky/index.d.ts.map +1 -0
- package/dist/lib/pi-sky/index.js +16 -0
- package/dist/lib/pi-sky/index.js.map +1 -0
- package/dist/lib/pi-sky/stratus-gate.d.ts +28 -0
- package/dist/lib/pi-sky/stratus-gate.d.ts.map +1 -0
- package/dist/lib/pi-sky/stratus-gate.js +61 -0
- package/dist/lib/pi-sky/stratus-gate.js.map +1 -0
- package/dist/lib/pi-sky/swarm.d.ts +28 -0
- package/dist/lib/pi-sky/swarm.d.ts.map +1 -0
- package/dist/lib/pi-sky/swarm.js +208 -0
- package/dist/lib/pi-sky/swarm.js.map +1 -0
- package/dist/lib/pi-sky/types.d.ts +139 -0
- package/dist/lib/pi-sky/types.d.ts.map +1 -0
- package/dist/lib/pi-sky/types.js +2 -0
- package/dist/lib/pi-sky/types.js.map +1 -0
- package/dist/lib/pi-sky/voice-bridge.d.ts +20 -0
- package/dist/lib/pi-sky/voice-bridge.d.ts.map +1 -0
- package/dist/lib/pi-sky/voice-bridge.js +91 -0
- package/dist/lib/pi-sky/voice-bridge.js.map +1 -0
- package/dist/lib/policy-head.d.ts +16 -1
- package/dist/lib/policy-head.d.ts.map +1 -1
- package/dist/lib/policy-head.js +117 -19
- package/dist/lib/policy-head.js.map +1 -1
- package/dist/lib/predictor.d.ts +10 -0
- package/dist/lib/predictor.d.ts.map +1 -1
- package/dist/lib/predictor.js +46 -7
- package/dist/lib/predictor.js.map +1 -1
- package/dist/lib/setup/agent-generator.d.ts +18 -0
- package/dist/lib/setup/agent-generator.d.ts.map +1 -0
- package/dist/lib/setup/agent-generator.js +114 -0
- package/dist/lib/setup/agent-generator.js.map +1 -0
- package/dist/lib/setup/context-analyzer.d.ts +16 -0
- package/dist/lib/setup/context-analyzer.d.ts.map +1 -0
- package/dist/lib/setup/context-analyzer.js +112 -0
- package/dist/lib/setup/context-analyzer.js.map +1 -0
- package/dist/lib/setup/doc-auditor.d.ts +54 -0
- package/dist/lib/setup/doc-auditor.d.ts.map +1 -0
- package/dist/lib/setup/doc-auditor.js +629 -0
- package/dist/lib/setup/doc-auditor.js.map +1 -0
- package/dist/lib/setup/domain-generator.d.ts +7 -0
- package/dist/lib/setup/domain-generator.d.ts.map +1 -0
- package/dist/lib/setup/domain-generator.js +58 -0
- package/dist/lib/setup/domain-generator.js.map +1 -0
- package/dist/lib/setup/smart-eval-generator.d.ts +38 -0
- package/dist/lib/setup/smart-eval-generator.d.ts.map +1 -0
- package/dist/lib/setup/smart-eval-generator.js +378 -0
- package/dist/lib/setup/smart-eval-generator.js.map +1 -0
- package/dist/lib/setup/smart-recommender.d.ts +63 -0
- package/dist/lib/setup/smart-recommender.d.ts.map +1 -0
- package/dist/lib/setup/smart-recommender.js +329 -0
- package/dist/lib/setup/smart-recommender.js.map +1 -0
- package/dist/lib/setup/spec-generator.d.ts +63 -0
- package/dist/lib/setup/spec-generator.d.ts.map +1 -0
- package/dist/lib/setup/spec-generator.js +310 -0
- package/dist/lib/setup/spec-generator.js.map +1 -0
- package/dist/lib/setup/violation-agent-generator.d.ts +32 -0
- package/dist/lib/setup/violation-agent-generator.d.ts.map +1 -0
- package/dist/lib/setup/violation-agent-generator.js +255 -0
- package/dist/lib/setup/violation-agent-generator.js.map +1 -0
- package/package.json +1 -1
- package/packages/pi/extensions/context.ts +88 -55
- package/packages/pi/extensions/hub-resolver.ts +63 -0
- package/packages/pi/extensions/index.ts +16 -3
- package/packages/pi/extensions/memory-tool.ts +9 -4
- package/packages/pi/extensions/session.ts +68 -16
- package/packages/pi/extensions/tool-renderers.ts +6 -5
- package/scripts/train/requirements.txt +5 -0
- package/scripts/train/train-policy-head.py +477 -0
- package/scripts/train/v2/dataset.py +81 -0
- package/scripts/train/v2/domain.json +18 -0
- package/scripts/train/v2/eval.py +196 -0
- package/scripts/train/v2/generate_data.py +219 -0
- package/scripts/train/v2/infer.py +188 -0
- package/scripts/train/v2/model.py +112 -0
- package/scripts/train/v2/precompute.py +132 -0
- package/scripts/train/v2/train.py +302 -0
- package/scripts/train/v2/transform_buffer.py +227 -0
- package/scripts/train/v2/validate_data.py +115 -0
- package/template/.claude/settings.json +2 -15
- package/template/scripts/session/session-cleanup.sh +2 -11
- package/template/scripts/session/session-end-hub.sh +72 -0
- package/template/scripts/session/session-start-hub.sh +105 -0
- package/dist/dashboard-static/assets/index-B6b867Pv.js +0 -121
- package/dist/dashboard-static/assets/index-Y4BrqxV-.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAy9EH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBjF;AAkMD,wBAAsB,qBAAqB,CAAC,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAiHxF;AAMD,wBAAsB,iBAAiB,CACrC,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,iBA8lBzF"}
|
|
@@ -547,6 +547,20 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
|
|
|
547
547
|
res.end(JSON.stringify({ status: "ok", port }));
|
|
548
548
|
return;
|
|
549
549
|
}
|
|
550
|
+
// Setup report — serves .jfl/setup-report.json for dashboard consumption
|
|
551
|
+
if (url.pathname === "/api/setup-report" && req.method === "GET") {
|
|
552
|
+
const reportPath = path.join(projectRoot, ".jfl", "setup-report.json");
|
|
553
|
+
if (fs.existsSync(reportPath)) {
|
|
554
|
+
const report = fs.readFileSync(reportPath, "utf-8");
|
|
555
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
556
|
+
res.end(report);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
560
|
+
res.end(JSON.stringify({ error: "No setup report. Run: jfl setup" }));
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
550
564
|
// Dashboard - served without API auth (has its own token flow in JS)
|
|
551
565
|
if (url.pathname.startsWith("/dashboard")) {
|
|
552
566
|
import("../dashboard/index.js").then(({ handleDashboardRoutes }) => {
|
|
@@ -1785,6 +1799,236 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
|
|
|
1785
1799
|
});
|
|
1786
1800
|
return;
|
|
1787
1801
|
}
|
|
1802
|
+
// ── Session Parity API ────────────────────────────────────────────
|
|
1803
|
+
// These endpoints provide runtime-agnostic session lifecycle.
|
|
1804
|
+
// Both Claude Code hooks and Pi extensions call these instead of
|
|
1805
|
+
// duplicating logic. Single source of truth for session init/end.
|
|
1806
|
+
// POST /api/session/init — sync repos, create branch, run doctor
|
|
1807
|
+
if (url.pathname === "/api/session/init" && req.method === "POST") {
|
|
1808
|
+
let body = "";
|
|
1809
|
+
req.on("data", (chunk) => body += chunk);
|
|
1810
|
+
req.on("end", async () => {
|
|
1811
|
+
try {
|
|
1812
|
+
const { runtime } = JSON.parse(body || "{}");
|
|
1813
|
+
const warnings = [];
|
|
1814
|
+
const scriptDir = path.join(projectRoot, "scripts", "session");
|
|
1815
|
+
// Step 1: Sync repos
|
|
1816
|
+
const syncScript = path.join(scriptDir, "session-sync.sh");
|
|
1817
|
+
let syncOk = true;
|
|
1818
|
+
if (fs.existsSync(syncScript)) {
|
|
1819
|
+
try {
|
|
1820
|
+
execSync(`bash "${syncScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1821
|
+
}
|
|
1822
|
+
catch (err) {
|
|
1823
|
+
syncOk = false;
|
|
1824
|
+
warnings.push(`Sync warning: ${(err.message || "").split("\n")[0]}`);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
// Step 2: Doctor check
|
|
1828
|
+
const doctorScript = path.join(scriptDir, "jfl-doctor.sh");
|
|
1829
|
+
let doctorErrors = 0;
|
|
1830
|
+
let doctorWarnings = 0;
|
|
1831
|
+
if (fs.existsSync(doctorScript)) {
|
|
1832
|
+
try {
|
|
1833
|
+
const doctorOut = execSync(`bash "${doctorScript}"`, { cwd: projectRoot, timeout: 30000, stdio: ["pipe", "pipe", "pipe"] }).toString();
|
|
1834
|
+
const em = doctorOut.match(/(\d+) error\(s\)/);
|
|
1835
|
+
const wm = doctorOut.match(/(\d+) warning\(s\)/);
|
|
1836
|
+
doctorErrors = em ? parseInt(em[1]) : 0;
|
|
1837
|
+
doctorWarnings = wm ? parseInt(wm[1]) : 0;
|
|
1838
|
+
if (doctorErrors > 0)
|
|
1839
|
+
warnings.push(`Doctor: ${doctorErrors} errors`);
|
|
1840
|
+
}
|
|
1841
|
+
catch { }
|
|
1842
|
+
}
|
|
1843
|
+
// Step 3: Create session branch
|
|
1844
|
+
let currentBranch = "";
|
|
1845
|
+
try {
|
|
1846
|
+
currentBranch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
1847
|
+
}
|
|
1848
|
+
catch { }
|
|
1849
|
+
let sessionBranch = currentBranch;
|
|
1850
|
+
if (!currentBranch.startsWith("session-")) {
|
|
1851
|
+
const user = (() => {
|
|
1852
|
+
try {
|
|
1853
|
+
return execSync("git config user.name", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] })
|
|
1854
|
+
.toString().trim().replace(/\s+/g, "-").toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 30) || "user";
|
|
1855
|
+
}
|
|
1856
|
+
catch {
|
|
1857
|
+
return "user";
|
|
1858
|
+
}
|
|
1859
|
+
})();
|
|
1860
|
+
const now = new Date();
|
|
1861
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, "");
|
|
1862
|
+
const timeStr = now.toISOString().slice(11, 16).replace(":", "");
|
|
1863
|
+
const randomId = Math.random().toString(16).slice(2, 8);
|
|
1864
|
+
sessionBranch = `session-${user}-${dateStr}-${timeStr}-${randomId}`;
|
|
1865
|
+
try {
|
|
1866
|
+
execSync(`git checkout -b "${sessionBranch}"`, { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] });
|
|
1867
|
+
}
|
|
1868
|
+
catch {
|
|
1869
|
+
sessionBranch = currentBranch || "main";
|
|
1870
|
+
warnings.push("Could not create session branch");
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
// Step 4: Save session info
|
|
1874
|
+
const jflDir = path.join(projectRoot, ".jfl");
|
|
1875
|
+
fs.mkdirSync(path.join(jflDir, "logs"), { recursive: true });
|
|
1876
|
+
fs.mkdirSync(path.join(jflDir, "journal"), { recursive: true });
|
|
1877
|
+
fs.writeFileSync(path.join(jflDir, "current-session-branch.txt"), sessionBranch);
|
|
1878
|
+
fs.writeFileSync(path.join(jflDir, "current-worktree.txt"), "direct");
|
|
1879
|
+
// Emit session start event
|
|
1880
|
+
if (eventBus) {
|
|
1881
|
+
eventBus.emit({
|
|
1882
|
+
type: "session:started",
|
|
1883
|
+
source: `hub:${runtime || "unknown"}`,
|
|
1884
|
+
data: { branch: sessionBranch, runtime, warnings },
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1888
|
+
res.end(JSON.stringify({
|
|
1889
|
+
ok: true,
|
|
1890
|
+
branch: sessionBranch,
|
|
1891
|
+
syncOk,
|
|
1892
|
+
doctor: { errors: doctorErrors, warnings: doctorWarnings },
|
|
1893
|
+
warnings,
|
|
1894
|
+
}));
|
|
1895
|
+
}
|
|
1896
|
+
catch (err) {
|
|
1897
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1898
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
// POST /api/prompt — get system prompt injection (CLAUDE.md + context)
|
|
1904
|
+
if (url.pathname === "/api/prompt" && req.method === "POST") {
|
|
1905
|
+
let body = "";
|
|
1906
|
+
req.on("data", (chunk) => body += chunk);
|
|
1907
|
+
req.on("end", async () => {
|
|
1908
|
+
try {
|
|
1909
|
+
const { taskType, maxItems } = JSON.parse(body || "{}");
|
|
1910
|
+
const parts = [];
|
|
1911
|
+
// 1. Load CLAUDE.md
|
|
1912
|
+
const claudeMdPath = path.join(projectRoot, "CLAUDE.md");
|
|
1913
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
1914
|
+
const content = fs.readFileSync(claudeMdPath, "utf-8");
|
|
1915
|
+
if (content.length > 50000) {
|
|
1916
|
+
const sections = content.split(/^## /m);
|
|
1917
|
+
const critical = sections.filter((s) => /CRITICAL|Session Sync|Journal Protocol|Immediate Decision|Working Mode|Core Architecture/i.test(s.slice(0, 100)));
|
|
1918
|
+
if (critical.length > 0) {
|
|
1919
|
+
parts.push("# Project Instructions (CLAUDE.md — critical sections)\n");
|
|
1920
|
+
parts.push("## " + critical.join("\n\n## "));
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
parts.push("# Project Instructions (CLAUDE.md — truncated)\n");
|
|
1924
|
+
parts.push(content.slice(0, 30000));
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
else {
|
|
1928
|
+
parts.push("# Project Instructions (CLAUDE.md)\n");
|
|
1929
|
+
parts.push(content);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
// 2. Load recent context
|
|
1933
|
+
try {
|
|
1934
|
+
const contextItems = readJournalEntries(projectRoot, maxItems ?? 20);
|
|
1935
|
+
if (contextItems.length > 0) {
|
|
1936
|
+
parts.push("\n## Recent Project Context\n");
|
|
1937
|
+
parts.push(contextItems.map(item => {
|
|
1938
|
+
const prefix = item.source ? `[${item.source}] ` : "";
|
|
1939
|
+
return `${prefix}${item.content}`;
|
|
1940
|
+
}).join("\n\n"));
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
catch { }
|
|
1944
|
+
// 3. Load knowledge docs summaries
|
|
1945
|
+
const knowledgeDir = path.join(projectRoot, "knowledge");
|
|
1946
|
+
if (fs.existsSync(knowledgeDir)) {
|
|
1947
|
+
const knowledgeDocs = ["VISION.md", "ROADMAP.md", "NARRATIVE.md", "THESIS.md"];
|
|
1948
|
+
const summaries = [];
|
|
1949
|
+
for (const doc of knowledgeDocs) {
|
|
1950
|
+
const docPath = path.join(knowledgeDir, doc);
|
|
1951
|
+
if (fs.existsSync(docPath)) {
|
|
1952
|
+
const content = fs.readFileSync(docPath, "utf-8");
|
|
1953
|
+
if (content.length > 100) {
|
|
1954
|
+
summaries.push(`### ${doc}\n${content.slice(0, 500)}${content.length > 500 ? "\n..." : ""}`);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
if (summaries.length > 0) {
|
|
1959
|
+
parts.push("\n## Knowledge Documents\n");
|
|
1960
|
+
parts.push(summaries.join("\n\n"));
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1964
|
+
res.end(JSON.stringify({
|
|
1965
|
+
prompt: parts.join("\n"),
|
|
1966
|
+
claudeMdSize: fs.existsSync(path.join(projectRoot, "CLAUDE.md"))
|
|
1967
|
+
? fs.statSync(path.join(projectRoot, "CLAUDE.md")).size : 0,
|
|
1968
|
+
taskType: taskType ?? "general",
|
|
1969
|
+
}));
|
|
1970
|
+
}
|
|
1971
|
+
catch (err) {
|
|
1972
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1973
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
// POST /api/session/end — cleanup session (merge, commit, etc)
|
|
1979
|
+
if (url.pathname === "/api/session/end" && req.method === "POST") {
|
|
1980
|
+
let body = "";
|
|
1981
|
+
req.on("data", (chunk) => body += chunk);
|
|
1982
|
+
req.on("end", async () => {
|
|
1983
|
+
try {
|
|
1984
|
+
const { runtime, skipCleanup } = JSON.parse(body || "{}");
|
|
1985
|
+
// Check journal exists
|
|
1986
|
+
let hasJournal = false;
|
|
1987
|
+
try {
|
|
1988
|
+
const branch = execSync("git branch --show-current", { cwd: projectRoot, stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
|
|
1989
|
+
const journalPath = path.join(projectRoot, ".jfl", "journal", `${branch}.jsonl`);
|
|
1990
|
+
hasJournal = fs.existsSync(journalPath) && fs.statSync(journalPath).size > 0;
|
|
1991
|
+
}
|
|
1992
|
+
catch { }
|
|
1993
|
+
let cleanupResult = "skipped";
|
|
1994
|
+
if (!skipCleanup) {
|
|
1995
|
+
const cleanupScript = path.join(projectRoot, "scripts", "session", "session-cleanup.sh");
|
|
1996
|
+
if (fs.existsSync(cleanupScript)) {
|
|
1997
|
+
try {
|
|
1998
|
+
execSync(`bash "${cleanupScript}"`, { cwd: projectRoot, timeout: 60000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1999
|
+
cleanupResult = "ok";
|
|
2000
|
+
}
|
|
2001
|
+
catch (err) {
|
|
2002
|
+
cleanupResult = `error: ${(err.message || "").split("\n")[0]}`;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
else {
|
|
2006
|
+
cleanupResult = "no cleanup script";
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
// Emit session end event
|
|
2010
|
+
if (eventBus) {
|
|
2011
|
+
eventBus.emit({
|
|
2012
|
+
type: "session:ended",
|
|
2013
|
+
source: `hub:${runtime || "unknown"}`,
|
|
2014
|
+
data: { hasJournal, cleanupResult, runtime },
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
2018
|
+
res.end(JSON.stringify({
|
|
2019
|
+
ok: true,
|
|
2020
|
+
hasJournal,
|
|
2021
|
+
cleanupResult,
|
|
2022
|
+
warnings: hasJournal ? [] : ["No journal entry for this session"],
|
|
2023
|
+
}));
|
|
2024
|
+
}
|
|
2025
|
+
catch (err) {
|
|
2026
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2027
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
1788
2032
|
// ── Findings API ──────────────────────────────────────────────────
|
|
1789
2033
|
// GET /api/v1/findings — list current findings
|
|
1790
2034
|
if (url.pathname === "/api/v1/findings" && req.method === "GET") {
|
|
@@ -2060,7 +2304,7 @@ async function startDaemon(projectRoot, port) {
|
|
|
2060
2304
|
}
|
|
2061
2305
|
// Start as detached process with CONTEXT_HUB_DAEMON=1 so the serve
|
|
2062
2306
|
// action knows to ignore SIGTERM during its startup grace period
|
|
2063
|
-
const child = spawn(jflCmd, ["context-hub", "serve", "--port", String(port)], {
|
|
2307
|
+
const child = spawn(jflCmd, ["context-hub", "serve", "--port", String(port), "--project-root", projectRoot], {
|
|
2064
2308
|
cwd: projectRoot,
|
|
2065
2309
|
detached: true,
|
|
2066
2310
|
stdio: ["ignore", fs.openSync(logFile, "a"), fs.openSync(logFile, "a")],
|
|
@@ -2245,7 +2489,7 @@ export async function ensureDaemonInstalled(opts) {
|
|
|
2245
2489
|
// ============================================================================
|
|
2246
2490
|
export async function contextHubCommand(action, options = {}) {
|
|
2247
2491
|
const isGlobal = options.global || false;
|
|
2248
|
-
const projectRoot = isGlobal ? homedir() : process.cwd();
|
|
2492
|
+
const projectRoot = options.projectRoot || (isGlobal ? homedir() : process.cwd());
|
|
2249
2493
|
const port = options.port || getProjectPort(projectRoot);
|
|
2250
2494
|
// Ensure directories exist (skip for actions that don't need local project root)
|
|
2251
2495
|
const globalActions = ["ensure-all", "doctor", "install-daemon", "uninstall-daemon"];
|