@ijfw/memory-server 1.4.4 → 1.5.1

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 (245) hide show
  1. package/bin/ijfw-memorize +14 -7
  2. package/fixtures/team/book.json +6 -6
  3. package/fixtures/team/business.json +146 -20
  4. package/fixtures/team/content.json +6 -6
  5. package/fixtures/team/design.json +148 -20
  6. package/fixtures/team/mixed.json +206 -27
  7. package/fixtures/team/research.json +146 -20
  8. package/fixtures/team/software.json +148 -20
  9. package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
  10. package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
  11. package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
  12. package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
  13. package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
  14. package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
  15. package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
  16. package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
  17. package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
  18. package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
  19. package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
  20. package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
  21. package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
  22. package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
  23. package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
  24. package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
  25. package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
  26. package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
  27. package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
  28. package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
  29. package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
  30. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
  31. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
  32. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
  33. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
  34. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
  35. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
  36. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
  37. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
  38. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
  39. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
  40. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
  41. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
  42. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
  43. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
  44. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
  45. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
  46. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
  47. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
  48. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
  49. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
  50. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
  51. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
  52. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
  53. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
  54. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
  55. package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
  56. package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
  57. package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
  58. package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
  59. package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
  60. package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
  61. package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
  62. package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  63. package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
  64. package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
  65. package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
  66. package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
  67. package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
  68. package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
  69. package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
  70. package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  71. package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
  72. package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
  73. package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
  74. package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
  75. package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
  76. package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
  77. package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
  78. package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
  79. package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
  80. package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
  81. package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
  82. package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
  83. package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
  84. package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
  85. package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
  86. package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
  87. package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
  88. package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
  89. package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
  90. package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
  91. package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
  92. package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
  93. package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
  94. package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
  95. package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
  96. package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
  97. package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
  98. package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
  99. package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
  100. package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
  101. package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
  102. package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
  103. package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
  104. package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
  105. package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
  106. package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
  107. package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
  108. package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  109. package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
  110. package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
  111. package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
  112. package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
  113. package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
  114. package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
  115. package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
  116. package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
  117. package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  118. package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
  119. package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
  120. package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
  121. package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
  122. package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
  123. package/package.json +6 -3
  124. package/src/active-extension-writer.js +144 -64
  125. package/src/api-client.js +43 -5
  126. package/src/audit-roster.js +80 -5
  127. package/src/blackboard.js +298 -6
  128. package/src/cli-run.js +33 -5
  129. package/src/codex-agents.js +96 -5
  130. package/src/cost/aggregator.js +39 -9
  131. package/src/cost/pricing.js +57 -0
  132. package/src/cost/readers/gemini.js +1 -1
  133. package/src/cross-audit-chunker.js +189 -0
  134. package/src/cross-dispatcher.js +124 -21
  135. package/src/cross-orchestrator-cli.js +754 -159
  136. package/src/cross-orchestrator.js +1065 -17
  137. package/src/cross-project-search.js +195 -9
  138. package/src/dashboard-client-waves.html +304 -0
  139. package/src/dashboard-client.html +5 -1
  140. package/src/dashboard-server.js +73 -0
  141. package/src/deploy-alerts.js +150 -0
  142. package/src/design/iframe-bridge.js +242 -0
  143. package/src/design-companion.js +144 -0
  144. package/src/dispatch/checkpoint-cli.js +97 -0
  145. package/src/dispatch/colon-syntax.js +81 -1
  146. package/src/dispatch/extension.js +26 -2
  147. package/src/dispatch/registry-cli.js +4 -1
  148. package/src/dispatch/wave-cli.js +201 -6
  149. package/src/dispatch/worktree-cli.js +40 -0
  150. package/src/dispatch-planner.js +97 -2
  151. package/src/dream/runner.mjs +47 -11
  152. package/src/dream/stage-runner.js +40 -0
  153. package/src/dream/state-file.js +102 -0
  154. package/src/extension-installer.js +70 -24
  155. package/src/extension-quota-tracker.js +4 -2
  156. package/src/extension-registry.js +289 -35
  157. package/src/feedback-detector.js +26 -0
  158. package/src/fs-lock.js +259 -7
  159. package/src/gate-result.js +95 -1
  160. package/src/hardware-signer.js +4 -2
  161. package/src/hero-line.js +86 -5
  162. package/src/intent-router.js +35 -0
  163. package/src/lib/a11y-contract.js +117 -0
  164. package/src/lib/atomic-io.js +29 -8
  165. package/src/lib/cache-keepalive.js +150 -0
  166. package/src/lib/jsonl-rotation.js +104 -0
  167. package/src/lib/lighthouse-pillar.js +121 -0
  168. package/src/lib/llm-call.js +121 -0
  169. package/src/lib/playwright-baseline.js +205 -0
  170. package/src/lib/rekor-bridge.js +221 -0
  171. package/src/lib/repo-map.js +392 -0
  172. package/src/lib/shasum-verify.js +164 -0
  173. package/src/lib/sketches-gc.js +132 -0
  174. package/src/lib/tmp-suffix.js +62 -0
  175. package/src/lib/ui-review-runner.js +595 -0
  176. package/src/lib/uispec-drift.js +301 -0
  177. package/src/lib/uispec-intake.js +381 -0
  178. package/src/lib/worktree-guards.js +118 -0
  179. package/src/lib/worktree-recovery.js +100 -0
  180. package/src/memory/auto-linker.js +267 -0
  181. package/src/memory/benchmark.js +498 -0
  182. package/src/memory/dedup.js +126 -0
  183. package/src/memory/embedding-cache.js +136 -0
  184. package/src/memory/fact-extractor.js +168 -0
  185. package/src/memory/fts5.js +65 -1
  186. package/src/memory/migration-runner.js +6 -1
  187. package/src/memory/migrations/004-bitemporal.js +91 -0
  188. package/src/memory/migrations/005-vector-cache.js +61 -0
  189. package/src/memory/migrations/006-obsidian-graph.js +46 -0
  190. package/src/memory/migrations/007-skill-telemetry.js +24 -0
  191. package/src/memory/migrations/008-write-provenance.js +41 -0
  192. package/src/memory/migrations/009-obsidian-backfill.js +50 -0
  193. package/src/memory/obsidian-parser.js +152 -0
  194. package/src/memory/query-dataview.js +86 -0
  195. package/src/memory/search.js +46 -15
  196. package/src/memory/temporal.js +529 -0
  197. package/src/memory/tokenize.js +10 -0
  198. package/src/memory-facts-handler.js +37 -0
  199. package/src/memory-feedback.js +260 -2
  200. package/src/model-refresh.js +292 -0
  201. package/src/observability/cost-anomaly.js +166 -0
  202. package/src/observability/evaluator-checkpoint-contract.js +117 -0
  203. package/src/observability/trace-id.js +163 -0
  204. package/src/orchestrator/agents-md-blackboard.js +152 -0
  205. package/src/orchestrator/checkpoint-contract.md +140 -0
  206. package/src/orchestrator/debug-trident-trigger.js +374 -0
  207. package/src/orchestrator/debug-trident.js +570 -0
  208. package/src/orchestrator/merge-block-aware.js +350 -0
  209. package/src/orchestrator/plan-checker.js +475 -0
  210. package/src/orchestrator/post-done-runner.js +277 -0
  211. package/src/orchestrator/review.js +38 -3
  212. package/src/orchestrator/skill-telemetry-sink.js +29 -0
  213. package/src/orchestrator/skill-telemetry.js +37 -0
  214. package/src/orchestrator/state-events.js +459 -0
  215. package/src/orchestrator/state-sdk.js +1932 -0
  216. package/src/orchestrator/status-protocol.js +84 -17
  217. package/src/orchestrator/subagent-telemetry.js +471 -0
  218. package/src/orchestrator/termination.js +160 -0
  219. package/src/orchestrator/verification-gate.js +200 -16
  220. package/src/orchestrator/wave-state.js +332 -23
  221. package/src/orchestrator/worktree-provision.js +77 -0
  222. package/src/override-resolver.js +5 -3
  223. package/src/override-use-registry.js +111 -5
  224. package/src/receipts.js +36 -4
  225. package/src/recovery/checkpoint.js +56 -3
  226. package/src/recovery/code-fixer.js +961 -0
  227. package/src/recovery/truncation.js +317 -0
  228. package/src/redactor.js +75 -6
  229. package/src/runtime-mediator.js +15 -1
  230. package/src/sanitizer.js +10 -0
  231. package/src/search-hybrid.js +139 -0
  232. package/src/server.js +795 -112
  233. package/src/swarm/worktree.js +27 -4
  234. package/src/swarm-config.js +102 -17
  235. package/src/team/domain-templates/book.json +51 -0
  236. package/src/team/domain-templates/business.json +44 -0
  237. package/src/team/domain-templates/content.json +50 -0
  238. package/src/team/domain-templates/design.json +44 -0
  239. package/src/team/domain-templates/research.json +44 -0
  240. package/src/team/domain-templates/software.json +40 -0
  241. package/src/team/generator.js +440 -3
  242. package/src/team/modify.js +203 -0
  243. package/src/team/schemas.js +48 -0
  244. package/src/update-apply.js +19 -3
  245. package/src/dashboard-charts.js +0 -239
