@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,341 @@
1
+ const ARCHETYPES = new Set([
2
+ 'software',
3
+ 'design',
4
+ 'content',
5
+ 'book',
6
+ 'research',
7
+ 'business',
8
+ 'education',
9
+ 'operations',
10
+ 'mixed',
11
+ ]);
12
+
13
+ const ROLE_TYPES = new Set([
14
+ 'lead',
15
+ 'software',
16
+ 'design',
17
+ 'content',
18
+ 'book',
19
+ 'research',
20
+ 'business',
21
+ 'education',
22
+ 'operations',
23
+ 'review',
24
+ 'qa',
25
+ ]);
26
+
27
+ const TASK_STATUSES = new Set(['todo', 'ready', 'claimed', 'in_progress', 'blocked', 'review', 'done', 'cancelled']);
28
+ const CLAIM_STATUSES = new Set(['active', 'released', 'expired']);
29
+
30
+ export function validateTeamCharter(charter) {
31
+ const errors = [];
32
+ requireObject(charter, 'charter', errors);
33
+ if (errors.length) return result(errors);
34
+
35
+ requireString(charter.schema_version, 'charter.schema_version', errors);
36
+ requireString(charter.team_name, 'charter.team_name', errors);
37
+ validateArchetypes(charter.project_archetypes, 'charter.project_archetypes', errors);
38
+ requireArray(charter.roles, 'charter.roles', errors, { min: 1 });
39
+
40
+ const roleNames = new Set();
41
+ if (Array.isArray(charter.roles)) {
42
+ charter.roles.forEach((role, index) => {
43
+ const path = `charter.roles[${index}]`;
44
+ validateRole(role, path, errors);
45
+ if (isObject(role) && typeof role.name === 'string') {
46
+ if (roleNames.has(role.name)) errors.push(`${path}.name must be unique`);
47
+ roleNames.add(role.name);
48
+ }
49
+ });
50
+ }
51
+
52
+ return result(errors);
53
+ }
54
+
55
+ export function validateWorkflowManifest(workflow, charter = null) {
56
+ const errors = [];
57
+ requireObject(workflow, 'workflow', errors);
58
+ if (errors.length) return result(errors);
59
+
60
+ requireString(workflow.schema_version, 'workflow.schema_version', errors);
61
+ validateArchetypes(workflow.project_archetypes, 'workflow.project_archetypes', errors);
62
+ requireArray(workflow.artifacts, 'workflow.artifacts', errors, { min: 1 });
63
+ requireArray(workflow.waves, 'workflow.waves', errors, { min: 1 });
64
+
65
+ const roleNames = rolesFromCharter(charter);
66
+ const artifactIds = new Set();
67
+
68
+ if (Array.isArray(workflow.artifacts)) {
69
+ workflow.artifacts.forEach((artifact, index) => {
70
+ const path = `workflow.artifacts[${index}]`;
71
+ validateArtifact(artifact, path, errors, roleNames);
72
+ if (isObject(artifact) && typeof artifact.id === 'string') {
73
+ if (artifactIds.has(artifact.id)) errors.push(`${path}.id must be unique`);
74
+ artifactIds.add(artifact.id);
75
+ }
76
+ });
77
+ }
78
+
79
+ if (Array.isArray(workflow.artifacts)) {
80
+ workflow.artifacts.forEach((artifact, index) => {
81
+ if (!isObject(artifact) || !Array.isArray(artifact.depends_on)) return;
82
+ artifact.depends_on.forEach((id, depIndex) => {
83
+ if (!artifactIds.has(id)) errors.push(`workflow.artifacts[${index}].depends_on[${depIndex}] references unknown artifact "${id}"`);
84
+ });
85
+ });
86
+ }
87
+
88
+ if (Array.isArray(workflow.waves)) {
89
+ const waveIds = new Set();
90
+ workflow.waves.forEach((wave, index) => {
91
+ const path = `workflow.waves[${index}]`;
92
+ validateWave(wave, path, errors, artifactIds);
93
+ if (isObject(wave) && typeof wave.id === 'string') {
94
+ if (waveIds.has(wave.id)) errors.push(`${path}.id must be unique`);
95
+ waveIds.add(wave.id);
96
+ }
97
+ });
98
+ }
99
+
100
+ return result(errors);
101
+ }
102
+
103
+ export function validateBlackboardTask(task, workflow = null) {
104
+ const errors = [];
105
+ requireObject(task, 'task', errors);
106
+ if (errors.length) return result(errors);
107
+
108
+ requireString(task.id, 'task.id', errors);
109
+ requireString(task.title, 'task.title', errors);
110
+ requireString(task.status, 'task.status', errors);
111
+ if (typeof task.status === 'string' && !TASK_STATUSES.has(task.status)) {
112
+ errors.push(`task.status must be one of: ${Array.from(TASK_STATUSES).join(', ')}`);
113
+ }
114
+ requireArray(task.artifact_ids, 'task.artifact_ids', errors, { min: 1, strings: true });
115
+ optionalStringArray(task.depends_on, 'task.depends_on', errors);
116
+ optionalString(task.owner, 'task.owner', errors);
117
+
118
+ const artifactIds = artifactsFromWorkflow(workflow);
119
+ if (artifactIds && Array.isArray(task.artifact_ids)) {
120
+ task.artifact_ids.forEach((id, index) => {
121
+ if (!artifactIds.has(id)) errors.push(`task.artifact_ids[${index}] references unknown artifact "${id}"`);
122
+ });
123
+ }
124
+
125
+ return result(errors);
126
+ }
127
+
128
+ export function validateBlackboardClaim(claim, workflow = null) {
129
+ const errors = [];
130
+ requireObject(claim, 'claim', errors);
131
+ if (errors.length) return result(errors);
132
+
133
+ requireString(claim.id, 'claim.id', errors);
134
+ requireString(claim.artifact_id, 'claim.artifact_id', errors);
135
+ requireString(claim.agent, 'claim.agent', errors);
136
+ requireString(claim.status, 'claim.status', errors);
137
+ if (typeof claim.status === 'string' && !CLAIM_STATUSES.has(claim.status)) {
138
+ errors.push(`claim.status must be one of: ${Array.from(CLAIM_STATUSES).join(', ')}`);
139
+ }
140
+ optionalStringArray(claim.paths, 'claim.paths', errors);
141
+ optionalString(claim.claimed_at, 'claim.claimed_at', errors);
142
+ optionalString(claim.expires_at, 'claim.expires_at', errors);
143
+
144
+ const artifactIds = artifactsFromWorkflow(workflow);
145
+ if (artifactIds && !artifactIds.has(claim.artifact_id)) {
146
+ errors.push(`claim.artifact_id references unknown artifact "${claim.artifact_id}"`);
147
+ }
148
+
149
+ return result(errors);
150
+ }
151
+
152
+ export function validateTeamBundle(bundle) {
153
+ const errors = [];
154
+ requireObject(bundle, 'bundle', errors);
155
+ if (errors.length) return result(errors);
156
+
157
+ errors.push(...validateTeamCharter(bundle.charter).errors);
158
+ errors.push(...validateWorkflowManifest(bundle.workflow, bundle.charter).errors);
159
+
160
+ if (bundle.blackboard != null) {
161
+ requireObject(bundle.blackboard, 'bundle.blackboard', errors);
162
+ if (isObject(bundle.blackboard)) {
163
+ if (bundle.blackboard.tasks != null) {
164
+ requireArray(bundle.blackboard.tasks, 'bundle.blackboard.tasks', errors);
165
+ if (Array.isArray(bundle.blackboard.tasks)) {
166
+ bundle.blackboard.tasks.forEach((task, index) => {
167
+ prefixErrors(validateBlackboardTask(task, bundle.workflow).errors, `bundle.blackboard.tasks[${index}]`, errors);
168
+ });
169
+ }
170
+ }
171
+ if (bundle.blackboard.claims != null) {
172
+ requireArray(bundle.blackboard.claims, 'bundle.blackboard.claims', errors);
173
+ if (Array.isArray(bundle.blackboard.claims)) {
174
+ bundle.blackboard.claims.forEach((claim, index) => {
175
+ prefixErrors(validateBlackboardClaim(claim, bundle.workflow).errors, `bundle.blackboard.claims[${index}]`, errors);
176
+ });
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ return result(errors);
183
+ }
184
+
185
+ export function assertValidTeamBundle(bundle) {
186
+ const validation = validateTeamBundle(bundle);
187
+ if (!validation.ok) {
188
+ throw new Error(`Invalid team bundle:\n${validation.errors.map((e) => `- ${e}`).join('\n')}`);
189
+ }
190
+ return bundle;
191
+ }
192
+
193
+ function validateRole(role, path, errors) {
194
+ requireObject(role, path, errors);
195
+ if (!isObject(role)) return;
196
+ requireString(role.name, `${path}.name`, errors);
197
+ requireString(role.role_type, `${path}.role_type`, errors);
198
+ if (typeof role.role_type === 'string' && !ROLE_TYPES.has(role.role_type)) {
199
+ errors.push(`${path}.role_type must be one of: ${Array.from(ROLE_TYPES).join(', ')}`);
200
+ }
201
+ requireString(role.model, `${path}.model`, errors);
202
+ optionalString(role.effort, `${path}.effort`, errors);
203
+ requireArray(role.phase_scope, `${path}.phase_scope`, errors, { min: 1, strings: true });
204
+ requireArray(role.owns, `${path}.owns`, errors, { min: 1 });
205
+ requireArray(role.reviews, `${path}.reviews`, errors);
206
+ validateHandoff(role.handoff, `${path}.handoff`, errors);
207
+ validateCoordination(role.coordination, `${path}.coordination`, errors);
208
+
209
+ if (Array.isArray(role.owns)) role.owns.forEach((item, index) => validateArtifactRef(item, `${path}.owns[${index}]`, errors));
210
+ if (Array.isArray(role.reviews)) role.reviews.forEach((item, index) => validateReviewRef(item, `${path}.reviews[${index}]`, errors));
211
+ }
212
+
213
+ function validateArtifact(artifact, path, errors, roleNames) {
214
+ requireObject(artifact, path, errors);
215
+ if (!isObject(artifact)) return;
216
+ requireString(artifact.id, `${path}.id`, errors);
217
+ requireString(artifact.type, `${path}.type`, errors);
218
+ if (artifact.paths == null && artifact.refs == null) {
219
+ errors.push(`${path} must include paths or refs`);
220
+ }
221
+ optionalStringArray(artifact.paths, `${path}.paths`, errors);
222
+ optionalStringArray(artifact.refs, `${path}.refs`, errors);
223
+ requireString(artifact.owner, `${path}.owner`, errors);
224
+ requireArray(artifact.reviewers, `${path}.reviewers`, errors, { strings: true });
225
+ optionalStringArray(artifact.depends_on, `${path}.depends_on`, errors);
226
+ requireArray(artifact.verification, `${path}.verification`, errors, { min: 1, strings: true });
227
+
228
+ if (roleNames) {
229
+ if (typeof artifact.owner === 'string' && !roleNames.has(artifact.owner)) {
230
+ errors.push(`${path}.owner references unknown role "${artifact.owner}"`);
231
+ }
232
+ if (Array.isArray(artifact.reviewers)) {
233
+ artifact.reviewers.forEach((reviewer, index) => {
234
+ if (!roleNames.has(reviewer)) errors.push(`${path}.reviewers[${index}] references unknown role "${reviewer}"`);
235
+ });
236
+ }
237
+ }
238
+ }
239
+
240
+ function validateWave(wave, path, errors, artifactIds) {
241
+ requireObject(wave, path, errors);
242
+ if (!isObject(wave)) return;
243
+ requireString(wave.id, `${path}.id`, errors);
244
+ requireString(wave.mode, `${path}.mode`, errors);
245
+ if (typeof wave.mode === 'string' && !['parallel', 'sequential', 'review'].includes(wave.mode)) {
246
+ errors.push(`${path}.mode must be parallel, sequential, or review`);
247
+ }
248
+ requireArray(wave.artifact_ids, `${path}.artifact_ids`, errors, { min: 1, strings: true });
249
+ if (Array.isArray(wave.artifact_ids)) {
250
+ wave.artifact_ids.forEach((id, index) => {
251
+ if (!artifactIds.has(id)) errors.push(`${path}.artifact_ids[${index}] references unknown artifact "${id}"`);
252
+ });
253
+ }
254
+ }
255
+
256
+ function validateArtifactRef(ref, path, errors) {
257
+ requireObject(ref, path, errors);
258
+ if (!isObject(ref)) return;
259
+ requireString(ref.artifact_type, `${path}.artifact_type`, errors);
260
+ optionalStringArray(ref.paths, `${path}.paths`, errors);
261
+ optionalStringArray(ref.refs, `${path}.refs`, errors);
262
+ if (ref.paths == null && ref.refs == null) errors.push(`${path} must include paths or refs`);
263
+ }
264
+
265
+ function validateReviewRef(ref, path, errors) {
266
+ requireObject(ref, path, errors);
267
+ if (!isObject(ref)) return;
268
+ requireString(ref.artifact_type, `${path}.artifact_type`, errors);
269
+ requireArray(ref.criteria, `${path}.criteria`, errors, { min: 1, strings: true });
270
+ }
271
+
272
+ function validateHandoff(handoff, path, errors) {
273
+ requireObject(handoff, path, errors);
274
+ if (!isObject(handoff)) return;
275
+ requireString(handoff.format, `${path}.format`, errors);
276
+ requireArray(handoff.required_sections, `${path}.required_sections`, errors, { min: 1, strings: true });
277
+ }
278
+
279
+ function validateCoordination(coordination, path, errors) {
280
+ requireObject(coordination, path, errors);
281
+ if (!isObject(coordination)) return;
282
+ if (typeof coordination.parallel_safe !== 'boolean') errors.push(`${path}.parallel_safe must be boolean`);
283
+ if (typeof coordination.claim_required !== 'boolean') errors.push(`${path}.claim_required must be boolean`);
284
+ optionalStringArray(coordination.conflicts_with, `${path}.conflicts_with`, errors);
285
+ }
286
+
287
+ function validateArchetypes(archetypes, path, errors) {
288
+ requireArray(archetypes, path, errors, { min: 1, strings: true });
289
+ if (!Array.isArray(archetypes)) return;
290
+ archetypes.forEach((archetype, index) => {
291
+ if (!ARCHETYPES.has(archetype)) errors.push(`${path}[${index}] must be a known project archetype`);
292
+ });
293
+ }
294
+
295
+ function requireObject(value, path, errors) {
296
+ if (!isObject(value)) errors.push(`${path} must be an object`);
297
+ }
298
+
299
+ function requireString(value, path, errors) {
300
+ if (typeof value !== 'string' || value.trim() === '') errors.push(`${path} must be a non-empty string`);
301
+ }
302
+
303
+ function optionalString(value, path, errors) {
304
+ if (value != null && (typeof value !== 'string' || value.trim() === '')) errors.push(`${path} must be a non-empty string when present`);
305
+ }
306
+
307
+ function requireArray(value, path, errors, options = {}) {
308
+ if (!Array.isArray(value)) {
309
+ errors.push(`${path} must be an array`);
310
+ return;
311
+ }
312
+ if (options.min != null && value.length < options.min) errors.push(`${path} must contain at least ${options.min} item(s)`);
313
+ if (options.strings) value.forEach((item, index) => requireString(item, `${path}[${index}]`, errors));
314
+ }
315
+
316
+ function optionalStringArray(value, path, errors) {
317
+ if (value == null) return;
318
+ requireArray(value, path, errors, { strings: true });
319
+ }
320
+
321
+ function rolesFromCharter(charter) {
322
+ if (!isObject(charter) || !Array.isArray(charter.roles)) return null;
323
+ return new Set(charter.roles.filter((role) => isObject(role) && typeof role.name === 'string').map((role) => role.name));
324
+ }
325
+
326
+ function artifactsFromWorkflow(workflow) {
327
+ if (!isObject(workflow) || !Array.isArray(workflow.artifacts)) return null;
328
+ return new Set(workflow.artifacts.filter((artifact) => isObject(artifact) && typeof artifact.id === 'string').map((artifact) => artifact.id));
329
+ }
330
+
331
+ function prefixErrors(source, prefix, target) {
332
+ source.forEach((error) => target.push(`${prefix}: ${error}`));
333
+ }
334
+
335
+ function result(errors) {
336
+ return { ok: errors.length === 0, errors };
337
+ }
338
+
339
+ function isObject(value) {
340
+ return value != null && typeof value === 'object' && !Array.isArray(value);
341
+ }
@@ -18,6 +18,7 @@
18
18
  //
19
19
  // Zero external deps. ESM. No emoji.
20
20
 
21
+ import { emitGateResult } from '../gate-result.js';
21
22
  import { probeLenses } from './lens-health.js';
22
23
 
23
24
  // Custom error so callers can distinguish degraded-rejection from a normal
@@ -108,12 +109,15 @@ export async function runTrident(opts = {}) {
108
109
  executor = defaultExecutor,
109
110
  probeOpts = {},
110
111
  which = { codex: true, gemini: true, claude: true },
112
+ projectRoot = null,
111
113
  } = opts;
112
114
 
113
115
  if (!brief || typeof brief !== 'string') {
114
116
  throw new Error('runTrident: `brief` (string) is required');
115
117
  }
116
118
 
119
+ const _t0 = Date.now();
120
+
117
121
  // 1. Probe lens health.
118
122
  const lensHealth = await probeLenses(which, probeOpts);
119
123
  const summary = lensHealth.summary || { live_count: 0, total: 0, live_lenses: [], dead_lenses: [], mode: 'offline' };
@@ -191,6 +195,48 @@ export async function runTrident(opts = {}) {
191
195
  }
192
196
  const finalVerdict = coerceVerdict(aggregateVerdict, floor);
193
197
 
198
+ // 7. Emit canonical gate-result block (additive — does not alter verdict
199
+ // semantics). FLAG passes through unchanged per W0/t1 schema.
200
+ const durationMs = Date.now() - _t0;
201
+ const lensesForGate = lensResults.map((r) => ({
202
+ model: String(r.lens || 'unknown'),
203
+ verdict: String(r.verdict || 'WARN').toUpperCase(),
204
+ confidence: typeof r.confidence === 'number' ? r.confidence : 0.5,
205
+ summary:
206
+ typeof r.summary === 'string' && r.summary.length > 0
207
+ ? r.summary
208
+ : (r.error || r.note || `${r.lens || 'lens'} verdict ${r.verdict || 'WARN'}`),
209
+ }));
210
+
211
+ const gateOpts = {
212
+ gate: 'trident',
213
+ status: finalVerdict,
214
+ lenses: lensesForGate,
215
+ affected_artifacts: [],
216
+ accounting: {
217
+ duration_ms: durationMs,
218
+ lenses_invoked: lensResults.length,
219
+ cost_usd: null,
220
+ },
221
+ remediation: [],
222
+ };
223
+
224
+ let gate_result_block = null;
225
+ try {
226
+ gate_result_block = await emitGateResult(
227
+ gateOpts,
228
+ projectRoot ? { projectRoot } : {},
229
+ );
230
+ } catch (err) {
231
+ const msg = err && err.message ? err.message : String(err);
232
+ try {
233
+ process.stderr.write(`ijfw: trident gate-result emit failed: ${msg}\n`);
234
+ } catch {
235
+ /* nothing we can do; never crash the gate */
236
+ }
237
+ gate_result_block = null;
238
+ }
239
+
194
240
  return {
195
241
  verdict: finalVerdict,
196
242
  mode,
@@ -200,6 +246,7 @@ export async function runTrident(opts = {}) {
200
246
  lens_health: lensHealth,
201
247
  accept_degraded: !!accept_degraded,
202
248
  gate: gate || null,
249
+ gate_result_block,
203
250
  };
204
251
  }
205
252
 
@@ -90,7 +90,7 @@ export async function ijfwUpdateCheck(args = {}) {
90
90
  latest,
91
91
  available,
92
92
  reachable: true,
93
- changelog_url: `https://github.com/${REPO}/releases/tag/v${latest}`,
93
+ changelog_url: `https://gitlab.com/${REPO}/-/releases/v${latest}`,
94
94
  };
95
95
 
96
96
  if (available) {
package/src/vectors.js CHANGED
@@ -1,13 +1,12 @@
1
1
  // --- Vector embeddings (W3.3 / H5a-b-c) ---
2
2
  //
3
- // Thin wrapper around @xenova/transformers. Lazily imports the library on
4
- // first use so the zero-deps default install path is unaffected -- users who
5
- // don't enable vectors never pay the ~5MB bundle cost, and the 23MB model
6
- // download only happens when the first embedding query fires.
3
+ // Thin wrapper around a user-installed @xenova/transformers package. IJFW does
4
+ // not ship that dependency by default: vectors are an explicit opt-in so the
5
+ // zero-deps install path and npm-audit surface stay small.
7
6
  //
8
7
  // Environment control:
9
- // IJFW_VECTORS=off -- disable vectors entirely (BM25-only)
10
- // IJFW_VECTORS=on -- enable (default if the library is present)
8
+ // IJFW_VECTORS=off -- disable vectors entirely (BM25-only; default)
9
+ // IJFW_VECTORS=on -- enable if the library is installed by the user
11
10
  // IJFW_VECTORS_MODEL -- override the embedding model (default: Xenova/all-MiniLM-L6-v2, ~23MB)
12
11
  //
13
12
  // Fallback: if @xenova/transformers isn't installed, vectors silently
@@ -129,8 +128,8 @@ async function loadPipeline() {
129
128
  }
130
129
 
131
130
  export function vectorsEnabled() {
132
- const v = (process.env.IJFW_VECTORS || 'on').toLowerCase();
133
- return v !== 'off' && v !== '0' && v !== 'false';
131
+ const v = (process.env.IJFW_VECTORS || 'off').toLowerCase();
132
+ return v === 'on' || v === '1' || v === 'true';
134
133
  }
135
134
 
136
135
  // Returns { available: true, embed(text) → Float32Array } or