jfl 0.3.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +294 -30
  2. package/dist/commands/context-hub.d.ts.map +1 -1
  3. package/dist/commands/context-hub.js +154 -0
  4. package/dist/commands/context-hub.js.map +1 -1
  5. package/dist/commands/flows.d.ts +4 -1
  6. package/dist/commands/flows.d.ts.map +1 -1
  7. package/dist/commands/flows.js +160 -1
  8. package/dist/commands/flows.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +42 -0
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/peter.d.ts.map +1 -1
  13. package/dist/commands/peter.js +220 -1
  14. package/dist/commands/peter.js.map +1 -1
  15. package/dist/commands/pi.d.ts +21 -0
  16. package/dist/commands/pi.d.ts.map +1 -0
  17. package/dist/commands/pi.js +154 -0
  18. package/dist/commands/pi.js.map +1 -0
  19. package/dist/commands/portfolio.d.ts.map +1 -1
  20. package/dist/commands/portfolio.js +22 -69
  21. package/dist/commands/portfolio.js.map +1 -1
  22. package/dist/commands/predict.d.ts +6 -0
  23. package/dist/commands/predict.d.ts.map +1 -0
  24. package/dist/commands/predict.js +234 -0
  25. package/dist/commands/predict.js.map +1 -0
  26. package/dist/commands/synopsis.d.ts +44 -0
  27. package/dist/commands/synopsis.d.ts.map +1 -1
  28. package/dist/commands/synopsis.js +1 -1
  29. package/dist/commands/synopsis.js.map +1 -1
  30. package/dist/commands/update.d.ts.map +1 -1
  31. package/dist/commands/update.js +49 -1
  32. package/dist/commands/update.js.map +1 -1
  33. package/dist/commands/viz.d.ts +7 -0
  34. package/dist/commands/viz.d.ts.map +1 -0
  35. package/dist/commands/viz.js +460 -0
  36. package/dist/commands/viz.js.map +1 -0
  37. package/dist/dashboard/index.d.ts +4 -5
  38. package/dist/dashboard/index.d.ts.map +1 -1
  39. package/dist/dashboard/index.js +57 -146
  40. package/dist/dashboard/index.js.map +1 -1
  41. package/dist/dashboard-static/assets/index-B6kRK9Rq.js +116 -0
  42. package/dist/dashboard-static/assets/index-BpdKJPLu.css +1 -0
  43. package/dist/dashboard-static/index.html +16 -0
  44. package/dist/index.js +112 -19
  45. package/dist/index.js.map +1 -1
  46. package/dist/lib/flow-engine.d.ts +1 -0
  47. package/dist/lib/flow-engine.d.ts.map +1 -1
  48. package/dist/lib/flow-engine.js +30 -1
  49. package/dist/lib/flow-engine.js.map +1 -1
  50. package/dist/lib/hub-client.d.ts +80 -0
  51. package/dist/lib/hub-client.d.ts.map +1 -0
  52. package/dist/lib/hub-client.js +46 -0
  53. package/dist/lib/hub-client.js.map +1 -0
  54. package/dist/lib/predictor.d.ts +99 -0
  55. package/dist/lib/predictor.d.ts.map +1 -0
  56. package/dist/lib/predictor.js +394 -0
  57. package/dist/lib/predictor.js.map +1 -0
  58. package/dist/lib/service-gtm.d.ts +86 -51
  59. package/dist/lib/service-gtm.d.ts.map +1 -1
  60. package/dist/lib/service-gtm.js +417 -242
  61. package/dist/lib/service-gtm.js.map +1 -1
  62. package/dist/lib/telemetry-agent.d.ts +57 -0
  63. package/dist/lib/telemetry-agent.d.ts.map +1 -0
  64. package/dist/lib/telemetry-agent.js +268 -0
  65. package/dist/lib/telemetry-agent.js.map +1 -0
  66. package/dist/lib/telemetry-digest.d.ts.map +1 -1
  67. package/dist/lib/telemetry-digest.js +17 -17
  68. package/dist/lib/telemetry-digest.js.map +1 -1
  69. package/dist/lib/telemetry.d.ts +1 -0
  70. package/dist/lib/telemetry.d.ts.map +1 -1
  71. package/dist/lib/telemetry.js +14 -6
  72. package/dist/lib/telemetry.js.map +1 -1
  73. package/dist/mcp/context-hub-mcp.js +0 -0
  74. package/dist/mcp/service-registry-mcp.js +0 -0
  75. package/dist/types/map.d.ts +1 -1
  76. package/dist/types/map.d.ts.map +1 -1
  77. package/dist/types/map.js.map +1 -1
  78. package/dist/utils/jfl-paths.d.ts +1 -0
  79. package/dist/utils/jfl-paths.d.ts.map +1 -1
  80. package/dist/utils/jfl-paths.js +1 -0
  81. package/dist/utils/jfl-paths.js.map +1 -1
  82. package/package.json +7 -2
  83. package/scripts/generate-changesets.sh +113 -0
  84. package/scripts/pp-branch-pr.sh +115 -0
  85. package/template/.jfl/flows-self-driving.yaml +170 -0
  86. package/dist/dashboard/components.d.ts +0 -7
  87. package/dist/dashboard/components.d.ts.map +0 -1
  88. package/dist/dashboard/components.js +0 -575
  89. package/dist/dashboard/components.js.map +0 -1
  90. package/dist/dashboard/pages.d.ts +0 -7
  91. package/dist/dashboard/pages.d.ts.map +0 -1
  92. package/dist/dashboard/pages.js +0 -1580
  93. package/dist/dashboard/pages.js.map +0 -1
  94. package/dist/dashboard/styles.d.ts +0 -7
  95. package/dist/dashboard/styles.d.ts.map +0 -1
  96. package/dist/dashboard/styles.js +0 -1110
  97. package/dist/dashboard/styles.js.map +0 -1
