@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
@@ -41,7 +41,21 @@ import { fileURLToPath } from 'url';
41
41
  import { dirname, join } from 'path';
42
42
 
43
43
  // Recognised namespaces -- gates dispatchRun against typos.
44
- const RUN_NAMESPACES = new Set(['compute', 'index', 'detect', 'graph']);
44
+ // v1.4.0 (F7): added 'override', 'extension', 'domain-manifest' for the
45
+ // Open Ecosystem dispatch surface.
46
+ // v1.4.0 (W6/S13): parser now accepts hyphens in namespace ([a-z_][a-z0-9_-]*)
47
+ // so the user-facing spelling 'domain-manifest:<op>' (matching the error
48
+ // message and CLI surface) parses + routes uniformly. The set entry is the
49
+ // hyphenated form so copy-paste from the error string Just Works.
50
+ const RUN_NAMESPACES = new Set([
51
+ 'compute',
52
+ 'index',
53
+ 'detect',
54
+ 'graph',
55
+ 'override',
56
+ 'extension',
57
+ 'domain-manifest',
58
+ ]);
45
59
  const SEARCH_NAMESPACES = new Set(['compute', 'graph']);
46
60
 
47
61
  // --- Parser ----------------------------------------------------------------
@@ -69,7 +83,9 @@ export function parseColonCommand(input) {
69
83
  if (colon <= 0) return null;
70
84
 
71
85
  const namespace = s.slice(0, colon);
72
- if (!/^[a-z_][a-z0-9_]*$/.test(namespace)) return null;
86
+ // v1.4.0 (W6/S13): allow hyphens so multi-word namespaces like
87
+ // 'domain-manifest' parse without forcing a separate normalisation step.
88
+ if (!/^[a-z_][a-z0-9_-]*$/.test(namespace)) return null;
73
89
 
74
90
  const remainder = s.slice(colon + 1);
75
91
  // Empty remainder -> command present but blank; treat as malformed.
@@ -138,10 +154,22 @@ export async function dispatchRun(parsed, ctx = {}) {
138
154
  if (parsed.namespace === 'graph') {
139
155
  return dispatchGraph(parsed, { projectRoot, sessionId });
140
156
  }
157
+ if (parsed.namespace === 'override') {
158
+ const m = await import('./override.js');
159
+ return m.overrideDispatch({ command: parsed.command, args: parsed.args, projectRoot });
160
+ }
161
+ if (parsed.namespace === 'extension') {
162
+ const m = await import('./extension.js');
163
+ return m.extensionDispatch({ command: parsed.command, args: parsed.args, projectRoot });
164
+ }
165
+ if (parsed.namespace === 'domain-manifest') {
166
+ const m = await import('./domain-manifest.js');
167
+ return m.domainManifestDispatch({ command: parsed.command, args: parsed.args, projectRoot });
168
+ }
141
169
 
142
170
  return {
143
171
  ok: false,
144
- error: 'Unknown ijfw_run sub-command. Supported: compute:python, compute:js, index:<source>, detect:project_type, graph:traverse',
172
+ error: 'Unknown ijfw_run sub-command. Supported: compute:python, compute:js, index:<source>, detect:project_type, graph:traverse, override:<op>, extension:<op>, domain-manifest:<op>',
145
173
  };
146
174
  }
147
175
 
@@ -0,0 +1,251 @@
1
+ /**
2
+ * domain-manifest.js
3
+ *
4
+ * IJFW v1.4.0 Wave 3 / t13 — Domain-manifest auto-loading dispatch.
5
+ *
6
+ * Detects project type via the W1/t5 project-type-detector and maps the
7
+ * primary_type to a built-in domain preset (book / campaign), then records
8
+ * the override in ~/.ijfw/state/active-overrides.json by invoking the W1/t6
9
+ * override-resolver's recordActiveOverride() directly. No child-process
10
+ * shell-out — pure in-process dispatch so session-start can fire-and-forget
11
+ * via `( ... & disown )` and the detection completes regardless of hook
12
+ * exit timing.
13
+ *
14
+ * Mapping (intentional; matches t13 spec):
15
+ * book -> preset "book"
16
+ * content -> preset "book" (closest narrative match)
17
+ * business -> preset "campaign"
18
+ * design -> preset "campaign" (closest match)
19
+ * software -> no preset (no-op)
20
+ * mixed -> no preset (user must pick explicitly)
21
+ * unknown -> no preset (no-op)
22
+ *
23
+ * Contract (per R11 + F11):
24
+ * - Errors are SWALLOWED. Never throws. Fire-and-forget surface.
25
+ * - Idempotent. If the preset is already in active_overrides for this
26
+ * projectRoot it short-circuits and reports loaded:[] with cached:true
27
+ * semantics through domainManifestStatus.
28
+ * - Synchronous detect() is called inside an async function so the caller
29
+ * can `await` without blocking the event loop on the project walk;
30
+ * session-start backgrounds the entire invocation regardless.
31
+ *
32
+ * Discipline:
33
+ * - ESM only.
34
+ * - ASCII only in strings.
35
+ * - No new prod deps.
36
+ * - No console output on the happy path (session-start banner stays clean).
37
+ */
38
+
39
+ import fs from 'node:fs/promises';
40
+ import path from 'node:path';
41
+ import os from 'node:os';
42
+
43
+ import { detect } from '../project-type-detector.js';
44
+ import { recordActiveOverride } from '../override-resolver.js';
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Mapping table
48
+ // ---------------------------------------------------------------------------
49
+
50
+ // project_type -> preset name (or null for no-op).
51
+ const TYPE_TO_PRESET = Object.freeze({
52
+ book: 'book',
53
+ content: 'book',
54
+ business: 'campaign',
55
+ design: 'campaign',
56
+ software: null,
57
+ mixed: null,
58
+ unknown: null,
59
+ });
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // State helpers (read-side; recordActiveOverride owns the write-side)
63
+ // ---------------------------------------------------------------------------
64
+
65
+ function activeOverridesPath() {
66
+ return path.join(os.homedir(), '.ijfw', 'state', 'active-overrides.json');
67
+ }
68
+
69
+ async function readActiveOverridesSafe() {
70
+ try {
71
+ const raw = await fs.readFile(activeOverridesPath(), 'utf8');
72
+ const parsed = JSON.parse(raw);
73
+ if (!parsed || typeof parsed !== 'object' || !parsed.projects) {
74
+ return { projects: {} };
75
+ }
76
+ return parsed;
77
+ } catch {
78
+ // ENOENT or invalid JSON -> empty state. Never throw from a fire-and-forget path.
79
+ return { projects: {} };
80
+ }
81
+ }
82
+
83
+ function activePresetsForProject(state, projectRoot) {
84
+ const proj = state && state.projects ? state.projects[projectRoot] : null;
85
+ if (!proj || !Array.isArray(proj.active_overrides)) return [];
86
+ return proj.active_overrides
87
+ .filter((o) => o && typeof o.preset === 'string')
88
+ .map((o) => o.preset);
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Project type resolution
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /**
96
+ * Resolve the primary project type. detect() returns an object with both
97
+ * `primary_type` and a `type` alias (per W1/t5 contract). We prefer
98
+ * primary_type; fall back to type; finally fall back to 'unknown'.
99
+ *
100
+ * Wrapped so a thrown detect() (e.g. unreadable root) becomes 'unknown'
101
+ * rather than propagating.
102
+ */
103
+ function resolveProjectType(projectRoot) {
104
+ try {
105
+ const result = detect(projectRoot, { resume: false });
106
+ if (!result || typeof result !== 'object') return 'unknown';
107
+ const t = result.primary_type || result.type || 'unknown';
108
+ return typeof t === 'string' ? t : 'unknown';
109
+ } catch {
110
+ return 'unknown';
111
+ }
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Public API
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * domainManifestLoad(projectRoot) -> Promise<{
120
+ * loaded: string[], // preset names newly loaded this call
121
+ * project_type: string, // primary_type from detect()
122
+ * duration_ms: number, // wall-clock duration
123
+ * error?: string, // present only on swallowed failure
124
+ * }>
125
+ *
126
+ * Detects project type, maps to a preset, and records the override in
127
+ * ~/.ijfw/state/active-overrides.json if not already active. Idempotent.
128
+ * Fire-and-forget contract: NEVER throws.
129
+ *
130
+ * @param {string} projectRoot
131
+ * @returns {Promise<{loaded: string[], project_type: string, duration_ms: number, error?: string}>}
132
+ */
133
+ export async function domainManifestLoad(projectRoot) {
134
+ const t0 = Date.now();
135
+ try {
136
+ const root = String(projectRoot || process.cwd());
137
+ const projectType = resolveProjectType(root);
138
+ const preset = TYPE_TO_PRESET[projectType] || null;
139
+
140
+ // No mapping -> nothing to load. Honest empty result.
141
+ if (!preset) {
142
+ return {
143
+ loaded: [],
144
+ project_type: projectType,
145
+ duration_ms: Date.now() - t0,
146
+ };
147
+ }
148
+
149
+ // Idempotence check. If this preset is already recorded for this project
150
+ // we skip the write — repeated session-starts must not churn the state file.
151
+ const state = await readActiveOverridesSafe();
152
+ const active = activePresetsForProject(state, root);
153
+ if (active.includes(preset)) {
154
+ return {
155
+ loaded: [],
156
+ project_type: projectType,
157
+ duration_ms: Date.now() - t0,
158
+ };
159
+ }
160
+
161
+ // Record the new override. Scope 'project' = auto-attached at the project
162
+ // tier, not the user/org tier. applied_at left to recordActiveOverride()
163
+ // to stamp.
164
+ await recordActiveOverride(root, {
165
+ preset,
166
+ scope: 'project',
167
+ });
168
+
169
+ return {
170
+ loaded: [preset],
171
+ project_type: projectType,
172
+ duration_ms: Date.now() - t0,
173
+ };
174
+ } catch (err) {
175
+ // Swallow. Fire-and-forget surface — never let session-start's detached
176
+ // child crash and emit a stderr line into the user's terminal.
177
+ return {
178
+ loaded: [],
179
+ project_type: 'unknown',
180
+ duration_ms: Date.now() - t0,
181
+ error: err && err.message ? err.message : String(err),
182
+ };
183
+ }
184
+ }
185
+
186
+ /**
187
+ * domainManifestStatus(projectRoot) -> Promise<{
188
+ * project_type: string,
189
+ * active_presets: string[],
190
+ * cached: boolean, // true if active_presets non-empty (i.e. a
191
+ * // prior session already auto-loaded one)
192
+ * }>
193
+ *
194
+ * Used by the prelude to surface a single-line "Active domain: book" beat
195
+ * once auto-load has fired at least once. Never throws.
196
+ */
197
+ export async function domainManifestStatus(projectRoot) {
198
+ try {
199
+ const root = String(projectRoot || process.cwd());
200
+ const projectType = resolveProjectType(root);
201
+ const state = await readActiveOverridesSafe();
202
+ const active = activePresetsForProject(state, root);
203
+ return {
204
+ project_type: projectType,
205
+ active_presets: active,
206
+ cached: active.length > 0,
207
+ };
208
+ } catch (err) {
209
+ return {
210
+ project_type: 'unknown',
211
+ active_presets: [],
212
+ cached: false,
213
+ error: err && err.message ? err.message : String(err),
214
+ };
215
+ }
216
+ }
217
+
218
+ // ---------------------------------------------------------------------------
219
+ // Test-only exports
220
+ // ---------------------------------------------------------------------------
221
+
222
+ export const __test = { TYPE_TO_PRESET, resolveProjectType, activePresetsForProject };
223
+
224
+ /**
225
+ * domainManifestDispatch — colon-syntax dispatch entry point (W3/t16).
226
+ *
227
+ * Commands:
228
+ * load — run domainManifestLoad(projectRoot)
229
+ * status — return domainManifestStatus(projectRoot)
230
+ */
231
+ export async function domainManifestDispatch({ command, projectRoot }) {
232
+ const root = String(projectRoot || process.cwd());
233
+ try {
234
+ if (command === 'load') {
235
+ const result = await domainManifestLoad(root);
236
+ return { ok: true, command, result };
237
+ }
238
+ if (command === 'status') {
239
+ const result = await domainManifestStatus(root);
240
+ return { ok: true, command, result };
241
+ }
242
+ return {
243
+ ok: false,
244
+ command,
245
+ error: `unknown domain-manifest command: ${command}. Supported: load | status`,
246
+ };
247
+ } catch (err) {
248
+ return { ok: false, command, error: err.message };
249
+ }
250
+ }
251
+