jfl 0.3.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +469 -36
  2. package/dist/commands/ci-setup.d.ts +5 -0
  3. package/dist/commands/ci-setup.d.ts.map +1 -0
  4. package/dist/commands/ci-setup.js +82 -0
  5. package/dist/commands/ci-setup.js.map +1 -0
  6. package/dist/commands/context-hub.d.ts.map +1 -1
  7. package/dist/commands/context-hub.js +154 -0
  8. package/dist/commands/context-hub.js.map +1 -1
  9. package/dist/commands/flows.d.ts +4 -1
  10. package/dist/commands/flows.d.ts.map +1 -1
  11. package/dist/commands/flows.js +160 -1
  12. package/dist/commands/flows.js.map +1 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +42 -0
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/peter.d.ts +2 -1
  17. package/dist/commands/peter.d.ts.map +1 -1
  18. package/dist/commands/peter.js +415 -2
  19. package/dist/commands/peter.js.map +1 -1
  20. package/dist/commands/pi.d.ts +21 -0
  21. package/dist/commands/pi.d.ts.map +1 -0
  22. package/dist/commands/pi.js +154 -0
  23. package/dist/commands/pi.js.map +1 -0
  24. package/dist/commands/portfolio.d.ts.map +1 -1
  25. package/dist/commands/portfolio.js +22 -69
  26. package/dist/commands/portfolio.js.map +1 -1
  27. package/dist/commands/predict.d.ts +6 -0
  28. package/dist/commands/predict.d.ts.map +1 -0
  29. package/dist/commands/predict.js +234 -0
  30. package/dist/commands/predict.js.map +1 -0
  31. package/dist/commands/synopsis.d.ts +44 -0
  32. package/dist/commands/synopsis.d.ts.map +1 -1
  33. package/dist/commands/synopsis.js +1 -1
  34. package/dist/commands/synopsis.js.map +1 -1
  35. package/dist/commands/update.d.ts.map +1 -1
  36. package/dist/commands/update.js +107 -5
  37. package/dist/commands/update.js.map +1 -1
  38. package/dist/commands/viz.d.ts +7 -0
  39. package/dist/commands/viz.d.ts.map +1 -0
  40. package/dist/commands/viz.js +460 -0
  41. package/dist/commands/viz.js.map +1 -0
  42. package/dist/dashboard/index.d.ts +4 -5
  43. package/dist/dashboard/index.d.ts.map +1 -1
  44. package/dist/dashboard/index.js +57 -146
  45. package/dist/dashboard/index.js.map +1 -1
  46. package/dist/dashboard-static/assets/index-B6kRK9Rq.js +116 -0
  47. package/dist/dashboard-static/assets/index-BpdKJPLu.css +1 -0
  48. package/dist/dashboard-static/index.html +16 -0
  49. package/dist/index.js +126 -19
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/flow-engine.d.ts +3 -0
  52. package/dist/lib/flow-engine.d.ts.map +1 -1
  53. package/dist/lib/flow-engine.js +70 -1
  54. package/dist/lib/flow-engine.js.map +1 -1
  55. package/dist/lib/hub-client.d.ts +80 -0
  56. package/dist/lib/hub-client.d.ts.map +1 -0
  57. package/dist/lib/hub-client.js +46 -0
  58. package/dist/lib/hub-client.js.map +1 -0
  59. package/dist/lib/predictor.d.ts +99 -0
  60. package/dist/lib/predictor.d.ts.map +1 -0
  61. package/dist/lib/predictor.js +394 -0
  62. package/dist/lib/predictor.js.map +1 -0
  63. package/dist/lib/service-gtm.d.ts +86 -51
  64. package/dist/lib/service-gtm.d.ts.map +1 -1
  65. package/dist/lib/service-gtm.js +417 -242
  66. package/dist/lib/service-gtm.js.map +1 -1
  67. package/dist/lib/telemetry-agent.d.ts +57 -0
  68. package/dist/lib/telemetry-agent.d.ts.map +1 -0
  69. package/dist/lib/telemetry-agent.js +268 -0
  70. package/dist/lib/telemetry-agent.js.map +1 -0
  71. package/dist/lib/telemetry-digest.d.ts.map +1 -1
  72. package/dist/lib/telemetry-digest.js +17 -17
  73. package/dist/lib/telemetry-digest.js.map +1 -1
  74. package/dist/lib/telemetry.d.ts +1 -0
  75. package/dist/lib/telemetry.d.ts.map +1 -1
  76. package/dist/lib/telemetry.js +14 -6
  77. package/dist/lib/telemetry.js.map +1 -1
  78. package/dist/mcp/context-hub-mcp.js +0 -0
  79. package/dist/mcp/service-registry-mcp.js +0 -0
  80. package/dist/types/map.d.ts +1 -1
  81. package/dist/types/map.d.ts.map +1 -1
  82. package/dist/types/map.js.map +1 -1
  83. package/dist/utils/jfl-paths.d.ts +1 -0
  84. package/dist/utils/jfl-paths.d.ts.map +1 -1
  85. package/dist/utils/jfl-paths.js +1 -0
  86. package/dist/utils/jfl-paths.js.map +1 -1
  87. package/package.json +7 -2
  88. package/scripts/generate-changesets.sh +113 -0
  89. package/scripts/pp-branch-pr.sh +115 -0
  90. package/template/.github/workflows/jfl-eval.yml +448 -0
  91. package/template/.github/workflows/jfl-review.yml +371 -0
  92. package/template/.jfl/flows/self-driving.yaml +190 -0
  93. package/dist/dashboard/components.d.ts +0 -7
  94. package/dist/dashboard/components.d.ts.map +0 -1
  95. package/dist/dashboard/components.js +0 -575
  96. package/dist/dashboard/components.js.map +0 -1
  97. package/dist/dashboard/pages.d.ts +0 -7
  98. package/dist/dashboard/pages.d.ts.map +0 -1
  99. package/dist/dashboard/pages.js +0 -1580
  100. package/dist/dashboard/pages.js.map +0 -1
  101. package/dist/dashboard/styles.d.ts +0 -7
  102. package/dist/dashboard/styles.d.ts.map +0 -1
  103. package/dist/dashboard/styles.js +0 -1110
  104. package/dist/dashboard/styles.js.map +0 -1
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @purpose CLI command for `jfl ci setup` — deploys eval + review CI workflows to a project
3
+ */
4
+ export declare function ciSetupCommand(): Promise<void>;
5
+ //# sourceMappingURL=ci-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-setup.d.ts","sourceRoot":"","sources":["../../src/commands/ci-setup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyBH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAsEpD"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @purpose CLI command for `jfl ci setup` — deploys eval + review CI workflows to a project
3
+ */
4
+ import chalk from "chalk";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { join, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { execSync } from "child_process";
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const TEMPLATE_DIR = join(__dirname, "../../template/.github/workflows");
12
+ const WORKFLOW_FILES = ["jfl-eval.yml", "jfl-review.yml"];
13
+ function getRepoSlug() {
14
+ try {
15
+ const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
16
+ const match = remote.match(/github\.com[:/](.+?)(?:\.git)?$/);
17
+ return match?.[1] ?? "OWNER/REPO";
18
+ }
19
+ catch {
20
+ return "OWNER/REPO";
21
+ }
22
+ }
23
+ export async function ciSetupCommand() {
24
+ const cwd = process.cwd();
25
+ const targetDir = join(cwd, ".github", "workflows");
26
+ if (!existsSync(TEMPLATE_DIR)) {
27
+ console.log(chalk.red("\n Template workflows not found at: " + TEMPLATE_DIR));
28
+ console.log(chalk.gray(" This usually means the jfl CLI package is incomplete. Try: npm install -g jfl\n"));
29
+ process.exit(1);
30
+ }
31
+ if (!existsSync(join(cwd, ".github"))) {
32
+ mkdirSync(join(cwd, ".github"), { recursive: true });
33
+ }
34
+ if (!existsSync(targetDir)) {
35
+ mkdirSync(targetDir, { recursive: true });
36
+ }
37
+ let deployed = 0;
38
+ let skipped = 0;
39
+ for (const file of WORKFLOW_FILES) {
40
+ const src = join(TEMPLATE_DIR, file);
41
+ const dest = join(targetDir, file);
42
+ if (!existsSync(src)) {
43
+ console.log(chalk.yellow(` Template missing: ${file} — skipping`));
44
+ skipped++;
45
+ continue;
46
+ }
47
+ const templateContent = readFileSync(src, "utf-8");
48
+ if (existsSync(dest)) {
49
+ const existingContent = readFileSync(dest, "utf-8");
50
+ if (existingContent === templateContent) {
51
+ console.log(chalk.gray(` ${file} — already up to date`));
52
+ deployed++;
53
+ continue;
54
+ }
55
+ console.log(chalk.yellow(` ${file} — exists and differs from template, overwriting`));
56
+ }
57
+ writeFileSync(dest, templateContent);
58
+ console.log(chalk.green(` ${file} — deployed`));
59
+ deployed++;
60
+ }
61
+ const repoSlug = getRepoSlug();
62
+ console.log();
63
+ if (deployed > 0) {
64
+ console.log(chalk.green(" CI workflows deployed to .github/workflows/"));
65
+ }
66
+ if (skipped > 0) {
67
+ console.log(chalk.yellow(` ${skipped} workflow(s) skipped (template missing)`));
68
+ }
69
+ console.log();
70
+ console.log(chalk.bold(" Required GitHub secrets") + chalk.gray(` (set at github.com/${repoSlug}/settings/secrets):`));
71
+ console.log(chalk.cyan(" OPENAI_API_KEY") + chalk.gray(" — For AI quality assessment and code review"));
72
+ console.log();
73
+ console.log(chalk.bold(" Optional:"));
74
+ console.log(chalk.cyan(" OPENROUTER_API_KEY") + chalk.gray(" — Fallback if OpenAI unavailable"));
75
+ console.log(chalk.cyan(" JFL_HUB_URL") + chalk.gray(" — Context Hub URL for real-time dashboard updates"));
76
+ console.log(chalk.cyan(" JFL_HUB_TOKEN") + chalk.gray(" — Auth token for hub API"));
77
+ console.log();
78
+ console.log(chalk.gray(" The eval workflow runs on PRs from Peter Parker (pp/ branches)."));
79
+ console.log(chalk.gray(" To trigger manually, add the 'run-eval' or 'ai-review' label to any PR."));
80
+ console.log();
81
+ }
82
+ //# sourceMappingURL=ci-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-setup.js","sourceRoot":"","sources":["../../src/commands/ci-setup.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmzCH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBjF;AA2ND,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,CAAA;CAAO,iBA8enE"}
1
+ {"version":3,"file":"context-hub.d.ts","sourceRoot":"","sources":["../../src/commands/context-hub.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAg8CH,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBjF;AA2ND,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,CAAA;CAAO,iBA+fnE"}
@@ -632,10 +632,16 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
632
632
  scope: cfg.context_scope || null,
633
633
  registered_services: (cfg.registered_services || []).map((s) => ({
634
634
  name: s.name,
635
+ path: s.path,
635
636
  type: s.type,
636
637
  status: s.status,
637
638
  context_scope: s.context_scope || null,
638
639
  })),
640
+ openclaw_agents: (cfg.openclaw_agents || []).map((a) => ({
641
+ id: a.id,
642
+ runtime: a.runtime,
643
+ registered_at: a.registered_at,
644
+ })),
639
645
  gtm_parent: cfg.gtm_parent || null,
640
646
  portfolio_parent: cfg.portfolio_parent || null,
641
647
  };
@@ -876,6 +882,38 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
876
882
  }
