@ijfw/memory-server 1.3.0 → 1.4.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.
- package/README.md +67 -0
- package/fixtures/team/book.json +47 -0
- package/fixtures/team/business.json +47 -0
- package/fixtures/team/content.json +47 -0
- package/fixtures/team/design.json +47 -0
- package/fixtures/team/mixed.json +59 -0
- package/fixtures/team/research.json +47 -0
- package/fixtures/team/software.json +47 -0
- package/package.json +1 -9
- package/src/.registry-meta-key.pem +3 -0
- package/src/active-extension-writer.js +142 -0
- package/src/blackboard.js +360 -0
- package/src/cli-run.js +91 -0
- package/src/codex-agents.js +177 -0
- package/src/compute/extract.js +3 -0
- package/src/compute/fts5.js +4 -4
- package/src/compute/graph-lock.js +0 -2
- package/src/compute/migrations/003-tier-semantic.js +3 -3
- package/src/compute/runner.js +44 -15
- package/src/compute/schema.sql +1 -1
- package/src/cross-orchestrator-cli.js +974 -13
- package/src/cross-orchestrator.js +9 -1
- package/src/dashboard-client.html +353 -1
- package/src/dashboard-server.js +318 -2
- package/src/design-intelligence.js +721 -0
- package/src/dispatch/colon-syntax.js +31 -3
- package/src/dispatch/domain-manifest.js +251 -0
- package/src/dispatch/extension.js +637 -0
- package/src/dispatch/override.js +221 -0
- package/src/dispatch-planner.js +1 -0
- package/src/dream/runner.mjs +3 -3
- package/src/extension-installer.js +1269 -0
- package/src/extension-manifest-schema.js +301 -0
- package/src/extension-permission-check.mjs +79 -0
- package/src/extension-registry.js +619 -0
- package/src/extension-signer.js +905 -0
- package/src/gate-result-formatter.js +95 -0
- package/src/gate-result-schema.js +274 -0
- package/src/gate-result.js +195 -0
- package/src/intent-router.js +2 -0
- package/src/lib/npm-view.js +1 -0
- package/src/memory/fts5.js +3 -3
- package/src/memory/migrations/002-tier-semantic.js +2 -2
- package/src/memory/staleness.js +1 -1
- package/src/memory/tier-promotion.js +6 -6
- package/src/memory/tokenize.js +1 -1
- package/src/memory-feedback.js +372 -0
- package/src/override-manifest-schema.js +146 -0
- package/src/override-resolver.js +699 -0
- package/src/override-use-registry.js +307 -0
- package/src/overrides/presets/academic.md +101 -0
- package/src/overrides/presets/book.md +87 -0
- package/src/overrides/presets/campaign.md +95 -0
- package/src/overrides/presets/screenplay.md +99 -0
- package/src/recovery/checkpoint.js +191 -0
- package/src/redactor.js +2 -0
- package/src/runtime-mediator.js +207 -0
- package/src/sandbox.js +17 -3
- package/src/server.js +94 -2
- package/src/swarm/dispatch-prompt.js +154 -0
- package/src/swarm/planner.js +399 -0
- package/src/swarm/review.js +136 -0
- package/src/swarm/worktree.js +239 -0
- package/src/team/generator.js +119 -0
- package/src/team/schemas.js +341 -0
- package/src/trident/dispatch.js +47 -0
- package/src/update-check.js +1 -1
- 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
|
+
}
|
package/src/trident/dispatch.js
CHANGED
|
@@ -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
|
|
package/src/update-check.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function ijfwUpdateCheck(args = {}) {
|
|
|
90
90
|
latest,
|
|
91
91
|
available,
|
|
92
92
|
reachable: true,
|
|
93
|
-
changelog_url: `https://
|
|
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.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
|
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 || '
|
|
133
|
-
return v
|
|
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
|