@jaimevalasek/aioson 1.29.1 → 1.30.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +28 -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 +19 -6
  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/integrations/dashboard-app-form-publish-mapping.md +183 -0
  83. package/template/.aioson/docs/play/README.md +72 -0
  84. package/template/.aioson/docs/play/agent-usage-guide.md +106 -0
  85. package/template/.aioson/docs/play/app-compatibility-guide.md +112 -0
  86. package/template/.aioson/docs/play/auth-services-and-testing.md +220 -0
  87. package/template/.aioson/docs/play/llm-data-and-bindings.md +238 -0
  88. package/template/.aioson/docs/play/manifest-and-runtime.md +244 -0
  89. package/template/.aioson/docs/play/source-map.md +104 -0
  90. package/template/.aioson/docs/product/conversation-playbook.md +1 -1
  91. package/template/.aioson/docs/sheldon/enrichment-paths.md +44 -1
  92. package/template/.aioson/docs/sheldon/harness-contract.md +23 -21
  93. package/template/.aioson/docs/tester/coverage-quality.md +1 -1
  94. package/template/.aioson/docs/ux-ui/design-execution.md +9 -7
  95. package/template/.aioson/rules/README.md +35 -17
  96. package/template/.aioson/rules/agent-structural-contract.md +165 -160
  97. package/template/.aioson/rules/aioson-context-boundary.md +5 -4
  98. package/template/.aioson/rules/canonical-path-contract.md +5 -4
  99. package/template/.aioson/rules/data-format-convention.md +5 -4
  100. package/template/.aioson/rules/disk-first-artifacts.md +2 -2
  101. package/template/.aioson/rules/implementation-structure-and-data-access.md +50 -0
  102. package/template/.aioson/rules/security-baseline.md +4 -3
  103. package/template/.aioson/rules/simple-plan-lane.md +18 -6
  104. package/template/.aioson/rules/source-code-language-convention.md +34 -0
  105. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +24 -23
  106. package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +4 -0
  107. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -2
  108. package/template/.aioson/skills/process/aioson-spec-driven/references/qa.md +1 -1
  109. package/template/.aioson/skills/process/briefing-expansion-scout/SKILL.md +72 -0
  110. package/template/.aioson/skills/process/product-scope-expansion/SKILL.md +74 -0
  111. package/template/.aioson/skills/process/sheldon-expansion-audit/SKILL.md +67 -0
  112. package/template/.aioson/skills/static/context-budget-guide.md +1 -1
  113. package/template/.aioson/skills/static/multi-agent-patterns.md +5 -4
  114. package/template/AGENTS.md +36 -19
  115. package/template/CLAUDE.md +9 -5
@@ -19,23 +19,25 @@ const {
19
19
  parseFrontmatter,
20
20
  contextDir
21
21
  } = require('../preflight-engine');
22
+ const { AC_ID_RE } = require('../lib/ac-test-audit');
22
23
 
23
24
  const BAR = '━'.repeat(45);
25
+ const REQ_ID_RE = /\bREQ(?:-[A-Za-z0-9]+)+\b/g;
24
26
 
