@crouton-kit/crouter 0.3.20 → 0.3.22
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/builtin-memory/crouter-development/marketplaces.md +2 -4
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +3 -5
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +3 -6
- package/dist/builtin-memory/crouter-development/personas.md +2 -6
- package/dist/builtin-memory/crouter-development/plugins.md +2 -4
- package/dist/builtin-memory/design.md +2 -4
- package/dist/builtin-memory/development.md +3 -3
- package/dist/builtin-memory/planning.md +3 -4
- package/dist/builtin-memory/spec.md +3 -10
- package/dist/builtin-personas/orchestration-kernel.md +1 -1
- package/dist/commands/canvas-history/read.d.ts +1 -0
- package/dist/commands/canvas-history/read.js +61 -0
- package/dist/commands/canvas-history/search.d.ts +1 -0
- package/dist/commands/canvas-history/search.js +270 -0
- package/dist/commands/canvas-history/show.d.ts +1 -0
- package/dist/commands/canvas-history/show.js +79 -0
- package/dist/commands/canvas-history.d.ts +2 -0
- package/dist/commands/canvas-history.js +33 -0
- package/dist/commands/canvas.js +3 -2
- package/dist/commands/human/queue.js +2 -2
- package/dist/commands/memory/__tests__/lint-schema.test.js +29 -0
- package/dist/commands/memory/find.js +14 -52
- package/dist/commands/memory/lint.d.ts +7 -0
- package/dist/commands/memory/lint.js +29 -35
- package/dist/commands/memory/list.js +8 -18
- package/dist/commands/memory/read.js +4 -37
- package/dist/commands/memory/shared.js +1 -2
- package/dist/commands/memory/write.js +6 -9
- package/dist/commands/node.js +47 -25
- package/dist/commands/pkg/bridge.d.ts +1 -0
- package/dist/commands/pkg/bridge.js +137 -0
- package/dist/commands/pkg/plugin-inspect.js +7 -6
- package/dist/commands/pkg.js +2 -1
- package/dist/commands/push.js +10 -15
- package/dist/commands/skill/author.js +35 -44
- package/dist/commands/skill/shared.d.ts +1 -5
- package/dist/commands/skill/shared.js +9 -63
- package/dist/commands/skill.js +5 -6
- package/dist/commands/sys/doctor.js +6 -144
- package/dist/commands/tmux-spread.js +16 -1
- package/dist/commands/view-cycle.js +9 -5
- package/dist/commands/view-run.js +3 -2
- package/dist/core/__tests__/broker-dormant-wake.test.js +71 -34
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +21 -5
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +21 -5
- package/dist/core/__tests__/child-death-wake.test.js +133 -185
- package/dist/core/__tests__/context-intro.test.js +23 -27
- package/dist/core/__tests__/daemon-liveness.test.js +28 -258
- package/dist/core/__tests__/flagship-lifecycle.test.js +156 -135
- package/dist/core/__tests__/frame-decoder-perf.test.js +51 -25
- package/dist/core/__tests__/{broker-attach-limits.test.js → full/broker-attach-limits.test.js} +62 -35
- package/dist/core/__tests__/{broker-attach-stream.test.js → full/broker-attach-stream.test.js} +54 -31
- package/dist/core/__tests__/full/broker-crash-teardown.test.js +143 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +153 -0
- package/dist/core/__tests__/full/broker-lifecycle.test.js +116 -0
- package/dist/core/__tests__/{cascade-close.test.js → full/cascade-close.test.js} +4 -3
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +292 -0
- package/dist/core/__tests__/{dead-pane-regression.test.js → full/dead-pane-regression.test.js} +2 -2
- package/dist/core/__tests__/{detach-focus.test.js → full/detach-focus.test.js} +7 -7
- package/dist/core/__tests__/{human-new-window-regression.test.js → full/human-new-window-regression.test.js} +2 -2
- package/dist/core/__tests__/{placement-focus.test.js → full/placement-focus.test.js} +5 -5
- package/dist/core/__tests__/{placement-reconcile.test.js → full/placement-reconcile.test.js} +3 -3
- package/dist/core/__tests__/{placement-revive.test.js → full/placement-revive.test.js} +5 -5
- package/dist/core/__tests__/{placement-teardown.test.js → full/placement-teardown.test.js} +4 -4
- package/dist/core/__tests__/{review-render-pane-regression.test.js → full/review-render-pane-regression.test.js} +2 -2
- package/dist/core/__tests__/full/spike-harness.test.d.ts +1 -0
- package/dist/core/__tests__/full/spike-harness.test.js +117 -0
- package/dist/core/__tests__/grace-clock.test.js +72 -75
- package/dist/core/__tests__/helpers/harness.d.ts +35 -1
- package/dist/core/__tests__/helpers/harness.js +70 -12
- package/dist/core/__tests__/live-mutation-verbs.test.js +100 -105
- package/dist/core/__tests__/live-mutation.test.js +111 -134
- package/dist/core/__tests__/memory.test.js +4 -4
- package/dist/core/__tests__/revive.test.js +96 -86
- package/dist/core/__tests__/subscription-delivery.test.js +116 -138
- package/dist/core/__tests__/wake-origin.test.js +54 -50
- package/dist/core/bootstrap.js +18 -14
- package/dist/core/bridge-map.d.ts +19 -0
- package/dist/core/bridge-map.js +73 -0
- package/dist/core/canvas/history.d.ts +91 -0
- package/dist/core/canvas/history.js +389 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/config.js +3 -22
- package/dist/core/help.d.ts +2 -2
- package/dist/core/help.js +1 -1
- package/dist/core/memory-resolver.d.ts +9 -2
- package/dist/core/memory-resolver.js +47 -7
- package/dist/core/render.d.ts +4 -3
- package/dist/core/render.js +38 -41
- package/dist/core/resolver.d.ts +1 -30
- package/dist/core/resolver.js +6 -546
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +2 -2
- package/dist/core/runtime/persona.js +5 -7
- package/dist/core/scope.d.ts +7 -5
- package/dist/core/scope.js +16 -17
- package/dist/core/substrate/ceiling.d.ts +28 -0
- package/dist/core/substrate/ceiling.js +87 -0
- package/dist/core/substrate/index.d.ts +2 -0
- package/dist/core/substrate/index.js +1 -0
- package/dist/core/substrate/on-read.js +18 -2
- package/dist/core/substrate/render.d.ts +3 -5
- package/dist/core/substrate/render.js +68 -59
- package/dist/core/substrate/schema.d.ts +18 -13
- package/dist/core/substrate/schema.js +12 -11
- package/dist/pi-extensions/canvas-context-intro.js +5 -3
- package/dist/pi-extensions/canvas-inbox-watcher.js +19 -2
- package/dist/types.d.ts +3 -18
- package/dist/types.js +0 -8
- package/package.json +4 -3
- package/dist/builtin-skills/.crouter-plugin/plugin.json +0 -5
- package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +0 -157
- package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +0 -106
- package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +0 -49
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +0 -49
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +0 -156
- package/dist/builtin-skills/skills/design/SKILL.md +0 -51
- package/dist/builtin-skills/skills/development/SKILL.md +0 -109
- package/dist/builtin-skills/skills/planning/SKILL.md +0 -59
- package/dist/builtin-skills/skills/spec/SKILL.md +0 -83
- package/dist/commands/skill/state.d.ts +0 -3
- package/dist/commands/skill/state.js +0 -71
- package/dist/core/__tests__/broker-crash-teardown.test.js +0 -116
- package/dist/core/__tests__/broker-dialogs.test.js +0 -126
- package/dist/core/__tests__/broker-lifecycle.test.js +0 -87
- package/dist/core/__tests__/resolver.test.js +0 -181
- package/dist/core/__tests__/spike-harness.test.js +0 -242
- /package/dist/{core/__tests__/broker-attach-limits.test.d.ts → commands/memory/__tests__/lint-schema.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-attach-stream.test.d.ts → full/broker-attach-limits.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-crash-teardown.test.d.ts → full/broker-attach-stream.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-dialogs.test.d.ts → full/broker-crash-teardown.test.d.ts} +0 -0
- /package/dist/core/__tests__/{broker-lifecycle.test.d.ts → full/broker-dialogs.test.d.ts} +0 -0
- /package/dist/core/__tests__/{cascade-close.test.d.ts → full/broker-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{dead-pane-regression.test.d.ts → full/cascade-close.test.d.ts} +0 -0
- /package/dist/core/__tests__/{detach-focus.test.d.ts → full/daemon-liveness-pane.full.test.d.ts} +0 -0
- /package/dist/core/__tests__/{human-new-window-regression.test.d.ts → full/dead-pane-regression.test.d.ts} +0 -0
- /package/dist/core/__tests__/{placement-focus.test.d.ts → full/detach-focus.test.d.ts} +0 -0
- /package/dist/core/__tests__/{placement-reconcile.test.d.ts → full/human-new-window-regression.test.d.ts} +0 -0
- /package/dist/core/__tests__/{placement-revive.test.d.ts → full/placement-focus.test.d.ts} +0 -0
- /package/dist/core/__tests__/{placement-teardown.test.d.ts → full/placement-reconcile.test.d.ts} +0 -0
- /package/dist/core/__tests__/{resolver.test.d.ts → full/placement-revive.test.d.ts} +0 -0
- /package/dist/core/__tests__/{review-render-pane-regression.test.d.ts → full/placement-teardown.test.d.ts} +0 -0
- /package/dist/core/__tests__/{spike-harness.test.d.ts → full/review-render-pane-regression.test.d.ts} +0 -0
package/dist/commands/node.js
CHANGED
|
@@ -164,7 +164,14 @@ const nodeNew = defineLeaf({
|
|
|
164
164
|
: childFollowUp(parent ?? process.env['CRTR_NODE_ID']),
|
|
165
165
|
};
|
|
166
166
|
},
|
|
167
|
-
render: (r) =>
|
|
167
|
+
render: (r) => {
|
|
168
|
+
const win = r['window'] ? ` in window ${r['window']}` : '';
|
|
169
|
+
const lines = [`Spawned "${r['name']}" (${r['node_id']}) — ${r['status']}${win}.`];
|
|
170
|
+
if (r['session'])
|
|
171
|
+
lines.push(`- session: ${r['session']}`);
|
|
172
|
+
lines.push('', String(r['follow_up']));
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
},
|
|
168
175
|
});
|
|
169
176
|
// ---------------------------------------------------------------------------
|
|
170
177
|
// node list — the active canvas (or a status slice)
|
|
@@ -352,9 +359,16 @@ const nodeRecycle = defineLeaf({
|
|
|
352
359
|
const res = await recycleNode(id, pane);
|
|
353
360
|
return { recycled: res.recycled, node_id: id, finalized: res.finalized, delivered: res.delivered.length, new_root: res.newRoot ?? undefined };
|
|
354
361
|
},
|
|
355
|
-
render: (r) =>
|
|
356
|
-
|
|
357
|
-
|
|
362
|
+
render: (r) => {
|
|
363
|
+
if (r['recycled'] !== true)
|
|
364
|
+
return `Recycle failed for ${r['node_id'] ?? '?'} — not in tmux, or no agent in this pane.`;
|
|
365
|
+
const lines = [`Recycled the pane — finished ${r['node_id']}.`, `- finalized: ${r['finalized']}`];
|
|
366
|
+
if (r['finalized'] === true)
|
|
367
|
+
lines.push(`- delivered: ${r['delivered']}`);
|
|
368
|
+
if (r['new_root'])
|
|
369
|
+
lines.push(`- new root: ${r['new_root']}`);
|
|
370
|
+
return lines.join('\n');
|
|
371
|
+
},
|
|
358
372
|
});
|
|
359
373
|
// ---------------------------------------------------------------------------
|
|
360
374
|
// node close — close a node + cascade-cancel its exclusive subtree (Alt+C → x)
|
|
@@ -398,9 +412,12 @@ const nodeClose = defineLeaf({
|
|
|
398
412
|
const res = closeNode(id);
|
|
399
413
|
return { closed: true, node_id: res.root, count: res.closed.length, closed_ids: res.closed, spared: res.spared };
|
|
400
414
|
},
|
|
401
|
-
render: (r) =>
|
|
402
|
-
|
|
403
|
-
|
|
415
|
+
render: (r) => {
|
|
416
|
+
if (r['closed'] !== true)
|
|
417
|
+
return 'Close failed — no node found in this pane to close.';
|
|
418
|
+
const spared = r['spared']?.length ?? 0;
|
|
419
|
+
return `Closed ${r['node_id']} and its exclusive subtree — ${r['count']} node(s) closed${spared > 0 ? `, ${spared} spared (still managed from outside the subtree)` : ''}.`;
|
|
420
|
+
},
|
|
404
421
|
});
|
|
405
422
|
// ---------------------------------------------------------------------------
|
|
406
423
|
// node cycle — DFS-walk the canvas one window at a time (Alt+] / Alt+[)
|
|
@@ -486,8 +503,8 @@ const nodeCycle = defineLeaf({
|
|
|
486
503
|
return { focused: res.focused, node_id: targetId, name: target.name, from: fromId };
|
|
487
504
|
},
|
|
488
505
|
render: (r) => r['focused'] === true
|
|
489
|
-
?
|
|
490
|
-
:
|
|
506
|
+
? `Cycled to "${r['name'] ?? ''}" (${r['node_id']}) — from ${r['from'] ?? '?'}.`
|
|
507
|
+
: 'No other live node to focus.',
|
|
491
508
|
});
|
|
492
509
|
// ---------------------------------------------------------------------------
|
|
493
510
|
// node msg — direct-address any node at a wake tier (wakes a dormant target)
|
|
@@ -582,7 +599,7 @@ const nodeSubscribe = defineLeaf({
|
|
|
582
599
|
subscribe(subscriber, publisher, !passive);
|
|
583
600
|
return { subscribed: true, subscriber, publisher, mode: passive ? 'passive' : 'active' };
|
|
584
601
|
},
|
|
585
|
-
render: (r) =>
|
|
602
|
+
render: (r) => `Subscribed ${r['subscriber']} → ${r['publisher']} (${r['mode']}).`,
|
|
586
603
|
});
|
|
587
604
|
const nodeUnsubscribe = defineLeaf({
|
|
588
605
|
name: 'unsubscribe',
|
|
@@ -609,7 +626,7 @@ const nodeUnsubscribe = defineLeaf({
|
|
|
609
626
|
unsubscribe(subscriber, publisher);
|
|
610
627
|
return { unsubscribed: true, subscriber, publisher };
|
|
611
628
|
},
|
|
612
|
-
render: (r) =>
|
|
629
|
+
render: (r) => `Unsubscribed ${r['subscriber']} from ${r['publisher']}.`,
|
|
613
630
|
});
|
|
614
631
|
// ---------------------------------------------------------------------------
|
|
615
632
|
// node promote — become an orchestrator (the worker→orchestrator polymorph)
|
|
@@ -728,7 +745,7 @@ const nodeDemote = defineLeaf({
|
|
|
728
745
|
const res = setLifecycle(id, 'terminal', { pane, detach: input['detach'] === true });
|
|
729
746
|
return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
|
|
730
747
|
},
|
|
731
|
-
render: (r) =>
|
|
748
|
+
render: (r) => `Demoted ${r['node_id']} — lifecycle now ${r['lifecycle']} (in place)${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
|
|
732
749
|
});
|
|
733
750
|
// ---------------------------------------------------------------------------
|
|
734
751
|
// node lifecycle — flip the lifecycle axis (terminal ↔ resident), independent
|
|
@@ -765,7 +782,7 @@ const nodeLifecycle = defineLeaf({
|
|
|
765
782
|
const res = setLifecycle(id, value, { pane, detach: input['detach'] === true });
|
|
766
783
|
return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
|
|
767
784
|
},
|
|
768
|
-
render: (r) =>
|
|
785
|
+
render: (r) => `Set ${r['node_id']} lifecycle → ${r['lifecycle']}${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
|
|
769
786
|
});
|
|
770
787
|
// ---------------------------------------------------------------------------
|
|
771
788
|
// node yield — refresh: discard context, revive fresh against the roadmap
|
|
@@ -908,10 +925,6 @@ function etaHint(fireAt, now) {
|
|
|
908
925
|
return ` (~${hrs}h)`;
|
|
909
926
|
return ` (~${Math.round(ms / 86_400_000)}d)`;
|
|
910
927
|
}
|
|
911
|
-
/** Escape a value for a rendered XML attribute (list rows carry free-text notes). */
|
|
912
|
-
function xmlAttr(s) {
|
|
913
|
-
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
914
|
-
}
|
|
915
928
|
// node wake at ---------------------------------------------------------------
|
|
916
929
|
const nodeWakeAt = defineLeaf({
|
|
917
930
|
name: 'at',
|
|
@@ -1011,7 +1024,10 @@ const nodeWakeAt = defineLeaf({
|
|
|
1011
1024
|
: `Bare self-alarm armed. You wake in a fresh window at ${fireAt}${eta}. End your turn now to go dormant; do not push final. The wake re-reads your roadmap.`;
|
|
1012
1025
|
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), target, guidance };
|
|
1013
1026
|
},
|
|
1014
|
-
render: (r) =>
|
|
1027
|
+
render: (r) => {
|
|
1028
|
+
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1029
|
+
return `Armed ${r['kind']} wake ${r['id']} — fires ${r['fires_at']}${recur}, target ${r['target']}.\n\n${r['guidance']}`;
|
|
1030
|
+
},
|
|
1015
1031
|
});
|
|
1016
1032
|
// node wake until ------------------------------------------------------------
|
|
1017
1033
|
const nodeWakeUntil = defineLeaf({
|
|
@@ -1063,7 +1079,7 @@ const nodeWakeUntil = defineLeaf({
|
|
|
1063
1079
|
const guidance = `Deadline armed. You wake on the first inbox message, or at ${w.fireAt}${eta} if none arrives. Now delegate / end your turn to go dormant — whichever fires first cancels the other.`;
|
|
1064
1080
|
return { id, fires_at: w.fireAt, target: 'self', guidance };
|
|
1065
1081
|
},
|
|
1066
|
-
render: (r) =>
|
|
1082
|
+
render: (r) => `Armed deadline ${r['id']} — fires ${r['fires_at']}, target ${r['target']}.\n\n${r['guidance']}`,
|
|
1067
1083
|
});
|
|
1068
1084
|
// node wake spawn -----------------------------------------------------------
|
|
1069
1085
|
const nodeWakeSpawn = defineLeaf({
|
|
@@ -1164,7 +1180,10 @@ const nodeWakeSpawn = defineLeaf({
|
|
|
1164
1180
|
: `Deferred spawn armed: a fresh ${kind} node is born at ${fireAt}${eta}. No node exists yet — inspect/cancel via \`crtr node wake list\` / \`crtr node wake cancel ${id}\`. Pick up other work or end your turn.`;
|
|
1165
1181
|
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), guidance };
|
|
1166
1182
|
},
|
|
1167
|
-
render: (r) =>
|
|
1183
|
+
render: (r) => {
|
|
1184
|
+
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1185
|
+
return `Armed deferred spawn ${r['id']} — a ${r['kind']} node, fires ${r['fires_at']}${recur}.\n\n${r['guidance']}`;
|
|
1186
|
+
},
|
|
1168
1187
|
});
|
|
1169
1188
|
// node wake list -------------------------------------------------------------
|
|
1170
1189
|
const nodeWakeList = defineLeaf({
|
|
@@ -1246,10 +1265,13 @@ const nodeWakeList = defineLeaf({
|
|
|
1246
1265
|
},
|
|
1247
1266
|
render: (r) => {
|
|
1248
1267
|
const wakes = r['wakes'];
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1268
|
+
const follow = 'Cancel one with `crtr node wake cancel <id>`. Fired one-shots are gone; a recurrence shows its NEXT fire, not past slots.';
|
|
1269
|
+
if (wakes.length === 0)
|
|
1270
|
+
return `No pending wakes in scope ${r['scope']}.\n\n${follow}`;
|
|
1271
|
+
const cell = (v) => String(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
1272
|
+
const head = '| id | kind | next | recur | target | owner | note |\n| --- | --- | --- | --- | --- | --- | --- |';
|
|
1273
|
+
const rows = wakes.map((w) => `| ${cell(w['id'])} | ${cell(w['kind'])} | ${cell(w['next'])} | ${cell(w['recur'])} | ${cell(w['target'])} | ${cell(w['owner'])} | ${cell(w['note'])} |`);
|
|
1274
|
+
return `${wakes.length} pending wake(s) in scope ${r['scope']}.\n\n${head}\n${rows.join('\n')}\n\n${follow}`;
|
|
1253
1275
|
},
|
|
1254
1276
|
});
|
|
1255
1277
|
// node wake cancel -----------------------------------------------------------
|
|
@@ -1276,7 +1298,7 @@ const nodeWakeCancel = defineLeaf({
|
|
|
1276
1298
|
cancelWake(id);
|
|
1277
1299
|
return { id, was_pending: wasPending };
|
|
1278
1300
|
},
|
|
1279
|
-
render: (r) =>
|
|
1301
|
+
render: (r) => `Canceled wake ${r['id']}${r['was_pending'] === true ? '' : ' (already gone — no-op)'}.`,
|
|
1280
1302
|
});
|
|
1281
1303
|
// node wake (branch) ---------------------------------------------------------
|
|
1282
1304
|
const nodeWake = defineBranch({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const bridgeBranch: import("../../core/command.js").BranchDef;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// `crtr pkg bridge` — generate crouter substrate docs from a Claude skills root
|
|
2
|
+
// and file-level reconcile the bridge plugin (design §2). The mapping lives in
|
|
3
|
+
// core/bridge-map.ts (one testable source of truth); the extension shells to
|
|
4
|
+
// this command rather than duplicating the transform.
|
|
5
|
+
import { join, basename, dirname, isAbsolute } from 'node:path';
|
|
6
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
7
|
+
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
8
|
+
import { usage } from '../../core/errors.js';
|
|
9
|
+
import { userScopeRoot } from '../../core/scope.js';
|
|
10
|
+
import { ensureDir, writeJson, writeText, readText, removePath, walkFiles, } from '../../core/fs-utils.js';
|
|
11
|
+
import { parseFrontmatter, parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
12
|
+
import { buildBridgeDoc } from '../../core/bridge-map.js';
|
|
13
|
+
import { PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE } from '../../types.js';
|
|
14
|
+
/** Enumerate `<source>/**\/SKILL.md`, stopping recursion at the first SKILL.md
|
|
15
|
+
* on any branch — a dir that holds SKILL.md is a skill; its subdirs are assets,
|
|
16
|
+
* not nested skills. */
|
|
17
|
+
function enumerateSkillFiles(root) {
|
|
18
|
+
const out = [];
|
|
19
|
+
if (!existsSync(root))
|
|
20
|
+
return out;
|
|
21
|
+
const stack = [root];
|
|
22
|
+
while (stack.length) {
|
|
23
|
+
const dir = stack.pop();
|
|
24
|
+
let entries;
|
|
25
|
+
try {
|
|
26
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const hasSkill = entries.some((e) => e.isFile() && e.name === 'SKILL.md');
|
|
32
|
+
if (hasSkill) {
|
|
33
|
+
out.push(join(dir, 'SKILL.md'));
|
|
34
|
+
continue; // stop descent — deeper dirs are assets
|
|
35
|
+
}
|
|
36
|
+
for (const e of entries)
|
|
37
|
+
if (e.isDirectory())
|
|
38
|
+
stack.push(join(dir, e.name));
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
const bridgeSync = defineLeaf({
|
|
43
|
+
name: 'sync',
|
|
44
|
+
description: 'generate substrate docs from a Claude skills root',
|
|
45
|
+
whenToUse: 'mirroring a Claude Code skills directory into a crtr bridge plugin: maps each SKILL.md to a substrate memory doc and file-level reconciles the plugin (regenerate, prune stale, preserve hand-authored leaves + INDEX)',
|
|
46
|
+
help: {
|
|
47
|
+
name: 'pkg bridge sync',
|
|
48
|
+
summary: 'mirror a Claude skills root into a crtr bridge plugin (idempotent reconcile)',
|
|
49
|
+
guide: 'Each <source>/skills/<skillname>/SKILL.md becomes ~/.crouter/plugins/<name>/memory/<skillname>.md, stamped generator:claude-bridge. Stamped leaves whose source skill is gone are pruned; hand-authored leaves and any INDEX.md are never touched.',
|
|
50
|
+
params: [
|
|
51
|
+
{ kind: 'flag', name: 'source', type: 'path', required: true, constraint: 'Absolute path to a Claude skills root (a dir whose <skillname>/SKILL.md leaves are bridged; recursion stops at the first SKILL.md).' },
|
|
52
|
+
{ kind: 'flag', name: 'name', type: 'string', required: true, constraint: 'Bridge plugin name. Dest plugin dir is ~/.crouter/plugins/<name>/.' },
|
|
53
|
+
{ kind: 'flag', name: 'source-desc', type: 'string', required: false, constraint: 'Optional plugin description written into the generated plugin.json.' },
|
|
54
|
+
],
|
|
55
|
+
output: [
|
|
56
|
+
{ name: 'plugin', type: 'string', required: true, constraint: 'Bridge plugin name.' },
|
|
57
|
+
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the bridge plugin directory.' },
|
|
58
|
+
{ name: 'written', type: 'number', required: true, constraint: 'Count of substrate leaves generated from source SKILL.md files.' },
|
|
59
|
+
{ name: 'pruned', type: 'number', required: true, constraint: 'Count of stale generator:claude-bridge leaves deleted (source skill gone).' },
|
|
60
|
+
{ name: 'preserved', type: 'number', required: true, constraint: 'Count of untouched protected leaves (INDEX.md or unstamped hand-authored docs).' },
|
|
61
|
+
],
|
|
62
|
+
outputKind: 'object',
|
|
63
|
+
effects: ['Writes ~/.crouter/plugins/<name>/.crouter-plugin/plugin.json and memory/*.md. Deletes stale generator:claude-bridge leaves. Never writes or deletes INDEX.md or unstamped docs.'],
|
|
64
|
+
},
|
|
65
|
+
run: async (input) => {
|
|
66
|
+
const source = input['source'];
|
|
67
|
+
const name = input['name'];
|
|
68
|
+
const sourceDesc = input['sourceDesc'];
|
|
69
|
+
if (!isAbsolute(source)) {
|
|
70
|
+
throw usage(`--source must be an absolute path: ${source}`);
|
|
71
|
+
}
|
|
72
|
+
if (!existsSync(source)) {
|
|
73
|
+
throw usage(`--source does not exist: ${source}`);
|
|
74
|
+
}
|
|
75
|
+
const destRoot = join(userScopeRoot(), 'plugins', name);
|
|
76
|
+
const memoryDir = join(destRoot, 'memory');
|
|
77
|
+
// 1. Ensure the bridge plugin manifest.
|
|
78
|
+
const manifestDir = join(destRoot, PLUGIN_MANIFEST_DIR);
|
|
79
|
+
ensureDir(manifestDir);
|
|
80
|
+
const manifest = {
|
|
81
|
+
name,
|
|
82
|
+
version: '0.0.0',
|
|
83
|
+
...(sourceDesc !== undefined ? { description: sourceDesc } : {}),
|
|
84
|
+
source,
|
|
85
|
+
generator: 'claude-bridge',
|
|
86
|
+
};
|
|
87
|
+
writeJson(join(manifestDir, PLUGIN_MANIFEST_FILE), manifest);
|
|
88
|
+
// 2 + 3. Regenerate one stamped leaf per source skill.
|
|
89
|
+
ensureDir(memoryDir);
|
|
90
|
+
const writtenPaths = new Set();
|
|
91
|
+
for (const file of enumerateSkillFiles(source)) {
|
|
92
|
+
const skillName = basename(dirname(file));
|
|
93
|
+
const parsed = parseFrontmatter(readText(file));
|
|
94
|
+
const doc = buildBridgeDoc({
|
|
95
|
+
name: parsed.data?.name ?? skillName,
|
|
96
|
+
description: parsed.data?.description ?? '',
|
|
97
|
+
body: parsed.body,
|
|
98
|
+
});
|
|
99
|
+
const dest = join(memoryDir, `${skillName}.md`);
|
|
100
|
+
writeText(dest, doc);
|
|
101
|
+
writtenPaths.add(dest);
|
|
102
|
+
}
|
|
103
|
+
const written = writtenPaths.size;
|
|
104
|
+
// 4. Reconcile: prune stale stamped leaves; never touch INDEX.md or
|
|
105
|
+
// unstamped (hand-authored) leaves.
|
|
106
|
+
let pruned = 0;
|
|
107
|
+
let preserved = 0;
|
|
108
|
+
for (const f of walkFiles(memoryDir, (n) => n.endsWith('.md'))) {
|
|
109
|
+
if (writtenPaths.has(f))
|
|
110
|
+
continue; // just regenerated → counted in `written`
|
|
111
|
+
if (basename(f) === 'INDEX.md') {
|
|
112
|
+
preserved++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const generator = parseFrontmatterGeneric(readText(f)).data?.['generator'];
|
|
116
|
+
if (generator === 'claude-bridge') {
|
|
117
|
+
removePath(f);
|
|
118
|
+
pruned++;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
preserved++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { plugin: name, path: destRoot, written, pruned, preserved };
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
export const bridgeBranch = defineBranch({
|
|
128
|
+
name: 'bridge',
|
|
129
|
+
description: 'mirror Claude skills into crtr bridge plugins',
|
|
130
|
+
whenToUse: 'generating a crtr bridge plugin from a Claude Code skills root — the substrate-doc mapping and file-level reconcile invoked by the pi bridge extension',
|
|
131
|
+
help: {
|
|
132
|
+
name: 'pkg bridge',
|
|
133
|
+
summary: 'mirror Claude skills roots into crtr bridge plugins',
|
|
134
|
+
model: 'A bridge plugin is a generator:claude-bridge plugin whose memory/ docs are generated from a Claude skills root. Hand-authored leaves and INDEX ceilings are preserved across syncs.',
|
|
135
|
+
},
|
|
136
|
+
children: [bridgeSync],
|
|
137
|
+
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { defineBranch, defineLeaf } from '../../core/command.js';
|
|
2
2
|
import { notFound } from '../../core/errors.js';
|
|
3
3
|
import { paginate } from '../../core/pagination.js';
|
|
4
|
-
import { listInstalledPlugins, findPluginByName,
|
|
4
|
+
import { listInstalledPlugins, findPluginByName, } from '../../core/resolver.js';
|
|
5
|
+
import { listAllMemoryDocs } from '../../core/memory-resolver.js';
|
|
5
6
|
import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
7
8
|
// plugin.inspect.list
|
|
@@ -78,8 +79,8 @@ const pluginList = defineLeaf({
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
const pluginShow = defineLeaf({
|
|
80
81
|
name: 'show',
|
|
81
|
-
description: 'read plugin manifest and
|
|
82
|
-
whenToUse: 'reading one installed plugin in detail to decide before you enable, disable, or remove it — returns the full manifest, the
|
|
82
|
+
description: 'read plugin manifest and substrate inventory',
|
|
83
|
+
whenToUse: 'reading one installed plugin in detail to decide before you enable, disable, or remove it — returns the full manifest, the substrate docs it provides (its `<pluginName>/` memory subtree), its scope, and whether it is currently enabled. Use `pkg plugin inspect list` instead to enumerate plugins rather than drill into one',
|
|
83
84
|
help: {
|
|
84
85
|
name: 'pkg plugin inspect show',
|
|
85
86
|
summary: 'read plugin manifest and metadata by name',
|
|
@@ -93,7 +94,7 @@ const pluginShow = defineLeaf({
|
|
|
93
94
|
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the plugin directory.' },
|
|
94
95
|
{ name: 'enabled', type: 'boolean', required: true, constraint: 'Whether the plugin is active.' },
|
|
95
96
|
{ name: 'manifest', type: 'object', required: true, constraint: 'Full plugin.json contents.' },
|
|
96
|
-
{ name: 'skills', type: 'object[]', required: true, constraint: 'Each: {name, path
|
|
97
|
+
{ name: 'skills', type: 'object[]', required: true, constraint: 'Each: {name, path}. Substrate docs provided by the plugin (its `<pluginName>/` memory subtree).' },
|
|
97
98
|
],
|
|
98
99
|
outputKind: 'object',
|
|
99
100
|
effects: ['None. Read-only.'],
|
|
@@ -117,7 +118,8 @@ const pluginShow = defineLeaf({
|
|
|
117
118
|
if (found === null) {
|
|
118
119
|
throw notFound(`plugin not found: ${name}`);
|
|
119
120
|
}
|
|
120
|
-
const
|
|
121
|
+
const prefix = `${found.name}/`;
|
|
122
|
+
const skills = listAllMemoryDocs().filter((d) => d.name.startsWith(prefix));
|
|
121
123
|
return {
|
|
122
124
|
name: found.name,
|
|
123
125
|
scope: found.scope,
|
|
@@ -127,7 +129,6 @@ const pluginShow = defineLeaf({
|
|
|
127
129
|
skills: skills.map((s) => ({
|
|
128
130
|
name: s.name,
|
|
129
131
|
path: s.path,
|
|
130
|
-
enabled: s.enabled,
|
|
131
132
|
})),
|
|
132
133
|
};
|
|
133
134
|
},
|
package/dist/commands/pkg.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { defineBranch } from '../core/command.js';
|
|
5
5
|
import { pluginBranch } from './pkg/plugin.js';
|
|
6
6
|
import { marketBranch } from './pkg/market.js';
|
|
7
|
+
import { bridgeBranch } from './pkg/bridge.js';
|
|
7
8
|
export function registerPkg() {
|
|
8
9
|
return defineBranch({
|
|
9
10
|
name: 'pkg',
|
|
@@ -16,6 +17,6 @@ export function registerPkg() {
|
|
|
16
17
|
name: 'pkg',
|
|
17
18
|
summary: 'manage plugins and plugin marketplaces',
|
|
18
19
|
},
|
|
19
|
-
children: [pluginBranch, marketBranch],
|
|
20
|
+
children: [pluginBranch, marketBranch, bridgeBranch],
|
|
20
21
|
});
|
|
21
22
|
}
|
package/dist/commands/push.js
CHANGED
|
@@ -112,7 +112,7 @@ function makeTierLeaf(tier) {
|
|
|
112
112
|
: tier === 'urgent'
|
|
113
113
|
? `Urgent report fanned to ${n} subscriber(s) — they are force-woken.`
|
|
114
114
|
: `Progress report fanned to ${n} subscriber(s).`;
|
|
115
|
-
return
|
|
115
|
+
return `${line}\n\n- report: ${r['report_path']}`;
|
|
116
116
|
},
|
|
117
117
|
});
|
|
118
118
|
}
|
|
@@ -161,16 +161,16 @@ const feedReadLeaf = defineLeaf({
|
|
|
161
161
|
const n = Array.isArray(r['entries']) ? r['entries'].length : 0;
|
|
162
162
|
const rawDigest = typeof r['digest'] === 'string' ? r['digest'] : '';
|
|
163
163
|
if (n > 0 && rawDigest.trim() !== '') {
|
|
164
|
-
return
|
|
164
|
+
return `${n} unread pointer${n === 1 ? '' : 's'} drained from ${r['node_id']}'s inbox.\n\n${rawDigest.trim()}`;
|
|
165
165
|
}
|
|
166
166
|
// Empty drain has two distinct causes — say which, honestly. An inbox that
|
|
167
167
|
// already holds entries was drained when the watcher woke you (the wake
|
|
168
168
|
// message WAS that digest); a never-used inbox simply has nothing yet.
|
|
169
169
|
const total = typeof r['inbox_total'] === 'number' ? r['inbox_total'] : 0;
|
|
170
|
-
const
|
|
171
|
-
? '
|
|
172
|
-
: '
|
|
173
|
-
return
|
|
170
|
+
const explanation = total > 0
|
|
171
|
+
? 'Your inbox is not empty, though. The watcher drains your inbox to wake you, so the entries you already saw in your wake message are the same ones you would read here; that is why this is empty. Re-read the whole history (full message bodies included) with `crtr feed read --all`, or dereference the refs from the wake digest you already have.'
|
|
172
|
+
: 'Your inbox is empty — nothing has arrived from your subscriptions yet. Expected while workers run: a worker that has not pushed yet leaves no pointer, and it will wake you the moment it does. The wake is automatic — just continue your own work or end your turn. (Reach for `crtr feed peek` only if you suspect a worker died, not to confirm a live one.)';
|
|
173
|
+
return `No unread pointers for ${r['node_id']} (unread=0).\n\n${explanation}`;
|
|
174
174
|
},
|
|
175
175
|
});
|
|
176
176
|
// ---------------------------------------------------------------------------
|
|
@@ -249,28 +249,23 @@ const feedPeekLeaf = defineLeaf({
|
|
|
249
249
|
const tail = unread > 0
|
|
250
250
|
? `${unread} unread report${unread === 1 ? '' : 's'} sit in your inbox — run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}.`
|
|
251
251
|
: 'If you spawned workers, they have finished and detached, or you never subscribed. Nothing will wake you.';
|
|
252
|
-
return
|
|
252
|
+
return `No nodes below you (0 subscriptions, ${unread} unread). ${tail}`;
|
|
253
253
|
}
|
|
254
254
|
const working = kids.filter((k) => k.status === 'active' || k.status === 'idle');
|
|
255
255
|
const liveWaking = working.filter((k) => k.active); // active sub to a live node = it will wake me
|
|
256
256
|
const done = kids.filter((k) => k.status === 'done' || k.status === 'canceled');
|
|
257
257
|
const dead = kids.filter((k) => k.status === 'dead');
|
|
258
|
-
let verdict;
|
|
259
258
|
let line;
|
|
260
259
|
if (dead.length > 0) {
|
|
261
|
-
verdict = 'attention';
|
|
262
260
|
line = `\u26a0 ${dead.length} below you ${dead.length === 1 ? 'is' : 'are'} dead and will NOT wake you. Inspect with \`crtr node inspect show <id>\`, then re-delegate or proceed without ${dead.length === 1 ? 'it' : 'them'}.`;
|
|
263
261
|
}
|
|
264
262
|
else if (liveWaking.length > 0) {
|
|
265
|
-
verdict = 'working';
|
|
266
263
|
line = `Safe to yield \u2014 ${liveWaking.length} worker${liveWaking.length === 1 ? '' : 's'} running async will wake you on the next push. Nothing to do now; end your turn and chill.`;
|
|
267
264
|
}
|
|
268
265
|
else if (unread > 0) {
|
|
269
|
-
verdict = 'ready';
|
|
270
266
|
line = `Nothing still running, but ${unread} unread report${unread === 1 ? '' : 's'} \u2014 run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}, then continue or finish.`;
|
|
271
267
|
}
|
|
272
268
|
else {
|
|
273
|
-
verdict = 'idle';
|
|
274
269
|
line = 'Everything below you has finished and been drained \u2014 nothing is running. Continue your own work, or `crtr push final` to finish.';
|
|
275
270
|
}
|
|
276
271
|
const rows = kids.map((k) => {
|
|
@@ -279,10 +274,10 @@ const feedPeekLeaf = defineLeaf({
|
|
|
279
274
|
? `pushed ${fmtAge(k.last_push.ts)} [${k.last_push.kind}]${k.last_push.ref !== null ? ` ref:${k.last_push.ref}` : ''}`
|
|
280
275
|
: 'no push yet';
|
|
281
276
|
const glyph = STATUS_GLYPH[k.status] ?? '?';
|
|
282
|
-
return
|
|
277
|
+
return `- ${glyph} ${k.name}${sub} (${k.node_id}) \u00b7 ${k.kind} \u00b7 ${k.status} \u00b7 spawned ${fmtAge(k.spawned)} \u00b7 cyc ${k.cycles} \u00b7 ${push}`;
|
|
283
278
|
}).join('\n');
|
|
284
|
-
const
|
|
285
|
-
return
|
|
279
|
+
const summary = `${kids.length} node${kids.length === 1 ? '' : 's'} below you — ${working.length} working, ${done.length} done, ${dead.length} dead, ${unread} unread:`;
|
|
280
|
+
return `${line}\n\n${summary}\n${rows}`;
|
|
286
281
|
},
|
|
287
282
|
});
|
|
288
283
|
// ---------------------------------------------------------------------------
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
2
|
import { writeFileSync } from 'node:fs';
|
|
3
3
|
import { defineLeaf, defineBranch } from '../../core/command.js';
|
|
4
4
|
import { usage, general, notFound } from '../../core/errors.js';
|
|
5
|
-
import {
|
|
5
|
+
import { SKILL_TYPES, isSkillType } from '../../types.js';
|
|
6
6
|
import { parseSkillQualifier, findPluginByName, } from '../../core/resolver.js';
|
|
7
|
-
import { resolveScopeArg, requireScopeRoot,
|
|
8
|
-
import { serializeFrontmatter } from '../../core/frontmatter.js';
|
|
7
|
+
import { resolveScopeArg, requireScopeRoot, scopeMemoryDir, pluginMemoryDir, } from '../../core/scope.js';
|
|
9
8
|
import { ensureScopeInitialized } from '../../core/config.js';
|
|
10
9
|
import { ensureDir, pathExists } from '../../core/fs-utils.js';
|
|
11
10
|
import { skillCreatePrompt, skillTemplatePrompt } from '../../prompts/skill.js';
|
|
11
|
+
import { memoryFilePath, serializeMemoryDoc } from '../memory/shared.js';
|
|
12
12
|
import { VALID_TYPES, resolveWriteScope } from './shared.js';
|
|
13
13
|
export const authorGuide = defineLeaf({
|
|
14
14
|
name: 'guide',
|
|
@@ -42,25 +42,25 @@ export const authorGuide = defineLeaf({
|
|
|
42
42
|
});
|
|
43
43
|
export const authorScaffold = defineLeaf({
|
|
44
44
|
name: 'scaffold',
|
|
45
|
-
description: 'create an empty
|
|
46
|
-
whenToUse: 'creating the empty
|
|
45
|
+
description: 'create an empty skill substrate doc stub',
|
|
46
|
+
whenToUse: 'creating the empty `kind: skill` substrate doc at a `<plugin>/<skill>` qualifier (or a bare `<skill>` name for a scope-owned doc) before you fill in content — it writes the frontmatter and file, then points you at the authoring guide.',
|
|
47
47
|
help: {
|
|
48
48
|
name: 'skill author scaffold',
|
|
49
|
-
summary: 'create an empty
|
|
49
|
+
summary: 'create an empty kind:skill substrate doc stub at the given qualifier',
|
|
50
50
|
params: [
|
|
51
|
-
{ kind: 'positional', name: 'qualifier', required: true, constraint: 'Skill identifier
|
|
52
|
-
{ kind: 'flag', name: 'type', type: 'enum', choices: [...VALID_TYPES], required: false, constraint: 'One of: playbook, primer, reference, runbook, freeform.' },
|
|
53
|
-
{ kind: 'flag', name: 'description', type: 'string', required: false, constraint: '
|
|
51
|
+
{ kind: 'positional', name: 'qualifier', required: true, constraint: 'Skill identifier: <plugin>/<skill> to scaffold into a plugin\'s memory/, or a bare <skill> for the scope-owned memory/.' },
|
|
52
|
+
{ kind: 'flag', name: 'type', type: 'enum', choices: [...VALID_TYPES], required: false, constraint: 'One of: playbook, primer, reference, runbook, freeform. Drives the follow-up authoring guide only — not stored on the substrate doc.' },
|
|
53
|
+
{ kind: 'flag', name: 'description', type: 'string', required: false, constraint: 'Read-routing line written to the doc\'s when-and-why-to-read frontmatter.' },
|
|
54
54
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: project if available, else user.' },
|
|
55
55
|
],
|
|
56
56
|
output: [
|
|
57
|
-
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the scaffolded
|
|
57
|
+
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the scaffolded substrate doc.' },
|
|
58
58
|
{ name: 'follow_up', type: 'string', required: true, constraint: 'Recommended next call to load the authoring guide.' },
|
|
59
59
|
],
|
|
60
60
|
outputKind: 'object',
|
|
61
61
|
effects: [
|
|
62
|
-
'Creates
|
|
63
|
-
'Writes frontmatter
|
|
62
|
+
'Creates a `kind: skill` substrate doc stub under the resolved memory/ dir (scope-owned, or the named plugin\'s).',
|
|
63
|
+
'Writes substrate frontmatter: kind:skill, when-and-why-to-read (from --description if given), system-prompt-visibility:name, file-read-visibility:none.',
|
|
64
64
|
],
|
|
65
65
|
},
|
|
66
66
|
run: async (input) => {
|
|
@@ -82,31 +82,20 @@ export const authorScaffold = defineLeaf({
|
|
|
82
82
|
throw usage(`unknown skill type: ${typeStr} / valid: ${SKILL_TYPES.join(' | ')}`);
|
|
83
83
|
}
|
|
84
84
|
const skillType = typeStr !== undefined && isSkillType(typeStr) ? typeStr : undefined;
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
85
|
+
// Resolve the memory/ dir to scaffold into: the named plugin's, or the
|
|
86
|
+
// scope-owned one. One substrate, one author path.
|
|
87
|
+
let memoryDir;
|
|
88
|
+
if (pluginName === undefined) {
|
|
88
89
|
const scope = resolveWriteScope(scopeStr);
|
|
89
90
|
const scopeRootPath = requireScopeRoot(scope);
|
|
90
91
|
ensureScopeInitialized(scope, scopeRootPath);
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
throw general(`no
|
|
92
|
+
const dir = scopeMemoryDir(scope);
|
|
93
|
+
if (!dir) {
|
|
94
|
+
throw general(`no memory dir for scope ${scope}`);
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
-
skillFile = join(skillDir, SKILL_ENTRY_FILE);
|
|
97
|
-
if (pathExists(skillFile)) {
|
|
98
|
-
throw general(`skill already exists: ${skillFile}`);
|
|
99
|
-
}
|
|
100
|
-
ensureDir(skillDir);
|
|
101
|
-
const fm = serializeFrontmatter({
|
|
102
|
-
name: skillName,
|
|
103
|
-
description,
|
|
104
|
-
type: skillType,
|
|
105
|
-
});
|
|
106
|
-
writeFileSync(skillFile, fm, 'utf8');
|
|
96
|
+
memoryDir = dir;
|
|
107
97
|
}
|
|
108
98
|
else {
|
|
109
|
-
// Plugin-scoped scaffold
|
|
110
99
|
const scopeForLookup = scopeStr !== undefined
|
|
111
100
|
? (() => {
|
|
112
101
|
const r = resolveScopeArg(scopeStr);
|
|
@@ -119,19 +108,21 @@ export const authorScaffold = defineLeaf({
|
|
|
119
108
|
if (!plugin) {
|
|
120
109
|
throw notFound(`plugin not found: ${pluginName}`);
|
|
121
110
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
ensureDir(skillDir);
|
|
128
|
-
const fm = serializeFrontmatter({
|
|
129
|
-
name: skillName,
|
|
130
|
-
description,
|
|
131
|
-
type: skillType,
|
|
132
|
-
});
|
|
133
|
-
writeFileSync(skillFile, fm, 'utf8');
|
|
111
|
+
memoryDir = pluginMemoryDir(plugin);
|
|
112
|
+
}
|
|
113
|
+
const skillFile = memoryFilePath(memoryDir, skillName);
|
|
114
|
+
if (pathExists(skillFile)) {
|
|
115
|
+
throw general(`skill already exists: ${skillFile}`);
|
|
134
116
|
}
|
|
117
|
+
ensureDir(dirname(skillFile));
|
|
118
|
+
// Catalog-default substrate frontmatter (design §1.5): a name-rung,
|
|
119
|
+
// file-read-none skill doc. --description becomes the read-routing line.
|
|
120
|
+
const frontmatter = { kind: 'skill' };
|
|
121
|
+
if (description !== undefined)
|
|
122
|
+
frontmatter['when-and-why-to-read'] = description;
|
|
123
|
+
frontmatter['system-prompt-visibility'] = 'name';
|
|
124
|
+
frontmatter['file-read-visibility'] = 'none';
|
|
125
|
+
writeFileSync(skillFile, serializeMemoryDoc(frontmatter, ''), 'utf8');
|
|
135
126
|
const typeHint = skillType !== undefined ? `--type ${skillType} ` : '';
|
|
136
127
|
const follow_up = `crtr skill author guide ${typeHint}--topic "${skillName}"`;
|
|
137
128
|
return { path: skillFile, follow_up };
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function formatNeighborQualifier(s: Skill): string;
|
|
3
|
-
export declare function formatNeighborKeywords(s: Skill): string;
|
|
4
|
-
export declare function buildNeighborsSection(skill: Skill): string | null;
|
|
5
|
-
export declare function appendNeighbors(skill: Skill, body: string): string;
|
|
1
|
+
import type { Scope } from '../../types.js';
|
|
6
2
|
export declare function resolveWriteScope(scopeStr: string | undefined): Scope;
|
|
7
3
|
export declare const VALID_TYPES: readonly ["playbook", "primer", "reference", "runbook", "freeform"];
|
|
8
4
|
export type CatalogSource = {
|