package/README.md CHANGED
@@ -55,28 +55,37 @@ That's it. SessionStart hooks handle repo sync, session branching, Context Hub s
55
55
 
56
56
  ## Architecture
57
57
 
58
- JFL workspaces are **context layers**, not code repos. Product code lives in separate service repos that register with the GTM.
58
+ JFL supports a three-level hierarchy: **Portfolio > GTM > Services**. Portfolios coordinate multiple products. GTMs are context layers for individual products. Services are the repos that do the actual work.
59
59
 
60
60
  ```
61
- my-project/ <- GTM workspace (strategy, context, orchestration)
61
+ visa-portfolio/ <- Portfolio (strategy, cross-product RL, data flow)
62
62
  ├── .jfl/
63
- │ ├── config.json <- Project config (team, services, ports)
64
- │ ├── journal/ <- Session journals (JSONL, one file per session)
65
- │ ├── memory.db <- Indexed memory (TF-IDF + embeddings)
66
- ├── agents/ <- Narrowly-scoped agent manifests + policies
67
- ├── flows/ <- Per-agent flow definitions (auto-loaded)
68
- ├── service-events.jsonl <- Event bus file-drop
69
- └── services.json <- Registered services
70
- ├── knowledge/ <- Strategy docs (VISION, ROADMAP, THESIS, etc.)
71
- ├── content/ <- Generated content
72
- ├── suggestions/ <- Per-contributor workspaces
73
- ├── .claude/
74
- │ ├── settings.json <- Claude Code hooks (SessionStart, Stop, etc.)
75
- ├── agents/ <- Service agent definitions
76
- └── skills/ <- Slash commands (/hud, /content, etc.)
77
- ├── scripts/session/ <- Session management (init, sync, cleanup)
78
- ├── CLAUDE.md <- AI instructions
79
- └── .mcp.json <- MCP server config (Context Hub)
63
+ │ ├── config.json <- type: "portfolio", registered child GTMs
64
+ │ ├── eval.jsonl <- Aggregated eval data from all children
65
+ │ ├── flows.yaml <- Cross-product event routing
66
+ └── journal/ <- Portfolio-level + synced child journals
67
+
68
+ ├── productrank-gtm/ <- GTM workspace (registered as child)
69
+ ├── .jfl/
70
+ │ │ ├── config.json <- type: "gtm", portfolio_parent, registered services
71
+ │ │ ├── eval.jsonl <- Eval entries from arena competitions
72
+ │ │ ├── journal/ <- Session journals + synced service journals
73
+ │ │ ├── agents/ <- Agent manifests + policies
74
+ ├── flows/ <- Per-agent flow definitions
75
+ │ └── service-events.jsonl
76
+ ├── knowledge/ <- Strategy docs (VISION, ROADMAP, THESIS, etc.)
77
+ ├── content/ <- Generated content
78
+ ├── suggestions/ <- Per-contributor workspaces
79
+ │ ├── .claude/
80
+ │ │ ├── settings.json <- Claude Code hooks
81
+ │ │ ├── agents/ <- Service agent definitions
82
+ │ │ └── skills/ <- Slash commands (/hud, /content, etc.)
83
+ │ ├── scripts/session/ <- Session management
84
+ │ ├── CLAUDE.md <- AI instructions
85
+ │ └── .mcp.json <- MCP server config
86
+
87
+ └── seo-agent/ <- Another GTM (registered as child)
88
+ └── ...
80
89
 
81
90
  my-api/ <- Service repo (registered in GTM)
82
91
  ├── src/
@@ -89,7 +98,8 @@ my-api/ <- Service repo (registered in GTM)
89
98
  - Services work independently
90
99
  - Multiple services register to one GTM
91
100
  - `jfl update` updates tooling without touching service code
92
- - Journal entries sync from services to parent GTM
101
+ - Eval data dual-writes up the chain (service > GTM > portfolio)
102
+ - Cross-product event routing at portfolio level
93
103
 
94
104
  ---
95
105
 
@@ -106,14 +116,14 @@ jfl context-hub stop # Stop daemon
106
116
  jfl context-hub restart # Restart daemon
107
117
  jfl context-hub doctor # Diagnose all projects (OK/ZOMBIE/DOWN/STALE)
108
118
  jfl context-hub ensure-all # Start for all GTM projects
109
- jfl context-hub dashboard # Live event + context dashboard
119
+ jfl context-hub dashboard # Open web dashboard (opens browser)
110
120
  jfl context-hub install-daemon # Auto-start on boot (launchd/systemd)
111
121
  jfl context-hub uninstall-daemon # Remove auto-start
112
122
  jfl context-hub query # Query context from CLI
113
123
  jfl context-hub serve # Run in foreground (daemon mode)
114
124
  ```