877
883
  return;
878
884
  }
885
+ // Synopsis (work summary)
886
+ if (url.pathname === "/api/synopsis" && req.method === "GET") {
887
+ try {
888
+ const hours = parseInt(url.searchParams.get("hours") || "24", 10);
889
+ const author = url.searchParams.get("author") || undefined;
890
+ const { generateSynopsis } = await import("./synopsis.js");
891
+ const synopsis = generateSynopsis(projectRoot, hours, author);
892
+ res.writeHead(200, { "Content-Type": "application/json" });
893
+ res.end(JSON.stringify(synopsis));
894
+ }
895
+ catch (err) {
896
+ res.writeHead(500, { "Content-Type": "application/json" });
897
+ res.end(JSON.stringify({ error: err.message }));
898
+ }
899
+ return;
900
+ }
901
+ // Prediction accuracy (Stratus)
902
+ if (url.pathname === "/api/eval/predictions" && req.method === "GET") {
903
+ try {
904
+ const { Predictor } = await import("../lib/predictor.js");
905
+ const predictor = new Predictor({ projectRoot });
906
+ const accuracy = predictor.getAccuracy();
907
+ const recent = predictor.getHistory(20).reverse();
908
+ res.writeHead(200, { "Content-Type": "application/json" });
909
+ res.end(JSON.stringify({ accuracy, recent }));
910
+ }
911
+ catch (err) {
912
+ res.writeHead(200, { "Content-Type": "application/json" });
913
+ res.end(JSON.stringify({ accuracy: { total: 0, resolved: 0, direction_accuracy: 0, mean_delta_error: 0, calibration: 0 }, recent: [] }));
914
+ }
915
+ return;
916
+ }
879
917
  // Cross-project health
