@jaimevalasek/aioson 1.19.0 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/package.json +1 -1
- package/src/cli.js +10 -2
- package/src/commands/feature-archive.js +165 -48
- package/src/commands/install.js +5 -0
- package/src/doctor.js +17 -0
- package/src/i18n/messages/en.js +4 -0
- package/src/i18n/messages/es.js +4 -0
- package/src/i18n/messages/fr.js +4 -0
- package/src/i18n/messages/pt-BR.js +4 -0
- package/src/install-wizard.js +3 -2
- package/src/lib/tool-capabilities.js +67 -64
- package/src/permissions-generator.js +3 -0
- package/template/.aioson/agents/dev.md +2 -0
- package/template/.aioson/agents/deyvin.md +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.21.0] - 2026-06-XX
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Gemini CLI deprecation warning (gemini-phaseout Phase 1).** Google announced (2026-05-20) that the Gemini CLI free/personal tier ends 2026-06-18.
|
|
11
|
+
- `install-wizard` now flags Gemini as `[DEPRECATED]` in the tool list and prints a post-selection notice when Gemini is chosen.
|
|
12
|
+
- `doctor` reports `harness:gemini_deprecation` (warning) when `.gemini/permissions.toml` or `.gemini/GEMINI.md` is detected — zero output on projects without `.gemini/`.
|
|
13
|
+
- `permissions-generator` continues to emit `.gemini/permissions.toml` with a header warning (enterprise unaffected).
|
|
14
|
+
- `tool-capabilities` Gemini entry annotated as deprecated.
|
|
15
|
+
- Warning strings localized in all 4 locales (en, pt-BR, es, fr).
|
|
16
|
+
- Enterprise users (Code Assist Standard/Enterprise) are unaffected.
|
|
17
|
+
- Hard removal scheduled for v1.22 (post 2026-06-18). Pre-existing `.gemini/permissions.toml` will be preserved.
|
|
18
|
+
- Recommended migration: `--tool=codex` or `--tool=opencode`.
|
|
19
|
+
|
|
7
20
|
## [1.18.0] - 2026-05-27
|
|
8
21
|
|
|
9
22
|
### Added
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -188,7 +188,7 @@ const { runOpShow } = require('./commands/op-show');
|
|
|
188
188
|
const { runOpReinforce } = require('./commands/op-reinforce');
|
|
189
189
|
const { runOpMigrate } = require('./commands/op-migrate');
|
|
190
190
|
const { runFeatureClose } = require('./commands/feature-close');
|
|
191
|
-
const { runFeatureArchive } = require('./commands/feature-archive');
|
|
191
|
+
const { runFeatureArchive, runFeatureSweep } = require('./commands/feature-archive');
|
|
192
192
|
const { runDossierInit, runDossierShow, runDossierAddFinding, runDossierAddCodemap, runDossierLinkRule, runDossierCompact } = require('./commands/dossier');
|
|
193
193
|
const { runDossierAddResearch } = require('./commands/dossier-add-research');
|
|
194
194
|
const { runDossierAudit } = require('./commands/dossier-audit');
|
|
@@ -620,6 +620,8 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
620
620
|
'feature-close',
|
|
621
621
|
'feature:archive',
|
|
622
622
|
'feature-archive',
|
|
623
|
+
'feature:sweep',
|
|
624
|
+
'feature-sweep',
|
|
623
625
|
'dossier:init',
|
|
624
626
|
'dossier-init',
|
|
625
627
|
'dossier:show',
|
|
@@ -1480,7 +1482,13 @@ async function main() {
|
|
|
1480
1482
|
} else if (command === 'feature:close' || command === 'feature-close') {
|
|
1481
1483
|
result = await runFeatureClose({ args, options, logger: commandLogger });
|
|
1482
1484
|
} else if (command === 'feature:archive' || command === 'feature-archive') {
|
|
1483
|
-
|
|
1485
|
+
if (options.sweep) {
|
|
1486
|
+
result = await runFeatureSweep({ args, options, logger: commandLogger });
|
|
1487
|
+
} else {
|
|
1488
|
+
result = await runFeatureArchive({ args, options, logger: commandLogger });
|
|
1489
|
+
}
|
|
1490
|
+
} else if (command === 'feature:sweep' || command === 'feature-sweep') {
|
|
1491
|
+
result = await runFeatureSweep({ args, options, logger: commandLogger });
|
|
1484
1492
|
} else if (command === 'dossier:init' || command === 'dossier-init') {
|
|
1485
1493
|
result = await runDossierInit({ args, options, logger: commandLogger });
|
|
1486
1494
|
} else if (command === 'dossier:show' || command === 'dossier-show') {
|
|
@@ -270,19 +270,42 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
270
270
|
const otherSlugs = await readOtherSlugs(featuresPath, slug);
|
|
271
271
|
const rootFiles = await findSlugFiles(ctxDir, slug, otherSlugs);
|
|
272
272
|
const alreadyArchived = (await dirExists(archiveDir)) ? await findArchivedFiles(archiveDir) : [];
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
|
|
274
|
+
const SLUG_DIRS = [
|
|
275
|
+
{ label: 'dossier', sourceBase: path.join(ctxDir, 'features'), archiveLabel: 'dossier' },
|
|
276
|
+
{ label: 'plans', sourceBase: path.join(targetDir, '.aioson', 'plans'), archiveLabel: 'plans' },
|
|
277
|
+
{ label: 'briefings', sourceBase: path.join(targetDir, '.aioson', 'briefings'), archiveLabel: 'briefings' }
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const dirPlans = [];
|
|
281
|
+
for (const dir of SLUG_DIRS) {
|
|
282
|
+
const sourceDir = path.join(dir.sourceBase, slug);
|
|
283
|
+
const targetDirPath = path.join(archiveDir, dir.archiveLabel);
|
|
284
|
+
const hasSource = await dirExists(sourceDir);
|
|
285
|
+
const alreadyDone = await dirExists(targetDirPath);
|
|
286
|
+
if (hasSource || alreadyDone) {
|
|
287
|
+
dirPlans.push({
|
|
288
|
+
label: dir.label,
|
|
289
|
+
sourceDir,
|
|
290
|
+
targetDir: targetDirPath,
|
|
291
|
+
sourceBase: dir.sourceBase,
|
|
292
|
+
action: hasSource
|
|
293
|
+
? (alreadyDone ? 'skip' : 'move')
|
|
294
|
+
: (alreadyDone ? 'noop' : null),
|
|
295
|
+
reason: alreadyDone ? 'already_archived' : null
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const hasAnyDir = dirPlans.some((d) => d.action === 'move' || d.action === 'skip' || d.action === 'noop');
|
|
277
301
|
|
|
278
302
|
if (
|
|
279
303
|
rootFiles.length === 0 &&
|
|
280
304
|
alreadyArchived.length === 0 &&
|
|
281
|
-
!
|
|
282
|
-
!dossierAlreadyArchived
|
|
305
|
+
!hasAnyDir
|
|
283
306
|
) {
|
|
284
307
|
if (jsonOut) return { ok: true, slug, moved: [], skipped: [], alreadyArchived: [], noop: true };
|
|
285
|
-
log(`No files matched "*-${slug}.{${ARCHIVED_EXTENSIONS.join(',')}}" in .aioson/context/ root and no
|
|
308
|
+
log(`No files matched "*-${slug}.{${ARCHIVED_EXTENSIONS.join(',')}}" in .aioson/context/ root and no slug directories found — nothing to archive.`);
|
|
286
309
|
return { ok: true, noop: true };
|
|
287
310
|
}
|
|
288
311
|
|
|
@@ -305,11 +328,16 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
305
328
|
: null;
|
|
306
329
|
const summary = summarySource ? await extractSummary(summarySource) : null;
|
|
307
330
|
|
|
308
|
-
const dossierPlan = hasDossierToMove
|
|
309
|
-
? (dossierAlreadyArchived ? { action: 'skip', reason: 'already_archived' } : { action: 'move' })
|
|
310
|
-
: (dossierAlreadyArchived ? { action: 'noop', reason: 'already_archived' } : null);
|
|
311
|
-
|
|
312
331
|
if (dryRun) {
|
|
332
|
+
const dirs = dirPlans
|
|
333
|
+
.filter((d) => d.action)
|
|
334
|
+
.map((d) => ({
|
|
335
|
+
label: d.label,
|
|
336
|
+
source: path.relative(targetDir, d.sourceDir),
|
|
337
|
+
target: path.relative(targetDir, d.targetDir),
|
|
338
|
+
action: d.action,
|
|
339
|
+
reason: d.reason
|
|
340
|
+
}));
|
|
313
341
|
const result = {
|
|
314
342
|
ok: true,
|
|
315
343
|
dryRun: true,
|
|
@@ -317,13 +345,7 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
317
345
|
targetDir: path.relative(targetDir, archiveDir),
|
|
318
346
|
move: toMove,
|
|
319
347
|
skip: toSkip,
|
|
320
|
-
|
|
321
|
-
? {
|
|
322
|
-
source: path.relative(targetDir, dossierSourceDir),
|
|
323
|
-
target: path.relative(targetDir, dossierTargetDir),
|
|
324
|
-
...dossierPlan
|
|
325
|
-
}
|
|
326
|
-
: null,
|
|
348
|
+
dirs,
|
|
327
349
|
manifestEntry: {
|
|
328
350
|
slug,
|
|
329
351
|
completed,
|
|
@@ -340,10 +362,12 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
340
362
|
log(` would skip: ${toSkip.length} file(s)`);
|
|
341
363
|
for (const s of toSkip) log(` • ${s.name} (${s.reason})`);
|
|
342
364
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
365
|
+
for (const d of dirPlans) {
|
|
366
|
+
if (d.action === 'move') {
|
|
367
|
+
log(` would move ${d.label} dir: ${path.relative(targetDir, d.sourceDir)}/ → ${path.relative(targetDir, d.targetDir)}/`);
|
|
368
|
+
} else if (d.action === 'skip') {
|
|
369
|
+
log(` would skip ${d.label} dir: already archived at ${path.relative(targetDir, d.targetDir)}/`);
|
|
370
|
+
}
|
|
347
371
|
}
|
|
348
372
|
log(` manifest entry: | ${slug} | ${completed} | ${toMove.length + alreadyArchived.length} | ${summary || '—'} |`);
|
|
349
373
|
return result;
|
|
@@ -359,27 +383,29 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
359
383
|
moved.push(name);
|
|
360
384
|
}
|
|
361
385
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
386
|
+
const dirResults = [];
|
|
387
|
+
for (const d of dirPlans) {
|
|
388
|
+
if (d.action === 'move') {
|
|
389
|
+
await fs.mkdir(path.dirname(d.targetDir), { recursive: true });
|
|
390
|
+
await fs.rename(d.sourceDir, d.targetDir);
|
|
391
|
+
dirResults.push({
|
|
392
|
+
label: d.label,
|
|
393
|
+
action: 'moved',
|
|
394
|
+
source: path.relative(targetDir, d.sourceDir),
|
|
395
|
+
target: path.relative(targetDir, d.targetDir)
|
|
396
|
+
});
|
|
397
|
+
try {
|
|
398
|
+
const remaining = await fs.readdir(d.sourceBase);
|
|
399
|
+
if (remaining.length === 0) await fs.rmdir(d.sourceBase);
|
|
400
|
+
} catch { /* parent missing or non-empty */ }
|
|
401
|
+
} else if (d.action === 'skip') {
|
|
402
|
+
dirResults.push({
|
|
403
|
+
label: d.label,
|
|
404
|
+
action: 'skipped',
|
|
405
|
+
reason: d.reason,
|
|
406
|
+
target: path.relative(targetDir, d.targetDir)
|
|
407
|
+
});
|
|
376
408
|
}
|
|
377
|
-
} else if (dossierPlan && dossierPlan.action === 'skip') {
|
|
378
|
-
dossierResult = {
|
|
379
|
-
action: 'skipped',
|
|
380
|
-
reason: dossierPlan.reason,
|
|
381
|
-
target: path.relative(targetDir, dossierTargetDir)
|
|
382
|
-
};
|
|
383
409
|
}
|
|
384
410
|
|
|
385
411
|
const totalArchived = (await findArchivedFiles(archiveDir)).length;
|
|
@@ -399,7 +425,8 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
399
425
|
moved,
|
|
400
426
|
skipped: toSkip,
|
|
401
427
|
totalArchived,
|
|
402
|
-
|
|
428
|
+
dirs: dirResults.length > 0 ? dirResults : undefined,
|
|
429
|
+
dossier: dirResults.find((d) => d.label === 'dossier') || null,
|
|
403
430
|
manifestEntry: entry
|
|
404
431
|
};
|
|
405
432
|
|
|
@@ -412,10 +439,12 @@ async function runFeatureArchive({ args = [], options = {}, logger }) {
|
|
|
412
439
|
log(` skipped: ${toSkip.length} file(s) already in archive`);
|
|
413
440
|
for (const s of toSkip) log(` • ${s.name}`);
|
|
414
441
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
442
|
+
for (const d of dirResults) {
|
|
443
|
+
if (d.action === 'moved') {
|
|
444
|
+
log(` moved ${d.label} dir: ${d.source}/ → ${d.target}/`);
|
|
445
|
+
} else if (d.action === 'skipped') {
|
|
446
|
+
log(` skipped ${d.label} dir: already archived at ${d.target}/`);
|
|
447
|
+
}
|
|
419
448
|
}
|
|
420
449
|
log(` manifest updated: .aioson/context/done/MANIFEST.md`);
|
|
421
450
|
return result;
|
|
@@ -510,4 +539,92 @@ async function runRestore({ slug, ctxDir, archiveDir, manifestPath, dryRun, json
|
|
|
510
539
|
return result;
|
|
511
540
|
}
|
|
512
541
|
|
|
513
|
-
|
|
542
|
+
async function listDoneFeatures(featuresPath) {
|
|
543
|
+
const content = await readFileSafe(featuresPath);
|
|
544
|
+
if (!content) return [];
|
|
545
|
+
const results = [];
|
|
546
|
+
const lines = content.split(/\r?\n/);
|
|
547
|
+
for (const line of lines) {
|
|
548
|
+
const m = line.match(/^\|\s*([a-z][a-z0-9-]*)\s*\|\s*done\s*\|/i);
|
|
549
|
+
if (m) results.push(m[1].toLowerCase());
|
|
550
|
+
}
|
|
551
|
+
return results;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function listArchivedSlugs(manifestPath) {
|
|
555
|
+
const content = await readFileSafe(manifestPath);
|
|
556
|
+
if (!content) return new Set();
|
|
557
|
+
const slugs = new Set();
|
|
558
|
+
const lines = content.split(/\r?\n/);
|
|
559
|
+
for (const line of lines) {
|
|
560
|
+
const m = line.match(/^\|\s*([a-z][a-z0-9-]+)\s*\|/i);
|
|
561
|
+
if (m && m[1] !== 'slug') slugs.add(m[1].toLowerCase());
|
|
562
|
+
}
|
|
563
|
+
return slugs;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function runFeatureSweep({ args = [], options = {}, logger }) {
|
|
567
|
+
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
568
|
+
const dryRun = Boolean(options['dry-run'] || options.dryRun);
|
|
569
|
+
const jsonOut = Boolean(options.json);
|
|
570
|
+
const log = (msg) => { if (logger && !jsonOut) logger.log(msg); };
|
|
571
|
+
|
|
572
|
+
const ctxDir = contextDir(targetDir);
|
|
573
|
+
if (!(await dirExists(ctxDir))) {
|
|
574
|
+
if (jsonOut) return { ok: false, reason: 'no_context_dir' };
|
|
575
|
+
log('.aioson/context/ not found. Run aioson setup first.');
|
|
576
|
+
return { ok: false };
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const featuresPath = path.join(ctxDir, 'features.md');
|
|
580
|
+
const manifestPath = path.join(ctxDir, 'done', 'MANIFEST.md');
|
|
581
|
+
|
|
582
|
+
const doneSlugs = await listDoneFeatures(featuresPath);
|
|
583
|
+
const archivedSlugs = await listArchivedSlugs(manifestPath);
|
|
584
|
+
const pending = doneSlugs.filter((s) => !archivedSlugs.has(s));
|
|
585
|
+
|
|
586
|
+
if (pending.length === 0) {
|
|
587
|
+
const result = { ok: true, pending: [], archived: [] };
|
|
588
|
+
if (jsonOut) return result;
|
|
589
|
+
log('All done features are already archived.');
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (dryRun) {
|
|
594
|
+
const result = { ok: true, dryRun: true, pending, archived: [] };
|
|
595
|
+
if (jsonOut) return result;
|
|
596
|
+
log(`[dry-run] ${pending.length} done feature(s) not yet archived:`);
|
|
597
|
+
for (const s of pending) log(` • ${s}`);
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const archived = [];
|
|
602
|
+
const failed = [];
|
|
603
|
+
for (const slug of pending) {
|
|
604
|
+
try {
|
|
605
|
+
const archiveResult = await runFeatureArchive({
|
|
606
|
+
args: [targetDir],
|
|
607
|
+
options: { feature: slug, json: true },
|
|
608
|
+
logger: null
|
|
609
|
+
});
|
|
610
|
+
if (archiveResult && archiveResult.ok) {
|
|
611
|
+
const movedCount = archiveResult.moved ? archiveResult.moved.length : 0;
|
|
612
|
+
archived.push({ slug, moved: movedCount });
|
|
613
|
+
log(` ✓ ${slug} — ${movedCount} file(s) archived`);
|
|
614
|
+
} else {
|
|
615
|
+
failed.push({ slug, reason: archiveResult.reason || 'unknown' });
|
|
616
|
+
log(` ✗ ${slug} — ${archiveResult.reason || 'unknown'}`);
|
|
617
|
+
}
|
|
618
|
+
} catch (err) {
|
|
619
|
+
failed.push({ slug, reason: err.message || String(err) });
|
|
620
|
+
log(` ✗ ${slug} — ${err.message || err}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const result = { ok: true, pending, archived, failed: failed.length > 0 ? failed : undefined };
|
|
625
|
+
if (jsonOut) return result;
|
|
626
|
+
log(`\nSweep complete: ${archived.length} archived, ${failed.length} failed.`);
|
|
627
|
+
return result;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
module.exports = { runFeatureArchive, runFeatureSweep };
|
package/src/commands/install.js
CHANGED
|
@@ -147,6 +147,11 @@ async function runInstall({ args, options, logger, t }) {
|
|
|
147
147
|
logger.log(t('install.existing_project_scan_hint'));
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
if (installProfile && Array.isArray(installProfile.tools) && installProfile.tools.includes('gemini')) {
|
|
151
|
+
logger.log('');
|
|
152
|
+
logger.log(t('install.gemini_deprecation_notice'));
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
return {
|
|
151
156
|
ok: true,
|
|
152
157
|
targetDir,
|
package/src/doctor.js
CHANGED
|
@@ -241,6 +241,23 @@ async function runDoctor(targetDir) {
|
|
|
241
241
|
});
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
// Gemini CLI deprecation advisory (gemini-phaseout Phase 1 / v1.21.0).
|
|
245
|
+
// Emits ONLY when the project actually uses Gemini (.gemini/permissions.toml
|
|
246
|
+
// OR .gemini/GEMINI.md present) so greenfield projects stay silent (BR-GP-03).
|
|
247
|
+
const geminiInUse =
|
|
248
|
+
(await exists(path.join(targetDir, '.gemini/permissions.toml'))) ||
|
|
249
|
+
(await exists(path.join(targetDir, '.gemini/GEMINI.md')));
|
|
250
|
+
if (geminiInUse) {
|
|
251
|
+
checks.push({
|
|
252
|
+
id: 'harness:gemini_deprecation',
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
key: 'doctor.gemini_deprecation',
|
|
255
|
+
params: {},
|
|
256
|
+
ok: false,
|
|
257
|
+
hintKey: 'doctor.gemini_deprecation_hint'
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
244
261
|
const contextPath = path.join(targetDir, '.aioson/context/project.context.md');
|
|
245
262
|
checks.push({
|
|
246
263
|
id: 'context:project',
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -327,6 +327,7 @@ module.exports = {
|
|
|
327
327
|
files_skipped: 'Files skipped: {count}',
|
|
328
328
|
dry_run_header: '⚠ DRY RUN — no files were written. Showing what install would do:',
|
|
329
329
|
dry_run_done_at: 'DRY RUN: nothing was written to {targetDir}',
|
|
330
|
+
gemini_deprecation_notice: '[DEPRECATED] Gemini CLI free/personal tier will be discontinued on 2026-06-18. Consider Codex or OpenCode for new projects. Enterprise (Code Assist Standard/Enterprise) continues to work.',
|
|
330
331
|
dry_run_files_copied: 'Files that would be copied: {count}',
|
|
331
332
|
dry_run_files_skipped: 'Files that would be skipped: {count}',
|
|
332
333
|
next_steps: 'Next steps:',
|
|
@@ -399,6 +400,9 @@ module.exports = {
|
|
|
399
400
|
context_conversation_language_format: '`conversation_language` is not a valid BCP-47 tag',
|
|
400
401
|
context_conversation_language_format_hint: 'Use values like en, en-US, pt-BR.',
|
|
401
402
|
node_version: 'Node.js >= 18 (current: {version})',
|
|
403
|
+
gemini_deprecation: 'Gemini CLI in use - free tier ends 2026-06-18 (enterprise unaffected)',
|
|
404
|
+
gemini_deprecation_hint:
|
|
405
|
+
'Run `aioson permissions-generator --tool=codex` (or --tool=opencode) to migrate. Enterprise (Code Assist Standard/Enterprise) keeps working; pre-existing .gemini/permissions.toml is preserved.',
|
|
402
406
|
gateway_claude_pointer: 'CLAUDE gateway references shared AIOSON files',
|
|
403
407
|
gateway_claude_pointer_hint:
|
|
404
408
|
'Ensure CLAUDE.md references .aioson/config.md and .aioson/agents/setup.md.',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -198,6 +198,7 @@ module.exports = {
|
|
|
198
198
|
files_skipped: 'Archivos omitidos: {count}',
|
|
199
199
|
dry_run_header: '⚠ DRY RUN — no se escribio ningun archivo. Mostrando lo que haria el install:',
|
|
200
200
|
dry_run_done_at: 'DRY RUN: no se escribio nada en {targetDir}',
|
|
201
|
+
gemini_deprecation_notice: '[DEPRECATED] El tier gratuito/personal de Gemini CLI se descontinuara el 2026-06-18. Considera Codex u OpenCode para nuevos proyectos. Enterprise (Code Assist Standard/Enterprise) sigue funcionando.',
|
|
201
202
|
dry_run_files_copied: 'Archivos que se copiarian (would be copied): {count}',
|
|
202
203
|
dry_run_files_skipped: 'Archivos que se omitirian (would be skipped): {count}',
|
|
203
204
|
next_steps: 'Siguientes pasos:',
|
|
@@ -278,6 +279,9 @@ module.exports = {
|
|
|
278
279
|
context_conversation_language_format: '`conversation_language` no es una etiqueta BCP-47 valida',
|
|
279
280
|
context_conversation_language_format_hint: 'Usa valores como en, en-US, pt-BR.',
|
|
280
281
|
node_version: 'Node.js >= 18 (actual: {version})',
|
|
282
|
+
gemini_deprecation: 'Gemini CLI en uso - el tier gratuito termina el 2026-06-18 (enterprise no afectado)',
|
|
283
|
+
gemini_deprecation_hint:
|
|
284
|
+
'Ejecuta `aioson permissions-generator --tool=codex` (o --tool=opencode) para migrar. Enterprise (Code Assist Standard/Enterprise) sigue funcionando; el .gemini/permissions.toml preexistente se conserva.',
|
|
281
285
|
gateway_claude_pointer: 'El gateway de CLAUDE referencia archivos compartidos de AIOSON',
|
|
282
286
|
gateway_claude_pointer_hint:
|
|
283
287
|
'Asegura que CLAUDE.md referencie .aioson/config.md y .aioson/agents/setup.md.',
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -198,6 +198,7 @@ module.exports = {
|
|
|
198
198
|
files_skipped: 'Fichiers ignores : {count}',
|
|
199
199
|
dry_run_header: '⚠ DRY RUN — aucun fichier ecrit. Apercu de ce que install ferait :',
|
|
200
200
|
dry_run_done_at: 'DRY RUN : rien n a ete ecrit dans {targetDir}',
|
|
201
|
+
gemini_deprecation_notice: '[DEPRECATED] Le palier gratuit/personnel de Gemini CLI sera supprime le 2026-06-18. Envisagez Codex ou OpenCode pour les nouveaux projets. Enterprise (Code Assist Standard/Enterprise) continue de fonctionner.',
|
|
201
202
|
dry_run_files_copied: 'Fichiers qui seraient copies (would be copied) : {count}',
|
|
202
203
|
dry_run_files_skipped: 'Fichiers qui seraient ignores (would be skipped) : {count}',
|
|
203
204
|
next_steps: 'Etapes suivantes :',
|
|
@@ -277,6 +278,9 @@ module.exports = {
|
|
|
277
278
|
context_conversation_language_format: '`conversation_language` n est pas une balise BCP-47 valide',
|
|
278
279
|
context_conversation_language_format_hint: 'Utilisez des valeurs comme en, en-US, pt-BR.',
|
|
279
280
|
node_version: 'Node.js >= 18 (actuel : {version})',
|
|
281
|
+
gemini_deprecation: 'Gemini CLI utilise - le palier gratuit se termine le 2026-06-18 (enterprise non affecte)',
|
|
282
|
+
gemini_deprecation_hint:
|
|
283
|
+
'Lancez `aioson permissions-generator --tool=codex` (ou --tool=opencode) pour migrer. Enterprise (Code Assist Standard/Enterprise) continue de fonctionner; le .gemini/permissions.toml preexistant est conserve.',
|
|
280
284
|
gateway_claude_pointer: 'La passerelle CLAUDE reference les fichiers partages AIOSON',
|
|
281
285
|
gateway_claude_pointer_hint:
|
|
282
286
|
'Assurez-vous que CLAUDE.md reference .aioson/config.md et .aioson/agents/setup.md.',
|
|
@@ -292,6 +292,7 @@ module.exports = {
|
|
|
292
292
|
files_skipped: 'Arquivos ignorados: {count}',
|
|
293
293
|
dry_run_header: '⚠ DRY RUN — nenhum arquivo foi escrito. Mostrando o que o install faria:',
|
|
294
294
|
dry_run_done_at: 'DRY RUN: nada foi escrito em {targetDir}',
|
|
295
|
+
gemini_deprecation_notice: '[DEPRECATED] O tier free/pessoal do Gemini CLI sera descontinuado em 2026-06-18. Considere Codex ou OpenCode para novos projetos. Enterprise (Code Assist Standard/Enterprise) continua funcionando.',
|
|
295
296
|
dry_run_files_copied: 'Arquivos que seriam copiados (would be copied): {count}',
|
|
296
297
|
dry_run_files_skipped: 'Arquivos que seriam ignorados (would be skipped): {count}',
|
|
297
298
|
next_steps: 'Proximos passos:',
|
|
@@ -372,6 +373,9 @@ module.exports = {
|
|
|
372
373
|
'`conversation_language` nao e uma tag BCP-47 valida',
|
|
373
374
|
context_conversation_language_format_hint: 'Use valores como en, en-US, pt-BR.',
|
|
374
375
|
node_version: 'Node.js >= 18 (atual: {version})',
|
|
376
|
+
gemini_deprecation: 'Gemini CLI em uso - tier free encerra em 2026-06-18 (enterprise nao afetado)',
|
|
377
|
+
gemini_deprecation_hint:
|
|
378
|
+
'Rode `aioson permissions-generator --tool=codex` (ou --tool=opencode) para migrar. Enterprise (Code Assist Standard/Enterprise) continua funcionando; .gemini/permissions.toml pre-existente e preservado.',
|
|
375
379
|
gateway_claude_pointer: 'Gateway do CLAUDE referencia arquivos compartilhados do AIOSON',
|
|
376
380
|
gateway_claude_pointer_hint:
|
|
377
381
|
'Garanta que CLAUDE.md referencie .aioson/config.md e .aioson/agents/setup.md.',
|
package/src/install-wizard.js
CHANGED
|
@@ -6,7 +6,7 @@ const { getCliVersionSync } = require('./version');
|
|
|
6
6
|
const TOOLS = [
|
|
7
7
|
{ id: 'claude', label: 'Claude Code', desc: 'Slash commands, CLAUDE.md, .claude/' },
|
|
8
8
|
{ id: 'codex', label: 'Codex (OpenAI)', desc: 'AGENTS.md protocol' },
|
|
9
|
-
{ id: 'gemini', label: 'Gemini CLI', desc: 'GEMINI.md + .gemini/commands/' },
|
|
9
|
+
{ id: 'gemini', label: 'Gemini CLI', desc: 'GEMINI.md + .gemini/commands/', deprecated: true },
|
|
10
10
|
{ id: 'opencode', label: 'OpenCode', desc: 'OPENCODE.md protocol' }
|
|
11
11
|
];
|
|
12
12
|
|
|
@@ -112,7 +112,8 @@ function renderScreen1(cursor, selected, warn, stdout) {
|
|
|
112
112
|
const tool = TOOLS[i];
|
|
113
113
|
const pointer = i === cursor ? '►' : ' ';
|
|
114
114
|
const check = selected.has(tool.id) ? '✓' : ' ';
|
|
115
|
-
|
|
115
|
+
const deprNote = tool.deprecated ? ' ⚠ [DEPRECATED] free tier ends 2026-06-18' : '';
|
|
116
|
+
stdout.write(` ${pointer} [${check}] ${tool.label.padEnd(20)} ${tool.desc}${deprNote}\n`);
|
|
116
117
|
}
|
|
117
118
|
if (warn) stdout.write('\n ⚠ Select at least one tool to continue.\n');
|
|
118
119
|
stdout.write('\n');
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
// "continue last conversation" is achieved by passing the right resume flag
|
|
6
6
|
// at spawn time — AIOSON never has to track an internal session ID.
|
|
7
7
|
//
|
|
8
|
-
// Used by:
|
|
9
|
-
// - `aioson live:start --resume[=last|<id>]` to map to the correct argv
|
|
10
|
-
// - `aioson live:start --permission-mode=yolo` to map to the correct argv
|
|
11
|
-
// - `aioson tool:capabilities` to expose this map as JSON to UI clients
|
|
12
|
-
// (e.g. AIOSON Play) so they don't duplicate the lookup.
|
|
8
|
+
// Used by:
|
|
9
|
+
// - `aioson live:start --resume[=last|<id>]` to map to the correct argv
|
|
10
|
+
// - `aioson live:start --permission-mode=yolo` to map to the correct argv
|
|
11
|
+
// - `aioson tool:capabilities` to expose this map as JSON to UI clients
|
|
12
|
+
// (e.g. AIOSON Play) so they don't duplicate the lookup.
|
|
13
13
|
//
|
|
14
14
|
// Keep entries minimal and source-of-truth here. Adding a new CLI = one entry.
|
|
15
15
|
const TOOL_CAPS = {
|
|
@@ -18,50 +18,53 @@ const TOOL_CAPS = {
|
|
|
18
18
|
binary: 'claude',
|
|
19
19
|
supports_resume: true,
|
|
20
20
|
resume_last: ['--continue'],
|
|
21
|
-
supports_session_id: true,
|
|
22
|
-
resume_session_id: ['--resume', '<id>'],
|
|
23
|
-
supports_session_picker: true,
|
|
24
|
-
session_picker: ['--resume'],
|
|
25
|
-
supports_yolo: true,
|
|
26
|
-
yolo_args: ['--dangerously-skip-permissions'],
|
|
27
|
-
},
|
|
28
|
-
codex: {
|
|
21
|
+
supports_session_id: true,
|
|
22
|
+
resume_session_id: ['--resume', '<id>'],
|
|
23
|
+
supports_session_picker: true,
|
|
24
|
+
session_picker: ['--resume'],
|
|
25
|
+
supports_yolo: true,
|
|
26
|
+
yolo_args: ['--dangerously-skip-permissions'],
|
|
27
|
+
},
|
|
28
|
+
codex: {
|
|
29
29
|
install_command: 'npm install -g @openai/codex',
|
|
30
30
|
binary: 'codex',
|
|
31
31
|
supports_resume: true,
|
|
32
32
|
resume_last: ['resume', '--last'],
|
|
33
|
-
supports_session_id: true,
|
|
34
|
-
resume_session_id: ['resume', '<id>'],
|
|
35
|
-
supports_session_picker: true,
|
|
36
|
-
session_picker: ['resume'],
|
|
37
|
-
supports_yolo: true,
|
|
38
|
-
yolo_args: ['--dangerously-bypass-approvals-and-sandbox'],
|
|
39
|
-
},
|
|
40
|
-
opencode: {
|
|
33
|
+
supports_session_id: true,
|
|
34
|
+
resume_session_id: ['resume', '<id>'],
|
|
35
|
+
supports_session_picker: true,
|
|
36
|
+
session_picker: ['resume'],
|
|
37
|
+
supports_yolo: true,
|
|
38
|
+
yolo_args: ['--dangerously-bypass-approvals-and-sandbox'],
|
|
39
|
+
},
|
|
40
|
+
opencode: {
|
|
41
41
|
install_command: 'npm install -g opencode-ai',
|
|
42
42
|
binary: 'opencode',
|
|
43
43
|
supports_resume: true,
|
|
44
44
|
resume_last: ['--continue'],
|
|
45
|
-
supports_session_id: true,
|
|
46
|
-
resume_session_id: ['--session', '<id>'],
|
|
47
|
-
supports_session_picker: false,
|
|
48
|
-
session_picker: null,
|
|
49
|
-
supports_yolo: false,
|
|
50
|
-
yolo_args: null,
|
|
51
|
-
},
|
|
52
|
-
|
|
45
|
+
supports_session_id: true,
|
|
46
|
+
resume_session_id: ['--session', '<id>'],
|
|
47
|
+
supports_session_picker: false,
|
|
48
|
+
session_picker: null,
|
|
49
|
+
supports_yolo: false,
|
|
50
|
+
yolo_args: null,
|
|
51
|
+
},
|
|
52
|
+
// DEPRECATED: Gemini CLI free/personal tier ends 2026-06-18. Enterprise
|
|
53
|
+
// (Code Assist Standard/Enterprise) unaffected. Hard removal in v1.22.
|
|
54
|
+
// See gemini-phaseout / CHANGELOG v1.21.x.
|
|
55
|
+
gemini: {
|
|
53
56
|
install_command: 'npm install -g @google/gemini-cli',
|
|
54
57
|
binary: 'gemini',
|
|
55
58
|
supports_resume: false,
|
|
56
59
|
resume_last: null,
|
|
57
|
-
supports_session_id: false,
|
|
58
|
-
resume_session_id: null,
|
|
59
|
-
supports_session_picker: false,
|
|
60
|
-
session_picker: null,
|
|
61
|
-
supports_yolo: false,
|
|
62
|
-
yolo_args: null,
|
|
63
|
-
},
|
|
64
|
-
};
|
|
60
|
+
supports_session_id: false,
|
|
61
|
+
resume_session_id: null,
|
|
62
|
+
supports_session_picker: false,
|
|
63
|
+
session_picker: null,
|
|
64
|
+
supports_yolo: false,
|
|
65
|
+
yolo_args: null,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
65
68
|
|
|
66
69
|
function getToolCapabilities(tool) {
|
|
67
70
|
const key = String(tool || '').trim().toLowerCase();
|
|
@@ -80,7 +83,7 @@ function listSupportedTools() {
|
|
|
80
83
|
// - '' / undefined / null / false → no resume
|
|
81
84
|
// - any other string → treat as session id
|
|
82
85
|
// Returns [] when the tool doesn't support resume or resumeOpt is falsy.
|
|
83
|
-
function resolveResumeArgs(tool, resumeOpt) {
|
|
86
|
+
function resolveResumeArgs(tool, resumeOpt) {
|
|
84
87
|
if (resumeOpt === undefined || resumeOpt === null || resumeOpt === '' || resumeOpt === false) {
|
|
85
88
|
return [];
|
|
86
89
|
}
|
|
@@ -101,29 +104,29 @@ function resolveResumeArgs(tool, resumeOpt) {
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
return Array.isArray(caps.resume_last) ? [...caps.resume_last] : [];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function resolvePermissionModeArgs(tool, permissionMode) {
|
|
107
|
-
const mode = String(permissionMode || '').trim().toLowerCase();
|
|
108
|
-
if (!mode || mode === 'default') return [];
|
|
109
|
-
if (mode !== 'yolo') {
|
|
110
|
-
throw new Error(`permission_mode_unknown:${permissionMode}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const caps = getToolCapabilities(tool);
|
|
114
|
-
if (!caps) {
|
|
115
|
-
throw new Error(`tool_unknown:${tool}`);
|
|
116
|
-
}
|
|
117
|
-
if (!caps.supports_yolo || !Array.isArray(caps.yolo_args)) {
|
|
118
|
-
throw new Error(`permission_mode_unsupported:${tool}:yolo`);
|
|
119
|
-
}
|
|
120
|
-
return [...caps.yolo_args];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
module.exports = {
|
|
124
|
-
TOOL_CAPS,
|
|
125
|
-
getToolCapabilities,
|
|
126
|
-
listSupportedTools,
|
|
127
|
-
resolveResumeArgs,
|
|
128
|
-
resolvePermissionModeArgs,
|
|
129
|
-
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function resolvePermissionModeArgs(tool, permissionMode) {
|
|
110
|
+
const mode = String(permissionMode || '').trim().toLowerCase();
|
|
111
|
+
if (!mode || mode === 'default') return [];
|
|
112
|
+
if (mode !== 'yolo') {
|
|
113
|
+
throw new Error(`permission_mode_unknown:${permissionMode}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const caps = getToolCapabilities(tool);
|
|
117
|
+
if (!caps) {
|
|
118
|
+
throw new Error(`tool_unknown:${tool}`);
|
|
119
|
+
}
|
|
120
|
+
if (!caps.supports_yolo || !Array.isArray(caps.yolo_args)) {
|
|
121
|
+
throw new Error(`permission_mode_unsupported:${tool}:yolo`);
|
|
122
|
+
}
|
|
123
|
+
return [...caps.yolo_args];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
TOOL_CAPS,
|
|
128
|
+
getToolCapabilities,
|
|
129
|
+
listSupportedTools,
|
|
130
|
+
resolveResumeArgs,
|
|
131
|
+
resolvePermissionModeArgs,
|
|
132
|
+
};
|
|
@@ -195,6 +195,9 @@ function tomlString(value) {
|
|
|
195
195
|
function buildGeminiToml({ shellPatterns, aiosonCommands, denyShellPatterns = [], denyAiosonCommands = [] }, tool) {
|
|
196
196
|
const lines = [];
|
|
197
197
|
lines.push('# Generated by aioson permissions-generator. Do not edit by hand.');
|
|
198
|
+
lines.push('# WARNING: Gemini CLI free tier ends 2026-06-18. This file remains');
|
|
199
|
+
lines.push('# functional for enterprise users (Code Assist Standard/Enterprise).');
|
|
200
|
+
lines.push('# See AIOSON CHANGELOG v1.21.x for migration guidance.');
|
|
198
201
|
lines.push('version = "1.1"');
|
|
199
202
|
lines.push(`mode = ${tomlString(tool.mode || 'guarded')}`);
|
|
200
203
|
lines.push(`requires_tty = ${Boolean(tool.requires_tty)}`);
|
|
@@ -228,6 +228,8 @@ Before the first code change, decide which dev docs must be loaded:
|
|
|
228
228
|
|
|
229
229
|
Do not preload these docs if the current slice does not need them.
|
|
230
230
|
|
|
231
|
+
Before touching code, if `aioson` is available, run `aioson feature:sweep . --dry-run --json` to detect done features not yet archived. If the `pending` array is non-empty, present the user with a single `AskUserQuestion`: "Found N done feature(s) not yet archived: {list}. Archive now?" with options "(Recomendado) Sim, arquivar agora" and "Não, seguir sem arquivar". If yes, run `aioson feature:sweep .` and report the result. This step is advisory — never block session start.
|
|
232
|
+
|
|
231
233
|
## Execution invariants
|
|
232
234
|
|
|
233
235
|
These rules apply even if no extra dev doc was loaded:
|
|
@@ -90,6 +90,7 @@ Run this after the immediate scope gate and before touching code:
|
|
|
90
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
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
92
|
8. Do not touch code until all required modules have been loaded
|
|
93
|
+
9. If `aioson` is available, run `aioson feature:sweep . --dry-run --json` to detect done features not yet archived. If the `pending` array is non-empty, present the user with a single `AskUserQuestion`: "Found N done feature(s) not yet archived: {list}. Archive now?" with options "(Recomendado) Sim, arquivar agora" and "Não, seguir sem arquivar". If yes, run `aioson feature:sweep .` and report the result. This step is advisory — never block session start.
|
|
93
94
|
|
|
94
95
|
## Working kernel
|
|
95
96
|
|