@storybloq/storybloq 1.4.0 → 1.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.
package/README.md CHANGED
@@ -40,7 +40,7 @@ The real cost isn't wasted setup time. It's repeated mistakes, relitigated desig
40
40
  Every project gets a `.story/` directory of JSON and markdown files. Tickets, issues, roadmap phases, session handovers, and lessons learned all live there, tracked by git, readable by any AI.
41
41
 
42
42
  - **CLI:** `storybloq` - inspect and mutate `.story/` from the terminal.
43
- - **MCP server:** 49 tools Claude Code and Codex can call directly, no subprocess spawning.
43
+ - **MCP server:** 53 tools Claude Code and Codex can call directly, no subprocess spawning.
44
44
  - **Skill:** `/story` in Claude Code or `$story` in Codex loads project state at the start of every session.
45
45
  - **Mac app:** native sidebar that watches `.story/` and updates live while your AI client works (separate product, free on the App Store).
46
46
 
@@ -82,6 +82,8 @@ cd your-project
82
82
  storybloq init --name "your-project"
83
83
  ```
84
84
 
85
+ For multi-repo projects, see [Federation](#federation) below.
86
+
85
87
  That scaffolds:
86
88
 
87
89
  ```
@@ -117,6 +119,36 @@ Outside Claude Code, the same state is one `storybloq` invocation away.
117
119
  <img src="https://raw.githubusercontent.com/Storybloq/storybloq/main/assets/autonomous.png" alt="Autonomous mode running a ticket through plan, implement, test, review" />
118
120
  </p>
119
121
 
122
+ ## Federation
123
+
124
+ Federation coordinates AI agent work across multiple repos. One project becomes the orchestrator. It declares which repos (nodes) are part of the system, how they depend on each other, and how they communicate at runtime. Each node keeps its own `.story/` with its own tickets, issues, and handovers. The orchestrator reads across all of them.
125
+
126
+ ```bash
127
+ # Create an orchestrator
128
+ storybloq init --type orchestrator --name "my-platform"
129
+
130
+ # Register nodes
131
+ storybloq node add api --path ../api --stack typescript --role "REST backend"
132
+ storybloq node add web --path ../web --stack nextjs --depends-on api
133
+ storybloq node add sdk --path ../sdk --stack typescript
134
+ ```
135
+
136
+ Three relationship types connect nodes:
137
+
138
+ - **`dependsOn`** on node config: build-order edges. The web app depends on the API.
139
+ - **`links`** on node config: runtime integration. The web app calls the API over HTTP.
140
+ - **`crossNodeBlockedBy`** on tickets: a ticket in one repo is blocked until a ticket in another repo is complete. Example: `"crossNodeBlockedBy": ["api:T-012"]`.
141
+
142
+ From the orchestrator directory:
143
+
144
+ ```bash
145
+ storybloq status # aggregated view across all nodes
146
+ storybloq recommend # federation-aware suggestions (bottlenecks, stale nodes, blockers)
147
+ storybloq ticket list --node api # list tickets in the api node without cd-ing
148
+ ```
149
+
150
+ The recommendation engine generates federation-specific suggestions: nodes blocking downstream work, bottleneck nodes depended on by many others, nodes with no handover in two weeks. Tickets with `crossNodeBlockedBy` refs never surface in recommendations until the blocking ticket is complete.
151
+
120
152
  ## CLI reference
121
153
 
122
154
  All commands accept `--format json|md` (default `md`). Pipe JSON through `jq` for scripting, read the markdown variant directly.
@@ -125,7 +157,7 @@ All commands accept `--format json|md` (default `md`). Pipe JSON through `jq` fo
125
157
 
126
158
  | Command | Description |
127
159
  |---------|-------------|
128
- | `storybloq init [--name] [--force]` | Scaffold `.story/` in the current directory |
160
+ | `storybloq init [--name] [--type orchestrator] [--force]` | Scaffold `.story/` (add `--type orchestrator` for multi-repo) |
129
161
  | `storybloq status` | Project summary with phase statuses, counts, and risks |
