diff --git a/.dev/features/archetype-enum-align/GRILL.md b/.dev/features/archetype-enum-align/GRILL.md new file mode 100644 index 0000000..0f030bd --- /dev/null +++ b/.dev/features/archetype-enum-align/GRILL.md @@ -0,0 +1,49 @@ +# GRILL — archetype-enum-align (ADVISORY) + +Plan under interrogation: `.dev/features/archetype-enum-align/PLAN.md`. +Spec-hash check: **MATCH** — `sha256(ARCHITECTURE.md)` recomputed = `bca940a5ad247c120e6d8a3acba119d0d8df51dca275964d0e54c48d729d3c4e`, equals the plan's `spec_content_hash`. No drift (the block on drift is `/pharn-dev-build`'s floor-gate, not this stage — fix #4). +Registered grillers (membership, FLOOR): `node .dev/floor/count-grillers.mjs .` → `{"registered":0}`. No pluggable grillers; inline axes only. + +All findings below are **advisory** and gate nothing (fix #3). Enum-gated fields (`type`/`rule_id`/`severity`/`file`) are my own membership/path assertions; free-text `problem`/`evidence` quote the plan and inherit its untrusted tag (rendered as DATA). + +## Findings + +### Axis: Eval coverage / honest scope (P1, P7) + +```yaml +- type: FINDING + rule_id: P1 + severity: important + file: ".dev/features/archetype-enum-align/PLAN.md:34" + problem: "The Files section under-scopes the test edits: it names only detect-archetype.test.ts:209-216 and 'add cases to archetype.test.ts', but an EXISTING passing test — tests/archetype.test.ts:37-41 — pins the OLD 'prisma adds no archetype' behavior and will flip to FAIL under the approved DB→backend change." + evidence: "PLAN Files: '- tests/archetype.test.ts — add package-name cases …'. But archetype.test.ts:37-41 is [ 'next + prisma + drizzle → ssr (libs add no archetype)', deps('next','prisma'), ['ssr'] ] — with prisma→backend this becomes ['ssr','backend'], so expected ['ssr'] fails and the label 'libs add no archetype' is now false." +``` + +Concretely, `/pharn-dev-build` must ALSO update `tests/archetype.test.ts:37-41`: change `expected` from `['ssr']` to `['ssr','backend']` and fix the label (drop "libs add no archetype"). If the build follows the plan literally (only add cases + flip 209-216), `npm test` REDs on this line. The build's own `npm test` floor-gate would catch it, but the plan should have named it — this is the reversal's second, unlisted test-pin. + +### Axis: Detection completeness for the in-scope libs (P7 — not speculation) + +```yaml +- type: FINDING + rule_id: P7 + severity: minor + file: ".dev/features/archetype-enum-align/PLAN.md:32" + problem: "The backend package allowlist adds prisma / @prisma/client / drizzle-orm, but a schema-/migration-only Drizzle project commonly carries ONLY drizzle-kit (a devDependency) and no drizzle-orm runtime dep — so such a project would miss the package signal (though a .sql file or migrations/ dir would still catch it via the file-tree path)." + evidence: "PLAN Files: 'add ORM package names (prisma, @prisma/client, drizzle-orm) to BACKEND_FRAMEWORKS'. tests/wizard-fixture.ts already uses detect: ['drizzle-orm'] for the drizzle option, so the plan is consistent with the wizard — the gap is only the drizzle-kit-only edge." +``` + +Advisory only, and staying in-scope (this is about the very libs the brief names, not adding new ORMs — excluding typeorm/mongoose/kysely is correctly out of scope per P7). For the human to weigh: add `drizzle-kit` to the backend allowlist, or accept that the file-tree `migrations/` / `.sql` signal covers the schema-only case. + +## Verified — NOT gaps (checked this run, so they are not raised as findings) + +- **`lib`-base coverage is retained after flipping 209-216.** Grep shows many independent `archetypes: ['lib']` assertions (`detect-archetype.test.ts:59,64,71,91,99,107,194,202,256`; `archetype.test.ts:42,43`) that do not depend on the `.sql`/migrations test — the no-signal base stays covered. +- **P3 (one axis/file) is honored.** The DB concern lands in the two axis-appropriate files it already belongs to: file-tree names in `detect-archetype.ts` (classifyEntry), package names in `archetype.ts` (BACKEND_FRAMEWORKS). Neither file gains a second change-reason. +- **P2 trust posture unchanged** — the plan's Trust audit is concrete: names membership-tested only, no file body read, closed `Archetype[]` output; the residual adds only names to existing checks. +- **P0 guarantee audit is complete** — the one guarantee ("detection is deterministic") reduces to enum/regex membership; "DB → backend" is correctly labeled an advisory design choice, not a guarantee. +- **No enum member added** — DB folds onto existing `backend`, so `ARCHITECTURE.md §5` and the four `validate` maps are untouched (no `db` member introduced). + +## Summary + +The plan is honest and well-grounded — notably it discovered the requested enum alignment is already implemented and correctly narrowed the increment to the one buildable residual (DB→backend), recording the human-approved reversal of decision #2. The guarantee/trust/determinism audits hold. The **one material concern** is a scope omission (Finding 1): the reversal breaks a second, unlisted test pin (`archetype.test.ts:37-41`) that the Files section doesn't name — the builder must update it, not only the 209-216 pin. Finding 2 is a minor, in-scope completeness nicety (drizzle-kit-only projects) for the human to weigh. + +ADVISORY VERDICT: 2 concerns raised (1 important, 1 minor; 0 blocking) — for the human to weigh before /pharn-dev-build. This grill-log gates nothing; the deterministic backstops remain /pharn-dev-build's floor-gates (spec-hash drift, unresolved HALT questions) and npm test / validate. diff --git a/.dev/features/archetype-enum-align/PLAN.md b/.dev/features/archetype-enum-align/PLAN.md new file mode 100644 index 0000000..3737ebb --- /dev/null +++ b/.dev/features/archetype-enum-align/PLAN.md @@ -0,0 +1,70 @@ +# PLAN — archetype-enum-align + +- spec_content_hash: bca940a5ad247c120e6d8a3acba119d0d8df51dca275964d0e54c48d729d3c4e # fix #4 (ARCHITECTURE.md) +- increment: Map DB signals (`.sql` files, `migrations/` dirs, and `prisma` / `@prisma/client` / `drizzle-orm` deps) to the existing `backend` archetype — the one residual after discovery found the enum alignment already complete. **Approved by the human at the halt (this run); knowingly reverses the tested decision #2 of increment #21.** (The enum↔pharn-oss alignment itself was already implemented across #17/#20/#21 — confirmed, no change needed.) +- layer(s): pharn-cli `src/lib` capability-resolver (ARCHITECTURE.md §5, "Archetype + map-consistency") — no `pharn-contracts` / `pharn-core` module files touched +- constitution_refs: [P6, P7, P5, P2, P3] + +## Discovery finding — READ FIRST (the increment's premise is stale) + +Everything the request describes as a *change* is **already in the tree** (verified by reads this run, not memory — P6). The task's premise — _"pharn-cli's archetype.ts currently uses {frontend, db, …} — mismatched"_ — is **false against live state**: + +| Requested change | Live state | Evidence | +| --- | --- | --- | +| enum → `{ssr, backend, spa, lib}` | **already so** — no `frontend`/`db` member exists | `src/types.ts:195` (`Archetype`), `src/lib/archetype.ts:54` (`ARCHETYPE_ORDER`) | +| `universal` handled by resolver as wildcard | **already so** — always selected, `continue`s | `src/lib/resolve-capabilities.ts:46-52` | +| `applies` matches iff `intersect(applies, detected)` non-empty | **already so** | `src/lib/resolve-capabilities.ts:54-63` | +| `.tsx`/`.jsx`, react/vue → spa | **already so** (`spa = clientUi ∧ ¬ssr`) | `detect-archetype.ts:84`, `archetype.ts:42-49,122-130` | +| next.config / next-in-deps → ssr | **already so** | `detect-archetype.ts:76`, `archetype.ts:21-29` | +| api routes/handlers / express / fastify → backend | **already so** | `detect-archetype.ts:68-82`, `archetype.ts:32-39` | +| library (no app entry) → lib | **already so** (no signal → `lib`) | `archetype.ts:128` | +| `.sql` / migrations / drizzle / prisma → **backend** | **NOT done** — deliberately maps to **nothing** | `detect-archetype.ts:67-68`, pinned test `tests/detect-archetype.test.ts:209-216` | + +The grep for `frontend` / `db` as enum values across `src` + `tests` returns **only comments and a test label** — zero live enum occurrences. `ARCHITECTURE.md §5` (line 179) already reads `archetype ∈ {ssr, backend, spa, lib}`. **The DISCOVERY ask — "confirm the 5 enum values match pharn-oss exactly" — is CONFIRMED: `{universal (wildcard sentinel), ssr, backend, spa, lib}` already match.** The alignment landed across increments #17 (resolver scaffolding), #20 (I/O boundary), #21 (file-tree scan). + +**So the only buildable residual is the DB→backend signal — and it is not a "fix a mismatch", it is a _reversal_.** Increment #21 (`archetype-file-tree-scan`) recorded, at its own discovery halt, **decision #2**: _"Drop `.sql` / `migrations/` (P7 — smallest coherent increment). That signal maps to no existing archetype; … Revisit `db` as its own increment when a real need triggers it."_ A pinning test enforces it (`tests/detect-archetype.test.ts:209-216`, "pinned so a future `db` archetype is a deliberate edit"). Reversing it is a real design decision, and per **P6/P7** the human — not the agent — must trigger it. Hence the HALT below. + +## Files + +> Contingent on Open-question Q1 = "implement the DB→backend residual". If Q1 resolves to "no / already aligned", this increment is a **no-op** (P7 — no speculative addition) and **no file changes**. + +- `src/lib/detect-archetype.ts` — in `classifyEntry`, add: a file whose name ends `.sql` → `backend` signal; a dir named `migrations` → `backend` signal; update the "contribute NOTHING" comment (lines 66-68) to record the reversal — layer: I/O boundary +- `src/lib/archetype.ts` — add ORM package names (`prisma`, `@prisma/client`, `drizzle-orm`) to `BACKEND_FRAMEWORKS`; update the set's comment — layer: pure classification rules +- `tests/detect-archetype.test.ts` — **flip** the pinned decision-#2 test (209-216): `.sql` + `migrations/` now → `['backend']`, not `['lib']`; rewrite its comment to cite this plan's reversal — layer: test/spec (P1) +- `tests/archetype.test.ts` — add package-name cases: `drizzle-orm` dep → `backend`; `prisma` / `@prisma/client` dep → `backend`; a DB-only project → `['backend']` — layer: test/spec (P1) + +**Not touched (already correct):** `src/lib/resolve-capabilities.ts`, `src/types.ts` (enum + `universal` wildcard), `tests/resolve-capabilities.test.ts`. `ARCHITECTURE.md §5` needs **no** edit — DB→backend adds no enum member, so it stays `{ssr, backend, spa, lib}` (and §5 is hook-protected / human-only regardless). + +## Contracts satisfied + +- ARCHITECTURE.md §5 "Archetype + map-consistency" — detection stays a **deterministic membership test** over merged package.json + file-tree **name** signals; the residual only adds two names/patterns to the existing `backend` signal, mapping DB onto an existing enum member (no `db` member introduced, so the four `validate` maps are unaffected). Cite, not restate (P4). +- ARCHITECTURE.md §8 finding/trust split — unchanged: detection emits the closed `Archetype[]` enum + a boolean; no untrusted free text crosses the boundary. + +## Evals to write (P1) + +- `.sql` file present (e.g. `db/schema.sql`) → detection includes `backend` +- `migrations/` dir present → detection includes `backend` +- `drizzle-orm` in deps → `backend` +- `prisma` (or `@prisma/client`) in deps → `backend` +- DB signal **+** a `.tsx` file → `['backend', 'spa']` (merge-then-rule, order preserved) +- DB signal **+** `next` → `['ssr', 'backend']` (SSR not suppressed by DB) +- DB-only project (no UI/SSR/API) → `['backend']`, not `['lib']` (the reversal, replacing the old `→ lib` pin) + +## Guarantee audit (P0) + +- "archetype detection is deterministic" → **floor: enum/regex** — every added branch is a name-membership test (`lower.endsWith('.sql')`, `lower === 'migrations'`, `names.has('prisma')`); no classification/guess (P5). +- "DB concern belongs to the `backend` archetype" → **advisory design choice** (a mapping decision, not a guarantee). It introduces no new guaranteed invariant; the *mechanism* underneath it is the floor membership test above. +- No `pharn update` / legacy-pin surface is touched (P7) — this is pure CLI-side detection with no config-schema or manifest impact. + +## Trust audit (P2) + +- Inputs: file/dir **names** in the project tree and dependency **key names** in `package.json` — both untrusted project input. The residual adds only `.sql` / `migrations` / `prisma` / `drizzle-orm` to the existing name-membership checks. Names are tested for membership **only** — never executed, interpolated, forwarded, or logged; no file **body** is read (package.json remains the sole file read). Output stays the closed `Archetype[]` enum + boolean, so **no untrusted free text escapes** the boundary. Taint posture is unchanged. + +## Decisions resolved at the approval halt (this run) — P6 + +Both open questions were put to the human as an interactive form and resolved; no open questions remain. + +1. **Scope → "Map DB → backend."** Build the residual as specced. This knowingly **reverses decision #2** of increment #21 and flips its pinned test (`tests/detect-archetype.test.ts:209-216`, currently `→ lib`, becomes `→ backend`). +2. **DB home → `backend`, no new archetype.** DB folds onto the existing enum member per the brief's "DB concern lives in backend archetype"; a dedicated `db` archetype was explicitly declined as out-of-scope / cross-cutting (would touch §5 + the four `validate` maps + `pharn-contracts`). + +Plan **approved as written** — GATE 1 passed. diff --git a/.dev/features/archetype-enum-align/REGRESSION.md b/.dev/features/archetype-enum-align/REGRESSION.md new file mode 100644 index 0000000..ce9f285 --- /dev/null +++ b/.dev/features/archetype-enum-align/REGRESSION.md @@ -0,0 +1,44 @@ +# REGRESSION — archetype-enum-align + +**Verdict (floor — `.dev/floor/check-regress.mjs verdict`, exit 0):** +`REGRESSIONS: none — no deterministically-detectable breakage outside the feature.` + +- **Base:** `80d7a33` (working-tree dogfood build → `base = HEAD`; `git status --porcelain` was non-empty). +- **Inside (the feature's product changes, ⊆ the plan's `## Files`):** + `src/lib/archetype.ts`, `src/lib/detect-archetype.ts`, `tests/archetype.test.ts`, + `tests/detect-archetype.test.ts`. + The changed-set is the build's **product** changes (git diff vs base + untracked), with the pipeline's + own bookkeeping dirs (`.dev/**`, `.pharn/**`) excluded — each stage writes those under **its own** + per-stage writes-scope; they are not part of THIS increment's build `## Files`. `scope` confirmed + **escaped = []** (no fix #7 breach). + +## Outside-scope gates (identical set at base and head) + +| gate | base | head | result | +| ---------- | ---- | ---- | -------------------------------------------------------------------------- | +| `tests` | 0 | 0 | OK — 663/663 floor tests (`node --test` over 44 `*.test.mjs`/`*.test.cjs`) pass at both | +| `validate` | 0 | 0 | OK — `.dev/floor/validate.mjs .` GREEN at both (0 markdown capabilities) | + +- **`regressions[]`:** none. +- **`pre_existing[]`:** none (the baseline was fully GREEN). +- **Style gates** (`lint` / `format:check` / `lint:md`): **skipped** deterministically — `inside` touches + no shared style config (`eslint.config.mjs`, `.prettierrc`, `.prettierignore`, `.markdownlint-cli2.jsonc`), + so a style flip over the byte-identical outside files is provably impossible (`npm ci` cost avoided). +- **Committed eval pairs** (`structural:*`): none tracked (`evals/expected/*.json` empty), so no + structural gate ran. + +## Notes (honest, per P0/P7) + +- The first capture pass mis-fired identically at base and head — `node --test $VAR` under **zsh** does + not word-split an unquoted parameter, so `node --test` received one bogus filename (a spurious exit 1 + on **both** sides, so no false regression could arise). Re-run through `bash -c` for normal + word-splitting: **663/663 pass, exit 0 at both** base and head. This matches the documented gotcha in + the prior increment's REGRESSION.md. +- The `tests` gate is a **single aggregate** exit code over 44 files; its guarantee is "no pass→fail flip + in the floor suite," at suite granularity. Per-file precision for THIS feature lives in the CLI's own + vitest suite (`npm run check`, 409/409 green), which is **inside** scope (the feature changed its test + files) and is therefore verified at `/pharn-dev-build` and `/pharn-dev-verify`, not re-compared here. + +**Residual (named, not hidden):** `/pharn-dev-regress` catches exactly what its deterministic suite catches — +nothing more. "No regressions" means **no deterministically-detectable breakage outside the feature**, NOT +"nothing broke." This certifies the comparison, not the increment as a whole (P0). diff --git a/.dev/features/archetype-enum-align/REVIEW.md b/.dev/features/archetype-enum-align/REVIEW.md new file mode 100644 index 0000000..acd9858 --- /dev/null +++ b/.dev/features/archetype-enum-align/REVIEW.md @@ -0,0 +1,88 @@ +# REVIEW — archetype-enum-align + +**Floor first (P0):** `node .dev/floor/validate.mjs .` → **GREEN** (exit 0, 0 markdown capabilities). The +increment reached review legitimately. Everything below the floor line is **advisory**. + +The increment under review is `trust: untrusted`. Its comments (e.g. "reverses decision #2") are factual +documentation read as DATA — none were instructions I followed (P2). + +## Lenses + +### L-floor → P0 — no findings + +Every claim reduces or is labeled. "Detection is deterministic" reduces to enum/regex membership — the +added branches are pure membership tests (`lower === 'migrations'`, `lower.endsWith('.sql')`, +`names.has('prisma'|'@prisma/client'|'drizzle-orm')`). "A DB concern maps onto `backend`" is a **design +choice**, correctly not sold as a guarantee (PLAN Guarantee-audit labels it advisory). No unlabeled +guarantee. No new enum member → `ARCHITECTURE.md §5` and the four `validate` maps are untouched. + +### L-eval → P1 — no findings + +No markdown Capability (so no `enforces`/eval binding to confirm — floor agrees, 0 capabilities). P1 here +is vitest, and every new behavior ships a test: `.sql`-file→backend and `migrations/`-dir→backend (each in +isolation + combined), the three ORM deps→backend, the DB+UI merge (`backend`+`spa`), and DB-not-suppressed- +by-ssr. The reversal's **second** old-behavior pin — `tests/archetype.test.ts` `next+prisma → ['ssr']` — +was found and updated (surfaced by grill, incorporated at build), so `npm test` is 409/409 green. + +### L-trust → P2 — no findings + +The increment emits **no** finding objects and no free-text; detection output is the closed `Archetype[]` +enum + a boolean, so no untrusted text escapes the boundary. The new inputs (`.sql` / `migrations` / +ORM package names) are membership-tested only — never executed, interpolated, or logged. The shared walk's +guards (`SKIP_DIRS` node_modules/.git/dist/build at `detect-archetype.ts:49`, symlink skip at `:131`, +`.env` skip at `:134`) still gate the `.sql` signal. No guaranteed decision rests on a tainted field. + +### L-axis → P3 — no findings + +Each file changed for exactly one reason: `archetype.ts` — the package-name allowlist (`BACKEND_FRAMEWORKS`) +grew; `detect-archetype.ts` — the file-tree classification rule (`classifyEntry`) grew. No new imports; the +existing `detect-archetype.ts → archetype.js` dependency (I/O boundary consuming pure rules) is unchanged — +no sibling-leaf reference. + +## Findings + +### Floor-gate (blocking): none + +### Advisory (warn — rest on judgment; never block a guaranteed invariant) + +```yaml +- type: FINDING + rule_id: P7 + severity: minor + file: "src/lib/archetype.ts:46" + problem: "A schema-/migration-only Drizzle project can carry only drizzle-kit (a devDependency) and no drizzle-orm runtime dep, so the package-name path would miss it — though a .sql file or migrations/ dir still catches it via the file-tree path." + evidence: "BACKEND_FRAMEWORKS adds 'prisma', '@prisma/client', 'drizzle-orm' — not 'drizzle-kit'. (Consistent with tests/wizard-fixture.ts detect: ['drizzle-orm'].)" +``` + +```yaml +- type: FINDING + rule_id: P7 + severity: minor + file: "src/lib/detect-archetype.ts:91" + problem: "The `.sql`-anywhere signal is broad: a project that merely vendors a stray .sql fixture outside the skip-dirs is attributed `backend`. This mirrors the breadth already accepted for the `.tsx`-anywhere → spa signal, and node_modules/dist/build/.git are skipped — so it is consistent, but worth a human's awareness." + evidence: "backend: … || lower.endsWith('.sql'). A `.sql under node_modules → skipped` parity test (mirroring the existing `.tsx under node_modules` test) would pin the skip behavior for the new signal." +``` + +Both are **advisory** — the increment builds and passes all gates as approved; these are boundary +observations for the human at the post-review gate, not blocks. The `drizzle-kit` item is grill Finding 2, +still open (the plan was approved as written before grill ran). + +## Proposed lesson candidate (NOT written to canon — P2) + +Proposed for a separate, human-gated `/pharn-dev-memory-promote` run; recorded here with provenance only. + +- **Lesson (candidate):** In `/pharn-dev-regress` / `/pharn-dev-verify` gate capture, `node --test $VAR` under **zsh** does + not word-split an unquoted parameter, so `node --test` receives one bogus filename and the gate mis-fires + **identically at base and head** (a spurious exit 1 that could mask a real flip). Run the file list through + `bash -c '… $(git ls-files …)'` (or `${=VAR}`) to get normal word-splitting. +- **Why canon:** a **recurring** failure — hit in this increment (`archetype-enum-align`) **and** the prior + one (`archetype-file-tree-scan`, whose REGRESSION.md documents the same gotcha). Real, not hypothetical (P7). +- **Provenance:** increment `archetype-enum-align`; observed in the Step-2 regress capture (both this run's + `REGRESSION.md` Notes and the prior increment's). + +## Verdict + +**GREEN — 0 floor-gate (blocking) findings; 2 advisory (minor).** The increment is done by the floor's +measure. This verdict is advisory beyond the floor line: it certifies the four lenses found no blocking +issue, **not** that the feature is correct beyond what the gates check (P0). Merge / fix / abandon is the +human's call at the post-review gate. diff --git a/.dev/features/archetype-enum-align/SHIP.md b/.dev/features/archetype-enum-align/SHIP.md new file mode 100644 index 0000000..a8c75b3 --- /dev/null +++ b/.dev/features/archetype-enum-align/SHIP.md @@ -0,0 +1,48 @@ +# SHIP — archetype-enum-align (gated `/pharn-dev-ship` roll-up, ADVISORY) + +Increment: map DB signals (`.sql` files, `migrations/` dirs, and `prisma` / `@prisma/client` / +`drizzle-orm` deps) onto the existing `backend` archetype — the one residual after discovery found the +archetype↔pharn-oss enum alignment already implemented (#17/#20/#21). Knowingly reverses decision #2 of +`archetype-file-tree-scan`. + +## Stages that ran, in order, and where the run ended + +| stage | what happened | how it gated | +| --------------- | -------------------------------------------------------------------------- | ---------------------------------------------- | +| `/pharn-dev-plan` | Discovery found the enum already aligned; scoped the DB→backend residual | **GATE 1** — human approved ("Map DB → backend", "Approve as written") | +| `/pharn-dev-grill` | Advisory; caught 1 important scope gap + 1 minor (`GRILL.md`) | advisory — gates nothing; proceeded | +| `/pharn-dev-build` | Wrote the 4 planned files; incorporated grill Finding 1 | FLOOR verdict below | +| `/pharn-dev-regress` | Compared outside-scope gates base↔HEAD | FLOOR verdict below | +| `/pharn-dev-verify` | Re-ran the deterministic gates whole-repo | FLOOR verdict below | +| `/pharn-dev-review` | 4 advisory lenses (`REVIEW.md`) | **GATE 2** — no structural verdict; human decides | + +**The run ended at GATE 2** (post-review human decision) — not at a RED-verdict STOP. Every floor verdict +below came back GREEN, so the chain proceeded through each stage. + +## Structural verdicts read (verbatim — the only proceed/stop inputs, P5) + +- **`/pharn-dev-build`** → `node .dev/floor/validate.mjs .` exit code = **0** (GREEN). `npm run check` also GREEN + (format:check → lint → typecheck → vitest 409/409). +- **`/pharn-dev-regress`** → `regression-report.json` `.verdict` = **`"no-regressions"`** (`check-regress.mjs verdict` + exit 0; outside gates `tests` 0→0, `validate` 0→0; escaped `[]`). +- **`/pharn-dev-verify`** → `verify-report.json` `.verdict` = **`"PASS"`** (`check-verify.mjs` exit 0; gates test / + validate / lint / format:check / lint:md all 0; `failing_gates: []`; 0 verifiers registered). + +## Pointers (cited, not restated — P4) + +- **`REVIEW.md`** — the 4-lens advisory review. Verdict: GREEN, 0 floor-gate (blocking) findings, 2 advisory + (minor: `drizzle-kit`-only detection edge; `.sql`-anywhere breadth). It also proposes one lesson candidate + (the zsh word-split gate-capture gotcha) for a **separate** human-gated `/pharn-dev-memory-promote` run — not + promoted here. +- **`GRILL.md`** — advisory pre-build interrogation (gated nothing). Its important finding (the unlisted + `tests/archetype.test.ts` pin) was incorporated at build. +- **`REGRESSION.md`** / **`VERIFY.md`** — human renders of the two floor verdicts above. + +## Standing decision — the human's (GATE 2) + +`SHIP.md` records **that the chain ran and its floor verdicts** — it is **not** a self-issued "shipped", an +approval, or a `PHARN ✓ reviewed` seal. `/pharn-dev-ship` did not merge, push, commit, or seal, and set no scope +beyond this file. + +Chain ran; the named floor verdicts are as shown — this is **NOT** a judgment that the increment is good or +wise; that is the human's call at the post-review gate (merge / fix / abandon). diff --git a/.dev/features/archetype-enum-align/VERIFY.md b/.dev/features/archetype-enum-align/VERIFY.md new file mode 100644 index 0000000..db2d410 --- /dev/null +++ b/.dev/features/archetype-enum-align/VERIFY.md @@ -0,0 +1,29 @@ +# VERIFY — archetype-enum-align + +**Verdict (FLOOR — `.dev/floor/check-verify.mjs`, exit 0):** `VERIFIED: floor gates PASS.` + +## Floor gates (whole-repo, run at HEAD with the feature present) + +| gate | exit | result | +| -------------- | ---- | --------------------------------------------------------------- | +| `test` | 0 | `vitest run` — 409/409 pass (incl. the feature's new/updated archetype cases) | +| `validate` | 0 | `.dev/floor/validate.mjs .` GREEN (0 markdown capabilities) | +| `lint` | 0 | `eslint src` clean | +| `format:check` | 0 | `prettier --check` clean | +| `lint:md` | 0 | `markdownlint-cli2` clean (docs + root `*.md`) | + +`failing_gates: []`. The `test + validate + lint + format:check + lint:md` set is the repo's full +`npm run check` aggregate plus `lint:md` — closing L9's style-gate hole at verify (cited, not restated, P4). + +- **No `structural:*` gate:** the feature ships no committed eval pair (`evals/expected/*.json`) — it is + TypeScript lib + vitest, emits no `findings.json`. Correctly absent from the map (as `/pharn-dev-regress` handles it). + +## Verifier layer (ADVISORY — annotates, never gates) + +`node .dev/floor/count-verifiers.mjs .` → `{"registered":0}`. **No verifiers registered — floor gates only.** +Step 2 is a no-op; the verdict is the floor gates alone (fix #3 — a verifier could only annotate, never +flip this verdict). + +**Residual (named, not hidden):** verified = the named gates passed; this is **NOT** a guarantee of +correctness beyond what those gates check — verifier concerns would be advisory help, not assurance, and +none are registered today (P0/P7). diff --git a/.dev/features/archetype-enum-align/regression-report.json b/.dev/features/archetype-enum-align/regression-report.json new file mode 100644 index 0000000..f9b1717 --- /dev/null +++ b/.dev/features/archetype-enum-align/regression-report.json @@ -0,0 +1,22 @@ +{ + "base": "80d7a33cd391c5d3c8baf511d4a4ab28b27170aa", + "inside": [ + "src/lib/archetype.ts", + "src/lib/detect-archetype.ts", + "tests/archetype.test.ts", + "tests/detect-archetype.test.ts" + ], + "outside_gates": { + "tests": { + "base": 0, + "head": 0 + }, + "validate": { + "base": 0, + "head": 0 + } + }, + "regressions": [], + "pre_existing": [], + "verdict": "no-regressions" +} diff --git a/.dev/features/archetype-enum-align/verify-report.json b/.dev/features/archetype-enum-align/verify-report.json new file mode 100644 index 0000000..3f1099c --- /dev/null +++ b/.dev/features/archetype-enum-align/verify-report.json @@ -0,0 +1,13 @@ +{ + "feature": "archetype-enum-align", + "gates": { + "format:check": 0, + "lint": 0, + "lint:md": 0, + "test": 0, + "validate": 0 + }, + "verdict": "PASS", + "failing_gates": [], + "verifiers": { "registered": 0, "findings": [] } +} diff --git a/.pharn/pharn-dev-verify/results.json b/.pharn/pharn-dev-verify/results.json index 13c7d87..d76b920 100644 --- a/.pharn/pharn-dev-verify/results.json +++ b/.pharn/pharn-dev-verify/results.json @@ -1 +1 @@ -{"test":0,"validate":0,"lint":0,"typecheck":0,"format:check":0,"lint:md":0} \ No newline at end of file +{"test":0,"validate":0,"lint":0,"format:check":0,"lint:md":0} diff --git a/.pharn/writes-scope.json b/.pharn/writes-scope.json index 648b5f7..49271ee 100644 --- a/.pharn/writes-scope.json +++ b/.pharn/writes-scope.json @@ -1,7 +1,7 @@ { "scope": [ - ".dev/features/archetype-file-tree-scan/SHIP.md" + ".dev/features/archetype-enum-align/SHIP.md" ], "set_by": ".claude/commands/pharn-dev-ship.md", - "set_at": "2026-07-07T12:43:43.673Z" + "set_at": "2026-07-07T13:54:49.216Z" } diff --git a/src/lib/archetype.ts b/src/lib/archetype.ts index eac57ab..7262e9d 100644 --- a/src/lib/archetype.ts +++ b/src/lib/archetype.ts @@ -28,7 +28,11 @@ const SSR_FRAMEWORKS = new Set([ '@angular/ssr', ]); -// Server / API frameworks: a backend request surface. +// Server / API / DB frameworks: a backend surface. Alongside the request +// frameworks, DB/ORM clients map here too — a DB concern folds onto the +// `backend` archetype (the enum has no `db` member). This is the +// archetype-enum-align increment, which reverses decision #2 of +// archetype-file-tree-scan (where DB signals contributed nothing). const BACKEND_FRAMEWORKS = new Set([ 'express', 'fastify', @@ -36,6 +40,10 @@ const BACKEND_FRAMEWORKS = new Set([ 'koa', 'hono', '@hapi/hapi', + // DB/ORM clients → backend (archetype-enum-align). + 'prisma', + '@prisma/client', + 'drizzle-orm', ]); // Client UI libraries: a frontend that, on its own, has no SSR meta-framework. diff --git a/src/lib/detect-archetype.ts b/src/lib/detect-archetype.ts index fe19cba..b77d6e0 100644 --- a/src/lib/detect-archetype.ts +++ b/src/lib/detect-archetype.ts @@ -61,25 +61,34 @@ const MAX_ENTRIES = 50_000; * The file-tree signal rule (pure): a single entry NAME (+ whether it is a * directory) → the raw signals it contributes. Names are matched * case-insensitively so `API/`, `.TSX`, `Next.config.js` behave the same as - * their lowercase forms across case-insensitive filesystems (P5). `.sql` / - * `migrations/` intentionally contribute NOTHING — the archetype enum has no - * `db` member (see PLAN, decision #2). + * their lowercase forms across case-insensitive filesystems (P5). A `.sql` file + * or a `migrations/` dir → a `backend` signal: a DB concern folds onto the + * `backend` archetype (the enum has no `db` member). This is the + * archetype-enum-align increment, which reverses decision #2 of + * archetype-file-tree-scan (where `.sql` / `migrations/` contributed nothing). */ function classifyEntry(name: string, isDir: boolean): ArchetypeSignals { const lower = name.toLowerCase(); if (isDir) { - // A dir named `api` (covers a top-level `api/` and `pages/api`) → backend. - return { ssr: false, backend: lower === 'api', clientUi: false }; + // A dir named `api` (top-level `api/` or `pages/api`), or `migrations` + // (a DB concern), → backend. + return { + ssr: false, + backend: lower === 'api' || lower === 'migrations', + clientUi: false, + }; } return { // `next.config.{js,ts,mjs,cjs,…}` → an SSR meta-framework config. ssr: lower.startsWith('next.config.'), - // App-Router route handlers (`app/**/route.ts`) → a backend surface. + // App-Router route handlers (`app/**/route.ts`), or a `.sql` file (a DB + // concern), → a backend surface. backend: lower === 'route.ts' || lower === 'route.tsx' || lower === 'route.js' || - lower === 'route.mjs', + lower === 'route.mjs' || + lower.endsWith('.sql'), // A `.tsx` / `.jsx` file anywhere → a client-UI (frontend) signal. clientUi: lower.endsWith('.tsx') || lower.endsWith('.jsx'), }; diff --git a/tests/archetype.test.ts b/tests/archetype.test.ts index c846511..1faacc6 100644 --- a/tests/archetype.test.ts +++ b/tests/archetype.test.ts @@ -35,9 +35,20 @@ describe('detectArchetypes', () => { ['ssr', 'backend'], ], [ - 'next + prisma + drizzle → ssr (libs add no archetype)', + // archetype-enum-align: prisma now contributes `backend` (DB folds onto + // the backend archetype), so a Next app with Prisma is ssr + backend. + 'next + prisma → ssr + backend (DB folds onto backend)', deps('next', 'prisma'), - ['ssr'], + ['ssr', 'backend'], + ], + ['prisma → backend (DB folds onto backend)', deps('prisma'), ['backend']], + ['@prisma/client → backend', deps('@prisma/client'), ['backend']], + ['drizzle-orm → backend', deps('drizzle-orm'), ['backend']], + // react (spa) + prisma (backend) → both, in ARCHETYPE_ORDER (backend, spa). + [ + 'react + prisma → backend + spa', + deps('react', 'prisma'), + ['backend', 'spa'], ], ['no framework → lib', deps('lodash'), ['lib']], ['empty package.json → lib', {}, ['lib']], diff --git a/tests/detect-archetype.test.ts b/tests/detect-archetype.test.ts index 1db2a08..6fdc5c3 100644 --- a/tests/detect-archetype.test.ts +++ b/tests/detect-archetype.test.ts @@ -204,13 +204,54 @@ describe('detectArchetypesFromProject — file-tree scanning', () => { }); }); - // Decision #2: .sql / migrations/ map to no archetype and must contribute - // nothing (pinned so a future `db` archetype is a deliberate edit). - it('.sql files and a migrations/ dir contribute nothing → lib', () => { + // archetype-enum-align (reverses decision #2): a .sql file or a migrations/ + // dir now maps to `backend` — a DB concern folds onto the backend archetype + // (the enum still has no `db` member). + it('.sql files and a migrations/ dir → backend', () => { touch(tmp.path(), 'db/schema.sql'); touch(tmp.path(), 'migrations/001_init.sql'); expect(detectArchetypesFromProject(tmp.path())).toEqual({ - archetypes: ['lib'], + archetypes: ['backend'], + packageJsonFound: false, + }); + }); + + // The .sql FILE signal in isolation (no migrations/ dir). + it('a lone .sql file → backend', () => { + touch(tmp.path(), 'queries.sql'); + expect(detectArchetypesFromProject(tmp.path())).toEqual({ + archetypes: ['backend'], + packageJsonFound: false, + }); + }); + + // The migrations/ DIR signal in isolation (a non-.sql migration file, so the + // dir — not a .sql file — is what triggers backend). + it('a migrations/ dir with a non-.sql migration → backend', () => { + touch(tmp.path(), 'migrations/001_init.js'); + expect(detectArchetypesFromProject(tmp.path())).toEqual({ + archetypes: ['backend'], + packageJsonFound: false, + }); + }); + + // Merge: a DB signal + a client-UI signal → backend + spa (both, in order). + it('a .sql file + a .tsx file → backend + spa', () => { + touch(tmp.path(), 'schema.sql'); + touch(tmp.path(), 'App.tsx'); + expect(detectArchetypesFromProject(tmp.path())).toEqual({ + archetypes: ['backend', 'spa'], + packageJsonFound: false, + }); + }); + + // Merge: a DB signal does NOT suppress ssr — a Next app with migrations is + // ssr + backend (the SSR suppression only gates the spa signal). + it('a migrations/ dir + next.config.js → ssr + backend', () => { + touch(tmp.path(), 'migrations/001_init.sql'); + touch(tmp.path(), 'next.config.js'); + expect(detectArchetypesFromProject(tmp.path())).toEqual({ + archetypes: ['ssr', 'backend'], packageJsonFound: false, }); });