@inspecto-dev/cli 0.3.3 → 0.3.5

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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-test.log +10594 -4044
  3. package/CHANGELOG.md +28 -0
  4. package/dist/bin.js +36 -2
  5. package/dist/{chunk-LJOKPCPD.js → chunk-7ABJRH3F.js} +1701 -182
  6. package/dist/index.d.ts +69 -4
  7. package/dist/index.js +7 -1
  8. package/package.json +3 -3
  9. package/src/bin.ts +49 -1
  10. package/src/commands/dev-config.ts +109 -0
  11. package/src/commands/doctor.ts +189 -9
  12. package/src/commands/init.ts +10 -3
  13. package/src/commands/integration-automation.ts +2 -0
  14. package/src/commands/integration-host-ide.ts +18 -15
  15. package/src/commands/integration-install.ts +100 -5
  16. package/src/commands/onboard.ts +80 -15
  17. package/src/detect/build-tool.ts +212 -15
  18. package/src/detect/framework.ts +3 -0
  19. package/src/detect/package-manager.ts +1 -1
  20. package/src/index.ts +1 -0
  21. package/src/inject/gitignore.ts +13 -2
  22. package/src/instructions.ts +33 -7
  23. package/src/onboarding/apply.ts +255 -28
  24. package/src/onboarding/nextjs-guidance.ts +257 -0
  25. package/src/onboarding/nuxt-guidance.ts +129 -0
  26. package/src/onboarding/planner.ts +337 -10
  27. package/src/onboarding/session.ts +127 -31
  28. package/src/onboarding/target-resolution.ts +79 -3
  29. package/src/onboarding/umi-guidance.ts +139 -0
  30. package/src/types.ts +58 -3
  31. package/tests/apply.test.ts +553 -0
  32. package/tests/build-tool.test.ts +199 -0
  33. package/tests/dev-config.test.ts +73 -0
  34. package/tests/doctor.test.ts +130 -0
  35. package/tests/init.test.ts +17 -0
  36. package/tests/install-wrapper.test.ts +56 -0
  37. package/tests/instructions.test.ts +10 -6
  38. package/tests/integration-host-ide.test.ts +20 -0
  39. package/tests/integration-install.test.ts +193 -0
  40. package/tests/nextjs-guidance.test.ts +128 -0
  41. package/tests/nuxt-guidance.test.ts +67 -0
  42. package/tests/onboard.test.ts +511 -0
  43. package/tests/plan.test.ts +283 -21
  44. package/tests/runner-script.test.ts +120 -1
  45. package/tests/session-resolve.test.ts +116 -0
  46. package/tests/session.test.ts +120 -0
@@ -118,6 +118,12 @@ async function writeFile(filePath, content) {
118
118
  await fs.mkdir(path.dirname(filePath), { recursive: true });
119
119
  await fs.writeFile(filePath, content, "utf-8");
120
120
  }