130
162
  | `storybloq validate` | Reference integrity + schema checks |
131
163
  | `storybloq setup --client claude\|codex\|all [--skip-hooks]` | Install Storybloq skills, register MCP, and configure client hooks |
@@ -152,8 +184,8 @@ All commands accept `--format json|md` (default `md`). Pipe JSON through `jq` fo
152
184
  | `storybloq ticket get <id>` | Full ticket detail |
153
185
  | `storybloq ticket next` | Highest-priority unblocked ticket |
154
186
  | `storybloq ticket blocked` | All currently blocked tickets |
155
- | `storybloq ticket create --title --type --phase [--description] [--blocked-by] [--parent-ticket]` | Create |
156
- | `storybloq ticket update <id> [--status] [--title] [--phase] [--order] ...` | Update |
187
+ | `storybloq ticket create --title --type --phase [--description] [--blocked-by] [--parent-ticket] [--node <name>]` | Create (use `--node` from orchestrator) |
188
+ | `storybloq ticket update <id> [--status] [--title] [--phase] [--cross-node-blocked-by] [--node <name>] ...` | Update |
157
189
  | `storybloq ticket meta get\|set\|unset <id> [path] [value]` | Manage custom passthrough metadata |
158
190
  | `storybloq ticket delete <id> [--force]` | Delete |
159
191
 
@@ -186,6 +218,17 @@ All commands accept `--format json|md` (default `md`). Pipe JSON through `jq` fo
186
218
  | `storybloq snapshot` · `storybloq recap` | Capture state and diff against the last snapshot |
187
219
  | `storybloq export [--phase <id>] [--all] [--format json\|md]` | Self-contained project document |
188
220
 
221
+ ### Federation (orchestrator projects)
222
+
223
+ | Command | Description |
224
+ |---------|-------------|
225
+ | `storybloq init --type orchestrator` | Scaffold an orchestrator `.story/` with a nodes map |
226
+ | `storybloq node add <name> --path <dir> [--stack] [--role] [--depends-on] [--link]` | Register a node repo |
227
+ | `storybloq node remove <name> [--force \| --prune]` | Unregister a node (checks for dependents first) |
228
+ | `storybloq node update <name> [--stack] [--role] [--depends-on] [--health]` | Update node metadata |
229
+ | `storybloq node list` | Table of all configured nodes |
230
+ | `storybloq config set-federation --allow-node-writes` | Allow orchestrator to write into node repos |
231
+
189
232
  ## MCP server reference
190
233
 
191
234
  Register with Claude Code or Codex (done automatically by setup):
@@ -197,7 +240,7 @@ codex mcp add storybloq --env STORYBLOQ_CLIENT=codex -- storybloq --mcp
197
240
 
198
241
  The server imports the same TypeScript modules as the CLI directly, so there's no subprocess overhead. It auto-discovers the project root by walking up from the working directory to the nearest `.story/` parent.
199
242
 
200
- **49 tools** grouped by responsibility:
243
+ **53 tools** grouped by responsibility:
201
244
 
202
245
  ### Read (no side effects)
203
246
 
@@ -215,6 +258,12 @@ The server imports the same TypeScript modules as the CLI directly, so there's n
215
258
 
216
259
  `storybloq_session_report` · `storybloq_register_subprocess` · `storybloq_unregister_subprocess` surface session health to the Mac app.
217
260
 
261
+ ### Federation (orchestrator projects)
262
+
263
+ `storybloq_node_init` bootstraps `.story/` in a node repo from the orchestrator context.
264
+
265
+ `storybloq_node_add` · `storybloq_node_list` · `storybloq_node_update` manage the orchestrator's node registry.
266
+
218
267
  <p align="center">
219
268
  <img src="https://raw.githubusercontent.com/Storybloq/storybloq/main/assets/handover.png" alt="Handover timeline with AI-summarized date groups" />
220
269
  </p>