880
918
  if (url.pathname === "/api/projects" && req.method === "GET") {
881
919
  const tracked = getTrackedProjects();
@@ -1005,6 +1043,39 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
1005
1043
  }
1006
1044
  return;
1007
1045
  }
1046
+ // Telemetry agent status
1047
+ if (url.pathname === "/api/telemetry/agent" && req.method === "GET") {
1048
+ const agent = server.__telemetryAgent;
1049
+ if (agent) {
1050
+ res.writeHead(200, { "Content-Type": "application/json" });
1051
+ res.end(JSON.stringify(agent.getStatus()));
1052
+ }
1053
+ else {
1054
+ res.writeHead(200, { "Content-Type": "application/json" });
1055
+ res.end(JSON.stringify({ running: false, lastRun: '', runCount: 0, lastInsights: [] }));
1056
+ }
1057
+ return;
1058
+ }
1059
+ // Telemetry agent: trigger manual run
1060
+ if (url.pathname === "/api/telemetry/agent/run" && req.method === "POST") {
1061
+ const agent = server.__telemetryAgent;
1062
+ if (agent) {
1063
+ try {
1064
+ const insights = await agent.run();
1065
+ res.writeHead(200, { "Content-Type": "application/json" });
1066
+ res.end(JSON.stringify({ ok: true, insights }));
1067
+ }
1068
+ catch (err) {
1069
+ res.writeHead(500, { "Content-Type": "application/json" });
1070
+ res.end(JSON.stringify({ error: err.message }));
1071
+ }
1072
+ }
1073
+ else {
1074
+ res.writeHead(503, { "Content-Type": "application/json" });
1075
+ res.end(JSON.stringify({ error: "Telemetry agent not running" }));
1076
+ }
1077
+ return;
1078
+ }
1008
1079
  // Flow definitions
