theforge-cli 0.2.4 → 0.2.6

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:
@@ -15272,6 +15288,8 @@ async function checkEnvironment() {
15272
15288
  return env;
15273
15289
  }
15274
15290
  function emitJson(data, exitCode = 0) {
15291
+ const handle = process.stdout._handle;
15292
+ handle?.setBlocking?.(true);
15275
15293
  process.stdout.write(`${JSON.stringify(data, null, 2)}
15276
15294
  `);
15277
15295
  process.exit(exitCode);
@@ -19705,7 +19723,7 @@ function errMsg(error) {
19705
19723
  }
19706
19724
 
19707
19725
  // src/doctor2/domains.ts
19708
- 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";
19709
19727
  import os4 from "os";
19710
19728
  import path34 from "path";
19711
19729
 
@@ -20186,6 +20204,303 @@ function analyzeBumps(current, candidates) {
20186
20204
  }).sort((a3, b) => a3.to.localeCompare(b.to, void 0, { numeric: true }));
20187
20205
  }
20188
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
+
20189
20504
  // src/doctor2/domains.ts
20190
20505
  function systemChecks(ctx) {
20191
20506
  const checks = [];
@@ -20277,7 +20592,7 @@ function systemChecks(ctx) {
20277
20592
  const dirEvidence = [];
20278
20593
  let dirFailed = 0;
20279
20594
  for (const { label, dir: d } of required) {
20280
- if (!existsSync24(d)) {
20595
+ if (!existsSync25(d)) {
20281
20596
  dirFailed += 1;
20282
20597
  dirEvidence.push(`${label}=${d}: ausente`);
20283
20598
  continue;
@@ -20348,7 +20663,7 @@ function backupScan(ctx) {
20348
20663
  scan(
20349
20664
  path34.join(ctx.home, ".forge-cli", "backups"),
20350
20665
  "host-backup",
20351
- (full) => existsSync24(path34.join(full, "manifest.json"))
20666
+ (full) => existsSync25(path34.join(full, "manifest.json"))
20352
20667
  );
20353
20668
  for (const repo of ctx.catalog) {
20354
20669
  const dir = resolveInside(ctx.root, repo.name);
@@ -20361,7 +20676,7 @@ function backupScan(ctx) {
20361
20676
  function installationChecks(ctx) {
20362
20677
  const checks = [];
20363
20678
  const forgeDir = artifactsRoot(ctx.root);
20364
- const forgeDirPresent = existsSync24(forgeDir);
20679
+ const forgeDirPresent = existsSync25(forgeDir);
20365
20680
  const stateFile = stateFileFor(ctx.root);
20366
20681
  checks.push(
20367
20682
  mkCheck(
@@ -20374,7 +20689,7 @@ function installationChecks(ctx) {
20374
20689
  diagnosis: "diret\xF3rio de estado .forge-cli presente",
20375
20690
  evidence: [
20376
20691
  `${forgeDir} presente`,
20377
- existsSync24(stateFile) ? "state.json presente" : "state.json ausente"
20692
+ existsSync25(stateFile) ? "state.json presente" : "state.json ausente"
20378
20693
  ],
20379
20694
  reversible: null
20380
20695
  } : {
@@ -20390,7 +20705,7 @@ function installationChecks(ctx) {
20390
20705
  )
20391
20706
  );
20392
20707
  const forgeJson = path34.join(ctx.root, "forge.json");
20393
- if (!existsSync24(forgeJson)) {
20708
+ if (!existsSync25(forgeJson)) {
20394
20709
  checks.push(
20395
20710
  mkCheck(ctx.now, {
20396
20711
  id: "installation.forge-json.present",
@@ -20440,9 +20755,9 @@ function installationChecks(ctx) {
20440
20755
  }
20441
20756
  }
20442
20757
  const lockFile = path34.join(forgeDir, "env.lock.json");
20443
- const lockParsed = existsSync24(lockFile) ? readJson(lockFile) : null;
20758
+ const lockParsed = existsSync25(lockFile) ? readJson(lockFile) : null;
20444
20759
  const lockObj = lockParsed?.ok && lockParsed.value !== null && typeof lockParsed.value === "object" ? lockParsed.value : null;
20445
- if (!forgeDirPresent || !existsSync24(lockFile)) {
20760
+ if (!forgeDirPresent || !existsSync25(lockFile)) {
20446
20761
  checks.push(
20447
20762
  mkCheck(ctx.now, {
20448
20763
  id: "installation.lockfile.present",
@@ -20487,7 +20802,7 @@ function installationChecks(ctx) {
20487
20802
  );
20488
20803
  }
20489
20804
  const trackedForges = lockObj ? Object.keys(lockObj.forges ?? {}) : [];
20490
- if (!existsSync24(lockFile) || !lockObj || !trackedForges.length) {
20805
+ if (!existsSync25(lockFile) || !lockObj || !trackedForges.length) {
20491
20806
  checks.push(
20492
20807
  mkCheck(ctx.now, {
20493
20808
  id: "installation.lockfile.drift",
@@ -20496,7 +20811,7 @@ function installationChecks(ctx) {
20496
20811
  severity: "info",
20497
20812
  diagnosis: "deriva do lockfile n\xE3o verific\xE1vel",
20498
20813
  evidence: [
20499
- !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"
20500
20815
  ],
20501
20816
  reversible: null
20502
20817
  })
@@ -20525,10 +20840,10 @@ function installationChecks(ctx) {
20525
20840
  mkCheck(ctx.now, {
20526
20841
  id: "installation.ledger.readable",
20527
20842
  domain: "installation",
20528
- result: existsSync24(ledgerDir) ? "warn" : "skip",
20529
- severity: existsSync24(ledgerDir) ? "medium" : "info",
20530
- 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)",
20531
- 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"}`],
20532
20847
  reversible: null
20533
20848
  })
20534
20849
  );
@@ -20610,11 +20925,11 @@ function installationChecks(ctx) {
20610
20925
  } else {
20611
20926
  const lines = localEntries.map(([name, r2]) => {
20612
20927
  const src = typeof r2.source_dir === "string" ? r2.source_dir : "?";
20613
- const srcOk = typeof r2.source_dir === "string" && existsSync24(r2.source_dir);
20928
+ const srcOk = typeof r2.source_dir === "string" && existsSync25(r2.source_dir);
20614
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)"}`;
20615
20930
  });
20616
20931
  const missing = localEntries.filter(
20617
- ([, r2]) => typeof r2.source_dir !== "string" || !existsSync24(r2.source_dir)
20932
+ ([, r2]) => typeof r2.source_dir !== "string" || !existsSync25(r2.source_dir)
20618
20933
  );
20619
20934
  checks.push(
20620
20935
  mkCheck(ctx.now, {
@@ -20688,6 +21003,58 @@ function installationChecks(ctx) {
20688
21003
  }
20689
21004
  )
20690
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
+ }
20691
21058
  return checks;
20692
21059
  }
20693
21060
  function environmentChecks(ctx) {
@@ -20695,7 +21062,7 @@ function environmentChecks(ctx) {
20695
21062
  const targets = [{ label: "<workspace>", dir: ctx.root }];
20696
21063
  for (const repo of ctx.catalog) {
20697
21064
  const dir = resolveInside(ctx.root, repo.name);
20698
- 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 });
20699
21066
  }
20700
21067
  const inspected = targets.map((t2) => ({
20701
21068
  ...t2,
@@ -20917,7 +21284,7 @@ function operationsChecks(ctx) {
20917
21284
  checks.push(
20918
21285
  mkCheck(
20919
21286
  ctx.now,
20920
- !existsSync24(reportsDir) ? {
21287
+ !existsSync25(reportsDir) ? {
20921
21288
  id: "operations.receipts.recent",
20922
21289
  domain: "operations",
20923
21290
  result: "skip",
@@ -20953,7 +21320,7 @@ function operationsChecks(ctx) {
20953
21320
  if (entry2.status === "error") failures.set(name, (failures.get(name) ?? 0) + 1);
20954
21321
  }
20955
21322
  const recurring = [...failures.entries()].filter(([, n4]) => n4 >= 2);
20956
- const scannedAnything = existsSync24(reportsDir) || ctx.state !== null;
21323
+ const scannedAnything = existsSync25(reportsDir) || ctx.state !== null;
20957
21324
  if (!scannedAnything) {
20958
21325
  checks.push(
20959
21326
  mkCheck(ctx.now, {
@@ -21229,7 +21596,7 @@ async function runDoctor2(root, opts = {}) {
21229
21596
  }
21230
21597
 
21231
21598
  // src/heal/fixes.ts
21232
- 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";
21233
21600
  import path36 from "path";
21234
21601
  var rel = (root, abs) => path36.relative(root, abs) || ".";
21235
21602
  function stampOf(iso) {
@@ -21248,7 +21615,7 @@ var forgeDirRepair = {
21248
21615
  return ["", ...FORGE_DIR_SUBDIRS].map((sub) => {
21249
21616
  const dir = sub ? path36.join(base, sub) : base;
21250
21617
  return {
21251
- description: `create directory ${rel(ctx.root, dir)}${existsSync25(dir) ? " (already present)" : ""}`,
21618
+ description: `create directory ${rel(ctx.root, dir)}${existsSync26(dir) ? " (already present)" : ""}`,
21252
21619
  touches: [rel(ctx.root, dir)]
21253
21620
  };
21254
21621
  });
@@ -21259,7 +21626,7 @@ var forgeDirRepair = {
21259
21626
  const created = [];
21260
21627
  for (const sub of ["", ...FORGE_DIR_SUBDIRS]) {
21261
21628
  const dir = sub ? path36.join(base, sub) : base;
21262
- if (existsSync25(dir)) continue;
21629
+ if (existsSync26(dir)) continue;
21263
21630
  mkdirSync8(dir, { recursive: true });
21264
21631
  created.push(rel(ctx.root, dir));
21265
21632
  }
@@ -21283,7 +21650,7 @@ function envPathOf(root) {
21283
21650
  }
21284
21651
  function presentKeys(root) {
21285
21652
  const file = envPathOf(root);
21286
- if (!existsSync25(file)) return /* @__PURE__ */ new Set();
21653
+ if (!existsSync26(file)) return /* @__PURE__ */ new Set();
21287
21654
  return new Set(parseEnv(readFileSync19(file, "utf8")).entries.map((e) => e.key));
21288
21655
  }
21289
21656
  function missingEnvKeys(root, check) {
@@ -21319,7 +21686,7 @@ var envMissingKeyRepair = {
21319
21686
  const root = ctx.root;
21320
21687
  const toAppend = keysToAppend(root, ctx.check);
21321
21688
  const steps = [];
21322
- if (existsSync25(envPathOf(root))) {
21689
+ if (existsSync26(envPathOf(root))) {
21323
21690
  steps.push({
21324
21691
  description: `backup .env to ${rel(root, path36.join(artifactsRoot(root), "backups"))}/env-<ts>/.env`,
21325
21692
  touches: [".env", `${rel(root, artifactsRoot(root))}/backups/`]
@@ -21338,7 +21705,7 @@ var envMissingKeyRepair = {
21338
21705
  const root = ctx.root;
21339
21706
  if (ctx.dryRun) return "dry-run \u2014 not applied";
21340
21707
  const file = envPathOf(root);
21341
- const exists = existsSync25(file);
21708
+ const exists = existsSync26(file);
21342
21709
  const current = exists ? readFileSync19(file, "utf8") : "";
21343
21710
  const env = parseEnv(current);
21344
21711
  const present = new Set(env.entries.map((e) => e.key));
@@ -21356,7 +21723,7 @@ var envMissingKeyRepair = {
21356
21723
  },
21357
21724
  async verify(ctx) {
21358
21725
  const file = envPathOf(ctx.root);
21359
- if (!existsSync25(file)) return false;
21726
+ if (!existsSync26(file)) return false;
21360
21727
  const present = new Set(parseEnv(readFileSync19(file, "utf8")).entries.map((e) => e.key));
21361
21728
  return missingEnvKeys(ctx.root, ctx.check).every((k) => present.has(k));
21362
21729
  }
@@ -21394,7 +21761,7 @@ var ENV_BACKUP_RE = /^env-\d{8}-\d{6}$/;
21394
21761
  var REPO_BACKUP_RE = /^([a-z0-9][a-z0-9._-]*)-\d{10,}$/i;
21395
21762
  function listBackups(root) {
21396
21763
  const base = path36.join(artifactsRoot(root), "backups");
21397
- if (!existsSync25(base)) return [];
21764
+ if (!existsSync26(base)) return [];
21398
21765
  const out = [];
21399
21766
  for (const entry2 of readdirSync12(base, { withFileTypes: true })) {
21400
21767
  if (!entry2.isDirectory()) continue;
@@ -21423,7 +21790,7 @@ function pickBackupFile(ctx) {
21423
21790
  if (inside.startsWith("..") || path36.isAbsolute(inside)) {
21424
21791
  throw new Error(`selected backup escapes .forge-cli/backups: ${ctx.backup}`);
21425
21792
  }
21426
- if (!existsSync25(abs) || !statSync5(abs).isFile()) {
21793
+ if (!existsSync26(abs) || !statSync5(abs).isFile()) {
21427
21794
  throw new Error(`selected backup not found: ${ctx.backup}`);
21428
21795
  }
21429
21796
  const dirName = inside.split(path36.sep)[0] ?? "";
@@ -21491,7 +21858,7 @@ var backupRestoreRepair = {
21491
21858
  try {
21492
21859
  const picked = pickBackupFile(ctx);
21493
21860
  const dest = path36.resolve(ctx.root, picked.dest);
21494
- return existsSync25(dest) && readFileSync19(dest, "utf8") === readFileSync19(picked.src, "utf8");
21861
+ return existsSync26(dest) && readFileSync19(dest, "utf8") === readFileSync19(picked.src, "utf8");
21495
21862
  } catch {
21496
21863
  return false;
21497
21864
  }
@@ -21746,7 +22113,7 @@ async function runDoctor2Command(cli) {
21746
22113
 
21747
22114
  // src/cmd/registry.ts
21748
22115
  import path38 from "path";
21749
- import { existsSync as existsSync26, mkdirSync as mkdirSync10 } from "fs";
22116
+ import { existsSync as existsSync27, mkdirSync as mkdirSync10 } from "fs";
21750
22117
 
21751
22118
  // src/registry/catalog.ts
21752
22119
  var REGISTRY_PACKAGES = [
@@ -22160,7 +22527,14 @@ async function runRegistryInstall(cli, names, deps = {}) {
22160
22527
  ...receipt.native ? { native: receipt.native } : {}
22161
22528
  });
22162
22529
  }
22163
- 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
+ };
22164
22538
  reportTo2(doc);
22165
22539
  if (cli.reportPath !== void 0) {
22166
22540
  log.info(`${S.reportSaved}: ${theme.dim(path38.resolve(cli.reportPath))}`);
@@ -22182,8 +22556,62 @@ async function runRegistryInstall(cli, names, deps = {}) {
22182
22556
  "Launchers em ~/.forge-cli/bin (no PATH do forge-cli); sem shim, invoque o caminho da venv"
22183
22557
  );
22184
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
+ }
22185
22586
  return results;
22186
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
+ }
22187
22615
  async function runRegistryLock(cli, names, deps = {}) {
22188
22616
  const home = deps.home ?? cliHome();
22189
22617
  const quiet2 = registryQuiet(cli);
@@ -22304,7 +22732,7 @@ function runRegistryList(cli, deps = {}) {
22304
22732
  }
22305
22733
  const rows = REGISTRY_PACKAGES.filter((e) => !wanted || wanted.has(e.name)).map((e) => {
22306
22734
  const inst = state.forges[e.name];
22307
- const binOk = inst ? typeof inst.bin === "string" && existsSync26(inst.bin) : null;
22735
+ const binOk = inst ? typeof inst.bin === "string" && existsSync27(inst.bin) : null;
22308
22736
  const consistency = installConsistency(home, e.name);
22309
22737
  const relevant = consistency.status !== "not-installed";
22310
22738
  return {
@@ -22333,7 +22761,7 @@ function runRegistryList(cli, deps = {}) {
22333
22761
  installed: inst.version ?? null,
22334
22762
  method: inst.method ?? null,
22335
22763
  verifyOk: inst.verify?.ok ?? null,
22336
- binPresent: typeof inst.bin === "string" ? existsSync26(inst.bin) : null,
22764
+ binPresent: typeof inst.bin === "string" ? existsSync27(inst.bin) : null,
22337
22765
  bin: inst.bin ?? null,
22338
22766
  consistent: null,
22339
22767
  consistency: null,
@@ -23101,7 +23529,7 @@ async function runRegistryPin(cli, names, pin, deps = {}) {
23101
23529
 
23102
23530
  // src/source/detect.ts
23103
23531
  import { createHash as createHash5 } from "crypto";
23104
- 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";
23105
23533
  import path39 from "path";
23106
23534
  var MANIFEST_MAX_BYTES = 64 * 1024;
23107
23535
  var CHECKSUM_MAX_ENTRIES = 1e4;
@@ -23124,7 +23552,7 @@ var PROJECT_MARKERS = [
23124
23552
  var CHECKSUM_FILES = ["SHA256SUMS", "checksums.sha256", ".forge-checksums.json"];
23125
23553
  function readManifestStrict(dir) {
23126
23554
  const file = path39.join(dir, "forge.json");
23127
- 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" };
23128
23556
  let raw;
23129
23557
  try {
23130
23558
  if (!statSync6(file).isFile() || statSync6(file).size > MANIFEST_MAX_BYTES) {
@@ -23199,7 +23627,7 @@ function scanStructure(dir) {
23199
23627
  return false;
23200
23628
  }
23201
23629
  });
23202
- const markers = PROJECT_MARKERS.filter((f) => existsSync27(path39.join(dir, f)));
23630
+ const markers = PROJECT_MARKERS.filter((f) => existsSync28(path39.join(dir, f)));
23203
23631
  return { setup_scripts: [...setup], project_markers: [...markers] };
23204
23632
  }
23205
23633
  function verifyChecksums(dir, reasons) {
@@ -23421,7 +23849,7 @@ function detectSource(input, cwd = process.cwd()) {
23421
23849
  const structure = scanStructure(dir);
23422
23850
  const expected = structure.setup_scripts.length > 0 && structure.project_markers.length > 0;
23423
23851
  if (gitKind === null) {
23424
- if (!existsSync27(path39.join(dir, "forge.json"))) {
23852
+ if (!existsSync28(path39.join(dir, "forge.json"))) {
23425
23853
  reasons.push("forge.json=ausente");
23426
23854
  return verdict(
23427
23855
  "UNKNOWN_DIRECTORY",
@@ -23504,7 +23932,7 @@ function looksLikePathArg(arg, cwd = process.cwd()) {
23504
23932
  import path41 from "path";
23505
23933
 
23506
23934
  // src/source/install.ts
23507
- 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";
23508
23936
  import { copyFile as copyFile3 } from "fs/promises";
23509
23937
  import path40 from "path";
23510
23938
  var fail = (failure, detection) => ({
@@ -23592,7 +24020,7 @@ function selectSetup(det, platform2, python, winShells) {
23592
24020
  if (script) return script;
23593
24021
  const bootstrap = path40.join("scripts", "forge_bootstrap.py");
23594
24022
  const argv = localPythonOf(python)?.argv;
23595
- if (argv?.length && existsSync28(path40.join(det.path, bootstrap))) {
24023
+ if (argv?.length && existsSync29(path40.join(det.path, bootstrap))) {
23596
24024
  return {
23597
24025
  file: bootstrap,
23598
24026
  command: argv[0],
@@ -23618,14 +24046,14 @@ function pythonFloorFailure(det, python) {
23618
24046
  }
23619
24047
  async function provisionEnvironment(dir, forgeId) {
23620
24048
  const envKeys = FORGE_REPOS.find((r2) => r2.name === forgeId)?.envKeys;
23621
- 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"))) {
23622
24050
  await ensureEnvFile(dir, envKeys);
23623
24051
  }
23624
24052
  await copyConfigExamples(dir);
23625
24053
  }
23626
24054
  function sourceVenv(det, platform2) {
23627
24055
  const venv = path40.join(det.path, ".venv");
23628
- return existsSync28(venvBin(venv, platform2)) ? venv : void 0;
24056
+ return existsSync29(venvBin(venv, platform2)) ? venv : void 0;
23629
24057
  }
23630
24058
  async function persistLocalReceipt(home, receipt, opts) {
23631
24059
  const envelope = resolveInside(forgesRoot(home), receipt.forge);
@@ -23650,7 +24078,7 @@ function buildReceipt(det, deps, script, managed, sourceFiles, prior) {
23650
24078
  const venv = sourceVenv(det, platform2);
23651
24079
  const cliName = manifest?.cli;
23652
24080
  const binCandidate = venv && cliName ? path40.join(venvBin(venv, platform2), cliName) : void 0;
23653
- const bin = binCandidate && existsSync28(binCandidate) ? binCandidate : cliName;
24081
+ const bin = binCandidate && existsSync29(binCandidate) ? binCandidate : cliName;
23654
24082
  const py = localPythonOf(deps.python);
23655
24083
  return {
23656
24084
  forge: det.forge_id,
@@ -23805,7 +24233,7 @@ async function localInstall(dir, deps) {
23805
24233
  await runVerify(receipt, det, deps, run);
23806
24234
  try {
23807
24235
  await persistLocalReceipt(deps.home, receipt, deps.lifecycleLock);
23808
- if (receipt.bin && existsSync28(receipt.bin)) {
24236
+ if (receipt.bin && existsSync29(receipt.bin)) {
23809
24237
  try {
23810
24238
  receipt.shim = emitForgeShim(deps.home, receipt.bin, platform2);
23811
24239
  } catch (shimError) {
@@ -23855,7 +24283,7 @@ async function localUpdate(dir, deps) {
23855
24283
  if (gate) return fail(gate, det);
23856
24284
  const targetDir = prior.source_dir;
23857
24285
  if (path40.resolve(targetDir) !== det.path) {
23858
- if (!existsSync28(targetDir)) {
24286
+ if (!existsSync29(targetDir)) {
23859
24287
  return fail(
23860
24288
  {
23861
24289
  code: "FORGE-SOURCE-NOT-FOUND",
@@ -23877,7 +24305,7 @@ async function localUpdate(dir, deps) {
23877
24305
  for (const rel2 of prior.source_files) {
23878
24306
  if (keep.has(rel2)) continue;
23879
24307
  const target = safeJoinInside(targetDir, rel2);
23880
- if (target && existsSync28(target)) rmSync7(target, { force: true });
24308
+ if (target && existsSync29(target)) rmSync7(target, { force: true });
23881
24309
  }
23882
24310
  }
23883
24311
  return runUpdateAt(targetDir, prior, deps, run);
@@ -23920,7 +24348,7 @@ async function runUpdateAt(dir, prior, deps, run) {
23920
24348
  }
23921
24349
  const managed = [
23922
24350
  .../* @__PURE__ */ new Set([
23923
- ...prior.managed_artifacts.filter((a3) => existsSync28(path40.join(det.path, a3))),
24351
+ ...prior.managed_artifacts.filter((a3) => existsSync29(path40.join(det.path, a3))),
23924
24352
  ...[...topLevelEntries(det.path)].filter((e) => !before.has(e))
23925
24353
  ])
23926
24354
  ].sort();
@@ -23972,7 +24400,7 @@ async function localRepair(name, deps) {
23972
24400
  });
23973
24401
  }
23974
24402
  const dir = prior.source_dir;
23975
- if (!dir || !existsSync28(dir)) {
24403
+ if (!dir || !existsSync29(dir)) {
23976
24404
  return fail({
23977
24405
  code: "FORGE-SOURCE-NOT-FOUND",
23978
24406
  cause: `${name}: fonte local ausente (${dir ?? "desconhecida"})`,
@@ -23987,7 +24415,7 @@ async function localRepair(name, deps) {
23987
24415
  if (floorFailure) return fail(floorFailure, det);
23988
24416
  for (const rel2 of prior.managed_artifacts) {
23989
24417
  const target = safeJoinInside(dir, rel2);
23990
- if (target && existsSync28(target)) rmSync7(target, { recursive: true, force: true });
24418
+ if (target && existsSync29(target)) rmSync7(target, { recursive: true, force: true });
23991
24419
  }
23992
24420
  const before = topLevelEntries(dir);
23993
24421
  const envFailure = await provisionOrRefuse(dir, det.forge_id);
@@ -24022,7 +24450,7 @@ async function localRepair(name, deps) {
24022
24450
  }
24023
24451
  const managed = [
24024
24452
  .../* @__PURE__ */ new Set([
24025
- ...prior.managed_artifacts.filter((a3) => existsSync28(path40.join(dir, a3))),
24453
+ ...prior.managed_artifacts.filter((a3) => existsSync29(path40.join(dir, a3))),
24026
24454
  ...[...topLevelEntries(dir)].filter((e) => !before.has(e))
24027
24455
  ])
24028
24456
  ].sort();
@@ -24078,7 +24506,7 @@ async function localUninstall(name, deps) {
24078
24506
  const dir = prior.source_dir;
24079
24507
  const removed = [];
24080
24508
  let backup;
24081
- if (dir && existsSync28(dir)) {
24509
+ if (dir && existsSync29(dir)) {
24082
24510
  const filesToBackup = prior.managed_artifacts.filter((rel2) => {
24083
24511
  const target = safeJoinInside(dir, rel2);
24084
24512
  try {
@@ -24103,7 +24531,7 @@ async function localUninstall(name, deps) {
24103
24531
  }
24104
24532
  for (const rel2 of prior.managed_artifacts) {
24105
24533
  const target = safeJoinInside(dir, rel2);
24106
- if (target && existsSync28(target)) {
24534
+ if (target && existsSync29(target)) {
24107
24535
  rmSync7(target, { recursive: true, force: true });
24108
24536
  removed.push(rel2);
24109
24537
  }
@@ -24116,7 +24544,7 @@ async function localUninstall(name, deps) {
24116
24544
  try {
24117
24545
  removeForgeShim(deps.home, prior.shim ?? prior.bin, deps.platform ?? process.platform);
24118
24546
  const envelope = resolveInside(forgesRoot(deps.home), name);
24119
- if (envelope && existsSync28(envelope)) rmSync7(envelope, { recursive: true, force: true });
24547
+ if (envelope && existsSync29(envelope)) rmSync7(envelope, { recursive: true, force: true });
24120
24548
  const cur = loadForgeState(deps.home);
24121
24549
  if (Object.hasOwn(cur.forges, name)) {
24122
24550
  delete cur.forges[name];
@@ -24481,15 +24909,15 @@ async function runLocalLifecycleCommand(cli, op, names) {
24481
24909
  }
24482
24910
 
24483
24911
  // src/lifecycle.ts
24484
- 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";
24485
24913
  import path42 from "path";
24486
24914
  function configFilesToBackup(repoDir) {
24487
24915
  const out = /* @__PURE__ */ new Set();
24488
- if (existsSync29(path42.join(repoDir, ".env"))) out.add(".env");
24916
+ if (existsSync30(path42.join(repoDir, ".env"))) out.add(".env");
24489
24917
  for (const file of readdirSync14(repoDir)) {
24490
24918
  if (file === ".env.example" || !file.includes(".example.")) continue;
24491
24919
  const real = file.replace(".example.", ".");
24492
- if (existsSync29(path42.join(repoDir, real))) out.add(real);
24920
+ if (existsSync30(path42.join(repoDir, real))) out.add(real);
24493
24921
  }
24494
24922
  return [...out];
24495
24923
  }
@@ -24498,7 +24926,7 @@ async function planUninstall(targetDir, repo) {
24498
24926
  if (!dir)
24499
24927
  return { kind: "refused", dir: path42.join(targetDir, repo.name), reason: "invalid-name" };
24500
24928
  if (isLinkOrJunction(dir)) return { kind: "refused", dir, reason: "symlink" };
24501
- if (!existsSync29(dir)) return { kind: "absent", dir };
24929
+ if (!existsSync30(dir)) return { kind: "absent", dir };
24502
24930
  if (!hasGitRepo(dir)) return { kind: "refused", dir, reason: "non-git" };
24503
24931
  const remote = await commandOutput("git", ["-C", dir, "remote", "get-url", "origin"]);
24504
24932
  if (remote && remote !== "ok" && !sameRemote(remote, repo.url)) {
@@ -24849,11 +25277,11 @@ async function runUninstallCommand(cli) {
24849
25277
  }
24850
25278
 
24851
25279
  // src/config.ts
24852
- import { existsSync as existsSync30, readFileSync as readFileSync21 } from "fs";
25280
+ import { existsSync as existsSync31, readFileSync as readFileSync21 } from "fs";
24853
25281
  import path44 from "path";
24854
25282
  function loadConfig(targetDir) {
24855
25283
  const file = path44.join(targetDir, "forge-cli.config.json");
24856
- if (!existsSync30(file)) return null;
25284
+ if (!existsSync31(file)) return null;
24857
25285
  let parsed;
24858
25286
  try {
24859
25287
  parsed = JSON.parse(readFileSync21(file, "utf8"));
@@ -24899,12 +25327,12 @@ function loadConfig(targetDir) {
24899
25327
  }
24900
25328
 
24901
25329
  // src/plan.ts
24902
- import { existsSync as existsSync31, readdirSync as readdirSync15 } from "fs";
25330
+ import { existsSync as existsSync32, readdirSync as readdirSync15 } from "fs";
24903
25331
  import path45 from "path";
24904
25332
  function planLines(repo, targetDir) {
24905
25333
  const repoDir = path45.join(targetDir, repo.name);
24906
25334
  const lines = [];
24907
- if (!existsSync31(repoDir)) {
25335
+ if (!existsSync32(repoDir)) {
24908
25336
  lines.push(`${S.planSync}: git clone (${S.planDetectAfter})`);
24909
25337
  for (const step of [
24910
25338
  S.phaseEnv,
@@ -24947,7 +25375,7 @@ async function executionSummary(repo, targetDir, native, platform2 = process.pla
24947
25375
  const trust = repo.origin === "custom" ? "custom" : "builtin";
24948
25376
  const commands = [];
24949
25377
  let revision;
24950
- if (!existsSync31(repoDir) || !hasGitRepo(repoDir)) {
25378
+ if (!existsSync32(repoDir) || !hasGitRepo(repoDir)) {
24951
25379
  commands.push(`git clone -- ${repo.url}`, S.planScriptsAfterClone);
24952
25380
  } else {
24953
25381
  revision = (await currentHead(repoDir))?.slice(0, 7);
@@ -25181,11 +25609,59 @@ ${planLines(repo, targetDir).map((line) => ` ${theme.dim(line)}`).join("\n")}`
25181
25609
  log.info(`${S.logSaved}: ${theme.dim(runLog.file)}`);
25182
25610
  const hasErrors = results.some((r2) => r2.status === "error");
25183
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
+ }
25184
25615
  if (hasErrors) process.exitCode = 1;
25185
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
+ }
25186
25662
 
25187
25663
  // src/cmd/env.ts
25188
- 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";
25189
25665
  import path48 from "path";
25190
25666
 
25191
25667
  // src/env/discover.ts
@@ -25193,7 +25669,7 @@ import { accessSync as accessSync3, constants as constants6 } from "fs";
25193
25669
  import os7 from "os";
25194
25670
 
25195
25671
  // src/env/paths.ts
25196
- 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";
25197
25673
  import os6 from "os";
25198
25674
  import path46 from "path";
25199
25675
  function resolveDeps2(deps) {
@@ -25202,7 +25678,7 @@ function resolveDeps2(deps) {
25202
25678
  osHome: deps.osHome ?? os6.homedir(),
25203
25679
  cwd: deps.cwd ?? process.cwd(),
25204
25680
  tmpDir: deps.tmpDir ?? os6.tmpdir(),
25205
- exists: deps.exists ?? existsSync32,
25681
+ exists: deps.exists ?? existsSync33,
25206
25682
  writable: deps.writable ?? ((dir) => {
25207
25683
  try {
25208
25684
  accessSync2(dir, constants5.W_OK);
@@ -25416,7 +25892,32 @@ function targetFindings(targets) {
25416
25892
  async function discoverEnvironment(deps = {}) {
25417
25893
  const platform2 = deps.platform ?? process.platform;
25418
25894
  const home = deps.home ?? cliHome();
25419
- const shells = await discoverShells(deps);
25895
+ const [shells, gitProbe, pyDiscovery] = await Promise.all([
25896
+ discoverShells(deps),
25897
+ deps.gitProbe ? deps.gitProbe() : commandOutput("git", ["--version"]).then((output) => ({ output, timedOut: false })),
25898
+ // Pipeline lf-0017 completo — `run`/`exists`/`env`/`platform` são os
25899
+ // MESMOS da sonda de shells: um fake único controla a descoberta toda.
25900
+ // `pythonProbe` legado (testes pré-lf-0017) vira descoberta sintética.
25901
+ deps.pythonDiscovery ? deps.pythonDiscovery({
25902
+ platform: platform2,
25903
+ env: deps.env,
25904
+ run: deps.run,
25905
+ exists: deps.exists,
25906
+ osHome: deps.osHome,
25907
+ cwd: deps.cwd,
25908
+ targetDir: deps.workspace,
25909
+ explicit: deps.pythonExplicit
25910
+ }) : deps.pythonProbe ? legacyPythonDiscovery(platform2, deps.pythonProbe) : discoverPythons({
25911
+ platform: platform2,
25912
+ env: deps.env,
25913
+ run: deps.run,
25914
+ exists: deps.exists,
25915
+ osHome: deps.osHome,
25916
+ cwd: deps.cwd,
25917
+ targetDir: deps.workspace,
25918
+ explicit: deps.pythonExplicit
25919
+ })
25920
+ ]);
25420
25921
  const route = routeOf(platform2, shells);
25421
25922
  const targets = resolveWriteTargets({
25422
25923
  env: deps.env,
@@ -25432,31 +25933,11 @@ async function discoverEnvironment(deps = {}) {
25432
25933
  workspaceDir: deps.workspace ?? deps.explicit?.workspaceDir
25433
25934
  }
25434
25935
  });
25435
- const gitProbe = deps.gitProbe ? await deps.gitProbe() : { output: await commandOutput("git", ["--version"]), timedOut: false };
25436
25936
  const git = {
25437
25937
  present: gitProbe !== null && !gitProbe.timedOut && gitProbe.output !== null,
25438
25938
  version: gitProbe?.output ?? null,
25439
25939
  executable: gitProbe?.output ? "git" : null
25440
25940
  };
25441
- const pyDiscovery = deps.pythonDiscovery ? await deps.pythonDiscovery({
25442
- platform: platform2,
25443
- env: deps.env,
25444
- run: deps.run,
25445
- exists: deps.exists,
25446
- osHome: deps.osHome,
25447
- cwd: deps.cwd,
25448
- targetDir: deps.workspace,
25449
- explicit: deps.pythonExplicit
25450
- }) : deps.pythonProbe ? await legacyPythonDiscovery(platform2, deps.pythonProbe) : await discoverPythons({
25451
- platform: platform2,
25452
- env: deps.env,
25453
- run: deps.run,
25454
- exists: deps.exists,
25455
- osHome: deps.osHome,
25456
- cwd: deps.cwd,
25457
- targetDir: deps.workspace,
25458
- explicit: deps.pythonExplicit
25459
- });
25460
25941
  const pySelected = pyDiscovery.selected;
25461
25942
  const pyInfo = pySelected ? {
25462
25943
  exe: pySelected.argv[0],
@@ -25719,7 +26200,7 @@ function envDoctor(repoDir, envKeys) {
25719
26200
 
25720
26201
  // src/envintel/migrate.ts
25721
26202
  import { randomBytes } from "crypto";
25722
- 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";
25723
26204
  import path47 from "path";
25724
26205
  function timestamp2(d = /* @__PURE__ */ new Date()) {
25725
26206
  const p = (n4) => String(n4).padStart(2, "0");
@@ -25735,11 +26216,11 @@ function addValue(spec, exampleEmpty) {
25735
26216
  function planMigration(repoDir, targetSchema) {
25736
26217
  const schema = targetSchema ?? deriveSchema(repoDir);
25737
26218
  const envPath = path47.join(repoDir, ".env");
25738
- const env = existsSync33(envPath) ? parseEnv(readFileSync22(envPath, "utf8")) : null;
26219
+ const env = existsSync34(envPath) ? parseEnv(readFileSync22(envPath, "utf8")) : null;
25739
26220
  const present = new Set(env?.entries.map((e) => e.key) ?? []);
25740
26221
  const examplePath = path47.join(repoDir, ".env.example");
25741
26222
  const exampleEmpty = /* @__PURE__ */ new Set();
25742
- if (existsSync33(examplePath)) {
26223
+ if (existsSync34(examplePath)) {
25743
26224
  for (const e of parseEnv(readFileSync22(examplePath, "utf8")).entries) {
25744
26225
  if (e.value === "") exampleEmpty.add(e.key);
25745
26226
  }
@@ -25779,7 +26260,7 @@ function backupEnv(repoDir, content) {
25779
26260
  }
25780
26261
  function applyMigration(repoDir, plan, opts = {}) {
25781
26262
  const envPath = path47.join(repoDir, ".env");
25782
- const exists = existsSync33(envPath);
26263
+ const exists = existsSync34(envPath);
25783
26264
  const current = exists ? readFileSync22(envPath, "utf8") : "";
25784
26265
  const env = parseEnv(current);
25785
26266
  const present = new Set(env.entries.map((e) => e.key));
@@ -25811,14 +26292,14 @@ function applyMigration(repoDir, plan, opts = {}) {
25811
26292
  }
25812
26293
  function latestEnvBackup(repoDir) {
25813
26294
  const root = path47.join(artifactsRoot(repoDir), "backups");
25814
- if (!existsSync33(root)) return null;
26295
+ if (!existsSync34(root)) return null;
25815
26296
  const candidates = readdirSync16(root, { withFileTypes: true }).filter((d) => d.isDirectory() && /^env-\d{8}-\d{6}$/.test(d.name)).map((d) => d.name).sort();
25816
26297
  const last = candidates.at(-1);
25817
26298
  return last ? path47.join(root, last) : null;
25818
26299
  }
25819
26300
  function rollbackEnv(repoDir, backupDir) {
25820
26301
  const src = path47.join(backupDir, ".env");
25821
- if (!existsSync33(src)) return false;
26302
+ if (!existsSync34(src)) return false;
25822
26303
  writeFileAtomic(path47.join(repoDir, ".env"), readFileSync22(src, "utf8"));
25823
26304
  return true;
25824
26305
  }
@@ -25839,7 +26320,7 @@ function envCandidates(dir) {
25839
26320
  try {
25840
26321
  return readdirSync17(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => {
25841
26322
  const r2 = resolveInside(dir, name);
25842
- 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")));
25843
26324
  }).sort();
25844
26325
  } catch {
25845
26326
  return [];
@@ -25916,7 +26397,7 @@ async function runEnvCommand(cli, args) {
25916
26397
  envKeys = catalogFor(targetDir).find((r2) => r2.name === name)?.envKeys;
25917
26398
  } else {
25918
26399
  const cwd = process.cwd();
25919
- 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"))) {
25920
26401
  repoDir = cwd;
25921
26402
  }
25922
26403
  }
@@ -25968,7 +26449,7 @@ async function runEnvCommand(cli, args) {
25968
26449
  note(lines.join("\n"), `${S.envDoctorTitle} \u2014 ${repo}`);
25969
26450
  return res2.checks.some((c5) => c5.result === "fail") ? 1 : 0;
25970
26451
  }
25971
- if (!existsSync34(repoDir)) {
26452
+ if (!existsSync35(repoDir)) {
25972
26453
  log.error(`${repo}: ${S.statusNotInstalled}`);
25973
26454
  return 2;
25974
26455
  }
@@ -25984,7 +26465,7 @@ async function runEnvCommand(cli, args) {
25984
26465
  log.error(S.envWizardNonInteractive);
25985
26466
  return 2;
25986
26467
  }
25987
- const env = parseEnv(existsSync34(envPath) ? readFileSync23(envPath, "utf8") : "");
26468
+ const env = parseEnv(existsSync35(envPath) ? readFileSync23(envPath, "utf8") : "");
25988
26469
  const current = new Map(env.entries.map((e) => [e.key, e]));
25989
26470
  const updates = /* @__PURE__ */ new Map();
25990
26471
  for (const spec of schema.vars) {
@@ -26027,7 +26508,7 @@ async function runEnvCommand(cli, args) {
26027
26508
  }
26028
26509
  for (const [k, v] of updates) lines.push(formatEntry(k, v));
26029
26510
  const out = serializeEnv({ ...env, lines });
26030
- if (existsSync34(envPath)) copyFileSync4(envPath, `${envPath}.bak`);
26511
+ if (existsSync35(envPath)) copyFileSync4(envPath, `${envPath}.bak`);
26031
26512
  writeFileAtomic(envPath, out.endsWith("\n") ? out : `${out}
26032
26513
  `);
26033
26514
  log.success(`${icons.ok} ${S.envWizardDone} ${theme.dim(`(${envPath}.bak)`)}`);
@@ -26055,7 +26536,7 @@ async function runEnvCommand(cli, args) {
26055
26536
  return 0;
26056
26537
  }
26057
26538
  if (verb === "init") {
26058
- if (existsSync34(envPath)) {
26539
+ if (existsSync35(envPath)) {
26059
26540
  if (json) console.log(JSON.stringify({ command: "env init", repo, created: false }));
26060
26541
  else log.info(S.envInitExists);
26061
26542
  return 0;
@@ -26170,11 +26651,11 @@ async function runEnvCommand(cli, args) {
26170
26651
 
26171
26652
  // src/cmd/hosts.ts
26172
26653
  import path50 from "path";
26173
- import { existsSync as existsSync36, readdirSync as readdirSync18 } from "fs";
26654
+ import { existsSync as existsSync37, readdirSync as readdirSync18 } from "fs";
26174
26655
 
26175
26656
  // src/mcp/handshake.ts
26176
26657
  import { spawn as spawn2 } from "child_process";
26177
- import { existsSync as existsSync35 } from "fs";
26658
+ import { existsSync as existsSync36 } from "fs";
26178
26659
  import path49 from "path";
26179
26660
  import { createInterface as createInterface2 } from "readline";
26180
26661
  var PROTOCOL_VERSION = "2024-11-05";
@@ -26265,7 +26746,7 @@ function probeMcpServer(argv, opts = {}) {
26265
26746
  const stepMs = opts.timeoutMs ?? STEP_MS;
26266
26747
  const levels = [];
26267
26748
  if (path49.isAbsolute(argv[0])) {
26268
- if (!existsSync35(argv[0])) {
26749
+ if (!existsSync36(argv[0])) {
26269
26750
  resolve(err("bin-missing", `bin\xE1rio MCP ausente: ${argv[0]}`));
26270
26751
  return;
26271
26752
  }
@@ -26695,7 +27176,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26695
27176
  const candidates = names.length > 0 ? names : [
26696
27177
  .../* @__PURE__ */ new Set([
26697
27178
  ...Object.keys(loadForgeState(home).forges),
26698
- ...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) : []
26699
27180
  ])
26700
27181
  ].sort();
26701
27182
  if (candidates.length === 0) {
@@ -26716,7 +27197,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26716
27197
  results.push({ forge: name, ok: false, detail: "sem mcp_command" });
26717
27198
  continue;
26718
27199
  }
26719
- const binPresent = path50.isAbsolute(argv[0]) ? existsSync36(argv[0]) : true;
27200
+ const binPresent = path50.isAbsolute(argv[0]) ? existsSync37(argv[0]) : true;
26720
27201
  const item = {
26721
27202
  forge: name,
26722
27203
  forge_id: forge.forge_id,
@@ -26764,7 +27245,7 @@ async function runMcpCommand(cli, args, home = cliHome()) {
26764
27245
  }
26765
27246
 
26766
27247
  // src/discovery.ts
26767
- 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";
26768
27249
  import os8 from "os";
26769
27250
  import path51 from "path";
26770
27251
  var DEFAULT_LIMITS = {
@@ -27054,10 +27535,10 @@ function registryState(entry2) {
27054
27535
  function discoverForge(name, dir, state) {
27055
27536
  const sources = [];
27056
27537
  const git = !!dir && hasGitRepo(dir);
27057
- const manifest = dir && existsSync37(dir) ? readForgeManifest(dir) : null;
27538
+ const manifest = dir && existsSync38(dir) ? readForgeManifest(dir) : null;
27058
27539
  const entry2 = state?.repos[name];
27059
27540
  if (entry2 && entry2.phase !== "not-installed") {
27060
- const dirExists = !!dir && existsSync37(dir);
27541
+ const dirExists = !!dir && existsSync38(dir);
27061
27542
  sources.push({
27062
27543
  origin: "manifest",
27063
27544
  // registro diz instalada mas o checkout sumiu → evidência falhando
@@ -27117,7 +27598,7 @@ async function discoverEnvironment2(targetDir, opts) {
27117
27598
  const dir = resolveInside(root, dirName);
27118
27599
  if (!dir) continue;
27119
27600
  const git = hasGitRepo(dir);
27120
- const manifest = existsSync37(path51.join(dir, "forge.json"));
27601
+ const manifest = existsSync38(path51.join(dir, "forge.json"));
27121
27602
  if (!git && !manifest) continue;
27122
27603
  presence.push({ name: dirName, path: dirName, git, manifest });
27123
27604
  if (!catalogNames.has(dirName) && manifest) {
@@ -27786,7 +28267,7 @@ async function answerRepair(targetDir, repos, opts = {}) {
27786
28267
  }
27787
28268
 
27788
28269
  // src/intel/context.ts
27789
- 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";
27790
28271
  import path52 from "path";
27791
28272
 
27792
28273
  // src/planner.ts
@@ -27904,7 +28385,7 @@ function safeConfig(targetDir) {
27904
28385
  function readManifestObject(repoDir) {
27905
28386
  const file = path52.join(repoDir, "forge.json");
27906
28387
  try {
27907
- if (!existsSync38(file) || statSync9(file).size > MAX_MANIFEST_BYTES) return null;
28388
+ if (!existsSync39(file) || statSync9(file).size > MAX_MANIFEST_BYTES) return null;
27908
28389
  const raw = JSON.parse(readFileSync25(file, "utf8"));
27909
28390
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
27910
28391
  } catch {
@@ -28074,7 +28555,7 @@ async function buildForgeContext(targetDir, repos, opts = {}) {
28074
28555
  }
28075
28556
 
28076
28557
  // src/intel/predict.ts
28077
- 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";
28078
28559
  import path53 from "path";
28079
28560
  var MAX_MANIFEST_BYTES2 = 64 * 1024;
28080
28561
  var SEMVERISH = /^v?\d+\.\d+\.\d+/;
@@ -28087,7 +28568,7 @@ var mk2 = (id, kind, subject, certainty, version, evidence, explanation) => ({ i
28087
28568
  function readManifest(repoDir) {
28088
28569
  const file = path53.join(repoDir, "forge.json");
28089
28570
  try {
28090
- if (!existsSync39(file) || statSync10(file).size > MAX_MANIFEST_BYTES2) return null;
28571
+ if (!existsSync40(file) || statSync10(file).size > MAX_MANIFEST_BYTES2) return null;
28091
28572
  const raw = JSON.parse(readFileSync26(file, "utf8"));
28092
28573
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
28093
28574
  } catch {
@@ -28404,7 +28885,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
28404
28885
  const allClaims = [];
28405
28886
  for (const repo of sorted) {
28406
28887
  const dir = resolveInside(targetDir, repo.name);
28407
- if (!dir || !existsSync39(dir)) continue;
28888
+ if (!dir || !existsSync40(dir)) continue;
28408
28889
  const m3 = readManifest(dir);
28409
28890
  manifests.set(repo.name, m3);
28410
28891
  if (m3) out.push(...schemaChange(repo.name, m3));
@@ -28416,7 +28897,7 @@ async function predictCompatibility(targetDir, repos, opts = {}) {
28416
28897
  out.push(...envVarFindings("<workspace>", targetDir));
28417
28898
  for (const repo of sorted) {
28418
28899
  const dir = resolveInside(targetDir, repo.name);
28419
- if (!dir || !existsSync39(dir)) continue;
28900
+ if (!dir || !existsSync40(dir)) continue;
28420
28901
  out.push(...envVarFindings(repo.name, dir, repo.envKeys));
28421
28902
  }
28422
28903
  out.push(...installConflict(allClaims));
@@ -28429,7 +28910,7 @@ import path55 from "path";
28429
28910
 
28430
28911
  // src/ai/cache.ts
28431
28912
  import { createHash as createHash7 } from "crypto";
28432
- 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";
28433
28914
  import path54 from "path";
28434
28915
 
28435
28916
  // src/ai/types.ts
@@ -28533,7 +29014,7 @@ function entryFile(rootDir, key) {
28533
29014
  function aiCacheGet(rootDir, key, opts) {
28534
29015
  if (opts.ttlMs <= 0) return null;
28535
29016
  const file = entryFile(rootDir, key);
28536
- if (!file || !existsSync40(file)) return null;
29017
+ if (!file || !existsSync41(file)) return null;
28537
29018
  try {
28538
29019
  const entry2 = JSON.parse(readFileSync27(file, "utf8"));
28539
29020
  const now = opts.now ?? Date.now();
@@ -28733,7 +29214,7 @@ async function runAiBridge(req, cfg, deps) {
28733
29214
  }
28734
29215
 
28735
29216
  // src/portability/bundle.ts
28736
- 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";
28737
29218
  import path56 from "path";
28738
29219
 
28739
29220
  // src/portability/manifest.ts
@@ -28845,7 +29326,7 @@ function collectPins(targetDir, repos) {
28845
29326
  const pins = /* @__PURE__ */ new Map();
28846
29327
  for (const repo of repos) {
28847
29328
  const repoDir = path56.join(targetDir, repo.name);
28848
- const version = existsSync41(repoDir) ? pinVersion(repoDir) : void 0;
29329
+ const version = existsSync42(repoDir) ? pinVersion(repoDir) : void 0;
28849
29330
  const head = state?.repos[repo.name]?.head;
28850
29331
  const ref = state?.pins?.[repo.name] ?? head;
28851
29332
  pins.set(repo.name, {
@@ -28872,7 +29353,7 @@ async function exportBundle(targetDir, repos, dest, opts) {
28872
29353
  const schemaByRepo = /* @__PURE__ */ new Map();
28873
29354
  for (const repo of repos) {
28874
29355
  const repoDir = path56.join(targetDir, repo.name);
28875
- const schema = existsSync41(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
29356
+ const schema = existsSync42(repoDir) ? deriveSchema(repoDir, repo.envKeys) : { vars: [] };
28876
29357
  schemaByRepo.set(repo.name, schema);
28877
29358
  for (const v of schema.vars) {
28878
29359
  if (v.secret && whitelist.includes(v.key)) refused.add(v.key);
@@ -28914,7 +29395,7 @@ async function exportBundle(targetDir, repos, dest, opts) {
28914
29395
  const values = {};
28915
29396
  for (const repo of repos) {
28916
29397
  const envFile = path56.join(targetDir, repo.name, ".env");
28917
- if (!existsSync41(envFile)) continue;
29398
+ if (!existsSync42(envFile)) continue;
28918
29399
  const entries = parseEnv(readFileSync28(envFile, "utf8")).entries;
28919
29400
  const found = {};
28920
29401
  for (const key of whitelist) {
@@ -29004,7 +29485,7 @@ function importBundle(src, opts) {
29004
29485
  }
29005
29486
 
29006
29487
  // src/intel/learning.ts
29007
- 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";
29008
29489
  import path57 from "path";
29009
29490
  var LEARNING_SCHEMA = "forge-cli/LearningStore/v1";
29010
29491
  function learningFile(targetDir) {
@@ -29012,7 +29493,7 @@ function learningFile(targetDir) {
29012
29493
  }
29013
29494
  function readStore(targetDir) {
29014
29495
  const file = learningFile(targetDir);
29015
- if (!existsSync42(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
29496
+ if (!existsSync43(file)) return { schema: LEARNING_SCHEMA, enabled: false, observations: [] };
29016
29497
  try {
29017
29498
  const parsed = JSON.parse(readFileSync29(file, "utf8"));
29018
29499
  if (parsed?.schema !== LEARNING_SCHEMA || !Array.isArray(parsed.observations)) {
@@ -29272,7 +29753,7 @@ async function runLearningCommand(cli) {
29272
29753
  }
29273
29754
 
29274
29755
  // src/wizard.ts
29275
- import { existsSync as existsSync43 } from "fs";
29756
+ import { existsSync as existsSync44 } from "fs";
29276
29757
  import path58 from "path";
29277
29758
  var STEP_LABELS = [
29278
29759
  S.wizardStepDetect,
@@ -29313,7 +29794,7 @@ function renderOperationPlan(plan) {
29313
29794
  }
29314
29795
  function planActionsFor(repo, targetDir, steps) {
29315
29796
  const repoDir = path58.join(targetDir, repo.name);
29316
- const cloned = existsSync43(repoDir) && hasGitRepo(repoDir);
29797
+ const cloned = existsSync44(repoDir) && hasGitRepo(repoDir);
29317
29798
  const actions = [
29318
29799
  {
29319
29800
  kind: "clone",