@@ -282,7 +331,8 @@ Full type definitions ship with the package (`exports.types`).
282
331
  "createdDate": "2026-04-12",
283
332
  "completedDate": null,
284
333
  "blockedBy": [],
285
- "parentTicket": null
334
+ "parentTicket": null,
335
+ "crossNodeBlockedBy": []
286
336
  }
287
337
  ```
288
338
 
package/dist/cli.js CHANGED
@@ -13996,10 +13996,11 @@ var init_types3 = __esm({
13996
13996
  * Updates the internal snapshot so subsequent reads via `this.state` are consistent.
13997
13997
  */
13998
13998
  writeState(updates, opts) {
13999
+ const prevState = this._state.state;
13999
14000
  const merged = { ...this._state, ...updates };
14000
14001
  const written = writeSessionSync(this.dir, merged);
14001
14002
  this._state = written;
14002
- if (opts?.refreshStatus) {
14003
+ if (opts?.refreshStatus || written.state !== prevState) {
14003
14004
  try {
14004
14005
  refreshStatusForSession(this.root, this.dir, written, "guide");
14005
14006
  } catch {
@@ -17570,7 +17571,7 @@ function getInstalledVersion() {
17570
17571
  }
17571
17572
  }
17572
17573
  function getRunningVersion() {
17573
- return "1.4.0";
17574
+ return "1.4.2";
17574
17575
  }
17575
17576
  var init_version_check = __esm({
17576
17577
  "src/autonomous/version-check.ts"() {
@@ -18612,7 +18613,12 @@ async function runPipelineStage(root, dir, state, report, recipe) {
18612
18613
  }
18613
18614
  const ctx = new StageContext(root, dir, state, recipe);
18614
18615
  const advance = await stage.report(ctx, report);
18615
- return processAdvance(ctx, stage, advance);
18616
+ const result = await processAdvance(ctx, stage, advance);
18617
+ try {
18618
+ refreshStatusForSession(root, dir, ctx.state, "guide");
18619
+ } catch {
18620
+ }
18621
+ return result;
18616
18622
  }
18617
18623
  async function handleReport(root, args) {
18618
18624
  if (!args.sessionId) return guideError(new Error("sessionId is required for report action"));
@@ -19857,7 +19863,7 @@ function registerAllTools(server, pinnedRoot) {
19857
19863
  const result = await runMcpReadTool(pinnedRoot, handleStatus);
19858
19864
  try {
19859
19865
  const { readUpdateCacheSync: readUpdateCacheSync2, refreshUpdateCacheInBackground: refreshUpdateCacheInBackground2 } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
19860
- const running = "1.4.0";
19866
+ const running = "1.4.2";
19861
19867
  const info = readUpdateCacheSync2(running);
19862
19868
  refreshUpdateCacheInBackground2();
19863
19869
  if (info?.updateAvailable && result.content[0]?.type === "text") {
@@ -21500,7 +21506,7 @@ var init_mcp = __esm({
21500
21506
  ENV_VAR2 = "STORYBLOQ_PROJECT_ROOT";
21501
21507
  LEGACY_ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
21502
21508
  CONFIG_PATH2 = ".story/config.json";
21503
- version = "1.4.0";
21509
+ version = "1.4.2";
21504
21510
  main().catch((err) => {
21505
21511
  process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
21506
21512
  `);
@@ -24422,7 +24428,8 @@ var init_setup_skill = __esm({
24422
24428
  "storybloq_recap",
24423
24429
  "storybloq_recommend",
24424
24430
  "storybloq_export",
24425
- "storybloq_session_report"
24431
+ "storybloq_session_report",
24432
+ "storybloq_node_list"
24426
24433
  ];
24427
24434
  }
24428
24435
  });
@@ -28600,7 +28607,7 @@ async function runCli() {
28600
28607
  registerMigrateCommand: registerMigrateCommand2,
28601
28608
  registerNodeCommand: registerNodeCommand2
28602
28609
  } = await Promise.resolve().then(() => (init_register(), register_exports));
