@jaimevalasek/aioson 1.29.1 → 1.30.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.
- package/CHANGELOG.md +19 -0
- package/README.md +7 -5
- package/docs/en/5-reference/cli-reference.md +40 -10
- package/docs/pt/4-agentes/pm.md +1 -1
- package/docs/pt/5-referencia/autopilot-handoff.md +4 -4
- package/docs/pt/5-referencia/comandos-cli.md +5 -3
- package/docs/pt/5-referencia/fluxo-artefatos.md +1 -1
- package/docs/pt/5-referencia/memoria-e-contexto.md +2 -2
- package/docs/pt/_arquivo/monitor-de-contexto.md +2 -2
- package/package.json +4 -2
- package/src/cli.js +67 -24
- package/src/commands/ac-test-audit.js +45 -0
- package/src/commands/artifact-validate.js +62 -50
- package/src/commands/classify.js +73 -2
- package/src/commands/context-brief.js +59 -0
- package/src/commands/context-guard.js +88 -0
- package/src/commands/context-monitor.js +1 -1
- package/src/commands/context-search.js +101 -52
- package/src/commands/context-select.js +11 -2
- package/src/commands/feature-archive.js +21 -12
- package/src/commands/feature-current.js +82 -0
- package/src/commands/gate-check.js +32 -15
- package/src/commands/harness-check.js +17 -1
- package/src/commands/hooks-install.js +169 -26
- package/src/commands/hygiene-scan.js +423 -0
- package/src/commands/rules-lint.js +11 -3
- package/src/commands/sdd-benchmark.js +134 -0
- package/src/commands/spec-analyze.js +6 -4
- package/src/commands/store-system.js +329 -49
- package/src/constants.js +8 -3
- package/src/context-brief.js +585 -0
- package/src/context-guard.js +209 -0
- package/src/context-search.js +796 -96
- package/src/context-selector.js +802 -444
- package/src/handoff-contract.js +14 -6
- package/src/harness/contract-schema.js +1 -1
- package/src/i18n/messages/en.js +12 -5
- package/src/i18n/messages/es.js +11 -4
- package/src/i18n/messages/fr.js +11 -4
- package/src/i18n/messages/pt-BR.js +12 -5
- package/src/lib/ac-test-audit.js +194 -0
- package/src/preflight-engine.js +10 -6
- package/src/squad/state-manager.js +1 -1
- package/template/.aioson/agents/analyst.md +41 -17
- package/template/.aioson/agents/architect.md +4 -2
- package/template/.aioson/agents/briefing-refiner.md +15 -2
- package/template/.aioson/agents/briefing.md +12 -8
- package/template/.aioson/agents/committer.md +1 -1
- package/template/.aioson/agents/copywriter.md +20 -9
- package/template/.aioson/agents/design-hybrid-forge.md +9 -5
- package/template/.aioson/agents/dev.md +22 -25
- package/template/.aioson/agents/deyvin.md +126 -124
- package/template/.aioson/agents/discover.md +3 -1
- package/template/.aioson/agents/discovery-design-doc.md +11 -2
- package/template/.aioson/agents/forge-run.md +3 -0
- package/template/.aioson/agents/genome.md +9 -5
- package/template/.aioson/agents/neo.md +30 -24
- package/template/.aioson/agents/orache.md +10 -6
- package/template/.aioson/agents/orchestrator.md +4 -2
- package/template/.aioson/agents/pentester.md +22 -12
- package/template/.aioson/agents/pm.md +5 -3
- package/template/.aioson/agents/product.md +25 -18
- package/template/.aioson/agents/profiler-enricher.md +10 -6
- package/template/.aioson/agents/profiler-forge.md +10 -6
- package/template/.aioson/agents/profiler-researcher.md +10 -6
- package/template/.aioson/agents/qa.md +21 -19
- package/template/.aioson/agents/scope-check.md +9 -3
- package/template/.aioson/agents/sheldon.md +22 -8
- package/template/.aioson/agents/site-forge.md +2 -0
- package/template/.aioson/agents/squad.md +4 -2
- package/template/.aioson/agents/tester.md +19 -15
- package/template/.aioson/agents/ux-ui.md +16 -8
- package/template/.aioson/config.md +4 -3
- package/template/.aioson/design-docs/agent-loading-contract.md +3 -3
- package/template/.aioson/docs/autopilot-handoff.md +3 -3
- package/template/.aioson/docs/dev/simple-plan-lane.md +73 -27
- package/template/.aioson/docs/dev/stack-conventions.md +1 -1
- package/template/.aioson/docs/deyvin/continuity-recovery.md +1 -1
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +3 -3
- package/template/.aioson/docs/feature-expansion-taxonomy.md +53 -0
- package/template/.aioson/docs/handoff-persistence.md +14 -12
- package/template/.aioson/docs/product/conversation-playbook.md +1 -1
- package/template/.aioson/docs/sheldon/enrichment-paths.md +44 -1
- package/template/.aioson/docs/sheldon/harness-contract.md +23 -21
- package/template/.aioson/docs/tester/coverage-quality.md +1 -1
- package/template/.aioson/docs/ux-ui/design-execution.md +9 -7
- package/template/.aioson/rules/README.md +35 -17
- package/template/.aioson/rules/agent-structural-contract.md +165 -160
- package/template/.aioson/rules/aioson-context-boundary.md +5 -4
- package/template/.aioson/rules/canonical-path-contract.md +5 -4
- package/template/.aioson/rules/data-format-convention.md +5 -4
- package/template/.aioson/rules/disk-first-artifacts.md +2 -2
- package/template/.aioson/rules/implementation-structure-and-data-access.md +50 -0
- package/template/.aioson/rules/security-baseline.md +4 -3
- package/template/.aioson/rules/simple-plan-lane.md +18 -6
- package/template/.aioson/rules/source-code-language-convention.md +34 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +24 -23
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +1 -1
- package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +72 -0
- package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +74 -0
- package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +67 -0
- package/template/.aioson/skills/static/context-budget-guide.md +1 -1
- package/template/.aioson/skills/static/multi-agent-patterns.md +5 -4
- package/template/AGENTS.md +36 -19
- package/template/CLAUDE.md +9 -5
package/src/context-selector.js
CHANGED
|
@@ -1,444 +1,802 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs/promises');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
],
|
|
56
|
-
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
['
|
|
66
|
-
['
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.
|
|
164
|
-
.
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
]);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const Database = require('better-sqlite3');
|
|
6
|
+
const {
|
|
7
|
+
parseFrontmatter,
|
|
8
|
+
parseAgentList,
|
|
9
|
+
appliesToAgent,
|
|
10
|
+
readFileSafe,
|
|
11
|
+
readProjectPulse,
|
|
12
|
+
readDevState
|
|
13
|
+
} = require('./preflight-engine');
|
|
14
|
+
const { openRuntimeDb } = require('./runtime-store');
|
|
15
|
+
const { searchProjectLearnings } = require('./learning-loop-fts5');
|
|
16
|
+
|
|
17
|
+
const VALID_MODES = new Set(['planning', 'executing']);
|
|
18
|
+
const SEMANTIC_MAX_TERMS = 24;
|
|
19
|
+
const SEMANTIC_RESULT_LIMIT = 80;
|
|
20
|
+
const MEMORY_RESULT_LIMIT = 5;
|
|
21
|
+
|
|
22
|
+
const SHORT_SEMANTIC_TERMS = new Set(['api', 'sql', 'orm', 'php', 'mvc', 'dto', 'ui', 'ux']);
|
|
23
|
+
const KNOWN_FRAMEWORK_TERMS = new Set([
|
|
24
|
+
'adonis', 'angular', 'astro', 'django', 'express', 'fastapi', 'flask', 'hono',
|
|
25
|
+
'laravel', 'next', 'node', 'nuxt', 'phoenix', 'rails', 'react', 'remix',
|
|
26
|
+
'svelte', 'symfony', 'vue'
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const SEMANTIC_STOP_WORDS = new Set([
|
|
30
|
+
'a', 'an', 'and', 'as', 'by', 'com', 'como', 'da', 'das', 'de', 'do', 'dos',
|
|
31
|
+
'e', 'em', 'for', 'from', 'in', 'into', 'no', 'nos', 'o', 'os', 'of', 'on',
|
|
32
|
+
'ou', 'para', 'por', 'que', 'the', 'to', 'um', 'uma', 'with',
|
|
33
|
+
'agent', 'agente', 'agents', 'aioson', 'dev', 'deyvin', 'architect',
|
|
34
|
+
'feature', 'funcionalidade', 'task', 'tarefa', 'work', 'trabalho',
|
|
35
|
+
'create', 'criar', 'fazer', 'implementar', 'implement', 'implementation',
|
|
36
|
+
'nova', 'novo', 'new', 'ajuste', 'change', 'update',
|
|
37
|
+
'evitar', 'exposto', 'http',
|
|
38
|
+
'boundary', 'code', 'codigo', 'código', 'component', 'developer', 'model',
|
|
39
|
+
'module', 'script', 'source', 'test'
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
const SEMANTIC_SYNONYMS = new Map([
|
|
43
|
+
['ingles', ['english']],
|
|
44
|
+
['english', ['ingles']],
|
|
45
|
+
['fonte', []],
|
|
46
|
+
['padrao', ['pattern', 'convention']],
|
|
47
|
+
['padroes', ['patterns', 'conventions']],
|
|
48
|
+
['pattern', ['convention']],
|
|
49
|
+
['patterns', ['conventions']],
|
|
50
|
+
['pasta', ['folder', 'directory']],
|
|
51
|
+
['pastas', ['folders', 'directories']],
|
|
52
|
+
['folder', ['directory']],
|
|
53
|
+
['folders', ['directories']],
|
|
54
|
+
['componentizacao', ['componentization']],
|
|
55
|
+
['componentizando', ['componentization']],
|
|
56
|
+
['componentizar', ['componentization']],
|
|
57
|
+
['separacao', ['separation', 'boundary']],
|
|
58
|
+
['separar', ['separation', 'boundary']],
|
|
59
|
+
['manutencao', ['maintainability']],
|
|
60
|
+
['manutenivel', ['maintainable']],
|
|
61
|
+
['consulta', ['query', 'queries']],
|
|
62
|
+
['consultas', ['query', 'queries']],
|
|
63
|
+
['query', ['queries']],
|
|
64
|
+
['queries', ['query']],
|
|
65
|
+
['banco', ['database', 'data']],
|
|
66
|
+
['dados', ['data', 'database']],
|
|
67
|
+
['frameworks', ['framework']],
|
|
68
|
+
['framework', ['convention']],
|
|
69
|
+
['laravel', ['eloquent', 'artisan']],
|
|
70
|
+
['php', ['laravel']],
|
|
71
|
+
['controller', ['controllers']],
|
|
72
|
+
['controllers', ['controller']],
|
|
73
|
+
['service', ['services']],
|
|
74
|
+
['services', ['service']],
|
|
75
|
+
['repository', ['repositories']],
|
|
76
|
+
['repositories', ['repository']],
|
|
77
|
+
['migration', ['migrations']],
|
|
78
|
+
['migrations', ['migration']],
|
|
79
|
+
['eloquent', ['laravel']],
|
|
80
|
+
['raw', ['sql']],
|
|
81
|
+
['sql', ['query', 'database']]
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
const SURFACES = [
|
|
85
|
+
{ key: 'rules', dir: path.join('.aioson', 'rules'), recursive: false, defaultTier: 'trigger' },
|
|
86
|
+
{ key: 'docs', dir: path.join('.aioson', 'docs'), recursive: true, defaultTier: 'trigger' },
|
|
87
|
+
{ key: 'design_governance', dir: path.join('.aioson', 'design-docs'), recursive: false, defaultTier: 'trigger' },
|
|
88
|
+
{ key: 'context', dir: path.join('.aioson', 'context'), recursive: false, defaultTier: 'trigger' },
|
|
89
|
+
{ key: 'bootstrap', dir: path.join('.aioson', 'context', 'bootstrap'), recursive: false, defaultTier: 'trigger' },
|
|
90
|
+
{ key: 'feature_dossier', dir: path.join('.aioson', 'context', 'features'), recursive: true, defaultTier: 'trigger' }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const FOUNDATION_CONTEXT_BASENAMES = new Set([
|
|
94
|
+
'project.context.md',
|
|
95
|
+
'project-pulse.md',
|
|
96
|
+
'dev-state.md',
|
|
97
|
+
'memory-index.md'
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const FOUNDATION_ACTIVATION_PATHS = new Set([
|
|
101
|
+
'.aioson/context/project.context.md',
|
|
102
|
+
'.aioson/context/project-pulse.md'
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const FOUNDATION_ACTIVATION_AGENTS = [
|
|
106
|
+
'briefing',
|
|
107
|
+
'product',
|
|
108
|
+
'sheldon',
|
|
109
|
+
'analyst',
|
|
110
|
+
'architect',
|
|
111
|
+
'ux-ui',
|
|
112
|
+
'pm',
|
|
113
|
+
'qa',
|
|
114
|
+
'orchestrator',
|
|
115
|
+
'scope-check',
|
|
116
|
+
'discovery-design-doc'
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const ACTIVATION_ONLY_CONTEXT_PATHS_BY_AGENT = new Map([
|
|
120
|
+
[
|
|
121
|
+
'deyvin',
|
|
122
|
+
new Set([...FOUNDATION_ACTIVATION_PATHS, '.aioson/context/dev-state.md'])
|
|
123
|
+
],
|
|
124
|
+
...FOUNDATION_ACTIVATION_AGENTS.map((agent) => [agent, FOUNDATION_ACTIVATION_PATHS])
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
const UNIVERSAL_ALWAYS_CONTEXT_BASENAMES = new Set([
|
|
128
|
+
'project.context.md',
|
|
129
|
+
'project-pulse.md'
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const AGENT_ALWAYS_CONTEXT_BASENAMES = new Map([
|
|
133
|
+
['dev', new Set(['dev-state.md', 'memory-index.md'])],
|
|
134
|
+
['deyvin', new Set(['dev-state.md', 'memory-index.md'])]
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
function normalizeSlashes(value) {
|
|
138
|
+
return String(value || '').replace(/\\/g, '/').replace(/^\.\//, '');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeToken(value) {
|
|
142
|
+
return String(value || '')
|
|
143
|
+
.normalize('NFD')
|
|
144
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
145
|
+
.toLowerCase()
|
|
146
|
+
.replace(/[`*_]/g, '')
|
|
147
|
+
.replace(/[^a-z0-9/-]+/g, ' ')
|
|
148
|
+
.trim();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeFeaturePointer(value) {
|
|
152
|
+
const normalized = normalizeToken(value).replace(/\s+/g, '-');
|
|
153
|
+
if (!normalized || normalized === 'none' || normalized === '-none-' || normalized === '-') return '';
|
|
154
|
+
return normalized;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isActivationOnlyTask(agent, mode, task) {
|
|
158
|
+
if (!ACTIVATION_ONLY_CONTEXT_PATHS_BY_AGENT.has(agent) || mode !== 'planning') return false;
|
|
159
|
+
const normalized = normalizeToken(task);
|
|
160
|
+
if (!normalized) return true;
|
|
161
|
+
return (
|
|
162
|
+
normalized.includes('agent activation') ||
|
|
163
|
+
normalized.includes('activation only') ||
|
|
164
|
+
normalized.includes('without concrete task') ||
|
|
165
|
+
normalized.includes('no concrete task')
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseListValue(value) {
|
|
170
|
+
if (value === undefined || value === null) return [];
|
|
171
|
+
const raw = String(value).trim();
|
|
172
|
+
if (!raw || raw === '[]') return [];
|
|
173
|
+
if (raw.startsWith('[') && raw.endsWith(']')) {
|
|
174
|
+
return raw
|
|
175
|
+
.slice(1, -1)
|
|
176
|
+
.split(',')
|
|
177
|
+
.map((item) => item.trim().replace(/^["']|["']$/g, ''))
|
|
178
|
+
.filter(Boolean);
|
|
179
|
+
}
|
|
180
|
+
return raw
|
|
181
|
+
.split(',')
|
|
182
|
+
.map((item) => item.trim().replace(/^["']|["']$/g, ''))
|
|
183
|
+
.filter(Boolean);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function semanticSearchEnabled(options) {
|
|
187
|
+
const raw = options.semantic;
|
|
188
|
+
if (raw === false) return false;
|
|
189
|
+
if (typeof raw === 'string' && raw.trim().toLowerCase() === 'false') return false;
|
|
190
|
+
if (options.noSemantic === true || options['no-semantic'] === true) return false;
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function modeFromOptions(mode) {
|
|
195
|
+
const normalized = normalizeToken(mode || 'planning');
|
|
196
|
+
return VALID_MODES.has(normalized) ? normalized : 'planning';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function escapeRegex(value) {
|
|
200
|
+
return String(value).replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function globToRegex(glob) {
|
|
204
|
+
const normalized = normalizeSlashes(glob);
|
|
205
|
+
let out = '^';
|
|
206
|
+
for (let i = 0; i < normalized.length; i += 1) {
|
|
207
|
+
const char = normalized[i];
|
|
208
|
+
const next = normalized[i + 1];
|
|
209
|
+
if (char === '*' && next === '*') {
|
|
210
|
+
out += '.*';
|
|
211
|
+
i += 1;
|
|
212
|
+
} else if (char === '*') {
|
|
213
|
+
out += '[^/]*';
|
|
214
|
+
} else {
|
|
215
|
+
out += escapeRegex(char);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
out += '$';
|
|
219
|
+
return new RegExp(out);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function pathMatchesPattern(filePath, pattern) {
|
|
223
|
+
const file = normalizeSlashes(filePath);
|
|
224
|
+
const normalizedPattern = normalizeSlashes(pattern);
|
|
225
|
+
if (!file || !normalizedPattern) return false;
|
|
226
|
+
if (normalizedPattern.endsWith('/**')) {
|
|
227
|
+
const prefix = normalizedPattern.slice(0, -3);
|
|
228
|
+
return file === prefix || file.startsWith(`${prefix}/`);
|
|
229
|
+
}
|
|
230
|
+
if (!normalizedPattern.includes('*')) {
|
|
231
|
+
return file === normalizedPattern || file.startsWith(`${normalizedPattern}/`);
|
|
232
|
+
}
|
|
233
|
+
return globToRegex(normalizedPattern).test(file);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function splitOptionList(value) {
|
|
237
|
+
if (Array.isArray(value)) return value.map(String).filter(Boolean);
|
|
238
|
+
return String(value || '')
|
|
239
|
+
.split(',')
|
|
240
|
+
.map((item) => item.trim())
|
|
241
|
+
.filter(Boolean);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function walkMarkdown(rootDir, relDir, recursive) {
|
|
245
|
+
const absDir = path.join(rootDir, relDir);
|
|
246
|
+
const out = [];
|
|
247
|
+
let entries;
|
|
248
|
+
try {
|
|
249
|
+
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
250
|
+
} catch {
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const entry of entries) {
|
|
255
|
+
if (entry.name.startsWith('.')) continue;
|
|
256
|
+
const childRel = path.join(relDir, entry.name);
|
|
257
|
+
if (entry.isDirectory()) {
|
|
258
|
+
if (recursive) out.push(...await walkMarkdown(rootDir, childRel, recursive));
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
|
262
|
+
if (entry.name.toLowerCase() === 'readme.md') continue;
|
|
263
|
+
out.push(normalizeSlashes(childRel));
|
|
264
|
+
}
|
|
265
|
+
return out.sort();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function inferContextMetadata(relPath, fm) {
|
|
269
|
+
const base = path.basename(relPath);
|
|
270
|
+
const slugMatch = base.match(/^(prd|requirements|spec|design-doc|readiness|implementation-plan|ui-spec|scope-check)-(.+)\.md$/);
|
|
271
|
+
const tags = [];
|
|
272
|
+
let featureSlug = fm.feature_slug || fm.feature || '';
|
|
273
|
+
let loadTier = fm.load_tier || 'trigger';
|
|
274
|
+
|
|
275
|
+
if (FOUNDATION_CONTEXT_BASENAMES.has(base)) {
|
|
276
|
+
if (UNIVERSAL_ALWAYS_CONTEXT_BASENAMES.has(base)) {
|
|
277
|
+
loadTier = fm.load_tier || 'always';
|
|
278
|
+
}
|
|
279
|
+
tags.push('foundation');
|
|
280
|
+
}
|
|
281
|
+
if (slugMatch) {
|
|
282
|
+
tags.push(slugMatch[1], 'feature');
|
|
283
|
+
if (!featureSlug) featureSlug = slugMatch[2];
|
|
284
|
+
}
|
|
285
|
+
if (base === 'discovery.md') tags.push('discovery', 'project-memory', 'entities', 'business-rules');
|
|
286
|
+
if (base === 'architecture.md') tags.push('architecture', 'technical-design', 'module-boundary');
|
|
287
|
+
if (base === 'ui-spec.md') tags.push('ui-spec', 'ui', 'ux', 'frontend', 'visual-design');
|
|
288
|
+
if (base === 'scope-check.md') tags.push('scope-check', 'alignment', 'pre-dev');
|
|
289
|
+
if (relPath.includes('/bootstrap/')) tags.push('bootstrap');
|
|
290
|
+
if (relPath.includes('/features/') && base === 'dossier.md') {
|
|
291
|
+
tags.push('feature', 'dossier');
|
|
292
|
+
if (!featureSlug) {
|
|
293
|
+
const parts = relPath.split('/');
|
|
294
|
+
const index = parts.indexOf('features');
|
|
295
|
+
if (index !== -1) featureSlug = parts[index + 1] || '';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { tags, featureSlug, loadTier };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function collectCandidates(targetDir) {
|
|
303
|
+
const candidates = [];
|
|
304
|
+
|
|
305
|
+
for (const surface of SURFACES) {
|
|
306
|
+
const relPaths = await walkMarkdown(targetDir, surface.dir, surface.recursive);
|
|
307
|
+
for (const relPath of relPaths) {
|
|
308
|
+
if (surface.key === 'feature_dossier' && !relPath.endsWith('/dossier.md')) continue;
|
|
309
|
+
const absPath = path.join(targetDir, relPath);
|
|
310
|
+
const content = await readFileSafe(absPath);
|
|
311
|
+
if (!content) continue;
|
|
312
|
+
const stat = await fs.stat(absPath).catch(() => null);
|
|
313
|
+
const fm = parseFrontmatter(content);
|
|
314
|
+
const inferred = inferContextMetadata(relPath, fm);
|
|
315
|
+
const description = fm.description || fm.name || path.basename(relPath, '.md');
|
|
316
|
+
candidates.push({
|
|
317
|
+
path: relPath,
|
|
318
|
+
surface: surface.key,
|
|
319
|
+
size: stat ? stat.size : content.length,
|
|
320
|
+
frontmatter: fm,
|
|
321
|
+
description,
|
|
322
|
+
agents: parseAgentList(fm.agents),
|
|
323
|
+
modes: parseListValue(fm.modes),
|
|
324
|
+
taskTypes: parseListValue(fm.task_types || fm.taskTypes),
|
|
325
|
+
triggers: parseListValue(fm.triggers),
|
|
326
|
+
aliases: parseListValue(fm.aliases || fm.alias),
|
|
327
|
+
entities: parseListValue(fm.entities || fm.entity),
|
|
328
|
+
retrievalIntents: parseListValue(fm.retrieval_intents || fm.intents || fm.intent),
|
|
329
|
+
pathPatterns: parseListValue(fm.paths || fm.globs),
|
|
330
|
+
scope: fm.scope || '',
|
|
331
|
+
featureSlug: fm.feature_slug || fm.feature || inferred.featureSlug || '',
|
|
332
|
+
tags: [...new Set([...parseListValue(fm.tags), ...inferred.tags])],
|
|
333
|
+
loadTier: fm.load_tier || inferred.loadTier || surface.defaultTier,
|
|
334
|
+
searchText: content.slice(0, 100_000)
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return candidates;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function normalizeForSemantic(value) {
|
|
343
|
+
return normalizeToken(value).replace(/[/-]+/g, ' ');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function addSemanticTerm(out, term) {
|
|
347
|
+
const normalized = normalizeForSemantic(term).trim();
|
|
348
|
+
if (!normalized) return;
|
|
349
|
+
for (const part of normalized.split(/\s+/)) {
|
|
350
|
+
if (!part) continue;
|
|
351
|
+
if (SEMANTIC_STOP_WORDS.has(part)) continue;
|
|
352
|
+
if (part.length < 4 && !SHORT_SEMANTIC_TERMS.has(part)) continue;
|
|
353
|
+
out.add(part);
|
|
354
|
+
if (part.endsWith('s') && part.length > 4) out.add(part.slice(0, -1));
|
|
355
|
+
const synonyms = SEMANTIC_SYNONYMS.get(part) || [];
|
|
356
|
+
for (const synonym of synonyms) out.add(synonym);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function projectSemanticTerms(candidates) {
|
|
361
|
+
const project = candidates.find((candidate) => candidate.path === '.aioson/context/project.context.md');
|
|
362
|
+
if (!project) return [];
|
|
363
|
+
const fm = project.frontmatter || {};
|
|
364
|
+
return [
|
|
365
|
+
fm.framework
|
|
366
|
+
].filter(Boolean);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function buildSemanticTerms({ task, paths, feature, activeFeature }, candidates) {
|
|
370
|
+
const terms = new Set();
|
|
371
|
+
const rawValues = [
|
|
372
|
+
task,
|
|
373
|
+
paths.join(' '),
|
|
374
|
+
feature,
|
|
375
|
+
activeFeature
|
|
376
|
+
].filter(Boolean);
|
|
377
|
+
|
|
378
|
+
for (const raw of rawValues) addSemanticTerm(terms, raw);
|
|
379
|
+
const taskAlreadyNamesFramework = [...terms].some((term) => KNOWN_FRAMEWORK_TERMS.has(term));
|
|
380
|
+
if (!taskAlreadyNamesFramework) {
|
|
381
|
+
for (const raw of projectSemanticTerms(candidates)) addSemanticTerm(terms, raw);
|
|
382
|
+
}
|
|
383
|
+
return [...terms].slice(0, SEMANTIC_MAX_TERMS);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function buildFtsQuery(terms) {
|
|
387
|
+
return terms
|
|
388
|
+
.map((term) => `"${String(term).replace(/"/g, '').trim()}"`)
|
|
389
|
+
.filter((term) => term !== '""')
|
|
390
|
+
.join(' OR ');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function semanticBaseScore(candidate) {
|
|
394
|
+
if (candidate.surface === 'rules') return 26;
|
|
395
|
+
if (candidate.surface === 'design_governance') return 24;
|
|
396
|
+
if (candidate.surface === 'docs') return 22;
|
|
397
|
+
if (candidate.surface === 'bootstrap') return 24;
|
|
398
|
+
if (candidate.surface === 'feature_dossier') return 18;
|
|
399
|
+
return 16;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function semanticMinimumTerms(candidate) {
|
|
403
|
+
if (candidate.surface === 'docs') return 4;
|
|
404
|
+
if (candidate.surface === 'bootstrap') return 3;
|
|
405
|
+
if (candidate.surface === 'context') return 3;
|
|
406
|
+
return 3;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function semanticCandidateAllowed(candidate) {
|
|
410
|
+
const base = path.basename(candidate.path);
|
|
411
|
+
if (candidate.loadTier === 'always') return false;
|
|
412
|
+
if (candidate.surface === 'bootstrap') return base !== 'current-state-archive.md';
|
|
413
|
+
if (candidate.surface === 'context') {
|
|
414
|
+
if (candidate.featureSlug) return true;
|
|
415
|
+
return base === 'design-doc.md' || base === 'readiness.md';
|
|
416
|
+
}
|
|
417
|
+
return ['rules', 'design_governance', 'docs', 'feature_dossier'].includes(candidate.surface);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function matchSemanticTerms(candidate, terms) {
|
|
421
|
+
const haystack = normalizeForSemantic([
|
|
422
|
+
candidate.path,
|
|
423
|
+
candidate.description,
|
|
424
|
+
candidate.scope,
|
|
425
|
+
candidate.tags.join(' '),
|
|
426
|
+
candidate.searchText
|
|
427
|
+
].join(' '));
|
|
428
|
+
return terms.filter((term) => haystack.includes(term));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function buildLexicalSemanticMatches(candidates, terms) {
|
|
432
|
+
const matches = new Map();
|
|
433
|
+
if (terms.length === 0) return matches;
|
|
434
|
+
|
|
435
|
+
for (const candidate of candidates) {
|
|
436
|
+
if (!semanticCandidateAllowed(candidate)) continue;
|
|
437
|
+
const matched = matchSemanticTerms(candidate, terms);
|
|
438
|
+
if (matched.length === 0) continue;
|
|
439
|
+
const score = Math.min(50, semanticBaseScore(candidate) + matched.length * 7);
|
|
440
|
+
matches.set(candidate.path, {
|
|
441
|
+
score,
|
|
442
|
+
terms: matched.slice(0, 6),
|
|
443
|
+
reason: `semantic:${matched.slice(0, 6).join(',')}`
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return matches;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function buildSemanticMatches(candidates, terms) {
|
|
451
|
+
if (terms.length === 0) return new Map();
|
|
452
|
+
const query = buildFtsQuery(terms);
|
|
453
|
+
if (!query) return new Map();
|
|
454
|
+
|
|
455
|
+
let db;
|
|
456
|
+
try {
|
|
457
|
+
db = new Database(':memory:');
|
|
458
|
+
db.exec(`
|
|
459
|
+
CREATE VIRTUAL TABLE candidates USING fts5(
|
|
460
|
+
candidate_id UNINDEXED,
|
|
461
|
+
path,
|
|
462
|
+
title,
|
|
463
|
+
body,
|
|
464
|
+
tokenize = "unicode61 remove_diacritics 2"
|
|
465
|
+
);
|
|
466
|
+
`);
|
|
467
|
+
const insert = db.prepare('INSERT INTO candidates (candidate_id, path, title, body) VALUES (?, ?, ?, ?)');
|
|
468
|
+
const insertMany = db.transaction((items) => {
|
|
469
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
470
|
+
const candidate = items[i];
|
|
471
|
+
insert.run(
|
|
472
|
+
i,
|
|
473
|
+
candidate.path,
|
|
474
|
+
candidate.description || path.basename(candidate.path),
|
|
475
|
+
[
|
|
476
|
+
candidate.path,
|
|
477
|
+
candidate.description,
|
|
478
|
+
candidate.scope,
|
|
479
|
+
candidate.tags.join(' '),
|
|
480
|
+
candidate.searchText
|
|
481
|
+
].join('\n')
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
insertMany(candidates);
|
|
486
|
+
|
|
487
|
+
const rows = db.prepare(`
|
|
488
|
+
SELECT candidate_id
|
|
489
|
+
FROM candidates
|
|
490
|
+
WHERE candidates MATCH ?
|
|
491
|
+
ORDER BY rank
|
|
492
|
+
LIMIT ?
|
|
493
|
+
`).all(query, SEMANTIC_RESULT_LIMIT);
|
|
494
|
+
|
|
495
|
+
const matches = new Map();
|
|
496
|
+
for (const row of rows) {
|
|
497
|
+
const candidate = candidates[Number(row.candidate_id)];
|
|
498
|
+
if (!candidate) continue;
|
|
499
|
+
if (!semanticCandidateAllowed(candidate)) continue;
|
|
500
|
+
const matched = matchSemanticTerms(candidate, terms);
|
|
501
|
+
if (matched.length === 0) continue;
|
|
502
|
+
const score = Math.min(50, semanticBaseScore(candidate) + matched.length * 7);
|
|
503
|
+
matches.set(candidate.path, {
|
|
504
|
+
score,
|
|
505
|
+
terms: matched.slice(0, 6),
|
|
506
|
+
reason: `semantic:${matched.slice(0, 6).join(',')}`
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
return matches;
|
|
510
|
+
} catch {
|
|
511
|
+
return buildLexicalSemanticMatches(candidates, terms);
|
|
512
|
+
} finally {
|
|
513
|
+
if (db) db.close();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function collectMemoryMatches(targetDir, terms) {
|
|
518
|
+
if (terms.length === 0) return [];
|
|
519
|
+
const query = terms.slice(0, 8).join(' ');
|
|
520
|
+
if (!query) return [];
|
|
521
|
+
|
|
522
|
+
let handle = null;
|
|
523
|
+
try {
|
|
524
|
+
handle = await openRuntimeDb(targetDir, { mustExist: true });
|
|
525
|
+
if (!handle || !handle.db) return [];
|
|
526
|
+
const outcome = searchProjectLearnings(handle.db, {
|
|
527
|
+
query,
|
|
528
|
+
limit: MEMORY_RESULT_LIMIT,
|
|
529
|
+
surface: 'all',
|
|
530
|
+
includeArchived: false
|
|
531
|
+
});
|
|
532
|
+
if (!outcome.ok) return [];
|
|
533
|
+
return outcome.results.map((result) => ({
|
|
534
|
+
surface: 'memory',
|
|
535
|
+
target_type: result.target_type,
|
|
536
|
+
target_id: result.target_id,
|
|
537
|
+
feature_slug: result.feature_slug || '',
|
|
538
|
+
status: result.status,
|
|
539
|
+
score: result.score,
|
|
540
|
+
snippet: result.snippet || '',
|
|
541
|
+
reason: `memory_fts:${query}`
|
|
542
|
+
}));
|
|
543
|
+
} catch {
|
|
544
|
+
return [];
|
|
545
|
+
} finally {
|
|
546
|
+
if (handle && handle.db) handle.db.close();
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function keywordMatches(haystack, needles) {
|
|
551
|
+
const normalizedHaystack = normalizeToken(haystack);
|
|
552
|
+
const haystackWords = new Set(normalizedHaystack.split(/\s+/).flatMap(wordVariants));
|
|
553
|
+
return needles.filter((needle) => {
|
|
554
|
+
const normalizedNeedle = normalizeToken(needle);
|
|
555
|
+
if (!normalizedNeedle) return false;
|
|
556
|
+
const needleTokens = normalizedNeedle.split(/\s+/).filter(Boolean);
|
|
557
|
+
// Short single-token needles (e.g. alias "ui", entity "api") must match on a
|
|
558
|
+
// word boundary. A bare substring check false-fires inside "build"/"require"/
|
|
559
|
+
// "rapid", which pollutes selection scoring and — via the entities/aliases
|
|
560
|
+
// salience gate — makes context:guard inject unrelated rules.
|
|
561
|
+
if (needleTokens.length === 1 && normalizedNeedle.length <= 3) {
|
|
562
|
+
return wordVariants(normalizedNeedle).some((variant) => haystackWords.has(variant));
|
|
563
|
+
}
|
|
564
|
+
if (normalizedHaystack.includes(normalizedNeedle)) return true;
|
|
565
|
+
const words = needleTokens.filter((word) => word.length >= 4);
|
|
566
|
+
if (words.length === 0) return false;
|
|
567
|
+
const hits = words.filter((word) => wordVariants(word).some((variant) => haystackWords.has(variant))).length;
|
|
568
|
+
return hits >= Math.min(2, words.length);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function wordVariants(word) {
|
|
573
|
+
const raw = String(word || '').trim();
|
|
574
|
+
if (!raw) return [];
|
|
575
|
+
const variants = new Set([raw]);
|
|
576
|
+
if (raw.endsWith('ing') && raw.length > 5) {
|
|
577
|
+
const stem = raw.slice(0, -3);
|
|
578
|
+
variants.add(stem);
|
|
579
|
+
variants.add(`${stem}e`);
|
|
580
|
+
}
|
|
581
|
+
if (raw.endsWith('s') && raw.length > 4) variants.add(raw.slice(0, -1));
|
|
582
|
+
return [...variants];
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function scoreCandidate(candidate, context) {
|
|
586
|
+
const reasons = [];
|
|
587
|
+
let score = 0;
|
|
588
|
+
let effectiveLoadTier = candidate.loadTier;
|
|
589
|
+
const base = path.basename(candidate.path);
|
|
590
|
+
|
|
591
|
+
if (!appliesToAgent(candidate.frontmatter, context.agent)) return null;
|
|
592
|
+
if (context.activationOnly) {
|
|
593
|
+
const allowedActivationPaths = ACTIVATION_ONLY_CONTEXT_PATHS_BY_AGENT.get(context.agent);
|
|
594
|
+
if (!allowedActivationPaths || !allowedActivationPaths.has(candidate.path)) return null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (candidate.modes.length > 0 && !candidate.modes.map(normalizeToken).includes(context.mode)) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
if (candidate.modes.length > 0) {
|
|
601
|
+
score += 5;
|
|
602
|
+
reasons.push(`mode:${context.mode}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const agentAlways = AGENT_ALWAYS_CONTEXT_BASENAMES.get(context.agent);
|
|
606
|
+
if (agentAlways && agentAlways.has(base)) {
|
|
607
|
+
effectiveLoadTier = 'always';
|
|
608
|
+
score += 100;
|
|
609
|
+
reasons.push('load_tier:always');
|
|
610
|
+
} else if (candidate.loadTier === 'always') {
|
|
611
|
+
score += 100;
|
|
612
|
+
reasons.push('load_tier:always');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const matchedPaths = [];
|
|
616
|
+
for (const requestedPath of context.paths) {
|
|
617
|
+
for (const pattern of candidate.pathPatterns) {
|
|
618
|
+
if (pathMatchesPattern(requestedPath, pattern)) matchedPaths.push(`${requestedPath}~${pattern}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const directPathMatch = context.paths.some((requestedPath) => {
|
|
622
|
+
const normalized = normalizeSlashes(requestedPath);
|
|
623
|
+
return normalized === candidate.path || pathMatchesPattern(candidate.path, normalized);
|
|
624
|
+
});
|
|
625
|
+
if (matchedPaths.length > 0) {
|
|
626
|
+
score += 10;
|
|
627
|
+
reasons.push(`paths:${matchedPaths.slice(0, 3).join(',')}`);
|
|
628
|
+
}
|
|
629
|
+
if (directPathMatch) {
|
|
630
|
+
score += 10;
|
|
631
|
+
reasons.push('paths:direct');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const activeFeature = context.feature || context.activeFeature || '';
|
|
635
|
+
const featureMentioned = candidate.featureSlug
|
|
636
|
+
&& context.lookup.includes(normalizeToken(candidate.featureSlug).replace(/-/g, ' '));
|
|
637
|
+
if (context.activationOnly && candidate.featureSlug && !context.feature) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
if (candidate.featureSlug && candidate.featureSlug !== activeFeature && !featureMentioned && !directPathMatch) {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
if (candidate.featureSlug && activeFeature && candidate.featureSlug === activeFeature) {
|
|
644
|
+
score += 45;
|
|
645
|
+
reasons.push(`feature:${candidate.featureSlug}`);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (featureMentioned) {
|
|
649
|
+
score += 45;
|
|
650
|
+
reasons.push(`feature-mentioned:${candidate.featureSlug}`);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const matchedTaskTypes = keywordMatches(context.lookup, candidate.taskTypes);
|
|
654
|
+
if (matchedTaskTypes.length > 0) {
|
|
655
|
+
score += 40;
|
|
656
|
+
reasons.push(`task_types:${matchedTaskTypes.slice(0, 3).join(',')}`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const matchedTriggers = keywordMatches(context.lookup, candidate.triggers);
|
|
660
|
+
if (matchedTriggers.length > 0) {
|
|
661
|
+
score += 40;
|
|
662
|
+
reasons.push(`triggers:${matchedTriggers.slice(0, 3).join(',')}`);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const matchedAliases = keywordMatches(context.lookup, candidate.aliases);
|
|
666
|
+
if (matchedAliases.length > 0) {
|
|
667
|
+
score += 35;
|
|
668
|
+
reasons.push(`aliases:${matchedAliases.slice(0, 3).join(',')}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const matchedEntities = keywordMatches(context.lookup, candidate.entities);
|
|
672
|
+
if (matchedEntities.length > 0) {
|
|
673
|
+
score += 30;
|
|
674
|
+
reasons.push(`entities:${matchedEntities.slice(0, 3).join(',')}`);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const matchedRetrievalIntents = keywordMatches(context.lookup, candidate.retrievalIntents);
|
|
678
|
+
if (matchedRetrievalIntents.length > 0) {
|
|
679
|
+
score += 25;
|
|
680
|
+
reasons.push(`retrieval_intents:${matchedRetrievalIntents.slice(0, 3).join(',')}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const matchedTags = keywordMatches(context.lookup, candidate.tags);
|
|
684
|
+
if (matchedTags.length > 0) {
|
|
685
|
+
score += 20;
|
|
686
|
+
reasons.push(`tags:${matchedTags.slice(0, 3).join(',')}`);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const descriptionHits = keywordMatches(context.lookup, [
|
|
690
|
+
candidate.description,
|
|
691
|
+
candidate.scope,
|
|
692
|
+
path.basename(candidate.path, '.md').replace(/-/g, ' ')
|
|
693
|
+
]);
|
|
694
|
+
if (descriptionHits.length > 0) {
|
|
695
|
+
score += 20;
|
|
696
|
+
reasons.push(`description:${descriptionHits.slice(0, 2).join(',')}`);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const semanticHit = context.semanticMatches && context.semanticMatches.get(candidate.path);
|
|
700
|
+
const featureRouted = Boolean(
|
|
701
|
+
(candidate.featureSlug && activeFeature && candidate.featureSlug === activeFeature) || featureMentioned
|
|
702
|
+
);
|
|
703
|
+
const hardRoutingHit = matchedPaths.length > 0
|
|
704
|
+
|| directPathMatch
|
|
705
|
+
|| matchedTaskTypes.length > 0
|
|
706
|
+
|| matchedTriggers.length > 0
|
|
707
|
+
|| matchedAliases.length > 0
|
|
708
|
+
|| matchedEntities.length > 0
|
|
709
|
+
|| matchedRetrievalIntents.length > 0
|
|
710
|
+
|| featureRouted;
|
|
711
|
+
const weakJustifiedSemanticHit = candidate.loadTier === 'justified' && semanticHit && semanticHit.terms.length < 3;
|
|
712
|
+
const weakPureSemanticHit = semanticHit && !hardRoutingHit && semanticHit.terms.length < semanticMinimumTerms(candidate);
|
|
713
|
+
if (semanticHit && effectiveLoadTier !== 'always' && !weakJustifiedSemanticHit && !weakPureSemanticHit) {
|
|
714
|
+
score += semanticHit.score;
|
|
715
|
+
reasons.push(semanticHit.reason);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const threshold = effectiveLoadTier === 'justified' ? 50 : 30;
|
|
719
|
+
if (score < threshold) return null;
|
|
720
|
+
|
|
721
|
+
return {
|
|
722
|
+
path: candidate.path,
|
|
723
|
+
surface: candidate.surface,
|
|
724
|
+
load_tier: effectiveLoadTier,
|
|
725
|
+
size: candidate.size,
|
|
726
|
+
score,
|
|
727
|
+
reason: reasons.join('; ')
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async function selectContext(targetDir, options = {}) {
|
|
732
|
+
const agent = normalizeToken(options.agent || 'dev');
|
|
733
|
+
const mode = modeFromOptions(options.mode);
|
|
734
|
+
const task = String(options.task || options.goal || '').trim();
|
|
735
|
+
const paths = splitOptionList(options.paths || options.path).map(normalizeSlashes);
|
|
736
|
+
const feature = normalizeFeaturePointer(options.feature || options.slug || '');
|
|
737
|
+
const activationOnly = isActivationOnlyTask(agent, mode, task);
|
|
738
|
+
|
|
739
|
+
const pulse = await readProjectPulse(targetDir);
|
|
740
|
+
const devState = await readDevState(targetDir);
|
|
741
|
+
const activeFeature = normalizeFeaturePointer(
|
|
742
|
+
feature || pulse.active_feature || devState.active_feature || ''
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
const lookup = normalizeToken([
|
|
746
|
+
task,
|
|
747
|
+
paths.join(' '),
|
|
748
|
+
activeFeature
|
|
749
|
+
].filter(Boolean).join(' '));
|
|
750
|
+
|
|
751
|
+
const candidates = await collectCandidates(targetDir);
|
|
752
|
+
const semanticEnabled = semanticSearchEnabled(options) && !activationOnly;
|
|
753
|
+
const semanticTerms = semanticEnabled
|
|
754
|
+
? buildSemanticTerms({ task, paths, feature, activeFeature }, candidates)
|
|
755
|
+
: [];
|
|
756
|
+
const semanticMatches = semanticEnabled
|
|
757
|
+
? buildSemanticMatches(candidates, semanticTerms)
|
|
758
|
+
: new Map();
|
|
759
|
+
const selected = [];
|
|
760
|
+
for (const candidate of candidates) {
|
|
761
|
+
const scored = scoreCandidate(candidate, {
|
|
762
|
+
agent,
|
|
763
|
+
mode,
|
|
764
|
+
task,
|
|
765
|
+
paths,
|
|
766
|
+
feature,
|
|
767
|
+
activeFeature,
|
|
768
|
+
lookup,
|
|
769
|
+
activationOnly,
|
|
770
|
+
semanticMatches
|
|
771
|
+
});
|
|
772
|
+
if (scored) selected.push(scored);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
selected.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
|
|
776
|
+
const memory = semanticEnabled ? await collectMemoryMatches(targetDir, semanticTerms) : [];
|
|
777
|
+
|
|
778
|
+
return {
|
|
779
|
+
ok: true,
|
|
780
|
+
agent,
|
|
781
|
+
mode,
|
|
782
|
+
task,
|
|
783
|
+
paths,
|
|
784
|
+
feature: feature || null,
|
|
785
|
+
active_feature: activeFeature || null,
|
|
786
|
+
activation_only: activationOnly,
|
|
787
|
+
semantic: {
|
|
788
|
+
enabled: semanticEnabled,
|
|
789
|
+
terms: semanticTerms
|
|
790
|
+
},
|
|
791
|
+
memory,
|
|
792
|
+
selected
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
module.exports = {
|
|
797
|
+
selectContext,
|
|
798
|
+
collectCandidates,
|
|
799
|
+
parseListValue,
|
|
800
|
+
pathMatchesPattern,
|
|
801
|
+
isActivationOnlyTask
|
|
802
|
+
};
|