@inspecto-dev/cli 0.3.4 → 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 (44) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/.turbo/turbo-test.log +10036 -5601
  3. package/CHANGELOG.md +8 -0
  4. package/dist/bin.js +32 -1
  5. package/dist/{chunk-2MOEVONN.js → chunk-7ABJRH3F.js} +1232 -145
  6. package/dist/index.d.ts +62 -4
  7. package/dist/index.js +7 -1
  8. package/package.json +2 -2
  9. package/src/bin.ts +45 -0
  10. package/src/commands/dev-config.ts +109 -0
  11. package/src/commands/doctor.ts +162 -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 +1 -1
  16. package/src/commands/onboard.ts +72 -21
  17. package/src/detect/build-tool.ts +14 -5
  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 +137 -3
  24. package/src/onboarding/nextjs-guidance.ts +257 -0
  25. package/src/onboarding/nuxt-guidance.ts +129 -0
  26. package/src/onboarding/planner.ts +257 -6
  27. package/src/onboarding/session.ts +117 -27
  28. package/src/onboarding/target-resolution.ts +3 -3
  29. package/src/onboarding/umi-guidance.ts +139 -0
  30. package/src/types.ts +51 -3
  31. package/tests/apply.test.ts +319 -0
  32. package/tests/dev-config.test.ts +73 -0
  33. package/tests/doctor.test.ts +89 -0
  34. package/tests/init.test.ts +17 -0
  35. package/tests/instructions.test.ts +10 -6
  36. package/tests/integration-host-ide.test.ts +20 -0
  37. package/tests/integration-install.test.ts +65 -0
  38. package/tests/nextjs-guidance.test.ts +128 -0
  39. package/tests/nuxt-guidance.test.ts +67 -0
  40. package/tests/onboard.test.ts +416 -0
  41. package/tests/plan.test.ts +181 -21
  42. package/tests/runner-script.test.ts +120 -1
  43. package/tests/session-resolve.test.ts +116 -0
  44. package/tests/session.test.ts +120 -0