28603
- const version2 = "1.4.0";
28610
+ const version2 = "1.4.2";
28604
28611
  const { preCommandHousekeeping: preCommandHousekeeping2 } = await Promise.resolve().then(() => (init_housekeeping(), housekeeping_exports));
28605
28612
  await preCommandHousekeeping2(version2);
28606
28613
  class HandledError extends Error {
package/dist/mcp.js CHANGED
@@ -13295,10 +13295,11 @@ var StageContext = class {
13295
13295
  * Updates the internal snapshot so subsequent reads via `this.state` are consistent.
13296
13296
  */
13297
13297
  writeState(updates, opts) {
13298
+ const prevState = this._state.state;
13298
13299
  const merged = { ...this._state, ...updates };
13299
13300
  const written = writeSessionSync(this.dir, merged);
13300
13301
  this._state = written;
13301
- if (opts?.refreshStatus) {
13302
+ if (opts?.refreshStatus || written.state !== prevState) {
13302
13303
  try {
13303
13304
  refreshStatusForSession(this.root, this.dir, written, "guide");
13304
13305
  } catch {
@@ -16742,7 +16743,7 @@ function getInstalledVersion() {
16742
16743
  }
16743
16744
  }
16744
16745
  function getRunningVersion() {
16745
- return "1.4.0";
16746
+ return "1.4.2";
16746
16747
  }
16747
16748
 
16748
16749
  // src/autonomous/guide.ts
@@ -17802,7 +17803,12 @@ async function runPipelineStage(root, dir, state, report, recipe) {
17802
17803
  }
17803
17804
  const ctx = new StageContext(root, dir, state, recipe);
17804
17805
  const advance = await stage.report(ctx, report);
17805
- return processAdvance(ctx, stage, advance);
17806
+ const result = await processAdvance(ctx, stage, advance);
17807
+ try {
17808
+ refreshStatusForSession(root, dir, ctx.state, "guide");
17809
+ } catch {
17810
+ }
17811
+ return result;
17806
17812
  }
17807
17813
  async function handleReport(root, args) {
17808
17814
  if (!args.sessionId) return guideError(new Error("sessionId is required for report action"));
@@ -18747,7 +18753,7 @@ function registerAllTools(server, pinnedRoot) {
18747
18753
  const result = await runMcpReadTool(pinnedRoot, handleStatus);
18748
18754
  try {
18749
18755
  const { readUpdateCacheSync: readUpdateCacheSync2, refreshUpdateCacheInBackground: refreshUpdateCacheInBackground2 } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
18750
- const running = "1.4.0";
18756
+ const running = "1.4.2";
18751
18757
  const info = readUpdateCacheSync2(running);
18752
18758
  refreshUpdateCacheInBackground2();
18753
18759
  if (info?.updateAvailable && result.content[0]?.type === "text") {
@@ -20106,7 +20112,7 @@ async function trimFailedDirectory(inboxPath) {
20106
20112
  var ENV_VAR2 = "STORYBLOQ_PROJECT_ROOT";
20107
20113
  var LEGACY_ENV_VAR2 = "CLAUDESTORY_PROJECT_ROOT";
20108
20114
  var CONFIG_PATH2 = ".story/config.json";
20109
- var version = "1.4.0";
20115
+ var version = "1.4.2";
20110
20116
  function tryDiscoverRoot() {
20111
20117
  const envRoot = process.env[ENV_VAR2] ?? process.env[LEGACY_ENV_VAR2];
20112
20118
  const envName = process.env[ENV_VAR2] ? ENV_VAR2 : LEGACY_ENV_VAR2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@storybloq/storybloq",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "license": "PolyForm-Noncommercial-1.0.0",
5
5
  "description": "Project memory for Claude Code. Storybloq stores tickets, issues, handovers, and lessons in a .story/ folder inside your repo so every session starts where the last one left off. Pairs with the Storybloq Mac app on the App Store.",
6
6
  "homepage": "https://storybloq.com",