@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.
- package/CHANGELOG.md +28 -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 +19 -6
- 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/integrations/dashboard-app-form-publish-mapping.md +183 -0
- package/template/.aioson/docs/play/README.md +72 -0
- package/template/.aioson/docs/play/agent-usage-guide.md +106 -0
- package/template/.aioson/docs/play/app-compatibility-guide.md +112 -0
- package/template/.aioson/docs/play/auth-services-and-testing.md +220 -0
- package/template/.aioson/docs/play/llm-data-and-bindings.md +238 -0
- package/template/.aioson/docs/play/manifest-and-runtime.md +244 -0
- package/template/.aioson/docs/play/source-map.md +104 -0
- 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
|
@@ -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(
|
|
74
|
-
const acs = artifacts.requirements.content.match(
|
|
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
|
};
|
package/src/commands/classify.js
CHANGED
|
@@ -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
|
-
|
|
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: /
|
|
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
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const cwd =
|
|
9
|
-
const limit = Number(options.limit) || 10;
|
|
10
|
-
|
|
11
|
-
if (!query) {
|
|
12
|
-
logger.log('Usage: aioson context:search <
|
|
13
|
-
return { ok: false, error: 'missing_query' };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const
|
|
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
|
-
logger
|
|
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
|
-
|
|
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
|
|