Skip to content

chore: onboard api-creator as a Lisa project (Bun) - #4

Merged
CodySwannGT merged 7 commits into
mainfrom
chore/lisa-onboarding
Mar 20, 2026
Merged

chore: onboard api-creator as a Lisa project (Bun)#4
CodySwannGT merged 7 commits into
mainfrom
chore/lisa-onboarding

Conversation

@CodySwannGT

@CodySwannGT CodySwannGT commented Mar 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • Migrate from npm to Bun as the package manager
  • Install @codyswann/lisa and apply TypeScript stack governance templates (ESLint, Prettier, Vitest, commitlint, Husky, knip, ast-grep)
  • Configure local overrides (eslint.config.local.ts, vitest.config.local.ts, thresholds) for initial onboarding of pre-existing CLI codebase
  • Replace inline OIDC publish logic in release.yml with Lisa's reusable publish-to-npm.yml workflow
  • Replace custom ci.yml with Lisa's reusable quality workflow
  • Fix all ESLint hard errors (unused variables, irregular whitespace) and apply Prettier formatting
  • All 6 quality checks pass: build, typecheck, lint, test (66 tests), format, knip

Test plan

  • bun run build exits 0 (tsup produces dist/cli.js)
  • bun run typecheck exits 0
  • bun run lint exits 0 (with relaxed thresholds for onboarding)
  • bun run test exits 0 (all 66 existing tests pass)
  • bun run knip exits 0
  • bun run format:check exits 0
  • Pre-commit hooks succeed
  • Pre-push hooks succeed (slow lint, coverage, integration tests, security audit)
  • CI quality workflow passes on GitHub Actions
  • After merge: release.yml runs successfully and publishes to npm via reusable workflow

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • CLI: richer project/endpoint commands, auth setup/status/clear, and improved test/run helpers.
    • Automated publishing and release workflows integrated into CI.
  • Chores

    • Standardized dev tooling and formatting (ESLint, Prettier, TypeScript, Vitest) and updated runtime version.
    • Expanded CI/CD workflows, dependency automation, and pre-commit/pre-push guardrails.
    • Large suite of new tests covering import/parsing, generation, runtime, and tooling.

Replace npm with Bun as the package manager. Delete package-lock.json
and generate bun.lock. Update .gitignore for coverage and Lisa backups.

🤖 Generated with Claude Code

Co-Authored-By: Claude
Install @codyswann/lisa and apply TypeScript stack templates:
- ESLint flat config with local overrides for initial onboarding
- Prettier, commitlint, husky hooks (pre-commit, pre-push, commit-msg)
- Vitest config with coverage thresholds
- TypeScript strict config extending Lisa base
- Knip dead code detection
- ast-grep structural search
- GitHub Actions: CI quality workflow, Claude workflows, dependabot
- Reusable publish-to-npm workflow with OIDC

🤖 Generated with Claude Code

Co-Authored-By: Claude
- Fix unused variables (prefix with _ or remove)
- Auto-fix prefer-template, prefer-const, brace-style
- Apply Prettier formatting across all source and test files
- Fix irregular whitespace in JSDoc comment

🤖 Generated with Claude Code

Co-Authored-By: Claude
- Replace npm with Bun for install/build/test steps
- Add oven-sh/setup-bun@v2 step
- Chain the reusable publish-to-npm.yml workflow instead of
  inline OIDC publish logic

🤖 Generated with Claude Code

Co-Authored-By: Claude
Lisa's postinstall overwrites knip.json (copy-overwrite), which
removes project-specific ignoreBinaries entries. Instead of fighting
the config, declare eslint, prettier, knip, and @ast-grep/cli as
direct devDependencies so knip can resolve them.

Also re-add coverage/ to .prettierignore after Lisa overwrite.

🤖 Generated with Claude Code

Co-Authored-By: Claude
- Reset eslint.config.local.ts to empty (test-only overrides for jsdoc,
  duplicate strings, publicly-writable-directories, max-lines)
- Reset eslint.thresholds.json to defaults (complexity 10, 300 lines, 75/fn)
- Reset vitest.thresholds.json to 70/70/70/70 coverage
- Remove globals:true from vitest.config.local.ts, add coverage exclusions
  for src/types (pure interfaces), src/recorder (Playwright), src/cli.ts
- Replace .gitignore with Lisa template + project-specific entries
- Rename test/ → tests/ to match Lisa convention

Split 5 oversized files (all now ≤300 lines):
- cli-project-emitter.ts → + auth-emitter, commands-emitter,
  graphql-commands-emitter, cli-entrypoint-emitter
- client-emitter.ts → + client-method-emitter
- type-inferrer.ts → + property-inferrer
- project-runner.ts → + endpoint-command-builder
- commands/test.ts → + test-helpers

Deduplicate singularize/camelToKebab/kebabToCamel into utils/naming.ts.

