@ijfw/memory-server 1.3.0 → 1.4.0

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 (64) hide show
  1. package/fixtures/team/book.json +47 -0
  2. package/fixtures/team/business.json +47 -0
  3. package/fixtures/team/content.json +47 -0
  4. package/fixtures/team/design.json +47 -0
  5. package/fixtures/team/mixed.json +59 -0
  6. package/fixtures/team/research.json +47 -0
  7. package/fixtures/team/software.json +47 -0
  8. package/package.json +1 -9
  9. package/src/active-extension-writer.js +116 -0
  10. package/src/blackboard.js +360 -0
  11. package/src/cli-run.js +91 -0
  12. package/src/codex-agents.js +177 -0
  13. package/src/compute/extract.js +3 -0
  14. package/src/compute/fts5.js +4 -4
  15. package/src/compute/graph-lock.js +0 -2
  16. package/src/compute/migrations/003-tier-semantic.js +3 -3
  17. package/src/compute/runner.js +44 -15
  18. package/src/compute/schema.sql +1 -1
  19. package/src/cross-orchestrator-cli.js +974 -13
  20. package/src/cross-orchestrator.js +9 -1
  21. package/src/dashboard-client.html +144 -1
  22. package/src/dashboard-server.js +75 -2
  23. package/src/design-intelligence.js +721 -0
  24. package/src/dispatch/colon-syntax.js +31 -3
  25. package/src/dispatch/domain-manifest.js +251 -0
  26. package/src/dispatch/extension.js +404 -0
  27. package/src/dispatch/override.js +221 -0
  28. package/src/dispatch-planner.js +1 -0
  29. package/src/dream/runner.mjs +3 -3
  30. package/src/extension-installer.js +1230 -0
  31. package/src/extension-manifest-schema.js +301 -0
  32. package/src/extension-signer.js +740 -0
  33. package/src/gate-result-formatter.js +95 -0
  34. package/src/gate-result-schema.js +274 -0
  35. package/src/gate-result.js +195 -0
  36. package/src/intent-router.js +2 -0
  37. package/src/lib/npm-view.js +1 -0
  38. package/src/memory/fts5.js +3 -3
  39. package/src/memory/migrations/002-tier-semantic.js +2 -2
  40. package/src/memory/staleness.js +1 -1
  41. package/src/memory/tier-promotion.js +6 -6
  42. package/src/memory/tokenize.js +1 -1
  43. package/src/memory-feedback.js +188 -0
  44. package/src/override-manifest-schema.js +146 -0
  45. package/src/override-resolver.js +699 -0
  46. package/src/override-use-registry.js +307 -0
  47. package/src/overrides/presets/academic.md +101 -0
  48. package/src/overrides/presets/book.md +87 -0
  49. package/src/overrides/presets/campaign.md +95 -0
  50. package/src/overrides/presets/screenplay.md +99 -0
  51. package/src/recovery/checkpoint.js +191 -0
  52. package/src/redactor.js +2 -0
  53. package/src/runtime-mediator.js +178 -0
  54. package/src/sandbox.js +17 -3
  55. package/src/server.js +94 -2
  56. package/src/swarm/dispatch-prompt.js +154 -0
  57. package/src/swarm/planner.js +399 -0
  58. package/src/swarm/review.js +136 -0
  59. package/src/swarm/worktree.js +239 -0
  60. package/src/team/generator.js +119 -0
  61. package/src/team/schemas.js +341 -0
  62. package/src/trident/dispatch.js +47 -0
  63. package/src/update-check.js +1 -1
  64. package/src/vectors.js +7 -8