1009
1080
  if (url.pathname === "/api/flows" && req.method === "GET") {
1010
1081
  if (!flowEngine) {
@@ -1061,6 +1132,72 @@ function createServer(projectRoot, port, eventBus, flowEngine) {
1061
1132
  });
1062
1133
  return;
1063
1134
  }
1135
+ if (url.pathname.match(/^\/api\/flows\/[^/]+\/toggle$/) && req.method === "POST") {
1136
+ if (!flowEngine) {
1137
+ res.writeHead(503, { "Content-Type": "application/json" });
1138
+ res.end(JSON.stringify({ error: "Flow engine not initialized" }));
1139
+ return;
1140
+ }
1141
+ const flowName = decodeURIComponent(url.pathname.split("/")[3]);
1142
+ let body = "";
1143
+ req.on("data", chunk => body += chunk);
1144
+ req.on("end", () => {
1145
+ try {
1146
+ const { enabled } = JSON.parse(body || "{}");
1147
+ const result = flowEngine.toggleFlow(flowName, enabled);
1148
+ if (!result) {
1149
+ res.writeHead(404, { "Content-Type": "application/json" });
1150
+ res.end(JSON.stringify({ error: "Flow not found" }));
1151
+ return;
1152
+ }
1153
+ res.writeHead(200, { "Content-Type": "application/json" });
1154
+ res.end(JSON.stringify({ ok: true, flow: flowName, enabled: result.enabled }));
1155
+ }
1156
+ catch (err) {
1157
+ res.writeHead(500, { "Content-Type": "application/json" });
1158
+ res.end(JSON.stringify({ error: err.message }));
1159
+ }
1160
+ });
1161
+ return;
1162
+ }
1163
+ if (url.pathname === "/api/actions/spawn" && req.method === "POST") {
1164
+ let body = "";
1165
+ req.on("data", chunk => body += chunk);
1166
+ req.on("end", () => {
1167
+ try {
1168
+ const { command, args, cwd, event_type, event_data } = JSON.parse(body || "{}");
1169
+ if (!command) {
1170
+ res.writeHead(400, { "Content-Type": "application/json" });
1171
+ res.end(JSON.stringify({ error: "command required" }));
1172
+ return;
1173
+ }
1174
+ const env = { ...process.env };
1175
+ delete env.ANTHROPIC_API_KEY;
1176
+ delete env.CLAUDE_CODE_ENTRYPOINT;
1177
+ const child = spawn(command, args || [], {
1178
+ cwd: cwd || projectRoot,
1179
+ detached: true,
1180
+ stdio: "ignore",
1181
+ env,
1182
+ });
1183
+ child.unref();
1184
+ if (event_type && eventBus) {
1185
+ eventBus.emit({
1186
+ type: event_type,
1187
+ source: "dashboard:action",
1188
+ data: event_data || { command, args, pid: child.pid },
1189
+ });
1190
+ }
1191
+ res.writeHead(200, { "Content-Type": "application/json" });
1192
+ res.end(JSON.stringify({ ok: true, pid: child.pid }));
1193
+ }
1194
+ catch (err) {
1195
+ res.writeHead(500, { "Content-Type": "application/json" });
1196
+ res.end(JSON.stringify({ error: err.message }));
1197
+ }
1198
+ });
1199
+ return;
1200
+ }
1064
1201
  // 404