115
125
 
116
- **Per-project ports** assigned automatically (or set in `.jfl/config.json` `contextHub.port`).
126
+ **Per-project ports** assigned automatically (or set in `.jfl/config.json` > `contextHub.port`).
117
127
 
118
128
  **MCP Tools** (available to Claude Code and any MCP client):
119
129
 
@@ -126,9 +136,32 @@ jfl context-hub serve # Run in foreground (daemon mode)
126
136
  | `memory_search` | Search indexed journal memories |
127
137
  | `memory_status` | Memory system statistics |
128
138
  | `memory_add` | Add manual memory entry |
139
+ | `query_experiment_history` | Query RL trajectories for agent experiments |
129
140
 
130
141
  **Resilience:** 5-layer system — MCP auto-recovery on ECONNREFUSED, health-check-before-ensure hooks, `ensure-all` for batch startup, `doctor` diagnostics, launchd/systemd daemon with keepalive.
131
142
 
143
+ ### Dashboard V2
144
+
145
+ A pre-built Vite + Preact + Tailwind SPA served by Context Hub at `/dashboard/`. Auto-detects workspace type and adapts layout.
146
+
147
+ **Pages:**
148
+
149
+ | Page | What It Shows |
150
+ |------|--------------|
151
+ | **Overview** | Activity charts, product cards, metric cards |
152
+ | **Journal** | Searchable journal entries with type filters |
153
+ | **Events** | Live event feed with pattern filter presets (eval, session, flow, etc.) |
154
+ | **Services** | Registered services with type badges, context scope visualization, data flows |
155
+ | **Flows** | Flow definitions and execution history |
156
+ | **Health** | System metrics, context sources, memory index, tracked projects |
157
+ | **Agents** | Eval leaderboards grouped by product domain |
158
+
159
+ **Features:** Sidebar with structured sections (Workspace / Infra / Eval), inline SVG icons, agent leaderboard in sidebar, sparkline charts, real-time polling.
160
+
161
+ ```bash
162
+ jfl context-hub dashboard # Opens /dashboard/ in browser
163
+ ```
164
+
132
165
  ### MAP Event Bus