@@ -0,0 +1,301 @@
1
+ /**
2
+ * extension-manifest-schema.js
3
+ *
4
+ * IJFW v1.4.0 Wave 0 / t3 — Extension Manifest Schema
5
+ *
6
+ * Extensions are pure-markdown skill bundles (no JS commands in v1.4.0).
7
+ * Each extension ships a manifest.json + skill files. The installer
8
+ * verifies integrity (SHA256 hash) + runs Trident audit, then deploys the
9
+ * skill files to all 14 platform skill dirs.
10
+ *
11
+ * Trust model:
12
+ * - `integrity` is SHA256 over canonical JSON. It detects tamper, NOT
13
+ * publisher identity.
14
+ * - `signature` + `publisher_key_id` (W7/B1) provide Ed25519 asymmetric
15
+ * publisher authentication against the ~/.ijfw/trusted-publishers.json
16
+ * store. Unsigned manifests require opts.allowUnsigned at install time.
17
+ * - `permissions` is declarative intent AND runtime-enforced by the W7/B2
18
+ * runtime mediator (tier-1 MCP wrap cross-platform; tier-2 Claude Code
19
+ * hook). Trident audit at install gates content; runtime mediator gates
20
+ * calls when an extension is activated via `ijfw_run extension:activate`.
21
+ * - `type: "full"` is reserved for v1.5.0. v1.4.0 only supports
22
+ * `type: "skill-only"`.
23
+ *
24
+ * Hand-rolled validator. Zero new prod deps.
25
+ */
26
+
27
+ export const SCHEMA_VERSION = '1.0';
28
+
29
+ export const EXTENSION_TYPES = Object.freeze(['skill-only', 'full']);
30
+
31
+ /**
32
+ * Declarative permission allowlists. Extensions list what they intend to
33
+ * read/write — Trident audit at install time catches divergence between
34
+ * intent, and at runtime the W7/B2 mediation wrapper enforces against this
35
+ * surface.
36
+ *
37
+ * W7.1/B2-H-02: the `tool:*` namespace was added so manifests can declare
38
+ * platform-tool permissions (e.g. tool:edit, tool:bash) that the Claude tier-2
39
+ * hook checks. Specific entries are listed for IDE completion, but any
40
+ * `tool:<lowercase-kebab>` value (matching TOOL_PERMISSION_PATTERN below) is
41
+ * accepted by validatePermissionList so manifest authors can declare per-tool
42
+ * permissions without schema bumps.
43
+ */
44
+ export const PERMISSION_READS = Object.freeze([
45
+ './README.md',
46
+ './docs/**',
47
+ './src/**',
48
+ './*.md',
49
+ './*.json',
50
+ 'memory:read',
51
+ 'project:read',
52
+ 'blackboard:read',
53
+ // tool:<name> entries -- read-side
54
+ 'tool:*',
55
+ 'tool:read',
56
+ 'tool:glob',
57
+ 'tool:grep',
58
+ 'tool:ls',
59
+ 'tool:notebookread',
60
+ 'tool:webfetch',
61
+ 'tool:websearch',
62
+ ]);
63
+
64
+ export const PERMISSION_WRITES = Object.freeze([
65
+ './output/**',
66
+ './build/**',
67
+ './dist/**',
68
+ 'memory:write',
69
+ 'blackboard:write',
70
+ // tool:<name> entries -- write-side
71
+ 'tool:*',
72
+ 'tool:edit',
73
+ 'tool:write',
74
+ 'tool:bash',
75
+ 'tool:notebookedit',
76
+ ]);
77
+
78
+ /**
79
+ * Open-ended pattern accepted by validatePermissionList for the `tool:` namespace.
80
+ * Allows manifests to declare a tool not pre-enumerated above (forward compat).
81
+ * Validates shape only; runtime enforcement layer decides what the tool actually
82
+ * does. Wildcard `tool:*` matches separately as an exact allowlist entry.
83
+ */
84
+ export const TOOL_PERMISSION_PATTERN = /^tool:(\*|[a-z][a-z0-9-]*)$/;
85
+
86
+ export const REPLACE_MODES = Object.freeze(['override', 'extend', 'wrap']);
87
+
88
+ /**
89
+ * v1.4.0 strict integrity hash format.
90
+ * sha256: <64 lowercase hex chars>
91
+ * Reject `sha256:abc` and similar shortened values.
92
+ */
93
+ export const INTEGRITY_PATTERN = /^sha256:[a-f0-9]{64}$/;
94
+
95
+ /**
96
+ * v1.4.0 W7/B1: Ed25519 publisher signature format.
97
+ * ed25519:<base64 chars> -- 64 raw bytes -> 88 base64 chars (with `=` pad).
98
+ * Allow standard or URL-safe base64 alphabet; trailing `=` padding optional.
99
+ */
100
+ export const SIGNATURE_PATTERN = /^ed25519:[A-Za-z0-9+/_-]{86,90}={0,2}$/;
101
+
102
+ /**
103
+ * v1.4.0 W7/B1: publisher key id format. sha256 fingerprint of the public
104
+ * key (lowercase hex), no scheme prefix.
105
+ */
106
+ export const PUBLISHER_KEY_ID_PATTERN = /^[a-f0-9]{64}$/;
107
+
108
+ // eslint-disable-next-line security/detect-unsafe-regex -- anchored semver shape; non-overlapping optional suffixes
109
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
110
+ const IJFW_REQUIRES_PATTERN = /^(>=|>|=|<=|<)?\s*\d+\.\d+\.\d+/;
111
+ // eslint-disable-next-line security/detect-unsafe-regex -- anchored, bounded npm name shape; no nested ambiguous repetition
112
+ const EXTENSION_NAME_PATTERN = /^(@[a-z0-9-]+\/)?[a-z][a-z0-9-]*$/;
113
+ const SKILL_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
114
+ const FILE_PATH_PATTERN = /^[a-zA-Z0-9_./-]+\.md$/;
115
+
116
+ function isString(v) {
117
+ return typeof v === 'string';
118
+ }
119
+
120
+ function isNonNullObject(v) {
121
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
122
+ }
123
+
124
+ function validatePermissionList(list, allowlist, fieldName, errors) {
125
+ if (!Array.isArray(list)) {
126
+ errors.push(`${fieldName}: must be an array`);
127
+ return;
128
+ }
129
+ list.forEach((p, i) => {
130
+ if (!isString(p)) {
131
+ errors.push(`${fieldName}[${i}]: must be a string`);
132
+ return;
133
+ }
134
+ // Accept exact-allowlist matches first, then the open-ended `tool:<name>`
135
+ // shape so manifest authors can declare new tools without a schema bump.
136
+ if (allowlist.includes(p)) return;
137
+ if (TOOL_PERMISSION_PATTERN.test(p)) return;
138
+ errors.push(
139
+ `${fieldName}[${i}]: ${JSON.stringify(p)} not in allowlist`,
140
+ );
141
+ });
142
+ }
143
+
144
+ /**
145
+ * validateExtensionManifest(manifest) — strict v1.4.0 validation.
146
+ *
147
+ * @param {unknown} obj
148
+ * @returns {{valid: boolean, errors: string[]}}
149
+ */
150
+ export function validateExtensionManifest(obj) {
151
+ const errors = [];
152
+
153
+ if (!isNonNullObject(obj)) {
154
+ return { valid: false, errors: ['root: must be an object'] };
155
+ }
156
+
157
+ // schema_version
158
+ if (obj.schema_version !== SCHEMA_VERSION) {
159
+ errors.push(
160
+ `schema_version: must equal "${SCHEMA_VERSION}", got ${JSON.stringify(obj.schema_version)}`,
161
+ );
162
+ }
163
+
164
+ // name
165
+ if (!isString(obj.name) || !EXTENSION_NAME_PATTERN.test(obj.name)) {
166
+ errors.push(
167
+ `name: must match ${EXTENSION_NAME_PATTERN} (kebab or @scope/kebab)`,
168
+ );
169
+ }
170
+
171
+ // version
172
+ if (!isString(obj.version) || !SEMVER_PATTERN.test(obj.version)) {
173
+ errors.push('version: must be semver (e.g. "1.0.0")');
174
+ }
175
+
176
+ // description (optional but if present must be string)
177
+ if (obj.description !== undefined && !isString(obj.description)) {
178
+ errors.push('description: must be a string when present');
179
+ }
180
+
181
+ // author / license (optional, strings)
182
+ if (obj.author !== undefined && !isString(obj.author)) {
183
+ errors.push('author: must be a string when present');
184
+ }
185
+ if (obj.license !== undefined && !isString(obj.license)) {
186
+ errors.push('license: must be a string when present');
187
+ }
188
+
189
+ // ijfw_requires
190
+ if (obj.ijfw_requires !== undefined) {
191
+ if (!isString(obj.ijfw_requires) || !IJFW_REQUIRES_PATTERN.test(obj.ijfw_requires)) {
192
+ errors.push('ijfw_requires: must be a version range like ">=1.4.0"');
193
+ }
194
+ }
195
+
196
+ // type
197
+ if (!EXTENSION_TYPES.includes(obj.type)) {
198
+ errors.push(
199
+ `type: must be one of ${EXTENSION_TYPES.join('|')}, got ${JSON.stringify(obj.type)}`,
200
+ );
201
+ } else if (obj.type === 'full') {
202
+ // v1.4.0 hard-line: full is reserved for 1.5.0.
203
+ errors.push(
204
+ 'type: "full" is reserved for v1.5.0; v1.4.0 only supports "skill-only"',
205
+ );
206
+ }
207
+
208
+ // skills (required, array)
209
+ if (!Array.isArray(obj.skills)) {
210
+ errors.push('skills: must be an array (may be empty)');
211
+ } else {
212
+ obj.skills.forEach((s, i) => {
213
+ if (!isNonNullObject(s)) {
214
+ errors.push(`skills[${i}]: must be an object`);
215
+ return;
216
+ }
217
+ if (!isString(s.name) || !SKILL_NAME_PATTERN.test(s.name)) {
218
+ errors.push(
219
+ `skills[${i}].name: must be kebab-case matching ${SKILL_NAME_PATTERN}`,
220
+ );
221
+ }
222
+ if (!isString(s.file) || !FILE_PATH_PATTERN.test(s.file)) {
223
+ errors.push(
224
+ `skills[${i}].file: must be a relative .md path matching ${FILE_PATH_PATTERN}`,
225
+ );
226
+ }
227
+ if (s.replaces !== undefined) {
228
+ if (!isNonNullObject(s.replaces)) {
229
+ errors.push(`skills[${i}].replaces: must be an object`);
230
+ } else {
231
+ if (!isString(s.replaces.skill) || !SKILL_NAME_PATTERN.test(s.replaces.skill)) {
232
+ errors.push(
233
+ `skills[${i}].replaces.skill: must be a kebab-case skill name`,
234
+ );
235
+ }
236
+ if (!REPLACE_MODES.includes(s.replaces.mode)) {
237
+ errors.push(
238
+ `skills[${i}].replaces.mode: must be one of ${REPLACE_MODES.join('|')}`,
239
+ );
240
+ }
241
+ }
242
+ }
243
+ });
244
+ }
245
+
246
+ // overrides (optional)
247
+ if (obj.overrides !== undefined) {
248
+ if (!Array.isArray(obj.overrides)) {
249
+ errors.push('overrides: must be an array when present');
250
+ } else {
251
+ obj.overrides.forEach((o, i) => {
252
+ if (!isNonNullObject(o)) {
253
+ errors.push(`overrides[${i}]: must be an object`);
254
+ return;
255
+ }
256
+ if (!isString(o.skill) || !SKILL_NAME_PATTERN.test(o.skill)) {
257
+ errors.push(`overrides[${i}].skill: must be a kebab-case skill name`);
258
+ }
259
+ if (!isString(o.file) || !FILE_PATH_PATTERN.test(o.file)) {
260
+ errors.push(`overrides[${i}].file: must be a relative .md path`);
261
+ }
262
+ });
263
+ }
264
+ }
265
+
266
+ // permissions
267
+ if (!isNonNullObject(obj.permissions)) {
268
+ errors.push('permissions: must be an object with reads/writes arrays');
269
+ } else {
270
+ validatePermissionList(obj.permissions.reads, PERMISSION_READS, 'permissions.reads', errors);
271
+ validatePermissionList(obj.permissions.writes, PERMISSION_WRITES, 'permissions.writes', errors);
272
+ }
273
+
274
+ // integrity — strict format per R5
275
+ if (!isString(obj.integrity)) {
276
+ errors.push('integrity: must be a string');
277
+ } else if (!INTEGRITY_PATTERN.test(obj.integrity)) {
278
+ errors.push(
279
+ `integrity: must match ${INTEGRITY_PATTERN} — full 64 hex char sha256 digest`,
280
+ );
281
+ }
282
+
283
+ // signature (optional, W7/B1). When present, publisher_key_id is required
284
+ // and both must match their patterns. When absent, manifest is "unsigned".
285
+ if (obj.signature !== undefined) {
286
+ if (!isString(obj.signature) || !SIGNATURE_PATTERN.test(obj.signature)) {
287
+ errors.push(`signature: must match ${SIGNATURE_PATTERN}`);
288
+ }
289
+ if (obj.publisher_key_id === undefined) {
290
+ errors.push('publisher_key_id: required when signature present');
291
+ } else if (!isString(obj.publisher_key_id) || !PUBLISHER_KEY_ID_PATTERN.test(obj.publisher_key_id)) {
292
+ errors.push(`publisher_key_id: must match ${PUBLISHER_KEY_ID_PATTERN}`);
293
+ }
294
+ } else if (obj.publisher_key_id !== undefined) {
295
+ if (!isString(obj.publisher_key_id) || !PUBLISHER_KEY_ID_PATTERN.test(obj.publisher_key_id)) {
296
+ errors.push(`publisher_key_id: must match ${PUBLISHER_KEY_ID_PATTERN}`);
297
+ }
298
+ }
299
+
300
+ return { valid: errors.length === 0, errors };
301
+ }