@@ -0,0 +1,203 @@
1
+ // F-FUN-4 + F-FUN-5 (audit-MED-teams-#7 + #13): team-charter mutation
2
+ // helpers. Honors the SKILL.md "Custom Agent Requests" promise -- users can
3
+ // add, remove, swap, list, and check roles on their custom team without
4
+ // hand-editing JSON.
5
+ //
6
+ // Every mutating helper:
7
+ // 1. Reads the current charter/workflow.
8
+ // 2. Applies the mutation in-memory.
9
+ // 3. Validates the resulting charter via team/schemas.js.
10
+ // 4. Writes the charter back atomically.
11
+ // 5. Re-runs syncCodexAgents so .codex/agents/ stays in lockstep.
12
+ //
13
+ // `checkTeamAssembly` is the standalone validator surfaced by
14
+ // `ijfw team check` -- it does not write anything and does not require
15
+ // `ijfw swarm plan` to surface error messages.
16
+
17
+ import { existsSync, readFileSync } from 'node:fs';
18
+ import { join, resolve } from 'node:path';
19
+ import { writeAtomic } from '../lib/atomic-io.js';
20
+ import { syncCodexAgents } from '../codex-agents.js';
21
+ import { readTeamAssembly } from './generator.js';
22
+ import { validateTeamCharter, validateWorkflowManifest } from './schemas.js';
23
+
24
+ function teamPaths(root) {
25
+ return {
26
+ charterPath: join(root, '.ijfw', 'team', 'charter.json'),
27
+ workflowPath: join(root, '.ijfw', 'team', 'workflow.json'),
28
+ };
29
+ }
30
+
31
+ function loadCharter(root) {
32
+ const { charterPath } = teamPaths(root);
33
+ if (!existsSync(charterPath)) return null;
34
+ return JSON.parse(readFileSync(charterPath, 'utf8'));
35
+ }
36
+
37
+ function loadWorkflow(root) {
38
+ const { workflowPath } = teamPaths(root);
39
+ if (!existsSync(workflowPath)) return null;
40
+ return JSON.parse(readFileSync(workflowPath, 'utf8'));
41
+ }
42
+
43
+ function writeCharter(root, charter) {
44
+ const { charterPath } = teamPaths(root);
45
+ writeAtomic(charterPath, `${JSON.stringify(charter, null, 2)}\n`, { mode: 0o600 });
46
+ return charterPath;
47
+ }
48
+
49
+ /**
50
+ * List the current roles + summary metadata for the team. Returns a
51
+ * structured payload so callers (CLI + future MCP tool) can render however
52
+ * they like.
53
+ */
54
+ export function listTeamRoles(projectRoot = process.cwd()) {
55
+ const root = resolve(projectRoot);
56
+ const charter = loadCharter(root);
57
+ if (!charter) return { ok: false, error: 'missing-charter' };
58
+ const roles = Array.isArray(charter.roles) ? charter.roles : [];
59
+ return {
60
+ ok: true,
61
+ team_name: charter.team_name || null,
62
+ project_archetypes: charter.project_archetypes || [],
63
+ roles: roles.map((r) => ({
64
+ name: r.name,
65
+ role_type: r.role_type,
66
+ model: r.model,
67
+ effort: r.effort || 'medium',
68
+ phase_scope: r.phase_scope || [],
69
+ })),
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Add a role to the charter. The charter is supplied as a JSON path; we
75
+ * read+validate it before splicing in. Triggers a codex agent re-sync.
76
+ */
77
+ export function addTeamRole(projectRoot, { charterPath, role }) {
78
+ const root = resolve(projectRoot);
79
+ const charter = loadCharter(root);
80
+ if (!charter) return { ok: false, error: 'missing-charter' };
81
+
82
+ const incoming = role || readJsonFile(charterPath);
83
+ if (!incoming || typeof incoming !== 'object') return { ok: false, error: 'invalid-role-payload' };
84
+ if (!incoming.name) return { ok: false, error: 'role-name-required' };
85
+
86
+ const existing = (charter.roles || []).find((r) => r.name === incoming.name);
87
+ if (existing) return { ok: false, error: 'role-exists', name: incoming.name };
88
+
89
+ const next = { ...charter, roles: [...(charter.roles || []), incoming] };
90
+ const validation = validateTeamCharter(next);
91
+ if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
92
+
93
+ writeCharter(root, next);
94
+ const codex = syncCodexAgents(root);
95
+ return { ok: true, role: incoming, codex };
96
+ }
97
+
98
+ /**
99
+ * Remove a role by name. Refuses if removing the role would leave the
100
+ * charter invalid (e.g. last role).
101
+ */
102
+ export function removeTeamRole(projectRoot, { name }) {
103
+ const root = resolve(projectRoot);
104
+ const charter = loadCharter(root);
105
+ if (!charter) return { ok: false, error: 'missing-charter' };
106
+
107
+ const before = (charter.roles || []).length;
108
+ const roles = (charter.roles || []).filter((r) => r.name !== name);
109
+ if (roles.length === before) return { ok: false, error: 'role-not-found', name };
110
+
111
+ const next = { ...charter, roles };
112
+ const validation = validateTeamCharter(next);
113
+ if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
114
+
115
+ writeCharter(root, next);
116
+ const codex = syncCodexAgents(root);
117
+ return { ok: true, removed: name, codex };
118
+ }
119
+
120
+ /**
121
+ * Replace one role with another. `oldName` must exist; `replacement` is a
122
+ * full role object (or path to a JSON file describing one).
123
+ */
124
+ export function swapTeamRole(projectRoot, { oldName, charterPath, replacement }) {
125
+ const root = resolve(projectRoot);
126
+ const charter = loadCharter(root);
127
+ if (!charter) return { ok: false, error: 'missing-charter' };
128
+
129
+ const incoming = replacement || readJsonFile(charterPath);
130
+ if (!incoming || typeof incoming !== 'object') return { ok: false, error: 'invalid-role-payload' };
131
+ if (!incoming.name) return { ok: false, error: 'role-name-required' };
132
+
133
+ const roles = charter.roles || [];
134
+ const idx = roles.findIndex((r) => r.name === oldName);
135
+ if (idx === -1) return { ok: false, error: 'role-not-found', name: oldName };
136
+
137
+ // Name collision when the replacement keeps a *different* existing name.
138
+ if (incoming.name !== oldName && roles.some((r) => r.name === incoming.name)) {
139
+ return { ok: false, error: 'role-exists', name: incoming.name };
140
+ }
141
+
142
+ const nextRoles = roles.slice();
143
+ nextRoles[idx] = incoming;
144
+ const next = { ...charter, roles: nextRoles };
145
+ const validation = validateTeamCharter(next);
146
+ if (!validation.ok) return { ok: false, error: 'invalid-charter', errors: validation.errors };
147
+
148
+ writeCharter(root, next);
149
+ const codex = syncCodexAgents(root);
150
+ return { ok: true, swapped: { old: oldName, new: incoming.name }, codex };
151
+ }
152
+
153
+ /**
154
+ * F-FUN-5 (audit-MED-teams-#13): standalone team validation. Returns a
155
+ * structured pass/fail report so the CLI can render a human-readable list
156
+ * without forcing the caller into `ijfw swarm plan`.
157
+ */
158
+ export function checkTeamAssembly(projectRoot = process.cwd()) {
159
+ const root = resolve(projectRoot);
160
+ const charter = loadCharter(root);
161
+ const workflow = loadWorkflow(root);
162
+ const report = {
163
+ ok: true,
164
+ has_charter: Boolean(charter),
165
+ has_workflow: Boolean(workflow),
166
+ charter: { ok: true, errors: [] },
167
+ workflow: { ok: true, errors: [] },
168
+ role_count: 0,
169
+ artifact_count: 0,
170
+ };
171
+ if (!charter) {
172
+ report.ok = false;
173
+ report.charter = { ok: false, errors: ['charter.json is missing -- run: ijfw team init'] };
174
+ } else {
175
+ const c = validateTeamCharter(charter);
176
+ report.charter = c;
177
+ report.role_count = Array.isArray(charter.roles) ? charter.roles.length : 0;
178
+ if (!c.ok) report.ok = false;
179
+ }
180
+ if (!workflow) {
181
+ report.ok = false;
182
+ report.workflow = { ok: false, errors: ['workflow.json is missing -- run: ijfw team init'] };
183
+ } else {
184
+ const w = validateWorkflowManifest(workflow, charter);
185
+ report.workflow = w;
186
+ report.artifact_count = Array.isArray(workflow.artifacts) ? workflow.artifacts.length : 0;
187
+ if (!w.ok) report.ok = false;
188
+ }
189
+ return report;
190
+ }
191
+
192
+ function readJsonFile(path) {
193
+ if (!path) return null;
194
+ if (!existsSync(path)) return null;
195
+ try {
196
+ return JSON.parse(readFileSync(path, 'utf8'));
197
+ } catch {
198
+ return null;
199
+ }
200
+ }
201
+
202
+ // Re-exports for the CLI surface so it can `import * as teamModify`.
203
+ export { readTeamAssembly };
@@ -10,6 +10,54 @@ const ARCHETYPES = new Set([
10
10
  'mixed',
11
11
  ]);
12
12
 
13
+ // T24 / G7-core: canonical software-core agent ids. Mirrored by
14
+ // `SOFTWARE_CORE_AGENT_IDS` in generator.js — this re-export gives
15
+ // schema-side consumers (validators, downstream tools, T25's domain-aware
16
+ // generator) a single source of truth without circular import on
17
+ // generator.js. Any change to the set MUST update both locations and the
18
+ // matching `claude/agents/<id>.md` files on disk.
19
+ export const SOFTWARE_CORE_AGENT_IDS = Object.freeze([
20
+ 'ijfw-doc-verifier',
21
+ 'ijfw-integration-checker',
22
+ 'ijfw-nyquist-auditor',
23
+ 'ijfw-code-fixer',
24
+ ]);
25
+
26
+ // T25 / G7-gen: canonical domain-specialist agent ids by archetype. The map
27
+ // is the single source of truth for which specialist ids the generator
28
+ // returns for each domain — T26 will author the matching
29
+ // `claude/agents/<id>.md` files so the installer can deploy them; T25 just
30
+ // makes the generator hand them back per archetype.
31
+ //
32
+ // Contract:
33
+ // - keys are normalized archetypes (post-alias-map). `book`, `content`,
34
+ // and `design` are populated today; other archetypes get `[]` until
35
+ // T26 expands the set.
36
+ // - values are deterministic arrays; insertion order is roster-display order.
37
+ // - software domain's specialists are the SOFTWARE_CORE_AGENT_IDS — this
38
+ // map intentionally does NOT duplicate them (`resolveDomainSpecialistAgentIds`
39
+ // in generator.js returns the union for software). Non-software domains
40
+ // own their own specialist lists exclusively.
41
+ //
42
+ // Choice of three+ domains (`book`, `content`, `design`) satisfies the
43
+ // "≥3 non-software domains" constraint and gives T26 concrete templates
44
+ // to flesh out. Other archetypes will gain rosters in later tasks.
45
+ export const DOMAIN_SPECIALIST_AGENT_IDS = Object.freeze({
46
+ book: Object.freeze([
47
+ 'ijfw-narrative-continuity-checker',
48
+ 'ijfw-line-editor',
49
+ 'ijfw-lore-keeper',
50
+ ]),
51
+ content: Object.freeze([
52
+ 'ijfw-campaign-strategist',
53
+ 'ijfw-copy-reviewer',
54
+ ]),
55
+ design: Object.freeze([
56
+ 'ijfw-design-critic',
57
+ 'ijfw-accessibility-reviewer',
58
+ ]),
59
+ });
60
+
13
61
  const ROLE_TYPES = new Set([
14
62
  'lead',
15
63
  'software',
@@ -1,5 +1,14 @@
1
1
  // MCP tool: ijfw_update_apply
2
2
  //
3
+ // @deprecated since v1.5.0; will be removed in v1.6.0 (F-FUN-3 / v1.5.0 audit-MED-M7).
4
+ // `ijfw_update_check` already issues a confirmation token whose instruction tells
5
+ // the user to type `ijfw update --confirm <token>` in their terminal directly.
6
+ // The intermediate `ijfw_update_apply` step writes a pending sentinel, but the
7
+ // terminal CLI does not require the sentinel to confirm — the token itself is
8
+ // authoritative. The tool is retained for v1.5.0 back-compat (older skills that
9
+ // still call it work unchanged) and slated for retirement in v1.6.0 to free the
10
+ // MCP-tool slot (see CLAUDE.md "MCP server: ≤13 tools" cap).
11
+ //
3
12
  // Does NOT execute the update. Validates the token, writes (or overwrites)
4
13
  // the pending sentinel, returns instruction telling the user to run the
5
14
  // terminal-side confirm command. Idempotent against a matching sentinel
@@ -10,6 +19,11 @@
10
19
  import { validateToken, writePendingSentinel } from './lib/token.js';
11
20
  import { isVersionStringValid } from './lib/npm-view.js';
12
21
 
22
+ /**
23
+ * @deprecated since v1.5.0; scheduled for removal in v1.6.0. Callers should
24
+ * skip straight from `ijfw_update_check` to the terminal-side confirm command;
25
+ * the intermediate sentinel write is redundant given the token contract.
26
+ */
13
27
  export function ijfwUpdateApply(args = {}) {
14
28
  const { target_version, confirmation_token } = args || {};
15
29
  const sessionId = args.session_id || process.env.IJFW_SESSION_ID || 'default-session';
@@ -64,9 +78,11 @@ export function ijfwUpdateApply(args = {}) {
64
78
  export const TOOL_DEF = {
65
79
  name: 'ijfw_update_apply',
66
80
  description:
67
- 'Stage an IJFW update behind out-of-band terminal confirmation. Writes a pending sentinel; ' +
68
- "actual update only runs when the user types 'ijfw update --confirm <token>' in their terminal. " +
69
- 'This MCP tool NEVER executes the update directly.',
81
+ '[DEPRECATED v1.5.0; removal in v1.6.0] Stage an IJFW update behind out-of-band terminal ' +
82
+ 'confirmation. Writes a pending sentinel; actual update only runs when the user types ' +
83
+ "'ijfw update --confirm <token>' in their terminal. This MCP tool NEVER executes the " +
84
+ 'update directly. Prefer calling ijfw_update_check and forwarding the returned ' +
85
+ "'ijfw update --confirm <token>' instruction directly to the user.",
70
86
  inputSchema: {
71
87
  type: 'object',
72
88
  required: ['target_version', 'confirmation_token'],
@@ -1,239 +0,0 @@
1
- /**
2
- * dashboard-charts.js — IJFW v1.4.3 W9-C (B19)
3
- *
4
- * Pure-JS canvas/DOM chart helpers for the dashboard. No external libs.
5
- * Theme-aware: reads CSS custom properties `--ijfw-chart-fg`,
6
- * `--ijfw-chart-bg`, `--ijfw-chart-warning` from the element's computed
7
- * style. Falls back to sane defaults if the host page hasn't set them.
8
- *
9
- * All helpers are defensive against malformed input so a bad payload from
10
- * the API can never throw inside the dashboard render loop.
11
- */
12
-
13
- const DEFAULTS = {
14
- fg: '#9ad2ff',
15
- bg: 'rgba(154,210,255,0.18)',
16
- warning: '#ff9b3a',
17
- text: '#cfd6dd',
18
- };
19
-
20
- function _readTheme(el) {
21
- // Both `canvas` and plain `div` go through `getComputedStyle`. In the test
22
- // environment the element is a hand-rolled mock that doesn't reach the DOM
23
- // — guard so we don't blow up if `getComputedStyle` is unavailable.
24
- let cs = null;
25
- try {
26
- if (el && typeof globalThis.getComputedStyle === 'function') {
27
- cs = globalThis.getComputedStyle(el);
28
- }
29
- } catch { cs = null; }
30
- const read = (prop, fallback) => {
31
- if (!cs || typeof cs.getPropertyValue !== 'function') return fallback;
32
- const v = cs.getPropertyValue(prop);
33
- return (v && v.trim()) ? v.trim() : fallback;
34
- };
35
- return {
36
- fg: read('--ijfw-chart-fg', DEFAULTS.fg),
37
- bg: read('--ijfw-chart-bg', DEFAULTS.bg),
38
- warning: read('--ijfw-chart-warning', DEFAULTS.warning),
39
- text: read('--ijfw-chart-text', DEFAULTS.text),
40
- };
41
- }
42
-
43
- function _safeNum(v, def = 0) {
44
- return (typeof v === 'number' && Number.isFinite(v)) ? v : def;
45
- }
46
-
47
- /**
48
- * lineChart(canvas, points, opts)
49
- * points: [{ x: number, y: number }, ...] OR [number, ...]
50
- * opts: { xMin?, xMax?, yMax?, color?, fill? }
51
- *
52
- * Empty data renders nothing (clears the canvas) without throwing.
53
- */
54
- export function lineChart(canvas, points, opts = {}) {
55
- if (!canvas || typeof canvas.getContext !== 'function') return;
56
- const ctx = canvas.getContext('2d');
57
- if (!ctx) return;
58
- const W = _safeNum(canvas.width, 200);
59
- const H = _safeNum(canvas.height, 100);
60
- const theme = _readTheme(canvas);
61
- const color = opts.color || theme.fg;
62
- const fill = opts.fill === false ? null : theme.bg;
63
-
64
- try { ctx.clearRect(0, 0, W, H); } catch {}
65
-
66
- const pts = Array.isArray(points) ? points : [];
67
- const norm = pts.map((p, i) => {
68
- if (typeof p === 'number') return { x: i, y: _safeNum(p, 0) };
69
- return { x: _safeNum(p && p.x, i), y: _safeNum(p && p.y, 0) };
70
- }).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
71
-
72
- if (norm.length === 0) return;
73
-
74
- let xMin = _safeNum(opts.xMin, norm[0].x);
75
- let xMax = _safeNum(opts.xMax, norm[norm.length - 1].x);
76
- if (xMax === xMin) xMax = xMin + 1;
77
- const yMax = _safeNum(opts.yMax, Math.max(1, ...norm.map((p) => p.y)));
78
- const yScale = yMax > 0 ? yMax : 1;
79
-
80
- const pad = 4;
81
- const innerW = Math.max(1, W - pad * 2);
82
- const innerH = Math.max(1, H - pad * 2);
83
-
84
- const px = (x) => pad + ((x - xMin) / (xMax - xMin)) * innerW;
85
- const py = (y) => pad + (1 - (Math.max(0, y) / yScale)) * innerH;
86
-
87
- // Filled area
88
- if (fill) {
89
- try {
90
- ctx.beginPath();
91
- ctx.moveTo(px(norm[0].x), H - pad);
92
- for (const p of norm) ctx.lineTo(px(p.x), py(p.y));
93
- ctx.lineTo(px(norm[norm.length - 1].x), H - pad);
94
- ctx.closePath();
95
- ctx.fillStyle = fill;
96
- ctx.fill();
97
- } catch {}
98
- }
99
-
100
- // Line stroke
101
- try {
102
- ctx.beginPath();
103
- ctx.moveTo(px(norm[0].x), py(norm[0].y));
104
- for (let i = 1; i < norm.length; i++) ctx.lineTo(px(norm[i].x), py(norm[i].y));
105
- ctx.strokeStyle = color;
106
- ctx.lineWidth = 1.5;
107
- ctx.stroke();
108
- } catch {}
109
- }
110
-
111
- /**
112
- * barChart(canvas, bars, opts)
113
- * bars: [{ label: string, value: number, color?: string }, ...]
114
- * opts: { horizontal?: boolean, color?: string, maxValue?: number }
115
- *
116
- * Zero-value bars render as empty rails. Negative values are clamped to 0.
117
- */
118
- export function barChart(canvas, bars, opts = {}) {
119
- if (!canvas || typeof canvas.getContext !== 'function') return;
120
- const ctx = canvas.getContext('2d');
121
- if (!ctx) return;
122
- const W = _safeNum(canvas.width, 200);
123
- const H = _safeNum(canvas.height, 100);
124
- const theme = _readTheme(canvas);
125
- const defaultColor = opts.color || theme.fg;
126
- const horizontal = Boolean(opts.horizontal);
127
-
128
- try { ctx.clearRect(0, 0, W, H); } catch {}
129
-
130
- const rows = Array.isArray(bars) ? bars.filter((b) => b && typeof b === 'object') : [];
131
- if (rows.length === 0) return;
132
-
133
- const values = rows.map((b) => Math.max(0, _safeNum(b.value, 0)));
134
- const maxVal = _safeNum(opts.maxValue, Math.max(1, ...values));
135
- const scale = maxVal > 0 ? maxVal : 1;
136
-
137
- const pad = 4;
138
- const labelGutter = horizontal ? 80 : 14;
139
- const innerW = Math.max(1, W - pad * 2 - (horizontal ? labelGutter : 0));
140
- const innerH = Math.max(1, H - pad * 2 - (horizontal ? 0 : labelGutter));
141
- const slot = (horizontal ? innerH : innerW) / rows.length;
142
- const barW = Math.max(1, slot * 0.7);
143
-
144
- for (let i = 0; i < rows.length; i++) {
145
- const b = rows[i];
146
- const v = values[i];
147
- const color = b.color || defaultColor;
148
- if (horizontal) {
149
- const y = pad + i * slot + (slot - barW) / 2;
150
- const len = (v / scale) * innerW;
151
- try {
152
- ctx.fillStyle = theme.bg;
153
- ctx.fillRect(pad + labelGutter, y, innerW, barW);
154
- ctx.fillStyle = color;
155
- ctx.fillRect(pad + labelGutter, y, len, barW);
156
- ctx.fillStyle = theme.text;
157
- ctx.fillText(String(b.label || ''), pad, y + barW * 0.75);
158
- } catch {}
159
- } else {
160
- const x = pad + i * slot + (slot - barW) / 2;
161
- const len = (v / scale) * innerH;
162
- try {
163
- ctx.fillStyle = theme.bg;
164
- ctx.fillRect(x, pad, barW, innerH);
165
- ctx.fillStyle = color;
166
- ctx.fillRect(x, pad + (innerH - len), barW, len);
167
- ctx.fillStyle = theme.text;
168
- ctx.fillText(String(b.label || '').slice(0, 8), x, H - pad);
169
- } catch {}
170
- }
171
- }
172
- }
173
-
174
- /**
175
- * progressBar(div, data)
176
- * data: { current: number, limit: number | null, label?: string, warning?: boolean }
177
- *
178
- * Mutates `div` in place. Renders an "unlimited" placeholder when limit is
179
- * null. Applies a warning class when `warning` is truthy.
180
- */
181
- export function progressBar(div, data = {}) {
182
- if (!div) return;
183
- const theme = _readTheme(div);
184
- const cur = Math.max(0, _safeNum(data.current, 0));
185
- const lim = (data.limit === null || data.limit === undefined) ? null : _safeNum(data.limit, null);
186
- const label = typeof data.label === 'string' ? data.label : '';
187
- const warn = Boolean(data.warning);
188
-
189
- // Clear children.
190
- try {
191
- while (div.firstChild) div.removeChild(div.firstChild);
192
- } catch {
193
- // Some test mocks omit firstChild/removeChild. Skip cleanup.
194
- }
195
-
196
- const setClass = (extra) => {
197
- try {
198
- div.className = ['ijfw-progress', extra, warn ? 'ijfw-progress--warn' : '']
199
- .filter(Boolean).join(' ');
200
- } catch {}
201
- };
202
-
203
- // Helper to create child elements safely.
204
- function appendEl(tag, opts) {
205
- try {
206
- if (typeof div.ownerDocument === 'object' && div.ownerDocument && typeof div.ownerDocument.createElement === 'function') {
207
- const el = div.ownerDocument.createElement(tag);
208
- if (opts && opts.text) el.textContent = opts.text;
209
- if (opts && opts.style) el.setAttribute('style', opts.style);
210
- if (opts && opts.cls) el.className = opts.cls;
211
- if (typeof div.appendChild === 'function') div.appendChild(el);
212
- return el;
213
- }
214
- } catch {}
215
- return null;
216
- }
217
-
218
- if (lim === null) {
219
- setClass('ijfw-progress--unlimited');
220
- appendEl('span', { cls: 'ijfw-progress-label', text: label });
221
- appendEl('span', { cls: 'ijfw-progress-val', text: cur + ' / unlimited' });
222
- return;
223
- }
224
-
225
- setClass('');
226
- const denom = lim > 0 ? lim : 1;
227
- const pct = Math.max(0, Math.min(100, (cur / denom) * 100));
228
- appendEl('span', { cls: 'ijfw-progress-label', text: label });
229
- const rail = appendEl('span', { cls: 'ijfw-progress-rail', style: 'background:' + theme.bg + ';display:inline-block;height:6px;width:120px;border-radius:3px;overflow:hidden;vertical-align:middle;margin:0 6px' });
230
- if (rail) {
231
- try {
232
- const fill = (rail.ownerDocument || div.ownerDocument).createElement('span');
233
- fill.className = 'ijfw-progress-fill';
234
- fill.setAttribute('style', 'display:block;height:100%;width:' + pct.toFixed(1) + '%;background:' + (warn ? theme.warning : theme.fg));
235
- rail.appendChild(fill);
236
- } catch {}
237
- }
238
- appendEl('span', { cls: 'ijfw-progress-val', text: cur + ' / ' + lim });
239
- }