@@ -33,7 +33,9 @@ vi.mock('../src/detect/package-manager.js', async () => {
33
33
 
34
34
  vi.mock('../src/utils/fs.js', () => ({
35
35
  exists: vi.fn(),
36
+ readFile: vi.fn(),
36
37
  readJSON: vi.fn(),
38
+ writeFile: vi.fn(),
37
39
  writeJSON: vi.fn(),
38
40
  }))
39
41
 
@@ -80,7 +82,9 @@ describe('apply onboarding flow', () => {
80
82
  mutations: [{ type: 'file_modified', path: 'vite.config.ts' }],
81
83
  })
82
84
  vi.mocked(fsUtils.exists).mockResolvedValue(false)
85
+ vi.mocked(fsUtils.readFile).mockResolvedValue(null)
83
86
  vi.mocked(fsUtils.readJSON).mockResolvedValue(null)
87
+ vi.mocked(fsUtils.writeFile).mockResolvedValue()
84
88
  vi.mocked(gitignoreUtils.updateGitignore).mockResolvedValue()
85
89
  vi.mocked(extensionUtils.installExtension).mockResolvedValue({
86
90
  type: 'extension_installed',
@@ -333,6 +337,321 @@ describe('apply onboarding flow', () => {
333
337
  )
334
338
  })
335
339
 
340
+ it('auto-applies high-confidence Next.js config patches for guided plans', async () => {
341
+ vi.mocked(fsUtils.readFile).mockImplementation(async filePath => {
342
+ if (filePath === '/repo/next.config.mjs') {
343
+ return 'export default {\n reactStrictMode: true,\n}\n'
344
+ }
345
+ return null
346
+ })
347
+
348
+ const result = await applyOnboardingPlan({
349
+ repoRoot: '/repo',
350
+ projectRoot: '/repo',
351
+ packageManager: 'pnpm',
352
+ supportedBuildTargets: [],
353
+ options: {
354
+ shared: false,
355
+ skipInstall: false,
356
+ dryRun: false,
357
+ noExtension: false,
358
+ },
359
+ selectedIDE: { ide: 'vscode', supported: true },
360
+ providerDefault: 'codex.extension',
361
+ plan: {
362
+ status: 'warning',
363
+ warnings: [],
364
+ blockers: [],
365
+ strategy: 'guided',
366
+ actions: [
367
+ {
368
+ type: 'install_dependency',
369
+ target: '@inspecto-dev/plugin @inspecto-dev/core',
370
+ description: 'Install the Inspecto runtime packages with pnpm.',
371
+ },
372
+ {
373
+ type: 'generate_patch_plan',
374
+ target: 'next.config',
375
+ description:
376
+ 'Generate a guided patch plan for the Next.js Inspecto webpack integration.',
377
+ },
378
+ {
379
+ type: 'manual_confirmation',
380
+ target: '/repo',
381
+ description:
382
+ 'Complete the remaining client-side Inspecto mount step in your assistant or editor.',
383
+ },
384
+ ],
385
+ defaults: {
386
+ provider: 'codex',
387
+ ide: 'vscode',
388
+ shared: false,
389
+ extension: true,
390
+ },
391
+ framework: 'react',
392
+ metaFramework: 'Next.js',
393
+ routerMode: 'app',
394
+ autoApplied: ['dependencies', 'inspecto_settings'],
395
+ pendingSteps: [
396
+ 'Review the generated Next.js patch plan for next.config.mjs.',
397
+ 'Complete the remaining client-side mount step for your App Router entry.',
398
+ ],
399
+ assistantPrompt: 'Complete the remaining Inspecto onboarding for this Next.js project.',
400
+ patches: [
401
+ {
402
+ path: 'next.config.mjs',
403
+ status: 'planned',
404
+ reason: 'next_config_object_export',
405
+ confidence: 'high',
406
+ snippet:
407
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nwebpack(config, { dev, isServer }) {\n if (dev && !isServer) {\n config.plugins.push(inspecto())\n }\n return config\n}",
408
+ },
409
+ ],
410
+ },
411
+ allowManualPlanApply: true,
412
+ })
413
+
414
+ expect(astInjectorUtils.injectPlugin).not.toHaveBeenCalled()
415
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
416
+ '/repo/next.config.mjs',
417
+ expect.stringContaining("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'"),
418
+ )
419
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
420
+ '/repo/next.config.mjs',
421
+ expect.stringContaining('if (dev) {\n config.plugins.push(inspecto())'),
422
+ )
423
+ expect(result.mutations).toEqual(
424
+ expect.arrayContaining([
425
+ {
426
+ type: 'file_modified',
427
+ path: 'next.config.mjs',
428
+ description: 'Automatically configured Inspecto guided Next.js patch',
429
+ },
430
+ ]),
431
+ )
432
+ expect(result.postInstall.nextSteps).toContain(
433
+ 'Complete the remaining client-side Inspecto mount step in your assistant or editor.',
434
+ )
435
+ })
436
+
437
+ it('auto-applies high-confidence Next.js patches for exported nextConfig objects', async () => {
438
+ vi.mocked(fsUtils.readFile).mockImplementation(async filePath => {
439
+ if (filePath === '/repo/next.config.ts') {
440
+ return "import type { NextConfig } from 'next'\nconst nextConfig: NextConfig = {\n reactStrictMode: true,\n}\n\nexport default nextConfig\n"
441
+ }
442
+ return null
443
+ })
444
+
445
+ const result = await applyOnboardingPlan({
446
+ repoRoot: '/repo',
447
+ projectRoot: '/repo',
448
+ packageManager: 'pnpm',
449
+ supportedBuildTargets: [],
450
+ options: {
451
+ shared: false,
452
+ skipInstall: false,
453
+ dryRun: false,
454
+ noExtension: false,
455
+ },
456
+ selectedIDE: { ide: 'vscode', supported: true },
457
+ providerDefault: 'codex.extension',
458
+ plan: {
459
+ status: 'warning',
460
+ warnings: [],
461
+ blockers: [],
462
+ strategy: 'guided',
463
+ actions: [],
464
+ defaults: {
465
+ provider: 'codex',
466
+ ide: 'vscode',
467
+ shared: false,
468
+ extension: true,
469
+ },
470
+ framework: 'react',
471
+ metaFramework: 'Next.js',
472
+ routerMode: 'app',
473
+ autoApplied: ['dependencies', 'inspecto_settings'],
474
+ pendingSteps: [
475
+ 'Review the generated Next.js patch plan for next.config.ts.',
476
+ 'Complete the remaining client-side mount step for your App Router entry.',
477
+ ],
478
+ assistantPrompt: 'Complete the remaining Inspecto onboarding for this Next.js project.',
479
+ patches: [
480
+ {
481
+ path: 'next.config.ts',
482
+ status: 'planned',
483
+ reason: 'next_config_object_export',
484
+ confidence: 'high',
485
+ snippet:
486
+ "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\n\nwebpack(config, { dev, isServer }) {\n if (dev && !isServer) {\n config.plugins.push(inspecto())\n }\n return config\n}",
487
+ },
488
+ ],
489
+ },
490
+ allowManualPlanApply: true,
491
+ })
492
+
493
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
494
+ '/repo/next.config.ts',
495
+ expect.stringContaining("import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'"),
496
+ )
497
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
498
+ '/repo/next.config.ts',
499
+ expect.stringContaining(
500
+ 'const nextConfig: NextConfig = {\n webpack(config, { dev, isServer }) {',
501
+ ),
502
+ )
503
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
504
+ '/repo/next.config.ts',
505
+ expect.not.stringContaining('!isServer'),
506
+ )
507
+ expect(result.mutations).toEqual(
508
+ expect.arrayContaining([
509
+ expect.objectContaining({
510
+ path: 'next.config.ts',
511
+ description: 'Automatically configured Inspecto guided Next.js patch',
512
+ }),
513
+ ]),
514
+ )
515
+ })
516
+
517
+ it('preserves manual patch follow-up for guided Next.js plans that are not auto-applicable', async () => {
518
+ const result = await applyOnboardingPlan({
519
+ repoRoot: '/repo',
520
+ projectRoot: '/repo',
521
+ packageManager: 'pnpm',
522
+ supportedBuildTargets: [],
523
+ options: {
524
+ shared: false,
525
+ skipInstall: false,
526
+ dryRun: false,
527
+ noExtension: false,
528
+ },
529
+ selectedIDE: { ide: 'vscode', supported: true },
530
+ providerDefault: 'codex.extension',
531
+ plan: {
532
+ status: 'warning',
533
+ warnings: [],
534
+ blockers: [],
535
+ strategy: 'guided',
536
+ actions: [
537
+ {
538
+ type: 'generate_patch_plan',
539
+ target: 'next.config',
540
+ description:
541
+ 'Generate a guided patch plan for the Next.js Inspecto webpack integration.',
542
+ },
543
+ ],
544
+ defaults: {
545
+ provider: 'codex',
546
+ ide: 'vscode',
547
+ shared: false,
548
+ extension: true,
549
+ },
550
+ framework: 'react',
551
+ metaFramework: 'Next.js',
552
+ routerMode: 'pages',
553
+ pendingSteps: ['Review the generated Next.js patch plan for next.config.js.'],
554
+ patches: [
555
+ {
556
+ path: 'next.config.js',
557
+ status: 'manual_patch_required',
558
+ reason: 'next_config_wrapped_export',
559
+ confidence: 'medium',
560
+ snippet: 'module.exports = withBundleAnalyzer({ /* ... */ })',
561
+ },
562
+ ],
563
+ },
564
+ allowManualPlanApply: true,
565
+ })
566
+
567
+ expect(astInjectorUtils.injectPlugin).not.toHaveBeenCalled()
568
+ expect(fsUtils.writeFile).not.toHaveBeenCalledWith('/repo/next.config.js', expect.any(String))
569
+ expect(result.postInstall.nextSteps).toContain(
570
+ 'Generate a guided patch plan for the Next.js Inspecto webpack integration.',
571
+ )
572
+ })
573
+
574
+ it('auto-applies high-confidence Nuxt config patches for guided plans', async () => {
575
+ vi.mocked(fsUtils.readFile).mockImplementation(async filePath => {
576
+ if (filePath === '/repo/nuxt.config.ts') {
577
+ return 'export default defineNuxtConfig({\n devtools: { enabled: true },\n})\n'
578
+ }
579
+ return null
580
+ })
581
+
582
+ const result = await applyOnboardingPlan({
583
+ repoRoot: '/repo',
584
+ projectRoot: '/repo',
585
+ packageManager: 'pnpm',
586
+ supportedBuildTargets: [],
587
+ options: {
588
+ shared: false,
589
+ skipInstall: false,
590
+ dryRun: false,
591
+ noExtension: false,
592
+ },
593
+ selectedIDE: { ide: 'vscode', supported: true },
594
+ providerDefault: 'codex.extension',
595
+ plan: {
596
+ status: 'warning',
597
+ warnings: [],
598
+ blockers: [],
599
+ strategy: 'guided',
600
+ actions: [
601
+ {
602
+ type: 'generate_patch_plan',
603
+ target: 'nuxt.config',
604
+ description: 'Generate a guided patch plan for the Nuxt Inspecto Vite integration.',
605
+ },
606
+ ],
607
+ defaults: {
608
+ provider: 'codex',
609
+ ide: 'vscode',
610
+ shared: false,
611
+ extension: true,
612
+ },
613
+ framework: 'vue',
614
+ metaFramework: 'Nuxt',
615
+ pendingSteps: [
616
+ 'Review the generated Nuxt patch plan for nuxt.config.ts.',
617
+ 'Complete the remaining Nuxt client plugin mount step in plugins/inspecto.client.ts.',
618
+ ],
619
+ patches: [
620
+ {
621
+ path: 'nuxt.config.ts',
622
+ status: 'planned',
623
+ reason: 'nuxt_config_object_export',
624
+ confidence: 'high',
625
+ snippet:
626
+ "import { vitePlugin as inspecto } from '@inspecto-dev/plugin'\n\nexport default defineNuxtConfig({\n vite: {\n plugins: [inspecto()],\n },\n})",
627
+ },
628
+ {
629
+ path: 'plugins/inspecto.client.ts',
630
+ status: 'manual_patch_required',
631
+ reason: 'nuxt_client_plugin_mount',
632
+ confidence: 'medium',
633
+ snippet: 'export default defineNuxtPlugin(() => {})',
634
+ },
635
+ ],
636
+ },
637
+ allowManualPlanApply: true,
638
+ })
639
+
640
+ expect(fsUtils.writeFile).toHaveBeenCalledWith(
641
+ '/repo/nuxt.config.ts',
642
+ expect.stringContaining("import { vitePlugin as inspecto } from '@inspecto-dev/plugin'"),
643
+ )
644
+ expect(result.mutations).toEqual(
645
+ expect.arrayContaining([
646
+ {
647
+ type: 'file_modified',
648
+ path: 'nuxt.config.ts',
649
+ description: 'Automatically configured Inspecto guided Next.js patch',
650
+ },
651
+ ]),
652
+ )
653
+ })
654
+
336
655
  it('uses onboarding context and planner output and prints JSON from the apply command', async () => {
337
656
  const context: OnboardingContext = {
338
657
  root: '/repo',
@@ -0,0 +1,73 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import * as fsUtils from '../src/utils/fs.js'
3
+ import * as gitignoreUtils from '../src/inject/gitignore.js'
4
+ import { devLink, devStatus, devUnlink } from '../src/commands/dev-config.js'
5
+
6
+ vi.mock('../src/utils/fs.js', () => ({
7
+ exists: vi.fn(),
8
+ readJSON: vi.fn(),
9
+ removeFile: vi.fn(),
10
+ writeJSON: vi.fn(),
11
+ }))
12
+
13
+ vi.mock('../src/inject/gitignore.js', () => ({
14
+ updateGitignore: vi.fn(),
15
+ }))
16
+
17
+ describe('dev config commands', () => {
18
+ beforeEach(() => {
19
+ vi.resetAllMocks()
20
+ vi.spyOn(process, 'cwd').mockReturnValue('/repo')
21
+ })
22
+
23
+ it('writes cliBin into .inspecto/dev.json', async () => {
24
+ vi.mocked(fsUtils.readJSON).mockResolvedValue(null)
25
+
26
+ const result = await devLink({ cliBin: '/tmp/inspecto/bin.js', json: true })
27
+
28
+ expect(gitignoreUtils.updateGitignore).toHaveBeenCalledWith('/repo', false, false, true)
29
+ expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/dev.json', {
30
+ cliBin: '/tmp/inspecto/bin.js',
31
+ })
32
+ expect(result.config).toEqual({ cliBin: '/tmp/inspecto/bin.js' })
33
+ })
34
+
35
+ it('writes devRepo into .inspecto/dev.json', async () => {
36
+ vi.mocked(fsUtils.readJSON).mockResolvedValue({ cliBin: '/tmp/old.js' })
37
+
38
+ const result = await devLink({ devRepo: '/tmp/inspecto', json: true })
39
+
40
+ expect(gitignoreUtils.updateGitignore).toHaveBeenCalledWith('/repo', false, false, true)
41
+ expect(fsUtils.writeJSON).toHaveBeenCalledWith('/repo/.inspecto/dev.json', {
42
+ cliBin: '/tmp/old.js',
43
+ devRepo: '/tmp/inspecto',
44
+ })
45
+ expect(result.config).toEqual({
46
+ cliBin: '/tmp/old.js',
47
+ devRepo: '/tmp/inspecto',
48
+ })
49
+ })
50
+
51
+ it('returns current dev config status', async () => {
52
+ vi.mocked(fsUtils.exists).mockResolvedValue(true)
53
+ vi.mocked(fsUtils.readJSON).mockResolvedValue({
54
+ cliBin: '/tmp/inspecto/bin.js',
55
+ devRepo: '/tmp/inspecto',
56
+ })
57
+
58
+ const result = await devStatus(true)
59
+
60
+ expect(result.status).toBe('ok')
61
+ expect(result.config).toEqual({
62
+ cliBin: '/tmp/inspecto/bin.js',
63
+ devRepo: '/tmp/inspecto',
64
+ })
65
+ })
66
+
67
+ it('clears the dev config on unlink', async () => {
68
+ const result = await devUnlink(true)
69
+
70
+ expect(fsUtils.removeFile).toHaveBeenCalledWith('/repo/.inspecto/dev.json')
71
+ expect(result.status).toBe('ok')
72
+ })
73
+ })
@@ -262,4 +262,93 @@ describe('doctor command', () => {
262
262
  expect(output).not.toContain('"status"')
263
263
  expect(output).not.toContain('"summary"')
264
264
  })
265
+
266
+ it('reports Next.js as guided onboarding instead of unsupported', async () => {
267
+ vi.mocked(fsUtils.exists).mockImplementation(async (filePath: string) => {
268
+ const existingPaths = new Set([
269
+ '/repo/package.json',
270
+ '/repo/node_modules/@inspecto-dev/plugin',
271
+ '/repo/next.config.ts',
272
+ '/repo/.inspecto/settings.local.json',
273
+ '/repo/.gitignore',
274
+ ])
275
+ return existingPaths.has(filePath)
276
+ })
277
+ vi.mocked(fsUtils.readJSON).mockImplementation(async (filePath: string) => {
278
+ if (filePath === '/repo/package.json') {
279
+ return { scripts: { dev: 'next dev --webpack' } }
280
+ }
281
+ if (filePath === '/repo/node_modules/@inspecto-dev/plugin/package.json') {
282
+ return { version: '1.2.3' }
283
+ }
284
+ if (filePath === '/repo/.inspecto/settings.local.json') {
285
+ return { ide: 'trae', 'provider.default': 'coco.cli' }
286
+ }
287
+ return null
288
+ })
289
+ vi.mocked(fsUtils.readFile).mockImplementation(async filePath => {
290
+ if (filePath === '/repo/next.config.ts') {
291
+ return "import { webpackPlugin as inspecto } from '@inspecto-dev/plugin'\nexport default {}"
292
+ }
293
+ if (filePath === '/repo/.gitignore') {
294
+ return '.inspecto/install.lock\n.inspecto/dev.json\n'
295
+ }
296
+ return null
297
+ })
298
+ vi.mocked(packageManager.detectPackageManager).mockResolvedValue('pnpm')
299
+ vi.mocked(packageManager.getInstallCommand).mockReturnValue('pnpm add -D @inspecto-dev/plugin')
300
+ vi.mocked(ide.detectIDE).mockResolvedValue({
301
+ detected: [{ ide: 'trae', supported: true }],
302
+ })
303
+ vi.mocked(framework.detectFrameworks).mockResolvedValue({
304
+ supported: ['react'],
305
+ unsupported: [],
306
+ })
307
+ vi.mocked(provider.detectProviders).mockResolvedValue({
308
+ detected: [{ label: 'Trae CLI (Coco)', providerModes: ['cli'] }],
309
+ })
310
+ vi.mocked(buildTool.detectBuildTools).mockResolvedValue({
311
+ supported: [],
312
+ unsupported: ['Next.js'],
313
+ })
314
+ vi.mocked(extension.isExtensionInstalled).mockResolvedValue(true)
315
+
316
+ const result = await collectDoctorResult('/repo')
317
+
318
+ expect(result.status).toBe('warning')
319
+ expect(result.errors).toEqual([])
320
+ expect(result.warnings).toEqual(
321
+ expect.arrayContaining([
322
+ expect.objectContaining({
323
+ code: 'build-tool-guided',
324
+ message: 'Build tool: Next.js (guided onboarding available)',
325
+ hints: expect.arrayContaining([
326
+ 'Run `inspecto onboard --json` to generate the remaining patch plan and assistant handoff.',
327
+ expect.stringContaining('Review the generated Next.js patch plan'),
328
+ ]),
329
+ details: expect.objectContaining({
330
+ metaFrameworks: ['Next.js'],
331
+ patchCount: expect.any(Number),
332
+ pendingSteps: expect.arrayContaining([
333
+ expect.stringContaining('Review the generated Next.js patch plan'),
334
+ ]),
335
+ assistantPrompt: expect.stringContaining(
336
+ 'Complete the remaining Inspecto onboarding for this Next.js project.',
337
+ ),
338
+ }),
339
+ }),
340
+ ]),
341
+ )
342
+ expect(result.warnings).toEqual(
343
+ expect.not.arrayContaining([expect.objectContaining({ code: 'build-tool-unsupported' })]),
344
+ )
345
+ expect(result.checks).toEqual(
346
+ expect.arrayContaining([
347
+ expect.objectContaining({
348
+ code: 'guided-config-patch-pending',
349
+ message: 'Guided config patch still needs review in next.config.js',
350
+ }),
351
+ ]),
352
+ )
353
+ })
265
354
  })