1065
1202
  res.writeHead(404, { "Content-Type": "application/json" });
1066
1203
  res.end(JSON.stringify({ error: "Not found" }));
@@ -1728,6 +1865,23 @@ export async function contextHubCommand(action, options = {}) {
1728
1865
  catch (err) {
1729
1866
  console.error(`[${timestamp}] Failed to start flow engine:`, err.message);
1730
1867
  }
1868
+ // Start telemetry agent (periodic pattern detection)
1869
+ try {
1870
+ const { TelemetryAgent } = await import("../lib/telemetry-agent.js");
1871
+ const telemetryAgent = new TelemetryAgent({
1872
+ projectRoot,
1873
+ intervalMs: 30 * 60 * 1000,
1874
+ emitEvent: (type, data, source) => {
1875
+ eventBus.emit({ type: type, data, source: source || 'telemetry-agent' });
1876
+ },
1877
+ });
1878
+ telemetryAgent.start();
1879
+ server.__telemetryAgent = telemetryAgent;
1880
+ console.log(`[${timestamp}] Telemetry agent started (interval: 30m)`);
1881
+ }
1882
+ catch (err) {
1883
+ console.error(`[${timestamp}] Failed to start telemetry agent:`, err.message);
1884
+ }
1731
1885
  console.log(`[${timestamp}] MAP event bus initialized (buffer: 1000, subscribers: ${eventBus.getSubscriberCount()})`);
1732
1886
  console.log(`[${timestamp}] Ready to serve requests`);
1733
1887
  });