lynxprompt 0.4.6 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -181,13 +181,16 @@ var ApiClient = class {
181
181
  if (data.codeStyle.loggingConventions) {
182
182
  preferences.push({ category: "code_style", key: "loggingConventions", value: data.codeStyle.loggingConventions, isDefault: true });
183
183
  }
184
+ if (data.codeStyle.loggingConventionsOther) {
185
+ preferences.push({ category: "code_style", key: "loggingConventionsOther", value: data.codeStyle.loggingConventionsOther, isDefault: true });
186
+ }
184
187
  if (data.codeStyle.notes) {
185
188
  preferences.push({ category: "code_style", key: "notes", value: data.codeStyle.notes, isDefault: true });
186
189
  }
187
190
  }
188
191
  if (data.boundaries) {
189
- if (data.boundaries.preset) {
190
- preferences.push({ category: "boundaries", key: "preset", value: data.boundaries.preset, isDefault: true });
192
+ if (data.boundaries.always?.length) {
193
+ preferences.push({ category: "boundaries", key: "always", value: JSON.stringify(data.boundaries.always), isDefault: true });
191
194
  }
192
195
  if (data.boundaries.never?.length) {
193
196
  preferences.push({ category: "boundaries", key: "never", value: JSON.stringify(data.boundaries.never), isDefault: true });
@@ -3114,9 +3117,10 @@ function generateFileContent(options, platform2) {
3114
3117
  }
3115
3118
  }
3116
3119
  let boundaries = BOUNDARIES[options.boundaries];
3117
- if (options.boundaryNever?.length || options.boundaryAsk?.length) {
3120
+ if (options.boundaryAlways?.length || options.boundaryNever?.length || options.boundaryAsk?.length) {
3118
3121
  boundaries = {
3119
3122
  ...boundaries,
3123
+ always: options.boundaryAlways?.length ? options.boundaryAlways : boundaries.always,
3120
3124
  never: options.boundaryNever?.length ? options.boundaryNever : boundaries.never,
3121
3125
  askFirst: options.boundaryAsk?.length ? options.boundaryAsk : boundaries.askFirst
3122
3126
  };
@@ -3183,7 +3187,8 @@ function generateFileContent(options, platform2) {
3183
3187
  }
3184
3188
  }
3185
3189
  if (options.loggingConventions) {
3186
- sections.push(`- **Logging:** ${options.loggingConventions}`);
3190
+ const loggingValue = options.loggingConventions === "other" && options.loggingConventionsOther ? options.loggingConventionsOther : options.loggingConventions.replace(/_/g, " ");
3191
+ sections.push(`- **Logging:** ${loggingValue}`);
3187
3192
  }
3188
3193
  if (options.styleNotes) {
3189
3194
  sections.push(`- **Notes:** ${options.styleNotes}`);
@@ -3317,13 +3322,14 @@ function generateYamlConfig(options, platform2) {
3317
3322
 
3318
3323
  // src/commands/wizard.ts
3319
3324
  var DRAFTS_DIR = ".lynxprompt/drafts";
3320
- async function saveDraftLocally(name, config2) {
3325
+ async function saveDraftLocally(name, config2, stepReached) {
3321
3326
  const draftsPath = join6(process.cwd(), DRAFTS_DIR);
3322
3327
  await mkdir4(draftsPath, { recursive: true });
3323
3328
  const draft = {
3324
3329
  name,
3325
3330
  savedAt: (/* @__PURE__ */ new Date()).toISOString(),
3326
- config: config2
3331
+ config: config2,
3332
+ stepReached
3327
3333
  };
3328
3334
  const filename = `${name.replace(/[^a-zA-Z0-9-_]/g, "_")}.json`;
3329
3335
  await writeFile4(join6(draftsPath, filename), JSON.stringify(draft, null, 2), "utf-8");
@@ -3535,6 +3541,7 @@ var CONTAINER_REGISTRIES = [
3535
3541
  ];
3536
3542
  var COMMON_COMMANDS = {
3537
3543
  build: [
3544
+ // JavaScript/Node
3538
3545
  "npm run build",
3539
3546
  "pnpm build",
3540
3547
  "yarn build",
@@ -3543,16 +3550,46 @@ var COMMON_COMMANDS = {
3543
3550
  "vite build",
3544
3551
  "tsc",
3545
3552
  "tsc --noEmit",
3553
+ "esbuild",
3554
+ "rollup -c",
3555
+ "webpack",
3556
+ "parcel build",
3557
+ // Python
3558
+ "python setup.py build",
3559
+ "pip install -e .",
3560
+ "poetry build",
3561
+ "pdm build",
3562
+ "hatch build",
3563
+ // Go
3546
3564
  "go build",
3565
+ "go build ./...",
3566
+ "go install",
3567
+ // Rust
3547
3568
  "cargo build",
3548
3569
  "cargo build --release",
3570
+ // Java/JVM
3549
3571
  "mvn package",
3572
+ "mvn clean install",
3550
3573
  "gradle build",
3574
+ // .NET
3551
3575
  "dotnet build",
3576
+ "dotnet publish",
3577
+ // Ruby
3578
+ "bundle exec rake build",
3579
+ "gem build",
3580
+ // PHP
3581
+ "composer install",
3582
+ "composer dump-autoload",
3583
+ // Docker
3552
3584
  "docker build -t app .",
3553
- "docker compose build"
3585
+ "docker compose build",
3586
+ // Make
3587
+ "make",
3588
+ "make build",
3589
+ "make all"
3554
3590
  ],
3555
3591
  test: [
3592
+ // JavaScript/Node
3556
3593
  "npm test",
3557
3594
  "pnpm test",
3558
3595
  "yarn test",
@@ -3561,31 +3598,86 @@ var COMMON_COMMANDS = {
3561
3598
  "vitest run",
3562
3599
  "jest",
3563
3600
  "jest --coverage",
3601
+ "mocha",
3602
+ "ava",
3603
+ "tap",
3604
+ // E2E
3605
+ "playwright test",
3606
+ "cypress run",
3607
+ "cypress open",
3608
+ "puppeteer",
3609
+ "selenium",
3610
+ // Python
3564
3611
  "pytest",
3565
3612
  "pytest --cov",
3613
+ "pytest -xvs",
3614
+ "unittest",
3615
+ "nose2",
3616
+ "hypothesis",
3617
+ // Go
3566
3618
  "go test ./...",
3619
+ "go test -v ./...",
3620
+ "go test -race ./...",
3621
+ // Rust
3567
3622
  "cargo test",
3623
+ "cargo test --release",
3624
+ // Java/JVM
3568
3625
  "mvn test",
3569
3626
  "gradle test",
3570
- "playwright test",
3571
- "cypress run"
3627
+ "mvn verify",
3628
+ // .NET
3629
+ "dotnet test",
3630
+ // Ruby
3631
+ "bundle exec rspec",
3632
+ "rake test",
3633
+ // PHP
3634
+ "phpunit",
3635
+ "pest",
3636
+ // Docker
3637
+ "docker compose run test"
3572
3638
  ],
3573
3639
  lint: [
3640
+ // JavaScript/Node
3574
3641
  "npm run lint",
3575
3642
  "pnpm lint",
3576
3643
  "eslint .",
3577
3644
  "eslint . --fix",
3578
3645
  "prettier --check .",
3579
3646
  "prettier --write .",
3647
+ "biome check",
3648
+ "biome check --apply",
3649
+ // Python
3580
3650
  "ruff check",
3651
+ "ruff check --fix",
3581
3652
  "ruff format",
3582
3653
  "black .",
3654
+ "black --check .",
3583
3655
  "flake8",
3656
+ "pylint",
3657
+ "mypy .",
3658
+ // Go
3584
3659
  "golangci-lint run",
3660
+ "go fmt ./...",
3661
+ "go vet ./...",
3662
+ // Rust
3585
3663
  "cargo clippy",
3586
- "rubocop"
3664
+ "cargo fmt",
3665
+ "cargo fmt --check",
3666
+ // Java
3667
+ "mvn checkstyle:check",
3668
+ "gradle spotlessCheck",
3669
+ // Ruby
3670
+ "rubocop",
3671
+ "rubocop -a",
3672
+ // PHP
3673
+ "php-cs-fixer fix",
3674
+ "phpcs",
3675
+ "phpstan analyse",
3676
+ // General
3677
+ "pre-commit run --all-files"
3587
3678
  ],
3588
3679
  dev: [
3680
+ // JavaScript/Node
3589
3681
  "npm run dev",
3590
3682
  "pnpm dev",
3591
3683
  "yarn dev",
@@ -3593,12 +3685,64 @@ var COMMON_COMMANDS = {
3593
3685
  "next dev",
3594
3686
  "vite",
3595
3687
  "vite dev",
3688
+ "nodemon",
3689
+ "ts-node-dev",
3690
+ // Python
3596
3691
  "uvicorn main:app --reload",
3597
3692
  "flask run",
3598
- "rails server",
3693
+ "python manage.py runserver",
3694
+ "gunicorn --reload",
3695
+ "hypercorn --reload",
3696
+ // Go
3599
3697
  "go run .",
3698
+ "air",
3699
+ "reflex",
3700
+ // Rust
3600
3701
  "cargo run",
3601
- "dotnet run"
3702
+ "cargo watch -x run",
3703
+ // Java
3704
+ "mvn spring-boot:run",
3705
+ "gradle bootRun",
3706
+ // .NET
3707
+ "dotnet run",
3708
+ "dotnet watch run",
3709
+ // Ruby
3710
+ "rails server",
3711
+ "bundle exec rails s",
3712
+ // PHP
3713
+ "php artisan serve",
3714
+ "symfony server:start",
3715
+ // Docker
3716
+ "docker compose up",
3717
+ "docker compose up -d"
3718
+ ],
3719
+ additional: [
3720
+ // Database
3721
+ "npm run db:push",
3722
+ "npm run db:migrate",
3723
+ "prisma migrate dev",
3724
+ "alembic upgrade head",
3725
+ "flask db upgrade",
3726
+ "rails db:migrate",
3727
+ "rake db:migrate",
3728
+ // Codegen
3729
+ "npm run codegen",
3730
+ "graphql-codegen",
3731
+ "prisma generate",
3732
+ // Docs
3733
+ "npm run docs",
3734
+ "mkdocs serve",
3735
+ "sphinx-build",
3736
+ // Deploy
3737
+ "npm run deploy",
3738
+ "vercel",
3739
+ "netlify deploy",
3740
+ "flyctl deploy",
3741
+ "railway up",
3742
+ // Clean
3743
+ "npm run clean",
3744
+ "make clean",
3745
+ "cargo clean"
3602
3746
  ]
3603
3747
  };
3604
3748
  var NAMING_CONVENTIONS = [
@@ -3615,15 +3759,26 @@ var ERROR_PATTERNS = [
3615
3759
  { id: "exceptions", label: "Custom exceptions" },
3616
3760
  { id: "other", label: "Other" }
3617
3761
  ];
3762
+ var LOGGING_OPTIONS = [
3763
+ { id: "structured_json", label: "Structured JSON" },
3764
+ { id: "console_log", label: "Console.log (dev)" },
3765
+ { id: "log_levels", label: "Log Levels (debug/info/warn/error)" },
3766
+ { id: "pino", label: "Pino" },
3767
+ { id: "winston", label: "Winston" },
3768
+ { id: "bunyan", label: "Bunyan" },
3769
+ { id: "python_logging", label: "Python logging" },
3770
+ { id: "log4j", label: "Log4j / SLF4J" },
3771
+ { id: "serilog", label: "Serilog" },
3772
+ { id: "opentelemetry", label: "OpenTelemetry" },
3773
+ { id: "other", label: "Other" }
3774
+ ];
3618
3775
  var AI_BEHAVIOR_RULES = [
3619
- { id: "explain_changes", label: "Explain changes before making them", recommended: true },
3620
- { id: "preserve_style", label: "Preserve existing code style", recommended: true },
3621
- { id: "minimal_changes", label: "Make minimal, focused changes", recommended: true },
3622
- { id: "no_comments", label: "Avoid adding unnecessary comments", recommended: false },
3623
- { id: "prefer_simple", label: "Prefer simpler solutions", recommended: true },
3624
- { id: "test_first", label: "Write tests before implementation", recommended: false },
3625
- { id: "no_console", label: "Remove console.log/print before committing", recommended: false },
3626
- { id: "type_strict", label: "Be strict with types (no any/Any)", recommended: false }
3776
+ { id: "always_debug_after_build", label: "Always Debug After Building", description: "Run and test locally after making changes", recommended: true },
3777
+ { id: "check_logs_after_build", label: "Check Logs After Build/Commit", description: "Check logs when build or commit finishes", recommended: true },
3778
+ { id: "run_tests_before_commit", label: "Run Tests Before Commit", description: "Ensure tests pass before committing", recommended: true },
3779
+ { id: "follow_existing_patterns", label: "Follow Existing Patterns", description: "Match the codebase's existing style", recommended: true },
3780
+ { id: "ask_before_large_refactors", label: "Ask Before Large Refactors", description: "Confirm before significant changes", recommended: true },
3781
+ { id: "check_for_security_issues", label: "Check for Security Issues", description: "Review for common vulnerabilities", recommended: false }
3627
3782
  ];
3628
3783
  var IMPORTANT_FILES = [
3629
3784
  { id: "readme", label: "README.md", icon: "\u{1F4D6}" },
@@ -3632,32 +3787,6 @@ var IMPORTANT_FILES = [
3632
3787
  { id: "architecture", label: "ARCHITECTURE.md", icon: "\u{1F3D7}\uFE0F" },
3633
3788
  { id: "contributing", label: "CONTRIBUTING.md", icon: "\u{1F91D}" }
3634
3789
  ];
3635
- var BOUNDARY_PRESETS = [
3636
- {
3637
- title: "\u{1F7E2} Standard",
3638
- value: "standard",
3639
- description: "Balanced freedom & safety",
3640
- always: ["Read any file", "Modify files in src/", "Run build/test/lint", "Create test files"],
3641
- askFirst: ["Add new dependencies", "Modify config files", "Create new modules"],
3642
- never: ["Delete production data", "Modify .env secrets", "Force push"]
3643
- },
3644
- {
3645
- title: "\u{1F7E1} Conservative",
3646
- value: "conservative",
3647
- description: "Ask before most changes",
3648
- always: ["Read any file", "Run lint/format commands"],
3649
- askFirst: ["Modify any file", "Add dependencies", "Create files", "Run tests"],
3650
- never: ["Delete files", "Modify .env", "Push to git"]
3651
- },
3652
- {
3653
- title: "\u{1F7E0} Permissive",
3654
- value: "permissive",
3655
- description: "AI can modify freely",
3656
- always: ["Modify any file in src/", "Run any script", "Add dependencies", "Create files"],
3657
- askFirst: ["Modify root configs", "Delete directories"],
3658
- never: ["Modify .env", "Access external APIs without confirmation"]
3659
- }
3660
- ];
3661
3790
  var BOUNDARY_OPTIONS = [
3662
3791
  "Delete files",
3663
3792
  "Create new files",
@@ -3678,29 +3807,118 @@ var BOUNDARY_OPTIONS = [
3678
3807
  "Skip tests temporarily"
3679
3808
  ];
3680
3809
  var TEST_FRAMEWORKS = [
3810
+ // JavaScript/TypeScript
3681
3811
  "jest",
3682
3812
  "vitest",
3683
3813
  "mocha",
3684
3814
  "ava",
3685
3815
  "tap",
3816
+ "bun:test",
3817
+ // E2E/Integration
3818
+ "playwright",
3819
+ "cypress",
3820
+ "puppeteer",
3821
+ "selenium",
3822
+ "webdriverio",
3823
+ "testcafe",
3824
+ // React/Frontend
3825
+ "rtl",
3826
+ "enzyme",
3827
+ "storybook",
3828
+ "chromatic",
3829
+ // API/Mocking
3830
+ "msw",
3831
+ "supertest",
3832
+ "pact",
3833
+ "dredd",
3834
+ "karate",
3835
+ "postman",
3836
+ "insomnia",
3837
+ // Python
3686
3838
  "pytest",
3687
3839
  "unittest",
3688
3840
  "nose2",
3689
- "go test",
3841
+ "hypothesis",
3842
+ "behave",
3843
+ "robot",
3844
+ // Go
3845
+ "go-test",
3690
3846
  "testify",
3691
- "cargo test",
3692
- "rstest",
3847
+ "ginkgo",
3848
+ "gomega",
3849
+ // Java/JVM
3693
3850
  "junit",
3694
3851
  "testng",
3852
+ "mockito",
3695
3853
  "spock",
3854
+ "cucumber-jvm",
3855
+ // Ruby
3696
3856
  "rspec",
3697
3857
  "minitest",
3858
+ "capybara",
3859
+ "factory_bot",
3860
+ // .NET
3861
+ "xunit",
3862
+ "nunit",
3863
+ "mstest",
3864
+ "specflow",
3865
+ // Infrastructure/DevOps
3866
+ "terratest",
3867
+ "conftest",
3868
+ "opa",
3869
+ "inspec",
3870
+ "serverspec",
3871
+ "molecule",
3872
+ "kitchen",
3873
+ "goss",
3874
+ // Kubernetes
3875
+ "kubetest",
3876
+ "kuttl",
3877
+ "chainsaw",
3878
+ "helm-unittest",
3879
+ // Security
3880
+ "owasp-zap",
3881
+ "burpsuite",
3882
+ "nuclei",
3883
+ "semgrep",
3884
+ // Load/Performance
3885
+ "k6",
3886
+ "locust",
3887
+ "jmeter",
3888
+ "artillery",
3889
+ "gatling",
3890
+ "vegeta",
3891
+ "wrk",
3892
+ "ab",
3893
+ // Chaos Engineering
3894
+ "chaos-mesh",
3895
+ "litmus",
3896
+ "gremlin",
3897
+ "toxiproxy",
3898
+ // Contract Testing
3899
+ "spring-cloud-contract",
3900
+ "specmatic",
3901
+ // BDD
3902
+ "cucumber",
3903
+ "gauge",
3904
+ "concordion",
3905
+ // Mutation Testing
3906
+ "stryker",
3907
+ "pitest",
3908
+ "mutmut",
3909
+ // Fuzzing
3910
+ "go-fuzz",
3911
+ "afl",
3912
+ "libfuzzer",
3913
+ "jazzer",
3914
+ // PHP
3698
3915
  "phpunit",
3699
3916
  "pest",
3700
- "playwright",
3701
- "cypress",
3702
- "puppeteer",
3703
- "selenium"
3917
+ "codeception",
3918
+ // Rust
3919
+ "cargo-test",
3920
+ "rstest",
3921
+ "proptest"
3704
3922
  ];
3705
3923
  var TEST_LEVELS = [
3706
3924
  { id: "smoke", label: "Smoke", desc: "Quick sanity checks" },
@@ -3845,12 +4063,40 @@ function showWizardOverview(userTier) {
3845
4063
  }
3846
4064
  console.log();
3847
4065
  }
4066
+ var wizardState = {
4067
+ inProgress: false,
4068
+ answers: {},
4069
+ stepReached: 0
4070
+ };
4071
+ async function saveDraftOnExit() {
4072
+ if (!wizardState.inProgress || Object.keys(wizardState.answers).length === 0) {
4073
+ return;
4074
+ }
4075
+ try {
4076
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
4077
+ const draftName = `autosave-${timestamp}`;
4078
+ console.log();
4079
+ console.log(chalk8.yellow(` \u{1F4BE} Saving wizard state to draft: ${draftName}...`));
4080
+ await saveDraftLocally(draftName, wizardState.answers, wizardState.stepReached);
4081
+ console.log(chalk8.green(` \u2713 Draft saved! Resume with: lynxp wizard --load-draft ${draftName}`));
4082
+ console.log(chalk8.gray(` Saved at step ${wizardState.stepReached}`));
4083
+ console.log();
4084
+ } catch (err) {
4085
+ console.log(chalk8.red(` \u2717 Could not save draft: ${err instanceof Error ? err.message : "unknown error"}`));
4086
+ }
4087
+ }
3848
4088
  var promptConfig = {
3849
- onCancel: () => {
4089
+ onCancel: async () => {
4090
+ await saveDraftOnExit();
3850
4091
  console.log(chalk8.yellow("\n Cancelled. Run 'lynxp wizard' anytime to restart.\n"));
3851
4092
  process.exit(0);
3852
4093
  }
3853
4094
  };
4095
+ process.on("SIGINT", async () => {
4096
+ await saveDraftOnExit();
4097
+ console.log(chalk8.yellow("\n Interrupted. Run 'lynxp wizard' anytime to restart.\n"));
4098
+ process.exit(0);
4099
+ });
3854
4100
  async function wizardCommand(options) {
3855
4101
  console.log();
3856
4102
  console.log(chalk8.cyan.bold(" \u{1F431} LynxPrompt Wizard"));
@@ -3859,9 +4105,13 @@ async function wizardCommand(options) {
3859
4105
  if (options.loadDraft) {
3860
4106
  const draft = await loadDraftLocally(options.loadDraft);
3861
4107
  if (draft) {
3862
- console.log(chalk8.green(` \u2713 Loaded draft: ${draft.name} (saved ${new Date(draft.savedAt).toLocaleString()})`));
4108
+ const stepInfo = draft.stepReached ? ` at step ${draft.stepReached}` : "";
4109
+ console.log(chalk8.green(` \u2713 Loaded draft: ${draft.name} (saved ${new Date(draft.savedAt).toLocaleString()}${stepInfo})`));
3863
4110
  console.log();
3864
- Object.assign(options, draft.config);
4111
+ options._draftAnswers = draft.config;
4112
+ options._resumeFromStep = draft.stepReached;
4113
+ if (draft.config.name) options.name = draft.config.name;
4114
+ if (draft.config.description) options.description = draft.config.description;
3865
4115
  } else {
3866
4116
  const availableDrafts = await listLocalDrafts();
3867
4117
  console.log(chalk8.red(` \u2717 Draft "${options.loadDraft}" not found.`));
@@ -3972,7 +4222,7 @@ async function wizardCommand(options) {
3972
4222
  if (canDetectRemote) {
3973
4223
  console.log();
3974
4224
  console.log(chalk8.magenta.bold(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
3975
- console.log(chalk8.magenta.bold(" \u2502 \u2728 AUTO-DETECT FROM REPOSITORY (MAX/TEAMS) \u2502"));
4225
+ console.log(chalk8.magenta.bold(" \u2502 \u2728 AUTO-DETECT FROM REPOSITORY (MAX/TEAMS) \u2502"));
3976
4226
  console.log(chalk8.magenta.bold(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
3977
4227
  console.log(chalk8.gray(" Analyze any public GitHub/GitLab repo, or private with git credentials."));
3978
4228
  console.log(chalk8.gray(" We'll detect: languages, frameworks, commands, license, CI/CD, and more!"));
@@ -4114,33 +4364,73 @@ async function wizardCommand(options) {
4114
4364
  initial: true
4115
4365
  });
4116
4366
  if (savePrefsResponse.savePrefs) {
4117
- const saveSpinner = ora7("Saving preferences to your profile...").start();
4118
- try {
4119
- await api.saveWizardPreferences({
4120
- commands: config2.commands,
4121
- codeStyle: {
4122
- naming: config2.namingConvention,
4123
- errorHandling: config2.errorHandling,
4124
- loggingConventions: config2.loggingConventions,
4125
- notes: config2.styleNotes
4126
- },
4127
- boundaries: {
4128
- preset: config2.boundaries,
4129
- never: config2.boundaryNever,
4130
- ask: config2.boundaryAsk
4131
- },
4132
- testing: {
4133
- levels: config2.testLevels,
4134
- frameworks: config2.testFrameworks,
4135
- coverage: config2.coverageTarget,
4136
- notes: config2.testNotes
4367
+ let saved = false;
4368
+ let attempts = 0;
4369
+ const maxAttempts = 3;
4370
+ while (!saved && attempts < maxAttempts) {
4371
+ attempts++;
4372
+ const saveSpinner = ora7("Saving preferences to your profile...").start();
4373
+ try {
4374
+ await api.saveWizardPreferences({
4375
+ commands: config2.commands,
4376
+ codeStyle: {
4377
+ naming: config2.namingConvention,
4378
+ errorHandling: config2.errorHandling,
4379
+ loggingConventions: config2.loggingConventions,
4380
+ loggingConventionsOther: config2.loggingConventionsOther,
4381
+ notes: config2.styleNotes
4382
+ },
4383
+ boundaries: {
4384
+ always: config2.boundaryAlways,
4385
+ never: config2.boundaryNever,
4386
+ ask: config2.boundaryAsk
4387
+ },
4388
+ testing: {
4389
+ levels: config2.testLevels,
4390
+ frameworks: config2.testFrameworks,
4391
+ coverage: config2.coverageTarget,
4392
+ notes: config2.testNotes
4393
+ }
4394
+ });
4395
+ saveSpinner.succeed("Preferences saved to your profile");
4396
+ saved = true;
4397
+ } catch (err) {
4398
+ saveSpinner.fail("Could not save preferences");
4399
+ let errorType = "unknown";
4400
+ if (err instanceof ApiRequestError) {
4401
+ if (err.statusCode === 401) {
4402
+ console.log(chalk8.yellow(" Your session may have expired. Try: lynxp login"));
4403
+ break;
4404
+ } else {
4405
+ console.log(chalk8.gray(` ${err.message} (status: ${err.statusCode})`));
4406
+ errorType = "api";
4407
+ }
4408
+ } else if (err instanceof Error) {
4409
+ if (err.message.includes("fetch failed") || err.message.includes("ENOTFOUND")) {
4410
+ console.log(chalk8.yellow(" Network error. Check your internet connection."));
4411
+ errorType = "network";
4412
+ } else {
4413
+ console.log(chalk8.gray(` ${err.message}`));
4414
+ }
4415
+ }
4416
+ if (errorType === "network" || errorType === "api") {
4417
+ if (attempts < maxAttempts) {
4418
+ const retryResponse = await prompts4({
4419
+ type: "confirm",
4420
+ name: "retry",
4421
+ message: chalk8.white("Would you like to retry?"),
4422
+ initial: true
4423
+ });
4424
+ if (!retryResponse.retry) {
4425
+ console.log(chalk8.gray(" Skipping preference save. Your config files are still generated."));
4426
+ break;
4427
+ }
4428
+ } else {
4429
+ console.log(chalk8.gray(` Max retries (${maxAttempts}) reached. Your config files are still generated.`));
4430
+ }
4431
+ } else {
4432
+ break;
4137
4433
  }
4138
- });
4139
- saveSpinner.succeed("Preferences saved to your profile");
4140
- } catch (err) {
4141
- saveSpinner.fail("Could not save preferences (you can still use the generated files)");
4142
- if (err instanceof Error) {
4143
- console.log(chalk8.gray(` ${err.message}`));
4144
4434
  }
4145
4435
  }
4146
4436
  }
@@ -4156,24 +4446,46 @@ async function wizardCommand(options) {
4156
4446
  }
4157
4447
  }
4158
4448
  async function runInteractiveWizard(options, detected, userTier) {
4159
- const answers = {};
4449
+ const answers = options._draftAnswers ? { ...options._draftAnswers } : {};
4450
+ const resumeFromStep = options._resumeFromStep || 0;
4160
4451
  const availableSteps = getAvailableSteps(userTier);
4161
4452
  let currentStepNum = 0;
4453
+ wizardState.inProgress = true;
4454
+ wizardState.answers = answers;
4455
+ wizardState.stepReached = resumeFromStep;
4456
+ if (resumeFromStep > 0 && Object.keys(answers).length > 0) {
4457
+ console.log(chalk8.cyan(` \u{1F4CB} Resuming from step ${resumeFromStep}...`));
4458
+ console.log(chalk8.gray(" Previously saved answers:"));
4459
+ if (answers.name) console.log(chalk8.gray(` \u2022 Name: ${answers.name}`));
4460
+ if (answers.platforms) console.log(chalk8.gray(` \u2022 Platforms: ${answers.platforms.join(", ")}`));
4461
+ if (answers.stack) console.log(chalk8.gray(` \u2022 Stack: ${answers.stack.slice(0, 5).join(", ")}${answers.stack.length > 5 ? "..." : ""}`));
4462
+ console.log();
4463
+ console.log(chalk8.yellow(" Steps 1-" + (resumeFromStep - 1) + " will use saved values. Continuing from step " + resumeFromStep + "."));
4464
+ console.log();
4465
+ }
4162
4466
  const getCurrentStep = (stepId) => {
4163
4467
  const step = availableSteps.find((s) => s.id === stepId);
4164
4468
  if (step) {
4165
4469
  currentStepNum++;
4470
+ wizardState.stepReached = currentStepNum;
4166
4471
  return step;
4167
4472
  }
4168
4473
  return null;
4169
4474
  };
4475
+ const shouldSkipStep = (stepNum) => {
4476
+ return resumeFromStep > 0 && stepNum < resumeFromStep;
4477
+ };
4170
4478
  const formatStep = getCurrentStep("format");
4171
- showStep(currentStepNum, formatStep, userTier);
4172
4479
  let platforms;
4173
- if (options.format) {
4480
+ if (shouldSkipStep(currentStepNum) && answers.platforms) {
4481
+ platforms = answers.platforms;
4482
+ console.log(chalk8.gray(` Step 1 (Output Format): Using saved platforms: ${platforms.join(", ")}`));
4483
+ } else if (options.format) {
4484
+ showStep(currentStepNum, formatStep, userTier);
4174
4485
  platforms = options.format.split(",").map((f) => f.trim());
4175
4486
  console.log(chalk8.gray(` Using format from flag: ${platforms.join(", ")}`));
4176
4487
  } else {
4488
+ showStep(currentStepNum, formatStep, userTier);
4177
4489
  console.log(chalk8.gray(" Select the AI editors you want to generate config for:"));
4178
4490
  console.log(chalk8.gray(" (AGENTS.md is recommended - works with most AI tools)"));
4179
4491
  console.log(chalk8.gray(" Type to search/filter the list."));
@@ -4259,6 +4571,15 @@ async function runInteractiveWizard(options, detected, userTier) {
4259
4571
  initial: 0
4260
4572
  }, promptConfig);
4261
4573
  answers.architecture = archResponse.architecture || "";
4574
+ if (answers.architecture === "other") {
4575
+ const customArchResponse = await prompts4({
4576
+ type: "text",
4577
+ name: "customArchitecture",
4578
+ message: chalk8.white("Describe your architecture pattern:"),
4579
+ hint: chalk8.gray("e.g., CQRS, Hexagonal, Clean Architecture")
4580
+ }, promptConfig);
4581
+ answers.architectureOther = customArchResponse.customArchitecture || "";
4582
+ }
4262
4583
  console.log();
4263
4584
  console.log(chalk8.yellow(" \u{1F9E9} Blueprint Template Mode"));
4264
4585
  console.log(chalk8.gray(" Create a reusable template with [[VARIABLE|default]] placeholders"));
@@ -4364,12 +4685,12 @@ async function runInteractiveWizard(options, detected, userTier) {
4364
4685
  type: "toggle",
4365
4686
  name: "isPublic",
4366
4687
  message: chalk8.white("Public repository?"),
4367
- initial: false,
4368
- // Default No
4688
+ initial: true,
4689
+ // Default Yes
4369
4690
  active: "Yes",
4370
4691
  inactive: "No"
4371
4692
  }, promptConfig);
4372
- answers.isPublic = visibilityResponse.isPublic ?? false;
4693
+ answers.isPublic = visibilityResponse.isPublic ?? true;
4373
4694
  const licenseChoices = [
4374
4695
  { title: chalk8.gray("\u23ED Skip"), value: "" },
4375
4696
  ...LICENSES.map((l) => ({
@@ -4390,30 +4711,33 @@ async function runInteractiveWizard(options, detected, userTier) {
4390
4711
  type: "toggle",
4391
4712
  name: "conventionalCommits",
4392
4713
  message: chalk8.white("Use Conventional Commits?"),
4393
- initial: false,
4714
+ initial: true,
4715
+ // Default Yes
4394
4716
  active: "Yes",
4395
4717
  inactive: "No"
4396
4718
  }, promptConfig);
4397
- answers.conventionalCommits = conventionalResponse.conventionalCommits || false;
4719
+ answers.conventionalCommits = conventionalResponse.conventionalCommits ?? true;
4398
4720
  const semverResponse = await prompts4({
4399
4721
  type: "toggle",
4400
4722
  name: "semver",
4401
4723
  message: chalk8.white("Use Semantic Versioning?"),
4402
- initial: false,
4724
+ initial: true,
4725
+ // Default Yes
4403
4726
  active: "Yes",
4404
4727
  inactive: "No"
4405
4728
  }, promptConfig);
4406
- answers.semver = semverResponse.semver || false;
4729
+ answers.semver = semverResponse.semver ?? true;
4407
4730
  if (answers.repoHost === "github" || answers.repoHost === "gitlab") {
4408
4731
  const dependabotResponse = await prompts4({
4409
4732
  type: "toggle",
4410
4733
  name: "dependabot",
4411
4734
  message: chalk8.white("Enable Dependabot/dependency updates?"),
4412
- initial: false,
4735
+ initial: true,
4736
+ // Default Yes
4413
4737
  active: "Yes",
4414
4738
  inactive: "No"
4415
4739
  }, promptConfig);
4416
- answers.dependabot = dependabotResponse.dependabot || false;
4740
+ answers.dependabot = dependabotResponse.dependabot ?? true;
4417
4741
  }
4418
4742
  const cicdChoices = [
4419
4743
  { title: chalk8.gray("\u23ED Skip"), value: "" },
@@ -4432,15 +4756,15 @@ async function runInteractiveWizard(options, detected, userTier) {
4432
4756
  }, promptConfig);
4433
4757
  answers.cicd = cicdResponse.cicd || "";
4434
4758
  const deployResponse = await prompts4({
4435
- type: "multiselect",
4759
+ type: "autocompleteMultiselect",
4436
4760
  name: "deploymentTargets",
4437
- message: chalk8.white("Deployment targets:"),
4761
+ message: chalk8.white("Deployment targets (type to search):"),
4438
4762
  choices: DEPLOYMENT_TARGETS.map((t) => ({
4439
4763
  title: t.id === "docker" && detected?.hasDocker ? `${t.icon} ${t.label} ${chalk8.green("(detected)")}` : `${t.icon} ${t.label}`,
4440
4764
  selected: t.id === "docker" && detected?.hasDocker,
4441
4765
  value: t.id
4442
4766
  })),
4443
- hint: chalk8.gray("space select \u2022 enter to skip/confirm"),
4767
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4444
4768
  instructions: false
4445
4769
  }, promptConfig);
4446
4770
  answers.deploymentTargets = deployResponse.deploymentTargets || [];
@@ -4486,66 +4810,78 @@ async function runInteractiveWizard(options, detected, userTier) {
4486
4810
  if (canAccessTier(userTier, "intermediate")) {
4487
4811
  const commandsStep = getCurrentStep("commands");
4488
4812
  showStep(currentStepNum, commandsStep, userTier);
4489
- console.log(chalk8.gray(" Select common commands for your project:"));
4813
+ console.log(chalk8.gray(" Select common commands for your project (type to search):"));
4490
4814
  console.log();
4491
4815
  const buildResponse = await prompts4({
4492
- type: "multiselect",
4816
+ type: "autocompleteMultiselect",
4493
4817
  name: "build",
4494
- message: chalk8.white("Build commands:"),
4495
- choices: COMMON_COMMANDS.build.slice(0, 12).map((c) => ({
4818
+ message: chalk8.white("Build commands (type to search):"),
4819
+ choices: COMMON_COMMANDS.build.map((c) => ({
4496
4820
  title: chalk8.cyan(c),
4497
4821
  value: c,
4498
4822
  selected: detected?.commands?.build === c
4499
4823
  })),
4500
- hint: chalk8.gray("space select \u2022 enter confirm"),
4824
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4501
4825
  instructions: false
4502
4826
  }, promptConfig);
4503
4827
  const testResponse = await prompts4({
4504
- type: "multiselect",
4828
+ type: "autocompleteMultiselect",
4505
4829
  name: "test",
4506
- message: chalk8.white("Test commands:"),
4507
- choices: COMMON_COMMANDS.test.slice(0, 12).map((c) => ({
4830
+ message: chalk8.white("Test commands (type to search):"),
4831
+ choices: COMMON_COMMANDS.test.map((c) => ({
4508
4832
  title: chalk8.yellow(c),
4509
4833
  value: c,
4510
4834
  selected: detected?.commands?.test === c
4511
4835
  })),
4512
- hint: chalk8.gray("space select \u2022 enter confirm"),
4836
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4513
4837
  instructions: false
4514
4838
  }, promptConfig);
4515
4839
  const lintResponse = await prompts4({
4516
- type: "multiselect",
4840
+ type: "autocompleteMultiselect",
4517
4841
  name: "lint",
4518
- message: chalk8.white("Lint/format commands:"),
4519
- choices: COMMON_COMMANDS.lint.slice(0, 12).map((c) => ({
4842
+ message: chalk8.white("Lint/format commands (type to search):"),
4843
+ choices: COMMON_COMMANDS.lint.map((c) => ({
4520
4844
  title: chalk8.green(c),
4521
4845
  value: c,
4522
4846
  selected: detected?.commands?.lint === c
4523
4847
  })),
4524
- hint: chalk8.gray("space select \u2022 enter confirm"),
4848
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4525
4849
  instructions: false
4526
4850
  }, promptConfig);
4527
4851
  const devResponse = await prompts4({
4528
- type: "multiselect",
4852
+ type: "autocompleteMultiselect",
4529
4853
  name: "dev",
4530
- message: chalk8.white("Dev server commands:"),
4531
- choices: COMMON_COMMANDS.dev.slice(0, 12).map((c) => ({
4854
+ message: chalk8.white("Dev server commands (type to search):"),
4855
+ choices: COMMON_COMMANDS.dev.map((c) => ({
4532
4856
  title: chalk8.magenta(c),
4533
4857
  value: c,
4534
4858
  selected: detected?.commands?.dev === c
4535
4859
  })),
4536
- hint: chalk8.gray("space select \u2022 enter confirm"),
4860
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4861
+ instructions: false
4862
+ }, promptConfig);
4863
+ const additionalResponse = await prompts4({
4864
+ type: "autocompleteMultiselect",
4865
+ name: "additional",
4866
+ message: chalk8.white("Additional commands (type to search):"),
4867
+ choices: COMMON_COMMANDS.additional.map((c) => ({
4868
+ title: chalk8.blue(c),
4869
+ value: c
4870
+ })),
4871
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4537
4872
  instructions: false
4538
4873
  }, promptConfig);
4539
4874
  answers.commands = {
4540
4875
  build: buildResponse.build || [],
4541
4876
  test: testResponse.test || [],
4542
4877
  lint: lintResponse.lint || [],
4543
- dev: devResponse.dev || []
4878
+ dev: devResponse.dev || [],
4879
+ additional: additionalResponse.additional || []
4544
4880
  };
4545
4881
  const customCmdResponse = await prompts4({
4546
4882
  type: "text",
4547
4883
  name: "custom",
4548
- message: chalk8.white("Additional custom command (optional):"),
4884
+ message: chalk8.white("Custom command (optional):"),
4549
4885
  hint: chalk8.gray("e.g., npm run migrate, make deploy")
4550
4886
  }, promptConfig);
4551
4887
  if (customCmdResponse.custom) {
@@ -4586,13 +4922,38 @@ async function runInteractiveWizard(options, detected, userTier) {
4586
4922
  initial: 0
4587
4923
  }, promptConfig);
4588
4924
  answers.errorHandling = errorResponse.errorHandling || "";
4925
+ if (answers.errorHandling === "other") {
4926
+ const customErrorResponse = await prompts4({
4927
+ type: "text",
4928
+ name: "customErrorHandling",
4929
+ message: chalk8.white("Describe your error handling approach:"),
4930
+ hint: chalk8.gray("e.g., Railway-oriented, custom error boundaries")
4931
+ }, promptConfig);
4932
+ answers.errorHandlingOther = customErrorResponse.customErrorHandling || "";
4933
+ }
4589
4934
  const loggingResponse = await prompts4({
4590
- type: "text",
4935
+ type: "autocomplete",
4591
4936
  name: "loggingConventions",
4592
- message: chalk8.white("Logging conventions (optional):"),
4593
- hint: chalk8.gray("e.g., use structured logging, JSON format, specific lib")
4937
+ message: chalk8.white("Logging conventions (type to search):"),
4938
+ choices: [
4939
+ { title: chalk8.gray("\u23ED Skip"), value: "" },
4940
+ ...LOGGING_OPTIONS.map((l) => ({
4941
+ title: l.label,
4942
+ value: l.id
4943
+ }))
4944
+ ],
4945
+ initial: 0
4594
4946
  }, promptConfig);
4595
4947
  answers.loggingConventions = loggingResponse.loggingConventions || "";
4948
+ if (answers.loggingConventions === "other") {
4949
+ const customLoggingResponse = await prompts4({
4950
+ type: "text",
4951
+ name: "customLogging",
4952
+ message: chalk8.white("Describe your logging convention:"),
4953
+ hint: chalk8.gray("e.g., custom logger, file-based logging")
4954
+ }, promptConfig);
4955
+ answers.loggingConventionsOther = customLoggingResponse.customLogging || "";
4956
+ }
4596
4957
  const styleNotesResponse = await prompts4({
4597
4958
  type: "text",
4598
4959
  name: "styleNotes",
@@ -4604,28 +4965,36 @@ async function runInteractiveWizard(options, detected, userTier) {
4604
4965
  const aiStep = getCurrentStep("ai");
4605
4966
  showStep(currentStepNum, aiStep, userTier);
4606
4967
  const aiBehaviorResponse = await prompts4({
4607
- type: "multiselect",
4968
+ type: "autocompleteMultiselect",
4608
4969
  name: "aiBehavior",
4609
- message: chalk8.white("AI behavior rules:"),
4970
+ message: chalk8.white("AI behavior rules (type to filter):"),
4610
4971
  choices: AI_BEHAVIOR_RULES.map((r) => ({
4611
- title: r.recommended ? `${r.label} ${chalk8.green("\u2605 recommended")}` : r.label,
4612
- value: r.id
4972
+ title: r.recommended ? `${r.label} ${chalk8.green("\u2605")}` : r.label,
4973
+ value: r.id,
4974
+ description: chalk8.gray(r.description)
4613
4975
  // No pre-selection - user must explicitly choose
4614
4976
  })),
4615
- hint: chalk8.gray("space select \u2022 enter to skip/confirm"),
4977
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4616
4978
  instructions: false
4617
4979
  }, promptConfig);
4618
4980
  answers.aiBehavior = aiBehaviorResponse.aiBehavior || [];
4981
+ if (answers.aiBehavior.length > 0) {
4982
+ console.log(chalk8.green(" \u2713 Selected:"));
4983
+ for (const ruleId of answers.aiBehavior) {
4984
+ const rule = AI_BEHAVIOR_RULES.find((r) => r.id === ruleId);
4985
+ if (rule) console.log(chalk8.cyan(` \u2022 ${rule.label}`));
4986
+ }
4987
+ }
4619
4988
  const importantFilesResponse = await prompts4({
4620
- type: "multiselect",
4989
+ type: "autocompleteMultiselect",
4621
4990
  name: "importantFiles",
4622
- message: chalk8.white("Important files AI should read:"),
4991
+ message: chalk8.white("Important files AI should read (type to search):"),
4623
4992
  choices: IMPORTANT_FILES.map((f) => ({
4624
4993
  title: `${f.icon} ${f.label}`,
4625
4994
  value: f.id
4626
4995
  // No pre-selection - user must explicitly choose
4627
4996
  })),
4628
- hint: chalk8.gray("space select \u2022 enter to skip/confirm"),
4997
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4629
4998
  instructions: false
4630
4999
  }, promptConfig);
4631
5000
  answers.importantFiles = importantFilesResponse.importantFiles || [];
@@ -4641,7 +5010,7 @@ async function runInteractiveWizard(options, detected, userTier) {
4641
5010
  const includePersonalResponse = await prompts4({
4642
5011
  type: "toggle",
4643
5012
  name: "includePersonalData",
4644
- message: chalk8.white("Include personal data (name/email for commits)?"),
5013
+ message: chalk8.white("Include personal data (as saved in WebUI)?"),
4645
5014
  initial: false,
4646
5015
  active: "Yes",
4647
5016
  inactive: "No"
@@ -4650,65 +5019,65 @@ async function runInteractiveWizard(options, detected, userTier) {
4650
5019
  if (canAccessTier(userTier, "advanced")) {
4651
5020
  const boundariesStep = getCurrentStep("boundaries");
4652
5021
  showStep(currentStepNum, boundariesStep, userTier);
4653
- const presetResponse = await prompts4({
4654
- type: "select",
4655
- name: "boundaryPreset",
4656
- message: chalk8.white("Boundary preset:"),
4657
- choices: [
4658
- { title: chalk8.gray("\u23ED Skip"), value: "" },
4659
- ...BOUNDARY_PRESETS.map((b) => ({
4660
- title: b.title,
4661
- value: b.value,
4662
- description: chalk8.gray(b.description)
4663
- }))
4664
- ],
4665
- initial: 0
5022
+ console.log(chalk8.gray(" Define what AI should never do, ask first, or always do."));
5023
+ console.log(chalk8.gray(" Each option can only be in one category."));
5024
+ console.log();
5025
+ const usedOptions = /* @__PURE__ */ new Set();
5026
+ console.log(chalk8.red.bold(" \u2717 NEVER ALLOW - AI will refuse to do"));
5027
+ const neverResponse = await prompts4({
5028
+ type: "autocompleteMultiselect",
5029
+ name: "never",
5030
+ message: chalk8.white("Never allow (type to filter):"),
5031
+ choices: BOUNDARY_OPTIONS.map((o) => ({
5032
+ title: chalk8.red(o),
5033
+ value: o
5034
+ })),
5035
+ hint: chalk8.gray("space select \u2022 enter confirm"),
5036
+ instructions: false
4666
5037
  }, promptConfig);
4667
- answers.boundaries = presetResponse.boundaryPreset || "";
4668
- const selectedPreset = BOUNDARY_PRESETS.find((b) => b.value === answers.boundaries);
4669
- if (selectedPreset) {
4670
- console.log();
4671
- console.log(chalk8.gray(" Preset details:"));
4672
- console.log(chalk8.green(` \u2713 Always: ${selectedPreset.always.slice(0, 3).join(", ")}`));
4673
- console.log(chalk8.yellow(` ? Ask: ${selectedPreset.askFirst.slice(0, 2).join(", ")}`));
4674
- console.log(chalk8.red(` \u2717 Never: ${selectedPreset.never.slice(0, 2).join(", ")}`));
4675
- }
4676
- const customizeResponse = await prompts4({
4677
- type: "toggle",
4678
- name: "customize",
4679
- message: chalk8.white("Customize specific boundaries?"),
4680
- initial: false,
4681
- active: "Yes",
4682
- inactive: "No"
5038
+ answers.boundaryNever = neverResponse.never || [];
5039
+ answers.boundaryNever.forEach((o) => usedOptions.add(o));
5040
+ console.log();
5041
+ console.log(chalk8.yellow.bold(" ? ASK FIRST - AI will ask before doing"));
5042
+ const availableForAsk = BOUNDARY_OPTIONS.filter((o) => !usedOptions.has(o));
5043
+ const askResponse = await prompts4({
5044
+ type: "autocompleteMultiselect",
5045
+ name: "ask",
5046
+ message: chalk8.white("Ask first (type to filter):"),
5047
+ choices: availableForAsk.map((o) => ({
5048
+ title: chalk8.yellow(o),
5049
+ value: o
5050
+ })),
5051
+ hint: chalk8.gray("space select \u2022 enter confirm"),
5052
+ instructions: false
4683
5053
  }, promptConfig);
4684
- if (customizeResponse.customize) {
4685
- console.log();
4686
- console.log(chalk8.gray(" Select actions AI should NEVER do:"));
4687
- const neverResponse = await prompts4({
4688
- type: "multiselect",
4689
- name: "never",
4690
- message: chalk8.white("Never allow:"),
4691
- choices: BOUNDARY_OPTIONS.map((o) => ({
4692
- title: chalk8.red(o),
4693
- value: o,
4694
- selected: selectedPreset?.never.includes(o)
4695
- })),
4696
- instructions: false
4697
- }, promptConfig);
4698
- answers.boundaryNever = neverResponse.never || [];
4699
- console.log(chalk8.gray(" Select actions AI should ASK before doing:"));
4700
- const askResponse = await prompts4({
4701
- type: "multiselect",
4702
- name: "ask",
4703
- message: chalk8.white("Ask first:"),
4704
- choices: BOUNDARY_OPTIONS.filter((o) => !answers.boundaryNever?.includes(o)).map((o) => ({
4705
- title: chalk8.yellow(o),
4706
- value: o,
4707
- selected: selectedPreset?.askFirst.includes(o)
4708
- })),
4709
- instructions: false
4710
- }, promptConfig);
4711
- answers.boundaryAsk = askResponse.ask || [];
5054
+ answers.boundaryAsk = askResponse.ask || [];
5055
+ answers.boundaryAsk.forEach((o) => usedOptions.add(o));
5056
+ console.log();
5057
+ console.log(chalk8.green.bold(" \u2713 ALWAYS ALLOW - AI will do these automatically"));
5058
+ const availableForAlways = BOUNDARY_OPTIONS.filter((o) => !usedOptions.has(o));
5059
+ const alwaysResponse = await prompts4({
5060
+ type: "autocompleteMultiselect",
5061
+ name: "always",
5062
+ message: chalk8.white("Always allow (type to filter):"),
5063
+ choices: availableForAlways.map((o) => ({
5064
+ title: chalk8.green(o),
5065
+ value: o
5066
+ })),
5067
+ hint: chalk8.gray("space select \u2022 enter confirm"),
5068
+ instructions: false
5069
+ }, promptConfig);
5070
+ answers.boundaryAlways = alwaysResponse.always || [];
5071
+ console.log();
5072
+ console.log(chalk8.gray(" Boundary summary:"));
5073
+ if (answers.boundaryAlways.length > 0) {
5074
+ console.log(chalk8.green(` \u2713 Always: ${answers.boundaryAlways.join(", ")}`));
5075
+ }
5076
+ if (answers.boundaryAsk.length > 0) {
5077
+ console.log(chalk8.yellow(` ? Ask: ${answers.boundaryAsk.join(", ")}`));
5078
+ }
5079
+ if (answers.boundaryNever.length > 0) {
5080
+ console.log(chalk8.red(` \u2717 Never: ${answers.boundaryNever.join(", ")}`));
4712
5081
  }
4713
5082
  } else {
4714
5083
  answers.boundaries = options.boundaries || "standard";
@@ -4717,28 +5086,29 @@ async function runInteractiveWizard(options, detected, userTier) {
4717
5086
  const testingStep = getCurrentStep("testing");
4718
5087
  showStep(currentStepNum, testingStep, userTier);
4719
5088
  const testLevelsResponse = await prompts4({
4720
- type: "multiselect",
5089
+ type: "autocompleteMultiselect",
4721
5090
  name: "testLevels",
4722
- message: chalk8.white("Test levels:"),
5091
+ message: chalk8.white("Test levels (type to search):"),
4723
5092
  choices: TEST_LEVELS.map((l) => ({
4724
5093
  title: `${l.label} - ${chalk8.gray(l.desc)}`,
4725
5094
  value: l.id,
4726
5095
  selected: l.id === "unit" || l.id === "integration"
4727
5096
  })),
5097
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4728
5098
  instructions: false
4729
5099
  }, promptConfig);
4730
5100
  answers.testLevels = testLevelsResponse.testLevels || [];
4731
5101
  const detectedFrameworks = answers.stack?.includes("typescript") || answers.stack?.includes("javascript") ? ["jest", "vitest"] : answers.stack?.includes("python") ? ["pytest"] : [];
4732
5102
  const testFrameworkResponse = await prompts4({
4733
- type: "multiselect",
5103
+ type: "autocompleteMultiselect",
4734
5104
  name: "testFrameworks",
4735
- message: chalk8.white("Testing frameworks:"),
4736
- choices: TEST_FRAMEWORKS.slice(0, 16).map((f) => ({
5105
+ message: chalk8.white("Testing frameworks (type to search):"),
5106
+ choices: TEST_FRAMEWORKS.map((f) => ({
4737
5107
  title: f,
4738
5108
  value: f,
4739
5109
  selected: detectedFrameworks.includes(f)
4740
5110
  })),
4741
- hint: chalk8.gray("space select \u2022 enter confirm"),
5111
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4742
5112
  instructions: false
4743
5113
  }, promptConfig);
4744
5114
  answers.testFrameworks = testFrameworkResponse.testFrameworks || [];
@@ -4793,15 +5163,15 @@ async function runInteractiveWizard(options, detected, userTier) {
4793
5163
  console.log();
4794
5164
  }
4795
5165
  const staticFilesResponse = await prompts4({
4796
- type: "multiselect",
5166
+ type: "autocompleteMultiselect",
4797
5167
  name: "staticFiles",
4798
- message: chalk8.white("Include static files:"),
5168
+ message: chalk8.white("Include static files (type to search):"),
4799
5169
  choices: STATIC_FILE_OPTIONS.map((f) => ({
4800
5170
  title: existingFiles[f.value] ? `${f.title} ${chalk8.green("(exists)")}` : f.title,
4801
5171
  value: f.value,
4802
5172
  description: chalk8.gray(f.desc)
4803
5173
  })),
4804
- hint: chalk8.gray("space select \u2022 enter to skip/confirm"),
5174
+ hint: chalk8.gray("type to filter \u2022 space select \u2022 enter confirm"),
4805
5175
  instructions: false
4806
5176
  }, promptConfig);
4807
5177
  answers.staticFiles = staticFilesResponse.staticFiles || [];
@@ -4903,33 +5273,7 @@ async function runInteractiveWizard(options, detected, userTier) {
4903
5273
  }
4904
5274
  const extraStep = getCurrentStep("extra");
4905
5275
  showStep(currentStepNum, extraStep, userTier);
4906
- const personaResponse = await prompts4({
4907
- type: "select",
4908
- name: "persona",
4909
- message: chalk8.white("AI assistant persona:"),
4910
- choices: [
4911
- { title: chalk8.gray("\u23ED Skip"), value: "" },
4912
- { title: "\u{1F9D1}\u200D\u{1F4BB} Full-Stack Developer", value: "fullstack", description: chalk8.gray("Complete application development") },
4913
- { title: "\u2699\uFE0F Backend Developer", value: "backend", description: chalk8.gray("APIs, databases, services") },
4914
- { title: "\u{1F3A8} Frontend Developer", value: "frontend", description: chalk8.gray("UI, components, styling") },
4915
- { title: "\u{1F680} DevOps Engineer", value: "devops", description: chalk8.gray("Infrastructure, CI/CD") },
4916
- { title: "\u{1F4CA} Data Engineer", value: "data", description: chalk8.gray("Pipelines, ETL, analytics") },
4917
- { title: "\u{1F512} Security Engineer", value: "security", description: chalk8.gray("Secure code, auditing") },
4918
- { title: "\u270F\uFE0F Custom...", value: "custom", description: chalk8.gray("Define your own") }
4919
- ],
4920
- initial: 0
4921
- }, promptConfig);
4922
- if (personaResponse.persona === "custom") {
4923
- const customPersona = await prompts4({
4924
- type: "text",
4925
- name: "value",
4926
- message: chalk8.white("Describe the custom persona:"),
4927
- hint: chalk8.gray("e.g., 'ML engineer focused on PyTorch'")
4928
- }, promptConfig);
4929
- answers.persona = customPersona.value || "";
4930
- } else {
4931
- answers.persona = personaResponse.persona || "";
4932
- }
5276
+ answers.persona = "";
4933
5277
  const hasAIAccess = canAccessAI(userTier);
4934
5278
  if (hasAIAccess) {
4935
5279
  console.log();
@@ -4978,6 +5322,7 @@ async function runInteractiveWizard(options, detected, userTier) {
4978
5322
  console.log();
4979
5323
  console.log(chalk8.green(" \u2705 All steps completed!"));
4980
5324
  console.log();
5325
+ wizardState.inProgress = false;
4981
5326
  return {
4982
5327
  name: answers.name,
4983
5328
  description: answers.description,
@@ -5004,6 +5349,7 @@ async function runInteractiveWizard(options, detected, userTier) {
5004
5349
  importantFiles: answers.importantFiles,
5005
5350
  selfImprove: answers.selfImprove,
5006
5351
  includePersonalData: answers.includePersonalData,
5352
+ boundaryAlways: answers.boundaryAlways,
5007
5353
  boundaryNever: answers.boundaryNever,
5008
5354
  boundaryAsk: answers.boundaryAsk,
5009
5355
  testLevels: answers.testLevels,
@@ -5022,7 +5368,8 @@ async function runInteractiveWizard(options, detected, userTier) {
5022
5368
  containerRegistry: answers.containerRegistry,
5023
5369
  exampleRepoUrl: answers.exampleRepoUrl,
5024
5370
  documentationUrl: answers.documentationUrl,
5025
- loggingConventions: answers.loggingConventions
5371
+ loggingConventions: answers.loggingConventions,
5372
+ loggingConventionsOther: answers.loggingConventionsOther
5026
5373
  };
5027
5374
  }
5028
5375