@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 +56 -6
- package/dist/cli.js +14 -7
- package/dist/mcp.js +11 -5
- package/package.json +1 -1
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:**
|
|
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/`
|
|
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] [--
|
|
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
|
-
**
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|