3 audit passes produced 30 raw findings. After honest re-evaluation:
- 18 real bugs — fixed (15 from the v0.1.0 audit + 1 found during v1.0.0 Phase 1 implementation + 2 found during an independent end-to-end fidelity recheck of the v1.0.0 phases). The recheck additionally closed 2 test-coverage gaps where production code was already correct but had no regression test proving it.
- 4 severity-overstated — fixed but reclassified
- 6 code quality improvements — applied but not bugs
- 4 theoretical/debatable — applied as defensive hardening
- 1 duplicate — removed (#19 = #9)
- File:
src/generators/hooks.ts - Description:
set -euo pipefailcauses bash to exit immediately when the verify command fails, making exit_code capture and the "Verification failed" message unreachable. - Fix: Removed
-e, addedset +e/set -earound the verify command.
- File:
src/generators/index.ts - Description:
writeFileSynccreates files with 0644. The hook script couldn't be executed directly. - Fix: Added
chmodSync(path, 0o755)after writing.
- File:
src/templates/registry.ts - Description: Template validation only checked
nameanddescription. Missingtagscaused TypeError crash insearchTemplates. - Fix: Strengthened validation to check all required fields with type checks. Defaults for optional fields.
- File:
src/generators/index.ts - Description: Every template YAML defined per-agent instructions. The generator silently discarded them.
- Fix: All generators now accept and include template instructions.
- File:
src/generators/hooks.ts - Description: Four code paths produce
verifyCommandstrings with embedded single quotes that break the echo line's quoting. Confirmed with live bash test. - Fix: Escape single quotes using
verifyCommand.replace(/'/g, "'\\''").
- File:
src/cli/init.ts - Description: Selecting "Custom command" provided no follow-up prompt. The placeholder
echo '...'exited 0, so verification always passed. - Fix: Added
p.text()follow-up prompt. Threaded viaWizardAnswers.customCommand.
- File:
src/detectors/verification.ts - Description: When no test runner is detected, the fallback
echo '...'exited 0, making the verification gate useless. - Fix: All fallback commands now include
&& exit 1.
- File:
src/cli/index.ts - Description: All commands have async action handlers.
parse()doesn't await the returned promise, so async errors become unhandled rejections. (Originally claimed Critical — reclassified to Medium. Commander does return a promise; the practical impact is confusing error output, not data loss.) - Fix: Changed to
program.parseAsync().catch(...). Also made version dynamic from package.json.
- File:
src/cli/init.ts - Description: If file writes fail, the Clack spinner keeps running, cursor stays hidden, terminal left in broken state.
- Fix: Wrapped
generateAllin try/catch that always stops the spinner.
- File:
src/cli/init.ts,src/cli/score.ts,src/cli/status.ts - Description: Non-existent or file paths crashed silently with exit 0. Confirmed with live test.
- Fix: Added existsSync + statSync validation. Clear error messages, exit 1.
- File:
src/cli/score.ts - Description:
padEnd(40)applied after ANSI color wrapping counted invisible escape codes, misaligning columns. - Fix: Apply padEnd before color wrapping.
- File:
src/types.ts - Description: Public API type didn't match runtime values from the generator.
- Fix: Changed to
number | nullfor all three budget fields.
- File:
src/cli/init.ts - Description: Exit 0 on Ctrl+C means chained commands (
kit init && deploy) proceed as if init succeeded. (Originally Medium — reclassified to Low. Many CLIs exit 0 on cancel. Defensible convention.) - Fix: Changed to
process.exit(130).
- File:
src/utils/git.ts - Description: Permission errors or read-only filesystems would crash
kit initwith raw Node.js error. - Fix: Wrapped in try/catch. Gitignore update is non-critical.
- File:
src/generators/budget.ts - Description:
opts.maxCostUsd || nullconverts 0 to null. In this codebase, 0 always means "unlimited" (from BUDGET_MAP). The|| nullwas correct-by-design. Changed to> 0for clarity, but the behavior is identical. - Fix: Changed to
> 0explicit check. (Functionally a no-op — same result for all real inputs.)
| # | Claimed | Actual | Why |
|---|---|---|---|
| 2 | HIGH: "Shell injection" in echo | LOW | The verify command is user-provided content that's intentionally executed. The real issue was broken quoting in the echo line (cosmetic), not a security vulnerability. Severity language was misleading. Subsumed by finding #11 which properly describes the quoting bug. |
| 14 | HIGH: Hook only at .claude/hooks/ | MEDIUM | Codex/Gemini/Cursor don't have standardized hook paths. The hook was referenced in the generated agent config. Users would find it. Design gap, not a showstopper. |
| 23 | HIGH: Command injection via execSync | LOW | The cmd parameter was only ever called with hardcoded strings ("claude", "codex", "gemini"). Never user-controlled. Zero real attack surface. Good defensive fix, bad severity claim. |
| 26 | MEDIUM: Missing .eslintrc extensionless | LOW | The extensionless .eslintrc is deprecated since ESLint 9 (which this project uses). Detecting a dead format is over-engineering backward compat. |
All four fixes are applied and correct — the issues were real but not as severe as claimed.
These are all applied and improve the codebase, but calling them "bugs" was inaccurate.
| # | Finding | Reality |
|---|---|---|
| 8 | Redundant detection calls | Performance nit. Never produced wrong results. |
| 17 | Double resolveVerifyCommand call | Efficiency nit. Same pure function, same args. |
| 18 | Redundant join(resolve()) | Style inconsistency. Produced correct results. |
| 28 | Identity ternary dead code | Code cleanup. x === 'a' ? 'a' : x always equals x. |
| 29 | git.ts lines recomputed in filter | Micro-optimization. Array has 2 entries. Unmeasurable. |
| 30 | CI Node 18 EOL | Maintenance chore. CI still worked. |
| # | Finding | Assessment |
|---|---|---|
| 5 | which fails on Windows |
Real gap but theoretical for macOS/Linux-targeting MVP. |
| 6 | Files overwritten on re-run | Design choice (ACCEPTED). Most scaffolders overwrite. |
| 15 | Falsy-zero hides $0 cost cap | Who sets a cost cap of $0? Theoretical edge case. |
| 16 | Template path resolution fragile | Templates loaded correctly in every test. Fix added validation but original wasn't broken. |
| # | Finding | Duplicate Of |
|---|---|---|
| 19 | ` |
Real bugs found and fixed: 15
Improvements applied: 14 (overstated + quality + theoretical)
Duplicates: 1
Total changes: 30 (all applied, all correct)
- File:
src/generators/hooks.ts - Severity: Critical
- Description:
${verifyCommand}was spliced directly into the generated script as literal bash source, unwrapped.set +e/set -eonly controls auto-exit-on-nonzero-return; it does not change the behavior of theexitbuiltin, which always terminates the current shell when invoked. Discovered via adversarial unit testing usingexit 0/exit 1as a stand-in verify command — the script terminated immediately at that line, never reachingexit_code=$?, duration calculation, or LTF trace emission. This is a real risk for Kit's "custom command" verification mode, where a user-authored script could plausibly end with an explicitexitcall (a common shell idiom). - Fix: Wrapped the verify command in a subshell:
( ${verifyCommand} ). Anexitinside a subshell only terminates the subshell; the parent script'sexit_code=$?correctly captures its exit status. No behavior change for the overwhelming majority of real verify commands (test runners, build tools) since subshell execution is observationally identical for stdout/stderr/exit-code. - Status: FIXED — caught before merge via the adversarial-testing step of Phase 1's own verification loop, not by a user report.
After all 5 v1.0.0 phases were implemented, 5 independent verification agents re-checked each phase's actual code against its planning + review documents. Two genuine production bugs and two test-coverage gaps (production code already correct, but with no regression test proving it) were found and fixed.
- File:
src/generators/hooks.ts - Severity: High
- Description:
ltf-config.ts's generated comment explicitly promisedloop_summaryis "emitted once, only on the first passing verification" — but the actual condition (if [ "$result_status" = "success" ]) fired unconditionally on every successful run. Confirmed empirically: 3 consecutive successfulverify.shinvocations produced 3 separateterminate+loop_summarypairs sharing oneloop_id, not one. A user re-running verification after the loop already succeeded (e.g. a periodic CI re-check) would accumulate multiple summary records, directly contradicting the documented, spec-aligned behavior (SPEC.md: loop_summary is a one-time terminal record per loop). - Fix: Added a
summarizedflag to the tracer's.ltf-state.json, set to1the first time a successful run occurs. Subsequent successful runs still emit averifyevent but skip theterminate/loop_summarypair oncesummarizedis already1. - Status: FIXED. Regression test added:
hooks.test.ts— "emits loop_summary exactly once, even after multiple successful re-runs."
- File:
src/cli/init.ts - Severity: Medium
- Description: The Phase 2 plan specified canonical agent ordering (matching
ALL_AGENTS's declared order) regardless of how a user lists agents in--agent, so generated file lists and.loop/kit.jsonare deterministic.parseAgentFlaginstead preserved insertion order:--agent gemini,codexproduced["gemini", "codex"]instead of the canonical["codex", "gemini"]. Cosmetic only (no missing files, no duplicates, no crash) but a real deviation from spec. - Fix:
parseAgentFlagnow collects requested agents into aSetand filtersALL_AGENTSby membership, guaranteeing canonical output order. - Status: FIXED. Regression test added:
init.test.ts— "normalizes output to canonical order regardless of input order."
- File:
src/cli/__tests__/init.test.ts - Severity: N/A (test gap, not a production bug —
process.exit(130)was already correct insrc/cli/init.ts) - Description: A describe block was literally titled "regression: FINDINGS #25, exit 130 not 0," but every test inside only asserted
runWizard()returnsnullon cancellation — never that the CLI actually callsprocess.exit(130). The real exit call lives in the Command action handler, which no test imported or exercised. A revert ofprocess.exit(130)back toprocess.exit(0)would have passed the entire suite. - Fix: Extracted the inline cancel-and-exit logic into standalone exported functions (
exitOnWizardCancel,exitOnInvalidDir,exitOnInvalidAgentFlag,exitOnTemplateNotFound) and added tests that mockprocess.exitand assert it's called with the correct code. Verified via mutation testing: revertingexitOnWizardCanceltoprocess.exit(0)now fails the new test. - Status: FIXED.
- File:
src/cli/__tests__/init.test.ts - Severity: N/A (test gap — production logic in
src/cli/init.tswas already correct) - Description: Only the non-Windows no-op branch had a test. The two Windows branches — including the one that produces the actual user-visible warning — had zero coverage.
- Fix: Added
vi.mockforcommandExistsand two new tests covering win32-with-bash-absent (warning fires) and win32-with-bash-present (no warning). - Status: FIXED.