@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaimevalasek/aioson",
3
- "version": "1.19.0",
3
+ "version": "1.21.0",
4
4
  "description": "AI operating framework for hyper-personalized software.",
5
5
  "keywords": [
6
6
  "ai",
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
- result = await runFeatureArchive({ args, options, logger: commandLogger });
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
- const dossierSourceDir = path.join(ctxDir, 'features', slug);
274
- const dossierTargetDir = path.join(archiveDir, 'dossier');
275
- const hasDossierToMove = await dirExists(dossierSourceDir);
276
- const dossierAlreadyArchived = await dirExists(dossierTargetDir);
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
- !hasDossierToMove &&
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 features/${slug}/ dossier dir — nothing to archive.`);
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
- dossier: dossierPlan
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
- if (dossierPlan && dossierPlan.action === 'move') {
344
- log(` would move dossier dir: features/${slug}/ → ${path.relative(targetDir, dossierTargetDir)}/`);
345
- } else if (dossierPlan && dossierPlan.action === 'skip') {
346
- log(` would skip dossier dir: already archived at ${path.relative(targetDir, dossierTargetDir)}/`);
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
- let dossierResult = null;
363
- if (dossierPlan && dossierPlan.action === 'move') {
364
- await fs.rename(dossierSourceDir, dossierTargetDir);
365
- dossierResult = {
366
- action: 'moved',
367
- source: path.relative(targetDir, dossierSourceDir),
368
- target: path.relative(targetDir, dossierTargetDir)
369
- };
370
- try {
371
- const parent = path.join(ctxDir, 'features');
372
- const remaining = await fs.readdir(parent);
373
- if (remaining.length === 0) await fs.rmdir(parent);
374
- } catch {
375
- // parent missing or non-empty — leave it
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
- dossier: dossierResult,
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
- if (dossierResult && dossierResult.action === 'moved') {
416
- log(` moved dossier dir: ${dossierResult.source}/ → ${dossierResult.target}/`);
417
- } else if (dossierResult && dossierResult.action === 'skipped') {
418
- log(` skipped dossier dir: already archived at ${dossierResult.target}/`);
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
- module.exports = { runFeatureArchive };
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 };
@@ -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',
@@ -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.',
@@ -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.',
@@ -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.',
@@ -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
- stdout.write(` ${pointer} [${check}] ${tool.label.padEnd(20)} ${tool.desc}\n`);
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
- gemini: {
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