@@ -244,6 +244,23 @@ describe('init in monorepo roots', () => {
244
244
  expect(instructionUtils.printNuxtManualInstructions).not.toHaveBeenCalled()
245
245
  })
246
246
 
247
+ it('prints detailed Nuxt guided instructions when Nuxt is detected', async () => {
248
+ vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
249
+ supported: [],
250
+ unsupported: ['Nuxt'],
251
+ })
252
+
253
+ await init({
254
+ shared: false,
255
+ skipInstall: true,
256
+ dryRun: true,
257
+ noExtension: true,
258
+ force: false,
259
+ })
260
+
261
+ expect(instructionUtils.printNuxtManualInstructions).toHaveBeenCalled()
262
+ })
263
+
247
264
  it('preserves detected preferred mode for an explicit provider override', async () => {
248
265
  vi.mocked(buildToolUtils.detectBuildTools).mockResolvedValue({
249
266
  supported: [
@@ -19,7 +19,9 @@ describe('manual framework instructions', () => {
19
19
  printNextJsManualInstructions()
20
20
 
21
21
  expect(log.blank).toHaveBeenCalled()
22
- expect(log.hint).toHaveBeenCalledWith('Next.js requires manual setup in the current version.')
22
+ expect(log.hint).toHaveBeenCalledWith(
23
+ 'Next.js supports guided setup in the current version. Inspecto can prepare the config patch, but the client-side mount step still needs review.',
24
+ )
23
25
  expect(log.hint).toHaveBeenCalledWith(
24
26
  '1. Update `next.config.mjs` to register the Inspecto webpack plugin:',
25
27
  )
@@ -30,10 +32,10 @@ describe('manual framework instructions', () => {
30
32
  ]),
31
33
  )
32
34
  expect(log.hint).toHaveBeenCalledWith(
33
- '2. Initialize `@inspecto-dev/core` from a client component such as `app/layout.tsx` or `pages/_app.tsx`:',
35
+ '2. Complete the remaining client-side mount step in `app/layout.tsx` or `pages/_app.tsx`:',
34
36
  )
35
37
  expect(log.hint).toHaveBeenCalledWith(
36
- '3. Restart your Next.js dev server after updating the config.',
38
+ '3. Restart your Next.js dev server after applying the guided patches.',
37
39
  )
38
40
  })
39
41
 
@@ -41,7 +43,9 @@ describe('manual framework instructions', () => {
41
43
  printNuxtManualInstructions()
42
44
 
43
45
  expect(log.blank).toHaveBeenCalled()
44
- expect(log.hint).toHaveBeenCalledWith('Nuxt requires manual setup in the current version.')
46
+ expect(log.hint).toHaveBeenCalledWith(
47
+ 'Nuxt supports guided setup in the current version. Inspecto can prepare the config patch, but the client plugin mount step still needs review.',
48
+ )
45
49
  expect(log.hint).toHaveBeenCalledWith(
46
50
  '1. Update `nuxt.config.ts` to register the Inspecto Vite plugin:',
47
51
  )
@@ -52,10 +56,10 @@ describe('manual framework instructions', () => {
52
56
  ]),
53
57
  )
54
58
  expect(log.hint).toHaveBeenCalledWith(
55
- '2. Create `plugins/inspecto.client.ts` to mount `@inspecto-dev/core` in development:',
59
+ '2. Complete the remaining client plugin mount step in `plugins/inspecto.client.ts`:',
56
60
  )
57
61
  expect(log.hint).toHaveBeenCalledWith(
58
- '3. Restart your Nuxt dev server after updating the config.',
62
+ '3. Restart your Nuxt dev server after applying the guided patches.',
59
63
  )
60
64
  })
61
65
  })
@@ -169,4 +169,24 @@ describe('resolveIntegrationHostIde', () => {
169
169
  candidates: ['cursor', 'vscode'],
170
170
  })
171
171
  })