133
166
 
134
167
  Metrics, Agents, Pipeline — an in-process event bus inside Context Hub.
@@ -138,10 +171,126 @@ Metrics, Agents, Pipeline — an in-process event bus inside Context Hub.
138
171
  - **Journal bridge** — watches `.jfl/journal/`, emits events on new entries
139
172
  - **Pattern-matching subscriptions** (glob support)
140
173
  - **Transports:** SSE, WebSocket, HTTP polling
141
- - **Event types:** `session:started`, `session:ended`, `task:completed`, `journal:entry`, `service:healthy`, `custom`, and more
174
+ - **Cross-product routing** portfolio flows route events between child GTMs
175
+ - **Event types:** `session:started`, `session:ended`, `eval:scored`, `journal:entry`, `flow:triggered`, `agent:iteration-complete`, `portfolio:phone-home`, and more
142
176
 
143
177
  Services emit events by appending to `.jfl/service-events.jsonl` — no auth needed, Context Hub watches the file automatically.
144
178
 
179
+ ### Eval Framework
180
+
181
+ Track agent performance over time. Eval entries dual-write up the parent chain (service > GTM > portfolio) so every level has visibility.
182
+
183
+ ```bash
184
+ jfl eval list # List recent eval entries
185
+ jfl eval list -a shadow # Filter by agent
186
+ jfl eval trajectory -a shadow # Composite score over time (with sparkline)
187
+ jfl eval log -a shadow -m '{"composite":0.69}' # Log an eval entry
188
+ jfl eval compare # Side-by-side agent comparison
189
+ jfl eval tuples # Extract (state, action, reward) training tuples
190
+ ```
191
+
192
+ **Eval entries** are JSONL with agent name, metrics, composite score, model version, and deltas:
193
+
194
+ ```json
195
+ {
196
+ "v": 1, "ts": "2026-03-05T15:22:47Z",
197
+ "agent": "productrank-shadow",
198
+ "dataset": "vibe-50-v1",
199
+ "model_version": "shadow-0.3.1",
200
+ "metrics": {"ndcg@10": 0.59, "mrr": 0.77, "precision@5": 0.43},
201
+ "composite": 0.6935,
202
+ "delta": {"composite": -0.029}
203
+ }
204
+ ```
205
+
206
+ **Leaderboard:** Agents grouped by metric domain. ProductRank agents scored on ndcg@10, mrr, precision@5. SEO agents scored on avg_rank, keywords_ranked. Dashboard Agents page shows leaderboards per domain.
207
+
208
+ **Training tuples** extracted from journals for fine-tuning: `(state, action, reward)` — maps codebase state + experiment action to eval score delta.
209
+
210
+ **API endpoints** on Context Hub:
211
+ - `GET /api/eval/leaderboard` — all agents ranked by composite
212
+ - `GET /api/eval/trajectory?agent=X&metric=composite` — score trajectory with timestamps
213
+
214
+ ### RL Infrastructure
215
+
216
+ JFL generalizes the Karpathy nanochat pattern: structured journals are the replay buffer, eval scores are rewards, agents learn in-context from past trajectories.
217
+
218
+ ```
219
+ Agent LLM (Policy) > reads trajectories, proposes experiments
220
+ Stratus (World Model) > predicts outcomes, filters bad proposals
221
+ Journals (Replay Buffer) > structured experiment history
222
+ Eval Framework (Reward) > composite scores, score deltas
223
+ Event Bus (Nervous System) > connects everything
224
+ ```
225
+
226
+ **JournalEntry type** — canonical schema with 6 RL fields: `hypothesis`, `outcome`, `score_delta`, `eval_snapshot`, `diff_hash`, `context_entries`.
227
+
228
+ **TrajectoryLoader** — query, filter, and render experiment trajectories for agent context windows. Supports filtering by session, agent, outcome, score range.
229
+
230
+ **Peter Parker** — model-routed orchestrator with cost/balanced/quality profiles. Routes tasks to haiku/sonnet/opus based on complexity. Subscribes to event bus for reactive dispatch.
231
+
232
+ **Flow Engine** — declarative trigger-action automation in `.jfl/flows.yaml`:
233
+
234
+ ```yaml
235
+ - name: eval-scored-trigger-analysis
236
+ trigger:
237
+ pattern: "eval:scored"
238
+ gate:
239
+ requires_approval: true
240
+ actions:
241
+ - type: spawn
242
+ command: "claude -p 'Analyze the latest eval results'"
243
+ ```
244
+
245
+ Flow actions: `log`, `emit`, `journal`, `webhook`, `command`, `spawn`. Gates: `after` (time-gated), `before` (deadline), `requires_approval`.
246
+
247
+ **MCP tool:** `query_experiment_history` — agents query past experiment trajectories to inform next proposals.
248
+
249
+ ### Portfolio Management
250
+
251
+ Coordinate multiple GTM workspaces under one portfolio.
252
+
253
+ ```bash
254
+ jfl portfolio register /path/to/gtm # Register a GTM in this portfolio
255
+ jfl portfolio list # List child GTMs with health
256
+ jfl portfolio unregister <name> # Remove a GTM
257
+ jfl portfolio status # Portfolio health + eval summary
258
+ jfl portfolio phone-home # Report GTM health to portfolio parent
259
+ ```
260
+
261
+ **Portfolio Context Hub** operates in fan-out mode:
262
+ - Connects to child GTM hubs via SSE
263
+ - Bridges child events into portfolio event bus
264
+ - Fans out search queries across all child hubs
265
+ - Aggregates eval leaderboard across products
266
+ - Enforces context scope (produces/consumes/denied) between GTMs
267
+
268
+ **Cross-product flows** defined in `.jfl/flows.yaml`:
269
+
270
+ ```yaml
271
+ - name: tool-trends-to-seo
272
+ trigger:
273
+ pattern: "discovery:tool-trend"
274
+ source: "productrank-gtm"
275
+ actions:
276
+ - type: webhook
277
+ url: "http://localhost:{{child.seo-agent.port}}/api/events"
278
+ ```
279
+
280
+ Template variables: `{{child.NAME.port}}`, `{{child.NAME.token}}`
281
+
282
+ **Context scope** — each child GTM declares what events it produces and consumes. Portfolio enforces boundaries:
283
+
284
+ ```json
285
+ {
286
+ "context_scope": {
287
+ "produces": ["discovery:tool-trend", "eval:*"],
288
+ "consumes": ["strategy:*", "seo:serp-data"],
289
+ "denied": []
290
+ }
291
+ }
292
+ ```
293
+
145
294
  ### Memory System