Fix all ESLint violations: functional/immutable-data (array-literal patterns),
functional/no-let, jsdoc/*, code-organization, sonarjs/*, no-param-reassign.

Add 338 tests across 31 files (up from 66 tests / 9 files).
Coverage: 89% stmts, 77% branches, 92% functions, 90% lines.

🤖 Generated with Claude Code

Co-Authored-By: Claude
@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds comprehensive CI/CD and developer tooling, substantial generator and parser refactors, new runtime CLI command builders and auth handling, expanded Git hooks and project-wide lint/test configs, and a large new/updated Vitest test suite converting old tests to a new tests/ layout.

Changes

Cohort / File(s) Summary
CI/CD & Automation
.github/workflows/*.yml, .github/GITHUB_ACTIONS.md, .github/dependabot.yml
Added 10+ workflows (quality, auto-update, Claude integrations, nightly jobs, publish/release), refactored ci.yml to use reusable workflows, added publish-to-npm reusable workflow, dispatch workflows, and Dependabot schedule; included documentation .github/GITHUB_ACTIONS.md.
Claude / Tooling Settings
.claude/settings.json, .claude/rules/PROJECT_RULES.md
Added Claude runtime/settings and project rules files configuring plugins, marketplaces, permissions, and project-specific rules.
Git & Local Dev Hooks
.husky/*, .gitignore, .gitleaksignore, .safety-net.json, .lintstagedrc.json
Introduced/updated Husky hooks (commit-msg, pre-commit, pre-push) with branch protection, gitleaks scans, typecheck, auditing; expanded .gitignore; added gitleaks ignores and safety-net rules.
Formatting & Linting
.prettierrc.json, .prettierignore, .yamllint, eslint.config*.ts, eslint.*.json, tsconfig.eslint.json
Added Prettier and ignore, yamllint, ESLint flat config + slow rules, ignore/threshold files, and TS config for ESLint.
Testing & Coverage
vitest.config*.ts, vitest.thresholds.json, tsup.config.ts
Reworked Vitest config composition, added local overrides/thresholds and tsup minor formatting changes.
Package & Build
package.json, .nvmrc, .versionrc
Expanded scripts, prepare/postinstall hooks, engines (prefer bun), devDependencies, resolutions/overrides, updated Node version file, and reformatted versionrc.
Generator Refactor
src/generator/... (many files) e.g. auth-emitter.ts, cli-entrypoint-emitter.ts, client-emitter.ts, client-method-emitter.ts, commands-emitter.ts, cli-project-emitter.ts, codegen.ts, types-emitter.ts, diff-merger.ts, index-emitter.ts
Major refactor: split generator into focused emitter modules, extracted auth/CLI/commands emitters, moved endpoint/health method emission into client-method-emitter, adopted functional pipelines and helper extraction across codegen/types emission/diff merging.
Importer & Parser
src/importer/*, src/parser/* e.g. paste-parser.ts, paste-parser-http.ts, format-detector.ts, har-reader.ts, endpoint-grouper.ts, property-inferrer.ts, type-inferrer.ts, auth-detector.ts, property-inferrer.ts
Added HTTP paste parser, refactored curl/fetch/tokenizers, introduced property inferrer for runtime types, reorganized parser pipeline, and applied functional/immutable patterns.
Recorder & Capture
src/recorder/* (e.g. auth-capture.ts, browser-session.ts, network-capture.ts)
Refactored browser session and auth capture flows into helpers, improved state/cleanup and request/header extraction logic.
Runtime & CLI commands
src/commands/*, src/runtime/*, src/cli.ts (e.g. test-helpers.ts, test.ts, generate.ts, import.ts, export.ts, record.ts, list.ts, endpoint-command-builder.ts, http-client.ts, project-manager.ts, project-runner.ts)
Extracted test helpers, centralized endpoint command registration, refactored test/record/generate/import/export commands, added HTTP client header builder, and introduced project manifest/auth path constants and helpers.
Utilities & Types
src/utils/*, src/types/*, src/runtime/* (e.g. naming.ts, url-pattern.ts, auth.ts, har.ts)
Refactored naming utilities (added singularize/case helpers), improved URL normalization, added JSDoc to type files, and normalized string quoting.
Tests — Removal & Replacement
test/**tests/**
Removed legacy test/ suites and added a broad set of new Vitest suites under tests/ covering commands, generator emitters, importer/parser, parser internals, runtime clients/CLI, and utilities with extensive mocking.
Small formatting/UX tweaks & misc files
bin/api-creator.js, .lisaignore, .prettierignore, .versionrc, sgconfig.yml, audit.ignore.*`
Minor quoting/format changes, added ignore/config files (sgconfig, audit ignore, lisa ignore), and other configuration/support files.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/lisa-onboarding

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (33)
src/runtime/curl-parser.ts-31-45 (1)

31-45: ⚠️ Potential issue | 🟠 Major

x-api-key auth is currently discarded.

AuthConfig and httpRequest() still support apiKey, but this parser demotes x-api-key to extraHeaders and then returns null unless a cookie or bearer token is present. API-key-only projects will fail auth setup. Please add an x-api-key-only regression test with the fix.

Suggested change
 function classifyHeader(
   hName: string,
   hValue: string
-): { cookie?: string; token?: string; extraHeader?: [string, string] } {
+): {
+  cookie?: string;
+  token?: string;
+  apiKey?: string;
+  extraHeader?: [string, string];
+} {
   const hLower = hName.toLowerCase();

   if (hLower === "cookie") {
     return { cookie: hValue };
   }
   if (
     hLower === "authorization" &&
     hValue.toLowerCase().startsWith("bearer ")
   ) {
     return { token: hValue.replace(/^[Bb]earer\s+/, "") };
   }
+  if (hLower === "x-api-key") {
+    return { apiKey: hValue };
+  }
   if (
     hLower.startsWith("x-") &&
     hValue &&
     !hLower.startsWith("x-client-") &&
     hLower !== "x-csrf-without-token"
@@
 function extractAllHeaderAuth(joined: string): {
   cookie: string | undefined;
   token: string | undefined;
+  apiKey: string | undefined;
   extraHeaders: Record<string, string>;
 } {
@@
   return matches.reduce<{
     cookie: string | undefined;
     token: string | undefined;
+    apiKey: string | undefined;
     extraHeaders: Record<string, string>;
   }>(
     (acc, hm) => {
@@
       return {
         cookie: classified.cookie ?? acc.cookie,
         token: classified.token ?? acc.token,
+        apiKey: classified.apiKey ?? acc.apiKey,
         extraHeaders: classified.extraHeader
           ? {
               ...acc.extraHeaders,
               [classified.extraHeader[0]]: classified.extraHeader[1],
             }
           : acc.extraHeaders,
       };
     },
-    { cookie: undefined, token: undefined, extraHeaders: {} }
+    { cookie: undefined, token: undefined, apiKey: undefined, extraHeaders: {} }
   );
 }
@@
   const {
     cookie: headerCookie,
     token,
+    apiKey,
     extraHeaders,
   } = extractAllHeaderAuth(joined);

   const cookie = bMatch ? bMatch[1] : headerCookie;

-  if (!cookie && !token) return null;
+  if (!cookie && !token && !apiKey) return null;

   return {
     ...(cookie ? { cookie } : {}),
     ...(token ? { token } : {}),
+    ...(apiKey ? { apiKey } : {}),
     ...(Object.keys(extraHeaders).length > 0 ? { extraHeaders } : {}),
   };

Also applies to: 54-77, 85-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/curl-parser.ts` around lines 31 - 45, The parser currently
ignores an x-api-key-only auth because extractAllHeaderAuth returns x-api-key
inside extraHeaders and the function returns null unless cookie or token exist;
update the logic in curl-parser (the block using extractAllHeaderAuth and
variables cookie, token, extraHeaders) to treat an x-api-key header as an apiKey
result (map extraHeaders['x-api-key'] into apiKey when present) so the returned
object includes apiKey even if cookie and token are absent, and add a regression
test that posts a curl containing only an x-api-key header to validate
AuthConfig/httpRequest accept apiKey-only auth.
package.json-4-4 (1)

4-4: ⚠️ Potential issue | 🟠 Major

Don't run Lisa from postinstall in the published npm package.

For npm, postinstall runs automatically during consumer installs, but @codyswann/lisa is a devDependency, which npm omits from transitive dependency installs. This guarantees the script fails, and the 2>/dev/null || true silencing masks the error rather than fixing it. (Bun doesn't run postinstall scripts by default for security, so this is less of a risk there.)

Move this to a contributor-only script or CI hook instead of the install lifecycle.

Suggested change
   "scripts": {
-    "postinstall": "node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check . 2>/dev/null || true",
+    "lisa:sync": "node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check .",
     "test": "vitest run",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 4, The postinstall lifecycle script ("postinstall":
"node node_modules/@codyswann/lisa/dist/index.js --yes --skip-git-check .
2>/dev/null || true") must be removed from package.json so consumers won't run a
devDependency during install; instead add a contributor-only script (e.g.,
"contrib:setup" or "setup") or move the invocation into CI/commit hooks so only
maintainers run `@codyswann/lisa`; update package.json by deleting the
"postinstall" entry and creating a clearly named dev script that documents how
contributors should run the Lisa command locally or via CI.
src/parser/endpoint-grouper.ts-140-195 (1)

140-195: ⚠️ Potential issue | 🟠 Major

This grouping path is now quadratic over large HARs.

The new Map([...groups, mapEntry]) on Line 194 copies the full groups map for every entry, and the same pattern is used for several arrays in the accumulator. Large HAR imports can contain thousands of requests, so this adds avoidable CPU and GC pressure. Keep the accumulator mutable while grouping and materialize the final Endpoint[] once at the end.

Also applies to: 228-233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/parser/endpoint-grouper.ts` around lines 140 - 195, processEntry is
creating a new Map and copying arrays for every HarEntry (e.g., new
Map([...groups, mapEntry]) and spreads like [...group.originalUrls,
entry.request.url]) which makes grouping quadratic; instead mutate the groups
Map and existing GroupData in-place: lookup or create a GroupData object, push
into its originalUrls, requestBodies/responseBodies and responseStatuses arrays,
merge query params/headers into the existing maps/objects (use
collectQueryParams/collectHeaders to update instead of returning new ones), and
then set groups.set(groupKey, group) without copying the whole Map; apply the
same in the other accumulator codepaths that use array spreads or new Map
constructions (the pattern noted around the other spread at 228-233) and only
materialize the final Endpoint[] once after processing all entries.
.github/workflows/claude-nightly-jira-triage.yml-28-28 (1)

28-28: ⚠️ Potential issue | 🟠 Major

Pin the reusable workflow to an immutable ref.

Using @main means this repository will execute whatever lands on that branch later, which weakens both supply-chain guarantees and reproducibility. Pin this to a commit SHA (or another immutable ref) instead.

🔒 Suggested change
-    uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@main
+    uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@<pinned-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/claude-nightly-jira-triage.yml at line 28, Replace the
floating branch ref on the reusable workflow invocation so it uses an immutable
ref: in the line containing the uses declaration (the string
"CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-jira-triage.yml@main"),
change the `@main` suffix to a commit SHA or a pinned tag/release (e.g.,
@<commit-sha> or `@vX.Y.Z`) so the workflow calls a stable, immutable revision.
src/parser/auth-detector.ts-218-230 (1)

218-230: ⚠️ Potential issue | 🟠 Major

Same O(n²) issue in deduplicateByValue.

This function also spreads the map on each iteration. Apply the same fix using map.set() for O(n) performance.

Suggested fix
 function deduplicateByValue(
   candidates: Map<string, AuthCandidate>
 ): Map<string, AuthCandidate> {
-  return [...candidates.values()].reduce<Map<string, AuthCandidate>>(
-    (byValue, candidate) => {
-      const vKey = candidate.info.value;
-      const existing = byValue.get(vKey);
-      const shouldReplace =
-        !existing ||
-        candidate.info.confidence > existing.info.confidence ||
-        (candidate.info.confidence === existing.info.confidence &&
-          candidate.count > existing.count);
-      return shouldReplace ? new Map([...byValue, [vKey, candidate]]) : byValue;
-    },
-    new Map()
-  );
+  const byValue = new Map<string, AuthCandidate>();
+  for (const candidate of candidates.values()) {
+    const vKey = candidate.info.value;
+    const existing = byValue.get(vKey);
+    const shouldReplace =
+      !existing ||
+      candidate.info.confidence > existing.info.confidence ||
+      (candidate.info.confidence === existing.info.confidence &&
+        candidate.count > existing.count);
+    if (shouldReplace) {
+      byValue.set(vKey, candidate);
+    }
+  }
+  return byValue;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/parser/auth-detector.ts` around lines 218 - 230, The reducer in
deduplicateByValue currently creates a new Map on each iteration (new
Map([...byValue, [vKey, candidate]])), causing O(n²) behavior; change it to
mutate the accumulator by calling byValue.set(vKey, candidate) when
shouldReplace is true and then return byValue (leave byValue untouched when not
replacing) so the reduce runs in O(n). Reference symbols: deduplicateByValue,
candidates, byValue, vKey, existing, candidate.info.confidence, candidate.count,
and use map.set().
src/parser/auth-detector.ts-146-156 (1)

146-156: ⚠️ Potential issue | 🟠 Major

Performance issue: Creating a new Map on each iteration is O(n²).

new Map([...map, entry]) spreads the entire map into a new one on every iteration. For large HAR files with many auth candidates, this becomes quadratic. Using map.set() with mutation inside the reduce would be more efficient.

Suggested fix
     return allInfos.reduce((map, info) => {
       const key = authKey(info);
       const existing = map.get(key);
-      const entry: [string, AuthCandidate] = [
-        key,
-        existing
-          ? { info: existing.info, count: existing.count + 1 }
-          : { info, count: 1 },
-      ];
-      return new Map([...map, entry]);
+      if (existing) {
+        existing.count += 1;
+      } else {
+        map.set(key, { info, count: 1 });
+      }
+      return map;
     }, seen);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/parser/auth-detector.ts` around lines 146 - 156, The reduce currently
creates a new Map each iteration (new Map([...map, entry])) causing O(n²)
behavior; change the reducer to mutate and reuse the existing Map by calling
map.set(key, entry) and returning map (or replace the reduce with an imperative
for/of loop over allInfos). Keep references to authKey, AuthCandidate,
allInfos.reduce and the seen initial Map, ensure the code updates the existing
Map entry for key (using existing to increment count) and returns the same Map
instance instead of allocating a new one each time.
src/recorder/browser-session.ts-33-67 (1)

33-67: ⚠️ Potential issue | 🟠 Major

Let the caller decide when to exit.

This helper resolves startBrowserSession() and then terminates the process, so the caller never resumes after await startBrowserSession(). That makes the post-recording output in recordCommand unreachable, and the direct context.close path also skips userDataDir cleanup. Keep shutdown here limited to cleanup/resolve and let the command exit naturally.

Also applies to: 133-139

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recorder/browser-session.ts` around lines 33 - 67, Remove the hard
process termination from cleanup; in cleanupSession (and the duplicate shutdown
block around the same logic later), stop calling process.exit(0) so the function
only performs cleanup and resolves the HAR path, allowing startBrowserSession
callers to continue. Ensure resolve(harPath) remains invoked after attempting
context.close() and after removing userDataDir (fs.rmSync), but do not invoke
process.exit; leave process termination to the caller/command.
src/recorder/browser-session.ts-87-93 (1)

87-93: ⚠️ Potential issue | 🟠 Major

Clean up the launched context if setup fails.

Any exception in this block rejects before the interactive cleanup handlers are registered, which leaves the browser context and temp profile directory behind.

Suggested guard around page setup
   const context = await launchContextWithHar(userDataDir, harPath, spinner);
-  const page = context.pages()[0] ?? (await context.newPage());
-
-  spinner.succeed("Browser launched");
-  attachNetworkCapture(page, { includeAssets: options.includeAssets ?? false });
-  console.log(chalk.blue(`Navigating to ${options.url}...`));
-  await page.goto(options.url);
+  try {
+    const page = context.pages()[0] ?? (await context.newPage());
+
+    spinner.succeed("Browser launched");
+    attachNetworkCapture(page, { includeAssets: options.includeAssets ?? false });
+    console.log(chalk.blue(`Navigating to ${options.url}...`));
+    await page.goto(options.url);
+  } catch (error) {
+    await context.close().catch(() => {});
+    fs.rmSync(userDataDir, { recursive: true, force: true });
+    throw error;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recorder/browser-session.ts` around lines 87 - 93, The launch and page
setup (launchContextWithHar -> context, page, attachNetworkCapture, page.goto)
must be wrapped in a try/catch so that if any error occurs before interactive
cleanup handlers are registered you explicitly close the Playwright context and
remove the temporary userDataDir; implement a catch that checks for an
initialized context and calls await context.close(), deletes the userDataDir
(use the existing profile cleanup helper or fs.rm with recursive:true),
optionally call spinner.fail("Browser launch failed"), then rethrow the error so
the caller still sees the failure.
.github/workflows/claude.yml-25-25 (1)

25-25: ⚠️ Potential issue | 🟠 Major

Pin the reusable workflow to an immutable commit SHA.

Using @main here means upstream changes can silently change a write-scoped workflow in this repository. Pinning the exact commit keeps runs reproducible and narrows the supply-chain risk.

Suggested change
-    uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@main
+    uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@<pinned-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/claude.yml at line 25, Replace the floating ref in the
workflow "uses: CodySwannGT/lisa/.github/workflows/reusable-claude.yml@main"
with an immutable commit SHA from the upstream repo (e.g., "uses:
CodySwannGT/lisa/.github/workflows/reusable-claude.yml@<commit-sha>"); obtain
the correct SHA by fetching the upstream repository commit you want to pin, then
update that uses: line to reference the SHA instead of `@main` and commit the
change.
src/recorder/auth-capture.ts-77-86 (1)

77-86: ⚠️ Potential issue | 🟠 Major

Header-only auth currently resolves to null.

buildCapturedAuth() bails out when count === 0, but Lines 204-205 only increment count after a cookie or bearer token is seen. APIs authenticated solely via X-API-Key or other captured extraHeaders will be discarded.

Suggested change
 function buildCapturedAuth(state: CaptureState): AuthConfig | null {
-  const { cookie, token, extraHeaders, count } = state.value;
-  if (count === 0) return null;
+  const { cookie, token, extraHeaders } = state.value;
+  const hasAuth =
+    Boolean(cookie) ||
+    Boolean(token) ||
+    Object.keys(extraHeaders).length > 0;
+  if (!hasAuth) return null;
   return {
     ...(cookie ? { cookie } : {}),
     ...(token ? { token } : {}),
@@
-  if (state.value.cookie || state.value.token) {
+  if (
+    state.value.cookie ||
+    state.value.token ||
+    Object.keys(state.value.extraHeaders).length > 0
+  ) {
     state.value = { ...state.value, count: state.value.count + 1 };
     const shortPath = new URL(request.url()).pathname.slice(0, 60);

Also applies to: 199-205

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recorder/auth-capture.ts` around lines 77 - 86, buildCapturedAuth
currently returns null when count === 0 which drops header-only auth because
count is only incremented for cookie/token; change the bailout to check actual
captured auth fields instead of count — i.e., return null only if cookie, token
and extraHeaders are all empty/absent (use Object.keys(extraHeaders).length to
detect headers). Update buildCapturedAuth (and the analogous logic referenced
around the other CaptureState handling) to rely on presence of
cookie/token/extraHeaders rather than the numeric count so X-API-Key and other
header-only auth are preserved.
src/recorder/auth-capture.ts-323-335 (1)

323-335: ⚠️ Potential issue | 🟠 Major

Always tear down the browser profile in a finally.

Any exception after mkdtempSync()—including extractRootDomain(url) at Line 330 or a rejected capture path—skips closeBrowser() and leaks the persistent profile directory.

Suggested change
 export async function captureAuth(url: string): Promise<AuthConfig | null> {
   const userDataDir = fs.mkdtempSync(
     path.join(os.tmpdir(), "api-creator-auth-")
   );
   const spinner = ora("Launching browser...").start();
-  const context = await launchContext(userDataDir, spinner);
-  const page = context.pages()[0] ?? (await context.newPage());
-  const rootDomain = extractRootDomain(url);
-  const auth = await promptAndCapture(url, page, context, rootDomain, spinner);
-
-  process.stdin.pause();
-  console.log("");
-  await closeBrowser(context, userDataDir);
-
-  return auth;
+  let context: BrowserContext | null = null;
+  try {
+    context = await launchContext(userDataDir, spinner);
+    const page = context.pages()[0] ?? (await context.newPage());
+    const rootDomain = extractRootDomain(url);
+    return await promptAndCapture(url, page, context, rootDomain, spinner);
+  } finally {
+    process.stdin.pause();
+    console.log("");
+    if (context) {
+      await closeBrowser(context, userDataDir);
+    } else {
+      fs.rmSync(userDataDir, { recursive: true, force: true });
+    }
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recorder/auth-capture.ts` around lines 323 - 335, captureAuth currently
creates a temp profile via fs.mkdtempSync and then may throw before closeBrowser
is called, leaking the profile; refactor captureAuth so that after creating
userDataDir you wrap the remaining logic (launchContext, promptAndCapture,
extractRootDomain, process.stdin.pause, console.log, and returning auth) in a
try block and always invoke closeBrowser(context, userDataDir) in a finally
block (ensuring context is declared in an outer scope so finally can reference
it), preserving error propagation and existing behavior of process.stdin.pause
and console output.
.github/workflows/claude-nightly-test-improvement.yml-27-32 (1)

27-32: ⚠️ Potential issue | 🟠 Major

Pin the nightly reusable workflow to an immutable commit SHA.

This job runs on a schedule with elevated permissions (contents: write, issues: write, pull-requests: write, id-token: write) and forwards the CLAUDE_CODE_OAUTH_TOKEN secret. Using a mutable branch reference like @main creates a supply chain risk where the workflow definition could be changed between runs. Pin to a full commit SHA instead.

Suggested change
-    uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@main
+    uses: CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@<commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/claude-nightly-test-improvement.yml around lines 27 - 32,
The reusable workflow reference in the improve-tests job uses a mutable branch
(`@main`); replace the branch ref in the uses field
(CodySwannGT/lisa/.github/workflows/reusable-claude-nightly-test-improvement.yml@main)
with the immutable full commit SHA of the target repository to pin the workflow;
update the uses value to the exact commit SHA string (e.g.,
...@<full-commit-sha>) so the improve-tests job always runs the fixed workflow
definition.
src/generator/auth-emitter.ts-11-13 (1)

11-13: ⚠️ Potential issue | 🟠 Major

Handle unsupported AuthInfo.type values explicitly.

The parser recognizes query-param and custom-header (src/types/auth.ts), but resolveAuthType() casts the first auth mechanism directly to the local AuthType without validation. This allows unsupported types to be emitted silently, when the generated auth module only handles cookie, bearer, and api-key.

Safer mapping
 function resolveAuthType(authInfos: AuthInfo[]): AuthType {
-  const primary = authInfos.length > 0 ? authInfos[0] : null;
-  return (primary?.type ?? "none") as AuthType;
+  const primaryType = authInfos[0]?.type;
+  switch (primaryType) {
+    case "cookie":
+    case "bearer":
+    case "api-key":
+      return primaryType;
+    default:
+      return "none";
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/auth-emitter.ts` around lines 11 - 13, resolveAuthType
currently casts the first AuthInfo.type directly to AuthType which allows
unsupported values to slip through; update resolveAuthType(AuthInfo[]) to
perform an explicit mapping/validation: inspect primary?.type and use a switch
or mapping that returns 'cookie', 'bearer', or 'api-key' only (map 'query-param'
and 'custom-header' to 'api-key'), and throw a clear error (or at minimum log
and return 'none') for any unrecognized types so unsupported AuthInfo.type
values are surfaced instead of being silently emitted.
.github/workflows/ci.yml-13-23 (1)

13-23: ⚠️ Potential issue | 🟠 Major

Pin the reusable workflow to a commit SHA instead of following main.

quality.yml@main allows a moving branch to execute workflow code with all forwarded security tokens. A malicious or compromised maintainer could inject arbitrary code without your knowledge. Pin to a full commit SHA for an immutable, reproducible CI environment.

Note: This file is auto-generated by Lisa. Update the Lisa configuration that generates this file to use the pinned SHA instead of @main.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 13 - 23, The workflow is using a
mutable ref "uses: CodySwannGT/lisa/.github/workflows/quality.yml@main" which
risks executing arbitrary changes; change that ref to a specific full commit SHA
(replace "@main" with the commit SHA of the pinned quality.yml) so the reusable
workflow is immutable, and update the Lisa config that generates this file to
emit the same pinned SHA going forward (locate the "uses:
CodySwannGT/lisa/.github/workflows/quality.yml@main" line in the ci.yml and the
Lisa generation settings that supply that ref).
src/generator/auth-emitter.ts-59-60 (1)

59-60: ⚠️ Potential issue | 🟠 Major

Use { encoding: 'utf-8', mode: 0o600 } to restrict .auth file permissions to owner only.

The file contains sensitive authentication data (cookies, bearer tokens, API keys), but writeFileSync(..., 'utf-8') with string encoding leaves permissions to the process umask, typically creating a world-readable 0644 file. Set explicit owner-only permissions by passing an options object instead of a string encoding parameter:

-    "  writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2), 'utf-8');",
+    "  writeFileSync(AUTH_FILE, JSON.stringify(config, null, 2), { encoding: 'utf-8', mode: 0o600 });",

(Note: Adding chmodSync is redundant when mode is set in writeFileSync options.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/auth-emitter.ts` around lines 59 - 60, The saveAuth function
writes sensitive credentials to disk using writeFileSync(AUTH_FILE,
JSON.stringify(config, null, 2), 'utf-8'); change this to pass an options object
so the file is created with owner-only permissions: replace the third argument
with { encoding: 'utf-8', mode: 0o600 } when calling writeFileSync in saveAuth
(refer to AUTH_FILE and saveAuth), and remove any redundant chmodSync calls if
present.
src/recorder/auth-capture.ts-247-248 (1)

247-248: ⚠️ Potential issue | 🟠 Major

Fix the hostname filter to prevent credential leakage to unrelated domains.

The endsWith(rootDomain) check at line 248 is vulnerable to broad domain matching. malicious-example.com would pass for example.com, capturing credentials from attacker-controlled domains. Additionally, extractRootDomain at lines 288-291 uses a naive 2-part split that fails for multi-part TLDs—for example, foo.example.co.uk becomes co.uk, which would match any .co.uk domain.

Implement a proper domain filter by either:

  • Adding a dot-boundary check: reqHost === rootDomain || reqHost.endsWith("." + rootDomain), combined with a public-suffix-aware registrable domain extraction
  • Using exact origin-host matching if sibling subdomains are not required
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/recorder/auth-capture.ts` around lines 247 - 248, The hostname filter in
auth-capture.ts is too permissive: change the check that currently uses
safeHostname(request.url()) and endsWith(rootDomain) to a strict boundary
match—use reqHost === rootDomain || reqHost.endsWith("." + rootDomain) (or use
exact origin-host matching if sibling subdomains are not needed) and replace the
naive extractRootDomain logic with a public-suffix-aware registrable domain
extraction (e.g., use a PSL library/function in place of the 2-part split in
extractRootDomain) so rootDomain represents the actual registrable domain and
prevents matches like malicious-example.com or incorrect co.uk handling. Ensure
the code references the same symbols (safeHostname, request.url(),
extractRootDomain, rootDomain) when applying the fix.
src/generator/auth-emitter.ts-46-47 (1)

46-47: ⚠️ Potential issue | 🟠 Major

Escape originalUrl and name values when generating code.

String interpolation without escaping creates a code injection vulnerability. If either value contains a single quote, backslash, or newline, the generated TypeScript/JavaScript becomes invalid or executes injected code. Use JSON.stringify() to properly escape these values:

Suggested change
-    `export const ORIGINAL_URL = '${originalUrl}';`,
-    `export const CLI_NAME = '${name}';`,
+    `export const ORIGINAL_URL = ${JSON.stringify(originalUrl)};`,
+    `export const CLI_NAME = ${JSON.stringify(name)};`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/auth-emitter.ts` around lines 46 - 47, The generated code
directly interpolates originalUrl and name into template strings for constants
ORIGINAL_URL and CLI_NAME, which can break or inject code if those values
contain quotes/backslashes/newlines; change the generation to use
JSON.stringify(originalUrl) and JSON.stringify(name) (or an equivalent escaping
function) when building the two lines so the emitted `export const ORIGINAL_URL
= ...;` and `export const CLI_NAME = ...;` contain properly escaped string
literals.
src/runtime/project-runner.ts-140-145 (1)

140-145: ⚠️ Potential issue | 🟠 Major

Piped stdin never reaches the cURL auth path.

Lines 140-144 gate the non-TTY fallback on options.file !== undefined, so pbpaste | api-creator <project> auth setup falls through to browser capture unless --stdin is also passed. That breaks the implicit stdin flow this refactor is trying to add.

💡 Suggested fix
-        (!process.stdin.isTTY && options.file !== undefined)
+        !process.stdin.isTTY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/project-runner.ts` around lines 140 - 145, The current
conditional that decides whether to call handleCurlAuth prevents piped stdin
from triggering cURL auth because it requires options.file !== undefined; remove
that extra check so that non-TTY stdin alone will trigger the path. Update the
if in project-runner.ts (the branch that currently checks options.file,
options.stdin, and process.stdin.isTTY) to call handleCurlAuth when options.file
is truthy OR options.stdin is truthy OR process.stdin is not a TTY (i.e., drop
the options.file !== undefined gate), keeping the call to await
handleCurlAuth(projectName, options) unchanged.
src/commands/generate.ts-63-70 (1)

63-70: ⚠️ Potential issue | 🟠 Major

--input no longer honors the documented directory form.

Lines 63-70 now pass any explicit --input straight through to codegen, but the help text still says the flag accepts a recordings directory. api-creator generate --input ./recordings will now hand a directory to generateClient instead of selecting the newest .har.

💡 Suggested fix
-      const inputPath: string =
-        options.input ?? (await findMostRecentHar(recordingsDir));
+      let inputPath = options.input;
+      if (inputPath) {
+        const inputStats = await stat(resolve(inputPath)).catch(() => null);
+        if (inputStats?.isDirectory()) {
+          inputPath = await findMostRecentHar(resolve(inputPath));
+        }
+      } else {
+        inputPath = await findMostRecentHar(recordingsDir);
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/generate.ts` around lines 63 - 70, The CLI no longer treats
--input as a recordings directory; update the action handler so that when
options.input is provided and points to a directory it selects the newest .har
inside it (use fs.stat/isDirectory or similar), otherwise treat options.input as
a file path; keep the existing fallback of calling
findMostRecentHar(resolve("./recordings")) when --input is omitted. Locate the
action callback where options.input is read (symbols: options.input,
recordingsDir, findMostRecentHar) and ensure the resolved inputPath passed to
generateClient is the newest .har when a directory is given.
.github/workflows/auto-update-pr-branches.yml-15-30 (1)

15-30: ⚠️ Potential issue | 🟠 Major

Pin the reusable workflow to an immutable commit SHA.

Line 22 uses @main despite the job having contents: write, pull-requests: write, and id-token: write permissions while passing CLAUDE_CODE_OAUTH_TOKEN. This creates a supply-chain risk—any upstream change would execute immediately with elevated privileges. Lock this to a full commit SHA and update intentionally.

Suggested fix
-    uses: CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@main
+    uses: CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@<full-commit-sha>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/auto-update-pr-branches.yml around lines 15 - 30, The
workflow currently references the reusable workflow via "uses:
CodySwannGT/lisa/.github/workflows/reusable-auto-update-pr-branches.yml@main"
which creates a supply-chain risk given the elevated permissions and the
CLAUDE_CODE_OAUTH_TOKEN secret; update the "uses" value in the autoupdate job to
point to an immutable commit SHA of that reusable workflow (replace the "@main"
suffix with the full commit SHA) so the autoupdate job always executes a pinned,
auditable version; ensure you commit the new SHA string where the "uses" key is
defined and verify the referenced commit contains the expected workflow.
src/commands/generate.ts-50-55 (1)

50-55: ⚠️ Potential issue | 🟠 Major

Use path.basename() instead of splitting on "/" for cross-platform compatibility.

On Windows, a path like C:\recordings\petstore.har has no forward slashes. The current harPath.split("/") leaves the full path in basename, causing the regex to match the drive letter and derive "c" as the project name. The path module is already imported—use path.basename() to extract the filename reliably on all platforms.

-import { join, resolve } from "node:path";
+import { basename as pathBasename, join, resolve } from "node:path";
…
 function deriveNameFromHarPath(harPath: string): string {
-  const segments = harPath.split("/");
-  const basename = segments[segments.length - 1] ?? "api-client";
-  const noExt = basename.replace(/\.har$/i, "");
+  const fileName = pathBasename(harPath);
+  const noExt = fileName.replace(/\.har$/i, "");
   const domainMatch = /^([a-zA-Z][a-zA-Z0-9-]*)/.exec(noExt);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/generate.ts` around lines 50 - 55, The deriveNameFromHarPath
function currently splits harPath by "/" which breaks on Windows; replace the
manual split and basename extraction with path.basename(harPath) to get the
filename cross-platform, then strip the .har extension (or use
path.parse(...).name) before running the domain regex; update references inside
deriveNameFromHarPath accordingly so the function uses path.basename (or
path.parse) instead of harPath.split("/").
src/commands/test.ts-77-93 (1)

77-93: ⚠️ Potential issue | 🟠 Major

Count the health-check result in the aggregate.

runTests() says it covers the health check and endpoint calls, but Lines 77-80 discard the probe result. A failing health check can still end with All N endpoint(s) responded successfully.

Possible fix
 async function runTests(
   baseUrl: string,
   endpoints: ReturnType<typeof parseEndpoints>,
   toTest: ReturnType<typeof parseEndpoints>,
   authHeaders: Record<string, string>
 ): Promise<{ passed: number; failed: number }> {
   const healthEndpoint = findHealthCheckEndpoint(endpoints);
+  let initial = { passed: 0, failed: 0 };
   if (healthEndpoint) {
-    await testEndpoint(baseUrl, healthEndpoint, authHeaders, "health check");
+    const ok = await testEndpoint(
+      baseUrl,
+      healthEndpoint,
+      authHeaders,
+      "health check"
+    );
+    initial = ok ? { passed: 1, failed: 0 } : { passed: 0, failed: 1 };
   }
 
   console.log(chalk.blue(`  Testing ${toTest.length} endpoint(s)...\n`));
 
   return toTest.reduce(
@@
-    Promise.resolve({ passed: 0, failed: 0 })
+    Promise.resolve(initial)
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/test.ts` around lines 77 - 93, The health-check result is
currently discarded after calling testEndpoint(…, "health check"); ensure its
boolean result is included in the final aggregate by capturing the health check
outcome and adding it to the reduce accumulator; specifically, call const
healthOk = healthEndpoint ? await testEndpoint(baseUrl, healthEndpoint,
authHeaders, "health check") : false and then either (a) include healthOk in the
initial Promise.resolve({ passed: healthOk ? 1 : 0, failed: healthOk ? 0 : 0 })
for the toTest.reduce or (b) add healthOk to the reduce result after it
completes (e.g., increment passed/failed based on healthOk) so the returned
counts reflect the health probe as well as the other endpoints.
src/generator/cli-entrypoint-emitter.ts-43-50 (1)

43-50: ⚠️ Potential issue | 🟠 Major

Accept header-only auth configs.

extraHeaders is populated on Lines 44-45, but Line 50 still returns null unless a cookie, token, or apiKey was also found. That makes auth setup fail for valid header-only schemes like x-api-key or x-auth-token.

Possible fix
-    "  if (!auth.cookie && !auth.token && !auth.apiKey) return null;",
+    "  if (",
+    "    !auth.cookie &&",
+    "    !auth.token &&",
+    "    !auth.apiKey &&",
+    "    Object.keys(extraHeaders).length === 0",
+    "  ) return null;",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/cli-entrypoint-emitter.ts` around lines 43 - 50, The auth
builder currently rejects header-only schemes because it returns null unless
auth.cookie, auth.token, or auth.apiKey exist; update the final check to also
accept cases where extraHeaders was populated: modify the return condition (the
place that currently does "if (!auth.cookie && !auth.token && !auth.apiKey)
return null;") to instead return null only when there are no cookies, tokens,
apiKeys, AND extraHeaders is empty (e.g. check Object.keys(extraHeaders).length
=== 0). Ensure you still assign auth.extraHeaders when extraHeaders has keys and
keep the existing header-filtering logic that builds extraHeaders.
src/importer/paste-parser-http.ts-122-145 (1)

122-145: ⚠️ Potential issue | 🟠 Major

Preserve the raw body when building the HAR entry.

Lines 122-127 call .trim() on the request body before it is attached to postData. That strips leading/trailing whitespace and blank lines from plain-text, XML, and form payloads, so the imported HAR no longer matches the captured request.

Possible fix
-  const body =
-    emptyIdx !== -1
-      ? lines
-          .slice(emptyIdx + 2)
-          .join("\n")
-          .trim() || undefined
-      : undefined;
+  const rawBody =
+    emptyIdx !== -1 ? lines.slice(emptyIdx + 2).join("\n") : undefined;
+  const body = rawBody === "" ? undefined : rawBody;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/importer/paste-parser-http.ts` around lines 122 - 145, The code currently
trims the request body (the variable body built in paste-parser-http.ts) which
removes leading/trailing whitespace; remove the .trim() so the raw body from
lines.slice(emptyIdx + 2).join("\n") is preserved, keep body as undefined only
if the joined string is exactly "" (e.g., use const bodyRaw =
lines.slice(...).join("\n"); const body = bodyRaw === "" ? undefined :
bodyRaw;), then pass that to attachPostData(makeEntry) and compute bodySize from
body.length when body is defined; update any references to bodySize on the
HarRequest object accordingly (see HarRequest construction, attachPostData,
makeEntry).
src/generator/client-method-emitter.ts-100-105 (1)

100-105: ⚠️ Potential issue | 🟠 Major

Quote query parameter names using bracket notation to support hyphens, brackets, and reserved words.

Lines 103 and 142 inject qp.name directly without quoting. Query parameter names like page-size, filter[status], or default will generate invalid TypeScript/JavaScript:

  • Line 103: { page-size?: string } is invalid syntax (needs { "page-size"?: string })
  • Line 142: options.page-size fails at runtime (needs options["page-size"])

Apply the fix across both locations:

Suggested changes
  const queryParam =
    endpoint.queryParams.length > 0
      ? [
-         `options?: { ${endpoint.queryParams.map(qp => `${qp.name}${qp.required ? "" : "?"}: string`).join("; ")} }`,
+         `options?: { ${endpoint.queryParams.map(qp => `${JSON.stringify(qp.name)}${qp.required ? "" : "?"}: string`).join("; ")} }`,
        ]
      : [];
-   qp =>
-     `      if (options.${qp.name} !== undefined) params.set('${qp.name}', options.${qp.name});`
+   qp =>
+     `      if (options[${JSON.stringify(qp.name)}] !== undefined) params.set(${JSON.stringify(qp.name)}, options[${JSON.stringify(qp.name)}]);`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/client-method-emitter.ts` around lines 100 - 105, The generated
query param type and usages currently inject qp.name unquoted causing invalid
identifiers for names like page-size or filter[status]; update the queryParam
construction in client-method-emitter.ts (the queryParam constant that maps
endpoint.queryParams) to emit quoted property names in the type literal (e.g.
wrap qp.name in quotes when building the `{ ... }` type) and update all runtime
accesses (where code reads options.<qp.name>) to use bracket notation
(options[qp.name]) so hyphens, brackets, and reserved words are handled safely;
ensure you preserve the optional marker (`?`) logic when quoting and apply the
same change where options properties are accessed (the usage around line ~142
referenced in the review).
src/generator/graphql-commands-emitter.ts-127-131 (1)

127-131: ⚠️ Potential issue | 🟠 Major

Use safe option-key mapping for otherParams to match parametrizedVariables pattern.

The setup emits --${qp.name} for otherParams without applying getParamVarName(), whereas parametrizedVariables consistently apply it at both definition (line 117-125) and access (line 179). Since Commander.js converts dashed option names (like --page-size) to camelCase properties (pageSize), the mismatch between raw query parameter names and how options are accessed creates inconsistency. The handler's broad Object.keys(options).forEach(...) copy at lines 170-172 will use camelCased keys, but the original dashed names won't round-trip correctly. Apply getParamVarName() to otherParams at both lines 127-131 and lines 202-205 to match the pattern used for parametrizedVariables.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/graphql-commands-emitter.ts` around lines 127 - 131, The
emitted options for otherParams are using raw qp.name (otherParams.map) which
mismatches Commander.js camelCasing and the existing parametrizedVariables
pattern; update the option key generation and later access to use
getParamVarName(qp.name) so option definitions (the .option(...) calls generated
from otherParams) and their consumption (the code that reads options and merges
into parametrizedVariables) both use getParamVarName, matching how
parametrizedVariables are created and accessed and ensuring dashed names like
"page-size" become the same camelCased property (use getParamVarName in the
otherParams.map location and in the later options-to-param merge logic).
src/generator/client-method-emitter.ts-33-58 (1)

33-58: ⚠️ Potential issue | 🟠 Major

Apply identifier sanitization to derived path-parameter names.

paramName at line 41 is built from raw path segment text without normalization. For paths like /user-groups/:id, the segment user-groups becomes user-groupId after singularization, which is invalid TypeScript. This breaks both the parameter list (line 95) and template reference (line 115).

Pass the segment through cleanSegment from naming.ts before calling singularize, matching the approach used in pathToMethodName.

This issue also applies to query parameters (line 103), which directly interpolate qp.name into the options type without sanitization, and the property access pattern (line 121+) should use bracket notation for non-identifier query param names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/client-method-emitter.ts` around lines 33 - 58, The derived
path param names in extractPathParams build paramName from raw segments without
sanitization causing invalid identifiers; update extractPathParams to call
cleanSegment(seg) before singularize (i.e. paramBase =
singularize(cleanSegment(segment))) when constructing paramName so names are
valid TypeScript, and apply the same sanitization to query parameter names
(qp.name) wherever they are used to form types (matching pathToMethodName
behavior); finally, update template usages that access potentially
non-identifier query params to use bracket notation (e.g., options[cleanedName])
or otherwise safe property access for any sanitized-but-not-identifier names.
src/generator/diff-merger.ts-62-65 (1)

62-65: ⚠️ Potential issue | 🟠 Major

Keep preserved custom blocks scoped to their original file.

detectExistingClient() flattens sections from client.ts and types.ts into one array, and applyMerge() replays that same array into both outputs. Any marker that exists in only one file gets injected into the other on merge, and shared marker names can overwrite each other across files.

♻️ Suggested direction
 export interface ExistingClient {
   methodNames: string[];
   typeNames: string[];
-  customSections: CustomSection[];
+  clientCustomSections: CustomSection[];
+  typesCustomSections: CustomSection[];
   clientSource: string;
   typesSource: string;
 }
@@
-  const customSections = [
-    ...parseCustomSections(clientSource),
-    ...parseCustomSections(typesSource),
-  ];
+  const clientCustomSections = parseCustomSections(clientSource);
+  const typesCustomSections = parseCustomSections(typesSource);
 
-  return { methodNames, typeNames, customSections, clientSource, typesSource };
+  return {
+    methodNames,
+    typeNames,
+    clientCustomSections,
+    typesCustomSections,
+    clientSource,
+    typesSource,
+  };
@@
-  const sections = existing ? existing.customSections : [];
+  const clientSections = existing?.clientCustomSections ?? [];
+  const typeSections = existing?.typesCustomSections ?? [];
 
-  const clientCode = sections.reduce(
+  const clientCode = clientSections.reduce(
     (code, section) => restoreCustomSection(code, section),
     clientWithDeprecations
   );
-  const typesCode = sections.reduce(
+  const typesCode = typeSections.reduce(
     (code, section) => restoreCustomSection(code, section),
     fullTypesCode
   );

Also applies to: 187-197

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/diff-merger.ts` around lines 62 - 65, detectExistingClient()
currently merges parseCustomSections(clientSource) and
parseCustomSections(typesSource) into a single customSections array and
applyMerge() replays that same array into both outputs, causing markers from one
file to be injected into the other; instead preserve file-scoped sections by
keeping the parsed sections separate and passing the correct list into each
merge. Change the logic so parseCustomSections(clientSource) and
parseCustomSections(typesSource) are stored as two distinct collections (e.g.,
clientCustomSections and typesCustomSections) and ensure applyMerge() (and any
callers that currently use customSections) receives the appropriate collection
for the corresponding output (client vs types), and update any merging/overwrite
logic in detectExistingClient() and the related merge block that replays
sections (the code around applyMerge() usage) so markers do not cross-inject or
override across files.
src/runtime/endpoint-command-builder.ts-160-168 (1)

160-168: ⚠️ Potential issue | 🟠 Major

URL-encode path arguments before replacement.

buildResolvedPath() injects raw CLI values into the URL. A value containing /, ?, #, or spaces will change the route or produce an invalid request.

🔧 Minimal fix
 function buildResolvedPath(
   pathTemplate: string,
   pathParams: string[],
   pathArgValues: string[]
 ): string {
   return pathParams.reduce(
-    (acc, _param, i) => acc.replace(":id", pathArgValues[i]),
+    (acc, _param, i) =>
+      acc.replace(":id", encodeURIComponent(pathArgValues[i])),
     pathTemplate
   );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/endpoint-command-builder.ts` around lines 160 - 168,
buildResolvedPath currently injects raw CLI values into the URL; update it to
URL-encode each path argument before replacement. In the reduce callback for
buildResolvedPath use the path param name (pathParams[i]) to replace the
matching placeholder (e.g., `:${pathParam}`) and substitute
encodeURIComponent(pathArgValues[i] ?? '') instead of the raw value so
characters like /, ?, # and spaces are percent-encoded.
src/generator/commands-emitter.ts-193-203 (1)

193-203: ⚠️ Potential issue | 🟠 Major

Omitting --body currently hard-fails every mutating command.

The generated handler always does JSON.parse('') when neither --body nor --json is passed, so every POST/PUT/PATCH command exits with “Invalid JSON” before sending the request. Match the runtime builder and only parse when a body flag is actually present.

🔧 Minimal fix
   const bodyLines = hasBody
     ? [
-        "        const bodyData = options.body || options.json || '';",
-        "        let parsedBody: any;",
-        "        try {",
-        "          parsedBody = JSON.parse(bodyData);",
-        "        } catch {",
-        "          console.error('Invalid JSON body. Provide valid JSON via --body or --json.');",
-        GEN_PROCESS_EXIT_1,
-        "        }",
+        "        const bodyData = options.body ?? options.json;",
+        "        let parsedBody: unknown;",
+        "        if (bodyData !== undefined) {",
+        "          try {",
+        "            parsedBody = JSON.parse(bodyData);",
+        "          } catch {",
+        "            console.error('Invalid JSON body. Provide valid JSON via --body or --json.');",
+        GEN_PROCESS_EXIT_1,
+        "          }",
+        "        }",
       ]
     : [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/commands-emitter.ts` around lines 193 - 203, The generated
handler currently sets bodyData to options.body || options.json || '' and
unconditionally calls JSON.parse, causing mutating commands to fail when no body
flag is provided; update the body generation in commands-emitter.ts (the
bodyLines block that defines bodyData and parsedBody and uses
GEN_PROCESS_EXIT_1) so that you only attempt JSON.parse when a body flag is
actually present (e.g., if options.body !== undefined || options.json !==
undefined), otherwise leave parsedBody undefined/absent and do not call
JSON.parse or trigger GEN_PROCESS_EXIT_1; ensure bodyData is taken from
options.body ?? options.json and wrap the try/catch parsing logic inside that
presence check.
src/generator/cli-project-emitter.ts-49-64 (1)

49-64: ⚠️ Potential issue | 🟠 Major

Add typescript and @types/node as devDependencies to generated package.json.

The emitted package.json defines scripts.build: "tsc" but only includes commander in dependencies. Generated projects will fail at the build step documented in the README (npm install && npm run build) unless users have TypeScript and Node types installed globally. Both should be included as devDependencies.

Additionally, the README attribution at line 153 incorrectly points to anthropics/api-creator instead of the current repository URL.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/cli-project-emitter.ts` around lines 49 - 64, The emitted
package.json from emitProjectPackageJson builds a pkg object with scripts.build
set to "tsc" but only includes commander in dependencies; update
emitProjectPackageJson to add a devDependencies entry on the pkg object that
includes "typescript" and "@types/node" (so projects can run npm install && npm
run build), and also correct the README attribution URL (replace the incorrect
"anthropics/api-creator" reference with the current repository URL) in the
README file mentioned in the PR; ensure you update the pkg variable and
scripts.build usage accordingly.
src/runtime/endpoint-command-builder.ts-87-101 (1)

87-101: ⚠️ Potential issue | 🟠 Major

Apply the GraphQL-variable pattern to query parameters for CLI-safe flag names and mapping.

qp.name is used verbatim for option registration and reading, which breaks parameter names containing special characters like dashes or brackets (e.g., page-size, filter[status]). Commander normalizes long-option names, and these raw keys are not valid flag syntax.

Reuse the existing pattern from GraphQL variables (lines 64-78, 228-235): transform query param names to kebab-case for the CLI flag using camelToKebab(), then map back to the original key using kebabToCamel() when reading from options. Update both registration (lines 87-101) and lookup (lines 244-253) to follow this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/endpoint-command-builder.ts` around lines 87 - 101, The CLI
currently registers query param flags using qp.name verbatim which breaks names
with special chars; in registerQueryParamOptions replace usages of qp.name for
the flag with camelToKebab(qp.name) (keep the same label/description and
preserve qp.defaultValue), and update the option-reading logic that currently
looks up options by qp.name (the lookup block around lines 244-253) to read the
kebab-cased flag from the parsed options and map it back to the original key
with kebabToCamel() before assigning to the final query param object; use the
same camelToKebab/kebabToCamel helpers already used for GraphQL variables to
ensure CLI-safe flags and correct mapping.
src/generator/commands-emitter.ts-150-154 (1)

150-154: ⚠️ Potential issue | 🟠 Major

Sanitize query parameter names before use in generated code.

Query parameter names like page-size, page[size], or foo.bar won't work as-is. Commander converts option flags with dashes to camelCase properties (e.g., --page-sizeoptions.pageSize), but the generated code at lines 150-154 and 206-212 uses qp.name directly in both the option flag and property access.

Use a kebab-to-camel conversion (already available via kebabToCamel utility and applied in graphql-commands-emitter.ts) and bracket notation for safe property access, matching the GraphQL commands pattern:

Example fix pattern from graphql-commands-emitter.ts
const optionAccessor = kebabToCamel(qp.name);  // Convert name to camelCase
`if (options[${JSON.stringify(optionAccessor)}] !== undefined) queryOpts[${JSON.stringify(qp.name)}] = options[${JSON.stringify(optionAccessor)}];`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/generator/commands-emitter.ts` around lines 150 - 154, The generated CLI
options use raw qp.name for both the flag and property access which breaks for
names like "page-size" or "page[size]"; update the endpoint.queryParams mapping
in commands-emitter.ts to compute an optionAccessor via the existing
kebabToCamel(qp.name) and emit option access using bracket notation (e.g.,
options[optionAccessor]) while keeping the flag as `--${qp.name}`; also apply
the same conversion when building queryOpts (the code block analogous to
graphql-commands-emitter.ts) so you set queryOpts[qp.name] =
options[optionAccessor] only when options[optionAccessor] !== undefined. Ensure
you reference kebabToCamel, qp.name, optionAccessor, options and queryOpts to
find and fix both the option emission (previously lines ~150-154) and the
property-access logic (previously lines ~206-212).

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/publish-to-npm.yml Outdated
- Update claude-ci-auto-fix.yml workflow trigger to match renamed
  '🔍 CI Quality Checks' workflow name
- Remove broken manual OIDC token wiring from publish-to-npm.yml;
  npm CLI v11.5.1+ handles OIDC exchange automatically via
  `npm publish --provenance`

🤖 Generated with Claude Code

Co-Authored-By: Claude
@CodySwannGT

Copy link
Copy Markdown
Owner Author

Both CodeRabbit findings addressed in 616e5d1:

  1. claude-ci-auto-fix.yml: Updated workflow trigger to reference '🔍 CI Quality Checks' (matching the renamed CI workflow).
  2. publish-to-npm.yml: Removed broken manual OIDC token wiring — npm CLI v11.5.1+ handles the exchange automatically via npm publish --provenance (already used on line 142).

Both fixes upstreamed to Lisa templates (typescript/create-only and npm-package/create-only).

@CodySwannGT
CodySwannGT merged commit fe53f3f into main Mar 20, 2026
26 of 27 checks passed
@CodySwannGT
CodySwannGT deleted the chore/lisa-onboarding branch March 20, 2026 13:33
@coderabbitai coderabbitai Bot mentioned this pull request May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant