@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.
Files changed (107) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +7 -5
  3. package/docs/en/5-reference/cli-reference.md +40 -10
  4. package/docs/pt/4-agentes/pm.md +1 -1
  5. package/docs/pt/5-referencia/autopilot-handoff.md +4 -4
  6. package/docs/pt/5-referencia/comandos-cli.md +5 -3
  7. package/docs/pt/5-referencia/fluxo-artefatos.md +1 -1
  8. package/docs/pt/5-referencia/memoria-e-contexto.md +2 -2
  9. package/docs/pt/_arquivo/monitor-de-contexto.md +2 -2
  10. package/package.json +4 -2
  11. package/src/cli.js +67 -24
  12. package/src/commands/ac-test-audit.js +45 -0
  13. package/src/commands/artifact-validate.js +62 -50
  14. package/src/commands/classify.js +73 -2
  15. package/src/commands/context-brief.js +59 -0
  16. package/src/commands/context-guard.js +88 -0
  17. package/src/commands/context-monitor.js +1 -1
  18. package/src/commands/context-search.js +101 -52
  19. package/src/commands/context-select.js +11 -2
  20. package/src/commands/feature-archive.js +21 -12
  21. package/src/commands/feature-current.js +82 -0
  22. package/src/commands/gate-check.js +32 -15
  23. package/src/commands/harness-check.js +17 -1
  24. package/src/commands/hooks-install.js +169 -26
  25. package/src/commands/hygiene-scan.js +423 -0
  26. package/src/commands/rules-lint.js +11 -3
  27. package/src/commands/sdd-benchmark.js +134 -0
  28. package/src/commands/spec-analyze.js +6 -4
  29. package/src/commands/store-system.js +329 -49
  30. package/src/constants.js +8 -3
  31. package/src/context-brief.js +585 -0
  32. package/src/context-guard.js +209 -0
  33. package/src/context-search.js +796 -96
  34. package/src/context-selector.js +802 -444
  35. package/src/handoff-contract.js +14 -6
  36. package/src/harness/contract-schema.js +1 -1
  37. package/src/i18n/messages/en.js +12 -5
  38. package/src/i18n/messages/es.js +11 -4
  39. package/src/i18n/messages/fr.js +11 -4
  40. package/src/i18n/messages/pt-BR.js +12 -5
  41. package/src/lib/ac-test-audit.js +194 -0
  42. package/src/preflight-engine.js +10 -6
  43. package/src/squad/state-manager.js +1 -1
  44. package/template/.aioson/agents/analyst.md +41 -17
  45. package/template/.aioson/agents/architect.md +4 -2
  46. package/template/.aioson/agents/briefing-refiner.md +15 -2
  47. package/template/.aioson/agents/briefing.md +12 -8
  48. package/template/.aioson/agents/committer.md +1 -1
  49. package/template/.aioson/agents/copywriter.md +20 -9
  50. package/template/.aioson/agents/design-hybrid-forge.md +9 -5
  51. package/template/.aioson/agents/dev.md +22 -25
  52. package/template/.aioson/agents/deyvin.md +126 -124
  53. package/template/.aioson/agents/discover.md +3 -1
  54. package/template/.aioson/agents/discovery-design-doc.md +11 -2
  55. package/template/.aioson/agents/forge-run.md +3 -0
  56. package/template/.aioson/agents/genome.md +9 -5
  57. package/template/.aioson/agents/neo.md +30 -24
  58. package/template/.aioson/agents/orache.md +10 -6
  59. package/template/.aioson/agents/orchestrator.md +4 -2
  60. package/template/.aioson/agents/pentester.md +22 -12
  61. package/template/.aioson/agents/pm.md +5 -3
  62. package/template/.aioson/agents/product.md +25 -18
  63. package/template/.aioson/agents/profiler-enricher.md +10 -6
  64. package/template/.aioson/agents/profiler-forge.md +10 -6
  65. package/template/.aioson/agents/profiler-researcher.md +10 -6
  66. package/template/.aioson/agents/qa.md +21 -19
  67. package/template/.aioson/agents/scope-check.md +9 -3
  68. package/template/.aioson/agents/sheldon.md +22 -8
  69. package/template/.aioson/agents/site-forge.md +2 -0
  70. package/template/.aioson/agents/squad.md +4 -2
  71. package/template/.aioson/agents/tester.md +19 -15
  72. package/template/.aioson/agents/ux-ui.md +16 -8
  73. package/template/.aioson/config.md +4 -3
  74. package/template/.aioson/design-docs/agent-loading-contract.md +3 -3
  75. package/template/.aioson/docs/autopilot-handoff.md +3 -3
  76. package/template/.aioson/docs/dev/simple-plan-lane.md +73 -27
  77. package/template/.aioson/docs/dev/stack-conventions.md +1 -1
  78. package/template/.aioson/docs/deyvin/continuity-recovery.md +1 -1
  79. package/template/.aioson/docs/deyvin/runtime-handoffs.md +3 -3
  80. package/template/.aioson/docs/feature-expansion-taxonomy.md +53 -0
  81. package/template/.aioson/docs/handoff-persistence.md +14 -12
  82. package/template/.aioson/docs/product/conversation-playbook.md +1 -1
  83. package/template/.aioson/docs/sheldon/enrichment-paths.md +44 -1
  84. package/template/.aioson/docs/sheldon/harness-contract.md +23 -21
  85. package/template/.aioson/docs/tester/coverage-quality.md +1 -1
  86. package/template/.aioson/docs/ux-ui/design-execution.md +9 -7
  87. package/template/.aioson/rules/README.md +35 -17
  88. package/template/.aioson/rules/agent-structural-contract.md +165 -160
  89. package/template/.aioson/rules/aioson-context-boundary.md +5 -4
  90. package/template/.aioson/rules/canonical-path-contract.md +5 -4
  91. package/template/.aioson/rules/data-format-convention.md +5 -4
  92. package/template/.aioson/rules/disk-first-artifacts.md +2 -2
  93. package/template/.aioson/rules/implementation-structure-and-data-access.md +50 -0
  94. package/template/.aioson/rules/security-baseline.md +4 -3
  95. package/template/.aioson/rules/simple-plan-lane.md +18 -6
  96. package/template/.aioson/rules/source-code-language-convention.md +34 -0
  97. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +24 -23
  98. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -0
  99. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -2
  100. package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +1 -1
  101. package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +72 -0
  102. package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +74 -0
  103. package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +67 -0
  104. package/template/.aioson/skills/static/context-budget-guide.md +1 -1
  105. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -4
  106. package/template/AGENTS.md +36 -19
  107. package/template/CLAUDE.md +9 -5