146
295
 
147
296
  Hybrid search over all journal entries with TF-IDF (40%) + semantic embeddings (60%).
@@ -164,6 +313,7 @@ Automatic session isolation for parallel work:
164
313
  - **Multiple concurrent sessions:** Isolated git worktrees prevent conflicts
165
314
  - **Auto-commit:** Saves work every 2 minutes (knowledge, journal, suggestions)
166
315
  - **Crash recovery:** Detects uncommitted work in stale sessions, auto-commits on next start
316
+ - **Cleanup guard:** Prevents `rm -rf` on main branch when no worktrees exist
167
317
 
168
318
  ```bash
169
319
  # Hooks handle everything automatically. Manual control:
@@ -212,6 +362,18 @@ jfl services # Interactive TUI (no args)
212
362
  - Service entry in `.jfl/services.json`
213
363
  - Config in service repo (`.jfl/config.json` with `gtm_parent`)
214
364
 
365
+ **Context scoping:** Each service declares what events it produces and consumes. The GTM enforces scope — teams can't read each other's journals unless explicitly granted.
366
+
367
+ ```json
368
+ {
369
+ "context_scope": {
370
+ "produces": ["eval:submission", "journal:my-team*"],
371
+ "consumes": ["eval:scored", "leaderboard:updated"],
372
+ "denied": ["journal:other-team*"]
373
+ }
374
+ }
375
+ ```
376
+
215
377
  **Phone-home on session end:** When a service session ends, it syncs to the parent GTM:
216
378
  - Journal entries copied to `GTM/.jfl/journal/service-{name}-*.jsonl`
217
379
  - Comprehensive sync payload (git stats, health, environment)
@@ -254,12 +416,32 @@ jfl services # Interactive TUI (no args)
254
416
  | `jfl context-hub status` | Health check |
255
417
  | `jfl context-hub doctor [--clean]` | Diagnose all projects |
256
418
  | `jfl context-hub ensure-all` | Start for all GTM projects |
257
- | `jfl context-hub dashboard` | Live event/context dashboard |
419
+ | `jfl context-hub dashboard` | Open web dashboard in browser |
258
420
  | `jfl context-hub query` | Query context from CLI |
259
421
  | `jfl context-hub serve` | Run in foreground (daemon mode) |
260
422
  | `jfl context-hub install-daemon` | Auto-start on boot |
261
423
  | `jfl context-hub uninstall-daemon` | Remove auto-start |
262
424
 
425
+ ### Eval Framework
426
+
427
+ | Command | Description |
428
+ |---------|-------------|
429
+ | `jfl eval list [-a agent] [-l limit]` | List recent eval entries |
430
+ | `jfl eval trajectory -a <agent>` | Composite score trajectory with sparkline |
431
+ | `jfl eval log -a <agent> -m <metrics>` | Log an eval entry |
432
+ | `jfl eval compare` | Side-by-side agent comparison |
433
+ | `jfl eval tuples [--limit N] [--format json]` | Extract training tuples from journals |
434
+
435
+ ### Portfolio
436
+
437
+ | Command | Description |
438
+ |---------|-------------|
439
+ | `jfl portfolio register <path>` | Register GTM workspace in portfolio |
440
+ | `jfl portfolio list` | List child GTMs with health status |
441
+ | `jfl portfolio unregister <name>` | Remove GTM from portfolio |
442
+ | `jfl portfolio status` | Portfolio health and eval summary |
443
+ | `jfl portfolio phone-home` | Report GTM health to portfolio parent |
444
+
263
445
  ### Memory
264
446
 
265
447
  | Command | Description |
@@ -302,6 +484,22 @@ jfl services # Interactive TUI (no args)
302
484
  | `jfl dashboard` | Interactive service monitoring TUI |
303
485
  | `jfl events [-p pattern]` | Live MAP event bus dashboard |
304
486
 
487
+ ### Hooks & Flows
488
+
489
+ | Command | Description |
490
+ |---------|-------------|
491
+ | `jfl hooks init` | Generate HTTP hooks + default flows |
492
+ | `jfl hooks status` | Check hooks and hub connectivity |
493
+ | `jfl hooks remove` | Remove HTTP hooks |
494
+ | `jfl hooks deploy` | Deploy hooks to all registered services |
495
+ | `jfl flows list` | List configured event-action flows |
496
+ | `jfl flows add` | Interactive flow builder |
497
+ | `jfl flows test <name>` | Test a flow with synthetic event |
498
+ | `jfl flows enable/disable <name>` | Toggle flows |
499
+ | `jfl scope list` | View service context scopes |
500
+ | `jfl scope set` | Set scope declarations |
501
+ | `jfl scope test` | Test scope enforcement |
502
+
305
503
  ### Platform
306
504
 
307
505
  | Command | Description |
@@ -319,7 +517,7 @@ jfl services # Interactive TUI (no args)
319
517
  |---------|-------------|
320
518
  | `jfl telemetry status` | Show telemetry status |
321
519
  | `jfl telemetry show` | Show queued events |
322
- | `jfl telemetry digest [--hours N] [--format json] [--platform]` | Cost breakdown, health analysis, suggestions |
520
+ | `jfl telemetry digest [--hours N] [--format json] [--plots]` | Cost breakdown, health analysis, terminal charts |
323
521
  | `jfl telemetry reset` | Reset install ID |
324
522
  | `jfl telemetry track --category <c> --event <e>` | Emit event from shell scripts |
325
523
  | `jfl improve [--dry-run] [--auto] [--hours N]` | Self-improvement loop: analyze, suggest, create issues |
@@ -327,7 +525,7 @@ jfl services # Interactive TUI (no args)
327
525
 
328
526
  **Model cost tracking:** Every Stratus API call emits token counts and estimated cost. Covers claude-opus-4-6, claude-sonnet-4-6, claude-sonnet-4-5, claude-haiku-3-5, gpt-4o.
329
527
 
330
- **`jfl telemetry digest`** analyzes local events: per-model cost tables, command stats, error rates, hub/memory/session health. Flags issues like high MCP latency, cost concentration, crash rates.
528
+ **`jfl telemetry digest`** analyzes local events: per-model cost tables, command stats, error rates, hub/memory/session health. `--plots` renders bar charts via kuva (falls back to ASCII).
331
529
 
332
530
  **`jfl improve`** generates actionable suggestions from the digest. `--dry-run` previews, `--auto` creates GitHub issues tagged `[jfl-improve]`.
333
531
 
@@ -461,14 +659,16 @@ Every session MUST write journal entries. Hooks enforce this.
461
659
  "summary": "Built jfl onboard command that registers service repos in GTM",
462
660
  "detail": "Creates agent definition, skill wrapper, services.json entry...",
463
661
  "files": ["src/commands/onboard.ts"],
464
- "incomplete": ["peer sync not wired"],
465
- "next": "Wire phone-home on session end"
662
+ "hypothesis": "Structured onboarding reduces setup errors",
663
+ "outcome": "confirmed",
664
+ "score_delta": 0.12,
665
+ "eval_snapshot": {"composite": 0.85}
466
666
  }
467
667
  ```
