theforge-cli 0.2.5 → 0.3.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/dist/index.js CHANGED
@@ -2664,6 +2664,19 @@ var FORGE_REPOS = [
2664
2664
  kind: "node",
2665
2665
  verify: ["npx", "--no-install", "ng", "version"],
2666
2666
  nextSteps: ["npm start"]
2667
+ },
2668
+ {
2669
+ // lf-0031 (gap G7): o repositório existe e é clonável, mas ainda não tem
2670
+ // rota de instalação — sem setup.sh/forge_bootstrap.py na fonte e sem
2671
+ // dist no PyPI (verificado). Sem `nativeInstall`: o fluxo git reporta
2672
+ // `noSetupSteps` honestamente; o registry recusa com "sem dist no
2673
+ // catálogo". Nenhum caminho finge uma instalação que não existe.
2674
+ name: "data-sentinel-forge",
2675
+ url: "https://github.com/EdgarSocrates98/data-sentinel-forge",
2676
+ description: "Diagn\xF3stico de runtime (logs/plans Spark, doctor-bundles)",
2677
+ kind: "python",
2678
+ verify: ["data-sentinel", "--help"],
2679
+ nextSteps: ["data-sentinel --help"]
2667
2680
  }
2668
2681
  ];
2669
2682
 
@@ -10787,6 +10800,9 @@ var S = {
10787
10800
  recoveredRun: "Execu\xE7\xE3o anterior interrompida recuperada do estado persistido",
10788
10801
  breakingTitle: "An\xE1lise de breaking changes",
10789
10802
  rollbackScopeNote: "Rollback cobre apenas efeitos revers\xEDveis documentados",
10803
+ integrationsTitle: "Integra\xE7\xF5es opcionais",
10804
+ integrationsOffer: "Integra\xE7\xF5es opcionais \u2014 marque para instalar (nada entra sem sua sele\xE7\xE3o)",
10805
+ integrationNoInstallRoute: "sem dist no cat\xE1logo registry \u2014 instale via git (`--method git`) ou pelo m\xE9todo do reposit\xF3rio",
10790
10806
  usage: `forge-cli \u2014 instalador do ecossistema Forge
10791
10807
 
10792
10808
  Uso:
@@ -19707,7 +19723,7 @@ function errMsg(error) {
19707
19723
  }
19708
19724
 
19709
19725
  // src/doctor2/domains.ts
19710
- import { existsSync as existsSync24, readdirSync as readdirSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
19726
+ import { existsSync as existsSync25, readdirSync as readdirSync11, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync6 } from "fs";
19711
19727
  import os4 from "os";
19712
19728
  import path34 from "path";
19713
19729
 
@@ -20188,6 +20204,303 @@ function analyzeBumps(current, candidates) {
20188
20204
  }).sort((a3, b) => a3.to.localeCompare(b.to, void 0, { numeric: true }));
20189
20205
  }
20190
20206
 
20207
+ // src/integrations.ts
20208
+ import { existsSync as existsSync24 } from "fs";
20209
+ var OPTIONAL_INTEGRATIONS = {
20210
+ "the-forge": [
20211
+ {
20212
+ name: "spark-forge-aws",
20213
+ reason: "an\xE1lise est\xE1tica/runtime PySpark \u2014 delegate do orquestrador",
20214
+ adapter: true,
20215
+ installable: true
20216
+ },
20217
+ {
20218
+ name: "spark-forge-azure",
20219
+ reason: "an\xE1lise PySpark em Azure \u2014 delegate do orquestrador",
20220
+ adapter: true,
20221
+ installable: true
20222
+ },
20223
+ {
20224
+ name: "api-forge",
20225
+ reason: "constru\xE7\xE3o/contratos de API \u2014 delegate do orquestrador",
20226
+ adapter: true,
20227
+ installable: true
20228
+ },
20229
+ {
20230
+ name: "platform-forge",
20231
+ reason: "postura de plataforma \u2014 delegate do orquestrador",
20232
+ adapter: true,
20233
+ installable: true
20234
+ },
20235
+ {
20236
+ name: "forge-doctor-data",
20237
+ reason: "verifica\xE7\xE3o independente de dados \u2014 delegate do orquestrador",
20238
+ adapter: true,
20239
+ installable: true
20240
+ },
20241
+ {
20242
+ name: "forge-doctor-api",
20243
+ reason: "verifica\xE7\xE3o independente de API \u2014 delegate do orquestrador",
20244
+ adapter: true,
20245
+ installable: true
20246
+ },
20247
+ {
20248
+ name: "data-sentinel-forge",
20249
+ reason: "diagn\xF3stico de runtime (logs/plans) \u2014 sem adapter the-forge e sem rota de install: gap documentado, nunca obrigat\xF3rio",
20250
+ adapter: false,
20251
+ installable: false
20252
+ }
20253
+ ],
20254
+ "spark-forge-aws": [
20255
+ {
20256
+ name: "forge-doctor-data",
20257
+ reason: "verificador independente das an\xE1lises produzidas",
20258
+ adapter: true,
20259
+ installable: true
20260
+ },
20261
+ {
20262
+ name: "platform-forge",
20263
+ reason: "estado da plataforma onde o job roda",
20264
+ adapter: true,
20265
+ installable: true
20266
+ },
20267
+ {
20268
+ name: "spark-forge-azure",
20269
+ reason: "fallback declarado (mesma fam\xEDlia spark)",
20270
+ adapter: true,
20271
+ installable: true
20272
+ },
20273
+ {
20274
+ name: "api-forge",
20275
+ reason: "contratos de API adjacentes ao pipeline",
20276
+ adapter: true,
20277
+ installable: true
20278
+ },
20279
+ {
20280
+ name: "data-sentinel-forge",
20281
+ reason: "diagn\xF3stico de runtime \u2014 sem adapter e sem rota de install: gap documentado, nunca obrigat\xF3rio",
20282
+ adapter: false,
20283
+ installable: false
20284
+ }
20285
+ ],
20286
+ "spark-forge-azure": [
20287
+ {
20288
+ name: "forge-doctor-data",
20289
+ reason: "verificador independente das an\xE1lises produzidas",
20290
+ adapter: true,
20291
+ installable: true
20292
+ },
20293
+ {
20294
+ name: "platform-forge",
20295
+ reason: "estado da plataforma onde o job roda",
20296
+ adapter: true,
20297
+ installable: true
20298
+ },
20299
+ {
20300
+ name: "spark-forge-aws",
20301
+ reason: "fallback declarado (mesma fam\xEDlia spark)",
20302
+ adapter: true,
20303
+ installable: true
20304
+ },
20305
+ {
20306
+ name: "data-sentinel-forge",
20307
+ reason: "diagn\xF3stico de runtime \u2014 sem adapter e sem rota de install: gap documentado, nunca obrigat\xF3rio",
20308
+ adapter: false,
20309
+ installable: false
20310
+ }
20311
+ ],
20312
+ "api-forge": [
20313
+ {
20314
+ name: "forge-doctor-api",
20315
+ reason: "verificador independente (producer\u2260verifier)",
20316
+ adapter: true,
20317
+ installable: true
20318
+ },
20319
+ {
20320
+ name: "platform-forge",
20321
+ reason: "governan\xE7a/estado da plataforma consumidora",
20322
+ adapter: true,
20323
+ installable: true
20324
+ }
20325
+ ],
20326
+ "platform-forge": [
20327
+ {
20328
+ name: "api-forge",
20329
+ reason: "comp\xF5e governan\xE7a de APIs da plataforma",
20330
+ adapter: true,
20331
+ installable: true
20332
+ },
20333
+ {
20334
+ name: "forge-doctor-data",
20335
+ reason: "verificador independente declarado",
20336
+ adapter: true,
20337
+ installable: true
20338
+ }
20339
+ ],
20340
+ "forge-doctor-api": [
20341
+ {
20342
+ name: "api-forge",
20343
+ reason: "produtor dos artefatos que ele verifica",
20344
+ adapter: true,
20345
+ installable: true
20346
+ },
20347
+ {
20348
+ name: "forge-doctor-data",
20349
+ reason: "fallback declarado entre verificadores",
20350
+ adapter: true,
20351
+ installable: true
20352
+ }
20353
+ ],
20354
+ "forge-doctor-data": [
20355
+ {
20356
+ name: "spark-forge-aws",
20357
+ reason: "produtor das an\xE1lises que ele verifica",
20358
+ adapter: true,
20359
+ installable: true
20360
+ },
20361
+ {
20362
+ name: "spark-forge-azure",
20363
+ reason: "produtor das an\xE1lises que ele verifica",
20364
+ adapter: true,
20365
+ installable: true
20366
+ },
20367
+ {
20368
+ name: "platform-forge",
20369
+ reason: "estado da plataforma verificada",
20370
+ adapter: true,
20371
+ installable: true
20372
+ },
20373
+ {
20374
+ name: "forge-doctor-api",
20375
+ reason: "fallback declarado entre verificadores",
20376
+ adapter: true,
20377
+ installable: true
20378
+ },
20379
+ {
20380
+ name: "data-sentinel-forge",
20381
+ reason: "importa doctor-bundles para diagn\xF3stico de runtime \u2014 sem adapter e sem rota de install: gap documentado",
20382
+ adapter: false,
20383
+ installable: false
20384
+ }
20385
+ ],
20386
+ "forge-portal": [
20387
+ {
20388
+ name: "the-forge",
20389
+ reason: "control plane que o portal renderiza",
20390
+ adapter: true,
20391
+ installable: true
20392
+ }
20393
+ ],
20394
+ "data-sentinel-forge": [
20395
+ {
20396
+ name: "forge-doctor-data",
20397
+ reason: "consome doctor-bundles para diagn\xF3stico independente",
20398
+ adapter: true,
20399
+ installable: true
20400
+ },
20401
+ {
20402
+ name: "spark-forge-aws",
20403
+ reason: "fonte de evid\xEAncia Spark para diagn\xF3stico",
20404
+ adapter: true,
20405
+ installable: true
20406
+ },
20407
+ {
20408
+ name: "spark-forge-azure",
20409
+ reason: "fonte de evid\xEAncia Spark para diagn\xF3stico",
20410
+ adapter: true,
20411
+ installable: true
20412
+ }
20413
+ ]
20414
+ };
20415
+ function integrationsOf(forge) {
20416
+ return OPTIONAL_INTEGRATIONS[forge] ?? [];
20417
+ }
20418
+ function detectLocal(integration, deps) {
20419
+ const { name } = integration;
20420
+ const evidence = [];
20421
+ const receipt = loadForgeState(deps.home).forges[name];
20422
+ if (receipt) {
20423
+ evidence.push(`receipt=${receipt.method}@${receipt.venv ?? receipt.bin ?? "?"}`);
20424
+ if (receipt.version) evidence.push(`version=${receipt.version}`);
20425
+ const localMissing = receipt.method === "local" && (typeof receipt.source_dir !== "string" || !existsSync24(receipt.source_dir));
20426
+ if (localMissing) {
20427
+ evidence.push(`source_dir=${receipt.source_dir ?? "?"} (AUSENTE)`);
20428
+ return { integration, state: "degraded", evidence };
20429
+ }
20430
+ if (receipt.verify?.ok === false) {
20431
+ evidence.push(`verify=${receipt.verify.command.join(" ") || "?"} falhou`);
20432
+ return { integration, state: "degraded", evidence };
20433
+ }
20434
+ evidence.push(`verify=${receipt.verify?.ok ? "ok" : "n\xE3o executado"}`);
20435
+ return { integration, state: receipt.verify?.ok ? "verified" : "installed", evidence };
20436
+ }
20437
+ const dir = resolveInside(deps.root, name);
20438
+ if (dir && existsSync24(dir)) {
20439
+ evidence.push(`checkout=${dir}${hasGitRepo(dir) ? " (git)" : " (sem .git)"}`);
20440
+ return { integration, state: "checkout", evidence };
20441
+ }
20442
+ evidence.push("sem receipt, checkout ou bin\xE1rio gerenciado detectado");
20443
+ return { integration, state: "absent", evidence };
20444
+ }
20445
+ async function detect(integration, deps) {
20446
+ const local = detectLocal(integration, deps);
20447
+ if (local.state !== "absent" || !deps.which) return local;
20448
+ const bin = deps.bin?.(integration.name);
20449
+ if (!bin) return local;
20450
+ const found = await deps.which(bin);
20451
+ if (!found) return local;
20452
+ return {
20453
+ integration,
20454
+ state: "installed",
20455
+ evidence: [`bin=${found} (PATH, n\xE3o-gerenciado)`]
20456
+ };
20457
+ }
20458
+ async function classifyIntegrations(forge, deps) {
20459
+ const out = [];
20460
+ for (const integration of integrationsOf(forge)) {
20461
+ out.push(await detect(integration, deps));
20462
+ }
20463
+ return out;
20464
+ }
20465
+ function classifyIntegrationsSync(forge, deps) {
20466
+ return integrationsOf(forge).map((i3) => detectLocal(i3, deps));
20467
+ }
20468
+ function pendingIntegrations(statuses) {
20469
+ return statuses.filter((s) => s.state === "absent" || s.state === "checkout");
20470
+ }
20471
+ function installableNames(statuses) {
20472
+ return statuses.filter((s) => s.integration.installable).map((s) => s.integration.name);
20473
+ }
20474
+ async function defaultWhich(bin) {
20475
+ const isWin = process.platform === "win32";
20476
+ try {
20477
+ const res = await execa(isWin ? "where" : "sh", isWin ? [bin] : ["-c", `command -v ${bin}`], {
20478
+ reject: false
20479
+ });
20480
+ const first = (res.stdout ?? "").split("\n")[0]?.trim();
20481
+ return res.exitCode === 0 && first ? first : null;
20482
+ } catch {
20483
+ return null;
20484
+ }
20485
+ }
20486
+ async function askOptionalIntegrations(statuses, ask) {
20487
+ const pending = pendingIntegrations(statuses);
20488
+ const chosen = await ask(
20489
+ pending.map((s) => ({
20490
+ value: s.integration.name,
20491
+ label: s.integration.name,
20492
+ hint: s.integration.reason
20493
+ }))
20494
+ ) ?? [];
20495
+ const installable = new Set(installableNames(pending));
20496
+ return {
20497
+ offered: pending,
20498
+ consented: chosen,
20499
+ installable: chosen.filter((n4) => installable.has(n4)),
20500
+ notInstallable: chosen.filter((n4) => !installable.has(n4))
20501
+ };
20502
+ }
20503
+
20191
20504
  // src/doctor2/domains.ts
20192
20505
  function systemChecks(ctx) {
20193
20506
  const checks = [];
@@ -20279,7 +20592,7 @@ function systemChecks(ctx) {
20279
20592
  const dirEvidence = [];
20280
20593
  let dirFailed = 0;
20281
20594
  for (const { label, dir: d } of required) {
20282
- if (!existsSync24(d)) {
20595
+ if (!existsSync25(d)) {
20283
20596
  dirFailed += 1;
20284
20597
  dirEvidence.push(`${label}=${d}: ausente`);
20285
20598
  continue;
@@ -20350,7 +20663,7 @@ function backupScan(ctx) {
20350
20663
  scan(
20351
20664
  path34.join(ctx.home, ".forge-cli", "backups"),
20352
20665
  "host-backup",
20353
- (full) => existsSync24(path34.join(full, "manifest.json"))
20666
+ (full) => existsSync25(path34.join(full, "manifest.json"))
20354
20667
  );
20355
20668
  for (const repo of ctx.catalog) {
20356
20669
  const dir = resolveInside(ctx.root, repo.name);
@@ -20363,7 +20676,7 @@ function backupScan(ctx) {
20363
20676
  function installationChecks(ctx) {
20364
20677
  const checks = [];
20365
20678
  const forgeDir = artifactsRoot(ctx.root);
20366
- const forgeDirPresent = existsSync24(forgeDir);
20679
+ const forgeDirPresent = existsSync25(forgeDir);
20367
20680
  const stateFile = stateFileFor(ctx.root);
20368
20681
  checks.push(
20369
20682
  mkCheck(
@@ -20376,7 +20689,7 @@ function installationChecks(ctx) {
20376
20689
  diagnosis: "diret\xF3rio de estado .forge-cli presente",
20377
20690
  evidence: [
20378
20691
  `${forgeDir} presente`,
20379
- existsSync24(stateFile) ? "state.json presente" : "state.json ausente"
20692
+ existsSync25(stateFile) ? "state.json presente" : "state.json ausente"
20380
20693
  ],
20381
20694
  reversible: null
20382
20695
  } : {
@@ -20392,7 +20705,7 @@ function installationChecks(ctx) {
20392
20705
  )
20393
20706
  );
20394
20707
  const forgeJson = path34.join(ctx.root, "forge.json");
20395
- if (!existsSync24(forgeJson)) {
20708
+ if (!existsSync25(forgeJson)) {
20396
20709
  checks.push(
20397
20710
  mkCheck(ctx.now, {
20398
20711
  id: "installation.forge-json.present",
@@ -20442,9 +20755,9 @@ function installationChecks(ctx) {
20442
20755
  }
20443
20756
  }
20444
20757
  const lockFile = path34.join(forgeDir, "env.lock.json");
20445
- const lockParsed = existsSync24(lockFile) ? readJson(lockFile) : null;
20758
+ const lockParsed = existsSync25(lockFile) ? readJson(lockFile) : null;
20446
20759
  const lockObj = lockParsed?.ok && lockParsed.value !== null && typeof lockParsed.value === "object" ? lockParsed.value : null;
20447
- if (!forgeDirPresent || !existsSync24(lockFile)) {
20760
+ if (!forgeDirPresent || !existsSync25(lockFile)) {
20448
20761
  checks.push(
20449
20762
  mkCheck(ctx.now, {
20450
20763
  id: "installation.lockfile.present",
@@ -20489,7 +20802,7 @@ function installationChecks(ctx) {
20489
20802
  );
20490
20803
  }
20491
20804
  const trackedForges = lockObj ? Object.keys(lockObj.forges ?? {}) : [];
20492
- if (!existsSync24(lockFile) || !lockObj || !trackedForges.length) {
20805
+ if (!existsSync25(lockFile) || !lockObj || !trackedForges.length) {
20493
20806
  checks.push(
20494
20807
  mkCheck(ctx.now, {
20495
20808
  id: "installation.lockfile.drift",
@@ -20498,7 +20811,7 @@ function installationChecks(ctx) {
20498
20811
  severity: "info",
20499
20812
  diagnosis: "deriva do lockfile n\xE3o verific\xE1vel",
20500
20813
  evidence: [
20501
- !existsSync24(lockFile) ? "sem lockfile para recomputar (.forge-cli/env.lock.json ausente)" : !lockObj ? "lockfile ileg\xEDvel \u2014 digest n\xE3o pode ser recomputado" : "lockfile n\xE3o cobre nenhuma Forja \u2014 nada para recomputar"
20814
+ !existsSync25(lockFile) ? "sem lockfile para recomputar (.forge-cli/env.lock.json ausente)" : !lockObj ? "lockfile ileg\xEDvel \u2014 digest n\xE3o pode ser recomputado" : "lockfile n\xE3o cobre nenhuma Forja \u2014 nada para recomputar"
20502
20815
  ],
20503
20816
  reversible: null
20504
20817
  })
@@ -20527,10 +20840,10 @@ function installationChecks(ctx) {
20527
20840
  mkCheck(ctx.now, {
20528
20841
  id: "installation.ledger.readable",
20529
20842
  domain: "installation",
20530
- result: existsSync24(ledgerDir) ? "warn" : "skip",
20531
- severity: existsSync24(ledgerDir) ? "medium" : "info",
20532
- diagnosis: existsSync24(ledgerDir) ? "ledger de instala\xE7\xF5es existe mas n\xE3o p\xF4de ser lido" : "sem ledger de instala\xE7\xF5es (~/.forge/installations ausente)",
20533
- evidence: [`${ledgerDir} ${existsSync24(ledgerDir) ? "ileg\xEDvel" : "ausente"}`],
20843
+ result: existsSync25(ledgerDir) ? "warn" : "skip",
20844
+ severity: existsSync25(ledgerDir) ? "medium" : "info",
20845
+ diagnosis: existsSync25(ledgerDir) ? "ledger de instala\xE7\xF5es existe mas n\xE3o p\xF4de ser lido" : "sem ledger de instala\xE7\xF5es (~/.forge/installations ausente)",
20846
+ evidence: [`${ledgerDir} ${existsSync25(ledgerDir) ? "ileg\xEDvel" : "ausente"}`],
20534
20847
  reversible: null
20535
20848
  })
20536
20849
  );
@@ -20612,11 +20925,11 @@ function installationChecks(ctx) {
20612
20925
  } else {
20613
20926
  const lines = localEntries.map(([name, r2]) => {
20614
20927
  const src = typeof r2.source_dir === "string" ? r2.source_dir : "?";
20615
- const srcOk = typeof r2.source_dir === "string" && existsSync24(r2.source_dir);
20928
+ const srcOk = typeof r2.source_dir === "string" && existsSync25(r2.source_dir);
20616
20929
  return `${name}: is_git=${r2.is_git === true} trust=${r2.source_trust ?? "?"} manifest_valid=${r2.manifest_valid === true} integrity=${r2.integrity ?? "?"} env=${r2.execution_environment?.platform ?? "?"} dir=${src}${srcOk ? "" : " (AUSENTE)"}`;
20617
20930
  });
20618
20931
  const missing = localEntries.filter(
20619
- ([, r2]) => typeof r2.source_dir !== "string" || !existsSync24(r2.source_dir)
20932
+ ([, r2]) => typeof r2.source_dir !== "string" || !existsSync25(r2.source_dir)
20620
20933
  );
20621
20934
  checks.push(
20622
20935
  mkCheck(ctx.now, {
@@ -20690,6 +21003,58 @@ function installationChecks(ctx) {
20690
21003
  }
20691
21004
  )
20692
21005
  );
21006
+ const forgeState = loadForgeState(ctx.home).forges;
21007
+ for (const repo of [...ctx.catalog].sort((a3, b) => a3.name.localeCompare(b.name))) {
21008
+ const dir = resolveInside(ctx.root, repo.name);
21009
+ const consumerPresent = !!dir && hasGitRepo(dir) || !!forgeState[repo.name];
21010
+ if (!consumerPresent) continue;
21011
+ const statuses = classifyIntegrationsSync(repo.name, { root: ctx.root, home: ctx.home });
21012
+ if (!statuses.length) continue;
21013
+ const lines = statuses.map((s) => {
21014
+ const sdir = resolveInside(ctx.root, s.integration.name);
21015
+ let contract = "contract=n\xE3o sondado";
21016
+ if (sdir && existsSync25(sdir)) {
21017
+ const manifest = path34.join(sdir, "forge.json");
21018
+ contract = !existsSync25(manifest) ? "contract=sem forge.json" : readJson(manifest).ok ? "contract=forge.json v\xE1lido" : "contract=forge.json inv\xE1lido";
21019
+ } else if (s.state === "verified" || s.state === "installed") {
21020
+ contract = "contract=manifesto dentro da venv (n\xE3o sondado)";
21021
+ }
21022
+ return `${s.integration.name}: state=${s.state} capability=${s.integration.adapter ? "deriv\xE1vel (adapter declarado)" : "n\xE3o declarada"} adapter=${s.integration.adapter ? "the-forge" : "ausente"} ${contract} [${s.evidence.join(" \xB7 ")}]`;
21023
+ });
21024
+ const degraded = statuses.filter((s) => s.state === "degraded").length;
21025
+ const present = statuses.filter((s) => s.state !== "absent").length;
21026
+ checks.push(
21027
+ mkCheck(
21028
+ ctx.now,
21029
+ degraded ? {
21030
+ id: `installation.integrations.${repo.name}`,
21031
+ domain: "installation",
21032
+ result: "warn",
21033
+ severity: "low",
21034
+ diagnosis: `${degraded} integra\xE7\xE3o(\xF5es) opcional(is) degradada(s)`,
21035
+ evidence: boundEntries(ctx, lines).shown,
21036
+ correctiveAction: "reinstall or verify degraded optional specialists",
21037
+ reversible: true
21038
+ } : present ? {
21039
+ id: `installation.integrations.${repo.name}`,
21040
+ domain: "installation",
21041
+ result: "pass",
21042
+ severity: "info",
21043
+ diagnosis: `${present} integra\xE7\xE3o(\xF5es) opcional(is) presente(s) e saud\xE1vel(is)`,
21044
+ evidence: boundEntries(ctx, lines).shown,
21045
+ reversible: null
21046
+ } : {
21047
+ id: `installation.integrations.${repo.name}`,
21048
+ domain: "installation",
21049
+ result: "skip",
21050
+ severity: "info",
21051
+ diagnosis: "integra\xE7\xF5es opcionais ausentes \u2014 nada exigido, nunca falha a Forja principal",
21052
+ evidence: boundEntries(ctx, lines).shown,
21053
+ reversible: null
21054
+ }
21055
+ )
21056
+ );
21057
+ }
20693
21058
  return checks;
20694
21059
  }
20695
21060
  function environmentChecks(ctx) {
@@ -20697,7 +21062,7 @@ function environmentChecks(ctx) {
20697
21062
  const targets = [{ label: "<workspace>", dir: ctx.root }];
20698
21063
  for (const repo of ctx.catalog) {
20699
21064
  const dir = resolveInside(ctx.root, repo.name);
20700
- if (dir && existsSync24(dir)) targets.push({ label: repo.name, dir, envKeys: repo.envKeys });
21065
+ if (dir && existsSync25(dir)) targets.push({ label: repo.name, dir, envKeys: repo.envKeys });
20701
21066
  }
20702
21067
  const inspected = targets.map((t2) => ({
20703
21068
  ...t2,
@@ -20919,7 +21284,7 @@ function operationsChecks(ctx) {
20919
21284
  checks.push(
20920
21285
  mkCheck(
20921
21286
  ctx.now,
20922
- !existsSync24(reportsDir) ? {
21287
+ !existsSync25(reportsDir) ? {
20923
21288
  id: "operations.receipts.recent",
20924
21289
  domain: "operations",
20925
21290
  result: "skip",
@@ -20955,7 +21320,7 @@ function operationsChecks(ctx) {
20955
21320
  if (entry2.status === "error") failures.set(name, (failures.get(name) ?? 0) + 1);
20956
21321
  }
20957
21322
  const recurring = [...failures.entries()].filter(([, n4]) => n4 >= 2);
20958
- const scannedAnything = existsSync24(reportsDir) || ctx.state !== null;
21323
+ const scannedAnything = existsSync25(reportsDir) || ctx.state !== null;
20959
21324
  if (!scannedAnything) {
20960
21325
  checks.push(
20961
21326
  mkCheck(ctx.now, {
@@ -21231,7 +21596,7 @@ async function runDoctor2(root, opts = {}) {
21231
21596
  }
21232
21597
 
21233
21598
  // src/heal/fixes.ts
21234
- import { existsSync as existsSync25, mkdirSync as mkdirSync8, readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as statSync5 } from "fs";
21599
+ import { existsSync as existsSync26, mkdirSync as mkdirSync8, readdirSync as readdirSync12, readFileSync as readFileSync19, statSync as statSync5 } from "fs";
21235
21600
  import path36 from "path";
21236
21601
  var rel = (root, abs) => path36.relative(root, abs) || ".";
21237
21602
  function stampOf(iso) {
@@ -21250,7 +21615,7 @@ var forgeDirRepair = {
21250
21615
  return ["", ...FORGE_DIR_SUBDIRS].map((sub) => {
21251
21616
  const dir = sub ? path36.join(base, sub) : base;
21252
21617
  return {
21253
- description: `create directory ${rel(ctx.root, dir)}${existsSync25(dir) ? " (already present)" : ""}`,
21618
+ description: `create directory ${rel(ctx.root, dir)}${existsSync26(dir) ? " (already present)" : ""}`,
21254
21619
  touches: [rel(ctx.root, dir)]
21255
21620
  };
21256
21621
  });
@@ -21261,7 +21626,7 @@ var forgeDirRepair = {
21261
21626
  const created = [];
21262
21627
  for (const sub of ["", ...FORGE_DIR_SUBDIRS]) {
21263
21628
  const dir = sub ? path36.join(base, sub) : base;
21264
- if (existsSync25(dir)) continue;
21629
+ if (existsSync26(dir)) continue;
21265
21630
  mkdirSync8(dir, { recursive: true });
21266
21631
  created.push(rel(ctx.root, dir));
21267
21632
  }
@@ -21285,7 +21650,7 @@ function envPathOf(root) {
21285
21650
  }
21286
21651
  function presentKeys(root) {
21287
21652
  const file = envPathOf(root);
21288
- if (!existsSync25(file)) return /* @__PURE__ */ new Set();
21653
+ if (!existsSync26(file)) return /* @__PURE__ */ new Set();
21289
21654
  return new Set(parseEnv(readFileSync19(file, "utf8")).entries.map((e) => e.key));
21290
21655
  }
21291
21656
  function missingEnvKeys(root, check) {
@@ -21321,7 +21686,7 @@ var envMissingKeyRepair = {
21321
21686
  const root = ctx.root;
21322
21687
  const toAppend = keysToAppend(root, ctx.check);
21323
21688
  const steps = [];
21324
- if (existsSync25(envPathOf(root))) {
21689
+ if (existsSync26(envPathOf(root))) {
21325
21690
  steps.push({
21326
21691
  description: `backup .env to ${rel(root, path36.join(artifactsRoot(root), "backups"))}/env-<ts>/.env`,
21327
21692
  touches: [".env", `${rel(root, artifactsRoot(root))}/backups/`]
@@ -21340,7 +21705,7 @@ var envMissingKeyRepair = {
21340
21705
  const root = ctx.root;
21341
21706
  if (ctx.dryRun) return "dry-run \u2014 not applied";
21342
21707
  const file = envPathOf(root);
21343
- const exists = existsSync25(file);
21708
+ const exists = existsSync26(file);
21344
21709
  const current = exists ? readFileSync19(file, "utf8") : "";
21345
21710
  const env = parseEnv(current);
21346
21711
  const present = new Set(env.entries.map((e) => e.key));
@@ -21358,7 +21723,7 @@ var envMissingKeyRepair = {
21358
21723
  },
21359
21724
  async verify(ctx) {
21360
21725
  const file = envPathOf(ctx.root);
21361
- if (!existsSync25(file)) return false;
21726
+ if (!existsSync26(file)) return false;
21362
21727
  const present = new Set(parseEnv(readFileSync19(file, "utf8")).entries.map((e) => e.key));
21363
21728
  return missingEnvKeys(ctx.root, ctx.check).every((k) => present.has(k));
21364
21729
  }
@@ -21396,7 +21761,7 @@ var ENV_BACKUP_RE = /^env-\d{8}-\d{6}$/;
21396
21761
  var REPO_BACKUP_RE = /^([a-z0-9][a-z0-9._-]*)-\d{10,}$/i;
21397
21762
  function listBackups(root) {
21398
21763
  const base = path36.join(artifactsRoot(root), "backups");
21399
- if (!existsSync25(base)) return [];
21764
+ if (!existsSync26(base)) return [];
21400
21765
  const out = [];
21401
21766
  for (const entry2 of readdirSync12(base, { withFileTypes: true })) {
21402
21767
  if (!entry2.isDirectory()) continue;
@@ -21425,7 +21790,7 @@ function pickBackupFile(ctx) {
21425
21790
  if (inside.startsWith("..") || path36.isAbsolute(inside)) {
21426
21791
  throw new Error(`selected backup escapes .forge-cli/backups: ${ctx.backup}`);
21427
21792
  }
21428
- if (!existsSync25(abs) || !statSync5(abs).isFile()) {
21793
+ if (!existsSync26(abs) || !statSync5(abs).isFile()) {
21429
21794
  throw new Error(`selected backup not found: ${ctx.backup}`);
21430
21795
  }
21431
21796
  const dirName = inside.split(path36.sep)[0] ?? "";
@@ -21493,7 +21858,7 @@ var backupRestoreRepair = {
21493
21858
  try {
21494
21859
  const picked = pickBackupFile(ctx);
21495
21860
  const dest = path36.resolve(ctx.root, picked.dest);
21496
- return existsSync25(dest) && readFileSync19(dest, "utf8") === readFileSync19(picked.src, "utf8");
21861
+ return existsSync26(dest) && readFileSync19(dest, "utf8") === readFileSync19(picked.src, "utf8");
21497
21862
  } catch {
21498
21863
  return false;
21499
21864
  }
@@ -21748,7 +22113,7 @@ async function runDoctor2Command(cli) {
21748
22113
 
21749
22114
  // src/cmd/registry.ts
21750
22115
  import path38 from "path";
21751
- import { existsSync as existsSync26, mkdirSync as mkdirSync10 } from "fs";
22116
+ import { existsSync as existsSync27, mkdirSync as mkdirSync10 } from "fs";
21752
22117
 
21753
22118
  // src/registry/catalog.ts
21754
22119
  var REGISTRY_PACKAGES = [
@@ -22162,7 +22527,14 @@ async function runRegistryInstall(cli, names, deps = {}) {
22162
22527
  ...receipt.native ? { native: receipt.native } : {}
22163
22528
  });
22164
22529
  }
22165
- const doc = { method: "pypi", results, warnings };
22530
+ const integrationStatuses = deps.skipIntegrations ? [] : await collectIntegrationStatuses(results, home, cli, deps.which);
22531
+ const pending = pendingIntegrations(integrationStatuses);
22532
+ const doc = {
22533
+ method: "pypi",
22534
+ results,
22535
+ warnings,
22536
+ ...pending.length ? { optional_integrations: pending.map(integrationJson) } : {}
22537
+ };
22166
22538
  reportTo2(doc);
22167
22539
  if (cli.reportPath !== void 0) {
22168
22540
  log.info(`${S.reportSaved}: ${theme.dim(path38.resolve(cli.reportPath))}`);
@@ -22184,8 +22556,62 @@ async function runRegistryInstall(cli, names, deps = {}) {
22184
22556
  "Launchers em ~/.forge-cli/bin (no PATH do forge-cli); sem shim, invoque o caminho da venv"
22185
22557
  );
22186
22558
  outro(`${icons.ok} instala\xE7\xE3o registry-first conclu\xEDda`);
22559
+ if (pending.length && !deps.skipIntegrations) {
22560
+ const canAsk = !quiet2 && !cli.yes && process.stdout.isTTY && process.stdin.isTTY;
22561
+ if (canAsk) {
22562
+ const offer = await askOptionalIntegrations(pending, async (options) => {
22563
+ const picked = await multiselect({
22564
+ message: S.integrationsOffer,
22565
+ options,
22566
+ required: false
22567
+ });
22568
+ if (isCancel(picked)) exitCancelled();
22569
+ return picked;
22570
+ });
22571
+ for (const n4 of offer.notInstallable) {
22572
+ log.warn(`${n4}: ${S.integrationNoInstallRoute}`);
22573
+ }
22574
+ if (offer.installable.length) {
22575
+ await runRegistryInstall(cli, offer.installable, { ...deps, skipIntegrations: true });
22576
+ }
22577
+ } else if (!quiet2) {
22578
+ note(
22579
+ pending.map(
22580
+ (s) => `${s.integration.name} \u2014 ${s.integration.reason}${s.integration.installable ? "" : ` (${S.integrationNoInstallRoute})`}`
22581
+ ).join("\n"),
22582
+ S.integrationsTitle
22583
+ );
22584
+ }
22585
+ }
22187
22586
  return results;
22188
22587
  }
22588
+ function integrationJson(s) {
22589
+ return {
22590
+ name: s.integration.name,
22591
+ state: s.state,
22592
+ adapter: s.integration.adapter,
22593
+ installable: s.integration.installable,
22594
+ reason: s.integration.reason,
22595
+ evidence: s.evidence
22596
+ };
22597
+ }
22598
+ async function collectIntegrationStatuses(results, home, cli, which) {
22599
+ const root = resolveTargetDir(cli.dir);
22600
+ const bin = (n4) => FORGE_REPOS.find((r2) => r2.name === n4)?.verify?.[0];
22601
+ const statuses = [];
22602
+ for (const r2 of results) {
22603
+ if (!r2.ok) continue;
22604
+ statuses.push(
22605
+ ...await classifyIntegrations(r2.forge, {
22606
+ root,
22607
+ home,
22608
+ which: which ?? defaultWhich,
22609
+ bin
22610
+ })
22611
+ );
22612
+ }
22613
+ return statuses;
22614
+ }
22189
22615
  async function runRegistryLock(cli, names, deps = {}) {
22190
22616
  const home = deps.home ?? cliHome();
22191
22617
  const quiet2 = registryQuiet(cli);
@@ -22306,7 +22732,7 @@ function runRegistryList(cli, deps = {}) {
22306
22732
  }
22307
22733
  const rows = REGISTRY_PACKAGES.filter((e) => !wanted || wanted.has(e.name)).map((e) => {
22308
22734
  const inst = state.forges[e.name];
22309
- const binOk = inst ? typeof inst.bin === "string" && existsSync26(inst.bin) : null;
22735
+ const binOk = inst ? typeof inst.bin === "string" && existsSync27(inst.bin) : null;
22310
22736
  const consistency = installConsistency(home, e.name);
22311
22737
  const relevant = consistency.status !== "not-installed";
22312
22738
  return {
@@ -22335,7 +22761,7 @@ function runRegistryList(cli, deps = {}) {
22335
22761
  installed: inst.version ?? null,
22336
22762
  method: inst.method ?? null,
22337
22763
  verifyOk: inst.verify?.ok ?? null,
22338
- binPresent: typeof inst.bin === "string" ? existsSync26(inst.bin) : null,
22764
+ binPresent: typeof inst.bin === "string" ? existsSync27(inst.bin) : null,
22339
22765
  bin: inst.bin ?? null,
22340
22766
  consistent: null,
22341
22767
  consistency: null,
@@ -23103,7 +23529,7 @@ async function runRegistryPin(cli, names, pin, deps = {}) {
23103
23529
 
23104
23530
  // src/source/detect.ts
23105
23531
  import { createHash as createHash5 } from "crypto";
23106
- import { existsSync as existsSync27, lstatSync as lstatSync3, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
23532
+ import { existsSync as existsSync28, lstatSync as lstatSync3, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
23107
23533
  import path39 from "path";
23108
23534
  var MANIFEST_MAX_BYTES = 64 * 1024;
23109
23535
  var CHECKSUM_MAX_ENTRIES = 1e4;
@@ -23126,7 +23552,7 @@ var PROJECT_MARKERS = [
23126
23552
  var CHECKSUM_FILES = ["SHA256SUMS", "checksums.sha256", ".forge-checksums.json"];
23127
23553
  function readManifestStrict(dir) {
23128
23554
  const file = path39.join(dir, "forge.json");
23129
- if (!existsSync27(file)) return { ok: false, reason: "absent", detail: "forge.json ausente" };
23555
+ if (!existsSync28(file)) return { ok: false, reason: "absent", detail: "forge.json ausente" };
23130
23556
  let raw;
23131
23557
  try {
23132
23558
  if (!statSync6(file).isFile() || statSync6(file).size > MANIFEST_MAX_BYTES) {
@@ -23201,7 +23627,7 @@ function scanStructure(dir) {
23201
23627
  return false;
23202
23628
  }
23203
23629
  });
23204
- const markers = PROJECT_MARKERS.filter((f) => existsSync27(path39.join(dir, f)));
23630
+ const markers = PROJECT_MARKERS.filter((f) => existsSync28(path39.join(dir, f)));
23205
23631
  return { setup_scripts: [...setup], project_markers: [...markers] };
23206
23632
  }
23207
23633
  function verifyChecksums(dir, reasons) {
@@ -23423,7 +23849,7 @@ function detectSource(input, cwd = process.cwd()) {
23423
23849
  const structure = scanStructure(dir);
23424
23850
  const expected = structure.setup_scripts.length > 0 && structure.project_markers.length > 0;
23425
23851
  if (gitKind === null) {
23426
- if (!existsSync27(path39.join(dir, "forge.json"))) {
23852
+ if (!existsSync28(path39.join(dir, "forge.json"))) {
23427
23853
  reasons.push("forge.json=ausente");
23428
23854
  return verdict(
23429
23855
  "UNKNOWN_DIRECTORY",
@@ -23506,7 +23932,7 @@ function looksLikePathArg(arg, cwd = process.cwd()) {
23506
23932
  import path41 from "path";
23507
23933
 
23508
23934
  // src/source/install.ts
23509
- import { existsSync as existsSync28, mkdirSync as mkdirSync11, readdirSync as readdirSync13, rmSync as rmSync7, statSync as statSync7 } from "fs";
23935
+ import { existsSync as existsSync29, mkdirSync as mkdirSync11, readdirSync as readdirSync13, rmSync as rmSync7, statSync as statSync7 } from "fs";
23510
23936
  import { copyFile as copyFile3 } from "fs/promises";
23511
23937
  import path40 from "path";
23512
23938
  var fail = (failure, detection) => ({
@@ -23594,7 +24020,7 @@ function selectSetup(det, platform2, python, winShells) {
23594
24020
  if (script) return script;
23595
24021
  const bootstrap = path40.join("scripts", "forge_bootstrap.py");
23596
24022
  const argv = localPythonOf(python)?.argv;
23597
- if (argv?.length && existsSync28(path40.join(det.path, bootstrap))) {
24023
+ if (argv?.length && existsSync29(path40.join(det.path, bootstrap))) {
23598
24024
  return {
23599
24025
  file: bootstrap,
23600
24026
  command: argv[0],
@@ -23620,14 +24046,14 @@ function pythonFloorFailure(det, python) {
23620
24046
  }
23621
24047
  async function provisionEnvironment(dir, forgeId) {
23622
24048
  const envKeys = FORGE_REPOS.find((r2) => r2.name === forgeId)?.envKeys;
23623
- if (envKeys?.length || existsSync28(path40.join(dir, ".env")) || existsSync28(path40.join(dir, ".env.example"))) {
24049
+ if (envKeys?.length || existsSync29(path40.join(dir, ".env")) || existsSync29(path40.join(dir, ".env.example"))) {
23624
24050
  await ensureEnvFile(dir, envKeys);
23625
24051
  }
23626
24052
  await copyConfigExamples(dir);
23627
24053
  }
23628
24054
  function sourceVenv(det, platform2) {
23629
24055
  const venv = path40.join(det.path, ".venv");
23630
- return existsSync28(venvBin(venv, platform2)) ? venv : void 0;
24056
+ return existsSync29(venvBin(venv, platform2)) ? venv : void 0;
23631
24057
  }
23632
24058
  async function persistLocalReceipt(home, receipt, opts) {
23633
24059
  const envelope = resolveInside(forgesRoot(home), receipt.forge);
@@ -23652,7 +24078,7 @@ function buildReceipt(det, deps, script, managed, sourceFiles, prior) {
23652
24078
  const venv = sourceVenv(det, platform2);
23653
24079
  const cliName = manifest?.cli;
23654
24080
  const binCandidate = venv && cliName ? path40.join(venvBin(venv, platform2), cliName) : void 0;
23655
- const bin = binCandidate && existsSync28(binCandidate) ? binCandidate : cliName;
24081
+ const bin = binCandidate && existsSync29(binCandidate) ? binCandidate : cliName;
23656
24082
  const py = localPythonOf(deps.python);
23657
24083
  return {
23658
24084
  forge: det.forge_id,
@@ -23807,7 +24233,7 @@ async function localInstall(dir, deps) {
23807
24233
  await runVerify(receipt, det, deps, run);
23808
24234
  try {
23809
24235
  await persistLocalReceipt(deps.home, receipt, deps.lifecycleLock);
23810
- if (receipt.bin && existsSync28(receipt.bin)) {
24236
+ if (receipt.bin && existsSync29(receipt.bin)) {
23811
24237
  try {
23812
24238
  receipt.shim = emitForgeShim(deps.home, receipt.bin, platform2);
23813
24239
  } catch (shimError) {
@@ -23857,7 +24283,7 @@ async function localUpdate(dir, deps) {
23857
24283
  if (gate) return fail(gate, det);
23858
24284
  const targetDir = prior.source_dir;
23859
24285
  if (path40.resolve(targetDir) !== det.path) {
23860
- if (!existsSync28(targetDir)) {
24286
+ if (!existsSync29(targetDir)) {
23861
24287
  return fail(
23862
24288
  {
23863
24289
  code: "FORGE-SOURCE-NOT-FOUND",
@@ -23879,7 +24305,7 @@ async function localUpdate(dir, deps) {
23879
24305
  for (const rel2 of prior.source_files) {
23880
24306
  if (keep.has(rel2)) continue;
23881
24307
  const target = safeJoinInside(targetDir, rel2);
23882
- if (target && existsSync28(target)) rmSync7(target, { force: true });
24308
+ if (target && existsSync29(target)) rmSync7(target, { force: true });
23883
24309
  }
23884
24310
  }
23885
24311
  return runUpdateAt(targetDir, prior, deps, run);
@@ -23922,7 +24348,7 @@ async function runUpdateAt(dir, prior, deps, run) {
23922
24348
  }
23923
24349
  const managed = [
23924
24350
  .../* @__PURE__ */ new Set([
23925
- ...prior.managed_artifacts.filter((a3) => existsSync28(path40.join(det.path, a3))),
24351
+ ...prior.managed_artifacts.filter((a3) => existsSync29(path40.join(det.path, a3))),
23926
24352
  ...[...topLevelEntries(det.path)].filter((e) => !before.has(e))
23927
24353
  ])
23928
24354
  ].sort();
@@ -23974,7 +24400,7 @@ async function localRepair(name, deps) {
23974
24400
  });
23975
24401
  }
23976
24402
  const dir = prior.source_dir;
23977
- if (!dir || !existsSync28(dir)) {
24403
+ if (!dir || !existsSync29(dir)) {
23978
24404
  return fail({
23979
24405
  code: "FORGE-SOURCE-NOT-FOUND",
23980
24406
  cause: `${name}: fonte local ausente (${dir ?? "desconhecida"})`,
@@ -23989,7 +24415,7 @@ async function localRepair(name, deps) {
23989
24415
  if (floorFailure) return fail(floorFailure, det);
23990
24416
  for (const rel2 of prior.managed_artifacts) {
23991
24417
  const target = safeJoinInside(dir, rel2);
23992
- if (target && existsSync28(target)) rmSync7(target, { recursive: true, force: true });
24418
+ if (target && existsSync29(target)) rmSync7(target, { recursive: true, force: true });
23993
24419
  }
23994
24420
  const before = topLevelEntries(dir);
23995
24421
  const envFailure = await provisionOrRefuse(dir, det.forge_id);
@@ -24024,7 +24450,7 @@ async function localRepair(name, deps) {
24024
24450
  }
24025
24451
  const managed = [
24026
24452
  .../* @__PURE__ */ new Set([
24027
- ...prior.managed_artifacts.filter((a3) => existsSync28(path40.join(dir, a3))),
24453
+ ...prior.managed_artifacts.filter((a3) => existsSync29(path40.join(dir, a3))),
24028
24454
  ...[...topLevelEntries(dir)].filter((e) => !before.has(e))
24029
24455
  ])
24030
24456
  ].sort();
@@ -24080,7 +24506,7 @@ async function localUninstall(name, deps) {
24080
24506
  const dir = prior.source_dir;
24081
24507
  const removed = [];
24082
24508
  let backup;
24083
- if (dir && existsSync28(dir)) {
24509
+ if (dir && existsSync29(dir)) {
24084
24510
  const filesToBackup = prior.managed_artifacts.filter((rel2) => {
24085
24511
  const target = safeJoinInside(dir, rel2);
24086
24512
  try {
@@ -24105,7 +24531,7 @@ async function localUninstall(name, deps) {
24105
24531
  }
24106
24532
  for (const rel2 of prior.managed_artifacts) {
24107
24533
  const target = safeJoinInside(dir, rel2);
24108
- if (target && existsSync28(target)) {
24534
+ if (target && existsSync29(target)) {
24109
24535
  rmSync7(target, { recursive: true, force: true });
24110
24536
  removed.push(rel2);
24111
24537
  }
@@ -24118,7 +24544,7 @@ async function localUninstall(name, deps) {
24118
24544
  try {
24119
24545
  removeForgeShim(deps.home, prior.shim ?? prior.bin, deps.platform ?? process.platform);
24120
24546
  const envelope = resolveInside(forgesRoot(deps.home), name);
24121
- if (envelope && existsSync28(envelope)) rmSync7(envelope, { recursive: true, force: true });
24547
+ if (envelope && existsSync29(envelope)) rmSync7(envelope, { recursive: true, force: true });
24122
24548
  const cur = loadForgeState(deps.home);
24123
24549
  if (Object.hasOwn(cur.forges, name)) {
24124
24550
  delete cur.forges[name];
@@ -24483,15 +24909,15 @@ async function runLocalLifecycleCommand(cli, op, names) {
24483
24909
  }
24484
24910
 
24485
24911
  // src/lifecycle.ts
24486
- import { copyFileSync as copyFileSync3, existsSync as existsSync29, mkdirSync as mkdirSync13, readdirSync as readdirSync14, rmSync as rmSync8 } from "fs";
24912
+ import { copyFileSync as copyFileSync3, existsSync as existsSync30, mkdirSync as mkdirSync13, readdirSync as readdirSync14, rmSync as rmSync8 } from "fs";
24487
24913
  import path42 from "path";
24488
24914
  function configFilesToBackup(repoDir) {
24489
24915
  const out = /* @__PURE__ */ new Set();
24490
- if (existsSync29(path42.join(repoDir, ".env"))) out.add(".env");
24916
+ if (existsSync30(path42.join(repoDir, ".env"))) out.add(".env");
24491
24917
  for (const file of readdirSync14(repoDir)) {
24492
24918
  if (file === ".env.example" || !file.includes(".example.")) continue;
24493
24919
  const real = file.replace(".example.", ".");
24494
- if (existsSync29(path42.join(repoDir, real))) out.add(real);
24920
+ if (existsSync30(path42.join(repoDir, real))) out.add(real);
24495
24921
  }
24496
24922
  return [...out];
24497
24923
  }
@@ -24500,7 +24926,7 @@ async function planUninstall(targetDir, repo) {
24500
24926
  if (!dir)
24501
24927
  return { kind: "refused", dir: path42.join(targetDir, repo.name), reason: "invalid-name" };
24502
24928
  if (isLinkOrJunction(dir)) return { kind: "refused", dir, reason: "symlink" };
24503
- if (!existsSync29(dir)) return { kind: "absent", dir };
24929
+ if (!existsSync30(dir)) return { kind: "absent", dir };
24504
24930
  if (!hasGitRepo(dir)) return { kind: "refused", dir, reason: "non-git" };
24505
24931
  const remote = await commandOutput("git", ["-C", dir, "remote", "get-url", "origin"]);
24506
24932
  if (remote && remote !== "ok" && !sameRemote(remote, repo.url)) {
@@ -24851,11 +25277,11 @@ async function runUninstallCommand(cli) {
24851
25277
  }
24852
25278
 
24853
25279
  // src/config.ts
24854
- import { existsSync as existsSync30, readFileSync as readFileSync21 } from "fs";
25280
+ import { existsSync as existsSync31, readFileSync as readFileSync21 } from "fs";
24855
25281
  import path44 from "path";
24856
25282
  function loadConfig(targetDir) {
24857
25283
  const file = path44.join(targetDir, "forge-cli.config.json");
24858
- if (!existsSync30(file)) return null;
25284
+ if (!existsSync31(file)) return null;
24859
25285
  let parsed;
24860
25286
  try {
24861
25287
  parsed = JSON.parse(readFileSync21(file, "utf8"));
@@ -24901,12 +25327,12 @@ function loadConfig(targetDir) {
24901
25327
  }
24902
25328
 
24903
25329
  // src/plan.ts
24904
- import { existsSync as existsSync31, readdirSync as readdirSync15 } from "fs";
25330
+ import { existsSync as existsSync32, readdirSync as readdirSync15 } from "fs";
24905
25331
  import path45 from "path";
24906
25332
  function planLines(repo, targetDir) {
24907
25333
  const repoDir = path45.join(targetDir, repo.name);
24908
25334
  const lines = [];
24909
- if (!existsSync31(repoDir)) {
25335
+ if (!existsSync32(repoDir)) {
24910
25336
  lines.push(`${S.planSync}: git clone (${S.planDetectAfter})`);
24911
25337
  for (const step of [
24912
25338
  S.phaseEnv,
@@ -24949,7 +25375,7 @@ async function executionSummary(repo, targetDir, native, platform2 = process.pla
24949
25375
  const trust = repo.origin === "custom" ? "custom" : "builtin";
24950
25376
  const commands = [];
24951
25377
  let revision;
24952
- if (!existsSync31(repoDir) || !hasGitRepo(repoDir)) {
25378
+ if (!existsSync32(repoDir) || !hasGitRepo(repoDir)) {
24953
25379
  commands.push(`git clone -- ${repo.url}`, S.planScriptsAfterClone);
24954
25380
  } else {
24955
25381
  revision = (await currentHead(repoDir))?.slice(0, 7);
@@ -25183,11 +25609,59 @@ ${planLines(repo, targetDir).map((line) => ` ${theme.dim(line)}`).join("\n")}`
25183
25609
  log.info(`${S.logSaved}: ${theme.dim(runLog.file)}`);
25184
25610
  const hasErrors = results.some((r2) => r2.status === "error");
25185
25611
  outro(`${icons.rocket} ${hasErrors ? S.outroWithErrors : S.outroOk}`);
25612
+ if (!hasErrors && (resolved.command === "install" || resolved.command === "clone")) {
25613
+ await offerOptionalIntegrations(cli, results, catalog, targetDir, opts, runLog, interactive);
25614
+ }
25186
25615
  if (hasErrors) process.exitCode = 1;
25187
25616
  }