121
+ async function removeFile(filePath) {
122
+ try {
123
+ await fs.unlink(filePath);
124
+ } catch {
125
+ }
126
+ }
121
127
  async function removeDir(dirPath) {
122
128
  try {
123
129
  await fs.rm(dirPath, { recursive: true, force: true });
@@ -144,8 +150,8 @@ async function detectPackageManager(root) {
144
150
  ["bun.lockb", "bun"],
145
151
  ["bun.lock", "bun"],
146
152
  ["pnpm-lock.yaml", "pnpm"],
147
- ["yarn.lock", "yarn"],
148
- ["package-lock.json", "npm"]
153
+ ["package-lock.json", "npm"],
154
+ ["yarn.lock", "yarn"]
149
155
  ];
150
156
  const results = await Promise.all(
151
157
  checks.map(async ([file, pm]) => {
@@ -826,8 +832,18 @@ async function isExtensionInstalled() {
826
832
 
827
833
  // src/inject/gitignore.ts
828
834
  import path5 from "path";
829
- var DEFAULT_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
830
- var SHARED_RULES = [".inspecto/install.lock", ".inspecto/cache.json", ".inspecto/*.local.json"];
835
+ var DEFAULT_RULES = [
836
+ ".inspecto/install.lock",
837
+ ".inspecto/cache.json",
838
+ ".inspecto/*.local.json",
839
+ ".inspecto/dev.json"
840
+ ];
841
+ var SHARED_RULES = [
842
+ ".inspecto/install.lock",
843
+ ".inspecto/cache.json",
844
+ ".inspecto/*.local.json",
845
+ ".inspecto/dev.json"
846
+ ];
831
847
  async function updateGitignore(root, shared, dryRun, quiet = false) {
832
848
  const gitignorePath = path5.join(root, ".gitignore");
833
849
  let content = await readFile(gitignorePath) ?? "";
@@ -864,11 +880,39 @@ async function cleanGitignore(root) {
864
880
  const gitignorePath = path5.join(root, ".gitignore");
865
881
  const content = await readFile(gitignorePath);
866
882
  if (!content) return;
867
- const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
883
+ const cleaned = content.replace(/^# Inspecto\s*$/m, "").replace(/^\.inspecto\/?\s*$/gm, "").replace(/^\.inspecto\/install\.lock\s*$/gm, "").replace(/^\.inspecto\/cache\.json\s*$/gm, "").replace(/^\.inspecto\/\*\.local\.json\s*$/gm, "").replace(/^\.inspecto\/dev\.json\s*$/gm, "").replace(/\n{3,}/g, "\n\n");
868
884
  await writeFile(gitignorePath, cleaned);
869
885
  }
870
886
 
871
887
  // src/onboarding/apply.ts
888
+ function normalizeSupportedIde(ide) {
889
+ if (!ide) return void 0;
890
+ return ide.toLowerCase() === "vscode" ? "vscode" : ide.toLowerCase();
891
+ }
892
+ async function readInheritedSettingsDefaults(repoRoot, projectRoot, settingsFileName) {
893
+ if (path6.resolve(repoRoot) === path6.resolve(projectRoot)) {
894
+ return {};
895
+ }
896
+ const inheritedSettingsPath = path6.join(repoRoot, ".inspecto", settingsFileName);
897
+ if (!await exists(inheritedSettingsPath)) {
898
+ return {};
899
+ }
900
+ const inheritedSettings = await readJSON(inheritedSettingsPath);
901
+ if (!inheritedSettings || typeof inheritedSettings !== "object") {
902
+ return {};
903
+ }
904
+ const inheritedDefaults = {};
905
+ if (typeof inheritedSettings.ide === "string") {
906
+ const normalizedIde = normalizeSupportedIde(inheritedSettings.ide);
907
+ if (normalizedIde) {
908
+ inheritedDefaults.ide = normalizedIde;
909
+ }
910
+ }
911
+ if (typeof inheritedSettings["provider.default"] === "string") {
912
+ inheritedDefaults.providerDefault = inheritedSettings["provider.default"];
913
+ }
914
+ return inheritedDefaults;
915
+ }
872
916
  function shellQuote(value) {
873
917
  return `'${value.replace(/'/g, `'\\''`)}'`;
874
918
  }
@@ -892,11 +936,120 @@ function resolveRuntimePackages() {
892
936
  function resultStatus(nextSteps) {
893
937
  return nextSteps.length > 0 ? "warning" : "ok";
894
938
  }
895
- function manualPlanSteps(plan2) {
896
- return [
897
- ...plan2.blockers.map((blocker) => blocker.message),
898
- ...plan2.actions.filter((action) => action.type === "manual_step").map((action) => action.description)
899
- ];
939
+ function manualPlanSteps(plan2, includeBlockers = true) {
940
+ const steps = plan2.actions.filter(
941
+ (action) => ["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
942
+ action.type
943
+ )
944
+ ).map((action) => action.description);
945
+ if (!includeBlockers) {
946
+ return steps;
947
+ }
948
+ return [...plan2.blockers.map((blocker) => blocker.message), ...steps];
949
+ }
950
+ function applyGuidedPatchContent(source, snippet) {
951
+ if (source.includes("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'")) {
952
+ return source;
953
+ }
954
+ if (source.includes("import { vitePlugin as inspecto } from '@inspecto-dev/plugin'")) {
955
+ return source;
956
+ }
957
+ const trimmedSource = source.trimEnd();
958
+ if (/export\s+default\s*\{/m.test(source)) {
959
+ const nextSource = trimmedSource.replace(
960
+ /export\s+default\s*\{/m,
961
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nexport default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
962
+ );
963
+ return `${nextSource}
964
+ `;
965
+ }
966
+ if (/module\.exports\s*=\s*\{/m.test(source)) {
967
+ const nextSource = trimmedSource.replace(
968
+ /module\.exports\s*=\s*\{/m,
969
+ "const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\nmodule.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
970
+ );
971
+ return `${nextSource}
972
+ `;
973
+ }
974
+ const objectExportVariableMatch = source.match(
975
+ /(const\s+([A-Za-z0-9_$]+)\s*(?::[^=]+)?=\s*\{[\s\S]*?\}\s*;?\s*)export\s+default\s+\2\s*;?/m
976
+ );
977
+ const variableDeclaration = objectExportVariableMatch?.[1];
978
+ const variableName = objectExportVariableMatch?.[2];
979
+ if (variableDeclaration && variableName) {
980
+ const nextDeclaration = variableDeclaration.replace(
981
+ /=\s*\{/m,
982
+ "= {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },"
983
+ );
984
+ const nextSource = source.replace(
985
+ objectExportVariableMatch[0],
986
+ `import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'
987
+
988
+ ${nextDeclaration}export default ${variableName}`
989
+ );
990
+ return `${nextSource.trimEnd()}
991
+ `;
992
+ }
993
+ if (/defineNuxtConfig\s*\(\s*\{/m.test(source)) {
994
+ const trimmedSource2 = source.trimEnd();
995
+ const nextSource = trimmedSource2.replace(
996
+ /defineNuxtConfig\s*\(\s*\{/m,
997
+ "defineNuxtConfig({\n vite: {\n plugins: [inspecto()],\n },"
998
+ );
999
+ return `import { vitePlugin as inspecto } from '@inspecto-dev/plugin'
1000
+
1001
+ ${nextSource}
1002
+ `;
1003
+ }
1004
+ const jsdocMatch = source.match(
1005
+ /\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{/m
1006
+ );
1007
+ if (jsdocMatch) {
1008
+ const isEsm = jsdocMatch[1] === "export default";
1009
+ const importStatement = isEsm ? "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\n" : "const { webpackPlugin: inspecto } = require('@inspecto-dev/plugin')\n\n";
1010
+ const replacementPattern = isEsm ? /export\s+default\s*\{/m : /module\.exports\s*=\s*\{/m;
1011
+ const injectConfig = isEsm ? "export default {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n }," : "module.exports = {\n webpack(config, { dev, isServer }) {\n if (dev) {\n config.plugins.push(inspecto())\n }\n return config\n },";
1012
+ const nextSource = source.replace(replacementPattern, injectConfig);
1013
+ return `${importStatement}${nextSource.trimEnd()}
1014
+ `;
1015
+ }
1016
+ return `${trimmedSource}
1017
+
1018
+ ${snippet}
1019
+ `;
1020
+ }
1021
+ async function applyGuidedPlanPatches(input, mutations, reporter) {
1022
+ const nextSteps = [];
1023
+ if (!input.plan || input.plan.strategy !== "guided" || !input.plan.patches?.length) {
1024
+ return nextSteps;
1025
+ }
1026
+ for (const patch of input.plan.patches) {
1027
+ if (patch.status !== "planned" || !patch.reason.startsWith("next_config_") && !patch.reason.startsWith("nuxt_config_")) {
1028
+ continue;
1029
+ }
1030
+ const patchPath = path6.join(input.projectRoot, patch.path);
1031
+ const existingContent = await readFile(patchPath);
1032
+ if (existingContent === null) {
1033
+ nextSteps.push(`Could not auto-apply the guided patch for ${patch.path}.`);
1034
+ continue;
1035
+ }
1036
+ const nextContent = applyGuidedPatchContent(existingContent, patch.snippet);
1037
+ if (nextContent === existingContent) {
1038
+ continue;
1039
+ }
1040
+ if (input.options.dryRun) {
1041
+ reporter.dryRun(`Would apply guided patch to ${patch.path}`);
1042
+ continue;
1043
+ }
1044
+ await writeFile(patchPath, nextContent);
1045
+ mutations.push({
1046
+ type: "file_modified",
1047
+ path: patch.path,
1048
+ description: "Automatically configured Inspecto guided Next.js patch"
1049
+ });
1050
+ reporter.success(`Applied guided patch to ${patch.path}`);
1051
+ }
1052
+ return nextSteps;
900
1053
  }
901
1054
  async function applyOnboardingPlan(input) {
902
1055
  return applyOnboardingPlanInternal(input);
@@ -960,6 +1113,7 @@ function createSpinner(text, quiet = false) {
960
1113
  }
961
1114
  async function applyOnboardingPlanInternal(input) {
962
1115
  const reporter = createReporter(input.options.quiet);
1116
+ const additiveManualPlan = (input.plan?.strategy === "manual" || input.plan?.strategy === "guided") && input.allowManualPlanApply && input.plan.blockers.length === 0;
963
1117
  if (input.plan && input.plan.strategy !== "supported" && !input.allowManualPlanApply) {
964
1118
  return {
965
1119
  status: input.plan.status,
@@ -968,7 +1122,7 @@ async function applyOnboardingPlanInternal(input) {
968
1122
  installFailed: false,
969
1123
  injectionFailed: false,
970
1124
  manualExtensionInstallNeeded: false,
971
- nextSteps: manualPlanSteps(input.plan)
1125
+ nextSteps: manualPlanSteps(input.plan, true)
972
1126
  }
973
1127
  };
974
1128
  }
@@ -978,6 +1132,11 @@ async function applyOnboardingPlanInternal(input) {
978
1132
  const promptsFileName = input.options.shared ? "prompts.json" : "prompts.local.json";
979
1133
  const settingsPath = path6.join(settingsDir, settingsFileName);
980
1134
  const promptsPath = path6.join(settingsDir, promptsFileName);
1135
+ const inheritedDefaults = await readInheritedSettingsDefaults(
1136
+ input.repoRoot,
1137
+ input.projectRoot,
1138
+ settingsFileName
1139
+ );
981
1140
  const runtimePackages = resolveRuntimePackages();
982
1141
  const installCmd = getInstallCommand(input.packageManager, runtimePackages.installSpec);
983
1142
  const nextSteps = [];
@@ -1003,6 +1162,11 @@ async function applyOnboardingPlanInternal(input) {
1003
1162
  spinner.fail("Dependency installation failed");
1004
1163
  installFailed = true;
1005
1164
  reporter.error(`Failed to install dependency: ${error?.message || "Unknown error"}`);
1165
+ if (error?.stderr) {
1166
+ reporter.error(`Details: ${error.stderr}`);
1167
+ } else if (error?.stdout) {
1168
+ reporter.error(`Details: ${error.stdout}`);
1169
+ }
1006
1170
  reporter.hint(`Run manually in ${input.projectRoot}: ${installCmd}`);
1007
1171
  reporter.hint(
1008
1172
  "Setup will continue without dependencies, but Inspecto may not run until installation succeeds."
@@ -1010,19 +1174,24 @@ async function applyOnboardingPlanInternal(input) {
1010
1174
  }
1011
1175
  }
1012
1176
  let injectionFailed = Boolean(input.injectionSkippedRequiresManualConfig);
1013
- for (const target of input.supportedBuildTargets) {
1014
- const result = await injectPlugin(
1015
- input.repoRoot,
1016
- target,
1017
- input.options.dryRun,
1018
- input.options.quiet ?? false
1019
- );
1020
- if (result.success) {
1021
- mutations.push(...result.mutations);
1022
- } else {
1023
- injectionFailed = true;
1177
+ if (!additiveManualPlan) {
1178
+ for (const target of input.supportedBuildTargets) {
1179
+ const result = await injectPlugin(
1180
+ input.repoRoot,
1181
+ target,
1182
+ input.options.dryRun,
1183
+ input.options.quiet ?? false
1184
+ );
1185
+ if (result.success) {
1186
+ mutations.push(...result.mutations);
1187
+ } else {
1188
+ injectionFailed = true;
1189
+ }
1024
1190
  }
1025
1191
  }
1192
+ if (additiveManualPlan) {
1193
+ nextSteps.push(...await applyGuidedPlanPatches(input, mutations, reporter));
1194
+ }
1026
1195
  if (await exists(settingsPath)) {
1027
1196
  const existingSettings = await readJSON(settingsPath);
1028
1197
  if (existingSettings === null) {
@@ -1030,15 +1199,43 @@ async function applyOnboardingPlanInternal(input) {
1030
1199
  reporter.hint("Please fix the syntax errors manually, or delete it and re-run init");
1031
1200
  nextSteps.push(`Fix .inspecto/${settingsFileName} or delete it and rerun Inspecto setup.`);
1032
1201
  } else {
1033
- reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`);
1202
+ const mergedSettings = existingSettings && typeof existingSettings === "object" ? { ...existingSettings } : {};
1203
+ let settingsChanged = false;
1204
+ const desiredIde = inheritedDefaults.ide ?? (input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : void 0);
1205
+ if (desiredIde && !mergedSettings.ide) {
1206
+ mergedSettings.ide = desiredIde;
1207
+ settingsChanged = true;
1208
+ }
1209
+ const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault;
1210
+ if (desiredProviderDefault && !mergedSettings["provider.default"]) {
1211
+ mergedSettings["provider.default"] = desiredProviderDefault;
1212
+ settingsChanged = true;
1213
+ }
1214
+ if (settingsChanged) {
1215
+ if (input.options.dryRun) {
1216
+ reporter.dryRun(`Would update .inspecto/${settingsFileName}`);
1217
+ } else {
1218
+ await writeJSON(settingsPath, mergedSettings);
1219
+ reporter.success(`Updated .inspecto/${settingsFileName} with missing defaults`);
1220
+ mutations.push({
1221
+ type: "file_modified",
1222
+ path: `.inspecto/${settingsFileName}`,
1223
+ description: "Merged missing Inspecto defaults into existing settings"
1224
+ });
1225
+ }
1226
+ } else {
1227
+ reporter.success(`.inspecto/${settingsFileName} already exists (skipped)`);
1228
+ }
1034
1229
  }
1035
1230
  } else {
1036
1231
  const defaultSettings = {};
1037
- if (input.selectedIDE?.supported) {
1038
- defaultSettings.ide = input.selectedIDE.ide.toLowerCase() === "vscode" ? "vscode" : input.selectedIDE.ide.toLowerCase();
1232
+ const desiredIde = inheritedDefaults.ide ?? (input.selectedIDE?.supported ? normalizeSupportedIde(input.selectedIDE.ide) : void 0);
1233
+ const desiredProviderDefault = inheritedDefaults.providerDefault ?? input.providerDefault;
1234
+ if (desiredIde) {
1235
+ defaultSettings.ide = desiredIde;
1039
1236
  }
1040
- if (input.providerDefault) {
1041
- defaultSettings["provider.default"] = input.providerDefault;
1237
+ if (desiredProviderDefault) {
1238
+ defaultSettings["provider.default"] = desiredProviderDefault;
1042
1239
  }
1043
1240
  if (input.options.dryRun) {
1044
1241
  reporter.dryRun(`Would create .inspecto/${settingsFileName}`);
@@ -1117,6 +1314,9 @@ async function applyOnboardingPlanInternal(input) {
1117
1314
  if (manualExtensionInstallNeeded) {
1118
1315
  nextSteps.push("Install the Inspecto IDE extension manually");
1119
1316
  }
1317
+ if (additiveManualPlan && input.plan) {
1318
+ nextSteps.push(...manualPlanSteps(input.plan, false));
1319
+ }
1120
1320
  if (input.manualConfigRequiredFor === "Nuxt") {
1121
1321
  nextSteps.push(
1122
1322
  "Nuxt detected\u2014please follow the Nuxt instructions printed above to finish setup."
@@ -1167,6 +1367,28 @@ async function getResolvedPackageVersion(pkgName, root) {
1167
1367
  return null;
1168
1368
  }
1169
1369
  }
1370
+ function parseFirstSemver(version) {
1371
+ if (!version) return null;
1372
+ const match = version.match(/(\d+)\.(\d+)\.(\d+)/);
1373
+ if (!match) {
1374
+ return null;
1375
+ }
1376
+ return {
1377
+ major: Number(match[1]),
1378
+ minor: Number(match[2]),
1379
+ patch: Number(match[3])
1380
+ };
1381
+ }
1382
+ function isLegacyRspackVersion(version) {
1383
+ const parsed = parseFirstSemver(version);
1384
+ if (!parsed) return false;
1385
+ return parsed.major === 0 && parsed.minor < 4;
1386
+ }
1387
+ function isLegacyWebpackVersion(version) {
1388
+ const parsed = parseFirstSemver(version);
1389
+ if (!parsed) return false;
1390
+ return parsed.major === 4;
1391
+ }
1170
1392
  var SUPPORTED_PATTERNS = [
1171
1393
  {
1172
1394
  tool: "vite",
@@ -1192,7 +1414,22 @@ var SUPPORTED_PATTERNS = [
1192
1414
  },
1193
1415
  {
1194
1416
  tool: "webpack",
1195
- files: ["webpack.config.js", "webpack.config.ts", "webpack.config.mjs", "webpack.config.cjs"],
1417
+ files: [
1418
+ "webpack.config.js",
1419
+ "webpack.config.ts",
1420
+ "webpack.config.mjs",
1421
+ "webpack.config.cjs",
1422
+ "webpack.config.common.js",
1423
+ "webpack.config.common.ts",
1424
+ "webpack.config.dev.js",
1425
+ "webpack.config.dev.ts",
1426
+ "webpack.config.prod.js",
1427
+ "webpack.config.prod.ts",
1428
+ "webpack.config.esbuild.js",
1429
+ "webpack.config.esbuild.ts",
1430
+ "webpack.config.build-pre.js",
1431
+ "webpack.config.build-pre.ts"
1432
+ ],
1196
1433
  label: "Webpack"
1197
1434
  },
1198
1435
  {
@@ -1207,6 +1444,11 @@ var SUPPORTED_PATTERNS = [
1207
1444
  }
1208
1445
  ];
1209
1446
  var UNSUPPORTED_META = [
1447
+ {
1448
+ name: "Umi",
1449
+ dep: "umi",
1450
+ files: [".umirc.ts", ".umirc.js", "config/config.ts", "config/config.js"]
1451
+ },
1210
1452
  { name: "Next.js", dep: "next", files: ["next.config.mjs", "next.config.js", "next.config.ts"] },
1211
1453
  { name: "Nuxt", dep: "nuxt", files: ["nuxt.config.ts", "nuxt.config.js"] },
1212
1454
  { name: "Remix", dep: "@remix-run/dev", files: ["remix.config.js", "remix.config.ts"] },
@@ -1320,7 +1562,8 @@ async function detectBuildTools(root, packagePaths) {
1320
1562
  }
1321
1563
  }
1322
1564
  const unsupportedChecks = UNSUPPORTED_META.map(async (meta) => {
1323
- if (!(meta.dep in allDeps)) return null;
1565
+ const hasDep = meta.dep in allDeps || Object.keys(allDeps).some((dep) => dep.includes(meta.dep));
1566
+ if (!hasDep) return null;
1324
1567
  for (const file of meta.files) {
1325
1568
  if (await exists(path7.join(target.absolutePath, file))) {
1326
1569
  return meta.name;
@@ -1337,6 +1580,89 @@ async function detectBuildTools(root, packagePaths) {
1337
1580
  }
1338
1581
  return { supported, unsupported: Array.from(unsupported) };
1339
1582
  }
1583
+ function rankScriptCommand(name, command) {
1584
+ const haystack = `${name} ${command}`.toLowerCase();
1585
+ let score = 0;
1586
+ if (/(^|[\s:_-])(start|dev|serve|watch)([\s:_-]|$)/.test(haystack)) score += 8;
1587
+ if (/(^|[\s:_-])(prod|build|release|stats)([\s:_-]|$)/.test(haystack)) score -= 3;
1588
+ if (/(^|[\s:_-])(dll|vendor)([\s:_-]|$)/.test(haystack)) score -= 6;
1589
+ if (haystack.includes("webpack-dev-server")) score += 3;
1590
+ if (haystack.includes("webpack")) score += 1;
1591
+ if (haystack.includes("rspack")) score += 1;
1592
+ return score;
1593
+ }
1594
+ function extractConfigArgs(scriptContent) {
1595
+ return Array.from(scriptContent.matchAll(/(?:-c|--config)\s+([^\s'"`;]+)/g)).map((match) => match[1]).filter((value) => Boolean(value));
1596
+ }
1597
+ async function resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate) {
1598
+ const normalizedCandidate = candidate.replace(/^['"`]|['"`]$/g, "");
1599
+ const normalizedRelativeCandidate = path7.normalize(normalizedCandidate);
1600
+ const possiblePaths = [];
1601
+ if (normalizedRelativeCandidate.startsWith("..")) {
1602
+ possiblePaths.push(
1603
+ path7.normalize(path7.join(path7.dirname(scriptPath), normalizedRelativeCandidate))
1604
+ );
1605
+ } else {
1606
+ possiblePaths.push(normalizedRelativeCandidate);
1607
+ possiblePaths.push(
1608
+ path7.normalize(path7.join(path7.dirname(scriptPath), normalizedRelativeCandidate))
1609
+ );
1610
+ }
1611
+ for (const possiblePath of possiblePaths) {
1612
+ if (await exists(path7.join(targetRoot, possiblePath))) {
1613
+ return possiblePath.split(path7.sep).join("/");
1614
+ }
1615
+ }
1616
+ return null;
1617
+ }
1618
+ async function resolveRspackConfigFromScript(targetRoot, scriptPath) {
1619
+ const scriptContent = await readFile(path7.join(targetRoot, scriptPath));
1620
+ if (!scriptContent) {
1621
+ return null;
1622
+ }
1623
+ for (const candidate of extractConfigArgs(scriptContent)) {
1624
+ const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate);
1625
+ if (resolved) {
1626
+ return resolved;
1627
+ }
1628
+ }
1629
+ const matches = scriptContent.matchAll(
1630
+ /['"`]([^'"`\n]*rspack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g
1631
+ );
1632
+ for (const match of matches) {
1633
+ const candidate = match[1];
1634
+ if (!candidate) continue;
1635
+ const resolved = await resolveScriptRelativeCandidate(targetRoot, scriptPath, candidate);
1636
+ if (resolved) {
1637
+ return resolved;
1638
+ }
1639
+ }
1640
+ return null;
1641
+ }
1642
+ async function resolveWebpackBaseConfigFromFile(targetRoot, configPath) {
1643
+ const configContent = await readFile(path7.join(targetRoot, configPath));
1644
+ if (!configContent) {
1645
+ return null;
1646
+ }
1647
+ for (const candidate of extractConfigArgs(configContent)) {
1648
+ const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate);
1649
+ if (resolved) {
1650
+ return resolved;
1651
+ }
1652
+ }
1653
+ const matches = configContent.matchAll(
1654
+ /(?:configPath\s*=\s*|require\()\s*['"`]([^'"`\n]*webpack[^'"`\n]*config[^'"`\n]*\.(?:js|ts|mjs|cjs))['"`]/g
1655
+ );
1656
+ for (const match of matches) {
1657
+ const candidate = match[1];
1658
+ if (!candidate) continue;
1659
+ const resolved = await resolveScriptRelativeCandidate(targetRoot, configPath, candidate);
1660
+ if (resolved) {
1661
+ return resolved;
1662
+ }
1663
+ }
1664
+ return null;
1665
+ }
1340
1666
  async function detectPattern({
1341
1667
  pattern,
1342
1668
  workspaceRoot,
@@ -1361,6 +1687,8 @@ async function detectPattern({
1361
1687
  }
1362
1688
  } else if (pattern.tool === "rsbuild") {
1363
1689
  hasDep = !!allDeps["@rsbuild/core"] || isPackageResolvable("@rsbuild/core", targetRoot);
1690
+ } else if (pattern.tool === "vite") {
1691
+ hasDep = !!allDeps["vite"] || isPackageResolvable("vite", targetRoot);
1364
1692
  } else {
1365
1693
  hasDep = !!allDeps[pattern.tool] || isPackageResolvable(pattern.tool, targetRoot);
1366
1694
  }
@@ -1376,13 +1704,38 @@ async function detectPattern({
1376
1704
  }
1377
1705
  }
1378
1706
  if (hasDep && !detectedFile && (pattern.tool === "esbuild" || pattern.tool === "rollup" || pattern.tool === "webpack" || pattern.tool === "rspack" || pattern.tool === "rsbuild")) {
1379
- for (const cmd of Object.values(scripts)) {
1707
+ const rankedScripts = Object.entries(scripts).sort(
1708
+ ([leftName, leftCommand], [rightName, rightCommand]) => rankScriptCommand(rightName, rightCommand) - rankScriptCommand(leftName, leftCommand)
1709
+ );
1710
+ for (const [, cmd] of rankedScripts) {
1711
+ if (pattern.tool === "webpack" || pattern.tool === "rspack") {
1712
+ for (const configArg of extractConfigArgs(cmd)) {
1713
+ const resolvedConfig = await resolveScriptRelativeCandidate(targetRoot, "", configArg);
1714
+ if (resolvedConfig && (cmd.includes(pattern.tool) || cmd.includes(`${pattern.tool}-`))) {
1715
+ if (pattern.tool === "webpack") {
1716
+ detectedFile = await resolveWebpackBaseConfigFromFile(targetRoot, resolvedConfig) ?? resolvedConfig;
1717
+ } else {
1718
+ detectedFile = await resolveRspackConfigFromScript(targetRoot, resolvedConfig) ?? resolvedConfig;
1719
+ }
1720
+ break;
1721
+ }
1722
+ }
1723
+ if (detectedFile) {
1724
+ break;
1725
+ }
1726
+ }
1380
1727
  if (cmd.includes("node ")) {
1381
1728
  const match = cmd.match(/node\s+([^\s]+\.(js|mjs|cjs|ts))/);
1382
1729
  if (match && match[1]) {
1383
1730
  if (await exists(path7.join(targetRoot, match[1]))) {
1384
1731
  if (cmd.includes(pattern.tool) || match[1].includes(pattern.tool)) {
1385
- detectedFile = match[1];
1732
+ if (pattern.tool === "rspack") {
1733
+ detectedFile = await resolveRspackConfigFromScript(targetRoot, match[1]) ?? match[1];
1734
+ } else if (pattern.tool === "webpack") {
1735
+ detectedFile = await resolveWebpackBaseConfigFromFile(targetRoot, match[1]) ?? match[1];
1736
+ } else {
1737
+ detectedFile = match[1];
1738
+ }
1386
1739
  break;
1387
1740
  }
1388
1741
  }
@@ -1411,7 +1764,7 @@ async function detectPattern({
1411
1764
  tool: pattern.tool,
1412
1765
  configPath: "package.json (dependency)",
1413
1766
  label: `${pattern.label} (detected via dependency)`,
1414
- packagePath: packagePath || void 0
1767
+ ...packagePath ? { packagePath } : {}
1415
1768
  };
1416
1769
  }
1417
1770
  return null;
@@ -1419,13 +1772,11 @@ async function detectPattern({
1419
1772
  let isLegacyRspack = false;
1420
1773
  let isLegacyWebpack = false;
1421
1774
  if (pattern.tool === "rspack") {
1422
- const version = resolvedVersion;
1423
- if (version && (version.includes("0.3.") || version.includes("0.2.") || version.includes("0.1."))) {
1775
+ if (isLegacyRspackVersion(resolvedVersion)) {
1424
1776
  isLegacyRspack = true;
1425
1777
  }
1426
1778
  } else if (pattern.tool === "webpack") {
1427
- const version = resolvedVersion;
1428
- if (version && version.includes("^4") || version?.startsWith("4.")) {
1779
+ if (isLegacyWebpackVersion(resolvedVersion)) {
1429
1780
  isLegacyWebpack = true;
1430
1781
  }
1431
1782
  }
@@ -1435,9 +1786,9 @@ async function detectPattern({
1435
1786
  tool: pattern.tool,
1436
1787
  configPath: relativeConfig,
1437
1788
  label: `${pattern.label} (${relativeConfig})${isLegacyRspack ? " [Legacy]" : ""}${isLegacyWebpack ? " [Webpack 4]" : ""}${inferredFromScripts ? " [Scripts Detected]" : ""}`,
1438
- isLegacyRspack,
1439
- isLegacyWebpack,
1440
- packagePath: packagePath || void 0
1789
+ ...isLegacyRspack ? { isLegacyRspack: true } : {},
1790
+ ...isLegacyWebpack ? { isLegacyWebpack: true } : {},
1791
+ ...packagePath ? { packagePath } : {}
1441
1792
  };
1442
1793
  }
1443
1794
  function resolveInjectionTarget(detections) {
@@ -1467,9 +1818,12 @@ var SUPPORTED_FRAMEWORKS = [
1467
1818
  var UNSUPPORTED_FRAMEWORKS = [
1468
1819
  { name: "Solid", dep: "solid-js" },
1469
1820
  { name: "Svelte", dep: "svelte" },
1821
+ { name: "SvelteKit", dep: "@sveltejs/kit" },
1470
1822
  { name: "Angular", dep: "@angular/core" },
1471
1823
  { name: "Preact", dep: "preact" },
1472
- { name: "Lit", dep: "lit" }
1824
+ { name: "Lit", dep: "lit" },
1825
+ { name: "Qwik", dep: "qwik" },
1826
+ { name: "Alpine", dep: "lit-html" }
1473
1827
  ];
1474
1828
  function isPackageResolvable2(pkgName, root) {
1475
1829
  try {
@@ -1685,6 +2039,418 @@ async function buildOnboardingContext(root) {
1685
2039
  };
1686
2040
  }
1687
2041
 
2042
+ // src/onboarding/nextjs-guidance.ts
2043
+ import fs3 from "fs";
2044
+ import path11 from "path";
2045
+ var NEXT_CONFIG_CANDIDATES = ["next.config.ts", "next.config.mjs", "next.config.js"];
2046
+ var APP_ROUTER_LAYOUTS = [
2047
+ "app/layout.tsx",
2048
+ "app/layout.jsx",
2049
+ "src/app/layout.tsx",
2050
+ "src/app/layout.jsx"
2051
+ ];
2052
+ var PAGES_ROUTER_APPS = [
2053
+ "pages/_app.tsx",
2054
+ "pages/_app.jsx",
2055
+ "src/pages/_app.tsx",
2056
+ "src/pages/_app.jsx"
2057
+ ];
2058
+ var PACKAGE_JSON_PATH = "package.json";
2059
+ function findFirstExisting(root, candidates) {
2060
+ for (const candidate of candidates) {
2061
+ if (fs3.existsSync(path11.join(root, candidate))) {
2062
+ return candidate;
2063
+ }
2064
+ }
2065
+ return void 0;
2066
+ }
2067
+ function detectRouterMode(root) {
2068
+ const hasAppRouter = Boolean(findFirstExisting(root, APP_ROUTER_LAYOUTS));
2069
+ const hasPagesRouter = Boolean(findFirstExisting(root, PAGES_ROUTER_APPS));
2070
+ if (hasAppRouter && hasPagesRouter) return "mixed";
2071
+ if (hasAppRouter) return "app";
2072
+ if (hasPagesRouter) return "pages";
2073
+ return "unknown";
2074
+ }
2075
+ function detectNextConfigPath(root) {
2076
+ return findFirstExisting(root, NEXT_CONFIG_CANDIDATES);
2077
+ }
2078
+ function readConfig(root, relativePath) {
2079
+ if (!relativePath) return "";
2080
+ const filePath = path11.join(root, relativePath);
2081
+ if (!fs3.existsSync(filePath)) return "";
2082
+ return fs3.readFileSync(filePath, "utf8");
2083
+ }
2084
+ function detectPatchShape(source) {
2085
+ if (/export\s+default\s*\{[\s\S]*\}/m.test(source) || /module\.exports\s*=\s*\{[\s\S]*\}/m.test(source) || /const\s+[A-Za-z0-9_$]+\s*(?::[^=]+)?=\s*\{[\s\S]*\}\s*;?\s*export\s+default\s+[A-Za-z0-9_$]+\s*;?/m.test(
2086
+ source
2087
+ ) || /\/\*\*[\s\S]*?@type\s*\{import\('next'\)\.NextConfig\}[\s\S]*?\*\/[\s\S]*?(export\s+default|module\.exports)\s*=?\s*\{[\s\S]*\}/m.test(
2088
+ source
2089
+ )) {
2090
+ return {
2091
+ status: "planned",
2092
+ reason: "next_config_object_export",
2093
+ confidence: "high"
2094
+ };
2095
+ }
2096
+ if (/module\.exports\s*=\s*[A-Za-z0-9_$]+\s*\(/m.test(source) || /export\s+default\s+[A-Za-z0-9_$]+\s*\(/m.test(source)) {
2097
+ return {
2098
+ status: "manual_patch_required",
2099
+ reason: "next_config_wrapped_export",
2100
+ confidence: "medium"
2101
+ };
2102
+ }
2103
+ if (source.trim().length === 0) {
2104
+ return {
2105
+ status: "manual_patch_required",
2106
+ reason: "next_config_missing",
2107
+ confidence: "low"
2108
+ };
2109
+ }
2110
+ return {
2111
+ status: "manual_patch_required",
2112
+ reason: "next_config_complex_shape",
2113
+ confidence: "medium"
2114
+ };
2115
+ }
2116
+ function buildPatchSnippet() {
2117
+ return [
2118
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
2119
+ "",
2120
+ "webpack(config, { dev, isServer }) {",
2121
+ " if (dev) {",
2122
+ " config.plugins.push(inspecto())",
2123
+ " }",
2124
+ " return config",
2125
+ "}"
2126
+ ].join("\n");
2127
+ }
2128
+ function buildAppRouterMountSnippet() {
2129
+ return [
2130
+ "import { useEffect } from 'react'",
2131
+ "",
2132
+ "function InspectoProvider() {",
2133
+ " useEffect(() => {",
2134
+ " if (process.env.NODE_ENV !== 'production') {",
2135
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
2136
+ " mountInspector()",
2137
+ " })",
2138
+ " }",
2139
+ " }, [])",
2140
+ "",
2141
+ " return null",
2142
+ "}",
2143
+ "",
2144
+ "Render <InspectoProvider /> from app/layout.tsx inside the <body> tree."
2145
+ ].join("\n");
2146
+ }
2147
+ function buildPagesRouterMountSnippet() {
2148
+ return [
2149
+ "import { useEffect } from 'react'",
2150
+ "",
2151
+ "useEffect(() => {",
2152
+ " if (process.env.NODE_ENV !== 'production') {",
2153
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
2154
+ " mountInspector()",
2155
+ " })",
2156
+ " }",
2157
+ "}, [])",
2158
+ "",
2159
+ "Add this effect to pages/_app.tsx without changing the existing app wrapper behavior."
2160
+ ].join("\n");
2161
+ }
2162
+ function detectWebpackDevScriptPatch(root) {
2163
+ const packageJsonSource = readConfig(root, PACKAGE_JSON_PATH);
2164
+ if (!packageJsonSource) {
2165
+ return void 0;
2166
+ }
2167
+ try {
2168
+ const packageJson = JSON.parse(packageJsonSource);
2169
+ const devScript = packageJson.scripts?.dev;
2170
+ if (!devScript || !/\bnext\s+dev\b/.test(devScript) || /--webpack\b/.test(devScript)) {
2171
+ return void 0;
2172
+ }
2173
+ return {
2174
+ path: PACKAGE_JSON_PATH,
2175
+ status: "manual_patch_required",
2176
+ reason: "next_dev_script_requires_webpack",
2177
+ confidence: "medium",
2178
+ snippet: '"dev": "next dev --webpack"'
2179
+ };
2180
+ } catch {
2181
+ return void 0;
2182
+ }
2183
+ }
2184
+ function buildPendingSteps(routerMode, configPath, needsWebpackDevScript) {
2185
+ const routerHint = routerMode === "app" ? "Complete the remaining client-side mount step for your App Router entry." : routerMode === "pages" ? "Complete the remaining client-side mount step for your Pages Router entry." : routerMode === "mixed" ? "Complete the remaining client-side mount step for the router entry your team actually uses in development." : "Complete the remaining client-side mount step in the appropriate Next.js entry file.";
2186
+ return [
2187
+ `Review the generated Next.js patch plan for ${configPath}.`,
2188
+ ...needsWebpackDevScript ? ["Update the Next.js dev script to use webpack mode for Inspecto validation."] : [],
2189
+ "Keep the Inspecto webpack plugin enabled for both server and client development compilers in App Router projects.",
2190
+ routerHint
2191
+ ];
2192
+ }
2193
+ function createNextJsGuidance(root) {
2194
+ const configPath = detectNextConfigPath(root) ?? "next.config.js";
2195
+ const configSource = readConfig(root, detectNextConfigPath(root));
2196
+ const patchShape = detectPatchShape(configSource);
2197
+ const routerMode = detectRouterMode(root);
2198
+ const webpackDevScriptPatch = detectWebpackDevScriptPatch(root);
2199
+ return {
2200
+ framework: "react",
2201
+ metaFramework: "Next.js",
2202
+ routerMode,
2203
+ autoApplied: ["dependencies", "inspecto_settings"],
2204
+ pendingSteps: buildPendingSteps(routerMode, configPath, Boolean(webpackDevScriptPatch)),
2205
+ assistantPrompt: "Complete the remaining Inspecto onboarding for this Next.js project. Use the generated patches directly, keep existing app behavior unchanged, avoid unrelated documentation searches unless a patch is insufficient, and finish the client-side mount step safely.",
2206
+ patches: [
2207
+ {
2208
+ path: configPath,
2209
+ status: patchShape.status,
2210
+ reason: patchShape.reason,
2211
+ confidence: patchShape.confidence,
2212
+ snippet: buildPatchSnippet()
2213
+ },
2214
+ ...routerMode === "app" || routerMode === "mixed" ? [
2215
+ {
2216
+ path: findFirstExisting(root, APP_ROUTER_LAYOUTS) ?? "app/layout.tsx",
2217
+ status: "manual_patch_required",
2218
+ reason: "next_app_router_mount",
2219
+ confidence: "medium",
2220
+ snippet: buildAppRouterMountSnippet()
2221
+ }
2222
+ ] : [],
2223
+ ...routerMode === "pages" || routerMode === "mixed" ? [
2224
+ {
2225
+ path: findFirstExisting(root, PAGES_ROUTER_APPS) ?? "pages/_app.tsx",
2226
+ status: "manual_patch_required",
2227
+ reason: "next_pages_router_mount",
2228
+ confidence: "medium",
2229
+ snippet: buildPagesRouterMountSnippet()
2230
+ }
2231
+ ] : [],
2232
+ ...webpackDevScriptPatch ? [webpackDevScriptPatch] : []
2233
+ ]
2234
+ };
2235
+ }
2236
+
2237
+ // src/onboarding/nuxt-guidance.ts
2238
+ import fs4 from "fs";
2239
+ import path12 from "path";
2240
+ var NUXT_CONFIG_CANDIDATES = ["nuxt.config.ts", "nuxt.config.js"];
2241
+ function findFirstExisting2(root, candidates) {
2242
+ for (const candidate of candidates) {
2243
+ if (fs4.existsSync(path12.join(root, candidate))) {
2244
+ return candidate;
2245
+ }
2246
+ }
2247
+ return void 0;
2248
+ }
2249
+ function readConfig2(root, relativePath) {
2250
+ if (!relativePath) return "";
2251
+ const filePath = path12.join(root, relativePath);
2252
+ if (!fs4.existsSync(filePath)) return "";
2253
+ return fs4.readFileSync(filePath, "utf8");
2254
+ }
2255
+ function detectPatchShape2(source) {
2256
+ if (/with[A-Za-z0-9_$]*\s*\(\s*defineNuxtConfig/m.test(source)) {
2257
+ return {
2258
+ status: "manual_patch_required",
2259
+ reason: "nuxt_config_wrapped_export",
2260
+ confidence: "medium"
2261
+ };
2262
+ }
2263
+ if (/defineNuxtConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
2264
+ return {
2265
+ status: "planned",
2266
+ reason: "nuxt_config_object_export",
2267
+ confidence: "high"
2268
+ };
2269
+ }
2270
+ if (source.trim().length === 0) {
2271
+ return {
2272
+ status: "manual_patch_required",
2273
+ reason: "nuxt_config_missing",
2274
+ confidence: "low"
2275
+ };
2276
+ }
2277
+ return {
2278
+ status: "manual_patch_required",
2279
+ reason: "nuxt_config_complex_shape",
2280
+ confidence: "medium"
2281
+ };
2282
+ }
2283
+ function buildNuxtConfigSnippet() {
2284
+ return [
2285
+ "import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
2286
+ "",
2287
+ "export default defineNuxtConfig({",
2288
+ " vite: {",
2289
+ " plugins: [inspecto()],",
2290
+ " },",
2291
+ "})"
2292
+ ].join("\n");
2293
+ }
2294
+ function buildNuxtPluginSnippet() {
2295
+ return [
2296
+ "export default defineNuxtPlugin(() => {",
2297
+ " if (import.meta.dev) {",
2298
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
2299
+ " mountInspector()",
2300
+ " })",
2301
+ " }",
2302
+ "})"
2303
+ ].join("\n");
2304
+ }
2305
+ function createNuxtGuidance(root) {
2306
+ const configPath = findFirstExisting2(root, NUXT_CONFIG_CANDIDATES) ?? "nuxt.config.ts";
2307
+ const configSource = readConfig2(root, findFirstExisting2(root, NUXT_CONFIG_CANDIDATES));
2308
+ const patchShape = detectPatchShape2(configSource);
2309
+ const hasSrcDir = fs4.existsSync(path12.join(root, "src")) && fs4.statSync(path12.join(root, "src")).isDirectory();
2310
+ const pluginPath = hasSrcDir ? "src/plugins/inspecto.client.ts" : "plugins/inspecto.client.ts";
2311
+ return {
2312
+ framework: "vue",
2313
+ metaFramework: "Nuxt",
2314
+ autoApplied: ["dependencies", "inspecto_settings"],
2315
+ pendingSteps: [
2316
+ `Review the generated Nuxt patch plan for ${configPath}.`,
2317
+ `Complete the remaining Nuxt client plugin mount step in ${pluginPath}.`
2318
+ ],
2319
+ assistantPrompt: "Complete the remaining Inspecto onboarding for this Nuxt project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client plugin mount step safely.",
2320
+ patches: [
2321
+ {
2322
+ path: configPath,
2323
+ status: patchShape.status,
2324
+ reason: patchShape.reason,
2325
+ confidence: patchShape.confidence,
2326
+ snippet: buildNuxtConfigSnippet()
2327
+ },
2328
+ {
2329
+ path: pluginPath,
2330
+ status: "manual_patch_required",
2331
+ reason: "nuxt_client_plugin_mount",
2332
+ confidence: "medium",
2333
+ snippet: buildNuxtPluginSnippet()
2334
+ }
2335
+ ]
2336
+ };
2337
+ }
2338
+
2339
+ // src/onboarding/umi-guidance.ts
2340
+ import fs5 from "fs";
2341
+ import path13 from "path";
2342
+ var UMI_CONFIG_CANDIDATES = [
2343
+ ".umirc.ts",
2344
+ ".umirc.js",
2345
+ "config/config.ts",
2346
+ "config/config.js"
2347
+ ];
2348
+ function findFirstExisting3(root, candidates) {
2349
+ for (const candidate of candidates) {
2350
+ if (fs5.existsSync(path13.join(root, candidate))) {
2351
+ return candidate;
2352
+ }
2353
+ }
2354
+ return void 0;
2355
+ }
2356
+ function readConfig3(root, relativePath) {
2357
+ if (!relativePath) return "";
2358
+ const filePath = path13.join(root, relativePath);
2359
+ if (!fs5.existsSync(filePath)) return "";
2360
+ return fs5.readFileSync(filePath, "utf8");
2361
+ }
2362
+ function detectPatchShape3(source) {
2363
+ if (/defineConfig\s*\(\s*\{[\s\S]*\}\s*\)/m.test(source)) {
2364
+ return {
2365
+ status: "planned",
2366
+ reason: "umi_config_object_export",
2367
+ confidence: "high"
2368
+ };
2369
+ }
2370
+ if (source.trim().length === 0) {
2371
+ return {
2372
+ status: "manual_patch_required",
2373
+ reason: "umi_config_missing",
2374
+ confidence: "low"
2375
+ };
2376
+ }
2377
+ return {
2378
+ status: "manual_patch_required",
2379
+ reason: "umi_config_complex_shape",
2380
+ confidence: "medium"
2381
+ };
2382
+ }
2383
+ function buildUmiConfigSnippet() {
2384
+ return [
2385
+ "import { defineConfig } from 'umi'",
2386
+ "import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
2387
+ "",
2388
+ "export default defineConfig({",
2389
+ " chainWebpack(memo) {",
2390
+ " if (process.env.NODE_ENV === 'development') {",
2391
+ " memo.plugin('inspecto').use(webpack4Plugin())",
2392
+ " }",
2393
+ " },",
2394
+ "})"
2395
+ ].join("\n");
2396
+ }
2397
+ function buildUmiMountSnippet() {
2398
+ return [
2399
+ "import { useEffect } from 'react'",
2400
+ "",
2401
+ "export function rootContainer(container: React.ReactNode) {",
2402
+ " return (",
2403
+ " <InspectoWrapper>{container}</InspectoWrapper>",
2404
+ " )",
2405
+ "}",
2406
+ "",
2407
+ "function InspectoWrapper({ children }: { children: React.ReactNode }) {",
2408
+ " useEffect(() => {",
2409
+ " if (process.env.NODE_ENV !== 'production') {",
2410
+ " import('@inspecto-dev/core').then(({ mountInspector }) => {",
2411
+ " mountInspector({",
2412
+ " serverUrl: 'http://127.0.0.1:' + ((window as any).__AI_INSPECTOR_PORT__ || 5678),",
2413
+ " })",
2414
+ " })",
2415
+ " }",
2416
+ " }, [])",
2417
+ "",
2418
+ " return <>{children}</>",
2419
+ "}"
2420
+ ].join("\n");
2421
+ }
2422
+ function createUmiGuidance(root) {
2423
+ const configPath = findFirstExisting3(root, UMI_CONFIG_CANDIDATES) ?? ".umirc.ts";
2424
+ const configSource = readConfig3(root, findFirstExisting3(root, UMI_CONFIG_CANDIDATES));
2425
+ const patchShape = detectPatchShape3(configSource);
2426
+ return {
2427
+ framework: "react",
2428
+ metaFramework: "Umi",
2429
+ autoApplied: ["dependencies", "inspecto_settings"],
2430
+ pendingSteps: [
2431
+ `Review the generated Umi patch plan for ${configPath}.`,
2432
+ "Complete the remaining client-side mount step in src/app.tsx."
2433
+ ],
2434
+ assistantPrompt: "Complete the remaining Inspecto onboarding for this Umi project. Review the generated patch plan, keep existing app behavior unchanged, and finish the client-side mount step safely.",
2435
+ patches: [
2436
+ {
2437
+ path: configPath,
2438
+ status: patchShape.status,
2439
+ reason: patchShape.reason,
2440
+ confidence: patchShape.confidence,
2441
+ snippet: buildUmiConfigSnippet()
2442
+ },
2443
+ {
2444
+ path: "src/app.tsx",
2445
+ status: "manual_patch_required",
2446
+ reason: "umi_app_mount",
2447
+ confidence: "medium",
2448
+ snippet: buildUmiMountSnippet()
2449
+ }
2450
+ ]
2451
+ };
2452
+ }
2453
+
1688
2454
  // src/onboarding/planner.ts
1689
2455
  function message(code, message2) {
1690
2456
  return { code, message: message2 };
@@ -1711,10 +2477,16 @@ function planStatus(warnings, blockers) {
1711
2477
  function supportedIde(context) {
1712
2478
  return context.ides.find((ide) => ide.supported)?.ide;
1713
2479
  }
2480
+ function shouldInstallInspectoExtension(ide) {
2481
+ return Boolean(ide && isSupportedHostIde(ide));
2482
+ }
1714
2483
  function supportedProvider(context) {
1715
2484
  return context.providers.find((provider) => provider.supported)?.id;
1716
2485
  }
1717
2486
  function buildToolBlockers(context) {
2487
+ if (isGuidedMetaFrameworkScenario(context)) {
2488
+ return [];
2489
+ }
1718
2490
  if (context.buildTools.unsupported.length > 0) {
1719
2491
  return [
1720
2492
  message(
@@ -1737,6 +2509,28 @@ function buildToolBlockers(context) {
1737
2509
  }
1738
2510
  return [message("missing-build-tool", "No supported build tool detected")];
1739
2511
  }
2512
+ function buildToolWarnings(context) {
2513
+ const warnings = [];
2514
+ if (context.buildTools.supported.length === 1) {
2515
+ const buildTool = context.buildTools.supported[0];
2516
+ if (buildTool.tool === "rspack" && buildTool.isLegacyRspack) {
2517
+ warnings.push(
2518
+ message(
2519
+ "legacy-rspack-requires-manual-config",
2520
+ `Legacy Rspack detected at ${buildTool.configPath}. Inspecto must use the legacy Rspack plugin entry and manual config steps.`
2521
+ )
2522
+ );
2523
+ } else if (buildTool.tool === "webpack" && buildTool.isLegacyWebpack) {
2524
+ warnings.push(
2525
+ message(
2526
+ "legacy-webpack4-requires-manual-config",
2527
+ `Webpack 4 detected at ${buildTool.configPath}. Inspecto must use the legacy Webpack 4 plugin entry and manual config steps.`
2528
+ )
2529
+ );
2530
+ }
2531
+ }
2532
+ return warnings;
2533
+ }
1740
2534
  function frameworkBlockers(context) {
1741
2535
  if (context.frameworks.supported.length > 0) {
1742
2536
  return [];
@@ -1798,6 +2592,35 @@ function manualBuildToolActions(context) {
1798
2592
  }
1799
2593
  ];
1800
2594
  }
2595
+ const buildTool = context.buildTools.supported[0];
2596
+ if (buildTool?.tool === "rspack" && buildTool.isLegacyRspack) {
2597
+ return [
2598
+ {
2599
+ type: "install_dependency",
2600
+ target: "@inspecto-dev/plugin @inspecto-dev/core",
2601
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`
2602
+ },
2603
+ {
2604
+ type: "manual_step",
2605
+ target: buildTool.configPath,
2606
+ description: `Update ${buildTool.configPath} to import \`rspackPlugin\` from \`@inspecto-dev/plugin/legacy/rspack\` and add it to the Rspack plugins array.`
2607
+ }
2608
+ ];
2609
+ }
2610
+ if (buildTool?.tool === "webpack" && buildTool.isLegacyWebpack) {
2611
+ return [
2612
+ {
2613
+ type: "install_dependency",
2614
+ target: "@inspecto-dev/plugin @inspecto-dev/core",
2615
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`
2616
+ },
2617
+ {
2618
+ type: "manual_step",
2619
+ target: buildTool.configPath,
2620
+ description: `Update ${buildTool.configPath} to import \`webpackPlugin\` from \`@inspecto-dev/plugin/legacy/webpack4\` and add it to the Webpack plugins array.`
2621
+ }
2622
+ ];
2623
+ }
1801
2624
  return [
1802
2625
  {
1803
2626
  type: "manual_step",
@@ -1806,6 +2629,46 @@ function manualBuildToolActions(context) {
1806
2629
  }
1807
2630
  ];
1808
2631
  }
2632
+ function hasUnsupportedBuildTool(context, buildTool) {
2633
+ return context.buildTools.unsupported.includes(buildTool);
2634
+ }
2635
+ function isGuidedNextJsScenario(context) {
2636
+ return context.buildTools.supported.length === 0 && hasUnsupportedBuildTool(context, "Next.js") && context.frameworks.supported.includes("react");
2637
+ }
2638
+ function isGuidedNuxtScenario(context) {
2639
+ return context.buildTools.supported.length === 0 && hasUnsupportedBuildTool(context, "Nuxt") && context.frameworks.supported.includes("vue");
2640
+ }
2641
+ function isGuidedUmiScenario(context) {
2642
+ return hasUnsupportedBuildTool(context, "Umi") && context.frameworks.supported.includes("react");
2643
+ }
2644
+ function isGuidedMetaFrameworkScenario(context) {
2645
+ return isGuidedNextJsScenario(context) || isGuidedNuxtScenario(context) || isGuidedUmiScenario(context);
2646
+ }
2647
+ function guidedBuildToolWarnings(context, guidedBuildTool) {
2648
+ return context.buildTools.unsupported.filter((buildTool) => buildTool !== guidedBuildTool).map(
2649
+ (buildTool) => message(
2650
+ "additional-unsupported-build-tool",
2651
+ `Additional unsupported build tool also detected: ${buildTool}`
2652
+ )
2653
+ );
2654
+ }
2655
+ function guidedFrameworkWarnings(context, framework) {
2656
+ return context.frameworks.unsupported.filter((item) => item !== framework).map(
2657
+ (item) => message(
2658
+ "additional-unsupported-framework",
2659
+ `Additional unsupported framework also detected: ${item}`
2660
+ )
2661
+ );
2662
+ }
2663
+ function buildGuidedWarnings(context, guidedBuildTool, guidedFramework) {
2664
+ return uniqueMessages([
2665
+ ...guidedBuildToolWarnings(context, guidedBuildTool),
2666
+ ...guidedFrameworkWarnings(context, guidedFramework),
2667
+ ...unsupportedEnvironmentWarnings(context)
2668
+ ]).filter(
2669
+ (warning) => warning.message !== `Unsupported framework(s) also detected: ${context.frameworks.unsupported.join(", ")}`
2670
+ );
2671
+ }
1809
2672
  function manualFrameworkActions(context) {
1810
2673
  if (context.frameworks.unsupported.length > 0) {
1811
2674
  return [
@@ -1826,7 +2689,10 @@ function manualFrameworkActions(context) {
1826
2689
  }
1827
2690
  async function createDetectionResult(root) {
1828
2691
  const context = await buildOnboardingContext(root);
1829
- const warnings = uniqueMessages([...unsupportedEnvironmentWarnings(context)]);
2692
+ const warnings = uniqueMessages([
2693
+ ...unsupportedEnvironmentWarnings(context),
2694
+ ...buildToolWarnings(context)
2695
+ ]);
1830
2696
  const buildToolResult = buildToolBlockers(context);
1831
2697
  const frameworkResult = frameworkBlockers(context);
1832
2698
  const blockers = uniqueMessages([...buildToolResult, ...frameworkResult]);
@@ -1849,13 +2715,169 @@ async function createDetectionResult(root) {
1849
2715
  };
1850
2716
  }
1851
2717
  function createPlanResult(context) {
1852
- const warnings = uniqueMessages(unsupportedEnvironmentWarnings(context));
2718
+ if (isGuidedNextJsScenario(context)) {
2719
+ const ide2 = supportedIde(context);
2720
+ const provider2 = supportedProvider(context);
2721
+ const guidance = createNextJsGuidance(context.root);
2722
+ const actions2 = [
2723
+ {
2724
+ type: "install_dependency",
2725
+ target: "@inspecto-dev/plugin @inspecto-dev/core",
2726
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`
2727
+ }
2728
+ ];
2729
+ if (shouldInstallInspectoExtension(ide2) && ide2) {
2730
+ actions2.push({
2731
+ type: "install_extension",
2732
+ target: ide2,
2733
+ description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
2734
+ });
2735
+ }
2736
+ actions2.push(
2737
+ {
2738
+ type: "generate_patch_plan",
2739
+ target: "next.config",
2740
+ description: "Generate a guided patch plan for the Next.js Inspecto webpack integration."
2741
+ },
2742
+ {
2743
+ type: "manual_confirmation",
2744
+ target: context.root,
2745
+ description: "Complete the remaining client-side Inspecto mount step in your assistant or editor."
2746
+ }
2747
+ );
2748
+ const defaults2 = {
2749
+ shared: false,
2750
+ extension: shouldInstallInspectoExtension(ide2),
2751
+ ...provider2 ? { provider: provider2 } : {},
2752
+ ...ide2 ? { ide: ide2 } : {}
2753
+ };
2754
+ return {
2755
+ status: "warning",
2756
+ warnings: buildGuidedWarnings(context, "Next.js", "react"),
2757
+ blockers: [],
2758
+ strategy: "guided",
2759
+ actions: actions2,
2760
+ defaults: defaults2,
2761
+ framework: guidance.framework,
2762
+ metaFramework: guidance.metaFramework,
2763
+ routerMode: guidance.routerMode,
2764
+ autoApplied: guidance.autoApplied,
2765
+ pendingSteps: guidance.pendingSteps,
2766
+ assistantPrompt: guidance.assistantPrompt,
2767
+ patches: guidance.patches
2768
+ };
2769
+ }
2770
+ if (isGuidedNuxtScenario(context)) {
2771
+ const ide2 = supportedIde(context);
2772
+ const provider2 = supportedProvider(context);
2773
+ const guidance = createNuxtGuidance(context.root);
2774
+ const actions2 = [
2775
+ {
2776
+ type: "install_dependency",
2777
+ target: "@inspecto-dev/plugin @inspecto-dev/core",
2778
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`
2779
+ }
2780
+ ];
2781
+ if (shouldInstallInspectoExtension(ide2) && ide2) {
2782
+ actions2.push({
2783
+ type: "install_extension",
2784
+ target: ide2,
2785
+ description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
2786
+ });
2787
+ }
2788
+ actions2.push(
2789
+ {
2790
+ type: "generate_patch_plan",
2791
+ target: "nuxt.config",
2792
+ description: "Generate a guided patch plan for the Nuxt Inspecto Vite integration."
2793
+ },
2794
+ {
2795
+ type: "manual_confirmation",
2796
+ target: context.root,
2797
+ description: "Complete the remaining Nuxt client plugin mount step in your assistant or editor."
2798
+ }
2799
+ );
2800
+ const defaults2 = {
2801
+ shared: false,
2802
+ extension: shouldInstallInspectoExtension(ide2),
2803
+ ...provider2 ? { provider: provider2 } : {},
2804
+ ...ide2 ? { ide: ide2 } : {}
2805
+ };
2806
+ return {
2807
+ status: "warning",
2808
+ warnings: buildGuidedWarnings(context, "Nuxt", "vue"),
2809
+ blockers: [],
2810
+ strategy: "guided",
2811
+ actions: actions2,
2812
+ defaults: defaults2,
2813
+ framework: guidance.framework,
2814
+ metaFramework: guidance.metaFramework,
2815
+ autoApplied: guidance.autoApplied,
2816
+ pendingSteps: guidance.pendingSteps,
2817
+ assistantPrompt: guidance.assistantPrompt,
2818
+ patches: guidance.patches
2819
+ };
2820
+ }
2821
+ if (isGuidedUmiScenario(context)) {
2822
+ const ide2 = supportedIde(context);
2823
+ const provider2 = supportedProvider(context);
2824
+ const guidance = createUmiGuidance(context.root);
2825
+ const actions2 = [
2826
+ {
2827
+ type: "install_dependency",
2828
+ target: "@inspecto-dev/plugin @inspecto-dev/core",
2829
+ description: `Install the Inspecto runtime packages with ${context.packageManager}.`
2830
+ }
2831
+ ];
2832
+ if (shouldInstallInspectoExtension(ide2) && ide2) {
2833
+ actions2.push({
2834
+ type: "install_extension",
2835
+ target: ide2,
2836
+ description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
2837
+ });
2838
+ }
2839
+ actions2.push({
2840
+ type: "generate_patch_plan",
2841
+ target: "umi.config",
2842
+ description: "Generate a guided patch plan for the Umi Inspecto webpack integration."
2843
+ });
2844
+ const defaults2 = {
2845
+ shared: false,
2846
+ extension: shouldInstallInspectoExtension(ide2),
2847
+ ...provider2 ? { provider: provider2 } : {},
2848
+ ...ide2 ? { ide: ide2 } : {}
2849
+ };
2850
+ return {
2851
+ status: "warning",
2852
+ warnings: buildGuidedWarnings(context, "Umi", "react"),
2853
+ blockers: [],
2854
+ strategy: "guided",
2855
+ actions: actions2,
2856
+ defaults: defaults2,
2857
+ framework: guidance.framework,
2858
+ metaFramework: guidance.metaFramework,
2859
+ autoApplied: guidance.autoApplied,
2860
+ pendingSteps: guidance.pendingSteps,
2861
+ assistantPrompt: guidance.assistantPrompt,
2862
+ patches: guidance.patches
2863
+ };
2864
+ }
2865
+ const warnings = uniqueMessages([
2866
+ ...unsupportedEnvironmentWarnings(context),
2867
+ ...buildToolWarnings(context)
2868
+ ]);
1853
2869
  const blockers = uniqueMessages([...buildToolBlockers(context), ...frameworkBlockers(context)]);
1854
2870
  const actions = [];
1855
2871
  let strategy = "supported";
1856
- if (blockers.length > 0) {
2872
+ const hasLegacyRspackManualPlan = context.buildTools.supported.length === 1 && context.buildTools.supported[0]?.tool === "rspack" && context.buildTools.supported[0]?.isLegacyRspack;
2873
+ const hasLegacyWebpackManualPlan = context.buildTools.supported.length === 1 && context.buildTools.supported[0]?.tool === "webpack" && context.buildTools.supported[0]?.isLegacyWebpack;
2874
+ if (blockers.length > 0 || hasLegacyRspackManualPlan || hasLegacyWebpackManualPlan) {
1857
2875
  strategy = "manual";
1858
- if (context.buildTools.unsupported.length > 0 || context.buildTools.supported.length === 0 || context.buildTools.supported.length > 1) {
2876
+ if (context.buildTools.unsupported.length > 0 || context.buildTools.supported.length === 0 || context.buildTools.supported.length > 1 || context.buildTools.supported.some(
2877
+ (buildTool) => buildTool.tool === "rspack" && buildTool.isLegacyRspack
2878
+ ) || context.buildTools.supported.some(
2879
+ (buildTool) => buildTool.tool === "webpack" && buildTool.isLegacyWebpack
2880
+ )) {
1859
2881
  actions.push(...manualBuildToolActions(context));
1860
2882
  }
1861
2883
  if (frameworkBlockers(context).length > 0) {
@@ -1875,23 +2897,23 @@ function createPlanResult(context) {
1875
2897
  });
1876
2898
  }
1877
2899
  const ide2 = supportedIde(context);
1878
- if (ide2 === "vscode") {
2900
+ if (shouldInstallInspectoExtension(ide2) && ide2) {
1879
2901
  actions.push({
1880
2902
  type: "install_extension",
1881
- target: "vscode",
1882
- description: "Install the Inspecto VS Code extension."
2903
+ target: ide2,
2904
+ description: `Install the Inspecto ${getHostIdeLabel(ide2)} extension.`
1883
2905
  });
1884
2906
  }
1885
2907
  }
2908
+ const ide = supportedIde(context);
1886
2909
  const defaults = {
1887
2910
  shared: false,
1888
- extension: supportedIde(context) === "vscode"
2911
+ extension: shouldInstallInspectoExtension(ide)
1889
2912
  };
1890
2913
  const provider = supportedProvider(context);
1891
2914
  if (provider) {
1892
2915
  defaults.provider = provider;
1893
2916
  }
1894
- const ide = supportedIde(context);
1895
2917
  if (ide) {
1896
2918
  defaults.ide = ide;
1897
2919
  }
@@ -1905,7 +2927,11 @@ function createPlanResult(context) {
1905
2927
  };
1906
2928
  }
1907
2929
  function planManualFollowUp(result) {
1908
- return result.actions.filter((action) => action.type === "manual_step").map((action) => action.description);
2930
+ return result.actions.filter(
2931
+ (action) => ["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
2932
+ action.type
2933
+ )
2934
+ ).map((action) => action.description);
1909
2935
  }
1910
2936
 
1911
2937
  // src/commands/apply.ts
@@ -2036,14 +3062,107 @@ async function detect(json = false) {
2036
3062
  return writeCommandOutput(result, json, printDetectionResult);
2037
3063
  }
2038
3064
 
3065
+ // src/commands/dev-config.ts
3066
+ import path14 from "path";
3067
+ var DEV_CONFIG_PATH = path14.join(".inspecto", "dev.json");
3068
+ function absoluteDevConfigPath(root) {
3069
+ return path14.join(root, DEV_CONFIG_PATH);
3070
+ }
3071
+ function printDevConfigResult(result) {
3072
+ log.header("Inspecto Dev");
3073
+ log.info(`Config: ${result.configPath}`);
3074
+ if (result.config.cliBin) {
3075
+ log.hint(`cliBin: ${result.config.cliBin}`);
3076
+ }
3077
+ if (result.config.devRepo) {
3078
+ log.hint(`devRepo: ${result.config.devRepo}`);
3079
+ }
3080
+ if (!result.config.cliBin && !result.config.devRepo) {
3081
+ log.hint("No local dev overrides are configured.");
3082
+ }
3083
+ }
3084
+ async function readExistingConfig(root) {
3085
+ const configPath = absoluteDevConfigPath(root);
3086
+ const config = await readJSON(configPath);
3087
+ if (!config || typeof config !== "object") {
3088
+ return {};
3089
+ }
3090
+ return config;
3091
+ }
3092
+ async function devLink(options) {
3093
+ const root = process.cwd();
3094
+ const configPath = absoluteDevConfigPath(root);
3095
+ const existing = await readExistingConfig(root);
3096
+ const nextConfig = {
3097
+ ...existing,
3098
+ ...options.cliBin ? { cliBin: options.cliBin } : {},
3099
+ ...options.devRepo ? { devRepo: options.devRepo } : {}
3100
+ };
3101
+ await writeJSON(configPath, nextConfig);
3102
+ await updateGitignore(root, false, false, true);
3103
+ return writeCommandOutput(
3104
+ {
3105
+ status: "ok",
3106
+ configPath,
3107
+ config: nextConfig
3108
+ },
3109
+ options.json ?? false,
3110
+ printDevConfigResult
3111
+ );
3112
+ }
3113
+ async function devStatus(json = false) {
3114
+ const root = process.cwd();
3115
+ const configPath = absoluteDevConfigPath(root);
3116
+ const config = await readExistingConfig(root);
3117
+ return writeCommandOutput(
3118
+ {
3119
+ status: "ok",
3120
+ configPath,
3121
+ config
3122
+ },
3123
+ json,
3124
+ printDevConfigResult
3125
+ );
3126
+ }
3127
+ async function devUnlink(json = false) {
3128
+ const root = process.cwd();
3129
+ const configPath = absoluteDevConfigPath(root);
3130
+ await removeFile(configPath);
3131
+ return writeCommandOutput(
3132
+ {
3133
+ status: "ok",
3134
+ configPath,
3135
+ config: {}
3136
+ },
3137
+ json,
3138
+ printDevConfigResult
3139
+ );
3140
+ }
3141
+
2039
3142
  // src/commands/init.ts
2040
- import path11 from "path";
3143
+ import path15 from "path";
2041
3144
 
2042
3145
  // src/onboarding/target-resolution.ts
3146
+ function buildCandidateId(candidate) {
3147
+ return [candidate.packagePath || ".", candidate.buildTool, candidate.configPath].join(":");
3148
+ }
2043
3149
  function normalizePackagePath(packagePath) {
2044
3150
  if (!packagePath || packagePath === ".") return "";
2045
3151
  return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
2046
3152
  }
3153
+ function normalizeTargetValue(target) {
3154
+ if (!target) return "";
3155
+ return target.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
3156
+ }
3157
+ function buildSelectionPurpose() {
3158
+ return "Choose the build target that runs your local development build so Inspecto can attach the right plugin and settings.";
3159
+ }
3160
+ function buildSelectionInstructions(hasCandidates) {
3161
+ if (!hasCandidates) {
3162
+ return "If auto-detection missed your build entrypoint, rerun with --target <configPath> using the config file or wrapper script your dev command actually starts.";
3163
+ }
3164
+ return "Rerun with --target <candidateId> using one returned candidateId. The CLI also accepts an exact configPath from the candidate list as a compatibility fallback.";
3165
+ }
2047
3166
  function looksLikeAppPackage(packagePath) {
2048
3167
  if (!packagePath) return true;
2049
3168
  return /(^|\/)(app|apps|web|client|frontend|site)(\/|$)/i.test(packagePath);
@@ -2054,13 +3173,19 @@ function looksLikeAuxiliaryPackage(packagePath) {
2054
3173
  function buildCandidates(input) {
2055
3174
  return input.buildTools.map((buildTool) => {
2056
3175
  const packagePath = normalizePackagePath(buildTool.packagePath);
2057
- return {
3176
+ const candidate = {
2058
3177
  packagePath,
2059
3178
  configPath: buildTool.configPath,
3179
+ label: buildTool.label,
2060
3180
  buildTool: buildTool.tool,
3181
+ ...buildTool.isLegacyRspack ? { isLegacyRspack: true } : {},
3182
+ ...buildTool.isLegacyWebpack ? { isLegacyWebpack: true } : {},
2061
3183
  frameworks: input.frameworkSupportByPackage[packagePath] ?? [],
2062
3184
  automaticInjection: true
2063
3185
  };
3186
+ candidate.id = buildCandidateId(candidate);
3187
+ candidate.candidateId = candidate.id;
3188
+ return candidate;
2064
3189
  });
2065
3190
  }
2066
3191
  function rankCandidate(candidate) {
@@ -2083,18 +3208,52 @@ function resolveOnboardingTarget(input) {
2083
3208
  return {
2084
3209
  status: "needs_selection",
2085
3210
  candidates,
2086
- reason: "No supported targets were detected."
3211
+ reason: "No supported targets were detected.",
3212
+ selectionPurpose: buildSelectionPurpose(),
3213
+ selectionInstructions: buildSelectionInstructions(false)
2087
3214
  };
2088
3215
  }
2089
3216
  const explicitlySelected = normalizePackagePath(input.selectedPackagePath);
3217
+ const explicitlySelectedValue = normalizeTargetValue(input.selectedPackagePath);
2090
3218
  if (input.selectedPackagePath !== void 0) {
2091
- const selected = candidates.find((candidate) => candidate.packagePath === explicitlySelected);
3219
+ const selectedById = candidates.find(
3220
+ (candidate) => candidate.id === input.selectedPackagePath || candidate.candidateId === input.selectedPackagePath
3221
+ );
3222
+ if (selectedById) {
3223
+ return {
3224
+ status: "resolved",
3225
+ selected: selectedById,
3226
+ candidates,
3227
+ reason: `Using the explicitly selected target: ${selectedById.configPath}.`,
3228
+ selectionPurpose: buildSelectionPurpose(),
3229
+ selectionInstructions: buildSelectionInstructions(true)
3230
+ };
3231
+ }
3232
+ const selectedByConfigPath = candidates.find(
3233
+ (candidate) => normalizeTargetValue(candidate.configPath) === explicitlySelectedValue
3234
+ );
3235
+ if (selectedByConfigPath) {
3236
+ return {
3237
+ status: "resolved",
3238
+ selected: selectedByConfigPath,
3239
+ candidates,
3240
+ reason: `Using the explicitly selected config path: ${selectedByConfigPath.configPath}.`,
3241
+ selectionPurpose: buildSelectionPurpose(),
3242
+ selectionInstructions: buildSelectionInstructions(true)
3243
+ };
3244
+ }
3245
+ const matchingPackageCandidates = candidates.filter(
3246
+ (candidate) => candidate.packagePath === explicitlySelected
3247
+ );
3248
+ const selected = matchingPackageCandidates.length === 1 ? matchingPackageCandidates[0] : void 0;
2092
3249
  if (selected) {
2093
3250
  return {
2094
3251
  status: "resolved",
2095
3252
  selected,
2096
3253
  candidates,
2097
- reason: `Using the explicitly selected target: ${selected.packagePath || "."}.`
3254
+ reason: `Using the explicitly selected target: ${selected.packagePath || "."}.`,
3255
+ selectionPurpose: buildSelectionPurpose(),
3256
+ selectionInstructions: buildSelectionInstructions(true)
2098
3257
  };
2099
3258
  }
2100
3259
  }
@@ -2103,7 +3262,9 @@ function resolveOnboardingTarget(input) {
2103
3262
  status: "resolved",
2104
3263
  selected: candidates[0],
2105
3264
  candidates,
2106
- reason: "Only one supported target was detected."
3265
+ reason: "Only one supported target was detected.",
3266
+ selectionPurpose: buildSelectionPurpose(),
3267
+ selectionInstructions: buildSelectionInstructions(true)
2107
3268
  };
2108
3269
  }
2109
3270
  const ranked = rankCandidates(candidates);
@@ -2111,14 +3272,18 @@ function resolveOnboardingTarget(input) {
2111
3272
  return {
2112
3273
  status: "needs_selection",
2113
3274
  candidates,
2114
- reason: "Multiple supported targets look equally plausible."
3275
+ reason: "Multiple supported targets look equally plausible.",
3276
+ selectionPurpose: buildSelectionPurpose(),
3277
+ selectionInstructions: buildSelectionInstructions(true)
2115
3278
  };
2116
3279
  }
2117
3280
  return {
2118
3281
  status: "resolved",
2119
3282
  selected: ranked[0].candidate,
2120
3283
  candidates,
2121
- reason: `Preselected ${ranked[0].candidate.packagePath || "."} because it has the strongest supported app signal.`
3284
+ reason: `Preselected ${ranked[0].candidate.packagePath || "."} because it has the strongest supported app signal.`,
3285
+ selectionPurpose: buildSelectionPurpose(),
3286
+ selectionInstructions: buildSelectionInstructions(true)
2122
3287
  };
2123
3288
  }
2124
3289
 
@@ -2236,7 +3401,9 @@ async function promptUnsupportedFrameworkContinue() {
2236
3401
  // src/instructions.ts
2237
3402
  function printNuxtManualInstructions() {
2238
3403
  log.blank();
2239
- log.hint("Nuxt requires manual setup in the current version.");
3404
+ log.hint(
3405
+ "Nuxt supports guided setup in the current version. Inspecto can prepare the config patch, but the client plugin mount step still needs review."
3406
+ );
2240
3407
  log.hint("1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:");
2241
3408
  log.copyableCodeBlock([
2242
3409
  "import { vitePlugin as inspecto } from '@inspecto-dev/plugin'",
@@ -2247,7 +3414,7 @@ function printNuxtManualInstructions() {
2247
3414
  " },",
2248
3415
  "})"
2249
3416
  ]);
2250
- log.hint("2. Create `plugins/inspecto.client.ts` to mount `@inspecto-dev/core` in development:");
3417
+ log.hint("2. Complete the remaining client plugin mount step in `plugins/inspecto.client.ts`:");
2251
3418
  log.copyableCodeBlock([
2252
3419
  "export default defineNuxtPlugin(() => {",
2253
3420
  " if (import.meta.dev) {",
@@ -2257,11 +3424,31 @@ function printNuxtManualInstructions() {
2257
3424
  " }",
2258
3425
  "})"
2259
3426
  ]);
2260
- log.hint("3. Restart your Nuxt dev server after updating the config.");
3427
+ log.hint("3. Restart your Nuxt dev server after applying the guided patches.");
3428
+ }
3429
+ function printUmiManualInstructions() {
3430
+ log.blank();
3431
+ log.hint("Umi supports guided setup in the current version.");
3432
+ log.hint("1. Update `config/config.ts` or `.umirc.ts` to register the Inspecto webpack plugin:");
3433
+ log.copyableCodeBlock([
3434
+ "import { defineConfig } from 'umi'",
3435
+ "import { webpack4Plugin } from '@inspecto-dev/plugin/legacy/webpack4'",
3436
+ "",
3437
+ "export default defineConfig({",
3438
+ " chainWebpack(memo) {",
3439
+ " if (process.env.NODE_ENV === 'development') {",
3440
+ " memo.plugin('inspecto').use(webpack4Plugin())",
3441
+ " }",
3442
+ " },",
3443
+ "})"
3444
+ ]);
3445
+ log.hint("2. Restart your Umi dev server after applying the guided patches.");
2261
3446
  }
2262
3447
  function printNextJsManualInstructions() {
2263
3448
  log.blank();
2264
- log.hint("Next.js requires manual setup in the current version.");
3449
+ log.hint(
3450
+ "Next.js supports guided setup in the current version. Inspecto can prepare the config patch, but the client-side mount step still needs review."
3451
+ );
2265
3452
  log.hint("1. Update `next.config.mjs` to register the Inspecto webpack plugin:");
2266
3453
  log.copyableCodeBlock([
2267
3454
  "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'",
@@ -2269,7 +3456,7 @@ function printNextJsManualInstructions() {
2269
3456
  "/** @type {import('next').NextConfig} */",
2270
3457
  "const nextConfig = {",
2271
3458
  " webpack: (config, { dev, isServer }) => {",
2272
- " if (dev && !isServer) {",
3459
+ " if (dev) {",
2273
3460
  " config.plugins.push(inspecto())",
2274
3461
  " }",
2275
3462
  " return config",
@@ -2279,7 +3466,10 @@ function printNextJsManualInstructions() {
2279
3466
  "export default nextConfig"
2280
3467
  ]);
2281
3468
  log.hint(
2282
- "2. Initialize `@inspecto-dev/core` from a client component such as `app/layout.tsx` or `pages/_app.tsx`:"
3469
+ "Keep the plugin enabled for both server and client development compilers so App Router server components also receive Inspecto transforms."
3470
+ );
3471
+ log.hint(
3472
+ "2. Complete the remaining client-side mount step in `app/layout.tsx` or `pages/_app.tsx`:"
2283
3473
  );
2284
3474
  log.copyableCodeBlock([
2285
3475
  "'use client'",
@@ -2298,7 +3488,7 @@ function printNextJsManualInstructions() {
2298
3488
  " return <html><body>{children}</body></html>",
2299
3489
  "}"
2300
3490
  ]);
2301
- log.hint("3. Restart your Next.js dev server after updating the config.");
3491
+ log.hint("3. Restart your Next.js dev server after applying the guided patches.");
2302
3492
  }
2303
3493
 
2304
3494
  // src/commands/init.ts
@@ -2313,7 +3503,7 @@ async function init(options) {
2313
3503
  verifiedPackages.push(pkg);
2314
3504
  continue;
2315
3505
  }
2316
- const absolutePath = path11.join(repoRoot, pkg);
3506
+ const absolutePath = path15.join(repoRoot, pkg);
2317
3507
  if (await exists(absolutePath)) {
2318
3508
  verifiedPackages.push(pkg);
2319
3509
  } else {
@@ -2326,7 +3516,7 @@ async function init(options) {
2326
3516
  return;
2327
3517
  }
2328
3518
  log.header("Inspecto Setup");
2329
- if (!await exists(path11.join(repoRoot, "package.json"))) {
3519
+ if (!await exists(path15.join(repoRoot, "package.json"))) {
2330
3520
  log.error("No package.json found in current directory");
2331
3521
  log.hint("Run this command from your project root");
2332
3522
  return;
@@ -2361,12 +3551,12 @@ async function init(options) {
2361
3551
  log.hint("Run `inspecto init` inside the target app, or pass --packages <app-path>.");
2362
3552
  return;
2363
3553
  }
2364
- projectRoot = path11.join(repoRoot, selectedPackage);
3554
+ projectRoot = path15.join(repoRoot, selectedPackage);
2365
3555
  buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
2366
3556
  log.info(`Continuing initialization in ${selectedPackage}`);
2367
3557
  } else if (targetResolution.selected?.packagePath) {
2368
3558
  const selectedPackage = targetResolution.selected.packagePath;
2369
- projectRoot = path11.join(repoRoot, selectedPackage);
3559
+ projectRoot = path15.join(repoRoot, selectedPackage);
2370
3560
  buildResult = await detectBuildTools(repoRoot, [selectedPackage]);
2371
3561
  log.warn(`Monorepo root detected. Using the only candidate app: ${selectedPackage}`);
2372
3562
  log.hint("Run `inspecto init` inside that app next time to skip this prompt.");
@@ -2426,14 +3616,17 @@ async function init(options) {
2426
3616
  if (buildResult.unsupported.length > 0) {
2427
3617
  const names = buildResult.unsupported.join(", ");
2428
3618
  manualConfigRequiredFor = buildResult.unsupported[0] || "";
2429
- log.warn(`Detected ${names} \u2014 automatic plugin injection is not supported in current version`);
2430
- log.hint("You can still manually configure it by modifying your configuration file");
3619
+ log.warn(`Detected ${names} \u2014 guided onboarding is available in the current version`);
3620
+ log.hint("Inspecto can prepare the remaining patch plan and assistant handoff for this stack.");
2431
3621
  if (buildResult.unsupported.includes("Next.js")) {
2432
3622
  printNextJsManualInstructions();
2433
3623
  }
2434
3624
  if (buildResult.unsupported.includes("Nuxt")) {
2435
3625
  printNuxtManualInstructions();
2436
3626
  }
3627
+ if (buildResult.unsupported.includes("Umi")) {
3628
+ printUmiManualInstructions();
3629
+ }
2437
3630
  }
2438
3631
  if (buildResult.supported.length === 0 && buildResult.unsupported.length === 0) {
2439
3632
  log.warn("No recognized build tool detected");
@@ -2585,7 +3778,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
2585
3778
  const supportByPackage = {};
2586
3779
  await Promise.all(
2587
3780
  packagePaths.map(async (packagePath) => {
2588
- const result = await detectFrameworks(path11.join(repoRoot, packagePath));
3781
+ const result = await detectFrameworks(path15.join(repoRoot, packagePath));
2589
3782
  supportByPackage[packagePath] = result.supported;
2590
3783
  })
2591
3784
  );
@@ -2593,7 +3786,7 @@ async function detectFrameworkSupportByPackage(repoRoot, buildTools) {
2593
3786
  }
2594
3787
 
2595
3788
  // src/commands/doctor.ts
2596
- import path12 from "path";
3789
+ import path16 from "path";
2597
3790
  function createDiagnostic(code, status, message2, hints = [], details) {
2598
3791
  return {
2599
3792
  code,
@@ -2608,6 +3801,94 @@ function doctorStatus(errors, warnings) {
2608
3801
  if (warnings > 0) return "warning";
2609
3802
  return "ok";
2610
3803
  }
3804
+ function isGuidedMetaFramework(buildTool) {
3805
+ return buildTool === "Next.js" || buildTool === "Nuxt";
3806
+ }
3807
+ function buildDoctorOnboardingContext(input) {
3808
+ return {
3809
+ root: input.root,
3810
+ packageManager: input.packageManager,
3811
+ buildTools: input.buildTools,
3812
+ frameworks: {
3813
+ supported: input.frameworks.supported,
3814
+ unsupported: input.frameworks.unsupported.map((item) => item.name)
3815
+ },
3816
+ ides: input.ides.detected.map(({ ide, supported }) => ({ ide, supported })),
3817
+ providers: input.providers.detected.map(({ id, label, supported, preferredMode }) => ({
3818
+ id,
3819
+ label,
3820
+ supported,
3821
+ preferredMode
3822
+ }))
3823
+ };
3824
+ }
3825
+ function isConfigPatchReason(reason) {
3826
+ return reason.startsWith("next_config_") || reason.startsWith("nuxt_config_");
3827
+ }
3828
+ function isNextWebpackDevPatchReason(reason) {
3829
+ return reason === "next_dev_script_requires_webpack";
3830
+ }
3831
+ async function collectGuidedPatchDiagnostics(root, plan2) {
3832
+ if (plan2.strategy !== "guided" || !plan2.patches?.length) {
3833
+ return [];
3834
+ }
3835
+ const diagnostics = [];
3836
+ for (const patch of plan2.patches) {
3837
+ if (isConfigPatchReason(patch.reason)) {
3838
+ const content = await readFile(path16.join(root, patch.path));
3839
+ if (content?.includes("@inspecto-dev/plugin")) {
3840
+ diagnostics.push(
3841
+ createDiagnostic(
3842
+ "guided-config-patch-detected",
3843
+ "ok",
3844
+ `Guided config patch appears to be applied in ${patch.path}`,
3845
+ [],
3846
+ { path: patch.path, reason: patch.reason }
3847
+ )
3848
+ );
3849
+ } else {
3850
+ diagnostics.push(
3851
+ createDiagnostic(
3852
+ "guided-config-patch-pending",
3853
+ "warning",
3854
+ `Guided config patch still needs review in ${patch.path}`,
3855
+ ["Run `inspecto onboard --json` to review the generated patch details."],
3856
+ { path: patch.path, reason: patch.reason }
3857
+ )
3858
+ );
3859
+ }
3860
+ continue;
3861
+ }
3862
+ if (isNextWebpackDevPatchReason(patch.reason)) {
3863
+ const packageJson = await readJSON(
3864
+ path16.join(root, "package.json")
3865
+ );
3866
+ const devScript = packageJson?.scripts?.dev ?? "";
3867
+ if (/next\s+dev\b/.test(devScript) && /--webpack\b/.test(devScript)) {
3868
+ diagnostics.push(
3869
+ createDiagnostic(
3870
+ "guided-dev-script-configured",
3871
+ "ok",
3872
+ "Next.js dev script is configured for webpack mode",
3873
+ [],
3874
+ { script: devScript }
3875
+ )
3876
+ );
3877
+ } else {
3878
+ diagnostics.push(
3879
+ createDiagnostic(
3880
+ "guided-dev-script-pending",
3881
+ "warning",
3882
+ "Next.js dev script still needs webpack mode for Inspecto validation",
3883
+ ["Update the `dev` script to `next dev --webpack` before browser validation."],
3884
+ { script: devScript }
3885
+ )
3886
+ );
3887
+ }
3888
+ }
3889
+ }
3890
+ return diagnostics;
3891
+ }
2611
3892
  function printDoctorResult(result) {
2612
3893
  log.header("Inspecto Doctor");
2613
3894
  for (const check of result.checks) {
@@ -2637,7 +3918,7 @@ function printDoctorResult(result) {
2637
3918
  }
2638
3919
  async function collectDoctorResult(root = process.cwd()) {
2639
3920
  const checks = [];
2640
- if (!await exists(path12.join(root, "package.json"))) {
3921
+ if (!await exists(path16.join(root, "package.json"))) {
2641
3922
  const diagnostic = createDiagnostic("missing-package-json", "error", "No package.json found", [
2642
3923
  "Run this command from your project root"
2643
3924
  ]);
@@ -2659,6 +3940,15 @@ async function collectDoctorResult(root = process.cwd()) {
2659
3940
  detectBuildTools(root),
2660
3941
  isExtensionInstalled()
2661
3942
  ]);
3943
+ const onboardingContext = buildDoctorOnboardingContext({
3944
+ root,
3945
+ packageManager: pm,
3946
+ buildTools: buildResult,
3947
+ frameworks: frameworkResult,
3948
+ ides: ideProbe,
3949
+ providers: providerProbe
3950
+ });
3951
+ const onboardingPlan = createPlanResult(onboardingContext);
2662
3952
  if (ideProbe.detected.length === 0) {
2663
3953
  checks.push(createDiagnostic("ide-not-detected", "warning", "IDE: not detected"));
2664
3954
  } else {
@@ -2731,9 +4021,9 @@ async function collectDoctorResult(root = process.cwd()) {
2731
4021
  }).join(", ");
2732
4022
  checks.push(createDiagnostic("provider-detected", "ok", `Provider: ${aiNames}`));
2733
4023
  }
2734
- const pluginPath = path12.join(root, "node_modules", "@inspecto-dev", "plugin");
4024
+ const pluginPath = path16.join(root, "node_modules", "@inspecto-dev", "plugin");
2735
4025
  if (await exists(pluginPath)) {
2736
- const pkgJson = await readJSON(path12.join(pluginPath, "package.json"));
4026
+ const pkgJson = await readJSON(path16.join(pluginPath, "package.json"));
2737
4027
  const version = pkgJson?.version ?? "unknown";
2738
4028
  checks.push(
2739
4029
  createDiagnostic("plugin-installed", "ok", `@inspecto-dev/plugin@${version} installed`, [], {
@@ -2750,7 +4040,7 @@ async function collectDoctorResult(root = process.cwd()) {
2750
4040
  if (buildResult.supported.length > 0) {
2751
4041
  let injected = false;
2752
4042
  for (const bt of buildResult.supported) {
2753
- const content = await readFile(path12.join(root, bt.configPath));
4043
+ const content = await readFile(path16.join(root, bt.configPath));
2754
4044
  if (content && content.includes("@inspecto-dev/plugin")) {
2755
4045
  checks.push(
2756
4046
  createDiagnostic("plugin-configured", "ok", `Plugin configured in ${bt.configPath}`, [], {
@@ -2773,15 +4063,40 @@ async function collectDoctorResult(root = process.cwd()) {
2773
4063
  );
2774
4064
  }
2775
4065
  } else if (buildResult.unsupported.length > 0) {
2776
- const names = buildResult.unsupported.join(", ");
2777
- checks.push(
2778
- createDiagnostic(
2779
- `build-tool-unsupported`,
2780
- "warning",
2781
- `Build tool: ${names} (not supported in v1)`,
2782
- ["current version supports: Vite, Webpack, Rspack, esbuild, Rollup"]
2783
- )
4066
+ const guidedBuildTools = buildResult.unsupported.filter(isGuidedMetaFramework);
4067
+ const trulyUnsupportedBuildTools = buildResult.unsupported.filter(
4068
+ (buildTool) => !isGuidedMetaFramework(buildTool)
2784
4069
  );
4070
+ if (guidedBuildTools.length > 0) {
4071
+ checks.push(
4072
+ createDiagnostic(
4073
+ "build-tool-guided",
4074
+ "warning",
4075
+ `Build tool: ${guidedBuildTools.join(", ")} (guided onboarding available)`,
4076
+ [
4077
+ "Run `inspecto onboard --json` to generate the remaining patch plan and assistant handoff.",
4078
+ ...onboardingPlan.pendingSteps ?? []
4079
+ ],
4080
+ {
4081
+ metaFrameworks: guidedBuildTools,
4082
+ ...onboardingPlan.pendingSteps ? { pendingSteps: onboardingPlan.pendingSteps } : {},
4083
+ ...onboardingPlan.assistantPrompt ? { assistantPrompt: onboardingPlan.assistantPrompt } : {},
4084
+ ...onboardingPlan.patches ? { patchCount: onboardingPlan.patches.length } : {}
4085
+ }
4086
+ )
4087
+ );
4088
+ }
4089
+ checks.push(...await collectGuidedPatchDiagnostics(root, onboardingPlan));
4090
+ if (trulyUnsupportedBuildTools.length > 0) {
4091
+ checks.push(
4092
+ createDiagnostic(
4093
+ "build-tool-unsupported",
4094
+ "warning",
4095
+ `Build tool: ${trulyUnsupportedBuildTools.join(", ")} (not supported in v1)`,
4096
+ ["current version supports: Vite, Webpack, Rspack, esbuild, Rollup"]
4097
+ )
4098
+ );
4099
+ }
2785
4100
  } else {
2786
4101
  checks.push(
2787
4102
  createDiagnostic("build-tool-missing", "warning", "No recognized build config found")
@@ -2809,8 +4124,8 @@ async function collectDoctorResult(root = process.cwd()) {
2809
4124
  );
2810
4125
  }
2811
4126
  }
2812
- const settingsJsonPath = path12.join(root, ".inspecto", "settings.json");
2813
- const settingsLocalPath = path12.join(root, ".inspecto", "settings.local.json");
4127
+ const settingsJsonPath = path16.join(root, ".inspecto", "settings.json");
4128
+ const settingsLocalPath = path16.join(root, ".inspecto", "settings.local.json");
2814
4129
  const hasSettingsJson = await exists(settingsJsonPath);
2815
4130
  const hasSettingsLocal = await exists(settingsLocalPath);
2816
4131
  if (hasSettingsJson || hasSettingsLocal) {
@@ -2819,6 +4134,25 @@ async function collectDoctorResult(root = process.cwd()) {
2819
4134
  const settings = await readJSON(targetPath);
2820
4135
  if (settings) {
2821
4136
  checks.push(createDiagnostic("settings-valid", "ok", `.inspecto/${fileName} valid`));
4137
+ const configuredIde = typeof settings.ide === "string" ? settings.ide : void 0;
4138
+ const detectedIdeCandidates = ideProbe.detected.map((item) => item.ide);
4139
+ if (configuredIde && detectedIdeCandidates.length > 0 && !detectedIdeCandidates.includes(configuredIde)) {
4140
+ checks.push(
4141
+ createDiagnostic(
4142
+ "settings-ide-mismatch",
4143
+ "warning",
4144
+ `.inspecto/${fileName} sets ide=${configuredIde}, but the current environment looks like ${detectedIdeCandidates.join(", ")}. Inspecto will use the configured IDE from ${fileName}.`,
4145
+ [
4146
+ `Update .inspecto/${fileName} if you want Inspecto to target the currently detected IDE instead.`
4147
+ ],
4148
+ {
4149
+ configuredIde,
4150
+ detectedIdeCandidates,
4151
+ precedence: `configured ide from ${fileName}`
4152
+ }
4153
+ )
4154
+ );
4155
+ }
2822
4156
  } else {
2823
4157
  checks.push(
2824
4158
  createDiagnostic(
@@ -2844,7 +4178,7 @@ async function collectDoctorResult(root = process.cwd()) {
2844
4178
  )
2845
4179
  );
2846
4180
  }
2847
- const gitignoreContent = await readFile(path12.join(root, ".gitignore"));
4181
+ const gitignoreContent = await readFile(path16.join(root, ".gitignore"));
2848
4182
  if (gitignoreContent) {
2849
4183
  const hasLockIgnore = gitignoreContent.includes(".inspecto/install.lock") || gitignoreContent.includes(".inspecto/");
2850
4184
  if (!hasLockIgnore) {
@@ -2882,7 +4216,7 @@ async function doctor(options = {}) {
2882
4216
  }
2883
4217
 
2884
4218
  // src/onboarding/session.ts
2885
- import path13 from "path";
4219
+ import path17 from "path";
2886
4220
  function normalizePackagePath2(packagePath) {
2887
4221
  if (!packagePath || packagePath === ".") return "";
2888
4222
  return packagePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
@@ -2906,9 +4240,7 @@ function getVerificationCommand(packageManager) {
2906
4240
  }
2907
4241
  }
2908
4242
  async function buildVerification(projectRoot, packageManager) {
2909
- const packageJson = await readJSON(
2910
- path13.join(projectRoot, "package.json")
2911
- );
4243
+ const packageJson = await readJSON(path17.join(projectRoot, "package.json"));
2912
4244
  if (packageJson?.scripts?.dev) {
2913
4245
  const devCommand = getVerificationCommand(packageManager);
2914
4246
  return {
@@ -2917,11 +4249,28 @@ async function buildVerification(projectRoot, packageManager) {
2917
4249
  message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`
2918
4250
  };
2919
4251
  }
4252
+ if (packageJson?.scripts?.start && packageJson?.dependencies?.next) {
4253
+ const devCommand = packageManager === "bun" ? "bunx next dev" : "npx next dev";
4254
+ return {
4255
+ available: true,
4256
+ devCommand,
4257
+ message: `Start the local dev server with \`${devCommand}\` to verify Inspecto in the browser.`
4258
+ };
4259
+ }
2920
4260
  return {
2921
4261
  available: false,
2922
4262
  message: "Start your normal local dev server command to verify Inspecto in the browser."
2923
4263
  };
2924
4264
  }
4265
+ function buildExtensionInstallCommand(ide) {
4266
+ if (ide && isSupportedHostIde(ide)) {
4267
+ const binaryName = getHostIdeBinaryName(ide);
4268
+ if (binaryName) {
4269
+ return `${binaryName} --install-extension inspecto.inspecto`;
4270
+ }
4271
+ }
4272
+ return "code --install-extension inspecto.inspecto";
4273
+ }
2925
4274
  function buildIdeExtensionStatus(input) {
2926
4275
  if (!input.required) {
2927
4276
  return {
@@ -2934,8 +4283,10 @@ function buildIdeExtensionStatus(input) {
2934
4283
  required: true,
2935
4284
  installed: input.installed,
2936
4285
  manualRequired: input.manualRequired,
2937
- installCommand: "code --install-extension inspecto.inspecto",
2938
- marketplaceUrl: "https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto",
4286
+ installCommand: buildExtensionInstallCommand(input.ide),
4287
+ ...input.ide === "vscode" ? {
4288
+ marketplaceUrl: "https://marketplace.visualstudio.com/items?itemName=inspecto.inspecto"
4289
+ } : {},
2939
4290
  openVsxUrl: "https://open-vsx.org/extension/inspecto/inspecto"
2940
4291
  };
2941
4292
  }
@@ -2947,15 +4298,16 @@ async function detectFrameworkSupportByPackage2(repoRoot, context) {
2947
4298
  await Promise.all(
2948
4299
  Array.from(packagePaths).map(async (packagePath) => {
2949
4300
  const frameworkResult = await detectFrameworks(
2950
- packagePath ? path13.join(repoRoot, packagePath) : repoRoot
4301
+ packagePath ? path17.join(repoRoot, packagePath) : repoRoot
2951
4302
  );
2952
4303
  supportByPackage[packagePath] = frameworkResult.supported;
2953
4304
  })
2954
4305
  );
2955
4306
  return supportByPackage;
2956
4307
  }
2957
- async function buildTargetedContext(rootContext, packagePath) {
2958
- const projectRoot = packagePath ? path13.join(rootContext.root, packagePath) : rootContext.root;
4308
+ async function buildTargetedContext(rootContext, target) {
4309
+ const packagePath = normalizePackagePath2(target.packagePath);
4310
+ const projectRoot = packagePath ? path17.join(rootContext.root, packagePath) : rootContext.root;
2959
4311
  const [frameworks, ides, providers] = await Promise.all([
2960
4312
  detectFrameworks(projectRoot),
2961
4313
  detectIDE(projectRoot),
@@ -2966,7 +4318,11 @@ async function buildTargetedContext(rootContext, packagePath) {
2966
4318
  packageManager: rootContext.packageManager,
2967
4319
  buildTools: {
2968
4320
  supported: rootContext.buildTools.supported.filter((item) => {
2969
- return normalizePackagePath2(item.packagePath) === packagePath;
4321
+ const itemPackagePath = normalizePackagePath2(item.packagePath);
4322
+ if (target.id) {
4323
+ return `${itemPackagePath || "."}:${item.tool}:${item.configPath}` === target.id;
4324
+ }
4325
+ return itemPackagePath === packagePath;
2970
4326
  }),
2971
4327
  unsupported: []
2972
4328
  },
@@ -2984,7 +4340,11 @@ async function buildTargetedContext(rootContext, packagePath) {
2984
4340
  };
2985
4341
  }
2986
4342
  function buildOnboardingSummary(plan2, projectRoot) {
2987
- const changes = plan2.actions.filter((action) => action.type !== "manual_step").map((action) => action.description);
4343
+ const changes = plan2.actions.filter(
4344
+ (action) => !["manual_step", "generate_patch_plan", "generate_file", "manual_confirmation"].includes(
4345
+ action.type
4346
+ )
4347
+ ).map((action) => action.description);
2988
4348
  const risks = [...plan2.warnings.map((item) => item.message)];
2989
4349
  const manualFollowUp = planManualFollowUp(plan2);
2990
4350
  let headline = `Inspecto is ready to onboard ${projectRoot}.`;
@@ -3028,18 +4388,27 @@ function buildPreApplyResult(status, session) {
3028
4388
  errors: session.plan.blockers.map((item) => item.message),
3029
4389
  nextSteps: session.summary.manualFollowUp
3030
4390
  } : void 0;
4391
+ const ideExtension = buildIdeExtensionStatus({
4392
+ ...session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {},
4393
+ required: session.plan.defaults.extension,
4394
+ installed: false,
4395
+ manualRequired: session.plan.defaults.extension
4396
+ });
3031
4397
  return {
3032
4398
  status,
3033
4399
  target: session.target,
3034
4400
  summary: session.summary,
3035
4401
  confirmation: session.confirmation,
3036
- ideExtension: buildIdeExtensionStatus({
3037
- required: session.plan.defaults.extension,
3038
- installed: false,
3039
- manualRequired: session.plan.defaults.extension
3040
- }),
4402
+ ideExtension,
3041
4403
  verification: session.verification,
3042
- diagnostics
4404
+ ...session.framework ? { framework: session.framework } : {},
4405
+ ...session.metaFramework ? { metaFramework: session.metaFramework } : {},
4406
+ ...session.routerMode ? { routerMode: session.routerMode } : {},
4407
+ ...session.autoApplied ? { autoApplied: session.autoApplied } : {},
4408
+ ...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
4409
+ ...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
4410
+ ...session.patches ? { patches: session.patches } : {},
4411
+ ...diagnostics ? { diagnostics } : {}
3043
4412
  };
3044
4413
  }
3045
4414
  function buildExecutionResult(session, applyResult) {
@@ -3050,8 +4419,8 @@ function buildExecutionResult(session, applyResult) {
3050
4419
  )
3051
4420
  ),
3052
4421
  installedDependencies: applyResult.mutations.map((item) => item.name).filter((value) => !!value),
3053
- selectedProviderDefault: session.providerDefault,
3054
- selectedIDE: session.selectedIDE?.ide,
4422
+ ...session.providerDefault ? { selectedProviderDefault: session.providerDefault } : {},
4423
+ ...session.selectedIDE?.ide ? { selectedIDE: session.selectedIDE.ide } : {},
3055
4424
  mutations: applyResult.mutations
3056
4425
  };
3057
4426
  }
@@ -3084,25 +4453,41 @@ async function resolveOnboardingSession(root, options = {}) {
3084
4453
  repoRoot: root,
3085
4454
  buildTools: rootContext.buildTools.supported,
3086
4455
  frameworkSupportByPackage,
3087
- selectedPackagePath: options.target
4456
+ ...options.target ? { selectedPackagePath: options.target } : {}
3088
4457
  });
3089
- if (target.candidates.length === 0) {
4458
+ const isGuided = rootContext.buildTools.unsupported.some(
4459
+ (t) => ["Next.js", "Nuxt", "Umi"].includes(t)
4460
+ );
4461
+ if (target.candidates.length === 0 || isGuided) {
3090
4462
  const plan3 = createPlanResult(rootContext);
4463
+ const guidedStatus = plan3.strategy === "guided" ? "partial_success" : "error";
4464
+ const resolvedTarget = plan3.strategy === "guided" ? {
4465
+ status: "guided",
4466
+ candidates: [],
4467
+ reason: `Guided onboarding is available for ${plan3.metaFramework ?? "this project"}.`
4468
+ } : target;
3091
4469
  return {
3092
- status: "error",
3093
- target,
4470
+ status: guidedStatus,
4471
+ target: resolvedTarget,
3094
4472
  summary: buildOnboardingSummary(plan3, root),
3095
4473
  confirmation: { required: false },
3096
4474
  verification: rootVerification,
3097
4475
  context: rootContext,
3098
4476
  plan: plan3,
3099
- projectRoot: root
4477
+ projectRoot: root,
4478
+ ...plan3.framework ? { framework: plan3.framework } : {},
4479
+ ...plan3.metaFramework ? { metaFramework: plan3.metaFramework } : {},
4480
+ ...plan3.routerMode ? { routerMode: plan3.routerMode } : {},
4481
+ ...plan3.autoApplied ? { autoApplied: plan3.autoApplied } : {},
4482
+ ...plan3.pendingSteps ? { pendingSteps: plan3.pendingSteps } : {},
4483
+ ...plan3.assistantPrompt ? { assistantPrompt: plan3.assistantPrompt } : {},
4484
+ ...plan3.patches ? { patches: plan3.patches } : {}
3100
4485
  };
3101
4486
  }
3102
4487
  if (target.status === "needs_selection") {
3103
4488
  const plan3 = createPlanResult(rootContext);
3104
4489
  const summary2 = {
3105
- headline: "Inspecto found multiple plausible app targets and needs one selection.",
4490
+ headline: "Inspecto needs one build target selection before setup so it knows which local dev build should receive the plugin and settings.",
3106
4491
  changes: [],
3107
4492
  risks: [],
3108
4493
  manualFollowUp: []
@@ -3118,8 +4503,7 @@ async function resolveOnboardingSession(root, options = {}) {
3118
4503
  projectRoot: root
3119
4504
  };
3120
4505
  }
3121
- const packagePath = normalizePackagePath2(target.selected?.packagePath);
3122
- const context = await buildTargetedContext(rootContext, packagePath);
4506
+ const context = await buildTargetedContext(rootContext, target.selected);
3123
4507
  const verification = await buildVerification(context.root, context.packageManager);
3124
4508
  const plan2 = createPlanResult(context);
3125
4509
  const summary = buildOnboardingSummary(plan2, context.root);
@@ -3143,6 +4527,10 @@ async function resolveOnboardingSession(root, options = {}) {
3143
4527
  } else if (summary.manualFollowUp.length > 0 || plan2.warnings.length > 0) {
3144
4528
  status = "partial_success";
3145
4529
  }
4530
+ const providerDefault = getProviderDefault2(
4531
+ plan2.defaults.provider,
4532
+ selectedProvider?.preferredMode
4533
+ );
3146
4534
  return {
3147
4535
  status,
3148
4536
  target,
@@ -3153,7 +4541,14 @@ async function resolveOnboardingSession(root, options = {}) {
3153
4541
  plan: plan2,
3154
4542
  projectRoot: context.root,
3155
4543
  selectedIDE,
3156
- providerDefault: getProviderDefault2(plan2.defaults.provider, selectedProvider?.preferredMode)
4544
+ ...providerDefault ? { providerDefault } : {},
4545
+ ...plan2.framework ? { framework: plan2.framework } : {},
4546
+ ...plan2.metaFramework ? { metaFramework: plan2.metaFramework } : {},
4547
+ ...plan2.routerMode ? { routerMode: plan2.routerMode } : {},
4548
+ ...plan2.autoApplied ? { autoApplied: plan2.autoApplied } : {},
4549
+ ...plan2.pendingSteps ? { pendingSteps: plan2.pendingSteps } : {},
4550
+ ...plan2.assistantPrompt ? { assistantPrompt: plan2.assistantPrompt } : {},
4551
+ ...plan2.patches ? { patches: plan2.patches } : {}
3157
4552
  };
3158
4553
  }
3159
4554
  async function applyResolvedOnboardingSession(session, options = {}) {
@@ -3172,23 +4567,33 @@ async function applyResolvedOnboardingSession(session, options = {}) {
3172
4567
  },
3173
4568
  selectedIDE: session.selectedIDE,
3174
4569
  providerDefault: session.providerDefault,
3175
- plan: session.plan
4570
+ plan: session.plan,
4571
+ allowManualPlanApply: (session.plan.strategy === "manual" || session.plan.strategy === "guided") && session.plan.blockers.length === 0
3176
4572
  });
3177
4573
  const diagnostics = buildExecutionDiagnostics(session, applyResult);
3178
4574
  const status = applyResult.postInstall.installFailed && session.context.buildTools.supported.length === 0 ? "error" : diagnostics?.nextSteps.length || diagnostics?.errors.length || diagnostics?.warnings.length ? "partial_success" : "success";
4575
+ const ideExtension = buildIdeExtensionStatus({
4576
+ ...session.selectedIDE?.ide ? { ide: session.selectedIDE.ide } : {},
4577
+ required: session.plan.defaults.extension,
4578
+ installed: session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
4579
+ manualRequired: applyResult.postInstall.manualExtensionInstallNeeded
4580
+ });
3179
4581
  return {
3180
4582
  status,
3181
4583
  target: session.target,
3182
4584
  summary: session.summary,
3183
4585
  confirmation: session.confirmation,
3184
- ideExtension: buildIdeExtensionStatus({
3185
- required: session.plan.defaults.extension,
3186
- installed: session.plan.defaults.extension && !applyResult.postInstall.manualExtensionInstallNeeded,
3187
- manualRequired: applyResult.postInstall.manualExtensionInstallNeeded
3188
- }),
4586
+ ideExtension,
3189
4587
  verification,
3190
4588
  result: buildExecutionResult(session, applyResult),
3191
- diagnostics
4589
+ ...session.framework ? { framework: session.framework } : {},
4590
+ ...session.metaFramework ? { metaFramework: session.metaFramework } : {},
4591
+ ...session.routerMode ? { routerMode: session.routerMode } : {},
4592
+ ...session.autoApplied ? { autoApplied: session.autoApplied } : {},
4593
+ ...session.pendingSteps ? { pendingSteps: session.pendingSteps } : {},
4594
+ ...session.assistantPrompt ? { assistantPrompt: session.assistantPrompt } : {},
4595
+ ...session.patches ? { patches: session.patches } : {},
4596
+ ...diagnostics ? { diagnostics } : {}
3192
4597
  };
3193
4598
  }
3194
4599
  function buildDeferredOnboardResult(session) {
@@ -3211,23 +4616,72 @@ function printManualExtensionGuidance(result) {
3211
4616
  log.hint(result.ideExtension.openVsxUrl);
3212
4617
  }
3213
4618
  }
4619
+ function buildAssistantHandoff(result) {
4620
+ if (!result.framework && !result.metaFramework && !result.routerMode && !result.autoApplied && !result.pendingSteps && !result.assistantPrompt && !result.patches) {
4621
+ return result.handoff;
4622
+ }
4623
+ return {
4624
+ ...result.framework ? { framework: result.framework } : {},
4625
+ ...result.metaFramework ? { metaFramework: result.metaFramework } : {},
4626
+ ...result.routerMode ? { routerMode: result.routerMode } : {},
4627
+ ...result.autoApplied ? { autoApplied: result.autoApplied } : {},
4628
+ ...result.pendingSteps ? { pendingSteps: result.pendingSteps } : {},
4629
+ ...result.assistantPrompt ? { assistantPrompt: result.assistantPrompt } : {},
4630
+ ...result.patches ? { patches: result.patches } : {}
4631
+ };
4632
+ }
4633
+ function normalizeOnboardResult(result) {
4634
+ const handoff = buildAssistantHandoff(result);
4635
+ if (!handoff) {
4636
+ return result;
4637
+ }
4638
+ return {
4639
+ ...result,
4640
+ handoff
4641
+ };
4642
+ }
4643
+ function collectDisplayNextSteps(result) {
4644
+ return Array.from(
4645
+ /* @__PURE__ */ new Set([...result.diagnostics?.nextSteps ?? [], ...result.handoff?.pendingSteps ?? []])
4646
+ );
4647
+ }
3214
4648
  function printOnboardResult(result) {
4649
+ const normalized = normalizeOnboardResult(result);
3215
4650
  log.header("Inspecto Onboard");
3216
- log.info(`Status: ${result.status}`);
3217
- log.info(result.summary.headline);
3218
- for (const change of result.summary.changes) {
4651
+ log.info(`Status: ${normalized.status}`);
4652
+ log.info(normalized.summary.headline);
4653
+ if (normalized.status === "needs_target_selection") {
4654
+ if (normalized.target.selectionPurpose) {
4655
+ log.warn(normalized.target.selectionPurpose);
4656
+ }
4657
+ for (const candidate of normalized.target.candidates) {
4658
+ const identifier = candidate.candidateId ?? candidate.id ?? candidate.configPath;
4659
+ const label = candidate.label ?? candidate.configPath;
4660
+ log.hint(`${identifier}: ${label}`);
4661
+ }
4662
+ if (normalized.target.selectionInstructions) {
4663
+ log.hint(normalized.target.selectionInstructions);
4664
+ }
4665
+ }
4666
+ for (const change of normalized.summary.changes) {
3219
4667
  log.hint(change);
3220
4668
  }
3221
- for (const step of result.diagnostics?.nextSteps ?? []) {
4669
+ for (const step of collectDisplayNextSteps(normalized)) {
3222
4670
  log.warn(step);
3223
4671
  }
3224
- if (result.confirmation.required && result.confirmation.question) {
3225
- log.warn(result.confirmation.question);
4672
+ for (const patch of normalized.handoff?.patches ?? []) {
4673
+ log.hint(`Patch target: ${patch.path} (${patch.reason})`);
4674
+ }
4675
+ if (normalized.handoff?.assistantPrompt) {
4676
+ log.hint(normalized.handoff.assistantPrompt);
3226
4677
  }
3227
- printManualExtensionGuidance(result);
3228
- const extensionReady = !result.ideExtension?.required || result.ideExtension.installed && !result.ideExtension.manualRequired;
3229
- if (extensionReady && (result.status === "success" || result.status === "partial_success") && result.verification?.message) {
3230
- log.info(result.verification.message);
4678
+ if (normalized.confirmation.required && normalized.confirmation.question) {
4679
+ log.warn(normalized.confirmation.question);
4680
+ }
4681
+ printManualExtensionGuidance(normalized);
4682
+ const extensionReady = !normalized.ideExtension?.required || normalized.ideExtension.installed && !normalized.ideExtension.manualRequired;
4683
+ if (extensionReady && (normalized.status === "success" || normalized.status === "partial_success") && normalized.verification?.message) {
4684
+ log.info(normalized.verification.message);
3231
4685
  }
3232
4686
  }
3233
4687
  async function onboard(options = {}) {
@@ -3235,12 +4689,12 @@ async function onboard(options = {}) {
3235
4689
  const session = await resolveOnboardingSession(root, options);
3236
4690
  if (session.status === "error" || session.status === "needs_target_selection" || session.status === "needs_confirmation") {
3237
4691
  return writeCommandOutput(
3238
- buildDeferredOnboardResult(session),
4692
+ normalizeOnboardResult(buildDeferredOnboardResult(session)),
3239
4693
  options.json ?? false,
3240
4694
  printOnboardResult
3241
4695
  );
3242
4696
  }
3243
- const result = await applyResolvedOnboardingSession(session, options);
4697
+ const result = normalizeOnboardResult(await applyResolvedOnboardingSession(session, options));
3244
4698
  return writeCommandOutput(result, options.json ?? false, printOnboardResult);
3245
4699
  }
3246
4700
 
@@ -3278,11 +4732,11 @@ async function plan(json = false) {
3278
4732
  }
3279
4733
 
3280
4734
  // src/commands/teardown.ts
3281
- import path14 from "path";
4735
+ import path18 from "path";
3282
4736
  async function teardown() {
3283
4737
  const root = process.cwd();
3284
4738
  log.header("Inspecto Teardown");
3285
- const lockPath = path14.join(root, ".inspecto", "install.lock");
4739
+ const lockPath = path18.join(root, ".inspecto", "install.lock");
3286
4740
  const lock = await readJSON(lockPath);
3287
4741
  if (!lock) {
3288
4742
  log.warn("No .inspecto/install.lock found. Running in best-effort mode.");
@@ -3295,8 +4749,8 @@ async function teardown() {
3295
4749
  } catch {
3296
4750
  log.warn("Could not remove @inspecto-dev/plugin (may not be installed)");
3297
4751
  }
3298
- if (await exists(path14.join(root, ".inspecto"))) {
3299
- await removeDir(path14.join(root, ".inspecto"));
4752
+ if (await exists(path18.join(root, ".inspecto"))) {
4753
+ await removeDir(path18.join(root, ".inspecto"));
3300
4754
  log.success("Deleted .inspecto/ directory");
3301
4755
  }
3302
4756
  await cleanGitignore(root);
@@ -3345,7 +4799,7 @@ async function teardown() {
3345
4799
  }
3346
4800
  }
3347
4801
  }
3348
- await removeDir(path14.join(root, ".inspecto"));
4802
+ await removeDir(path18.join(root, ".inspecto"));
3349
4803
  log.success("Deleted .inspecto/ directory");
3350
4804
  await cleanGitignore(root);
3351
4805
  log.blank();
@@ -3354,13 +4808,13 @@ async function teardown() {
3354
4808
  }
3355
4809
 
3356
4810
  // src/commands/integration-install.ts
3357
- import fs3 from "fs/promises";
4811
+ import fs6 from "fs/promises";
3358
4812
  import { homedir as homedir2 } from "os";
3359
- import path17 from "path";
4813
+ import path21 from "path";
3360
4814
  import { fileURLToPath } from "url";
3361
4815
 
3362
4816
  // src/commands/integration-host-ide.ts
3363
- import path15 from "path";
4817
+ import path19 from "path";
3364
4818
  async function resolveIntegrationHostIde(options = {}) {
3365
4819
  if (isSupportedHostIde(options.explicitIde)) {
3366
4820
  return {
@@ -3397,22 +4851,24 @@ async function resolveIntegrationHostIde(options = {}) {
3397
4851
  candidates: envCandidates
3398
4852
  };
3399
4853
  }
3400
- const artifactCandidates = await detectArtifactHostIdes(cwd);
3401
- if (artifactCandidates.length === 1) {
3402
- return {
3403
- ide: artifactCandidates[0],
3404
- confidence: "medium",
3405
- source: "artifact",
3406
- candidates: artifactCandidates
3407
- };
3408
- }
3409
- if (artifactCandidates.length > 1) {
3410
- return {
3411
- ide: null,
3412
- confidence: "low",
3413
- source: "ambiguous",
3414
- candidates: artifactCandidates
3415
- };
4854
+ if (!options.ignoreProjectArtifacts) {
4855
+ const artifactCandidates = await detectArtifactHostIdes(cwd);
4856
+ if (artifactCandidates.length === 1) {
4857
+ return {
4858
+ ide: artifactCandidates[0],
4859
+ confidence: "medium",
4860
+ source: "artifact",
4861
+ candidates: artifactCandidates
4862
+ };
4863
+ }
4864
+ if (artifactCandidates.length > 1) {
4865
+ return {
4866
+ ide: null,
4867
+ confidence: "low",
4868
+ source: "ambiguous",
4869
+ candidates: artifactCandidates
4870
+ };
4871
+ }
3416
4872
  }
3417
4873
  return {
3418
4874
  ide: null,
@@ -3423,8 +4879,8 @@ async function resolveIntegrationHostIde(options = {}) {
3423
4879
  }
3424
4880
  async function resolveConfiguredIde(cwd) {
3425
4881
  const settingsPaths = [
3426
- path15.join(cwd, ".inspecto", "settings.local.json"),
3427
- path15.join(cwd, ".inspecto", "settings.json")
4882
+ path19.join(cwd, ".inspecto", "settings.local.json"),
4883
+ path19.join(cwd, ".inspecto", "settings.json")
3428
4884
  ];
3429
4885
  for (const settingsPath of settingsPaths) {
3430
4886
  const settings = await readJSON(settingsPath);
@@ -3464,7 +4920,7 @@ async function detectArtifactHostIdes(cwd) {
3464
4920
 
3465
4921
  // src/commands/integration-dispatch-mode.ts
3466
4922
  import { homedir } from "os";
3467
- import path16 from "path";
4923
+ import path20 from "path";
3468
4924
  async function resolveIntegrationDispatchMode(options) {
3469
4925
  const assistantRule = getDualModeAssistantCapability(options.assistant);
3470
4926
  const home = options.homeDir ?? homedir();
@@ -3507,7 +4963,7 @@ async function isIdeExtensionInstalled(extensionId, extensionsDir) {
3507
4963
  } catch {
3508
4964
  return false;
3509
4965
  }
3510
- const obsoletePath = path16.join(extensionsDir, ".obsolete");
4966
+ const obsoletePath = path20.join(extensionsDir, ".obsolete");
3511
4967
  let obsoleteFolders = /* @__PURE__ */ new Set();
3512
4968
  if (await exists(obsoletePath)) {
3513
4969
  const obsolete = await readJSON(obsoletePath);
@@ -3546,7 +5002,8 @@ async function runIntegrationAutomation(assistant, options = {}, cwd) {
3546
5002
  const silent = options.silent ?? false;
3547
5003
  const resolvedHostIde = await resolveIntegrationHostIde({
3548
5004
  ...options.ide ? { explicitIde: options.ide } : {},
3549
- ...cwd ? { cwd } : {}
5005
+ ...cwd ? { cwd } : {},
5006
+ ...options.ignoreProjectArtifacts ? { ignoreProjectArtifacts: true } : {}
3550
5007
  });
3551
5008
  const details = {
3552
5009
  hostIde: {
@@ -3889,6 +5346,10 @@ function getDispatchModeLabel(assistant, ide, mode) {
3889
5346
  }
3890
5347
 
3891
5348
  // src/commands/integration-install.ts
5349
+ import {
5350
+ DEFAULT_PROVIDER_MODE,
5351
+ VALID_MODES
5352
+ } from "@inspecto-dev/types";
3892
5353
  var REPO_RAW_BASE = "https://raw.githubusercontent.com/inspecto-dev/inspecto/main";
3893
5354
  var TOTAL_STEPS2 = 6;
3894
5355
  var INTEGRATION_MANIFESTS = [
@@ -3937,7 +5398,7 @@ var INTEGRATION_MANIFESTS = [
3937
5398
  {
3938
5399
  assistant: "coco",
3939
5400
  type: "native-skill",
3940
- installTarget: ".traecli/skills/inspecto-onboarding/",
5401
+ installTarget: ".trae/skills/inspecto-onboarding/",
3941
5402
  preferredInstall: "npx @inspecto-dev/cli integrations install coco --host-ide <vscode|cursor|trae|trae-cn>",
3942
5403
  cliSupported: true
3943
5404
  }
@@ -3955,7 +5416,7 @@ async function installIntegration(assistant, options = {}) {
3955
5416
  if (await exists(asset.target)) {
3956
5417
  if (options.force) {
3957
5418
  } else if (manifest.type === "context-template") {
3958
- const originalContent = await fs3.readFile(asset.target, "utf-8");
5419
+ const originalContent = await fs6.readFile(asset.target, "utf-8");
3959
5420
  existingFiles.set(asset.target, originalContent);
3960
5421
  if (!silent) {
3961
5422
  log.info(`File ${asset.target} already exists. Content will be appended safely.`);
@@ -3992,10 +5453,13 @@ ${content}`;
3992
5453
  for (const { asset, content } of downloadedAssets) {
3993
5454
  await writeFile(asset.target, content);
3994
5455
  if (asset.executable) {
3995
- await fs3.chmod(asset.target, 493);
5456
+ await fs6.chmod(asset.target, 493);
3996
5457
  }
3997
5458
  }
3998
5459
  }
5460
+ if (shouldPersistProjectOnboardingDefaults(options)) {
5461
+ await persistProjectOnboardingDefaults(assistant, options);
5462
+ }
3999
5463
  const stepOneMessage = options.preview ? formatIntegrationStep2(1, `Previewing ${getAssistantLabel2(assistant)} integration assets`) : formatIntegrationStep2(1, `Installed ${getAssistantLabel2(assistant)} integration assets`);
4000
5464
  if (!silent) {
4001
5465
  if (options.preview) {
@@ -4041,7 +5505,7 @@ ${content}`;
4041
5505
  }
4042
5506
  const automationResult = await runIntegrationAutomation(
4043
5507
  assistant,
4044
- { ...options, silent },
5508
+ { ...options, silent, ignoreProjectArtifacts: true },
4045
5509
  process.cwd()
4046
5510
  );
4047
5511
  const result = {
@@ -4081,6 +5545,46 @@ ${content}`;
4081
5545
  }
4082
5546
  return result;
4083
5547
  }
5548
+ function shouldPersistProjectOnboardingDefaults(options) {
5549
+ return !options.preview && !shouldSkipAutomationForInstall(options) && isSupportedHostIde(options.ide);
5550
+ }
5551
+ function isProviderAssistant(value) {
5552
+ return Object.prototype.hasOwnProperty.call(VALID_MODES, value);
5553
+ }
5554
+ async function resolveProviderDefaultForAssistant(assistant, ide) {
5555
+ if (!isProviderAssistant(assistant)) {
5556
+ return void 0;
5557
+ }
5558
+ let mode;
5559
+ if (assistant === "codex" || assistant === "claude-code" || assistant === "gemini") {
5560
+ const dispatchMode = await resolveIntegrationDispatchMode({ assistant, hostIde: ide });
5561
+ mode = dispatchMode.mode ?? DEFAULT_PROVIDER_MODE[assistant];
5562
+ } else {
5563
+ mode = DEFAULT_PROVIDER_MODE[assistant];
5564
+ }
5565
+ if (!mode || !VALID_MODES[assistant].includes(mode)) {
5566
+ return void 0;
5567
+ }
5568
+ return `${assistant}.${mode}`;
5569
+ }
5570
+ async function persistProjectOnboardingDefaults(assistant, options) {
5571
+ const settingsPath = path21.join(process.cwd(), ".inspecto", "settings.local.json");
5572
+ const existingSettings = await readJSON(settingsPath);
5573
+ const resolvedHostIde = await resolveIntegrationHostIde({
5574
+ explicitIde: options.ide,
5575
+ cwd: process.cwd()
5576
+ });
5577
+ const providerDefault = resolvedHostIde.ide && resolvedHostIde.confidence !== "low" ? await resolveProviderDefaultForAssistant(assistant, resolvedHostIde.ide) : void 0;
5578
+ const mergedSettings = existingSettings && typeof existingSettings === "object" ? {
5579
+ ...existingSettings,
5580
+ ide: options.ide,
5581
+ ...providerDefault ? { "provider.default": providerDefault } : {}
5582
+ } : {
5583
+ ide: options.ide,
5584
+ ...providerDefault ? { "provider.default": providerDefault } : {}
5585
+ };
5586
+ await writeJSON(settingsPath, mergedSettings);
5587
+ }
4084
5588
  function shouldSkipAutomationForInstall(options) {
4085
5589
  return options.scope === "user" && !options.preview;
4086
5590
  }
@@ -4143,6 +5647,12 @@ function resolveInstallPlan(assistant, options) {
4143
5647
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
4144
5648
  target: ".trae/skills/inspecto-onboarding/SKILL.md",
4145
5649
  localSource: "skills/inspecto-onboarding-trae/SKILL.md"
5650
+ },
5651
+ {
5652
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
5653
+ target: ".trae/skills/inspecto-onboarding/scripts/run-inspecto.sh",
5654
+ localSource: "skills/inspecto-onboarding-trae/scripts/run-inspecto.sh",
5655
+ executable: true
4146
5656
  }
4147
5657
  ],
4148
5658
  successMessage: "Installed Trae skill to .trae/skills/inspecto-onboarding/SKILL.md",
@@ -4153,11 +5663,17 @@ function resolveInstallPlan(assistant, options) {
4153
5663
  assets: [
4154
5664
  {
4155
5665
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/SKILL.md`,
4156
- target: ".traecli/skills/inspecto-onboarding/SKILL.md",
5666
+ target: ".trae/skills/inspecto-onboarding/SKILL.md",
4157
5667
  localSource: "skills/inspecto-onboarding-trae/SKILL.md"
5668
+ },
5669
+ {
5670
+ source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-trae/scripts/run-inspecto.sh`,
5671
+ target: ".trae/skills/inspecto-onboarding/scripts/run-inspecto.sh",
5672
+ localSource: "skills/inspecto-onboarding-trae/scripts/run-inspecto.sh",
5673
+ executable: true
4158
5674
  }
4159
5675
  ],
4160
- successMessage: "Installed Coco skill to .traecli/skills/inspecto-onboarding/SKILL.md",
5676
+ successMessage: "Installed Coco skill to .trae/skills/inspecto-onboarding/SKILL.md",
4161
5677
  nextStep: "Start a new Coco session."
4162
5678
  };
4163
5679
  default:
@@ -4198,22 +5714,22 @@ function resolveCodexPlan(options) {
4198
5714
  if (options.mode !== void 0) {
4199
5715
  throw new Error("`--mode` is not supported for codex.");
4200
5716
  }
4201
- const baseDir = scope === "user" ? path17.join(homedir2(), ".agents/skills/inspecto-onboarding-codex") : ".agents/skills/inspecto-onboarding-codex";
5717
+ const baseDir = scope === "user" ? path21.join(homedir2(), ".agents/skills/inspecto-onboarding-codex") : ".agents/skills/inspecto-onboarding-codex";
4202
5718
  return {
4203
5719
  assets: [
4204
5720
  {
4205
5721
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/SKILL.md`,
4206
- target: path17.join(baseDir, "SKILL.md"),
5722
+ target: path21.join(baseDir, "SKILL.md"),
4207
5723
  localSource: "skills/inspecto-onboarding-codex/SKILL.md"
4208
5724
  },
4209
5725
  {
4210
5726
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/agents/openai.yaml`,
4211
- target: path17.join(baseDir, "agents/openai.yaml"),
5727
+ target: path21.join(baseDir, "agents/openai.yaml"),
4212
5728
  localSource: "skills/inspecto-onboarding-codex/agents/openai.yaml"
4213
5729
  },
4214
5730
  {
4215
5731
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-codex/scripts/run-inspecto.sh`,
4216
- target: path17.join(baseDir, "scripts/run-inspecto.sh"),
5732
+ target: path21.join(baseDir, "scripts/run-inspecto.sh"),
4217
5733
  executable: true,
4218
5734
  localSource: "skills/inspecto-onboarding-codex/scripts/run-inspecto.sh"
4219
5735
  }
@@ -4233,22 +5749,22 @@ function resolveClaudeCodePlan(options) {
4233
5749
  if (scope !== "project" && scope !== "user") {
4234
5750
  throw new Error(`Unknown Claude Code scope: ${scope}`);
4235
5751
  }
4236
- const baseDir = scope === "user" ? path17.join(homedir2(), ".claude/skills/inspecto-onboarding-claude-code") : ".claude/skills/inspecto-onboarding-claude-code";
5752
+ const baseDir = scope === "user" ? path21.join(homedir2(), ".claude/skills/inspecto-onboarding-claude-code") : ".claude/skills/inspecto-onboarding-claude-code";
4237
5753
  return {
4238
5754
  assets: [
4239
5755
  {
4240
5756
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/SKILL.md`,
4241
- target: path17.join(baseDir, "SKILL.md"),
5757
+ target: path21.join(baseDir, "SKILL.md"),
4242
5758
  localSource: "skills/inspecto-onboarding-claude-code/SKILL.md"
4243
5759
  },
4244
5760
  {
4245
5761
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/agents/openai.yaml`,
4246
- target: path17.join(baseDir, "agents/openai.yaml"),
5762
+ target: path21.join(baseDir, "agents/openai.yaml"),
4247
5763
  localSource: "skills/inspecto-onboarding-claude-code/agents/openai.yaml"
4248
5764
  },
4249
5765
  {
4250
5766
  source: `${REPO_RAW_BASE}/skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh`,
4251
- target: path17.join(baseDir, "scripts/run-inspecto.sh"),
5767
+ target: path21.join(baseDir, "scripts/run-inspecto.sh"),
4252
5768
  executable: true,
4253
5769
  localSource: "skills/inspecto-onboarding-claude-code/scripts/run-inspecto.sh"
4254
5770
  }
@@ -4313,20 +5829,20 @@ async function loadAsset(asset) {
4313
5829
  if (asset.localSource) {
4314
5830
  const localPath = await resolveBundledAssetPath(asset.localSource);
4315
5831
  if (localPath) {
4316
- return await fs3.readFile(localPath, "utf-8");
5832
+ return await fs6.readFile(localPath, "utf-8");
4317
5833
  }
4318
5834
  }
4319
5835
  return await downloadAsset(asset.source);
4320
5836
  }
4321
5837
  async function resolveBundledAssetPath(relativePath) {
4322
- const startDir = path17.dirname(fileURLToPath(import.meta.url));
5838
+ const startDir = path21.dirname(fileURLToPath(import.meta.url));
4323
5839
  let currentDir = startDir;
4324
5840
  for (let depth = 0; depth < 8; depth += 1) {
4325
- const candidate = path17.join(currentDir, relativePath);
5841
+ const candidate = path21.join(currentDir, relativePath);
4326
5842
  if (await exists(candidate)) {
4327
5843
  return candidate;
4328
5844
  }
4329
- const parent = path17.dirname(currentDir);
5845
+ const parent = path21.dirname(currentDir);
4330
5846
  if (parent === currentDir) break;
4331
5847
  currentDir = parent;
4332
5848
  }
@@ -4434,6 +5950,9 @@ export {
4434
5950
  reportCommandError,
4435
5951
  apply,
4436
5952
  detect,
5953
+ devLink,
5954
+ devStatus,
5955
+ devUnlink,
4437
5956
  init,
4438
5957
  collectDoctorResult,
4439
5958
  doctor,