468
668
 
469
669
  **Write entries when:** Feature completed, decision made, bug fixed, milestone reached, session ending.
470
670
 
471
- Entries become searchable via `jfl memory search` and MCP `memory_search` tool.
671
+ Entries become searchable via `jfl memory search` and MCP `memory_search` tool. RL fields (`hypothesis`, `outcome`, `score_delta`, `eval_snapshot`, `diff_hash`, `context_entries`) enable trajectory-based learning.
472
672
 
473
673
  ---
474
674
 
@@ -488,13 +688,63 @@ SessionStart hook fires You work normally Stop hook fire
488
688
  ├─ Serves MCP tools to Claude Code
489
689
  ├─ Aggregates journal + knowledge + code
490
690
  ├─ Bridges service events from file-drop
491
- └─ Watches journal/ for live entries
691
+ ├─ Watches journal/ for live entries
692
+ ├─ Portfolio mode: fans out to child hubs
693
+ ├─ Flow engine: reactive trigger→action
694
+ └─ Web dashboard at /dashboard/
492
695
  ```
493
696
 
494
697
  **Everything is files.** No proprietary database. No lock-in. Context is git-native — version controlled, portable, model-agnostic.
495
698
 
496
699
  ---
497
700
 
701
+ ## CI/CD
702
+
703
+ Two GitHub Actions workflows handle quality and releases.
704
+
705
+ ### CI — `.github/workflows/ci.yml`
706
+
707
+ Runs on every push and PR to `main`:
708
+
709
+ - TypeScript strict mode type checking
710
+ - Full test suite (~365 tests across 17 test files)
711
+ - Coverage report uploaded as artifact
712
+
713
+ ### CD — `.github/workflows/release.yml`
714
+
715
+ Fires after CI passes on `main`. Uses [Changesets](https://github.com/changesets/changesets) for version management and npm Trusted Publisher (OIDC) for secretless publishing.
716
+
717
+ **Auto-changeset generation:** `scripts/generate-changesets.sh` converts conventional commit messages into changesets automatically:
718
+ - `feat:` = minor bump
719
+ - `fix:` = patch bump
720
+ - `feat!:` = major bump
721
+
722
+ No manual `npx changeset` needed for most changes.
723
+
724
+ **Release flow:**
725
+
726
+ ```bash
727
+ # Option A: Manual changeset
728
+ npx changeset # pick bump level, write summary
729
+
730
+ # Option B: Just use conventional commits — auto-generated on CI
731
+
732
+ # Push to main — CI runs, then release.yml fires
733
+ # → changesets/action creates a "Version Packages" PR
734
+
735
+ # Merge the Version PR
736
+ # → release.yml fires again → npm publish --provenance --access public
737
+ ```
738
+
739
+ No `NPM_TOKEN` needed. Publishing uses OIDC provenance via npm Trusted Publisher.
740
+
741
+ **One-time setup (per package):**
742
+ > npmjs.com > `jfl` package > Settings > Publish Access > Add Provenance
743
+ > - Repository: `402goose/jfl-cli`
744
+ > - Workflow: `.github/workflows/release.yml`
745
+
746
+ ---
747
+
498
748
  ## Auto-Update
499
749
 
500
750
  JFL checks for npm updates on session start (24-hour cache):
@@ -530,6 +780,20 @@ jfl wallet # Wallet and day pass status
530
780
 
531
781
  ## What's New
532
782
 
783
+ **0.3.0**
784
+ - Feat: **Portfolio workspace type** — `jfl portfolio register/list/unregister/status/phone-home`. Portfolios contain multiple GTM workspaces with cross-product event routing via SSE, context scope enforcement (produces/consumes/denied), fan-out queries to child hubs, and portfolio-level leaderboard aggregation
785
+ - Feat: **Dashboard V2** — pre-built Vite + Preact + Tailwind SPA served at `/dashboard/`. Pages: Overview (activity charts, metric cards), Journal (search + type filters), Events (pattern filter presets), Services (type badges, context scope, data flows), Flows (definitions + execution history), Health (system metrics, memory index), Agents (eval leaderboards grouped by domain)
786
+ - Feat: **Eval framework** — `jfl eval list/trajectory/log/compare/tuples`. Track agent metrics over time with composite scores, dual-write up parent chain, extract (state, action, reward) training tuples. Agents grouped by metric domain (ProductRank: ndcg@10/mrr/precision@5, SEO: avg_rank/keywords_ranked)
787
+ - Feat: **RL infrastructure (Phase 1)** — `JournalEntry` type with 6 RL fields, `TrajectoryLoader` for querying experiment history, `query_experiment_history` MCP tool
788
+ - Feat: **Flow engine** — declarative trigger-action automation in `.jfl/flows.yaml`. Actions: log, emit, journal, webhook, command, spawn. Gates: time-gated, deadline, requires_approval. Template interpolation with `{{child.NAME.port}}`
789
+ - Feat: **HTTP hooks** — Claude Code lifecycle hooks (PostToolUse, Stop, PreCompact, SubagentStart/Stop) POST to Context Hub. `jfl hooks init/status/remove/deploy`
790
+ - Feat: **Context scope enforcement** — produces/consumes/denied patterns. Event bus filters by scope declarations. `jfl scope list/set/test`
791
+ - Feat: CI/CD pipeline — GitHub Actions CI (strict TypeScript + Jest gate) + CD via Changesets with auto-generation from conventional commits. npm Trusted Publisher with OIDC provenance
792
+ - Feat: Service agent templates (CLAUDE.md, settings.json, knowledge docs)
793
+ - Feat: Session cleanup guard — prevents `rm -rf` on main when no worktrees exist
794
+ - Fix: TypeScript strict mode build errors resolved
795
+ - Test: ~365 tests across 17 test files (up from 237)
796
+
533
797
  **0.2.5**
534
798
  - Feat: Docker-style grouped `jfl --help` — 5 groups (Getting Started, Daily Use, Management, Platform, Advanced), ~30 lines down from 52
535
799
  - Feat: `jfl doctor [--fix]` — unified project health checker (9 checks: .jfl dir, config, Context Hub, hooks, memory, journal, agents, flows, git). Auto-repairs hooks, config, and journal with `--fix`
@@ -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
  });