25617
+ async function offerOptionalIntegrations(cli, results, catalog, targetDir, opts, runLog, interactive) {
25618
+ const present = new Set(results.filter((r2) => r2.status !== "error").map((r2) => r2.repo.name));
25619
+ const statuses = [];
25620
+ for (const r2 of results) {
25621
+ if (!present.has(r2.repo.name)) continue;
25622
+ statuses.push(
25623
+ ...await classifyIntegrations(r2.repo.name, {
25624
+ root: targetDir,
25625
+ home: cliHome(),
25626
+ which: defaultWhich,
25627
+ bin: (n4) => catalog.find((c5) => c5.name === n4)?.verify?.[0]
25628
+ })
25629
+ );
25630
+ }
25631
+ const pending = pendingIntegrations(statuses);
25632
+ if (!pending.length) return;
25633
+ if (interactive && !cli.yes) {
25634
+ const offer = await askOptionalIntegrations(pending, async (options) => {
25635
+ const picked = await multiselect({
25636
+ message: S.integrationsOffer,
25637
+ options,
25638
+ required: false
25639
+ });
25640
+ if (isCancel(picked)) {
25641
+ cancel(S.cancelled);
25642
+ return null;
25643
+ }
25644
+ return picked;
25645
+ });
25646
+ const selected = catalog.filter((c5) => offer.consented.includes(c5.name));
25647
+ const unrouted = offer.consented.filter((n4) => !catalog.some((c5) => c5.name === n4));
25648
+ for (const n4 of unrouted) log.warn(`${n4}: ${S.integrationNoInstallRoute}`);
25649
+ if (selected.length) {
25650
+ const more = await processBatch(selected, targetDir, opts, runLog.write, 0, selected.length);
25651
+ renderReport(more);
25652
+ }
25653
+ } else {
25654
+ note(
25655
+ pending.map(
25656
+ (s) => `${s.integration.name} \u2014 ${s.integration.reason}${catalog.some((c5) => c5.name === s.integration.name) ? "" : ` (${S.integrationNoInstallRoute})`}`
25657
+ ).join("\n"),
25658
+ S.integrationsTitle
25659
+ );
25660
+ }
25661
+ }
25188
25662
 
