@jaimevalasek/aioson 1.9.0 → 1.9.2
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/package.json +1 -1
- package/src/cli.js +8 -1
- package/src/commands/setup-context.js +6 -6
- package/src/commands/state-save.js +111 -13
- package/src/commands/update.js +6 -0
- package/src/constants.js +5 -1
- package/src/doctor.js +52 -0
- package/src/gateway-pointer-merge.js +103 -0
- package/src/i18n/messages/en.js +9 -2
- package/src/i18n/messages/es.js +9 -2
- package/src/i18n/messages/fr.js +9 -2
- package/src/i18n/messages/pt-BR.js +9 -2
- package/src/installer.js +39 -1
- package/src/jargon-leak-doctor.js +257 -0
- package/src/migrations/profile-rename.js +66 -0
- package/src/onboarding.js +7 -5
- package/src/parser.js +11 -2
- package/src/updater.js +21 -4
- package/template/.aioson/agents/analyst.md +12 -0
- package/template/.aioson/agents/dev.md +3 -0
- package/template/.aioson/agents/deyvin.md +9 -7
- package/template/.aioson/agents/neo.md +20 -5
- package/template/.aioson/agents/product.md +22 -6
- package/template/.aioson/agents/setup.md +6 -4
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +6 -0
- package/template/.aioson/docs/handoff-persistence.md +94 -0
- package/template/.aioson/skills/process/decision-presentation/SKILL.md +119 -0
- package/template/.aioson/skills/process/decision-presentation/references/jargon-map.en.yaml +108 -0
- package/template/.aioson/skills/process/decision-presentation/references/jargon-map.pt-BR.yaml +108 -0
package/src/installer.js
CHANGED
|
@@ -9,6 +9,7 @@ const { ensureProjectRuntime } = require('./execution-gateway');
|
|
|
9
9
|
const { shouldIncludeForProfile } = require('./install-profile');
|
|
10
10
|
const { generatePermissions } = require('./permissions-generator');
|
|
11
11
|
const { isConfigMergePath, mergeConfigFile } = require('./installer-config-merge');
|
|
12
|
+
const { isGatewayPointerPath, mergeGatewayPointer } = require('./gateway-pointer-merge');
|
|
12
13
|
|
|
13
14
|
const ROOT_DIR = path.join(__dirname, '..');
|
|
14
15
|
const TEMPLATE_DIR = path.join(ROOT_DIR, 'template');
|
|
@@ -67,7 +68,10 @@ const GITIGNORE_POLICY_LINES = [
|
|
|
67
68
|
'.aioson/profiler-reports/*',
|
|
68
69
|
'!.aioson/profiler-reports/.gitkeep',
|
|
69
70
|
'.claude/settings.local.json',
|
|
70
|
-
'*:Zone.Identifier'
|
|
71
|
+
'*:Zone.Identifier',
|
|
72
|
+
'# AIOSON — shared agent scratch caches (local-only)',
|
|
73
|
+
'researchs/',
|
|
74
|
+
'squad-searches/'
|
|
71
75
|
];
|
|
72
76
|
|
|
73
77
|
async function detectExistingInstall(targetDir) {
|
|
@@ -341,6 +345,40 @@ async function installTemplate(targetDir, options = {}) {
|
|
|
341
345
|
const dest = path.join(targetDir, rel);
|
|
342
346
|
const destExists = await exists(dest);
|
|
343
347
|
|
|
348
|
+
// Gateway pointer files (CLAUDE.md, AGENTS.md, OPENCODE.md, .gemini/GEMINI.md)
|
|
349
|
+
// get block-merged so that an existing project-authored file keeps its content
|
|
350
|
+
// and only the AIOSON-managed block is created or refreshed in place.
|
|
351
|
+
if (isGatewayPointerPath(rel)) {
|
|
352
|
+
if (!destExists && selectiveUpdate) {
|
|
353
|
+
skipped.push({ path: rel, reason: 'not-installed' });
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
const mergeResult = await mergeGatewayPointer({
|
|
357
|
+
templatePath: absPath,
|
|
358
|
+
targetPath: dest,
|
|
359
|
+
backupRoot: backupOnOverwrite ? backupRoot : null,
|
|
360
|
+
targetDir,
|
|
361
|
+
dryRun
|
|
362
|
+
});
|
|
363
|
+
if (mergeResult.action === 'unchanged') {
|
|
364
|
+
skipped.push({ path: rel, reason: 'unchanged' });
|
|
365
|
+
} else {
|
|
366
|
+
copied.push(rel);
|
|
367
|
+
mergedConfigs.push({ path: rel, action: mergeResult.action });
|
|
368
|
+
if (mergeResult.backupPath) {
|
|
369
|
+
backedUp.push(toRelativeSafe(targetDir, mergeResult.backupPath));
|
|
370
|
+
}
|
|
371
|
+
if (mergeResult.backupError) {
|
|
372
|
+
failedBackups.push({ path: rel, error: mergeResult.backupError });
|
|
373
|
+
console.warn(`[aioson update] backup of ${rel} failed; update proceeding without rollback for this file: ${mergeResult.backupError}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (onProgress) {
|
|
377
|
+
onProgress({ copied: copied.length, total: templateFiles.length, file: rel });
|
|
378
|
+
}
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
|
|
344
382
|
// Project-local files are never overwritten from template.
|
|
345
383
|
// On fresh install they are created once; on any subsequent operation they are preserved
|
|
346
384
|
// even if the file was manually deleted.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* lay-user-agent-mode — Phase 3 doctor check `jargon_leak_detection`.
|
|
5
|
+
*
|
|
6
|
+
* Pure-where-possible helpers consumed by `src/doctor.js#runDoctor`. The check
|
|
7
|
+
* scans `agent_events` rows from the 5 MVP agents (neo, setup, product, dev,
|
|
8
|
+
* deyvin) and flags occurrences of framework jargon (MICRO/SMALL/MEDIUM, Gate
|
|
9
|
+
* A-D, tier1/2/3, etc.) emitted while the project's profile is `creator`.
|
|
10
|
+
*
|
|
11
|
+
* Profile semantics (lay-user-agent-mode E4):
|
|
12
|
+
* - profile=creator (or absent/auto/empty) → run the check
|
|
13
|
+
* - profile=developer → skip (jargon permitted; ok=true with skipped marker)
|
|
14
|
+
* - profile=team → skip (jargon permitted in operator-facing flow)
|
|
15
|
+
*
|
|
16
|
+
* Word-boundary matching: case-sensitive `\b{term}\b` semantics implemented
|
|
17
|
+
* via non-word lookarounds so multi-word keys ("Gate D") match correctly and
|
|
18
|
+
* substring hits ("MICRO" inside "MICROserviços") DO NOT trigger.
|
|
19
|
+
*
|
|
20
|
+
* EC-LUM-05: missing/empty runtime DB → ok=true count=0 (greenfield).
|
|
21
|
+
* EC-LUM-08: missing/deleted jargon-map → ok=true count=0 with marker.
|
|
22
|
+
* EC-LUM-10: 50+ leaks → samples truncated to MAX_SAMPLES.
|
|
23
|
+
* EC-LUM-11: future enhancement — `payload_json.jargon_intentional=true` opt-out.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('node:fs/promises');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
|
|
29
|
+
const MVP_AGENTS = ['neo', 'setup', 'product', 'dev', 'deyvin'];
|
|
30
|
+
const MAX_SAMPLES = 10;
|
|
31
|
+
const MAX_EVENTS_SCANNED = 500;
|
|
32
|
+
const JARGON_MAP_REL = '.aioson/skills/process/decision-presentation/references/jargon-map.en.yaml';
|
|
33
|
+
|
|
34
|
+
// Extract canonical term keys from the YAML's `terms:` block without pulling
|
|
35
|
+
// in a YAML parser. Schema is fixed by E2 (version: 1, terms: { KEY: {...} }).
|
|
36
|
+
// Keys are either bare identifiers (MICRO) or quoted ("Gate D").
|
|
37
|
+
function extractTermKeys(yamlContent) {
|
|
38
|
+
if (typeof yamlContent !== 'string') return [];
|
|
39
|
+
const inTermsMatch = yamlContent.match(/^terms:\s*\n([\s\S]*)$/m);
|
|
40
|
+
if (!inTermsMatch) return [];
|
|
41
|
+
const keys = [];
|
|
42
|
+
const lines = inTermsMatch[1].split('\n');
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
// 2-space indent, then key (bare or quoted), then trailing colon.
|
|
45
|
+
const m = line.match(/^ (?:"([^"]+)"|'([^']+)'|([A-Za-z_][^:\s]*)):\s*$/);
|
|
46
|
+
if (m) keys.push(m[1] || m[2] || m[3]);
|
|
47
|
+
}
|
|
48
|
+
return keys;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function loadJargonTerms(targetDir) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = await fs.readFile(path.join(targetDir, JARGON_MAP_REL), 'utf8');
|
|
54
|
+
return extractTermKeys(raw);
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readProjectProfile(targetDir) {
|
|
61
|
+
try {
|
|
62
|
+
const raw = await fs.readFile(
|
|
63
|
+
path.join(targetDir, '.aioson/context/project.context.md'),
|
|
64
|
+
'utf8'
|
|
65
|
+
);
|
|
66
|
+
const m = raw.match(/^profile\s*:\s*["']?([\w-]+)["']?/m);
|
|
67
|
+
return m ? m[1].toLowerCase() : null;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Default-profile rule from the skill: absent/empty/auto → creator (safer).
|
|
74
|
+
// Legacy `beginner` already migrated by src/migrations/profile-rename, but
|
|
75
|
+
// accept it defensively in case migration didn't run.
|
|
76
|
+
function normalizeEffectiveProfile(raw) {
|
|
77
|
+
if (!raw) return 'creator';
|
|
78
|
+
const v = String(raw).toLowerCase();
|
|
79
|
+
if (v === 'auto' || v === '') return 'creator';
|
|
80
|
+
if (v === 'beginner') return 'creator';
|
|
81
|
+
return v;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function escapeRegex(s) {
|
|
85
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Build one alternation regex with all terms. Non-word lookarounds enforce
|
|
89
|
+
// word-boundary semantics WITHOUT \b (which fails for terms containing
|
|
90
|
+
// spaces, like "Gate D"). Treat hyphen as a word char so terms like
|
|
91
|
+
// "harness-contract" do not split on the dash.
|
|
92
|
+
function buildJargonRegex(terms) {
|
|
93
|
+
if (!terms || terms.length === 0) return null;
|
|
94
|
+
// Sort longest-first so "Gate D" wins over a hypothetical "Gate" key.
|
|
95
|
+
const sorted = [...terms].sort((a, b) => b.length - a.length);
|
|
96
|
+
const alt = sorted.map(escapeRegex).join('|');
|
|
97
|
+
return new RegExp(`(?<![\\w-])(${alt})(?![\\w-])`, 'g');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Find all term occurrences in a string. Returns the matched substrings.
|
|
101
|
+
function findLeaks(text, regex) {
|
|
102
|
+
if (!text || !regex) return [];
|
|
103
|
+
const out = [];
|
|
104
|
+
regex.lastIndex = 0;
|
|
105
|
+
let m;
|
|
106
|
+
while ((m = regex.exec(text)) !== null) {
|
|
107
|
+
out.push(m[1]);
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Pure: detect leaks across a list of event records. Truncates samples to
|
|
113
|
+
// MAX_SAMPLES; `count` reflects the total occurrence count across all events.
|
|
114
|
+
function detectJargonInEvents(events, terms) {
|
|
115
|
+
const regex = buildJargonRegex(terms);
|
|
116
|
+
const samples = [];
|
|
117
|
+
let count = 0;
|
|
118
|
+
if (!regex || !Array.isArray(events)) return { count, samples };
|
|
119
|
+
|
|
120
|
+
for (const ev of events) {
|
|
121
|
+
if (!ev) continue;
|
|
122
|
+
const message = typeof ev.message === 'string' ? ev.message : '';
|
|
123
|
+
const payload = typeof ev.payload_json === 'string' ? ev.payload_json : '';
|
|
124
|
+
|
|
125
|
+
// EC-LUM-11 opt-out: explicit `jargon_intentional: true` skips this event.
|
|
126
|
+
if (payload) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(payload);
|
|
129
|
+
if (parsed && parsed.jargon_intentional === true) continue;
|
|
130
|
+
} catch {
|
|
131
|
+
// not JSON — ignore opt-out, treat payload as opaque text below
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const hits = [...findLeaks(message, regex), ...findLeaks(payload, regex)];
|
|
136
|
+
if (hits.length === 0) continue;
|
|
137
|
+
|
|
138
|
+
count += hits.length;
|
|
139
|
+
if (samples.length < MAX_SAMPLES) {
|
|
140
|
+
samples.push({
|
|
141
|
+
agent: ev.agent_name || null,
|
|
142
|
+
created_at: ev.created_at || null,
|
|
143
|
+
terms: Array.from(new Set(hits)).slice(0, 3),
|
|
144
|
+
excerpt: message.slice(0, 120)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { count, samples };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Top-level assessment consumed by doctor.js.
|
|
153
|
+
*
|
|
154
|
+
* @param {object} opts
|
|
155
|
+
* @param {object|null} opts.db better-sqlite3 handle (or null)
|
|
156
|
+
* @param {string} opts.targetDir project root
|
|
157
|
+
* @param {string[]} [opts.scope] agent_name whitelist (defaults to MVP_AGENTS)
|
|
158
|
+
* @param {number} [opts.eventLimit] cap on agent_events scanned
|
|
159
|
+
*
|
|
160
|
+
* @returns {Promise<{
|
|
161
|
+
* ok: boolean,
|
|
162
|
+
* count: number,
|
|
163
|
+
* samples: Array<{agent,created_at,terms,excerpt}>,
|
|
164
|
+
* profile: string,
|
|
165
|
+
* skipped?: boolean,
|
|
166
|
+
* reason?: string,
|
|
167
|
+
* eventsScanned?: number,
|
|
168
|
+
* jargonMapMissing?: boolean
|
|
169
|
+
* }>}
|
|
170
|
+
*/
|
|
171
|
+
async function assessJargonLeak(opts) {
|
|
172
|
+
const {
|
|
173
|
+
db,
|
|
174
|
+
targetDir,
|
|
175
|
+
scope = MVP_AGENTS,
|
|
176
|
+
eventLimit = MAX_EVENTS_SCANNED
|
|
177
|
+
} = opts || {};
|
|
178
|
+
|
|
179
|
+
const rawProfile = await readProjectProfile(targetDir);
|
|
180
|
+
const profile = normalizeEffectiveProfile(rawProfile);
|
|
181
|
+
|
|
182
|
+
// AC-LUM-08: developer/team skip — jargon permitted in those modes.
|
|
183
|
+
if (profile === 'developer' || profile === 'team') {
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
skipped: true,
|
|
187
|
+
reason: 'profile-permits-jargon',
|
|
188
|
+
profile,
|
|
189
|
+
count: 0,
|
|
190
|
+
samples: []
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// EC-LUM-05: no runtime DB → fresh project, nothing to scan.
|
|
195
|
+
if (!db) {
|
|
196
|
+
return { ok: true, count: 0, samples: [], profile, eventsScanned: 0 };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const terms = await loadJargonTerms(targetDir);
|
|
200
|
+
if (terms.length === 0) {
|
|
201
|
+
// EC-LUM-08: jargon-map missing — fail open (cannot detect without dict).
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
count: 0,
|
|
205
|
+
samples: [],
|
|
206
|
+
profile,
|
|
207
|
+
jargonMapMissing: true,
|
|
208
|
+
eventsScanned: 0
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let events;
|
|
213
|
+
try {
|
|
214
|
+
const placeholders = scope.map(() => '?').join(',');
|
|
215
|
+
events = db
|
|
216
|
+
.prepare(
|
|
217
|
+
`SELECT ae.message AS message,
|
|
218
|
+
ae.payload_json AS payload_json,
|
|
219
|
+
ar.agent_name AS agent_name,
|
|
220
|
+
ae.created_at AS created_at
|
|
221
|
+
FROM agent_events ae
|
|
222
|
+
JOIN agent_runs ar ON ae.run_key = ar.run_key
|
|
223
|
+
WHERE ar.agent_name IN (${placeholders})
|
|
224
|
+
ORDER BY ae.created_at DESC
|
|
225
|
+
LIMIT ?`
|
|
226
|
+
)
|
|
227
|
+
.all(...scope, eventLimit);
|
|
228
|
+
} catch {
|
|
229
|
+
// Schema drift / missing tables on a stale runtime DB. Treat as greenfield.
|
|
230
|
+
return { ok: true, count: 0, samples: [], profile, eventsScanned: 0 };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const { count, samples } = detectJargonInEvents(events, terms);
|
|
234
|
+
return {
|
|
235
|
+
ok: count === 0,
|
|
236
|
+
count,
|
|
237
|
+
samples,
|
|
238
|
+
profile,
|
|
239
|
+
eventsScanned: events.length
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
MVP_AGENTS,
|
|
245
|
+
MAX_SAMPLES,
|
|
246
|
+
MAX_EVENTS_SCANNED,
|
|
247
|
+
JARGON_MAP_REL,
|
|
248
|
+
extractTermKeys,
|
|
249
|
+
loadJargonTerms,
|
|
250
|
+
readProjectProfile,
|
|
251
|
+
normalizeEffectiveProfile,
|
|
252
|
+
escapeRegex,
|
|
253
|
+
buildJargonRegex,
|
|
254
|
+
findLeaks,
|
|
255
|
+
detectJargonInEvents,
|
|
256
|
+
assessJargonLeak
|
|
257
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Migration: project.context.md `profile: beginner` -> `profile: creator`.
|
|
4
|
+
//
|
|
5
|
+
// Why: lay-user-agent-mode renames the persona value from `beginner` (which
|
|
6
|
+
// reads as "tutorial mode" / patronizing) to `creator` (neutral, aligns with
|
|
7
|
+
// vibe-coding market vocabulary). Existing projects on v1.9.0 or earlier may
|
|
8
|
+
// have `profile: beginner` in their project.context.md frontmatter. On the
|
|
9
|
+
// next `aioson update`, this migration rewrites that one line and emits a
|
|
10
|
+
// notify so the user understands the rename.
|
|
11
|
+
//
|
|
12
|
+
// Idempotent: re-running on a project already migrated does nothing.
|
|
13
|
+
|
|
14
|
+
const fs = require('node:fs/promises');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
|
|
17
|
+
const PROJECT_CONTEXT_REL = '.aioson/context/project.context.md';
|
|
18
|
+
|
|
19
|
+
// Match `profile: beginner` with optional surrounding quotes in YAML frontmatter.
|
|
20
|
+
// Anchored to start-of-line to avoid matching prose mentions inside the body.
|
|
21
|
+
const FRONTMATTER_PROFILE_RE = /^profile:\s*["']?beginner["']?\s*$/m;
|
|
22
|
+
|
|
23
|
+
async function readFileOrNull(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
return await fs.readFile(filePath, 'utf8');
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err && err.code === 'ENOENT') return null;
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rewriteProfileLine(content) {
|
|
33
|
+
return content.replace(FRONTMATTER_PROFILE_RE, 'profile: creator');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Returns { changed: boolean, file: string | null }.
|
|
37
|
+
// changed=true means the file was rewritten this run.
|
|
38
|
+
// changed=false means either the file is missing, has no frontmatter,
|
|
39
|
+
// or already says `creator` (or any non-`beginner` value).
|
|
40
|
+
async function migrateProfileRename(targetDir) {
|
|
41
|
+
const file = path.join(targetDir, PROJECT_CONTEXT_REL);
|
|
42
|
+
const content = await readFileOrNull(file);
|
|
43
|
+
if (!content) return { changed: false, file: null };
|
|
44
|
+
|
|
45
|
+
// Only touch frontmatter — first --- block at top of file.
|
|
46
|
+
const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
47
|
+
if (!fmMatch) return { changed: false, file };
|
|
48
|
+
|
|
49
|
+
if (!FRONTMATTER_PROFILE_RE.test(fmMatch[1])) {
|
|
50
|
+
return { changed: false, file };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const newFrontmatter = rewriteProfileLine(fmMatch[1]);
|
|
54
|
+
const newContent = content.replace(fmMatch[0], `---\n${newFrontmatter}\n---`);
|
|
55
|
+
await fs.writeFile(file, newContent, 'utf8');
|
|
56
|
+
|
|
57
|
+
return { changed: true, file };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
migrateProfileRename,
|
|
62
|
+
// Exported for tests:
|
|
63
|
+
rewriteProfileLine,
|
|
64
|
+
FRONTMATTER_PROFILE_RE,
|
|
65
|
+
PROJECT_CONTEXT_REL
|
|
66
|
+
};
|
package/src/onboarding.js
CHANGED
|
@@ -72,7 +72,7 @@ const SERVICE_ALIASES = {
|
|
|
72
72
|
|
|
73
73
|
const PROFILE_CHOICES = {
|
|
74
74
|
'1': 'developer',
|
|
75
|
-
'2': '
|
|
75
|
+
'2': 'creator',
|
|
76
76
|
'3': 'team'
|
|
77
77
|
};
|
|
78
78
|
|
|
@@ -101,7 +101,9 @@ function normalizeProfile(value, fallback = 'developer') {
|
|
|
101
101
|
return PROFILE_CHOICES[input];
|
|
102
102
|
}
|
|
103
103
|
const lower = input.toLowerCase();
|
|
104
|
-
|
|
104
|
+
// Accept legacy 'beginner' as input but normalize to 'creator' (E4 migration shim).
|
|
105
|
+
if (lower === 'beginner') return 'creator';
|
|
106
|
+
if (['developer', 'creator', 'team'].includes(lower)) return lower;
|
|
105
107
|
return fallback;
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -191,14 +193,14 @@ function buildDeveloperProfile(input) {
|
|
|
191
193
|
};
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
function
|
|
196
|
+
function recommendCreatorProfile(input = {}) {
|
|
195
197
|
const expectedUsers = normalizeText(input.expectedUsers).toLowerCase();
|
|
196
198
|
const mobileNeed = normalizeText(input.mobileRequirement).toLowerCase();
|
|
197
199
|
const hosting = normalizeText(input.hostingPreference).toLowerCase();
|
|
198
200
|
const summary = normalizeText(input.projectSummary);
|
|
199
201
|
|
|
200
202
|
const recommendation = {
|
|
201
|
-
profile: '
|
|
203
|
+
profile: 'creator',
|
|
202
204
|
projectType: 'web_app',
|
|
203
205
|
framework: 'Laravel',
|
|
204
206
|
backend: 'Laravel',
|
|
@@ -300,6 +302,6 @@ module.exports = {
|
|
|
300
302
|
inferProjectTypeFromFramework,
|
|
301
303
|
inferWeb3NetworkFromFramework,
|
|
302
304
|
buildDeveloperProfile,
|
|
303
|
-
|
|
305
|
+
recommendCreatorProfile,
|
|
304
306
|
buildTeamProfile
|
|
305
307
|
};
|
package/src/parser.js
CHANGED
|
@@ -10,11 +10,19 @@ function parseArgv(argv) {
|
|
|
10
10
|
const token = tokens[i];
|
|
11
11
|
|
|
12
12
|
if (token.startsWith('--')) {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Split on the FIRST `=` only — values may contain `=` (e.g. URLs,
|
|
14
|
+
// SQL, or natural-language sentences like "profile=creator").
|
|
15
|
+
// Using `.split('=')` without a limit + array destructuring discards
|
|
16
|
+
// anything after the second `=`, truncating flag values silently.
|
|
17
|
+
const stripped = token.slice(2);
|
|
18
|
+
const eqIdx = stripped.indexOf('=');
|
|
19
|
+
if (eqIdx !== -1) {
|
|
20
|
+
const k = stripped.slice(0, eqIdx);
|
|
21
|
+
const v = stripped.slice(eqIdx + 1);
|
|
15
22
|
options[k] = v;
|
|
16
23
|
continue;
|
|
17
24
|
}
|
|
25
|
+
const k = stripped;
|
|
18
26
|
|
|
19
27
|
// Boolean-only flags that never consume the next token
|
|
20
28
|
const boolOnly = new Set([
|
|
@@ -22,6 +30,7 @@ function parseArgv(argv) {
|
|
|
22
30
|
'help', 'version', 'no-launch', 'attach', 'tmux',
|
|
23
31
|
'allow-warnings', 'install-hook', 'uninstall-hook', 'remove-hook',
|
|
24
32
|
'agent-safe',
|
|
33
|
+
'selective',
|
|
25
34
|
'status', 'suggest', 'apply',
|
|
26
35
|
// `--resume` alone means "resume last"; `--resume=<id>` carries a value
|
|
27
36
|
// and is handled by the `=` branch above. Without this entry, `--resume`
|
package/src/updater.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
const { detectExistingInstall, installTemplate, readInstallProfile } = require('./installer');
|
|
5
|
+
const { migrateProfileRename } = require('./migrations/profile-rename');
|
|
5
6
|
|
|
6
7
|
async function updateInstallation(targetDir, options = {}) {
|
|
7
8
|
const installed = await detectExistingInstall(targetDir);
|
|
@@ -15,8 +16,12 @@ async function updateInstallation(targetDir, options = {}) {
|
|
|
15
16
|
|
|
16
17
|
const savedProfile = await readInstallProfile(targetDir);
|
|
17
18
|
|
|
18
|
-
// Default:
|
|
19
|
-
//
|
|
19
|
+
// Default: sync everything from the template, including files added by the
|
|
20
|
+
// release that aren't yet in the target. Pre-1.9.2 this defaulted to
|
|
21
|
+
// selective and silently dropped new agents/slash commands between versions.
|
|
22
|
+
// `--selective` restores the legacy conservative mode; `--all` is accepted
|
|
23
|
+
// as a backwards-compat no-op since "all" is now the default.
|
|
24
|
+
const selective = Boolean(options.selective) && !options.all;
|
|
20
25
|
const result = await installTemplate(targetDir, {
|
|
21
26
|
overwrite: true,
|
|
22
27
|
dryRun: Boolean(options.dryRun),
|
|
@@ -24,13 +29,25 @@ async function updateInstallation(targetDir, options = {}) {
|
|
|
24
29
|
backupOnOverwrite: true,
|
|
25
30
|
frameworkDetection: options.frameworkDetection || null,
|
|
26
31
|
installProfile: savedProfile,
|
|
27
|
-
selectiveUpdate:
|
|
32
|
+
selectiveUpdate: selective
|
|
28
33
|
});
|
|
29
34
|
|
|
35
|
+
// Post-install migrations. Best-effort: a migration failure must not break
|
|
36
|
+
// the update flow. Skip migrations in dry-run mode.
|
|
37
|
+
let profileMigration = { changed: false, file: null };
|
|
38
|
+
if (!options.dryRun) {
|
|
39
|
+
try {
|
|
40
|
+
profileMigration = await migrateProfileRename(targetDir);
|
|
41
|
+
} catch {
|
|
42
|
+
// swallow — migrations are advisory
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
return {
|
|
31
47
|
ok: true,
|
|
32
48
|
...result,
|
|
33
|
-
savedProfile
|
|
49
|
+
savedProfile,
|
|
50
|
+
migrations: { profileRename: profileMigration }
|
|
34
51
|
};
|
|
35
52
|
}
|
|
36
53
|
|
|
@@ -329,5 +329,17 @@ Generate `.aioson/context/discovery.md` with the following sections:
|
|
|
329
329
|
- In feature mode: never duplicate content already in `discovery.md` — only document what is new or changed.
|
|
330
330
|
- If `readiness.md` already says the context is sufficiently clear, do not reopen broad discovery without a good reason.
|
|
331
331
|
|
|
332
|
+
## Dev handoff producer
|
|
333
|
+
|
|
334
|
+
Before the final `agent:done` call, when the next agent in the workflow is `@dev`, produce `dev-state.md` so the next `/dev` session auto-resumes on cold start instead of pinging the user for context:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
aioson dev:state:write . --feature={slug} --phase=1 \
|
|
338
|
+
--next="<concrete first slice description for @dev>" \
|
|
339
|
+
--context=spec,requirements
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`--context` accepts canonical tokens (`prd`, `requirements`, `spec`, `architecture`, `impl-plan`, `sheldon`, `design-doc`, `dossier`), max 4 entries total; missing files emit a warning and are skipped. Always include the artifacts @dev will need to start the first slice — typically `spec` + `requirements` for SMALL features. Idempotent: re-running with the same args does not duplicate state.
|
|
343
|
+
|
|
332
344
|
## Observability
|
|
333
345
|
At session end, register: `aioson agent:done . --agent=analyst --summary="Discovery <slug>: <N> entities, <N> rules" 2>/dev/null || true`
|
|
@@ -215,6 +215,8 @@ If `.aioson/skills/process/secure-tdd/SKILL.md` exists and the active feature is
|
|
|
215
215
|
|
|
216
216
|
## Deterministic preflight
|
|
217
217
|
|
|
218
|
+
Always load `.aioson/skills/process/decision-presentation/SKILL.md` before the first user-facing question. Mandatory regardless of profile.
|
|
219
|
+
|
|
218
220
|
Before the first code change, decide which dev docs must be loaded:
|
|
219
221
|
|
|
220
222
|
| Condition | Required module |
|
|
@@ -282,6 +284,7 @@ Interface copy, onboarding text, email content, and marketing text are not withi
|
|
|
282
284
|
|
|
283
285
|
## Hard constraints
|
|
284
286
|
- Use `interaction_language` (fallback: `conversation_language`) from project context for all interaction/output.
|
|
287
|
+
- Never present multiple open questions in one turn when `profile=creator` (or absent/auto). Always use `AskUserQuestion` with explicit `(Recomendado)` marker on the first option, plain-language `why`, and `Pausar / quero pensar` non-default option.
|
|
285
288
|
- If discovery/architecture is ambiguous, ask for clarification before implementing guessed behavior.
|
|
286
289
|
- If a UI implementation depends on visual direction and `design_skill` is still blank, do not invent one silently.
|
|
287
290
|
- No unnecessary rewrites outside current responsibility.
|
|
@@ -82,13 +82,14 @@ The detailed pair-programming protocol is split into on-demand framework docs:
|
|
|
82
82
|
|
|
83
83
|
Run this after the immediate scope gate and before touching code:
|
|
84
84
|
|
|
85
|
-
1. Always load `.aioson/
|
|
86
|
-
2.
|
|
87
|
-
3. If
|
|
88
|
-
4. If
|
|
89
|
-
5. If the
|
|
90
|
-
6. If the
|
|
91
|
-
7.
|
|
85
|
+
1. Always load `.aioson/skills/process/decision-presentation/SKILL.md` before the first user-facing question. Mandatory regardless of profile.
|
|
86
|
+
2. Always load `.aioson/docs/deyvin/continuity-recovery.md`
|
|
87
|
+
3. If `aioson` is available, run `aioson preflight . --agent=deyvin --feature={slug}` when a feature slug is known; load any listed `rules` and `design_governance` files before touching code
|
|
88
|
+
4. If continuation depends on `spec*.md`, `dev-state.md`, or a feature already in progress, load `.aioson/skills/process/aioson-spec-driven/SKILL.md` and then only `references/deyvin.md`
|
|
89
|
+
5. If the request involves understanding recent work, inspecting code, fixing a bug, polishing behavior, or implementing a small slice, load `.aioson/docs/deyvin/pair-execution.md`
|
|
90
|
+
6. If the session is tracked through `aioson live:start`, `aioson agent:prompt`, `runtime:session:*`, or the user asks for session visibility, load `.aioson/docs/deyvin/runtime-handoffs.md`
|
|
91
|
+
7. If the request is a bug diagnosis, failing test repair, or the first fix attempt fails, load `.aioson/docs/deyvin/debugging-escalation.md`
|
|
92
|
+
8. Do not touch code until all required modules have been loaded
|
|
92
93
|
|
|
93
94
|
## Working kernel
|
|
94
95
|
|
|
@@ -184,6 +185,7 @@ Dispatch via harness sub-agent with the tool whitelist `[Read, Grep]`. Read the
|
|
|
184
185
|
## Hard constraints
|
|
185
186
|
|
|
186
187
|
- Use `interaction_language` (fallback: `conversation_language`) from project context for all interaction and output.
|
|
188
|
+
- Never present multiple open questions in one turn when `profile=creator` (or absent/auto). Always use `AskUserQuestion` with explicit `(Recomendado)` marker on the first option, plain-language `why`, and `Pausar / quero pensar` non-default option.
|
|
187
189
|
- Always check `.aioson/rules/` and relevant `.aioson/docs/` when they exist.
|
|
188
190
|
- Always apply relevant `.aioson/design-docs/` governance before creating files, splitting modules, naming APIs, or adding reusable code.
|
|
189
191
|
- Do not silently replace `@product`, `@analyst`, or `@architect` when the task clearly needs them.
|
|
@@ -17,7 +17,13 @@ Tone: calm, direct, confident. No filler. You present what you found, ask one fo
|
|
|
17
17
|
|
|
18
18
|
On activation, run the diagnostic sequence below and present results. Do not wait for user input before running diagnostics.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Always load `.aioson/skills/process/decision-presentation/SKILL.md` before the first user-facing question. Mandatory regardless of profile.
|
|
21
|
+
|
|
22
|
+
If `aioson` is available, run these in parallel before the table scan (Living Memory + harness snapshot — do not require the user to know these commands):
|
|
23
|
+
|
|
24
|
+
- `aioson memory:status .` — bootstrap coverage (N/4), brains, runtime sessions
|
|
25
|
+
- `aioson memory:summary . --last=5` — recent activity + retrieval hints
|
|
26
|
+
- `aioson workflow:next . --status` — active stage, pending gate, handoff contract
|
|
21
27
|
|
|
22
28
|
## Project pulse (read at session start)
|
|
23
29
|
|
|
@@ -68,6 +74,11 @@ Check these in order. Stop at the first failure:
|
|
|
68
74
|
| Spec exists | `.aioson/context/spec.md` | Note presence — used for continuity detection |
|
|
69
75
|
| Dev state | `.aioson/context/dev-state.md` | If present: @dev has an active session. Read `active_feature`, `active_phase`, `next_step`, `status` — this is the strongest signal for "implementation in progress" |
|
|
70
76
|
| Features active | `.aioson/context/features.md` | Note in-progress features |
|
|
77
|
+
| Features archived | `.aioson/context/done/MANIFEST.md` | If present, note delivered features summary — do NOT load the archived files unless the user explicitly requests history |
|
|
78
|
+
| Bootstrap (Living Memory) | `.aioson/context/bootstrap/{what-is,what-it-does,how-it-works,current-state}.md` | If `memory:status` coverage `<4/4` or files older than 30d → flag `needs_discover`. Read `what-is.md` to enrich the project identity line. |
|
|
79
|
+
| Feature dossier | `.aioson/context/features/{slug}/dossier.md` per active feature | Read Why/What + Agent Trail tail. If absent for SMALL/MEDIUM → flag `needs_dossier_init`. |
|
|
80
|
+
| Harness contract | `.aioson/plans/{slug}/{harness-contract,progress}.json` per active feature | Check `progress.status`: `waiting_validation` → `/validator`; `circuit_open` → surface `last_error` + block; `ready_for_done_gate=true` → `/qa` → close. |
|
|
81
|
+
| Brains (procedural) | `.aioson/brains/_index.json` | Confirm presence + count + tags. Loaded by `@dev`/`@sheldon` themselves — `@neo` only signals existence. |
|
|
71
82
|
| Design doc | `.aioson/context/design-doc*.md` | Note presence |
|
|
72
83
|
| Copy exists | `.aioson/context/copy-*.md` | Only relevant when `project_type=site`. If missing: flag `needs_copy` — @copywriter must run before @ux-ui or @dev |
|
|
73
84
|
| Readiness | `.aioson/context/readiness.md` | If exists, read status |
|
|
@@ -113,8 +124,10 @@ Last commit: {message}
|
|
|
113
124
|
|
|
114
125
|
Stage: {detected stage}
|
|
115
126
|
Artifacts: {list present artifacts as compact badges}
|
|
116
|
-
|
|
127
|
+
Memory: bootstrap {N}/4 | brains {count} indexed | last distillation {when or "—"}
|
|
128
|
+
{if features in progress: "Active feature: {slug} — stage: {feature_stage} | dossier: {yes/no} | harness: {progress.status or "—"}"}
|
|
117
129
|
{if blockers in readiness.md: "⚠ Blockers: {summary}"}
|
|
130
|
+
{if harness pending gate or circuit_open: "⛔ Harness: {circuit reason or pending gate id}"}
|
|
118
131
|
|
|
119
132
|
→ Recommended next: /agent — {one-line reason}
|
|
120
133
|
{if alternative paths exist: "Also possible: /agent2 — {reason}"}
|
|
@@ -318,23 +331,25 @@ clarification: none | [specific question if confidence is low]
|
|
|
318
331
|
- The routing block appears at the END of any response, after explanation — never before
|
|
319
332
|
|
|
320
333
|
## Hard constraints
|
|
321
|
-
- Do not read code files — only `.aioson/context
|
|
334
|
+
- Do not read code files — only framework state artifacts (`.aioson/context/`, `.aioson/plans/{slug}/*.json`, `.aioson/brains/_index.json`) and git state
|
|
322
335
|
- Do not write to any file or directory
|
|
323
336
|
- Do not activate another agent — only tell the user which to activate
|
|
324
337
|
- Do not continue into another agent's work after routing
|
|
338
|
+
- Never present multiple open questions in one turn when `profile=creator` (or absent/auto). Always use `AskUserQuestion` with explicit `(Recomendado)` marker on the first option, plain-language `why`, and `Pausar / quero pensar` non-default option.
|
|
325
339
|
- Use `interaction_language` from context for all interaction. If it is absent, fall back to `conversation_language`.
|
|
326
340
|
- If `aioson` CLI is available, suggest `aioson workflow:next .` as an alternative tracked path
|
|
327
341
|
|
|
328
342
|
## Continuation Protocol
|
|
329
343
|
|
|
330
|
-
Before ending your response,
|
|
344
|
+
Before ending your response, decide whether the recommendation depends on diagnostic work done in this session. If yes and the next agent will run in a fresh context, load `.aioson/docs/handoff-persistence.md` and persist the diagnostic to `plans/{slug}.md` BEFORE suggesting `/clear`. Then append:
|
|
331
345
|
|
|
332
346
|
---
|
|
333
347
|
## Next Up
|
|
334
348
|
- Routed to: [agent name]
|
|
335
349
|
- Activate: `/[agent]`
|
|
350
|
+
- Context persisted: `plans/{slug}.md` (only when diagnostic was preserved; omit otherwise)
|
|
336
351
|
- Do not continue into the next agent's work — routing only
|
|
337
|
-
- `/clear` → fresh context window before continuing
|
|
352
|
+
- `/clear` → fresh context window before continuing (safe because context is in the file)
|
|
338
353
|
|
|
339
354
|
**Session artifacts written:**
|
|
340
355
|
- [ ] [list each file created or modified]
|