172
+
173
+ it('ignores project artifacts when requested by integration install flows', async () => {
174
+ vi.mocked(fsUtils.exists).mockImplementation(async filePath => {
175
+ return filePath === '/repo/.trae'
176
+ })
177
+
178
+ const { resolveIntegrationHostIde } = await import('../src/commands/integration-host-ide.js')
179
+
180
+ await expect(
181
+ resolveIntegrationHostIde({
182
+ cwd: '/repo',
183
+ ignoreProjectArtifacts: true,
184
+ }),
185
+ ).resolves.toMatchObject({
186
+ ide: null,
187
+ confidence: 'low',
188
+ source: 'none',
189
+ candidates: [],
190
+ })
191
+ })
172
192
  })
@@ -279,6 +279,40 @@ describe('integration install', () => {
279
279
  )
280
280
  })
281
281
 
282
+ it('blocks automatic onboarding when no explicit host ide, config, or env signal is available', async () => {
283
+ runIntegrationAutomationMock.mockResolvedValue({
284
+ status: 'blocked',
285
+ message:
286
+ 'Automatic setup stopped: Inspecto could not determine which IDE should receive onboarding.',
287
+ nextStep:
288
+ 'Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup.',
289
+ })
290
+ resolveIntegrationHostIdeMock.mockResolvedValue({
291
+ ide: null,
292
+ source: 'none',
293
+ confidence: 'low',
294
+ candidates: [],
295
+ })
296
+
297
+ const { installIntegration } = await import('../src/commands/integration-install.js')
298
+
299
+ await installIntegration('coco')
300
+
301
+ expect(runIntegrationAutomationMock).toHaveBeenCalledWith(
302
+ 'coco',
303
+ expect.objectContaining({
304
+ ignoreProjectArtifacts: true,
305
+ }),
306
+ '/repo',
307
+ )
308
+ expect(logMock.warn).toHaveBeenCalledWith(
309
+ 'Automatic setup stopped: Inspecto could not determine which IDE should receive onboarding.',
310
+ )
311
+ expect(logMock.hint).toHaveBeenCalledWith(
312
+ 'Re-run with --host-ide <vscode|cursor|trae|trae-cn> or run the command from the target IDE terminal to continue automatic setup.',
313
+ )
314
+ })
315
+
282
316
  it('surfaces a partial automation outcome as a warning with follow-up guidance', async () => {
283
317
  runIntegrationAutomationMock.mockResolvedValue({
284
318
  status: 'partial',
@@ -662,6 +696,37 @@ describe('integration install', () => {
662
696
  })
663
697
  })
664
698
 
699
+ it('wires the dev command group to link, status, and unlink handlers', async () => {
700
+ const linkCommand = vi.fn().mockResolvedValue(undefined)
701
+ const statusCommand = vi.fn().mockResolvedValue(undefined)
702
+ const unlinkCommand = vi.fn().mockResolvedValue(undefined)
703
+
704
+ vi.doMock('../src/commands/init.js', () => ({ init: vi.fn() }))
705
+ vi.doMock('../src/commands/doctor.js', () => ({ doctor: vi.fn() }))
706
+ vi.doMock('../src/commands/teardown.js', () => ({ teardown: vi.fn() }))
707
+ vi.doMock('../src/commands/detect.js', () => ({ detect: vi.fn() }))
708
+ vi.doMock('../src/commands/plan.js', () => ({ plan: vi.fn() }))
709
+ vi.doMock('../src/commands/apply.js', () => ({ apply: vi.fn() }))
710
+ vi.doMock('../src/commands/dev-config.js', () => ({
711
+ devLink: linkCommand,
712
+ devStatus: statusCommand,
713
+ devUnlink: unlinkCommand,
714
+ }))
715
+
716
+ const { runCli } = await import('../src/bin.js')
717
+
718
+ await runCli(['node', 'inspecto', 'dev', 'link', '--cli-bin', '/tmp/inspecto/bin.js'])
719
+ await runCli(['node', 'inspecto', 'dev', 'status', '--json'])
720
+ await runCli(['node', 'inspecto', 'dev', 'unlink'])
721
+
722
+ expect(linkCommand).toHaveBeenCalledWith({
723
+ cliBin: '/tmp/inspecto/bin.js',
724
+ json: false,
725
+ })
726
+ expect(statusCommand).toHaveBeenCalledWith(true)
727
+ expect(unlinkCommand).toHaveBeenCalledWith(false)
728
+ })
729
+
665
730
  it('sets a non-zero exit code when integrations doctor reports a blocked result', async () => {
666
731
  const doctorCommand = vi.fn().mockResolvedValue({ status: 'blocked' })
667
732