25189
25663
  // src/cmd/env.ts
25190
- import { copyFileSync as copyFileSync4, existsSync as existsSync34, readFileSync as readFileSync23, readdirSync as readdirSync17 } from "fs";
25664
+ import { copyFileSync as copyFileSync4, existsSync as existsSync35, readFileSync as readFileSync23, readdirSync as readdirSync17 } from "fs";
25191
25665
  import path48 from "path";
25192
25666
 
25193
25667
  // src/env/discover.ts
@@ -25195,7 +25669,7 @@ import { accessSync as accessSync3, constants as constants6 } from "fs";
25195
25669
  import os7 from "os";
25196
25670
 
25197
25671
  // src/env/paths.ts
25198
- import { accessSync as accessSync2, constants as constants5, existsSync as existsSync32 } from "fs";
25672
+ import { accessSync as accessSync2, constants as constants5, existsSync as existsSync33 } from "fs";
25199
25673
  import os6 from "os";
25200
25674
  import path46 from "path";
25201
25675
  function resolveDeps2(deps) {
@@ -25204,7 +25678,7 @@ function resolveDeps2(deps) {
25204
25678
  osHome: deps.osHome ?? os6.homedir(),
25205
25679
  cwd: deps.cwd ?? process.cwd(),
25206
25680
  tmpDir: deps.tmpDir ?? os6.tmpdir(),
25207
- exists: deps.exists ?? existsSync32,
25681
+ exists: deps.exists ?? existsSync33,
25208
25682
  writable: deps.writable ?? ((dir) => {
25209
25683
  try {
25210
25684
  accessSync2(dir, constants5.W_OK);
@@ -25726,7 +26200,7 @@ function envDoctor(repoDir, envKeys) {
25726
26200
 
25727
26201
  // src/envintel/migrate.ts
25728
26202
  import { randomBytes } from "crypto";
25729
- import { existsSync as existsSync33, mkdirSync as mkdirSync14, readdirSync as readdirSync16, readFileSync as readFileSync22 } from "fs";
26203
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, readdirSync as readdirSync16, readFileSync as readFileSync22 } from "fs";
25730
26204
  import path47 from "path";
25731
26205
  function timestamp2(d = /* @__PURE__ */ new Date()) {
25732
26206
  const p = (n4) => String(n4).padStart(2, "0");
@@ -25742,11 +26216,11 @@ function addValue(spec, exampleEmpty) {
25742
26216
  function planMigration(repoDir, targetSchema) {
25743
26217
  const schema = targetSchema ?? deriveSchema(repoDir);
25744
26218
  const envPath = path47.join(repoDir, ".env");
25745
- const env = existsSync33(envPath) ? parseEnv(readFileSync22(envPath, "utf8")) : null;
26219
+ const env = existsSync34(envPath) ? parseEnv(readFileSync22(envPath, "utf8")) : null;
25746
26220
  const present = new Set(env?.entries.map((e) => e.key) ?? []);
25747
26221
  const examplePath = path47.join(repoDir, ".env.example");
25748
26222
  const exampleEmpty = /* @__PURE__ */ new Set();
25749
- if (existsSync33(examplePath)) {
26223
+ if (existsSync34(examplePath)) {
25750
26224
  for (const e of parseEnv(readFileSync22(examplePath, "utf8")).entries) {
25751
26225
  if (e.value === "") exampleEmpty.add(e.key);
25752
26226
  }
@@ -25786,7 +26260,7 @@ function backupEnv(repoDir, content) {
25786
26260
  }
25787
26261
  function applyMigration(repoDir, plan, opts = {}) {
25788
26262
  const envPath = path47.join(repoDir, ".env");
25789
- const exists = existsSync33(envPath);
26263
+ const exists = existsSync34(envPath);
25790
26264
  const current = exists ? readFileSync22(envPath, "utf8") : "";
25791
26265
  const env = parseEnv(current);
25792
26266
  const present = new Set(env.entries.map((e) => e.key));
@@ -25818,14 +26292,14 @@ function applyMigration(repoDir, plan, opts = {}) {
25818
26292
  }
25819
26293
  function latestEnvBackup(repoDir) {
25820
26294
  const root = path47.join(artifactsRoot(repoDir), "backups");
25821
- if (!existsSync33(root)) return null;
26295
+ if (!existsSync34(root)) return null;
25822
26296
  const candidates = readdirSync16(root, { withFileTypes: true }).filter((d) => d.isDirectory() && /^env-\d{8}-\d{6}$/.test(d.name)).map((d) => d.name).sort();
25823
26297
  const last = candidates.at(-1);
25824
26298
  return last ? path47.join(root, last) : null;
25825
26299
  }
25826
26300
  function rollbackEnv(repoDir, backupDir) {
25827
26301
  const src = path47.join(backupDir, ".env");
25828
- if (!existsSync33(src)) return false;
26302
+ if (!existsSync34(src)) return false;
25829
26303
  writeFileAtomic(path47.join(repoDir, ".env"), readFileSync22(src, "utf8"));
25830
26304
  return true;
25831
26305
  }
@@ -25846,7 +26320,7 @@ function envCandidates(dir) {
25846
26320
  try {
25847
26321
  return readdirSync17(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => {
25848
26322
  const r2 = resolveInside(dir, name);
25849
- return r2 !== null && (existsSync34(path48.join(r2, ".env")) || existsSync34(path48.join(r2, ".env.example")));
26323
+ return r2 !== null && (existsSync35(path48.join(r2, ".env")) || existsSync35(path48.join(r2, ".env.example")));
25850
26324
  }).sort();
25851
26325
  } catch {
25852
26326
  return [];
@@ -25923,7 +26397,7 @@ async function runEnvCommand(cli, args) {
25923
26397
  envKeys = catalogFor(targetDir).find((r2) => r2.name === name)?.envKeys;
25924
26398
  } else {
25925
26399
  const cwd = process.cwd();
25926
- if (existsSync34(path48.join(cwd, ".env")) || existsSync34(path48.join(cwd, ".env.example"))) {
26400
+ if (existsSync35(path48.join(cwd, ".env")) || existsSync35(path48.join(cwd, ".env.example"))) {
25927
26401
  repoDir = cwd;
25928
26402
  }
25929
26403
  }
@@ -25975,7 +26449,7 @@ async function runEnvCommand(cli, args) {
25975
26449
  note(lines.join("\n"), `${S.envDoctorTitle} \u2014 ${repo}`);
25976
26450
  return res2.checks.some((c5) => c5.result === "fail") ? 1 : 0;
25977
26451
  }
25978
- if (!existsSync34(repoDir)) {
26452
+ if (!existsSync35(repoDir)) {
25979
26453
  log.error(`${repo}: ${S.statusNotInstalled}`);
25980
26454
  return 2;
25981
26455
  }
@@ -25991,7 +26465,7 @@ async function runEnvCommand(cli, args) {
25991
26465
  log.error(S.envWizardNonInteractive);
25992
26466
  return 2;
25993
26467
  }
25994
- const env = parseEnv(existsSync34(envPath) ? readFileSync23(envPath, "utf8") : "");
26468
+ const env = parseEnv(existsSync35(envPath) ? readFileSync23(envPath, "utf8") : "");
25995
26469
  const current = new Map(env.entries.map((e) => [e.key, e]));
25996
26470
  const updates = /* @__PURE__ */ new Map();
25997
26471
  for (const spec of schema.vars) {
@@ -26034,7 +26508,7 @@ async function runEnvCommand(cli, args) {
26034
26508
  }
26035
26509
  for (const [k, v] of updates) lines.push(formatEntry(k, v));
26036
26510
  const out = serializeEnv({ ...env, lines });
26037
- if (existsSync34(envPath)) copyFileSync4(envPath, `${envPath}.bak`);
26511
+ if (existsSync35(envPath)) copyFileSync4(envPath, `${envPath}.bak`);
26038
26512
  writeFileAtomic(envPath, out.endsWith("\n") ? out : `${out}
26039
26513
  `);
26040
26514
  log.success(`${icons.ok} ${S.envWizardDone} ${theme.dim(`(${envPath}.bak)`)}`);
@@ -26062,7 +26536,7 @@ async function runEnvCommand(cli, args) {
26062
26536
  return 0;
26063
26537
  }
26064
26538
  if (verb === "init") {
26065
- if (existsSync34(envPath)) {
26539
+ if (existsSync35(envPath)) {
26066
26540
  if (json) console.log(JSON.stringify({ command: "env init", repo, created: false }));
26067
26541
  else log.info(S.envInitExists);
26068
26542
  return 0;
@@ -26177,11 +26651,11 @@ async function runEnvCommand(cli, args) {
26177
26651
 
26178
26652
  // src/cmd/hosts.ts
26179
26653
  import path50 from "path";
26180
- import { existsSync as existsSync36, readdirSync as readdirSync18 } from "fs";
26654
+ import { existsSync as existsSync37, readdirSync as readdirSync18 } from "fs";
26181
26655
 
26182
26656
  // src/mcp/handshake.ts
26183
26657
  import { spawn as spawn2 } from "child_process";
26184
- import { existsSync as existsSync35 } from "fs";
26658
+ import { existsSync as existsSync36 } from "fs";
26185
26659
  import path49 from "path";
26186
26660
  import { createInterface as createInterface2 } from "readline";
26187
26661
  var PROTOCOL_VERSION = "2024-11-05";
@@ -26272,7 +26746,7 @@ function probeMcpServer(argv, opts = {}) {
26272
26746
  const stepMs = opts.timeoutMs ?? STEP_MS;
26273
26747
  const levels = [];
26274
26748
  if (path49.isAbsolute(argv[0])) {
26275
- if (!existsSync35(argv[0])) {
26749
+ if (!existsSync36(argv[0])) {
26276
26750
  resolve(err("bin-missing", `bin\xE1rio MCP ausente: ${argv[0]}`));
26277
26751
  return;
26278
26752
  }
@@ -26702,7 +27176,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26702
27176
  const candidates = names.length > 0 ? names : [
26703
27177
  .../* @__PURE__ */ new Set([
26704
27178
  ...Object.keys(loadForgeState(home).forges),
26705
- ...existsSync36(targetDir) ? readdirSync18(targetDir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeRepoName(d.name)).map((d) => d.name) : []
27179
+ ...existsSync37(targetDir) ? readdirSync18(targetDir, { withFileTypes: true }).filter((d) => d.isDirectory() && isSafeRepoName(d.name)).map((d) => d.name) : []
26706
27180
  ])
26707
27181
  ].sort();
26708
27182
  if (candidates.length === 0) {
@@ -26723,7 +27197,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26723
27197
  results.push({ forge: name, ok: false, detail: "sem mcp_command" });
26724
27198
  continue;
26725
27199
  }
26726
- const binPresent = path50.isAbsolute(argv[0]) ? existsSync36(argv[0]) : true;
27200
+ const binPresent = path50.isAbsolute(argv[0]) ? existsSync37(argv[0]) : true;
26727
27201
  const item = {
26728
27202
  forge: name,
26729
27203
  forge_id: forge.forge_id,
@@ -26771,7 +27245,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26771
27245
  }
26772
27246
 
26773
27247
  // src/discovery.ts
26774
- import { existsSync as existsSync37, readdirSync as readdirSync19, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
27248
+ import { existsSync as existsSync38, readdirSync as readdirSync19, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
26775
27249
  import os8 from "os";
26776
27250
  import path51 from "path";
26777
27251
  var DEFAULT_LIMITS = {
@@ -27061,10 +27535,10 @@ function registryState(entry2) {
27061
27535
  function discoverForge(name, dir, state) {
27062
27536
  const sources = [];
27063
27537
  const git = !!dir && hasGitRepo(dir);
27064
- const manifest = dir && existsSync37(dir) ? readForgeManifest(dir) : null;
27538
+ const manifest = dir && existsSync38(dir) ? readForgeManifest(dir) : null;
27065
27539
  const entry2 = state?.repos[name];
27066
27540
  if (entry2 && entry2.phase !== "not-installed") {
27067
- const dirExists = !!dir && existsSync37(dir);
27541
+ const dirExists = !!dir && existsSync38(dir);
27068
27542
  sources.push({
27069
27543
  origin: "manifest",
27070
27544
  // registro diz instalada mas o checkout sumiu → evidência falhando
@@ -27124,7 +27598,7 @@ async function discoverEnvironment2(targetDir, opts) {
27124
27598
  const dir = resolveInside(root, dirName);
27125
27599
  if (!dir) continue;
27126
27600
  const git = hasGitRepo(dir);
27127
- const manifest = existsSync37(path51.join(dir, "forge.json"));
27601
+ const manifest = existsSync38(path51.join(dir, "forge.json"));
27128
27602
  if (!git && !manifest) continue;
27129
27603
  presence.push({ name: dirName, path: dirName, git, manifest });
27130
27604
  if (!catalogNames.has(dirName) && manifest) {
@@ -27793,7 +28267,7 @@ async function answerRepair(targetDir, repos, opts = {}) {
27793
28267
  }
27794
28268
 
27795
28269
  // src/intel/context.ts
27796
- import { existsSync as existsSync38, readdirSync as readdirSync20, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
28270
+ import { existsSync as existsSync39, readdirSync as readdirSync20, readFileSync as readFileSync25, statSync as statSync9 } from "fs";
27797
28271
  import path52 from "path";
27798
28272
 
27799
28273
  // src/planner.ts
@@ -27911,7 +28385,7 @@ function safeConfig(targetDir) {
27911
28385
  function readManifestObject(repoDir) {
27912
28386
  const file = path52.join(repoDir, "forge.json");
27913
28387
  try {
27914
- if (!existsSync38(file) || statSync9(file).size > MAX_MANIFEST_BYTES) return null;
28388
+ if (!existsSync39(file) || statSync9(file).size > MAX_MANIFEST_BYTES) return null;
27915
28389
  const raw = JSON.parse(readFileSync25(file, "utf8"));
27916
28390
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
27917
28391
  } catch {
@@ -28081,7 +28555,7 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
28081
28555
  }
28082
28556
 
28083
28557
  // src/intel/predict.ts
28084
- import { existsSync as existsSync39, readFileSync as readFileSync26, statSync as statSync10 } from "fs";
28558
+ import { existsSync as existsSync40, readFileSync as readFileSync26, statSync as statSync10 } from "fs";
28085
28559
  import path53 from "path";
28086
28560
  var MAX_MANIFEST_BYTES2 = 64 * 1024;
28087
28561
  var SEMVERISH = /^v?\d+\.\d+\.\d+/;
@@ -28094,7 +28568,7 @@ var mk2 = (id, kind, subject, certainty, version, evidence, explanation) => ({ i
28094
28568
  function readManifest(repoDir) {
28095
28569
  const file = path53.join(repoDir, "forge.json");
28096
28570
  try {
28097
- if (!existsSync39(file) || statSync10(file).size > MAX_MANIFEST_BYTES2) return null;
28571
+ if (!existsSync40(file) || statSync10(file).size > MAX_MANIFEST_BYTES2) return null;
28098
28572
  const raw = JSON.parse(readFileSync26(file, "utf8"));
28099
28573
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
28100
28574
  } catch {
@@ -28411,7 +28885,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
28411
28885
  const allClaims = [];
28412
28886
  for (const repo of sorted) {
28413
28887
  const dir = resolveInside(targetDir, repo.name);
28414
- if (!dir || !existsSync39(dir)) continue;
28888
+ if (!dir || !existsSync40(dir)) continue;
28415
28889
  const m3 = readManifest(dir);
28416
28890
  manifests.set(repo.name, m3);
28417
28891
  if (m3) out.push(...schemaChange(repo.name, m3));
@@ -28423,7 +28897,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
28423
28897
  out.push(...envVarFindings("<workspace>", targetDir));
28424
28898
  for (const repo of sorted) {
28425
28899
  const dir = resolveInside(targetDir, repo.name);
28426
- if (!dir || !existsSync39(dir)) continue;
28900
+ if (!dir || !existsSync40(dir)) continue;
28427
28901
  out.push(...envVarFindings(repo.name, dir, repo.envKeys));
28428
28902
  }
28429
28903
  out.push(...installConflict(allClaims));
@@ -28436,7 +28910,7 @@ import path55 from "path";
28436
28910
 
28437
28911
  // src/ai/cache.ts
28438
28912
  import { createHash as createHash7 } from "crypto";
28439
- import { existsSync as existsSync40, mkdirSync as mkdirSync15, readFileSync as readFileSync27, readdirSync as readdirSync21, rmSync as rmSync9 } from "fs";
28913
+ import { existsSync as existsSync41, mkdirSync as mkdirSync15, readFileSync as readFileSync27, readdirSync as readdirSync21, rmSync as rmSync9 } from "fs";
28440
28914
  import path54 from "path";
28441
28915
 
28442
28916
  // src/ai/types.ts
@@ -28540,7 +29014,7 @@ function entryFile(rootDir, key) {
28540
29014
  function aiCacheGet(rootDir, key, opts) {
28541
29015
  if (opts.ttlMs <= 0) return null;
28542
29016
  const file = entryFile(rootDir, key);
28543
- if (!file || !existsSync40(file)) return null;
29017
+ if (!file || !existsSync41(file)) return null;
28544
29018
  try {
28545
29019
  const entry2 = JSON.parse(readFileSync27(file, "utf8"));
28546
29020
  const now = opts.now ?? Date.now();
@@ -28740,7 +29214,7 @@ async function runAiBridge(req, cfg, deps) {
28740
29214
  }
28741
29215
 
28742
29216
  // src/portability/bundle.ts
28743
- import { existsSync as existsSync41, mkdirSync as mkdirSync17, readFileSync as readFileSync28 } from "fs";
29217
+ import { existsSync as existsSync42, mkdirSync as mkdirSync17, readFileSync as readFileSync28 } from "fs";
28744
29218
  import path56 from "path";
28745
29219
 
28746
29220
  // src/portability/manifest.ts
@@ -28852,7 +29326,7 @@ function collectPins(targetDir, repos) {
28852
29326
  const pins = /* @__PURE__ */ new Map();
28853
29327
  for (const repo of repos) {
28854
29328
  const repoDir = path56.join(targetDir, repo.name);
28855
- const version = existsSync41(repoDir) ? pinVersion(repoDir) : void 0;
29329
+ const version = existsSync42(repoDir) ? pinVersion(repoDir) : void 0;
28856
29330
  const head = state?.repos[repo.name]?.head;
28857
29331
  const ref = state?.pins?.[repo.name] ?? head;
28858
29332
  pins.set(repo.name, {
@@ -28879,7 +29353,7 @@ async function exportBundle(targetDir, repos, dest, opts) {
28879
29353
  const schemaByRepo = /* @__PURE__ */ new Map();
28880
29354
  for (const repo of repos) {
28881
29355
  const repoDir = path56.join(targetDir, repo.name);
28882
- const schema = existsSync41(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
29356
+ const schema = existsSync42(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
28883
29357
  schemaByRepo.set(repo.name, schema);
28884
29358
  for (const v of schema.vars) {
28885
29359
  if (v.secret && whitelist.includes(v.key)) refused.add(v.key);
@@ -28921,7 +29395,7 @@ async function exportBundle(targetDir, repos, dest, opts) {
28921
29395
  const values = {};
28922
29396
  for (const repo of repos) {
28923
29397
  const envFile = path56.join(targetDir, repo.name, ".env");
28924
- if (!existsSync41(envFile)) continue;
29398
+ if (!existsSync42(envFile)) continue;
28925
29399
  const entries = parseEnv(readFileSync28(envFile, "utf8")).entries;
28926
29400
  const found = {};
28927
29401
  for (const key of whitelist) {
@@ -29011,7 +29485,7 @@ function importBundle(src, opts) {
29011
29485
  }
29012
29486
 
29013
29487
  // src/intel/learning.ts
29014
- import { existsSync as existsSync42, mkdirSync as mkdirSync18, readFileSync as readFileSync29 } from "fs";
29488
+ import { existsSync as existsSync43, mkdirSync as mkdirSync18, readFileSync as readFileSync29 } from "fs";
29015
29489
  import path57 from "path";
29016
29490
  var LEARNING_SCHEMA = "forge-cli/LearningStore/v1";
29017
29491
  function learningFile(targetDir) {
@@ -29019,7 +29493,7 @@ function learningFile(targetDir) {
29019
29493
  }
29020
29494
  function readStore(targetDir) {
29021
29495
  const file = learningFile(targetDir);
29022
- if (!existsSync42(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
29496
+ if (!existsSync43(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
29023
29497
  try {
29024
29498
  const parsed = JSON.parse(readFileSync29(file, "utf8"));
29025
29499
  if (parsed?.schema !== LEARNING_SCHEMA || !Array.isArray(parsed.observations)) {
@@ -29279,7 +29753,7 @@ async function runLearningCommand(cli) {
29279
29753
  }
29280
29754
 
29281
29755
  // src/wizard.ts
29282
- import { existsSync as existsSync43 } from "fs";
29756
+ import { existsSync as existsSync44 } from "fs";
29283
29757
  import path58 from "path";
29284
29758
  var STEP_LABELS = [
29285
29759
  S.wizardStepDetect,
@@ -29320,7 +29794,7 @@ function renderOperationPlan(plan) {
29320
29794
  }
29321
29795
  function planActionsFor(repo, targetDir, steps) {
29322
29796
  const repoDir = path58.join(targetDir, repo.name);
29323
- const cloned = existsSync43(repoDir) && hasGitRepo(repoDir);
29797
+ const cloned = existsSync44(repoDir) && hasGitRepo(repoDir);
29324
29798
  const actions = [
29325
29799
  {
29326
29800
  kind: "clone",