diff --git a/.dev/features/readme-adoption-rewrite/GRILL.md b/.dev/features/readme-adoption-rewrite/GRILL.md new file mode 100644 index 0000000..e511c8d --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/GRILL.md @@ -0,0 +1,97 @@ +# GRILL — readme-adoption-rewrite + +Plan interrogated: `.dev/features/readme-adoption-rewrite/PLAN.md`. Spec-hash check: **MATCH** — recomputed `sha256(pharn/ARCHITECTURE.md)` = `8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52`, equal to the plan's pin, so the plan was made against the current spec. + +Griller membership read deterministically: `node pharn/floor/count-grillers.mjs .` → `registered=13`. The five plan-scanners those grillers own were run over the plan and all returned clean: `scan-plan-secrets`, `scan-plan-pii`, `scan-plan-i18n` → `{"found":false}`; `scan-plan-migrations`, `scan-plan-observability` → `{"mentions":false}`. + +**This grill-log is ADVISORY end-to-end. It gates nothing.** No finding below blocks `/pharn-dev-build`; the deterministic backstops remain `/pharn-dev-build`'s spec-hash and open-questions gates and `pharn/floor/validate.mjs`. + +## Findings + +### Axis: honest scope / writes-scope (P7, P0) + +```yaml +- type: FINDING + rule_id: "P0" + severity: important + file: ".dev/features/readme-adoption-rewrite/PLAN.md:44" + problem: "The exclusion block names docs/capabilities/ as out of scope while build hazard #3 (line 135) prescribes `npm run docs:generate`, which writes exactly that directory plus docs/lessons-index.md — the plan contradicts itself, and per L19 the honest move is to DECLARE the Bash write, not to leave it fenced off in prose the gate cannot see." + evidence: "line 44: '- `docs/capabilities/` — generated; regenerated only by `npm run docs:generate`, never hand-edited.' against line 135: 'npm run docs:generate && npm run docs:check'. Verified live: docs:generate = `node .dev/floor/gen-capability-catalog.mjs . && node .dev/floor/gen-lessons-index.mjs .`" +``` + +The plan cites L19 in `applied_lessons` and then reproduces the exact shape L19 warns about — a Bash-invoked tool writing outside the declared scope, benign in result, invisible to fix #7. L19's own words: the remedy "is to **declare it**, not to pretend the gate covered it." + +### Axis: guarantee-audit completeness (P0) + +```yaml +- type: FINDING + rule_id: "P0" + severity: important + file: ".dev/features/readme-adoption-rewrite/PLAN.md:79" + problem: "The guarantee audit enumerates sentences the README WILL contain, but nothing binds the built artifact to that table — a new guarantee-shaped sentence written at build time would never be audited, and the audit dies with the plan." + evidence: "Section header at line 79: '## Guarantee audit (P0)', whose rows are all prospective ('Sentence the README will carry'). The plan cites L2 at line 12 but carries only its first half." +``` + +L2's remedy is explicitly two-part: the honesty must be written **into the durable artifact**, not just the ephemeral plan. The plan declares L2 and implements the reduction, but no step re-reads the finished README against this table. Suggested: add a build step that walks the rendered README and confirms every guarantee-shaped sentence appears in the audit — advisory, but at least performed on the artifact. + +### Axis: documentation drift (P6, P7) + +```yaml +- type: FINDING + rule_id: "P6" + severity: important + file: ".dev/features/readme-adoption-rewrite/PLAN.md:157" + problem: "S3 hardcodes 'what lands in the repo' from a one-time install observation, but the CLI fetches the repo's current HEAD, so the described tree drifts with every commit and no generator or checker owns it — the same unowned-number shape §2.4 forbids for counts." + evidence: "line 157: '- **S3 Quick start** — install, then `/pharn-spec`, then what lands in the repo (the verified tree from the discovery report).' Verified live: the probe install wrote pharn.config.json commit 71e71ee, byte-equal to HEAD." +``` + +The plan is careful to hardcode no capability count and no test total, then reintroduces the same class of claim as a prose tree listing. Suggested: describe what lands **by kind** ("the product commands, the write-gating hooks, the floor, the contracts") rather than by enumerated path, so the sentence stays true as the tree grows. + +### Axis: determinism / unbounded build-time choice (P5) + +```yaml +- type: FINDING + rule_id: "P5" + severity: important + file: ".dev/features/readme-adoption-rewrite/PLAN.md:162" + problem: "S8 requires citing one defect the loop caught in PHARN's own code, cited to the REVIEW.md that caught it, but the plan names no file — leaving the build to pick from 146 increment directories with no criterion, which is an unbounded model choice inside an otherwise pinned step." + evidence: "line 162: '- **S8 PHARN builds PHARN** — short; the generated CURRENT-STATE block lives here, plus one defect the loop caught in PHARN's own code, cited to the `.dev/features/*/REVIEW.md` that caught it.'" +``` + +The plan pins every command line it prescribes (L22) and then leaves its single most persuasive claim unpinned. Suggested: name the exact `REVIEW.md` and finding id in the plan so the build quotes rather than selects. + +### Axis: one axis of change (P3, P7) + +```yaml +- type: FINDING + rule_id: "P3" + severity: minor + file: ".dev/features/readme-adoption-rewrite/PLAN.md:28" + problem: "The increment bundles a full structural rewrite of README.md with a one-sentence status realignment across four sibling files; these have different reasons to change and different risk profiles, and a build that reds on the rewrite drags the trivial edits with it." + evidence: "## Files at line 28 lists five paths whose descriptions range from 'full rewrite to the S1–S10 structure' to 'line 7's ... replaced with the Q3(a) status wording.'" +``` + +Raised, not pressed: the human explicitly chose the four-file scope at GATE 1 with L1's meta-doc argument on the table, and the setter resolves all five in one call, so the mechanical cost is low. Recorded so the coupling is a decision on the record rather than an omission. + +### Axis: product judgment (P0) + +```yaml +- type: FINDING + rule_id: "P0" + severity: minor + file: ".dev/features/readme-adoption-rewrite/PLAN.md:163" + problem: "S9 plans to state that an install ships two of the four trusted docs — a real packaging gap, but one that belongs to pharn-cli; surfacing it in the adoption README may read as documenting a defect rather than describing the product, and the plan does not say which framing it intends." + evidence: "line 163: '- **S9 Honest scope** — what has not shipped, labelled in the same sentence; links `LIMITS.md`; notes that an install ships two of the four trusted docs.'" +``` + +P7 favours stating the limit; product sense favours fixing the packaging instead of narrating it. Either is defensible — the plan should say which, since the README's credibility rests on S9 being read as candour rather than as an apology. + +## Summary + +The plan is unusually well-grounded: every claim in its source prompt was re-verified against live state, four were corrected, and three of its own hero sentences were struck rather than softened. The discovery is the strongest part and the guarantee audit is genuinely reduced rather than asserted. + +The concerns cluster in one place — **the plan applies its cited lessons to the README's content but not to the plan's own procedure**. It cites L19 and then prescribes a repo-wide generator write it fenced off two sections earlier (F1). It cites L2 and implements only its first half, leaving the audit ephemeral (F2). It cites L20/L24 against hardcoded numbers and then hardcodes an install tree that drifts the same way (F3). It cites L22's "pin the command line" and leaves its most load-bearing citation unpinned (F4). This is L29's shape exactly: a rule applied to part of its domain reads as discharged. + +None of these is a reason to stop. F1 and F4 are cheap to fix in the plan before building; F2 and F3 are judgment calls the human should make. + +ADVISORY VERDICT: 6 concerns raised (0 blocking-severity, 4 important, 2 minor) — for the human to weigh before /pharn-dev-build. This is not a pass, and it is not a gate. diff --git a/.dev/features/readme-adoption-rewrite/PLAN.md b/.dev/features/readme-adoption-rewrite/PLAN.md new file mode 100644 index 0000000..9cb6015 --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/PLAN.md @@ -0,0 +1,181 @@ +# PLAN — README adoption rewrite + +- spec_content_hash: 8f5ec002e3b18cbfd2f094b08a3671f7ed42a05a3fbaf01a11bbbd28da30fb52 # fix #4 +- applied_lessons: [L1, L2, L6, L7, L8, L12, L13, L18, L19, L20, L22, L24, L26, L28, L29, L30, L32] +- increment: Rewrite the root `README.md` (and align the three status files) so a developer arriving cold can say what PHARN does, install it, and run the first command — with no claim the repo contradicts. +- layer(s): repo-meta (no product-surface bytes; no `SKILLS_VERSION` bump) +- constitution_refs: [P0, P2, P4, P6, P7] + +## Applied lessons + +- L1 — The README rewrite invalidates the adoption-status sentence in three sibling meta-docs; the meta-doc sweep found them and `## Files` names all four, so `/pharn-dev-build` cannot ship stale canon. +- L2 — Every guarantee-shaped sentence the new README carries is reduced in `## Guarantee audit (P0)` against a floor op I read live this run, not one merely spec'd; two were demoted as a result. +- L6 — Capability counts come from the generated `CURRENT-STATE` block and test totals from a real `npm test` run, never grepped from CHANGELOG prose or a commit message. +- L7 — This stage declares exactly one output (`PLAN.md`). `README.md` and the three status files are `/pharn-dev-build`'s targets and are declared in `## Files` for the build, never in this command's `writes:`. +- L8 — Q4 chose four files, so I MEASURED the setter rather than assuming its resolution shape: `--from-plan` emitted `4 path(s)` in one call, so L8's one-`--target` limit binds placeholder narrowing, not `--from-plan`, and no per-write re-scoping is needed. +- L12 — Formatting happens at build time over the just-written files, not as a repair after `/pharn-dev-verify` reddens `format:check`. +- L13 — The same format discipline is pinned for this stage's own artifact (`PLAN.md`) and named for every later artifact-writing stage in this chain. +- L18 — The exclusion block below is a `###` heading inside `## Files`, never a bold prose intro, so the setter's authorized list terminates structurally rather than by matching vocabulary. +- L20 — L18's remedy is discipline-only and has already recurred once, so the build MUST read the setter's printed path count against the approved list; Step 2 pins the expected count per call. +- L22 — Every build hazard below is a pinned literal command line, never a prose description of a technique the build then implements its own way. +- L24 — The current README's "no installer" sentence is a claim inherited from a superseded state of the world; I re-measured it against the live registry and a real install rather than carrying it across. +- L26 — I verified the installer in a scratch directory OUTSIDE the repo, so that run proves what the CLI does and proves NOTHING about this repo's gates; the four files are judged only by `npm run check` at their real paths. +- L28 — Each `## Files` bullet is kept on ONE line under `printWidth: 140`, so prettier cannot wrap it into a continuation line whose ordinary vocabulary would trip the setter's exclusion cue. +- L29 — The adoption-status remedy ranges over a SET, so the deliverable is the enumeration: all six sites are listed by file and line in Step 1 below, not just the README ones. +- L30 — Step 2's gate list names only gates the build INVOKES as pinned command lines; no gate is named in prose for the agent to "confirm". +- L32 — I verified the canonical repo slug with `gh api repos/pharn-dev/pharn-oss --jq .full_name`, not the git remote, because a rename redirect keeps a stale slug resolving; `@latest` likewise proves reachability, not that any specific version is canonical. + +## Files + +- `README.md` — full rewrite to the S1–S10 structure; the generated CURRENT-STATE block moves intact into S8. — layer repo-meta +- `SECURITY.md` — line 7's "early-stage and in active development" replaced with the Q3(a) status wording. — layer repo-meta +- `CONTRIBUTING.md` — line 3's "early-stage and in active development" replaced with the Q3(a) status wording. — layer repo-meta +- `CLAUDE.md` — line 9's "early-stage and in active development" replaced with the Q3(a) status wording. — layer repo-meta +- `CHANGELOG.md` — an `[Unreleased]` entry recording the README rewrite and the status realignment, with no version bump. — layer repo-meta + +### Deliberately NOT in scope + +- `pharn/ARCHITECTURE.md` — hook-denied to the agent (fix #2); the pipeline spine is READ from it, never edited. +- `pharn/CONSTITUTION.md` — hook-denied to the agent (fix #2). +- `THREAT-MODEL.md` — hook-denied to the agent (fix #2). +- `LIMITS.md` — hook-denied to the agent (fix #2). +- `.claude/commands/pharn-ship.md` — writable, but the pipeline-shape reconciliation is a second axis (P3). +- `SKILLS_VERSION` — no product-surface byte changes, so it stays at 2.7.14 and `check:badge` stays GREEN. +- `docs/capabilities/` — never hand-edited; it IS rewritten by the `docs:generate` Bash call in hazard 3, declared there per L19 rather than silently escaping the gate. + +## Discovery report (P6) — every §2 item, verified live this run + +### Live state + +| Item | Verdict | Evidence read this run | +| ------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| HEAD | CONFIRMED | `71e71ee03c7e2a0ad1bbfee9daa4c8336addf615` | +| `SKILLS_VERSION` / badge | CONFIRMED | `2.7.14`; `check-version-badge.mjs` → GREEN, badge matches | +| Generated inventory | CONFIRMED | 36 capabilities (13 grillers, 22 lenses, 1 skill, 0 validators, 0 verifiers, 0 auditors); 6 contracts; 10 product commands; 9 dev commands; 3 hooks; 50 floor checkers | +| `npm test` | CONFIRMED | `tests 1620 · pass 1620 · fail 0 · skipped 0` | +| `validate.mjs` | CONFIRMED | `FLOOR: GREEN — 36 capabilities checked in "."` | +| Increment dirs | CONFIRMED | `ls -d .dev/features/*/ \| wc -l` → 146; `find .dev/features -name SHIP.md \| wc -l` → 109 | +| root `features/` | CONFIRMED | holds `README.md` only | + +### Claims A–F + +- **A — CORRECTED, and this is the increment's whole justification.** `@pharn-dev/pharn` is published; `npm view … version` → `0.3.2`. I ran `npx @pharn-dev/pharn@latest init` in a scratch git repo with a Next.js `package.json` and drove its confirm prompt through a pty to completion. It fetched from `github.com/pharn-dev/pharn-oss`, detected the `ssr` archetype, selected 35 of 36 capabilities with per-row archetype reasons, and landed `.claude/commands/` (10 product commands), `.claude/hooks/` (3), `.claude/settings.json`, `pharn/` (CONSTITUTION, ARCHITECTURE, floor with 50 checkers, contracts, pipeline, review) and `pharn.config.json` pinning `skillsVersion 2.7.14` and commit `71e71ee`. The README's "no installer", "not an adoptable release" and "Please do not adopt it yet" are therefore false against live state. +- **B — CORRECTED.** `.claude/hooks/protect-trusted-paths.cjs` states in its own HONEST BOUNDS: "Bash-tool writes bypass PreToolUse hooks ENTIRELY. That is by far the largest hole in this guard and no amount of path matching narrows it." So "denies **any** agent edit" is not defensible. Two further corrections the current README omits: the hook protects more than the four docs (CODEOWNERS, both settings files, the three hook scripts, `.pharn/writes-scope.json`), and PHARN vendored at a subpath of a larger project is not guarded at all. +- **C — CORRECTED.** The linked post is dated **March 14, 2026**, and it makes no coinage claim — it credits Margaret-Anne Storey's _cognitive debt_ and the MIT Media Lab work. "A term coined by Addy Osmani in early 2026" is unsupported by the source the sentence itself links. The rewrite cites the post as a description of the problem, never as an origin. +- **D — CORRECTED.** Verified at the Anthropic URL: 52 mostly-junior engineers, Python ≥1×/week for over a year, two features using the Trio async library, assessed by a quiz weighted to debugging, code reading and conceptual questions. AI-assisted averaged **50%**, hand-coding **67%** (Cohen's d = 0.738, p = 0.01). **Nothing was shipped** and nobody was maintaining production code, so "code they shipped" is false; and 67 → 50 is 17 **percentage points** (~25% relative), so "~17% lower" is at best ambiguous. The rewrite states the two scores and the population, or drops the citation. +- **E — MIXED; all three legs resolved below in `## Guarantee audit (P0)`.** Leg 1 names the wrong stage and implies a gate that does not exist; leg 2's "every write" is false for Bash; leg 3 is sound and is the strongest of the three. +- **F — CONFIRMED.** `pharn/` holds only `pharn-contracts`, `pharn-core`, `pharn-pipeline`, `pharn-review`; `pharn-core/` contains `seam-resolver` alone. So `pharn-audits`, `pharn-skills-*`, `pharn-stack-*` and the rest of `pharn-core` are correctly listed as still planned. + +### Pipeline shape (§2.3) — CONFIRMED + +`.claude/commands/pharn-ship.md:194` states verbatim: "There is **no product `/review` stage** (the dev loop's `/pharn-dev-review` is not a §6 spine stage — lenses live in `pharn-review`, §4)". Canonical architecture wins; per Q1 the README shows the spine without `review` and gives `/pharn-review` its own standalone block. The installer's own summary and the shipped command set confirm the first command a new user runs is `/pharn-spec`. + +### Findings this increment's prompt did not anticipate + +1. **A user's install ships two of the four trusted docs.** `THREAT-MODEL.md` and `LIMITS.md` are absent from the installed tree; only `pharn/CONSTITUTION.md` and `pharn/ARCHITECTURE.md` land. This directly constrains the rewrite: S9 may link `LIMITS.md` on GitHub but must not tell an installed user to read a file they do not have, and the phrase "these four are trusted and human-only" is true of this repository, not of an install. +2. **The prompt's §1 is textually corrupted** ("installemmands/`"). Its substance was re-derived by running the installer rather than adapted to (L24). +3. **`/pharn-dev-verify` passes no `--complete`** (`grep -c` → 0), so the build-completeness guarantee is product-surface only. The README must not present it as covering PHARN's own dev loop. + +## Guarantee audit (P0) + +Every guarantee-shaped sentence the new README will carry, each reduced to a floor op read live this run or relabelled. + +| Sentence the README will carry | Reduction | +| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| The four trusted docs cannot be edited by the agent through Write/Edit/MultiEdit | **floor: hook** — `protect-trusted-paths.cjs`, exact repo-relative membership. Stated WITH its bound: Bash-tool writes bypass `PreToolUse` entirely. Claim B is fixed here. | +| "denies **any** agent edit" | **STRUCK** — not defensible; the hook's own bounds contradict it. Replaced by the bounded sentence above. | +| A command may write only the paths it declares in `writes:` | **floor: hook** — `set-writes-scope.cjs` + `enforce-writes-scope.cjs` (fix #7), fail-closed. Bounded: gates `Write\|Edit\|MultiEdit` only (L19). | +| "**every** write confined to its declared scope" | **STRUCK** — false for Bash-invoked tooling. Replaced by the bounded sentence above. Claim E leg 2. | +| A deterministic scanner flags secret-shaped literals in a plan | **floor: enum-regex** — `scan-plan-secrets.mjs`, fixed regex set, injection-immune by construction. | +| "secrets screened at the **plan gate**" | **STRUCK** — two overclaims. It runs at **grill**, not plan; and grillers never gate, so nothing is "screened" in the gating sense. The griller's own text: "the guarantee is 'the scanner IS deterministic', not 'the model always ran it'". Claim E leg 1. | +| Every concrete path a plan declared must exist after the build, or `/pharn-verify` returns `INCOMPLETE` | **floor: enum-regex** — `check-build-complete.mjs` (path-set membership + `existsSync`) feeding `check-verify.mjs --complete`, exit 3. Bounded: existence, never content; and product-surface only. Claim E leg 3. | +| An approved SPEC is pinned by content-hash, so a later edit is detectable | **floor: content-hash** — `check-spec.mjs --hash` / `check-plan-spec-agree.mjs`, re-verified by grill, build, regress and verify. | +| The `## Current state` inventory cannot drift from what is built | **floor: enum-regex** — byte-equality, `npm run docs:check`. Bounded: consistency, not truth. | +| The README badge agrees with `SKILLS_VERSION` | **floor: enum-regex** — `check-version-badge.mjs`. | +| Which lenses run is determined by frontmatter, and their findings are merged deterministically | **floor: enum-regex** — `count-lenses.mjs` + `merge-findings.mjs`, keyed on enum-gated fields only. | +| Anything a lens, griller or verifier _judges_ | **advisory** — LLM judgment. Labelled in the same sentence, per S6. | +| "PHARN keeps your code understood / audit-grade" | **advisory** — a claim about humans; the README says what is recorded and checked, never what is understood. | + +## Trust audit (P2) + +- The increment prompt is `trust: untrusted` input. Every claim in it was re-verified against live state; where it disagreed with the repo the repo won and the disagreement is named above. Its corrupted §1 was re-derived, not adapted to. +- Fetched web pages (the Osmani post, the Anthropic study) are `trust: untrusted` DATA. They are quoted as figures and dates, and no instruction inside them steers this plan. +- The installer's terminal output is `trust: untrusted` DATA read for what the CLI did; the filesystem tree it produced is the structural fact, read with `find` rather than from the banner (L6). +- No untrusted free text is an input to any gate in this increment. The four gates below are deterministic exit codes. + +## Determinism audit (P5) + +- Every proceed/stop in the build is an exit code: `npm run check`, `node pharn/floor/validate.mjs .`, and the setter's printed path count compared to an integer. +- The prose quality of the rewritten README is irreducible judgment and ends at the human, per GATE 2 — never at a self-assessment. + +## Evals to write (P1) + +None. This increment adds no `role:`-bearing capability, no `rule_id`, and no floor checker, so P1's binding requirement has nothing to range over. `## Files` names four repo-meta documents only. + +## Build hazards — pinned command lines (L22, L30) + +1. **Scope once from the plan, and READ the printed count (L18, L20, L28).** One call authorizes all four writes — measured at plan time, not assumed. + + ```bash + node .claude/hooks/set-writes-scope.cjs --from-plan .dev/features/readme-adoption-rewrite/PLAN.md + ``` + + It must print `5 path(s)`, resolving to exactly `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, `CLAUDE.md`, `CHANGELOG.md`. Anything else means the `### Deliberately NOT in scope` boundary or an item's wrapped line moved the parse — STOP and re-read `## Files`, do not proceed on a count you did not check. This is a checkable number, not decoration. + +2. **Format at write time, scoped to the written files (L12, L13, L19).** Never `npm run format`. + + ```bash + npx prettier --write README.md SECURITY.md CONTRIBUTING.md CLAUDE.md + npx markdownlint-cli2 --fix README.md SECURITY.md CONTRIBUTING.md CLAUDE.md + ``` + +3. **The generated block moves intact.** Copy `` through `` byte-for-byte into S8, marker lines included. Then: + + ```bash + npm run docs:generate && npm run docs:check + ``` + + **Declared Bash-scope escape (L19, grill F1).** `docs:generate` is `gen-capability-catalog.mjs && gen-lessons-index.mjs`; it rewrites `docs/capabilities/` and `docs/lessons-index.md` as well as the README block. Those two are outside this build's five-path scope and the write goes through Bash, so fix #7 never sees it. This is declared, not pretended-covered: both are generated artifacts whose only correct content is the generator's output, and `docs:check` re-verifies them byte-for-byte immediately after. + +4. **Do not touch the badge value.** `check:badge` binds `img.shields.io/badge/pharn-2.7.14-` to `SKILLS_VERSION`; restyling the hero must leave that string intact. + +5. **Both gates, verdicts reported verbatim.** + + ```bash + npm run check + node pharn/floor/validate.mjs . + ``` + + A GREEN gate means the shape is sound. It is never evidence the copy is good. + +6. **GATE 2 is where the prose is judged.** No floor check can evaluate a README's copy. The full rendered file is presented for human reading after `/pharn-dev-review`, and merge / fix / abandon is the human's call. + +## README structure to build (S1–S10) + +Order is product → usage → proof → mechanism → philosophy. + +- **S1 Hero** — name, hook line, 2–3 sentences of what PHARN does, `npx @pharn-dev/pharn@latest init`, badges unchanged. +- **S2 What PHARN does** — Before / While / After coding, as concrete outcomes in the reader's repo, no internal vocabulary. +- **S3 Quick start** — install, then `/pharn-spec`, then what lands in the repo described BY KIND (product commands, write-gating hooks, the floor, the contracts, a pinned `pharn.config.json`), never as an enumerated tree: the CLI fetches the repo's HEAD, so a path listing would drift with every commit and no checker owns it (grill F3). +- **S4 Built-in capabilities** — grillers and lenses named by the PROBLEM they catch; link `docs/capabilities/`. +- **S5 Why not just CLAUDE.md / AGENTS.md?** — what PHARN adds as mechanism; never "a plain file can't". +- **S6 Guaranteed vs advisory** — stated once, with the real checks named from the guarantee audit above. +- **S7 The pipeline** — the spine without `review`; `/pharn-ship` as orchestrator, `/pharn-loop` as the bounded floor-gated iteration, and per Q1 a separate standalone block for `/pharn-review`. +- **S8 PHARN builds PHARN** — short; the generated CURRENT-STATE block lives here, plus ONE pinned defect (grill F4, no build-time pick): `.dev/features/span-redos-linear/REVIEW.md` F1, where the review caught a blocking P0 overclaim inside the fix for a P0 overclaim — a false performance bound re-asserted in `pharn/floor/scan-code-ssrf.mjs`. Cite the file and quote it; never paraphrase it into a stronger claim. +- **S9 Honest scope** — what has not shipped, labelled in the same sentence; links `LIMITS.md`; notes that an install ships two of the four trusted docs. +- **S10 Design docs · Contributing · Security · License** — per Q2, one line pointing at `pharn/CONSTITUTION.md` replaces the eight-row P0–P7 table. + +Status wording, per Q3(a), used verbatim in all four files: _"Ready to install and use with Claude Code today. Active development continues; functionality that has not shipped yet is explicitly labeled."_ + +Writing rules the build must hold: every factual claim survives a sceptic with the repo open in another tab; no number a generator or a test run already owns is hardcoded; nothing is called a guarantee unless the check behind it can be named; no superlatives; no named-competitor comparisons; anything unshipped is labelled in the same sentence. + +## Open questions (HALT) + +**None outstanding.** Q1–Q4 were asked and answered at Step 4; the one remaining question (`CHANGELOG.md`) was put to the human at GATE 1 and answered "include". Nothing below blocks `/pharn-dev-build`; the items that follow are resolutions and reported residuals, recorded here so the decisions are on the record rather than implicit. + +## Decisions and reported residuals + +1. **`CHANGELOG.md` entry — RESOLVED at GATE 1: include.** Repo-meta triggers no `SKILLS_VERSION` bump, so the entry lands under `[Unreleased]` with the version left at 2.7.14, honouring the file's own "all notable changes are documented" contract (L1) without inventing a bump the CLAUDE.md rule does not authorize. +2. **Hardcoded numbers (L20, L24).** The rewrite hardcodes NO count — capability totals come from the generated block and test totals are not quoted at all. The one unavoidable literal is the install command's package name. Per L32 it stays `@pharn-dev/pharn@latest`, a mutable alias: correct for a README, and it means the README never pins `0.3.2`. +3. **Reported, not fixed — out of axis.** The `pharn-cli` README's "What it installs" table is wrong on two rows (my install created no `.dev/` and put the constitution at `pharn/CONSTITUTION.md`); its pipeline line includes `review`; `pharn-cli`'s `package.json` is ahead of the published `0.3.2`; `pharn-oss` has **0 git tags and 0 GitHub releases** (verified via `gh api`); the installer writes a vestigial `"modules": []`; the installer's capability table breaks its column padding on longer names (reproduced live: `unsafe-deserialization (lens)ssr`); and the repo's topic list carries a malformed slug **`agentic-skill-`** (verified via `gh api`, not rate-limited from here — this corrects the prompt's UNVERIFIABLE). +4. **The two missing trusted docs.** Whether `THREAT-MODEL.md` and `LIMITS.md` should ship with an install is a `pharn-cli` decision, not a README one. Flagged for a human; the README will describe only what an install actually contains. diff --git a/.dev/features/readme-adoption-rewrite/REGRESSION.md b/.dev/features/readme-adoption-rewrite/REGRESSION.md new file mode 100644 index 0000000..5bcdee3 --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/REGRESSION.md @@ -0,0 +1,41 @@ +# REGRESSION — readme-adoption-rewrite + +**Verdict: `no-regressions`** — the deterministic exit-code comparison in `pharn/floor/check-regress.mjs verdict`, read verbatim from `regression-report.json`. Exit 0. + +## Base resolution + +`git status --porcelain` was non-empty (a working-tree dogfood build), so `base = HEAD` = `71e71ee03c7e2a0ad1bbfee9daa4c8336addf615`. The baseline suite ran in a detached `git worktree` at that commit — a real checkout of the repo, so every config-driven gate resolves its configuration by the same path rules the repo enforces (`.dev/memory-bank/lessons-learned.md` **L26**, cited not restated). + +## Scope partition (FLOOR — `check-regress.mjs scope`, exit 0) + +No changed path fell outside the plan's declared `## Files`. The helper computed the partition; it was not filtered by hand (**L17**, **L20**). + +- **inside** — `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, `CLAUDE.md`, `CHANGELOG.md` +- **escape_exempt** (reported, never silently dropped) — `.dev/features/readme-adoption-rewrite/PLAN.md`, `.dev/features/readme-adoption-rewrite/GRILL.md`, each written by its own stage under that stage's own Step-0 writes-scope +- **outside test universe** — 70 test files + +## Gates, base → head + +Every gate in `package.json`'s `scripts.check` chain, run at both points. Nothing flipped. + +| Gate | base | head | flip | +| -------------------- | ---- | ---- | ---- | +| `format:check` | 0 | 0 | — | +| `lint` | 0 | 0 | — | +| `lint:md` | 0 | 0 | — | +| `docs:check` | 0 | 0 | — | +| `check:markers` | 0 | 0 | — | +| `check:badge` | 0 | 0 | — | +| `check:contributing` | 0 | 0 | — | +| `test` | 0 | 0 | — | + +`regressions: []` · `pre_existing: []`. + +Two gates deserve a note because this increment could plausibly have broken them and did not: + +- **`check:badge`** — the hero was restyled around the badge block. The badge value `pharn-2.7.14` was left untouched and is present exactly once, so the checker's ambiguity refusal (>1 badge is an AMBIGUOUS-RED, never first-match-wins) was never approached. +- **`docs:check`** — the generated `CURRENT-STATE` block was relocated within the rewritten README. It was moved **byte-exact**, verified independently by `md5` before and after the move (`d663e4eefcf776cd1f6a266a3effe760` both times) and again after the formatter ran. Because the bytes never changed, `npm run docs:generate` was **not** needed, so the declared Bash-scope escape the plan authorized for it (grill F1) never fired. + +## Honest scope (P0) + +This stage catches **exactly what its suite catches** — a pass→fail flip in one of the eight gates above. A regression no deterministic check covers is invisible to it. `no-regressions` therefore means "no covered gate outside the feature flipped", **never** "nothing broke". For a documentation increment the residual is unusually wide: no gate in this repo reads a README's prose for truth, so every claim in the rewritten text rests on the discovery in `PLAN.md` and on human reading at GATE 2. diff --git a/.dev/features/readme-adoption-rewrite/REVIEW.md b/.dev/features/readme-adoption-rewrite/REVIEW.md new file mode 100644 index 0000000..dbb2293 --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/REVIEW.md @@ -0,0 +1,84 @@ +# REVIEW — readme-adoption-rewrite + +Increment reviewed as `trust: untrusted` (the standard posture — trusted `/pharn-dev-build` produced it, and it is still reviewed as data). Files: `README.md`, `SECURITY.md`, `CONTRIBUTING.md`, `CLAUDE.md`, `CHANGELOG.md`. + +**Floor first:** `node pharn/floor/validate.mjs .` → `FLOOR: GREEN — 36 capabilities checked in "."`. All nine `/pharn-dev-verify` gates exit 0 (`verify-report.json`, verdict `PASS`); regress verdict `no-regressions`. The floor-gate layer is clean. Everything below is **ADVISORY** — four lenses, each citing a principle. Severity is an LLM assignment (fix #3) and gates nothing. + +## Lens 1 — Guarantee honesty (P0) + +```yaml +- type: FINDING + rule_id: "P0" + severity: blocking + file: "README.md:66" + problem: "The rewrite hardcoded 'Thirteen grillers' and 'Twenty-two review lenses' into unguarded README prose while the generated CURRENT-STATE block owns those exact numbers — the drift shape the plan's own writing rule forbids, reintroduced by the build that wrote the rule." + evidence: "line 66: 'Thirteen grillers interrogate the plan before a line is written'; line 68: 'Twenty-two review lenses read the resulting diff'. The guarded block asserts '**13** grillers, **22** lenses' four sections below." +``` + +**Status: FIXED inside this increment.** The counts were removed and the section now points at the two drift-guarded lists as authoritative, labelling its own prose a tour rather than an inventory. Re-verified after the fix: no `Thirteen` / `Twenty-two` remains, the block's md5 is unchanged (`d663e4eefcf776cd1f6a266a3effe760`), and all gates re-ran green. + +This is the sharpest thing the increment produced, and it deserves recording rather than quiet repair. The plan states the rule explicitly — "no number a generator or a test run already owns is hardcoded" — cites **L20** and **L24** in `applied_lessons`, and the very next stage violated it. `check-plan-lessons.mjs` returned GREEN before and after, exactly as it is documented to: it verifies the **declaration**, never the **application**. That is [[L20]]'s thesis reproduced live — a lesson whose only remedy is discipline recurs — and it lands in the one region the repo openly leaves unguarded: README prose outside the markers, which the README itself now says carries no guarantee. + +```yaml +- type: FINDING + rule_id: "P0" + severity: minor + file: "README.md:152" + problem: "The Guaranteed-vs-advisory table names nine checkers as the reductions behind six guarantees; a table of this shape is exactly where a cited-but-dead floor op would hide, and nothing in the build verified that each named file exists." + evidence: "Rows cite protect-trusted-paths.cjs, set-writes-scope.cjs, enforce-writes-scope.cjs, check-spec.mjs --hash, scan-plan-secrets.mjs, check-build-complete.mjs, check-verify.mjs, count-lenses.mjs, merge-findings.mjs." +``` + +**Status: CHECKED, no defect.** All nine were confirmed present on disk at review time, per **L2** ("a contract may cite only live floor ops, verified by reading the implementation this run"). Recorded because the check was performed, not because it failed — the absence of a finding here is only meaningful if the check is named. + +## Lens 2 — Trust and untrusted input (P2) + +No finding. The increment ingested three untrusted sources — the increment prompt, two fetched web pages, and the installer's terminal output — and none reached a gate. The structural facts were taken from structured locations rather than from any of them: the install tree from `find`, the capability membership from `count-grillers.mjs` / `count-lenses.mjs`, the version from `SKILLS_VERSION`, the canonical repo slug from `gh api ... --jq .full_name` rather than the git remote ([[L32]] — the remote is a mutable alias that survives a rename). + +Worth noting as a **strength** rather than a finding: the prompt driving this increment was internally corrupted (a truncated sentence in its §1) and asserted a claim about the writes-scope setter that is false (`set-writes-scope.cjs --from-plan` resolves all declared paths in one call; the prompt asserted one `--target` per write was required). Both were caught by measuring rather than adapting, and both are named in `PLAN.md`. Untrusted input was treated as data throughout. + +## Lens 3 — Single axis and citation discipline (P3, P4) + +```yaml +- type: FINDING + rule_id: "P3" + severity: minor + file: ".dev/features/readme-adoption-rewrite/PLAN.md:28" + problem: "Five files changed for two different reasons — a full structural rewrite of README.md, and a one-sentence status realignment in four siblings — so a red on either half would have dragged the other with it." + evidence: '## Files lists paths whose descriptions range from ''full rewrite to the S1–S10 structure'' to "line 7''s ... replaced with the Q3(a) status wording".' +``` + +Raised at grill as F5 and carried forward unchanged. The coupling was put to the human at GATE 1 with [[L1]]'s meta-doc argument stated, and chosen deliberately; the mechanical cost proved low (one setter call, five paths). Recorded so the decision stays on the record rather than reading as an oversight. + +On P4: the README cites rather than restates throughout — it names checkers and links the four design documents instead of paraphrasing their content, and the one dogfooding claim in S8 quotes `.dev/features/span-redos-linear/REVIEW.md` verbatim instead of summarising it into something stronger. No finding. + +## Lens 4 — Honest scope (P6, P7) + +```yaml +- type: FINDING + rule_id: "P7" + severity: important + file: "README.md:249" + problem: "The README now tells a reader that two of the four design documents are not copied into an install. That is true and worth stating, but it documents a packaging defect in pharn-cli from inside the product README, and no follow-up exists to actually fix it — so the honest disclosure may become a permanent substitute for the repair." + evidence: "'Two of the four design documents (`THREAT-MODEL.md` and `LIMITS.md`) are not currently copied into an install — read them here.'" +``` + +Verified live during discovery: a completed `npx @pharn-dev/pharn@latest init` landed `pharn/CONSTITUTION.md` and `pharn/ARCHITECTURE.md` and neither root document. Stating it is correct under P7. The finding is that stating it is not sufficient — the repair belongs in `pharn-cli`, which is a different repository and outside this increment's axis. **For the human at GATE 2:** decide whether to open that as work, or accept the disclosure as the standing answer. + +```yaml +- type: FINDING + rule_id: "P6" + severity: minor + file: "README.md:88" + problem: "S3 describes what the installer lands by kind rather than by path, which is drift-resistant, but the description was still derived from a single observed run of a CLI that fetches the repository's current HEAD — so it is accurate today and owned by no checker." + evidence: "'The installer reads your `package.json`, detects your project's archetype, and selects the capabilities that apply ... It then installs: the product commands ... the write-gating hooks ... the floor ... and a `pharn.config.json`.'" +``` + +This was grill F3, and the fix (describe by kind, never as an enumerated tree) is the reason the severity is minor rather than important: kinds change far more slowly than paths. The residual is real and unowned, and is the same class as the finding in Lens 1 — README prose is not guarded, by design. + +## Summary + +The increment did what it set out to do. The README no longer contradicts the repository: the installer claim was corrected against a live install rather than an inference, three hero sentences that could not be reduced to a floor operation were struck rather than softened, and two citations that the linked sources do not support were dropped. Every checker the new text names as a guarantee was confirmed live, and every guarantee is stated with its bound in the same breath — including the largest one, that Bash-tool writes bypass the write-guards entirely. + +The one real defect was self-inflicted and instructive: the build hardcoded two counts the generated block already owns, in the same increment whose plan forbade exactly that and cited the two lessons about it. It is fixed, and it is recorded here rather than smoothed over, because it is a cleaner instance of [[L20]] than the one that promoted L20 — the rule was written, cited, declared, floor-checked as declared, and then broken one stage later, in the only part of the file no checker reads. + +**Advisory verdict: 5 findings (1 blocking-severity, fixed inside the increment; 1 important; 3 minor). Floor GREEN, verify PASS, regress no-regressions.** This is not a judgment that the increment is good — the README is prose, and no check in this repo evaluates prose. That judgment is the human's at GATE 2. diff --git a/.dev/features/readme-adoption-rewrite/SHIP.md b/.dev/features/readme-adoption-rewrite/SHIP.md new file mode 100644 index 0000000..357b5c1 --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/SHIP.md @@ -0,0 +1,38 @@ +# SHIP — readme-adoption-rewrite + +Gated `/pharn-dev-ship` run (no `--loop`). Base `71e71ee03c7e2a0ad1bbfee9daa4c8336addf615`, working-tree dogfood. + +## Stages that ran, in order + +| # | Stage | Outcome | +| --- | -------------------- | --------------------------------------------------------------------------------------------------------- | +| 1 | `/pharn-dev-plan` | `PLAN.md` written; `check-plan-lessons.mjs` exit 0 → **GATE 1**, human approved with `CHANGELOG.md` added | +| 2 | `/pharn-dev-grill` | `GRILL.md` written; 6 advisory concerns — gates nothing, proceeded | +| 3 | `/pharn-dev-build` | 5 files written; floor re-run | +| 4 | `/pharn-dev-regress` | `regression-report.json` + `REGRESSION.md` | +| 5 | `/pharn-dev-verify` | `verify-report.json` + `VERIFY.md` | +| 6 | `/pharn-dev-review` | `REVIEW.md`; 5 findings, 1 fixed inside the increment | + +**The run ended at GATE 2.** No stage returned a non-GREEN verdict, so there was no RED-verdict STOP. + +## Structural verdicts read, verbatim + +- **`/pharn-dev-build`** → `node pharn/floor/validate.mjs .` exit **0**, printing `FLOOR: GREEN — 36 capabilities checked in "."` +- **`/pharn-dev-regress`** → `regression-report.json` `.verdict` = **`no-regressions`**; `regressions: []`, `pre_existing: []`. The `check-regress.mjs scope` partition exited **0** — no changed path escaped the plan's `## Files`, with `.dev/features/readme-adoption-rewrite/{PLAN,GRILL}.md` reported in `escape_exempt` rather than silently dropped. +- **`/pharn-dev-verify`** → `verify-report.json` `.verdict` = **`PASS`**; `failing_gates: []` across nine gates (`format:check`, `lint`, `lint:md`, `docs:check`, `check:markers`, `check:badge`, `check:contributing`, `test`, `validate`). + +Recorded as information, deliberately **not** a verdict input: `check-build-complete.mjs` returned `complete` over all five declared paths. `/pharn-dev-verify` passes no `--complete` flag, so `INCOMPLETE` was not reachable at this stage. + +## Pointers (cited, not restated — P4) + +- Advisory grill-log: [`GRILL.md`](./GRILL.md) — 6 concerns (0 blocking, 4 important, 2 minor). +- Advisory review: [`REVIEW.md`](./REVIEW.md) — 5 findings (1 blocking-severity, **fixed inside this increment**; 1 important; 3 minor). +- Regress detail: [`REGRESSION.md`](./REGRESSION.md). Verify detail: [`VERIFY.md`](./VERIFY.md). Approved intent: [`PLAN.md`](./PLAN.md). + +The one blocking-severity finding is worth the human's attention even though it is closed: the build hardcoded two capability counts into unguarded README prose that the generated block already owns — in the increment whose own plan forbade exactly that and cited the two lessons about it. It was caught at review, fixed, and re-verified. Read `REVIEW.md` Lens 1 rather than this summary. + +## Standing decision + +`/pharn-dev-ship` performed **zero** git operations — no branch, no add, no commit, no push, no tag, no release. It applied no `PHARN ✓ reviewed` seal. + +The 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. diff --git a/.dev/features/readme-adoption-rewrite/VERIFY.md b/.dev/features/readme-adoption-rewrite/VERIFY.md new file mode 100644 index 0000000..e7f785d --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/VERIFY.md @@ -0,0 +1,35 @@ +# VERIFY — readme-adoption-rewrite + +**Verdict: `PASS`** — every floor gate exited 0. Read verbatim from `verify-report.json`, computed by `pharn/floor/check-verify.mjs` (exit 0), which reads gate exit codes and nothing else. + +## Floor layer — the gates that OWN the verdict + +| Gate | Exit | +| -------------------- | ---- | +| `format:check` | 0 | +| `lint` | 0 | +| `lint:md` | 0 | +| `docs:check` | 0 | +| `check:markers` | 0 | +| `check:badge` | 0 | +| `check:contributing` | 0 | +| `test` | 0 | +| `validate` | 0 | + +`failing_gates: []`. `node pharn/floor/validate.mjs .` printed `FLOOR: GREEN — 36 capabilities checked in "."`. + +No `structural:` gate ran: this increment ships no eval pair, because it authored no `role:`-bearing capability and introduced no `rule_id`. P1 binds Capabilities, and none was created — so there is nothing for it to have bound. + +## Build completeness — computed, and deliberately NOT an input + +`pharn/floor/check-build-complete.mjs` returned `{"complete": true, "missing": [], "verdict": "complete"}` over the plan's five declared `## Files` paths. + +It is recorded as information only. **`/pharn-dev-verify` passes no `--complete` flag**, so this stage runs `check-verify.mjs`'s legacy three-valued behaviour and an `INCOMPLETE` verdict is not reachable here — that retryable verdict belongs to the product `/pharn-verify`. Verified live rather than assumed: `grep -c -- --complete .claude/commands/pharn-dev-verify.md` → 0. Stated because the rewritten README now cites that same checker as a guarantee, and the honest scope of the citation is product-surface, not this dev loop. + +## Advisory layer — verifiers + +Zero `role: verifier` capabilities are registered (P7 — none authored). The plug-in slot exists; nothing fills it. Even if one did, a verifier finding **annotates and never flips the verdict** (fix #3) — the helper's only inputs are the gate exit-code map and the feature name, so a verifier finding cannot reach it structurally. + +## Honest scope (P0) + +`PASS` means **exactly** "the named gates passed". It does not mean the increment is correct, and for this increment the gap is the whole point: **not one of these nine gates reads English prose for truth.** They check that markdown is formatted, that the badge matches `SKILLS_VERSION`, that the generated block is byte-identical to a recompute, that `CONTRIBUTING.md` names every gate in the check chain, and that 1620 tests pass. Whether the rewritten README's claims are _true_ is established by the live-state discovery recorded in `PLAN.md` and by a human reading the file at GATE 2 — never by this verdict. diff --git a/.dev/features/readme-adoption-rewrite/regression-report.json b/.dev/features/readme-adoption-rewrite/regression-report.json new file mode 100644 index 0000000..0a50dfe --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/regression-report.json @@ -0,0 +1,30 @@ +{ + "feature": "readme-adoption-rewrite", + "base": "HEAD", + "base_sha": "71e71ee03c7e2a0ad1bbfee9daa4c8336addf615", + "inside": [ + "README.md", + "SECURITY.md", + "CONTRIBUTING.md", + "CLAUDE.md", + "CHANGELOG.md" + ], + "escape_exempt": [ + ".dev/features/readme-adoption-rewrite/GRILL.md", + ".dev/features/readme-adoption-rewrite/PLAN.md" + ], + "scope_exit": 0, + "outside_gates": { + "check:badge": { "base": 0, "head": 0 }, + "check:contributing": { "base": 0, "head": 0 }, + "check:markers": { "base": 0, "head": 0 }, + "docs:check": { "base": 0, "head": 0 }, + "format:check": { "base": 0, "head": 0 }, + "lint": { "base": 0, "head": 0 }, + "lint:md": { "base": 0, "head": 0 }, + "test": { "base": 0, "head": 0 } + }, + "regressions": [], + "pre_existing": [], + "verdict": "no-regressions" +} diff --git a/.dev/features/readme-adoption-rewrite/verify-report.json b/.dev/features/readme-adoption-rewrite/verify-report.json new file mode 100644 index 0000000..395003e --- /dev/null +++ b/.dev/features/readme-adoption-rewrite/verify-report.json @@ -0,0 +1,35 @@ +{ + "feature": "readme-adoption-rewrite", + "gates": { + "check:badge": 0, + "check:contributing": 0, + "check:markers": 0, + "docs:check": 0, + "format:check": 0, + "lint": 0, + "lint:md": 0, + "test": 0, + "validate": 0 + }, + "verdict": "PASS", + "failing_gates": [], + "completeness": { + "declared": [ + "README.md", + "SECURITY.md", + "CONTRIBUTING.md", + "CLAUDE.md", + "CHANGELOG.md" + ], + "skipped": [], + "missing": [], + "complete": true, + "verdict": "complete", + "note": "Computed with pharn/floor/check-build-complete.mjs and reported as information only. /pharn-dev-verify passes no --complete flag, so this value was NOT an input to the verdict above." + }, + "verifiers": { + "registered": 0, + "findings": [], + "note": "Zero role: verifier capabilities exist (P7 - none authored). Floor gates only. A verifier finding could never flip the verdict in any case (fix #3)." + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 427dad7..b4ecbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -999,6 +999,7 @@ floor-verified; ...)` paragraph — the _only_ generated prose in the artifact, ### Changed +- **Rewrote the root `README.md` for adoption, and realigned the stated adoption status across `SECURITY.md`, `CONTRIBUTING.md` and `CLAUDE.md`.** No `SKILLS_VERSION` bump: this changes repo-meta documents only and alters no product-surface bytes. The README described a repository with no installer and closed with "Please do not adopt it yet", while `@pharn-dev/pharn` was published and working. Verified by running it rather than inferring: `npx @pharn-dev/pharn@latest init` in a scratch repo detected the `ssr` archetype, listed the applicable capabilities with a reason beside each, and installed the product commands, the write-gating hooks, the floor, the contracts and a `pharn.config.json` pinning the source commit. The communication layer was behind the product; this closes that gap. **Four claims were corrected rather than restyled**, each against live state: (1) "a `PreToolUse` write-guard hook **denies any agent edit**" — not defensible, since the hook's own documented bounds state that Bash-tool writes bypass `PreToolUse` entirely, so the README now states the guard and its bound in the same breath; (2) "secrets screened at the **plan gate**" — `scan-plan-secrets.mjs` is genuine floor, but it runs at **grill**, and grillers never gate, so it is restated as detection that surfaces rather than a gate; (3) "**every** write confined to its declared scope" — fix #7 gates `Write|Edit|MultiEdit` only, restated with that surface named; (4) the comprehension-debt and AI-comprehension-study citations — the linked post makes no coinage claim and credits prior work, and the study measured a lab exercise in which nothing was shipped (50% vs 67% on a quiz), so "~17% lower on code they shipped" was wrong in both halves; the coinage attribution is dropped and the study is no longer cited as a headline number. The third hero guarantee survived intact and is now named with its checker: a plan-declared file the build never wrote yields `INCOMPLETE`, via `check-build-complete.mjs` feeding `check-verify.mjs`. Recorded honestly (P0): that the new prose is _accurate_ is advisory — no floor op reads a README's claims; the guarantee is only that `check:badge`, `docs:check` and the floor stayed GREEN across the rewrite. - **Deferred `product-capability-catalog` — the capability catalog stays dev-apparatus, and the decision is now on record** ([`CLAUDE.md`](./CLAUDE.md), and in full in [`.dev/features/product-capability-catalog/PLAN.md`](./.dev/features/product-capability-catalog/PLAN.md)). The third and last of the three dev→product ports — after `product-memory-promote` (#117) and `product-lessons-index` (#118) — was **planned and declined at its P7 gate**, so nothing was ported: `capability-catalog-core.mjs` and its generator + drift checker remain under `.dev/floor/`, and no equivalent ships under `pharn/floor/`. **This entry IS the increment** — a deliberate, reasoned "no" recorded durably, rather than a silent non-decision that the next contributor would have to re-derive. The gate question was _"do PHARN users author their own `role:`-bearing capabilities?"_, it was put to the human explicitly at the plan halt, and the answer was **defer**. **Five pieces of live evidence, each read this run (P6):** (1) the population is **zero, not small** — `README.md` states there is _"no installer, no versioned release you can drop into your own repo"_ and _"Please do not adopt it yet"_, so no installed user exists who could author a capability; (2) the product surface **already takes this exact posture for the adjacent case** — `/pharn-verify` ships _"The verifier plug-in slot (defined here; ZERO verifiers authored — P7)"_ and defers its live runner until _"the first verifier lands"_, so shipping a **catalog** of user-authored capabilities while deliberately deferring the **runner** for those same capabilities would be internally inconsistent; (3) **nothing promises it** — `product-capability-catalog` was named as a follow-up nowhere in the repo, and unlike `product-memory-promote` (which closed a real `ARCHITECTURE §5` gap) no trusted doc claims a product catalog; (4) the **`product-lessons-index` precedent removes the catalog's only reader** — that port fixed product-derived output at the **gitignored, disposable `.pharn/` cache**, which is justified there because `/pharn-plan` **machine-reads** the index, whereas a capability catalog is human-readable prose with **no machine consumer**, so the consistent answer gives it no reader at all and the inconsistent answer (`docs/`) claims a directory PHARN does not own; (5) **the drift guard would have no invoker** — a user repo has no `npm run docs:check`, and an unreachable guarantee is an argument for deferring rather than a detail to settle later. **Reopens when** the first `role:`-bearing capability is authored outside PHARN's own shipped surface — a real event, the same trigger `/pharn-verify` already names, which is what P7 requires before this is planned again. **Honest scope (P0):** that the deferral is recorded is **advisory** — no floor op checks that a decision was written down, or that the written reasoning is the real reasoning; these are bytes a human reads. This increment adds **no** floor primitive, no capability, no `rule_id`, and no eval — P1 binds Capabilities, and none was created. **`SKILLS_VERSION` is NOT bumped:** no product-surface byte changed (the bump-triggering set is the `pharn/` tree, `pharn/floor/*.mjs`, the four trusted docs, and the `pharn-*` `.claude/` surface — all untouched); `CLAUDE.md` and `CHANGELOG.md` are repo-meta. Note that "no bump is required" is itself **advisory** — verified live, **no checker reads `SKILLS_VERSION`**; the bump rule is documented human discipline, not a floor primitive. - **The root `README.md` `## Current state` inventory is now GENERATED and drift-guarded, not hand-written** ([`.dev/floor/capability-catalog-core.mjs`](./.dev/floor/capability-catalog-core.mjs), [`.dev/floor/gen-capability-catalog.mjs`](./.dev/floor/gen-capability-catalog.mjs), [`.dev/floor/check-capability-catalog.mjs`](./.dev/floor/check-capability-catalog.mjs)). The hand-written section had drifted into stating two falsehoods: it listed `pharn-core` as "still **planned**" while `pharn/pharn-core/seam-resolver/` was built and cited two bullets earlier in the same section, and it said "three contracts" while `pharn/pharn-contracts/` held four — the disease of lesson L1 at README scale. The factual core now renders between one `` / `` marker pair from the live repository: capabilities per role (via the **same** `enumerateCapabilities()` the capability catalog uses, mirroring `pharn/floor/validate.mjs`'s `role:` frontmatter test), contracts, product vs `pharn-dev-` commands, hook scripts, and floor checkers — so both falsehoods disappear as a **consequence of generation**, not as separate edits. Roles with zero instances render as `0` rather than vanishing (the honest read is "the enum exists, instances don't"). One renderer serves both the generator and the checker, so recompute is byte-identical to generate by construction (P3); both marker lines sit **inside** the guarded region, so hand-editing a marker is itself drift. The generator splices strictly between an existing pair and **hard-errors** on a missing, duplicated, or inverted pair — it never invents a marker or guesses a boundary — and enumeration fails closed on a missing directory or a non-inert basename rather than rendering a plausible `0`. Wired into the existing `npm run docs:generate` / `npm run docs:check` pair with **no new npm script and no CI change** (the existing docs step covers it). The guarantee (P0) is narrow and stated in the checker's own header: **byte-equality** (committed block == recomputed block) plus marker-occurrence counting — it is **not** a guarantee that the content is true (a wrong enumerator would regenerate cleanly and stay GREEN), the capability count's agreement with `validate.mjs` is **advisory** (a mirrored implementation, not a shared one), and README prose **outside** the markers stays hand-written, advisory, and **unguarded**. **`SKILLS_VERSION` is NOT bumped**, by rule rather than precedent: everything touched is build apparatus (`.dev/floor/**`, `*.test.*`) or pure repo-meta (`README.md`, `CLAUDE.md`, `CHANGELOG.md`) — per CLAUDE.md § _SKILLS_VERSION discipline_, "Pure repo-meta … does not bump either — it is not methodology a user runs" — and no path in the increment is in the bump-triggering set. Known accepted cost, recorded rather than hidden: `capability-catalog-core.mjs` now renders **two** artifacts and its filename names only one (P3); the alternative split was put to the human at the plan gate and this shape was chosen deliberately. - **Split the repo into a dev/product boundary** — moved the build apparatus under `.dev/` (`.dev/floor/` checkers + tests, `.dev/features/` audit trails, `.dev/memory-bank/`), excluded wholesale by `.dev/floor/validate.mjs`; the product surface stays at the root (`pharn-review/`, `pharn-pipeline/`, `pharn-contracts/`). Commands split by name prefix — `pharn-dev-*` (apparatus) vs `pharn-*` (product) — since `.claude/commands/` cannot move. diff --git a/CLAUDE.md b/CLAUDE.md index 0b382c3..b370fbf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,8 +6,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This repository **is PHARN-OSS** — the audit-grade methodology for AI-native development itself, not scaffolding for a "real PHARN" that lives elsewhere. PHARN-OSS is **self-hosting**: it is built using -its own minimal tooling, one increment at a time (PHARN builds PHARN). It is early-stage and in active -development; see `README.md` for the product framing. +its own minimal tooling, one increment at a time (PHARN builds PHARN). It is ready to install and use +with Claude Code today (`npx @pharn-dev/pharn@latest init`); active development continues, and +functionality that has not shipped yet is explicitly labeled. See `README.md` for the product framing. There is **no application code**. The product is a _methodology expressed as prompts_: markdown specs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c13fad..3d7156f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to PHARN -Thanks for your interest in improving PHARN. This repository **is PHARN-OSS** — the audit-grade methodology for AI-native development, built using its own minimal tooling (PHARN builds PHARN; self-hosting). It is early-stage and in active development. There is no application code: the product is a _methodology expressed as markdown specs_ plus a few deterministic Node helpers (`.mjs`/`.cjs`). Treat the markdown as the source, not as docs about source. +Thanks for your interest in improving PHARN. This repository **is PHARN-OSS** — the audit-grade methodology for AI-native development, built using its own minimal tooling (PHARN builds PHARN; self-hosting). It is ready to install and use with Claude Code today; active development continues, and functionality that has not shipped yet is explicitly labeled. There is no application code: the product is a _methodology expressed as markdown specs_ plus a few deterministic Node helpers (`.mjs`/`.cjs`). Treat the markdown as the source, not as docs about source. ## Read first diff --git a/README.md b/README.md index f21fcaa..4739951 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,16 @@ **Code got cheap. Understanding got scarce.** -PHARN is an open, audit-grade system of record for AI-written code — the intent, the constraints, and -the checks behind a change, written as plain markdown that lives in your own repo and diffs in git. It -runs on Claude Code today, and the discipline itself ships as readable markdown — skills, commands, -lenses, rules — that you read, diff, and version yourself. PHARN does not make anyone understand the -code; it keeps a deterministic floor under it and the record available the moment someone needs it. +PHARN is an audit-grade methodology for building software with AI agents. It installs into your repo as +plain markdown — commands, grillers, review lenses, a constitution — and puts a deterministic floor under +the agent: the intent is written down and approved before code is written, secret-shaped literals in a +plan are found by a regex not a judgment call, an agent may only write the files its plan declared, and a +build that skipped a declared file cannot report success. Everything the floor cannot guarantee is +labeled advisory, in the same sentence. + +```bash +npx @pharn-dev/pharn@latest init +``` [![pharn](https://img.shields.io/badge/pharn-2.7.14-blue)](./CHANGELOG.md) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) @@ -20,130 +25,188 @@ code; it keeps a deterministic floor under it and the record available the momen -> **Status: early, active development.** This repository, **PHARN-OSS**, is PHARN's open-source -> edition: the architecture is specified and the methodology is being built incrementally, in the -> open, using its own tooling (PHARN builds PHARN). The foundation is in place — the spec, the build -> tooling, and the pipeline commands — but that is **not** an adoptable release. It is **not yet ready -> to adopt**: the pipeline runs here (self-hosting), but there is no installer or packaged release you -> can drop into your own repo yet. Star or watch to follow along; see -> [Current state](#current-state) for exactly what exists today. +> **Status:** Ready to install and use with Claude Code today. Active development continues; +> functionality that has not shipped yet is explicitly labeled. --- ## Contents -- [Why PHARN?](#why-pharn) -- [What makes it different](#what-makes-it-different) +- [What PHARN does](#what-pharn-does) +- [Quick start](#quick-start) +- [What it catches](#what-it-catches) +- [Why not just CLAUDE.md or AGENTS.md?](#why-not-just-claudemd-or-agentsmd) +- [Guaranteed vs advisory](#guaranteed-vs-advisory) - [The pipeline](#the-pipeline) -- [The design](#the-design) -- [Principles](#principles) -- [Current state](#current-state) -- [How it's built](#how-its-built) +- [PHARN builds PHARN](#pharn-builds-pharn) +- [Honest scope](#honest-scope) +- [Design docs](#design-docs) - [Contributing](#contributing) - [Security](#security) - [License](#license) --- -## Why PHARN? +## What PHARN does + +Vibe-coding with an agent is fast — until the chat history scrolls away and takes the _understanding_ +with it. Six months later nobody can say why the code is shaped the way it is, what the constraints +were, or which decisions were deliberate. -Vibe-coding with an AI agent is fast — until the chat history scrolls away and takes the -_understanding_ with it. Six months later, nobody on the team can say why the code is shaped the way -it is, what the constraints were, or which decisions were deliberate. That gap is **comprehension -debt** — a term [coined by Addy Osmani](https://addyosmani.com/blog/comprehension-debt/) in early -2026 — and it compounds faster than any other kind. +PHARN does not make anyone understand the code. Nothing can, and anything that claims to just gets +bypassed. What it does is make the reasoning survive the session, and put deterministic checks around +the parts that can be checked deterministically. -This isn't hypothetical. A [2026 Anthropic RCT](https://www.anthropic.com/research/AI-assistance-coding-skills) -measured developers scoring ~17% lower on comprehension of code they shipped with AI assistance, even -as the volume of AI-generated code keeps climbing — more code, understood less. +**Before you write code** — you state the intent, and a human approves it. PHARN turns your prose into a +structured `SPEC.md`, interrogates it for gaps, and waits. The model never approves its own spec. Once +approved, the spec is pinned by a SHA-256 of its own body, so editing it after planning is detectable +rather than silent. -PHARN doesn't force you to understand the code — nothing can, and anything that tries just gets bypassed. Instead it does two things it can guarantee: a deterministic floor that holds without you (secrets screened at the plan gate, every write confined to its declared scope, the plan actually built), and a markdown-canonical record — spec, constitution, diff, audit trail — kept in your repo, readable and diffable, available the moment you need it. The agent does the typing. PHARN keeps the result legible for whoever reads it next — if anyone does. +**While the agent works** — the plan declares which files it will touch, and a pre-write hook denies +everything else. Grillers interrogate the plan before a line is written: the security one runs a fixed +regex set over it and flags AWS key ids, private-key headers, and secret-named fields assigned string +literals. Afterwards, review lenses read the diff for injection, SSRF, path traversal, n-plus-one +queries, swallowed exceptions, race conditions, and placeholder code shipped as done. -> **Your chat history is gone. Your spec isn't.** +**After the change lands** — the repo holds a diffable trail: the spec, the plan, the grill log, the +regression report, the verify report. If the plan declared a file the build never wrote, `/pharn-verify` +returns `INCOMPLETE` rather than a pass. Your chat history is gone. Your spec isn't. -PHARN is meant to augment the whole team — the PM becomes a product strategist, the developer an -architect and reviewer, everyone working off the same artifact. It does **not** replace developers. +PHARN is meant to augment a whole team — the PM writes intent, the developer reviews architecture, +everyone works off the same artifact. It does **not** replace developers. --- -## What makes it different - -- **The methodology is the product, and it is readable.** PHARN ships as plain markdown — skills, - commands, lenses, rules — that you read, diff, and version in git. Nothing is obfuscated behind a - binary or a closed API. You can audit exactly what the discipline does. -- **Guarantees reduce to a deterministic floor, or they are labeled advisory.** PHARN draws a hard - line between what is _guaranteed_ (enforced by a deterministic check — a hook, a content-hash, an - enum/regex) and what is _advisory_ (a model's judgment). It does not sell a probabilistic claim as a - guarantee. (`pharn/CONSTITUTION.md`, P0.) -- **Built to resist its own attack surface.** An agent that reviews code, fetches docs, and - accumulates memory is operating on hostile input. PHARN treats trust as a structural property, not - the model's judgment — because prompt injection is unsolved. (`THREAT-MODEL.md`.) -- **Audit-grade traceability.** Findings cite stable rule IDs; rules cite principles; the spec is - pinned by content-hash. The chain from a line of code back to the intent that justified it is - explicit. +## Quick start ---- +PHARN runs on [Claude Code](https://claude.com/claude-code). In your project root: -## The pipeline +```bash +npx @pharn-dev/pharn@latest init +``` -The workflow is a spine of typed stages — each emits a versioned artifact that links back to -the spec (`pharn/ARCHITECTURE.md §6`): +The installer reads your `package.json`, detects your project's archetype, and selects the capabilities +that apply — showing you the full list, with a reason beside each one, before it writes anything. It +then installs: + +- the **product commands** into `.claude/commands/`, +- the **write-gating hooks** into `.claude/hooks/`, +- the **floor** — the deterministic checkers — plus the **contracts**, **grillers** and **review lenses** + under `pharn/`, +- and a `pharn.config.json` pinning the skills version and the exact commit it installed from. + +Then open Claude Code and run your first command: ```text -spec → plan → grill → build → regress → verify → ship +/pharn-spec ``` -Each stage reads the artifacts the previous stage produced, and every downstream artifact carries the -`spec_id` (the plan additionally pins the spec's `spec_content_hash`, so a spec edited after planning -is detectable, not silent). +Describe what you want to build. PHARN writes a `SPEC.md`, asks about what you left out, and stops for +your approval. From there, `/pharn-ship` runs the whole chain for you, or you can drive each stage +yourself. -> **What runs today:** the seven-stage spine now exists as runnable commands — `/pharn-spec` → -> `/pharn-plan` → `/pharn-grill` → `/pharn-build` → `/pharn-regress` → `/pharn-verify` → `/pharn-ship` -> — exercised by PHARN on itself (self-hosting), alongside the `/pharn-dev-*` build tooling. What does -> **not** exist yet is packaging: no installer, no versioned release you can drop into your own repo. -> See [Current state](#current-state). +Already installed? `pharn status` reports your version and whether any installed file has drifted; +`pharn update` re-fetches at the latest skills version; `pharn add ` installs another one. --- -## The design +## What it catches + +Capabilities are named for the problem they find, not the technology they use. Grillers interrogate a +**plan**; lenses read **code**. + +**Grillers** — a11y, architecture, comprehension, coupling, documentation, error-handling, i18n, +migrations, observability, performance, privacy, security, testability. -The architecture is fully specified in four documents — read them in this order: +**Lenses** — injection, SSRF, path traversal, insecure crypto, unsafe deserialization, secrets in code, +input validation, hallucinated APIs, missing `await`, missing timeouts, null dereference, off-by-one, +race conditions, resource leaks, swallowed exceptions, missing error handling, n-plus-one queries, +duplicated logic, copy-paste drift, magic values, placeholder-as-done, and a trust fence. -- [`pharn/CONSTITUTION.md`](./pharn/CONSTITUTION.md) — the eight non-negotiable principles (P0–P7). -- [`pharn/ARCHITECTURE.md`](./pharn/ARCHITECTURE.md) — the floor, the primitives, the layers, the pipeline. -- [`THREAT-MODEL.md`](./THREAT-MODEL.md) — the security foundation and the attack surface. -- [`LIMITS.md`](./LIMITS.md) — what PHARN does **not** guarantee, stated plainly. +Every capability ships with its own eval cases and expected outputs; the floor refuses a capability +whose rules no eval exercises. -These four are **trusted and human-only**: a `PreToolUse` write-guard hook denies any agent edit to -them. +This prose is hand-written and unguarded, so treat it as a tour rather than an inventory. The +authoritative, drift-guarded lists are the generated ones: [`docs/capabilities/`](./docs/capabilities/README.md) +and the [current-state block](#pharn-builds-pharn) below. --- -## Principles +## Why not just CLAUDE.md or AGENTS.md? -PHARN ships a **constitution** ([`pharn/CONSTITUTION.md`](./pharn/CONSTITUTION.md)) — eight principles that -override every command, rule, skill, and agent decision in this repo, including the process of -building PHARN itself. A violation is always blocking and is flagged for a human, never auto-fixed. +Keep them. PHARN is not a replacement for a project instructions file — it adds the parts an +instructions file structurally cannot provide, because an instructions file is text the model may follow. -| Principle | In one line | -| --------- | -------------------------------------------------------------------------------------------- | -| **P0** | Floor-or-advisory — every _guarantee_ reduces to a hook, content-hash, or enum/regex check | -| **P1** | Evals are the spec — no capability ships without eval cases binding each rule it enforces | -| **P2** | Trust is structural, not judged — untrusted input is fenced as data, never as instructions | -| **P3** | One axis of change per file; modules form a tree with no sibling imports | -| **P4** | Rules are the single source of truth — enforcers cite rule IDs, never restate them | -| **P5** | Determinism over classification — the terminal fallback is to ask the human, never to guess | -| **P6** | Discovery-first — read and verify live state; halt and ask on any ambiguity | -| **P7** | Honest scope — limits are labeled as limits; no speculative additions without a real failure | +- **A file states a rule. A hook enforces one.** "Don't edit the spec" is a sentence a model can be + talked out of. A `PreToolUse` hook that denies the write is not. +- **Approval becomes a gate, not a habit.** The spec is `Draft` until a human flips it to `Approved`, and + downstream stages refuse a `Draft`. The model cannot flip it. +- **Instructions drift; content-hashes don't.** An approved spec is pinned by a hash of its body, and + four later stages re-verify that pin. +- **Findings cite stable rule IDs.** A review finding names the rule it violates, so the chain from a + line of code back to the intent that justified it is explicit rather than recalled. +- **Untrusted text is fenced structurally.** Code under review, fetched docs and accumulated memory are + tagged untrusted, and no guaranteed decision reads a free-text field — because prompt injection is + unsolved, so trust cannot be the model's judgment call. -> The privacy / multi-tenant / accessibility-style principles you may have seen described elsewhere -> are the _app-level_ constitution PHARN intends to ship as selectable templates for the projects it -> builds. Those templates are **not present yet** — the eight principles above are this repo's own -> governing constitution. +--- + +## Guaranteed vs advisory + +This distinction is the whole point, so it is stated once and applied everywhere. A **guarantee** must +reduce to one of three deterministic, non-LLM operations — a hook, a content-hash comparison, or an +enum/regex check. Anything else is **advisory**, and is labeled advisory wherever it appears. + +**Guaranteed** — each of these is a named checker you can read: + +| Guarantee | The check behind it | +| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | +| The four trusted docs cannot be edited by the agent through Write/Edit/MultiEdit | `.claude/hooks/protect-trusted-paths.cjs` | +| A command writes only the paths it declared | `set-writes-scope.cjs` + `enforce-writes-scope.cjs`, fail-closed | +| An approved spec is pinned, so a later edit is detectable | `check-spec.mjs --hash`, re-verified at grill, build, regress and verify | +| Secret-shaped literals in a plan are detected | `scan-plan-secrets.mjs` — a fixed regex set, immune to prose claiming the plan is clean | +| A plan-declared file the build never wrote yields `INCOMPLETE`, not a pass | `check-build-complete.mjs` feeding `check-verify.mjs` | +| Which lenses run, and how their findings merge | `count-lenses.mjs` reads frontmatter; `merge-findings.mjs` keys on enum fields only | + +**Advisory** — everything a model judges: whether a plan is wise, whether a lens's finding is real, +whether a severity is right, whether a griller actually ran its scanner. These surface for a human. They +never gate. + +**And the bounds, stated rather than buried.** The write-guards gate the Write/Edit/MultiEdit tool +surface; **writes made through the Bash tool bypass them entirely**, which is the largest hole in that +guard and no amount of path matching narrows it. `scan-plan-secrets` proves a pattern is present, never +that the literal is a live secret — and it runs inside a griller, which surfaces findings and never +blocks. `check-build-complete` proves a declared path exists, never that its contents are right. A green +floor means the named checks passed. It never means the code is correct. --- -## Current state +## The pipeline + +Seven typed stages. Each emits a versioned artifact carrying the `spec_id`, and each reads what the +previous stage produced: + +```text +spec → plan → grill → build → regress → verify → ship +``` + +`/pharn-ship` runs that chain for you and stops at the two human gates — spec approval, and the +post-verify merge/fix/abandon decision. It never self-approves and has no `--yolo`. + +`/pharn-loop` runs the same chain, but iterates the build→regress→verify middle until a deterministic +stop: green, a bounded iteration cap, or the first terminal failure. The stop is computed by a tested +checker whose only inputs are the two floor verdicts, so no advisory stage can keep the loop running. + +**Standalone:** `/pharn-review` is the one command that is not a pipeline stage. It runs the review +lenses in parallel as subagents and merges their findings deterministically, keyed on enum-gated fields +only. Point it at code whenever you want, pipeline or not. + +--- + +## PHARN builds PHARN + +PHARN is self-hosting: it is built with its own tooling, one approved increment at a time, and the +audit trail of every increment is committed in this repo. The inventory below is **generated** from the live repository by `npm run docs:generate` and guarded byte-for-byte by `npm run docs:check`, so it cannot quietly drift from what is actually built. The prose @@ -160,52 +223,71 @@ around it is hand-written and carries no such guarantee. -Alongside those: **the architecture spec** — the four trusted documents above — and **the floor and its -guards**, the deterministic validator ([`pharn/floor/validate.mjs`](./pharn/floor/validate.mjs)) plus the -`PreToolUse` write-guards wired in `.claude/settings.json`: the trusted-doc guard -([`.claude/hooks/protect-trusted-paths.cjs`](./.claude/hooks/protect-trusted-paths.cjs)) and the -writes-scope guard (`enforce-writes-scope.cjs`, fix #7), which confines every command to its declared -`writes:`. Every capability ships with evals. `pharn/pharn-review/trust-fence/` (attempt 0) is the -injection-residual probe, recorded in -[`.dev/features/trust-fence/REVIEW.md`](./.dev/features/trust-fence/REVIEW.md). +The evidence worth reading is a defect the loop caught in PHARN's own code. +[`.dev/features/span-redos-linear/REVIEW.md`](./.dev/features/span-redos-linear/REVIEW.md) records +finding F1 against an increment whose entire purpose was to repair a false performance claim in a floor +checker — and whose replacement claim was false in the same way: -`pharn-contracts` — the schemas-only root — sits at the bottom of the layer tree in -`pharn/ARCHITECTURE.md §4`, with `pharn-review` and `pharn-pipeline` above it, and `pharn-core` opened by -the `seam-resolver`. Still **planned**: `pharn-audits`, `pharn-skills-*`, `pharn-stack-*`, and the rest of -`pharn-core` — the constitution engine, the agnostic rule set, the memory-bank, and the base commands. +> the increment repairing a false bound in a floor file shipped a second, weaker false bound in the same +> paragraph -What does **not** exist yet: any installer, wizard, or packaged release. The pipeline runs _here_, on -PHARN itself; it is not yet something you can drop into your own repo. This repository is the -foundation and the tooling, not a finished product. Please do not adopt it yet. +That finding is marked `severity: blocking` and was fixed inside the increment that raised it. The +review that caught it was PHARN's own. --- -## How it's built +## Honest scope -PHARN is developed in the open and is **self-hosting**: it is built using its own minimal tooling, one -increment at a time, with a deterministic floor gating every step. +**Not yet built.** `pharn-audits`, `pharn-skills-*`, `pharn-stack-*`, and the rest of `pharn-core` — the +constitution engine, the agnostic rule set, and the memory-bank commands beyond promotion. As the +generated inventory above shows, no `validator`, `verifier` or `auditor` capability has been authored: +`/pharn-verify` ships the verifier plug-in slot empty, deliberately, because adding one before a real +need is the speculation the constitution forbids. -```text -/pharn-dev-plan → approve/correct PLAN.md → /pharn-dev-build → pharn/floor/validate.mjs → /pharn-dev-review → fold lessons → next -``` +**Not guaranteed.** [`LIMITS.md`](./LIMITS.md) states plainly what PHARN does not promise, and +[`THREAT-MODEL.md`](./THREAT-MODEL.md) states the attack surface — starting from the assumption that +prompt injection is unsolved. Both are worth reading before you rely on anything here. Claims in the +shipped docs that describe a check which is specified but not yet running are marked +`(specified; ships with the guarded surface)`, and a checker in CI fails the build if such a marker +survives after the check goes live, or is deleted while it is still absent. -The floor and the write-guard hook carry **zero runtime dependencies** (Node stdlib, Node 24); the -dev tooling (ESLint, Prettier, markdownlint) is dev-only. To understand or contribute to the build -process, start with [`CLAUDE.md`](./CLAUDE.md) and [`CONTRIBUTING.md`](./CONTRIBUTING.md). +**Packaging.** No git tags and no GitHub releases yet; the installer fetches this repository's current +`main`. Two of the four design documents (`THREAT-MODEL.md` and `LIMITS.md`) are not currently copied +into an install — read them here. + +--- + +## Design docs + +The architecture is fully specified in four documents. Read them in this order: + +1. [`pharn/CONSTITUTION.md`](./pharn/CONSTITUTION.md) — eight principles (P0–P7) that override every + command, rule and agent decision here, including the process of building PHARN itself. P0 is the one + the rest exists to serve: a guarantee reduces to a deterministic floor operation, or it is labeled + advisory. +2. [`pharn/ARCHITECTURE.md`](./pharn/ARCHITECTURE.md) — the floor, the three primitives, the layer tree, + the pipeline. +3. [`THREAT-MODEL.md`](./THREAT-MODEL.md) — the security foundation and the attack surface. +4. [`LIMITS.md`](./LIMITS.md) — what PHARN does **not** guarantee. + +These four are trusted and human-only: a `PreToolUse` write-guard denies agent edits to them through the +Write, Edit and MultiEdit tools. As noted above, that guard does not cover writes made through Bash. --- ## Contributing -PHARN is small-surface on purpose: a rule or enforcer is added only in response to a real failure, -never a hypothetical (P7). See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the read-first order, the -gates to run before pushing, and the build loop. Conduct expectations live in +PHARN is small-surface on purpose: a rule or enforcer is added only in response to a real failure, never +a hypothetical. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the read-first order, the gates to run +before pushing, and the build loop. Conduct expectations live in [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md); release history is in [`CHANGELOG.md`](./CHANGELOG.md). +The floor and the hooks carry **zero runtime dependencies** (Node stdlib, Node 24); ESLint, Prettier and +markdownlint are dev-only. + ## Security -Found a vulnerability? Please follow [`SECURITY.md`](./SECURITY.md) rather than opening a public -issue. +Found a vulnerability? Please follow [`SECURITY.md`](./SECURITY.md) rather than opening a public issue. ## License diff --git a/SECURITY.md b/SECURITY.md index 60f7bec..cf88de7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ PHARN is an audit-grade methodology — taking security seriously is part of the ## What this repo is, and its security surface -This repository **is PHARN-OSS** — the audit-grade methodology itself, early-stage and in active development. Its security surface is small by design: four trusted markdown spec docs, the `pharn-dev-*` build and `pharn-*` product commands, two `PreToolUse` hooks (`.claude/hooks/protect-trusted-paths.cjs`, the trusted-doc write-guard, and `enforce-writes-scope.cjs`, the writes-scope guard), and the deterministic floor (`pharn/floor/`). No transpile step, no bundled runtime dependencies, no network egress, no secrets — stdlib-only Node (`.cjs`/`.mjs`) plus markdown. +This repository **is PHARN-OSS** — the audit-grade methodology itself. It is ready to install and use with Claude Code today; active development continues, and functionality that has not shipped yet is explicitly labeled. Its security surface is small by design: four trusted markdown spec docs, the `pharn-dev-*` build and `pharn-*` product commands, two `PreToolUse` hooks (`.claude/hooks/protect-trusted-paths.cjs`, the trusted-doc write-guard, and `enforce-writes-scope.cjs`, the writes-scope guard), and the deterministic floor (`pharn/floor/`). No transpile step, no bundled runtime dependencies, no network egress, no secrets — stdlib-only Node (`.cjs`/`.mjs`) plus markdown. PHARN's security model (`THREAT-MODEL.md`, threat model B) starts from one axiom: **prompt injection is not solved.** An agent that must read hostile context — code under review, fetched docs, accumulated memory, another model's output — cannot be made to reliably ignore instructions embedded in that content. Defense therefore rests on the **deterministic floor** (hooks, content-hashes, enum/regex checks that do not depend on model judgment), not on "the model will notice the attack." The security-relevant surfaces of this repo follow from that shape.