@@ -0,0 +1,585 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { selectContext } = require('./context-selector');
5
+ const { parseFrontmatter, readFileSafe } = require('./preflight-engine');
6
+ const { withIndex } = require('./context-search');
7
+
8
+ const CODE_AGENTS = new Set(['dev', 'deyvin', 'qa', 'tester', 'pentester']);
9
+ const IMPLEMENTATION_AGENTS = new Set(['dev', 'deyvin']);
10
+ const REVIEW_AGENTS = new Set(['qa', 'tester']);
11
+
12
+ // Recall only consumes markdown knowledge surfaces (rules, docs, features,
13
+ // plans, prds, researchs). Indexing/returning .json/.txt is cost and noise.
14
+ const RECALL_EXTENSIONS = ['.md'];
15
+
16
+ // Framework-keyed convention skills (e.g. `laravel-conventions.md`). When the
17
+ // project stack is known, recalling a *different* framework's convention skill
18
+ // is cross-stack noise, so it is dropped from the advisory recall list.
19
+ const FRAMEWORK_SKILL_TOKENS = new Set([
20
+ 'adonis', 'angular', 'astro', 'django', 'express', 'fastapi', 'flask', 'hono',
21
+ 'laravel', 'next', 'node', 'nuxt', 'phoenix', 'rails', 'react', 'remix',
22
+ 'spring', 'svelte', 'symfony', 'vue'
23
+ ]);
24
+
25
+ const AGENT_PROFILES = {
26
+ dev: {
27
+ role: 'implementation',
28
+ mustSurfaces: new Set(['rules', 'design_governance']),
29
+ shouldSurfaces: new Set(['docs', 'bootstrap', 'context', 'feature_dossier'])
30
+ },
31
+ deyvin: {
32
+ role: 'pair-implementation',
33
+ mustSurfaces: new Set(['rules', 'design_governance']),
34
+ shouldSurfaces: new Set(['docs', 'bootstrap', 'context', 'feature_dossier'])
35
+ },
36
+ qa: {
37
+ role: 'quality-review',
38
+ mustSurfaces: new Set(['rules', 'design_governance']),
39
+ shouldSurfaces: new Set(['docs', 'context', 'feature_dossier', 'bootstrap'])
40
+ },
41
+ tester: {
42
+ role: 'test-design',
43
+ mustSurfaces: new Set(['rules', 'design_governance']),
44
+ shouldSurfaces: new Set(['docs', 'context', 'feature_dossier', 'bootstrap'])
45
+ },
46
+ pentester: {
47
+ role: 'security-review',
48
+ mustSurfaces: new Set(['rules', 'design_governance']),
49
+ shouldSurfaces: new Set(['docs', 'context', 'feature_dossier', 'bootstrap'])
50
+ },
51
+ sheldon: {
52
+ role: 'prd-enrichment',
53
+ mustSurfaces: new Set(['rules']),
54
+ shouldSurfaces: new Set(['docs', 'context', 'feature_dossier', 'bootstrap', 'design_governance'])
55
+ },
56
+ architect: {
57
+ role: 'architecture',
58
+ mustSurfaces: new Set(['rules', 'design_governance']),
59
+ shouldSurfaces: new Set(['docs', 'context', 'feature_dossier', 'bootstrap'])
60
+ }
61
+ };
62
+
63
+ const CONCERN_KEYWORDS = [
64
+ { concern: 'english-code', terms: ['english', 'ingles', 'naming', 'identifier', 'identifiers', 'class', 'function', 'variable'] },
65
+ { concern: 'componentization', terms: ['componentization', 'componentizar', 'split', 'folder', 'folders', 'module', 'file-size', 'service'] },
66
+ { concern: 'data-access', terms: ['query', 'queries', 'sql', 'database', 'eloquent', 'repository', 'controller', 'raw'] },
67
+ { concern: 'framework-conventions', terms: ['laravel', 'framework', 'eloquent', 'artisan', 'controller', 'resource', 'policy', 'formrequest'] },
68
+ { concern: 'security', terms: ['security', 'auth', 'permission', 'token', 'secret', 'tenant', 'upload', 'sanitize', 'password'] },
69
+ { concern: 'testing', terms: ['test', 'tests', 'qa', 'coverage', 'regression', 'verify', 'assert'] },
70
+ { concern: 'prd-enrichment', terms: ['prd', 'requirement', 'acceptance', 'criteria', 'enrich', 'sheldon'] },
71
+ { concern: 'ui', terms: ['ui', 'ux', 'screen', 'frontend', 'layout'] }
72
+ ];
73
+
74
+ const PROFILE_HINTS = {
75
+ implementation: [
76
+ 'Load must_load paths before editing code.',
77
+ 'Use should_load paths when a decision is ambiguous or the touched path overlaps their reason.',
78
+ 'After implementation, verify the diff against verification_hints before marking the slice done.'
79
+ ],
80
+ 'pair-implementation': [
81
+ 'Load must_load paths before code inspection or editing.',
82
+ 'Keep the slice small enough to verify in one loop.',
83
+ 'Escalate when the package shows missing paths, missing feature context, or architecture uncertainty.'
84
+ ],
85
+ 'quality-review': [
86
+ 'Treat loaded rules and constraints as review criteria.',
87
+ 'Verify behavior, regressions, and implementation shape against the package before PASS.',
88
+ 'Report gaps as actionable findings with file/path references.'
89
+ ],
90
+ 'test-design': [
91
+ 'Turn constraints and forbidden_patterns into focused regression tests.',
92
+ 'Prioritize tests around touched paths, data boundaries, framework conventions, and prior failure signals.',
93
+ 'Name the verification command required to prove the package.'
94
+ ],
95
+ 'security-review': [
96
+ 'Turn constraints into a feature-specific threat model.',
97
+ 'Probe auth, permission, input validation, data exposure, tenant boundaries, uploads, secrets, and unsafe query construction when present.',
98
+ 'Separate confirmed vulnerabilities from hardening recommendations.'
99
+ ],
100
+ 'prd-enrichment': [
101
+ 'Use constraints to enrich requirements before implementation starts.',
102
+ 'Convert downstream ambiguity into explicit acceptance criteria or open questions.',
103
+ 'Do not reopen decisions already grounded by selected rules or feature artifacts.'
104
+ ],
105
+ architecture: [
106
+ 'Use selected rules and design governance as architecture constraints.',
107
+ 'Make module boundaries, data boundaries, and framework conventions explicit for downstream agents.',
108
+ 'Link applicable governance artifacts in the architecture handoff.'
109
+ ],
110
+ generic: [
111
+ 'Load must_load paths first.',
112
+ 'Use constraints as the operating contract for this turn.',
113
+ 'Treat gaps as clarification or routing signals.'
114
+ ]
115
+ };
116
+
117
+ function normalizeToken(value) {
118
+ return String(value || '')
119
+ .normalize('NFD')
120
+ .replace(/[\u0300-\u036f]/g, '')
121
+ .toLowerCase()
122
+ .replace(/[^a-z0-9/-]+/g, ' ')
123
+ .trim();
124
+ }
125
+
126
+ function dedupe(items, limit = 12) {
127
+ const seen = new Set();
128
+ const out = [];
129
+ for (const item of items) {
130
+ const text = String(item || '').trim();
131
+ if (!text) continue;
132
+ const key = normalizeToken(text);
133
+ if (!key || seen.has(key)) continue;
134
+ seen.add(key);
135
+ out.push(text);
136
+ if (out.length >= limit) break;
137
+ }
138
+ return out;
139
+ }
140
+
141
+ function compactPathItem(item) {
142
+ return {
143
+ path: item.path,
144
+ surface: item.surface,
145
+ load_tier: item.load_tier,
146
+ score: item.score,
147
+ reason: item.reason
148
+ };
149
+ }
150
+
151
+ function inferOperation(agent, mode, task) {
152
+ const text = normalizeToken(`${agent} ${mode} ${task}`);
153
+ if (text.includes('pentest') || text.includes('security') || text.includes('vulnerability')) return 'security-review';
154
+ if (agent === 'sheldon' || text.includes('prd') || text.includes('enrich')) return 'prd-enrichment';
155
+ if (agent === 'tester' || text.includes('test')) return 'test-design';
156
+ if (agent === 'qa' || text.includes('review')) return 'quality-review';
157
+ if (agent === 'architect' || text.includes('architecture')) return 'architecture';
158
+ if (mode === 'executing' || text.includes('implement') || text.includes('refactor')) return 'implementation';
159
+ return 'planning';
160
+ }
161
+
162
+ function inferStack(selection, documents) {
163
+ const terms = new Set(selection.semantic && Array.isArray(selection.semantic.terms) ? selection.semantic.terms : []);
164
+ for (const term of terms) {
165
+ if (term === 'laravel') return 'Laravel';
166
+ if (term === 'php') return 'PHP';
167
+ if (term === 'react') return 'React';
168
+ if (term === 'next') return 'Next.js';
169
+ if (term === 'node') return 'Node.js';
170
+ }
171
+
172
+ const project = documents.get('.aioson/context/project.context.md') || '';
173
+ const match = project.match(/^framework:\s*"?([^"\n]+)"?\s*$/m);
174
+ return match ? match[1].trim() : '';
175
+ }
176
+
177
+ function includesConcernTerm(haystack, rawTerm) {
178
+ const term = normalizeToken(rawTerm);
179
+ if (!term) return false;
180
+ if (term.length <= 3) {
181
+ return new RegExp(`(^|\\s)${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`).test(haystack);
182
+ }
183
+ return haystack.includes(term);
184
+ }
185
+
186
+ function inferConcerns(selection, task) {
187
+ const haystack = normalizeToken([
188
+ task,
189
+ selection.paths ? selection.paths.join(' ') : '',
190
+ selection.selected ? selection.selected.map((item) => `${item.path} ${item.reason}`).join(' ') : ''
191
+ ].join(' '));
192
+
193
+ const concerns = [];
194
+ for (const item of CONCERN_KEYWORDS) {
195
+ if (item.terms.some((term) => includesConcernTerm(haystack, term))) concerns.push(item.concern);
196
+ }
197
+ return dedupe(concerns, 8);
198
+ }
199
+
200
+ function extractSectionBullets(markdown, headingPatterns) {
201
+ const lines = String(markdown || '').split(/\r?\n/);
202
+ const out = [];
203
+ let active = false;
204
+ let activeLevel = 0;
205
+ for (const line of lines) {
206
+ const heading = line.match(/^(#{2,6})\s+(.+?)\s*$/);
207
+ if (heading) {
208
+ const level = heading[1].length;
209
+ const title = normalizeToken(heading[2]);
210
+ if (active && level <= activeLevel) active = false;
211
+ if (!active && headingPatterns.some((pattern) => pattern.test(title))) {
212
+ active = true;
213
+ activeLevel = level;
214
+ }
215
+ continue;
216
+ }
217
+ if (!active) continue;
218
+ const bullet = line.match(/^\s*[-*]\s+(.+?)\s*$/);
219
+ if (bullet) out.push(bullet[1].trim());
220
+ }
221
+ return out;
222
+ }
223
+
224
+ function extractDirectiveBullets(markdown) {
225
+ const out = [];
226
+ const lines = String(markdown || '').split(/\r?\n/);
227
+ for (const line of lines) {
228
+ const bullet = line.match(/^\s*[-*]\s+(.+?)\s*$/);
229
+ if (!bullet) continue;
230
+ const text = bullet[1].trim();
231
+ if (/^(use|keep|prefer|put|load|treat|turn|verify|report|make|add|separate)\b/i.test(text)) out.push(text);
232
+ if (/^(do not|never|avoid|no\s+)/i.test(text)) out.push(text);
233
+ }
234
+ return out;
235
+ }
236
+
237
+ function forbiddenFromBullets(bullets) {
238
+ return bullets.filter((text) => (
239
+ /^(do not|never|avoid|no\s+)/i.test(text)
240
+ || /\bmust not\b/i.test(text)
241
+ || /\bnot expose\b/i.test(text)
242
+ || /\braw sql\b/i.test(text)
243
+ || /\buser input\b/i.test(text)
244
+ ));
245
+ }
246
+
247
+ const REQUIRED_CONSTRAINT_HEADINGS = [
248
+ /required behavior/,
249
+ /framework first/,
250
+ /componentization/,
251
+ /data access/,
252
+ /controls/,
253
+ /rules/,
254
+ /baseline/
255
+ ];
256
+
257
+ const REVIEW_HEADINGS = [/review checklist/, /checklist/, /verification/];
258
+
259
+ // Extract the operating constraints from a SINGLE governing document.
260
+ // Shared by the brief (aggregate over all selected docs) and the guard
261
+ // (per-rule attribution — the injection carries only the matched rule's own
262
+ // constraints, not the generic concern-based ones).
263
+ function extractDocConstraints(content) {
264
+ const required = extractSectionBullets(content, REQUIRED_CONSTRAINT_HEADINGS);
265
+ const review = extractSectionBullets(content, REVIEW_HEADINGS);
266
+ const directives = extractDirectiveBullets(content);
267
+ const base = [...required, ...directives];
268
+ return {
269
+ constraints: base,
270
+ forbidden_patterns: forbiddenFromBullets(base),
271
+ verification_hints: review
272
+ };
273
+ }
274
+
275
+ function isHardConstraintDoc(item, content) {
276
+ if (item.surface === 'rules' || item.surface === 'design_governance') return true;
277
+ if (item.surface !== 'docs') return false;
278
+ const frontmatter = parseFrontmatter(content || '');
279
+ const scope = normalizeToken(frontmatter.constraint_scope || frontmatter.constraints || '');
280
+ return scope === 'hard' || scope === 'contract';
281
+ }
282
+
283
+ function constraintsFromDocuments(documents, selected) {
284
+ const constraints = [];
285
+ const forbidden = [];
286
+ const checks = [];
287
+
288
+ for (const item of selected) {
289
+ const content = documents.get(item.path) || '';
290
+ if (!isHardConstraintDoc(item, content)) continue;
291
+ const doc = extractDocConstraints(content);
292
+ constraints.push(...doc.constraints);
293
+ forbidden.push(...doc.forbidden_patterns);
294
+ checks.push(...doc.verification_hints);
295
+ }
296
+
297
+ return {
298
+ constraints: dedupe(constraints, 14),
299
+ forbidden_patterns: dedupe(forbidden, 10),
300
+ verification_hints: dedupe(checks, 10)
301
+ };
302
+ }
303
+
304
+ function profileVerificationHints(profile, concerns) {
305
+ const hints = [...(PROFILE_HINTS[profile.role] || PROFILE_HINTS.generic)];
306
+
307
+ if (IMPLEMENTATION_AGENTS.has(profile.agent)) {
308
+ hints.push('Scan the final diff for violations of forbidden_patterns.');
309
+ }
310
+ if (REVIEW_AGENTS.has(profile.agent)) {
311
+ hints.push('Check that must_load rules are represented in the review criteria.');
312
+ }
313
+ if (profile.agent === 'pentester') {
314
+ hints.push('Build probes from attack surfaces implied by touched paths and constraints.');
315
+ }
316
+ if (profile.agent === 'sheldon') {
317
+ hints.push('Convert missing downstream constraints into PRD acceptance criteria or explicit open questions.');
318
+ }
319
+ if (concerns.includes('english-code')) {
320
+ hints.push('Check new source identifiers are technical English while user-facing copy stays in project language.');
321
+ }
322
+
323
+ return hints;
324
+ }
325
+
326
+ function concernConstraints(concerns) {
327
+ const constraints = [];
328
+
329
+ if (concerns.includes('english-code')) {
330
+ constraints.push('Use technical English for source code identifiers, filenames, classes, methods, variables, migrations, tests, and framework artifacts.');
331
+ }
332
+ if (concerns.includes('componentization')) {
333
+ constraints.push('Keep files focused and split orchestration, validation, data access, formatting, and side effects into framework-appropriate units.');
334
+ }
335
+ if (concerns.includes('data-access')) {
336
+ constraints.push('Keep controllers and route handlers thin: validate, authorize, delegate, and return a response.');
337
+ constraints.push('Keep persistence details out of controllers, route handlers, UI components, views, jobs, and unrelated services.');
338
+ constraints.push('Parameterize queries and keep reusable filters in the framework-appropriate data access layer.');
339
+ }
340
+ if (concerns.includes('framework-conventions')) {
341
+ constraints.push('Prefer existing project and framework conventions before creating custom plumbing.');
342
+ }
343
+ if (concerns.includes('security')) {
344
+ constraints.push('Map auth, ownership, input validation, data exposure, tenant, upload, secret, and query-construction surfaces before approval.');
345
+ }
346
+ if (concerns.includes('prd-enrichment')) {
347
+ constraints.push('Convert implementation, security, and testing ambiguity into explicit acceptance criteria or open questions.');
348
+ }
349
+
350
+ return constraints;
351
+ }
352
+
353
+ function suggestedStructure(concerns) {
354
+ if (concerns.includes('componentization')) {
355
+ return [
356
+ 'Keep entrypoints thin and delegate orchestration to focused modules.',
357
+ 'Split reusable data access, validation, formatting, and side effects into framework-appropriate units.',
358
+ 'Keep tests aligned with the new module boundaries.'
359
+ ];
360
+ }
361
+ return [];
362
+ }
363
+
364
+ function profileForAgent(agent) {
365
+ const base = AGENT_PROFILES[agent] || {
366
+ role: 'generic',
367
+ mustSurfaces: new Set(['rules']),
368
+ shouldSurfaces: new Set(['docs', 'context', 'design_governance', 'bootstrap', 'feature_dossier'])
369
+ };
370
+ return { ...base, agent };
371
+ }
372
+
373
+ function classifyLoads(selection, profile) {
374
+ const selected = selection.selected || [];
375
+ const must = [];
376
+ const should = [];
377
+
378
+ for (const item of selected) {
379
+ if (item.load_tier === 'always' || profile.mustSurfaces.has(item.surface)) {
380
+ must.push(compactPathItem(item));
381
+ continue;
382
+ }
383
+ if (profile.shouldSurfaces.has(item.surface)) should.push(compactPathItem(item));
384
+ }
385
+
386
+ return {
387
+ must_load: must.slice(0, 14),
388
+ should_load: should.slice(0, 10)
389
+ };
390
+ }
391
+
392
+ function confidenceFrom({ selection, mustLoad, gaps }) {
393
+ if (selection.activation_only || gaps.some((gap) => gap.code === 'missing_task')) return 'low';
394
+ if (mustLoad.length === 0) return 'low';
395
+ if (gaps.length > 0) return 'medium';
396
+ return 'high';
397
+ }
398
+
399
+ function buildGaps({ selection, agent, mode, task, paths, mustLoad }) {
400
+ const gaps = [];
401
+ if (!String(task || '').trim()) {
402
+ gaps.push({ code: 'missing_task', message: 'No concrete task was provided; package is foundation-only.' });
403
+ }
404
+ if (selection.activation_only) {
405
+ gaps.push({ code: 'activation_only', message: 'Activation-only context; do not expand into implementation or review work.' });
406
+ }
407
+ if (mode === 'executing' && CODE_AGENTS.has(agent) && paths.length === 0) {
408
+ gaps.push({ code: 'missing_paths', message: 'Executing code/review work without touched paths reduces retrieval precision.' });
409
+ }
410
+ if (mustLoad.length === 0) {
411
+ gaps.push({ code: 'no_must_load', message: 'No mandatory rules or governance were selected for this task.' });
412
+ }
413
+ return gaps;
414
+ }
415
+
416
+ async function loadSelectedDocuments(targetDir, selected) {
417
+ const documents = new Map();
418
+ for (const item of selected) {
419
+ const content = await readFileSafe(path.join(targetDir, item.path));
420
+ if (content) documents.set(item.path, content);
421
+ }
422
+ return documents;
423
+ }
424
+
425
+ function normalizeForRecall(value) {
426
+ const normalized = String(value || '').replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
427
+ return normalized.startsWith('template/') ? normalized.slice('template/'.length) : normalized;
428
+ }
429
+
430
+ function recallEnabled(options) {
431
+ const raw = options.recall;
432
+ return raw === true || (typeof raw === 'string' && raw.trim().toLowerCase() === 'true');
433
+ }
434
+
435
+ // A recalled framework-convention skill for a stack other than the project's is
436
+ // cross-stack noise (e.g. surfacing `laravel-conventions.md` on a Node project).
437
+ // Only fires when the project stack is known AND the hit is a framework-keyed
438
+ // skill — everything else is kept.
439
+ function isForeignStackSkill(relPath, stack) {
440
+ const normalizedStack = normalizeToken(stack);
441
+ if (!normalizedStack) return false;
442
+ const normalizedPath = normalizeForRecall(relPath);
443
+ if (!normalizedPath.includes('/skills/')) return false;
444
+ const base = normalizedPath.split('/').pop() || '';
445
+ const token = (base.match(/^([a-z0-9]+)[-_]/) || [])[1] || '';
446
+ if (!token || !FRAMEWORK_SKILL_TOKENS.has(token)) return false;
447
+ return !normalizedStack.split(/\s+/).includes(token);
448
+ }
449
+
450
+ // Broad recall over the indexed corpus (incl. archived features, plans, prds,
451
+ // researchs) — the historical surface the live `select` walk cannot see. Kept
452
+ // as a SEPARATE advisory section: it never feeds must_load (select stays the
453
+ // precision gate), and is deduped against what select already selected. Off by
454
+ // default; only the agent-facing `context:brief` command opts in.
455
+ async function collectRecall(targetDir, query, selection, options) {
456
+ if (!query) return [];
457
+ const selectedPaths = new Set((selection.selected || []).map((item) => normalizeForRecall(item.path)));
458
+ const stack = options.stack || '';
459
+ try {
460
+ const pkg = await withIndex(async (idx) => {
461
+ await idx.indexDirectory(targetDir, { extensions: RECALL_EXTENSIONS });
462
+ return idx.searchPackage(query, {
463
+ projectDir: targetDir,
464
+ limit: 8,
465
+ agent: selection.agent,
466
+ mode: selection.mode,
467
+ paths: (selection.paths || []).join(',')
468
+ });
469
+ }, options.searchDir);
470
+
471
+ const hits = pkg.results || [];
472
+ const seen = new Set();
473
+ const related = [];
474
+ for (const hit of hits) {
475
+ const key = normalizeForRecall(hit.relPath);
476
+ if (!key || !key.endsWith('.md') || selectedPaths.has(key) || seen.has(key)) continue;
477
+ if (isForeignStackSkill(hit.relPath, stack)) continue;
478
+ seen.add(key);
479
+ related.push({
480
+ path: hit.relPath,
481
+ title: hit.title,
482
+ snippet: hit.snippet,
483
+ score: hit.score,
484
+ source_type: hit.source_type,
485
+ reason: hit.reason
486
+ });
487
+ if (related.length >= 6) break;
488
+ }
489
+ return related;
490
+ } catch {
491
+ return [];
492
+ }
493
+ }
494
+
495
+ async function buildContextBrief(targetDir, options = {}) {
496
+ const agent = normalizeToken(options.agent || 'dev');
497
+ const mode = options.mode || 'planning';
498
+ const task = String(options.task || options.goal || '').trim();
499
+ const paths = Array.isArray(options.paths)
500
+ ? options.paths
501
+ : String(options.paths || options.path || '')
502
+ .split(',')
503
+ .map((item) => item.trim())
504
+ .filter(Boolean);
505
+
506
+ const selection = await selectContext(targetDir, {
507
+ agent,
508
+ mode,
509
+ task,
510
+ paths: paths.join(','),
511
+ feature: options.feature || options.slug || '',
512
+ semantic: options.semantic,
513
+ noSemantic: options.noSemantic || options['no-semantic']
514
+ });
515
+
516
+ const profile = profileForAgent(selection.agent);
517
+ const documents = await loadSelectedDocuments(targetDir, selection.selected || []);
518
+ const stack = inferStack(selection, documents);
519
+ const concerns = inferConcerns(selection, task);
520
+ const { must_load: mustLoad, should_load: shouldLoad } = classifyLoads(selection, profile);
521
+ const mustLoadPaths = new Set(mustLoad.map((item) => item.path));
522
+ const constraintSources = (selection.selected || []).filter((item) => {
523
+ const hard = isHardConstraintDoc(item, documents.get(item.path) || '');
524
+ if (!hard) return false;
525
+ return mustLoadPaths.has(item.path) || item.surface === 'docs';
526
+ });
527
+ const extracted = constraintsFromDocuments(documents, constraintSources);
528
+ const structure = suggestedStructure(concerns);
529
+ const profileHints = profileVerificationHints(profile, concerns);
530
+ const constraints = dedupe([...concernConstraints(concerns), ...extracted.constraints, ...structure], 18);
531
+ const forbiddenPatterns = dedupe(extracted.forbidden_patterns, 10);
532
+ const verificationHints = dedupe([...extracted.verification_hints, ...profileHints], 14);
533
+ const gaps = buildGaps({ selection, agent: selection.agent, mode: selection.mode, task, paths: selection.paths, mustLoad });
534
+
535
+ const fallbackUsed = ['context_select'];
536
+ if (selection.semantic && selection.semantic.enabled) fallbackUsed.push('semantic_search');
537
+ if (selection.memory && selection.memory.length > 0) fallbackUsed.push('runtime_memory');
538
+
539
+ const recallQuery = [task, paths.join(' '), options.feature || options.slug || ''].filter(Boolean).join(' ').trim();
540
+ const related = recallEnabled(options)
541
+ ? await collectRecall(targetDir, recallQuery, selection, { ...options, stack })
542
+ : [];
543
+ if (related.length > 0) fallbackUsed.push('broad_recall');
544
+
545
+ return {
546
+ ok: true,
547
+ agent: selection.agent,
548
+ mode: selection.mode,
549
+ task: selection.task,
550
+ paths: selection.paths,
551
+ feature: selection.feature,
552
+ active_feature: selection.active_feature,
553
+ intent: {
554
+ agent: selection.agent,
555
+ mode: selection.mode,
556
+ role: profile.role,
557
+ operation: inferOperation(selection.agent, selection.mode, task),
558
+ stack,
559
+ concerns
560
+ },
561
+ must_load: mustLoad,
562
+ should_load: shouldLoad,
563
+ constraints,
564
+ forbidden_patterns: forbiddenPatterns,
565
+ suggested_structure: structure,
566
+ verification_hints: verificationHints,
567
+ review_criteria: dedupe([...verificationHints, ...constraints], 18),
568
+ memory: selection.memory || [],
569
+ related,
570
+ selected_count: selection.selected.length,
571
+ semantic: selection.semantic,
572
+ confidence: confidenceFrom({ selection, mustLoad, gaps }),
573
+ gaps,
574
+ fallback_used: fallbackUsed
575
+ };
576
+ }
577
+
578
+ module.exports = {
579
+ buildContextBrief,
580
+ inferOperation,
581
+ inferConcerns,
582
+ suggestedStructure,
583
+ extractDocConstraints,
584
+ classifyLoads
585
+ };