intent(test-ownership): move tests into website/server/tests/ so they collocate with the code under test and stop reaching up through three parents; reviewer follow-up wanted dedicated coverage and the root vs. website/server package boundary makes collocation the right long-term layout
decision(vitest-config): give website/server its own vitest.config.ts + `test` script; root's existing tests/**/*.test.ts include no longer catches server tests since they moved outside that tree, so the two test runs stay independent
decision(tsconfig-test): add tsconfig.test.json extending the build config and lift lint-tsc to `-p tsconfig.test.json` — the build tsconfig's rootDir: "./src" excludes tests/, so a single lint command wouldn't have type-checked them
learned(valibot-instanceof): with tests now resolving valibot from the same website/server/node_modules as validateRequest, the cause-check can go back to `instanceof v.ValiError` — the duck-type workaround was only needed when the root harness and server pulled different valibot copies
constraint(ci-website): added a `test-website-server` job that links the local repomix build the same way lint-website-server does; tests don't actually import repomix today, but colocation means they easily could later and the link step keeps parity
intent(server): land validateRequest unit tests alongside this PR's rewrite per reviewer follow-up — previously only exercised indirectly via classifyRejectReason with mocks, so silently dropping `.cause` during a future refactor would not fail any test
learned(valibot-instanceof): website/server resolves valibot from its own node_modules (distinct copy from the root harness), so `instanceof v.ValiError` fails across that boundary even for genuine ValiError instances; duck-type the cause check on `{ name: 'ValiError', issues: [] }` to match classifyRejectReason's own contract
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(drift-guard): swap the hard-coded 'Either URL or file must be provided' literal for MESSAGES.MISSING_INPUT so the cause-chain test honors the classifier drift-guard the file's header comment promises — a future message rewrite now propagates to this assertion too, instead of silently falling back to 'unknown'
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(pack-event-test): migrate tests/website/server/packEventSchema.test.ts to the valibot-shaped `{ name, issues: [{ message, path: [{ key }] }] }` fixture so the invalid_format case — which relies on the path fallback — actually exercises the post-migration classifier
decision(path-formatting): replace the hand-rolled `path.map/filter/join` in both packEventSchema.ts and validation.ts with `v.getDotPath(issue)` — the valibot built-in does the same reconstruction and eliminates the two-site drift risk reviewers flagged
fix(validation-error-message): drop the leading `": "` when a valibot issue has no path (top-level checks like MISSING_INPUT / BOTH_PROVIDED), so the rendered AppError message reads cleanly
rejected(cause-walk-depth): coderabbit suggested walking `.cause` recursively up to depth 3 — declined because validateRequest wraps exactly once by contract and no second wrapper exists in the chain; defensive recursion would add surface area for no caller
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intent(test-coverage): normalizeObjectNode is the load-bearing workaround for @valibot/to-json-schema's output gaps (missing additionalProperties:false, noisy empty required arrays). A bug in its tree walk would silently break the published repomix.config.json IntelliSense schema. Extract to its own module and pin the behavior: adding/respecting additionalProperties, stripping empty required, recursing through properties / anyOf / oneOf / items, handling null and primitives
Addresses PR #1493 round 5 follow-up (schema-drift tracking) from claude.
intent(eliminate-drift): prior reviews flagged the string-coupling
between packRequestSchema (produces zod messages) and
classifyRejectReason (matches them back to a metric label) as
fragile — a message rewrite in one place would silently land
requests in the 'other' bucket, quietly mislabeling the dashboard.
Extract all 11 messages to a shared MESSAGES module so the
producer, the consumer, and the test's expected values all
reference the same constants
decision(map-over-switch): classifyRejectReason's 11-case switch
becomes a MESSAGE_TO_REASON lookup table. Same runtime behavior,
easier to scan, and the keys are compile-time-typed via the
shared constants (so a typo in either side is a TS error)
decision(preserve-invalid_format-path): 'invalid_format' is keyed by
path (`format`), not by message text. Left as path-match so it
continues to work even if zod's default enum error message changes
improve(test-references-constants): test.each cases now reference
MESSAGES directly. The test continues to catch classifier-logic
drift (wrong label for a known key, missing entry in the lookup)
while schema/classifier drift becomes impossible by construction
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intent(ci-green): CI failed on `Cannot find package 'repomix'` because
the test imported `packRequestSchema.ts` which has `import from
'repomix'` — the root vitest harness doesn't install server-only
packages (repomix is this repo, hono is a server dep). Local runs
passed by coincidence of self-reference resolution
decision(construct-zoderror-directly): rewrite the test to construct
`z.ZodError` instances with messages matching the schema rather
than invoking the schema. Keeps classifier-drift coverage (if the
switch stops matching a known label, a test fails) at the cost of
not catching schema-drift (if a zod message in packRequestSchema is
edited without updating the test, the test still passes but
dashboards mislabel). Schema-drift catching would need a shared
constants module — tracked as a follow-up
decision(native-error-cause): use `new Error(msg, { cause })` instead
of importing `AppError`. AppError pulls `hono/utils/http-status`
(type-only, but some module resolvers still probe). Native Error
with cause exercises the same classifier code path
improve(table-driven): restructure the 11 label cases as `test.each`
so each case is named individually in the test output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses PR #1493 round 3 review from claude.
intent(catch-schema-drift): every previous review round flagged the
fragile string coupling between classifyRejectReason and the zod
messages in packRequestSchema. The earlier response was that adding
tests would require new infrastructure in website/server/ — but
tests/website/cliCommand.test.ts already tests website/ code under
the root vitest harness. Extended the same pattern to cover every
rejection path through the real schema so message edits surface as
CI failures instead of silent 'other'-bucket dashboard drift
decision(table-driven-through-real-schema): the tests invoke
validateRequest(packRequestSchema, input) rather than constructing
synthetic ZodError objects — this catches both classifier drift AND
schema drift in one assertion. 13 tests covering 11 reject labels
+ cause-chain extraction + non-error input
improve(boolean-intent-comment): claude noted that Boolean() collapses
`undefined` (user didn't send field) with `false` (user explicitly
disabled), losing that distinction in the pack_options_usage metric.
This is intentional — the metric answers "what % of packs had
compress enabled" where both "off" and "unspecified" correctly mean
the feature wasn't active. Added a comment so future contributors
don't "fix" it
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Define CliCommandPackOptions interface locally in cliCommand.ts instead of
importing PackOptions from usePackOptions.ts which depends on Vue module.
This prevents tsc from following the import chain to Vue in CI.
Address PR review comments:
- Add shell escaping for user-controlled values (repositoryUrl, includePatterns, ignorePatterns)
to prevent command injection when users copy-paste the generated command
- Skip --remote flag for uploaded file names by validating with isValidRemoteValue
- Add unit tests for generateCliCommand covering all option combinations