@passioncode-ai/passioncode 0.1.45 → 0.1.47

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 (31) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -0
  3. package/SECURITY.md +6 -1
  4. package/family.json +1 -1
  5. package/lib/launcher.js +212 -3
  6. package/package.json +1 -1
  7. package/payload/.claude-plugin/marketplace.json +3 -3
  8. package/payload/manifest.json +11 -10
  9. package/payload/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +2 -2
  10. package/payload/plugins/fabric-agent-adapter/hooks/hooks.json +43 -1
  11. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +3 -3
  12. package/payload/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +2 -2
  13. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +2 -2
  14. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +10 -7
  15. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/estate-record.md +32 -15
  16. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +46 -14
  17. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +4 -2
  18. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +5 -2
  19. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-delegation.mjs +35 -20
  20. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-estate.mjs +77 -9
  21. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +167 -36
  22. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_delegation.py +53 -19
  23. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_estate.py +107 -16
  24. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +270 -49
  25. package/payload/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/sample_service.py +8 -2
  26. package/payload/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +3 -3
  27. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +217 -0
  28. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +99 -0
  29. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/references/session-hooks.md +111 -0
  30. package/payload/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session.py +1265 -0
  31. package/payload/plugins/passioncode/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.47 - 2026-10-13
4
+
5
+ ### Changed
6
+
7
+ - **Fabric Agent Adapter is pinned at `v0.23.0`** (it was `v0.21.0`). It brings two adapter releases.
8
+ - **0.22.0 (FAA-12a)** adds the fourth skill, `working-with-fabric`, with the plugin's session hooks. A coding agent opened in a terminal can then work in a project through Fabric's project board.
9
+ - **0.23.0 (FAA-32)** moves the adapter's contract pin to `fe55fc6`:
10
+ - a service's supervisor can mark itself with an argument as well as the environment. The Task Scheduler and Project Observatory's node are now recognised, and markers that disagree stop the service before it takes its lock;
11
+ - a delegation key the estate record certifies follows the contract's verdict and refusal codes;
12
+ - an estate record may be issued up to 60 s ahead of the clock, and its passkey must sign under a `sign.<domain>` RP;
13
+ - Windows token files are checked against the contract's reader rule.
14
+
15
+ ## 0.1.46 - 2026-10-12
16
+
17
+ ### Added
18
+
19
+ - **`update` registers the Fabric Dashboards MCP server for every agent that is present**
20
+ (`fabric-dashboards`, stdio, the app bundle's `bin/fabric-dashboards-mcp`): Claude Code
21
+ through `claude mcp add --scope user`, Codex CLI as a `["mcp_servers"."fabric-dashboards"]`
22
+ table appended to `~/.codex/config.toml`, Gemini CLI and Kimi Code in the `mcpServers` of
23
+ `~/.gemini/settings.json` and `$KIMI_CODE_HOME/mcp.json`. A machine without the app skips
24
+ the step; an entry that already points at the same command is left alone, and one that
25
+ points elsewhere, or is declared in a form the launcher does not edit, is reported and
26
+ kept. Each registration is journalled before the file changes and removed again by
27
+ `restore` and `uninstall` only while it is still exactly what the launcher wrote
28
+ ([ADR-0003](docs/adr/0003-fabric-dashboards-mcp-registration.md)).
29
+
3
30
  ## 0.1.45 - 2026-10-12
4
31
 
5
32
  ### Changed
package/README.md CHANGED
@@ -55,6 +55,12 @@ out of it, even when they implement Fabric protocols.
55
55
  `~/.passioncode/quarantine/` — `npx @passioncode-ai/passioncode@latest restore` puts them back.
56
56
  - Each version is an immutable release under `~/.passioncode/releases/<version>`; an update
57
57
  keeps the release it replaced for rollback and removes older ones.
58
+ - When the Fabric Dashboards app is installed, `update` also registers its MCP server
59
+ (`fabric-dashboards`) for every agent that is present — Claude Code (through
60
+ `claude mcp add --scope user`), Codex CLI (`~/.codex/config.toml`), Gemini CLI
61
+ (`~/.gemini/settings.json`) and Kimi Code (`~/.kimi-code/mcp.json`). An entry that
62
+ already points at the same command is left alone; one that points elsewhere is reported
63
+ and kept. `restore` and `uninstall` remove what the launcher added.
58
64
 
59
65
  ## Staying current
60
66
 
package/SECURITY.md CHANGED
@@ -11,7 +11,12 @@ that replaces it is verified. For a member's old single-member marketplace
11
11
  (`legacyMarketplaces` in `family.json`), once that plugin is verified and nothing else
12
12
  installed comes from it, it also moves the `extraKnownMarketplaces` entry out of
13
13
  `~/.claude/settings.json` (a copy of the file is kept in the quarantine) and runs
14
- `claude plugin marketplace remove <name>`. Displaced files are moved rather than
14
+ `claude plugin marketplace remove <name>`. When the Fabric Dashboards app is installed it
15
+ also registers a `fabric-dashboards` MCP entry for every agent that is present: through
16
+ `claude mcp add --scope user` for Claude Code (it never writes `~/.claude.json` itself),
17
+ and in `~/.codex/config.toml`, `~/.gemini/settings.json` and `~/.kimi-code/mcp.json`,
18
+ copying each existing file into the quarantine first; an entry that already exists is
19
+ left alone and reported. Displaced files are moved rather than
15
20
  deleted: a recovery record is written before the move in
16
21
  `~/.passioncode/quarantine/<run>/moved.json`. `restore` processes the latest batch,
17
22
  checkpoints completed entries and refuses to overwrite a conflicting user replacement.
package/family.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "name": "fabric-agent-adapter",
9
9
  "displayName": "Fabric Agent Adapter",
10
10
  "repo": "passioncode-ai/fabric-agent-adapter",
11
- "ref": "v0.21.0",
11
+ "ref": "v0.23.0",
12
12
  "kind": "plugin",
13
13
  "path": "plugins/fabric-agent-adapter",
14
14
  "legacyPluginIds": [
package/lib/launcher.js CHANGED
@@ -2,8 +2,9 @@
2
2
  /**
3
3
  * The PassionCode.ai launcher: installs the vendored payload as one release,
4
4
  * gives Claude Code the plugins through a local marketplace, gives every other
5
- * agent the same skills through the ~/.agents/skills hub, and removes what
6
- * would shadow them — reversibly. Every step is recorded; nothing is deleted.
5
+ * agent the same skills through the ~/.agents/skills hub, registers the Fabric
6
+ * Dashboards MCP server for every agent that is present, and removes what would
7
+ * shadow them — reversibly. Every step is recorded; nothing is deleted.
7
8
  *
8
9
  * One channel per agent: Claude Code reads plugins, so nothing is written to
9
10
  * ~/.claude/skills (a plain copy there shadows the plugin and serves its frozen
@@ -47,6 +48,12 @@ function writeJson(file, value) {
47
48
  fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n');
48
49
  fs.renameSync(tmp, file);
49
50
  }
51
+ function writeText(file, text) {
52
+ fs.mkdirSync(path.dirname(file), { recursive: true });
53
+ const tmp = `${file}.${process.pid}.tmp`;
54
+ fs.writeFileSync(tmp, text);
55
+ fs.renameSync(tmp, file);
56
+ }
50
57
  function lstat(p) { try { return fs.lstatSync(p); } catch (_) { return null; } }
51
58
  function linkTarget(p) { try { return fs.readlinkSync(p); } catch (_) { return null; } }
52
59
  function pointsTo(at, target) { const link = linkTarget(at); return link !== null && path.resolve(path.dirname(at), link) === target; }
@@ -187,6 +194,103 @@ function marketplaceAddArg(source) {
187
194
  return source.url || source.repo || source.path || null;
188
195
  }
189
196
 
197
+ const MCP_NAME = 'fabric-dashboards';
198
+ const MCP_COMMAND = () => process.env.PASSIONCODE_FABRIC_DASHBOARDS_MCP
199
+ || '/Applications/Fabric Dashboards.app/Contents/Resources/bin/fabric-dashboards-mcp';
200
+
201
+ /**
202
+ * The agents besides Claude Code the Fabric Dashboards MCP server is registered for, and
203
+ * the file each keeps its MCP configuration in. An agent is present when its directory
204
+ * is; a missing file under a present agent is created. Claude Code is registered through
205
+ * its own CLI instead: every running session rewrites ~/.claude.json, so a second writer
206
+ * there would lose one side's change.
207
+ */
208
+ function mcpFileTargets(home) {
209
+ const kimiHome = process.env.KIMI_CODE_HOME || path.join(home, '.kimi-code');
210
+ return [
211
+ { agent: 'Codex CLI', dir: path.join(home, '.codex'), file: path.join(home, '.codex', 'config.toml'), kind: 'toml' },
212
+ { agent: 'Gemini CLI', dir: path.join(home, '.gemini'), file: path.join(home, '.gemini', 'settings.json'), kind: 'json' },
213
+ { agent: 'Kimi Code', dir: kimiHome, file: path.join(kimiHome, 'mcp.json'), kind: 'json' },
214
+ ];
215
+ }
216
+
217
+ /** The line range of the `["mcp_servers"."<name>"]` table in a TOML text, or null. */
218
+ function tomlTableRange(text, name) {
219
+ const lines = text.split('\n');
220
+ const header = new RegExp(`^\\s*\\[\\s*["']?mcp_servers["']?\\s*\\.\\s*["']?${name}["']?\\s*\\]\\s*(?:#.*)?$`);
221
+ for (let i = 0; i < lines.length; i++) {
222
+ if (!header.test(lines[i])) continue;
223
+ let end = lines.length;
224
+ for (let j = i + 1; j < lines.length; j++) {
225
+ if (/^\s*\[/.test(lines[j])) { end = j; break; }
226
+ }
227
+ return { start: i, end, lines };
228
+ }
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Any other way a TOML text can already define the server — a sub-table such as
234
+ * `[mcp_servers.<name>.env]` or an inline `<name> = { … }` under `[mcp_servers]`.
235
+ * Appending a table then would declare the key twice and Codex would refuse the file.
236
+ */
237
+ function tomlMentions(text, name) {
238
+ return new RegExp(`^\\s*\\[\\s*["']?mcp_servers["']?\\s*\\.\\s*["']?${name}["']?\\s*\\.`, 'm').test(text)
239
+ || new RegExp(`^\\s*["']?${name}["']?\\s*=`, 'm').test(text);
240
+ }
241
+
242
+ /** The `command` of a TOML table range: the string, null when absent, undefined when unreadable. */
243
+ function tomlTableCommand(range) {
244
+ for (let i = range.start + 1; i < range.end; i++) {
245
+ const m = /^\s*command\s*=\s*("(?:[^"\\]|\\.)*")\s*(?:#.*)?$/.exec(range.lines[i]);
246
+ if (m) { try { return JSON.parse(m[1]); } catch (_) { return undefined; } }
247
+ }
248
+ return null;
249
+ }
250
+
251
+ function mcpTomlTable(command) {
252
+ return `["mcp_servers"."${MCP_NAME}"]\ncommand = ${JSON.stringify(command)}\nargs = []\n`;
253
+ }
254
+
255
+ /** Two MCP entries are the same when their JSON lines up at every depth, key order aside. */
256
+ function sameEntry(a, b) {
257
+ const canon = (v) => (v && typeof v === 'object' && !Array.isArray(v)
258
+ ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canon(v[k])]))
259
+ : Array.isArray(v) ? v.map(canon) : v);
260
+ return JSON.stringify(canon(a)) === JSON.stringify(canon(b));
261
+ }
262
+
263
+ /**
264
+ * Remove an MCP entry the launcher added, only while it is still exactly that entry.
265
+ * Returns a note, or null when the entry is already gone. Never deletes the file.
266
+ */
267
+ function removeMcpRegistration(record, claude) {
268
+ const kept = (why) => `${record.file}: ${record.name} for ${record.agent} ${why}; kept`;
269
+ const removed = `${record.name} for ${record.agent} removed from ${record.file}`;
270
+ if (record.file.endsWith('.toml')) {
271
+ let text;
272
+ try { text = fs.readFileSync(record.file, 'utf8'); } catch (_) { return null; }
273
+ const range = tomlTableRange(text, record.name);
274
+ if (!range) return null;
275
+ if (tomlTableCommand(range) !== record.entry.command) return kept('was changed');
276
+ writeText(record.file, range.lines.slice(0, range.start).concat(range.lines.slice(range.end)).join('\n'));
277
+ return removed;
278
+ }
279
+ let cfg;
280
+ try { cfg = readSettings(record.file); } catch (error) { return kept(`could not be read (${error.message})`); }
281
+ const servers = cfg.doc.mcpServers;
282
+ if (!servers || !Object.prototype.hasOwnProperty.call(servers, record.name)) return null;
283
+ if (!sameEntry(servers[record.name], record.entry)) return kept('was changed');
284
+ if (record.via === 'claude') {
285
+ if (!claude.available()) return kept('is registered with Claude Code, whose CLI was not found');
286
+ const r = claude.run(['mcp', 'remove', '--scope', 'user', record.name]);
287
+ return r.code === 0 ? removed : kept(`could not be removed (${r.out || 'claude mcp remove failed'})`);
288
+ }
289
+ delete servers[record.name];
290
+ writeJson(record.file, cfg.doc);
291
+ return removed;
292
+ }
293
+
190
294
  function payloadManifest(dir = PAYLOAD()) {
191
295
  const m = readJson(path.join(dir, 'manifest.json'), null);
192
296
  if (!m || !Array.isArray(m.members)) throw new Error(`No payload in ${dir}: the package is incomplete (run scripts/vendor.mjs).`);
@@ -391,7 +495,93 @@ function update({ home, dryRun = false, claude = claudeRunner(), log = () => {}
391
495
  }
392
496
  }
393
497
 
394
- // 5. The launcher's own releases: keep this one and the one it replaced, remove the rest.
498
+ // 5. The Fabric Dashboards MCP server, for every agent that is present. Entries a
499
+ // person or another tool set are never rewritten: a matching entry is left, a
500
+ // different one is reported. What the launcher adds is journalled before it is
501
+ // written, so restore and uninstall can take it out again.
502
+ const registered = [];
503
+ const mcpCommand = MCP_COMMAND();
504
+ const mcpBackup = (file, agent) => {
505
+ if (!lstat(file)) return null;
506
+ fs.mkdirSync(quarantineDir, { recursive: true });
507
+ const backup = path.join(quarantineDir, `${path.basename(file)}.before-mcp-${agent.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`);
508
+ fs.copyFileSync(file, backup);
509
+ return backup;
510
+ };
511
+ const register = (record, write) => step('mcp', `register ${MCP_NAME} for ${record.agent} in ${record.file}`, () => {
512
+ recordMove(record);
513
+ write();
514
+ registered.push({ agent: record.agent, file: record.file, name: record.name, entry: record.entry, ...(record.via ? { via: record.via } : {}) });
515
+ });
516
+ const existingNote = (agent, existing) => existing === mcpCommand
517
+ ? { kind: 'mcp', detail: `${agent} already runs ${MCP_NAME} from ${mcpCommand}`, outcome: 'skipped' }
518
+ : { kind: 'mcp', detail: `${agent} has its own ${MCP_NAME} entry (${existing || 'not a stdio server'}); it was kept`, outcome: 'skipped' };
519
+ if (!lstat(mcpCommand)) {
520
+ steps.push({ kind: 'mcp', detail: `no Fabric Dashboards MCP at ${mcpCommand}; nothing was registered`, outcome: 'skipped' });
521
+ } else {
522
+ const claudeJson = path.join(p.home, '.claude.json');
523
+ if (!hasClaude) {
524
+ steps.push({ kind: 'mcp', detail: `Claude Code CLI not found; ${MCP_NAME} was not registered for it`, outcome: 'skipped' });
525
+ } else {
526
+ let servers = null;
527
+ try { servers = readSettings(claudeJson).doc.mcpServers || {}; } catch (error) {
528
+ steps.push({ kind: 'mcp', detail: `keep ${claudeJson}: it could not be read`, outcome: 'failed', error: error.message });
529
+ }
530
+ const existing = servers && servers[MCP_NAME];
531
+ if (existing) steps.push(existingNote('Claude Code', typeof existing === 'object' ? existing.command || existing.url : null));
532
+ else if (servers) {
533
+ const entry = { type: 'stdio', command: mcpCommand, args: [], env: {} };
534
+ register({ kind: 'mcp', via: 'claude', agent: 'Claude Code', file: claudeJson, name: MCP_NAME, entry, backup: null }, () => {
535
+ const r = claude.run(['mcp', 'add', '--scope', 'user', MCP_NAME, '--', mcpCommand], { cwd: p.state });
536
+ if (r.code !== 0) throw new Error(r.out || 'claude mcp add failed');
537
+ const now = readSettings(claudeJson).doc.mcpServers || {};
538
+ if (!now[MCP_NAME] || now[MCP_NAME].command !== mcpCommand) throw new Error(`Claude Code did not register ${MCP_NAME} in ${claudeJson}`);
539
+ // What the CLI wrote is what restore and uninstall compare against.
540
+ entry.type = now[MCP_NAME].type; entry.args = now[MCP_NAME].args; entry.env = now[MCP_NAME].env;
541
+ });
542
+ }
543
+ }
544
+ for (const target of mcpFileTargets(p.home)) {
545
+ if (!isDirectory(target.dir)) {
546
+ steps.push({ kind: 'mcp', detail: `${target.agent} not found; ${MCP_NAME} was not registered for it`, outcome: 'skipped' });
547
+ continue;
548
+ }
549
+ if (target.kind === 'json') {
550
+ let cfg;
551
+ try { cfg = readSettings(target.file); } catch (error) {
552
+ steps.push({ kind: 'mcp', detail: `keep ${target.file}: it could not be read`, outcome: 'failed', error: error.message });
553
+ continue;
554
+ }
555
+ const existing = (cfg.doc.mcpServers || {})[MCP_NAME];
556
+ if (existing) { steps.push(existingNote(target.agent, typeof existing === 'object' ? existing.command || existing.url : null)); continue; }
557
+ const entry = { command: mcpCommand, args: [], env: {} };
558
+ register({ kind: 'mcp', agent: target.agent, file: target.file, name: MCP_NAME, entry, backup: mcpBackup(target.file, target.agent) }, () => {
559
+ cfg.doc.mcpServers = { ...(cfg.doc.mcpServers || {}), [MCP_NAME]: entry };
560
+ writeJson(target.file, cfg.doc);
561
+ });
562
+ } else {
563
+ let text = '';
564
+ try { text = fs.readFileSync(target.file, 'utf8'); } catch (error) {
565
+ if (error.code !== 'ENOENT') {
566
+ steps.push({ kind: 'mcp', detail: `keep ${target.file}: it could not be read`, outcome: 'failed', error: error.message });
567
+ continue;
568
+ }
569
+ }
570
+ const range = tomlTableRange(text, MCP_NAME);
571
+ if (range) { const c = tomlTableCommand(range); steps.push(existingNote(target.agent, c == null ? null : c)); continue; }
572
+ if (tomlMentions(text, MCP_NAME)) {
573
+ steps.push({ kind: 'mcp', detail: `${target.agent} declares ${MCP_NAME} in ${target.file} in a form the launcher does not edit; it was kept`, outcome: 'skipped' });
574
+ continue;
575
+ }
576
+ const entry = { command: mcpCommand, args: [] };
577
+ register({ kind: 'mcp', agent: target.agent, file: target.file, name: MCP_NAME, entry, backup: mcpBackup(target.file, target.agent) }, () => {
578
+ writeText(target.file, text ? `${text.endsWith('\n') ? text : `${text}\n`}\n${mcpTomlTable(mcpCommand)}` : mcpTomlTable(mcpCommand));
579
+ });
580
+ }
581
+ }
582
+ }
583
+
584
+ // 6. The launcher's own releases: keep this one and the one it replaced, remove the rest.
395
585
  // They are its own output, reinstallable from npm — not the person's files, which are
396
586
  // only ever moved to the quarantine.
397
587
  const stale = staleReleases(p, version, state.installed || null);
@@ -410,6 +600,9 @@ function update({ home, dryRun = false, claude = claudeRunner(), log = () => {}
410
600
  fresh.members = manifest.members.map((m) => ({ name: m.name, version: m.version, ref: m.ref, commit: m.commit }));
411
601
  fresh.quarantines = [...new Set([...(fresh.quarantines || []), ...(moved.length ? [quarantineDir] : [])])];
412
602
  if (state.legacyRemoved) fresh.legacyRemoved = state.legacyRemoved;
603
+ if (registered.length) {
604
+ fresh.mcpRegistrations = [...(fresh.mcpRegistrations || []).filter((r) => !registered.some((n) => n.file === r.file && n.name === r.name)), ...registered];
605
+ }
413
606
  delete fresh.updatingSince;
414
607
  writeJson(p.stateFile, fresh);
415
608
  }
@@ -430,6 +623,15 @@ function restore({ home, which, claude = claudeRunner() } = {}) {
430
623
  if (entry.restoredAt) continue;
431
624
  if (entry.kind === 'marketplace') {
432
625
  restored.push(restoreMarketplace(p, entry, claude));
626
+ } else if (entry.kind === 'mcp') {
627
+ const note = removeMcpRegistration(entry, claude);
628
+ if (note) restored.push(note);
629
+ const state = readState(p.stateFile);
630
+ if (state.mcpRegistrations) {
631
+ state.mcpRegistrations = state.mcpRegistrations.filter((r) => !(r.file === entry.file && r.name === entry.name));
632
+ if (!state.mcpRegistrations.length) delete state.mcpRegistrations;
633
+ writeJson(p.stateFile, state);
634
+ }
433
635
  } else if (lstat(entry.to)) {
434
636
  if (lstat(entry.from)) {
435
637
  // Old journals did not record the replacement. Only recognise a link
@@ -561,6 +763,13 @@ function uninstall({ home, claude = claudeRunner() } = {}) {
561
763
  fs.unlinkSync(p.current);
562
764
  }
563
765
  const state = readState(p.stateFile);
766
+ // The MCP registrations this launcher added — only while they are still exactly what
767
+ // it wrote; an entry a person changed since stays.
768
+ for (const record of state.mcpRegistrations || []) {
769
+ const note = removeMcpRegistration(record, claude);
770
+ if (note) removed.push(note);
771
+ }
772
+ delete state.mcpRegistrations;
564
773
  state.installed = null;
565
774
  state.members = [];
566
775
  state.config = { ...(state.config || {}), auto: false };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passioncode-ai/passioncode",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "description": "The PassionCode.ai agent skill set \u2014 Fabric Agent Adapter, Observatory Log and the organisation's working rules \u2014 for Claude Code and every other agent on the machine, updated as one set.",
5
5
  "bin": {
6
6
  "passioncode": "bin/passioncode.js"
@@ -11,8 +11,8 @@
11
11
  "name": "fabric-agent-adapter",
12
12
  "displayName": "Fabric Agent Adapter",
13
13
  "source": "./plugins/fabric-agent-adapter",
14
- "version": "0.21.0",
15
- "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
14
+ "version": "0.23.0",
15
+ "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
16
16
  "author": {
17
17
  "name": "PassionCode.ai",
18
18
  "url": "https://passioncode.ai/"
@@ -35,7 +35,7 @@
35
35
  "name": "passioncode",
36
36
  "displayName": "PassionCode.ai",
37
37
  "source": "./plugins/passioncode",
38
- "version": "0.1.45",
38
+ "version": "0.1.47",
39
39
  "description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
40
40
  "author": {
41
41
  "name": "PassionCode.ai",
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "family": "passioncode",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "release": true,
5
5
  "members": [
6
6
  {
7
7
  "name": "fabric-agent-adapter",
8
8
  "displayName": "Fabric Agent Adapter",
9
9
  "repo": "passioncode-ai/fabric-agent-adapter",
10
- "ref": "v0.21.0",
11
- "commit": "7dfbb0a7af36dc1a28e266c0ef248168dabc904f",
12
- "version": "0.21.0",
10
+ "ref": "v0.23.0",
11
+ "commit": "7dec8549cacb1aae600486405abc77508f30c077",
12
+ "version": "0.23.0",
13
13
  "via": "clone of https://***@github.com/passioncode-ai/fabric-agent-adapter.git",
14
14
  "skills": [
15
15
  "adapting-projects-to-fabric",
16
16
  "building-fabric-services",
17
- "creating-fabric-agents"
17
+ "creating-fabric-agents",
18
+ "working-with-fabric"
18
19
  ],
19
20
  "legacyPluginIds": [
20
21
  "fabric-agent-adapter@fabric-agent-adapter"
@@ -22,8 +23,8 @@
22
23
  "legacyMarketplaces": [
23
24
  "fabric-agent-adapter"
24
25
  ],
25
- "contentHash": "sha256:c849515947a8e3ccb28b9bcc570e5cb7a3b417eb5c61e5ab9e1d2a7e656c78ef",
26
- "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
26
+ "contentHash": "sha256:f2f2577b3438e3e495a1c9ce076c22398eb8d3ee40bbed19dac0476edb4187c4",
27
+ "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
27
28
  "author": {
28
29
  "name": "PassionCode.ai",
29
30
  "url": "https://passioncode.ai/"
@@ -61,16 +62,16 @@
61
62
  "name": "passioncode",
62
63
  "displayName": "PassionCode.ai",
63
64
  "repo": "passioncode-ai/passioncode",
64
- "ref": "v0.1.45",
65
+ "ref": "v0.1.47",
65
66
  "commit": null,
66
- "version": "0.1.45",
67
+ "version": "0.1.47",
67
68
  "via": "this repository",
68
69
  "skills": [
69
70
  "working-in-passioncode"
70
71
  ],
71
72
  "legacyPluginIds": [],
72
73
  "legacyMarketplaces": [],
73
- "contentHash": "sha256:9cca65f79d5971661e6593ae604ab184e1aef8de06c60fbc05b00240cabd84ac",
74
+ "contentHash": "sha256:199b4876498ad7cacf1a788ea5aa45013e80c0e7fcd1be33df11d6f7658458ef",
74
75
  "description": "The organisation's working rules for every contributor's agent (working-in-passioncode), and a once-a-day check that keeps the PassionCode.ai set current.",
75
76
  "author": {
76
77
  "name": "PassionCode.ai",
@@ -2,8 +2,8 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "fabric-agent-adapter",
4
4
  "displayName": "Fabric Agent Adapter",
5
- "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles.",
6
- "version": "0.21.0",
5
+ "description": "Create new Fabric-compatible agents, build them as always-alive local services with dashboards (fabric-service/0.1), and adapt existing projects to the Fabric Agent Contract through evidence-backed MCP, A2A, or local-runner provider bundles; and teach a coding agent to work in a project through Fabric (fabric-mcp, fabric_whoami, claim and release), with Claude Code session hooks.",
6
+ "version": "0.23.0",
7
7
  "author": {
8
8
  "name": "PassionCode.ai",
9
9
  "url": "https://passioncode.ai/"
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "One line at session start in a repository whose Fabric dashboard has no dashboard kit or an older one (fabric-dashboard/0.1, DEC-0036). Silent everywhere else; reads only, never writes, always exits 0.",
2
+ "description": "Two silent-unless-relevant hook sets. The dashboard-kit notice: one line at session start in a repository whose Fabric dashboard has no kit or an older one (fabric-dashboard/0.1, DEC-0036). The Fabric session hooks (working-with-fabric, COM-04, FAA-12a): at session start the preamble when Claude Code has the MCP server `fabric` (fabric_whoami, then the project board through com.status and com.list, claim and release), and the machine's Fabric-service count; each turn's facts (HEAD, files changed, lines added and removed, never content), the board's unread count and the session's end through Fabric, only when the running Fabric advertises session-hooks/1; at each prompt one line only when the unread count rose. All silent in a session Fabric launched. Every hook reads no token, prints at most one block, always exits 0, and hands any Fabric call to a detached worker with a 10 s deadline.",
3
3
  "hooks": {
4
4
  "SessionStart": [
5
5
  {
@@ -11,6 +11,48 @@
11
11
  "timeout": 5
12
12
  }
13
13
  ]
14
+ },
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-start",
20
+ "timeout": 5
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "Stop": [
26
+ {
27
+ "hooks": [
28
+ {
29
+ "type": "command",
30
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook stop",
31
+ "timeout": 5
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "SessionEnd": [
37
+ {
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook session-end",
42
+ "timeout": 5
43
+ }
44
+ ]
45
+ }
46
+ ],
47
+ "UserPromptSubmit": [
48
+ {
49
+ "hooks": [
50
+ {
51
+ "type": "command",
52
+ "command": "python3 -S \"${CLAUDE_PLUGIN_ROOT}/skills/working-with-fabric/scripts/fabric_session.py\" hook user-prompt-submit",
53
+ "timeout": 5
54
+ }
55
+ ]
14
56
  }
15
57
  ]
16
58
  }
@@ -5,9 +5,9 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
5
5
  compatibility: Requires filesystem access and Python 3.9+. Exact schema checks additionally need git, Node.js, pnpm, and the pinned fabric-agent-contract checkout (a public repository). Works without those tools in an explicitly degraded structural-check mode.
6
6
  metadata:
7
7
  author: PassionCode.ai
8
- version: "0.21.0"
8
+ version: "0.23.0"
9
9
  contract-version: "0.1.0"
10
- contract-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
10
+ contract-commit: "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
11
11
  ---
12
12
 
13
13
  # Adapting projects to Fabric
@@ -70,7 +70,7 @@ Use exactly:
70
70
 
71
71
  - contract version `0.1.0`;
72
72
  - repository `https://github.com/passioncode-ai/fabric-agent-contract`;
73
- - commit `b291bf09e0a9f9fd3c16cf540832cd021203b7d1`.
73
+ - commit `fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0`.
74
74
 
75
75
  This is the default for new bundles. When checking an issued legacy bundle, preserve its
76
76
  explicit supported lock and use that exact clean checkout. Never silently upgrade or
@@ -21,10 +21,10 @@ import fabric_secrets as _secrets # noqa: E402 - the contract's credential dete
21
21
 
22
22
  CONTRACT_VERSION = "0.1.0"
23
23
  CONTRACT_REPOSITORY = "https://github.com/passioncode-ai/fabric-agent-contract"
24
- CONTRACT_COMMIT = "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
24
+ CONTRACT_COMMIT = "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
25
25
  # #region supported-contract-revisions — docs: README.md#contract-pin
26
26
  # Only this declaration may name a legacy revision in live files.
27
- SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
27
+ SUPPORTED_CONTRACT_COMMITS = (CONTRACT_COMMIT, "b291bf09e0a9f9fd3c16cf540832cd021203b7d1", "0cacd08b0e95b6abf131eae1206b60792138bc3c", "2eb91c00e852d9c31d804310d38fad91589fcbac", "8e49fbd5fbbf230e32c1969cd24ac3c22422f0a5", "a83bec6b47776af688402c18efec507143061f68", "52da526d7cc1e063d74c5b520dbc03d0ace15246", "78b2da018d2c4bd9ac1bde1dc3b7f1a110a175f5", "623bf61358c339cb10297807b3f024b5d9f1f327", "23f9fda4c05f8a3852246ee98d4f2adf74ed0875", "9091d3d6606b0c4b5591a38c3674356a01fd7f55", "df55c8c54a23251342a7ee57ba95642b7eb39e61", "2ce392291c6668598d12cd38327e24696b5ca15c")
28
28
  # #endregion supported-contract-revisions
29
29
  INTEROP_KEY = "https://fabric.passioncode.ai/agent-contract/extensions/interop/0.1"
30
30
  MCP_REVISION = "2026-07-28"
@@ -16,10 +16,10 @@ license: AGPL-3.0-only OR LicenseRef-PassionCode-Commercial
16
16
  compatibility: Python 3.9+ or Node.js 20+ for the kits; the probe needs Python 3.9+. launchd steps are macOS-only; Linux services install a systemd user unit and Windows ones a per-user Scheduled Task with the kits' writers. Dashboard handoff optionally uses Fabric Dashboards MCP link/host_status/open; without it, report unresolved host capability. The contract checkout is optional.
17
17
  metadata:
18
18
  author: PassionCode.ai
19
- version: "0.21.0"
19
+ version: "0.23.0"
20
20
  contract-version: "0.1.0"
21
21
  extension: "fabric-service/0.1"
22
- extension-commit: "b291bf09e0a9f9fd3c16cf540832cd021203b7d1"
22
+ extension-commit: "fe55fc67cba1a9e34a736da7d148d4fe2a8fbfa0"
23
23
  ---
24
24
 
25
25
  # Building Fabric services
@@ -95,16 +95,19 @@ itself (`isSmallOrderKey`), because Node before 26 lets OpenSSL accept a signatu
95
95
 
96
96
  ## Keys the estate record certifies
97
97
 
98
- A verifier that also reads the estate's record (DEC-0055, [the estate record reference](estate-record.md)) trusts a
99
- `kid` only while a current revision certifies it for a machine and does not revoke it (DL-14). Pass the current
100
- verified revision as `estate=` / `{ estate }`, as a value or as a function returning it, to `delegation_gate`,
98
+ A verifier that also reads the estate's record (DEC-0055, DEC-0060, [the estate record reference](estate-record.md))
99
+ trusts a `kid` only while the revision it reads certifies it (DL-14, row 4a, right after the signature). Pass the
100
+ current verified revision as `estate=` / `{ estate }`, as a value or as a function returning it, with
101
+ `estate_applied` / `estateApplied` and `estate_revocations` / `estateRevocations`, to `delegation_gate`,
101
102
  `verify_delegation` or `verify_approval`.
102
- - An uncertified `kid` is refused `unauthenticated` (`approval-invalid` for an approval), with detail
103
+ - An uncertified `kid` is the token's fault: `unauthenticated` (`approval-invalid` for an approval), detail
103
104
  `kid-not-certified`.
104
- - An unreadable record is refused `issuer-unavailable`, with detail `estate-unreadable`.
105
+ - A revision that cannot be read, is outside its window, is lower than the applied one or forks it is the trust
106
+ source's fault: `issuer-unavailable`, detail `estate-unreadable`, `estate-outside-window`, `estate-rolled-back` or
107
+ `estate-forked`.
105
108
  - Without `estate`, nothing changes.
106
- - Pass only a revision `verify_estate_record` accepted, and the highest one the machine has applied. The rule trusts
107
- what it is given, so an unverified or older revision would certify a key the current one revoked.
109
+ - Every verifier refuses a clock that is not a number of seconds `unknown-state` `no-clock` before row 1: a NaN clock
110
+ would make every time row false and pass an expired token.
108
111
 
109
112
  ## What the kit does not do
110
113