25
- function gateDisplay(gates) {
26
- const letters = { requirements: 'A', design: 'B', plan: 'C', execution: 'D' };
27
+ function gateDisplay(gates) {
28
+ const letters = { requirements: 'A', design: 'B', plan: 'C', execution: 'D' };
27
29
  return Object.entries(letters).map(([name, letter]) => {
28
30
  const status = gates[name];
29
31
  return status === 'approved' ? `${letter}✓` : `${letter}○`;
30
- }).join(' ');
31
- }
32
-
33
- function artifactDisplayName(artifact, fallbackName) {
34
- if (artifact && artifact.exists && artifact.path) return path.basename(artifact.path);
35
- return fallbackName;
36
- }
37
-
38
- async function runArtifactValidate({ args, options = {}, logger }) {
32
+ }).join(' ');
33
+ }
34
+
35
+ function artifactDisplayName(artifact, fallbackName) {
36
+ if (artifact && artifact.exists && artifact.path) return path.basename(artifact.path);
37
+ return fallbackName;
38
+ }
39
+
40
+ async function runArtifactValidate({ args, options = {}, logger }) {
39
41
  const targetDir = path.resolve(process.cwd(), args[0] || '.');
40
42
  const slug = options.feature ? String(options.feature) : null;
41
43
 
@@ -60,6 +62,9 @@ async function runArtifactValidate({ args, options = {}, logger }) {
60
62
  const sheldonReady = artifacts.sheldon_enrichment.exists
61
63
  ? (artifacts.sheldon_enrichment.frontmatter.readiness === 'ready_for_downstream' ? 'ready_for_downstream' : 'present')
62
64
  : null;
65
+ const sheldonValidationReady = artifacts.sheldon_validation.exists
66
+ ? (artifacts.sheldon_validation.frontmatter.verdict || artifacts.sheldon_validation.frontmatter.readiness || 'present')
67
+ : null;
63
68
 
64
69
  // Implementation plan status
65
70
  const planStatus = artifacts.implementation_plan.exists
@@ -70,19 +75,19 @@ async function runArtifactValidate({ args, options = {}, logger }) {
70
75
  // (REQ-SDLC-01), because feature contracts use slugged identifiers.
71
76
  let reqCount = null;
72
77
  if (artifacts.requirements.exists && artifacts.requirements.content) {
73
- const reqs = artifacts.requirements.content.match(/\bREQ(?:-[A-Z0-9]+)+\b/g) || [];
74
- const acs = artifacts.requirements.content.match(/\bAC(?:-[A-Z0-9]+)+\b/g) || [];
78
+ const reqs = artifacts.requirements.content.match(REQ_ID_RE) || [];
79
+ const acs = artifacts.requirements.content.match(AC_ID_RE) || [];
75
80
  reqCount = `${new Set(reqs).size} REQs, ${new Set(acs).size} ACs`;
76
81
  }
77
82
 
78
- // Conformance required?
79
- const conformanceRequired = classification === 'MEDIUM';
80
- const designDocRequired = classification === 'SMALL' || classification === 'MEDIUM';
81
- const designDocName = artifactDisplayName(artifacts.design_doc, `design-doc-${slug}.md`);
82
- const readinessName = artifactDisplayName(artifacts.readiness, `readiness-${slug}.md`);
83
-
84
- // Build chain items
85
- const chain = [
83
+ // Conformance required?
84
+ const conformanceRequired = classification === 'MEDIUM';
85
+ const designDocRequired = classification === 'SMALL' || classification === 'MEDIUM';
86
+ const designDocName = artifactDisplayName(artifacts.design_doc, `design-doc-${slug}.md`);
87
+ const readinessName = artifactDisplayName(artifacts.readiness, `readiness-${slug}.md`);
88
+
89
+ // Build chain items
90
+ const chain = [
86
91
  {
87
92
  name: 'project.context.md',
88
93
  exists: artifacts.project_context.exists,
@@ -104,6 +109,13 @@ async function runArtifactValidate({ args, options = {}, logger }) {
104
109
  required: false,
105
110
  indent: 1
106
111
  },
112
+ {
113
+ name: `sheldon-validation-${slug}.md`,
114
+ exists: artifacts.sheldon_validation.exists,
115
+ detail: sheldonValidationReady ? `verdict: ${sheldonValidationReady}` : 'MEDIUM readiness verdict when @sheldon runs',
116
+ required: false,
117
+ indent: 1
118
+ },
107
119
  {
108
120
  name: `requirements-${slug}.md`,
109
121
  exists: artifacts.requirements.exists,
@@ -118,29 +130,29 @@ async function runArtifactValidate({ args, options = {}, logger }) {
118
130
  required: true,
119
131
  indent: 1
120
132
  },
121
- {
122
- name: 'architecture.md',
123
- exists: artifacts.architecture.exists,
124
- detail: null,
125
- required: true,
126
- indent: 1
127
- },
128
- {
129
- name: designDocName,
130
- exists: artifacts.design_doc.exists,
131
- detail: designDocRequired ? 'pre-dev design governance contract' : `SMALL/MEDIUM only — NOT required for ${classification || 'MICRO'}`,
132
- required: designDocRequired,
133
- indent: 1
134
- },
135
- {
136
- name: readinessName,
137
- exists: artifacts.readiness.exists,
138
- detail: designDocRequired ? 'pre-dev readiness contract' : `SMALL/MEDIUM only — NOT required for ${classification || 'MICRO'}`,
139
- required: designDocRequired,
140
- indent: 1
141
- },
142
- {
143
- name: `implementation-plan-${slug}.md`,
133
+ {
134
+ name: 'architecture.md',
135
+ exists: artifacts.architecture.exists,
136
+ detail: null,
137
+ required: true,
138
+ indent: 1
139
+ },
140
+ {
141
+ name: designDocName,
142
+ exists: artifacts.design_doc.exists,
143
+ detail: designDocRequired ? 'pre-dev design governance contract' : `SMALL/MEDIUM only — NOT required for ${classification || 'MICRO'}`,
144
+ required: designDocRequired,
145
+ indent: 1
146
+ },
147
+ {
148
+ name: readinessName,
149
+ exists: artifacts.readiness.exists,
150
+ detail: designDocRequired ? 'pre-dev readiness contract' : `SMALL/MEDIUM only — NOT required for ${classification || 'MICRO'}`,
151
+ required: designDocRequired,
152
+ indent: 1
153
+ },
154
+ {
155
+ name: `implementation-plan-${slug}.md`,
144
156
  exists: artifacts.implementation_plan.exists,
145
157
  detail: planStatus ? `status: ${planStatus}` : null,
146
158
  required: true,
@@ -164,12 +176,12 @@ async function runArtifactValidate({ args, options = {}, logger }) {
164
176
  // Determine next_missing and next_agent (AC-SDLC-22)
165
177
  const ARTIFACT_OWNER_MAP = {
166
178
  'project.context.md': { agent: '@setup', reason: 'setup not complete' },
167
- [`prd-${slug}.md`]: { agent: '@product', reason: 'PRD not produced yet' },
168
- [`requirements-${slug}.md`]: { agent: '@analyst', reason: 'requirements not produced yet (Gate A)' },
169
- 'architecture.md': { agent: '@architect', reason: 'architecture not produced yet (Gate B)' },
170
- [designDocName]: { agent: '@discovery-design-doc', reason: 'design governance contract not produced yet' },
171
- [readinessName]: { agent: '@discovery-design-doc', reason: 'readiness contract not produced yet' },
172
- [`implementation-plan-${slug}.md`]: { agent: '@pm', reason: 'implementation plan not produced yet (Gate C)' },
179
+ [`prd-${slug}.md`]: { agent: '@product', reason: 'PRD not produced yet' },
180
+ [`requirements-${slug}.md`]: { agent: '@analyst', reason: 'requirements not produced yet (Gate A)' },
181
+ 'architecture.md': { agent: '@architect', reason: 'architecture not produced yet (Gate B)' },
182
+ [designDocName]: { agent: '@discovery-design-doc', reason: 'design governance contract not produced yet' },
183
+ [readinessName]: { agent: '@discovery-design-doc', reason: 'readiness contract not produced yet' },
184
+ [`implementation-plan-${slug}.md`]: { agent: '@pm', reason: 'implementation plan not produced yet (Gate C)' },
173
185
  [`spec-${slug}.md`]: { agent: '@analyst', reason: 'spec not produced yet — @analyst seeds the feature memory' },
174
186
  [`conformance-${slug}.yaml`]: { agent: '@analyst', reason: 'conformance contract missing — @analyst creates it for MEDIUM features' }
175
187
  };
@@ -116,6 +116,60 @@ const COMPLEXITY_SOME_PATTERNS = [
116
116
  /\b(notification|trigger|event)\b/gi
117
117
  ];
118
118
 
119
+ // Sensitive-surface floor (Gap 3B): a feature touching any of these surfaces is
120
+ // never MICRO. Mirrors the secure-tdd sensitive list in @dev. The floor can only
121
+ // RAISE the tier (MICRO -> SMALL); it never lowers it. Keep patterns tight — a
122
+ // false positive needlessly costs the SMALL chain. Tune as the project learns.
123
+ const SENSITIVE_SURFACE_PATTERNS = [
124
+ { surface: 'money', re: /\b(money|stripe|paypal|braintree|square|payments?|payouts?|refunds?|subscriptions?|billing|invoices?|credit card)\b/i },
125
+ { surface: 'auth', re: /\b(oauth|jwt|saml|sso|auth0|firebase auth|log[- ]?in|sign[- ]?in|sign[- ]?up|passwords?|authenticat\w*|2fa|mfa)\b/i },
126
+ { surface: 'authz', re: /\b(authoriz\w*|access control|role[- ]based|rbac|ownership|owner[- ]only|only the owner)\b/i },
127
+ { surface: 'uploads', re: /\b(file uploads?|uploads?|attachments?)\b/i },
128
+ { surface: 'external_url', re: /\b(webhooks?|callback urls?|ssrf|user[- ]?supplied urls?)\b/i },
129
+ { surface: 'secrets', re: /\b(secrets?|api keys?|credentials?|private key|access tokens?)\b/i },
130
+ { surface: 'sensitive_storage', re: /\b(pii|personal data|ssn|sensitive (data|storage|information))\b/i }
131
+ ];
132
+
133
+ function detectSensitiveSurfaces(content) {
134
+ const found = [];
135
+ for (const { surface, re } of SENSITIVE_SURFACE_PATTERNS) {
136
+ if (re.test(content)) found.push(surface);
137
+ }
138
+ return found;
139
+ }
140
+
141
+ // Explicit `sensitive_surfaces:` frontmatter override — additive, can only force
142
+ // the floor when content detection misses. Supports inline (`[a, b]` / `a, b`)
143
+ // and YAML block list forms.
144
+ function parseSensitiveSurfacesOverride(content) {
145
+ const fm = String(content || '').match(/^---\r?\n([\s\S]*?)\r?\n---/);
146
+ if (!fm) return [];
147
+ const body = fm[1];
148
+ const items = [];
149
+ const inline = body.match(/^sensitive_surfaces:[ \t]*(.+)$/m);
150
+ if (inline) {
151
+ inline[1].trim().replace(/^\[|\]$/g, '').split(',').forEach((s) => {
152
+ const v = s.trim().replace(/^["']|["']$/g, '');
153
+ if (v) items.push(v);
154
+ });
155
+ }
156
+ const block = body.match(/^sensitive_surfaces:[ \t]*\r?\n((?:[ \t]*-[ \t]*.+\r?\n?)+)/m);
157
+ if (block) {
158
+ block[1].split(/\r?\n/).forEach((line) => {
159
+ const m = line.match(/^[ \t]*-[ \t]*(.+)$/);
160
+ if (m) {
161
+ const v = m[1].trim().replace(/^["']|["']$/g, '');
162
+ if (v) items.push(v);
163
+ }
164
+ });
165
+ }
166
+ return items;
167
+ }
168
+
169
+ function applySensitiveFloor(classification) {
170
+ return classification === 'MICRO' ? 'SMALL' : classification;
171
+ }
172
+
119
173
  function analyzeContent(content) {
120
174
  // Count unique user types
121
175
  const userTypeSet = new Set();
@@ -185,6 +239,7 @@ async function runClassify({ args, options = {}, logger }) {
185
239
 
186
240
  let userTypeCount, integrationCount, complexityLevel;
187
241
  let sourceFile = null;
242
+ let content = null;
188
243
 
189
244
  if (interactive) {
190
245
  ({ userTypeCount, integrationCount, complexityLevel } = await runInteractive(logger));
@@ -199,7 +254,6 @@ async function runClassify({ args, options = {}, logger }) {
199
254
  ]
200
255
  : [path.join(dir, 'requirements.md'), path.join(dir, 'prd.md')];
201
256
 
202
- let content = null;
203
257
  for (const candidate of candidates) {
204
258
  content = await readFileSafe(candidate);
205
259
  if (content) { sourceFile = path.relative(targetDir, candidate); break; }
@@ -218,7 +272,19 @@ async function runClassify({ args, options = {}, logger }) {
218
272
  const intScore = scoreIntegrations(integrationCount);
219
273
  const cxScore = scoreComplexity(complexityLevel);
220
274
  const totalScore = utScore + intScore + cxScore;
221
- const classification = scoreToClassification(totalScore);
275
+ let classification = scoreToClassification(totalScore);
276
+
277
+ // Gap 3B — sensitive-surface floor (deterministic; raises MICRO -> SMALL only).
278
+ const detectedSurfaces = content ? detectSensitiveSurfaces(content) : [];
279
+ const declaredSurfaces = content ? parseSensitiveSurfacesOverride(content) : [];
280
+ const sensitiveSurfaces = [...new Set([...detectedSurfaces, ...declaredSurfaces])];
281
+ let floored = false;
282
+ if (sensitiveSurfaces.length > 0) {
283
+ const scored = classification;
284
+ classification = applySensitiveFloor(classification);
285
+ floored = classification !== scored;
286
+ }
287
+
222
288
  const phaseDepth = classificationToPhaseDepth(classification);
223
289
 
224
290
  const result = {
@@ -228,6 +294,8 @@ async function runClassify({ args, options = {}, logger }) {
228
294
  inputs: { user_types: userTypeCount, external_integrations: integrationCount, rule_complexity: complexityLevel },
229
295
  scores: { user_types: utScore, integrations: intScore, complexity: cxScore, total: totalScore },
230
296
  classification,
297
+ sensitive_surfaces: sensitiveSurfaces,
298
+ floored,
231
299
  phase_depth: phaseDepth
232
300
  };
233
301
 
@@ -243,6 +311,9 @@ async function runClassify({ args, options = {}, logger }) {
243
311
  logger.log(`Business rule complexity: ${complexityLevel} → +${cxScore}`);
244
312
  logger.log(BAR);
245
313
  logger.log(`Score: ${totalScore} → ${classification}`);
314
+ if (sensitiveSurfaces.length > 0) {
315
+ logger.log(`Sensitive surfaces: ${sensitiveSurfaces.join(', ')}${floored ? ' → floored to SMALL' : ''}`);
316
+ }
246
317
  logger.log('');
247
318
  logger.log('Phase depth:');
248
319
  for (const [phase, desc] of Object.entries(phaseDepth)) {
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const path = require('node:path');
4
+ const { buildContextBrief } = require('../context-brief');
5
+
6
+ async function runContextBrief({ args, options = {}, logger }) {
7
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
8
+ const result = await buildContextBrief(targetDir, {
9
+ agent: options.agent || options.a || 'dev',
10
+ mode: options.mode || 'planning',
11
+ task: options.task || options.goal || '',
12
+ paths: options.paths || options.path || '',
13
+ feature: options.feature || options.slug || '',
14
+ semantic: options.semantic,
15
+ noSemantic: options.noSemantic || options['no-semantic'],
16
+ recall: !(options['no-recall'] || options.recall === false)
17
+ });
18
+
19
+ if (options.json) return result;
20
+
21
+ logger.log(`Context brief for @${result.agent} (${result.mode})`);
22
+ if (result.task) logger.log(`Task: ${result.task}`);
23
+ logger.log(`Intent: ${result.intent.operation}${result.intent.stack ? ` / ${result.intent.stack}` : ''}`);
24
+ if (result.intent.concerns.length > 0) logger.log(`Concerns: ${result.intent.concerns.join(', ')}`);
25
+ logger.log(`Confidence: ${result.confidence}`);
26
+
27
+ if (result.must_load.length > 0) {
28
+ logger.log('Must load:');
29
+ for (const item of result.must_load) logger.log(`- ${item.path} [${item.surface}] ${item.reason}`);
30
+ }
31
+ if (result.should_load.length > 0) {
32
+ logger.log('Should load when needed:');
33
+ for (const item of result.should_load) logger.log(`- ${item.path} [${item.surface}] ${item.reason}`);
34
+ }
35
+ if (result.constraints.length > 0) {
36
+ logger.log('Constraints:');
37
+ for (const item of result.constraints.slice(0, 8)) logger.log(`- ${item}`);
38
+ }
39
+ if (result.forbidden_patterns.length > 0) {
40
+ logger.log('Forbidden patterns:');
41
+ for (const item of result.forbidden_patterns.slice(0, 8)) logger.log(`- ${item}`);
42
+ }
43
+ if (result.verification_hints.length > 0) {
44
+ logger.log('Verification hints:');
45
+ for (const item of result.verification_hints.slice(0, 8)) logger.log(`- ${item}`);
46
+ }
47
+ if (result.gaps.length > 0) {
48
+ logger.log('Gaps:');
49
+ for (const gap of result.gaps) logger.log(`- ${gap.code}: ${gap.message}`);
50
+ }
51
+ if (result.related && result.related.length > 0) {
52
+ logger.log('Related (recall — history/archive select cannot see):');
53
+ for (const item of result.related) logger.log(`- ${item.path} [${item.source_type}] ${item.reason || ''}`);
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ module.exports = { runContextBrief };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { buildGuardResponse } = require('../context-guard');
6
+
7
+ // `aioson context:guard [path] --tool=claude [--json]`
8
+ //
9
+ // Reference adapter for the operational retrieval loop. A harness hook pipes the
10
+ // pending tool event on stdin; the guard answers with a harness-shaped injection
11
+ // payload (or an empty object when no project rule is salient). Always exits 0 —
12
+ // it is advisory and must never block the host harness.
13
+ async function runContextGuard({ args, options = {}, logger }) {
14
+ const targetDir = path.resolve(process.cwd(), args[0] || '.');
15
+ const event = await resolveEvent(args, options);
16
+
17
+ let response;
18
+ try {
19
+ response = await buildGuardResponse(event || {}, targetDir, {
20
+ tool: options.tool || 'claude',
21
+ agent: options.agent || options.a || 'dev'
22
+ });
23
+ } catch {
24
+ // The guard is advisory and runs on the PreToolUse hot path. Any internal
25
+ // failure must surface as an empty injection ({}), never a non-hook envelope
26
+ // ({"ok":false,...}) on the hook's stdout channel.
27
+ response = {};
28
+ }
29
+
30
+ const guard = response && response._guard;
31
+
32
+ if (options.json) {
33
+ // Keep the wire payload pristine — strip the internal observability field.
34
+ const { _guard, ...wire } = response;
35
+ return wire;
36
+ }
37
+
38
+ if (guard && guard.injected) {
39
+ logger.log(`context:guard injected ${guard.rules.length} rule(s): ${guard.rules.join(', ')} (confidence ${guard.confidence})`);
40
+ } else {
41
+ logger.log('context:guard: no salient project rule for this change');
42
+ }
43
+
44
+ return response;
45
+ }
46
+
47
+ async function resolveEvent(args, options) {
48
+ if (typeof options.event === 'string') return safeParse(options.event);
49
+ if (typeof options['event-file'] === 'string') {
50
+ try {
51
+ const raw = fs.readFileSync(path.resolve(process.cwd(), options['event-file']), 'utf8');
52
+ return safeParse(raw);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+ return readStdinEvent();
58
+ }
59
+
60
+ function safeParse(text) {
61
+ try {
62
+ return JSON.parse(text);
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ function readStdinEvent() {
69
+ return new Promise((resolve) => {
70
+ if (process.stdin.isTTY) {
71
+ resolve(null);
72
+ return;
73
+ }
74
+ let data = '';
75
+ let settled = false;
76
+ const settle = (value) => {
77
+ if (settled) return;
78
+ settled = true;
79
+ resolve(value);
80
+ };
81
+ process.stdin.setEncoding('utf8');
82
+ process.stdin.on('data', (chunk) => { data += chunk; });
83
+ process.stdin.on('end', () => settle(safeParse(data)));
84
+ process.stdin.on('error', () => settle(null));
85
+ });
86
+ }
87
+
88
+ module.exports = { runContextGuard };
@@ -87,7 +87,7 @@ async function runContextMonitor({ args, options, logger }) {
87
87
  if (!options.json) {
88
88
  logger.log(` ${icon} Context: ${tokens.toLocaleString()} tokens (${pct}%) — ${zone.toUpperCase()}`);
89
89
  if (zone === 'warning') {
90
- logger.log(` Suggestion: /clear before next agent activation`);
90
+ logger.log(` Suggestion: /compact before next agent activation; use /clear only for a hard reset`);
91
91
  } else if (zone === 'critical' || zone === 'overflow') {
92
92
  logger.log(` Run: aioson context:health . for reduction options`);
93
93
  }
@@ -1,50 +1,61 @@
1
- 'use strict';
2
-
3
- const path = require('node:path');
4
- const { withIndex } = require('../context-search');
5
-
6
- async function runContextSearch({ args, options, logger }) {
7
- const query = args[0] || options.query || '';
8
- const cwd = path.resolve(process.cwd(), options.cwd || '.');
9
- const limit = Number(options.limit) || 10;
10
-
11
- if (!query) {
12
- logger.log('Usage: aioson context:search <query> [--limit=10] [--cwd=.]');
13
- return { ok: false, error: 'missing_query' };
14
- }
15
-
16
- const results = await withIndex(async (idx) => {
17
- return idx.search(query, { limit, projectDir: cwd });
18
- });
19
-
20
- if (options.json) {
21
- return { ok: true, results };
22
- }
23
-
24
- if (results.length === 0) {
25
- logger.log(`No results for: ${query}`);
26
- return { ok: true, results: [] };
27
- }
28
-
29
- logger.log(`\n Search results for: "${query}"\n`);
30
- for (let i = 0; i < results.length; i++) {
31
- const r = results[i];
32
- logger.log(` ${i + 1}. ${r.title}`);
33
- logger.log(` ${r.relPath}`);
34
- if (r.snippet) {
35
- logger.log(` ${r.snippet.replace(/\n/g, ' ')}`);
36
- }
37
- logger.log('');
38
- }
39
-
40
- return { ok: true, results };
41
- }
42
-
43
- async function runContextSearchIndex({ args, options, logger }) {
44
- const cwd = path.resolve(process.cwd(), args[0] || '.');
45
- const force = Boolean(options.force);
46
-
47
- logger.log(`Indexing: ${cwd} ...`);
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { withIndex } = require('../context-search');
6
+
7
+ async function runContextSearch({ args, options, logger }) {
8
+ const { query, cwd } = resolveSearchTarget(args, options);
9
+ const limit = Number(options.limit) || 10;
10
+
11
+ if (!query) {
12
+ logger.log('Usage: aioson context:search [path] --query="<text>" [--agent=dev] [--mode=executing] [--task="<text>"] [--paths=src/**] [--intent=memory|feature|rules] [--limit=10]');
13
+ return { ok: false, error: 'missing_query' };
14
+ }
15
+
16
+ const result = await withIndex(async (idx) => {
17
+ let index = null;
18
+ if (!options['no-index']) {
19
+ index = await idx.indexDirectory(cwd, {
20
+ force: Boolean(options.force || options.refresh)
21
+ });
22
+ }
23
+ const search = idx.searchPackage(query, {
24
+ limit,
25
+ projectDir: cwd,
26
+ agent: options.agent,
27
+ mode: options.mode,
28
+ task: options.task || options.goal,
29
+ paths: options.paths || options.path,
30
+ intent: options.intent || options.intents,
31
+ source: options.source || options.sourceType || options['source-type']
32
+ });
33
+ return { ...search, index };
34
+ });
35
+
36
+ if (options.json) {
37
+ return { ok: true, ...result };
38
+ }
39
+
40
+ const results = result.results || [];
41
+ if (results.length === 0) {
42
+ logger.log(`No results for: ${query}`);
43
+ return { ok: true, ...result };
44
+ }
45
+
46
+ logger.log(`\n Context search for: "${query}"\n`);
47
+ printBucket(logger, 'Must read', result.package.must_read);
48
+ printBucket(logger, 'Should read', result.package.should_read);
49
+ printBucket(logger, 'Maybe', result.package.maybe);
50
+
51
+ return { ok: true, ...result };
52
+ }
53
+
54
+ async function runContextSearchIndex({ args, options, logger }) {
55
+ const cwd = path.resolve(process.cwd(), args[0] || options.cwd || '.');
56
+ const force = Boolean(options.force);
57
+
58
+ logger.log(`Indexing: ${cwd} ...`);
48
59
 
49
60
  const result = await withIndex(async (idx) => {
50
61
  const r = await idx.indexDirectory(cwd, { force });
@@ -59,8 +70,46 @@ async function runContextSearchIndex({ args, options, logger }) {
59
70
  logger.log(` Indexed: ${result.indexed} files`);
60
71
  logger.log(` Skipped: ${result.skipped} files (already indexed)`);
61
72
  logger.log(` Total in index: ${result.stats.totalDocs} docs`);
62
-
63
- return { ok: true, ...result };
64
- }
65
-
66
- module.exports = { runContextSearch, runContextSearchIndex };
73
+
74
+ return { ok: true, ...result };
75
+ }
76
+
77
+ function resolveSearchTarget(args, options = {}) {
78
+ let cwd = path.resolve(process.cwd(), options.cwd || '.');
79
+ let query = String(options.query || options.q || '').trim();
80
+
81
+ if (args.length > 0 && query) {
82
+ cwd = path.resolve(process.cwd(), args[0]);
83
+ } else if (args.length > 1 && pathExists(path.resolve(process.cwd(), args[0]))) {
84
+ cwd = path.resolve(process.cwd(), args[0]);
85
+ query = args.slice(1).join(' ').trim();
86
+ } else if (!query) {
87
+ query = args.join(' ').trim();
88
+ }
89
+
90
+ return { cwd, query };
91
+ }
92
+
93
+ function pathExists(targetPath) {
94
+ try {
95
+ fs.accessSync(targetPath);
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ function printBucket(logger, title, items) {
103
+ if (!items || items.length === 0) return;
104
+ logger.log(` ${title}:`);
105
+ for (let i = 0; i < items.length; i++) {
106
+ const item = items[i];
107
+ logger.log(` ${i + 1}. ${item.title} (${item.source_type}, ${item.confidence})`);
108
+ logger.log(` ${item.relPath}`);
109
+ if (item.reason) logger.log(` reason: ${item.reason}`);
110
+ if (item.snippet) logger.log(` ${item.snippet.replace(/\n/g, ' ')}`);
111
+ logger.log('');
112
+ }
113
+ }
114
+
115
+ module.exports = { runContextSearch, runContextSearchIndex, resolveSearchTarget };
@@ -10,7 +10,9 @@ async function runContextSelect({ args, options = {}, logger }) {
10
10
  mode: options.mode || 'planning',
11
11
  task: options.task || options.goal || '',
12
12
  paths: options.paths || options.path || '',
13
- feature: options.feature || options.slug || ''
13
+ feature: options.feature || options.slug || '',
14
+ semantic: options.semantic,
15
+ noSemantic: options.noSemantic || options['no-semantic']
14
16
  });
15
17
 
16
18
  if (options.json) return result;
@@ -19,7 +21,7 @@ async function runContextSelect({ args, options = {}, logger }) {
19
21
  if (result.task) logger.log(`Task: ${result.task}`);
20
22
  if (result.paths.length > 0) logger.log(`Paths: ${result.paths.join(', ')}`);
21
23
  logger.log('Boundary: load only the selected files until the task, mode, feature, or touched paths change.');
22
- if (result.selected.length === 0) {
24
+ if (result.selected.length === 0 && (!result.memory || result.memory.length === 0)) {
23
25
  logger.log('No context files selected.');
24
26
  return result;
25
27
  }
@@ -28,6 +30,13 @@ async function runContextSelect({ args, options = {}, logger }) {
28
30
  logger.log(`- ${item.path} [${item.surface}; ${item.load_tier}] ${item.reason}`);
29
31
  }
30
32
 
33
+ if (result.memory && result.memory.length > 0) {
34
+ logger.log('Memory matches:');
35
+ for (const item of result.memory) {
36
+ logger.log(`- [${item.target_type}] ${item.target_id} ${item.reason}`);
37
+ }
38
+ }
39
+
31
40
  return result;
32
41
  }
33
42