docs: prune docs/ to guides, deployment, records, assets (#1357) - #1395
Merged
Conversation
radandevist
force-pushed
the
lane/wt-1357
branch
from
August 25, 2026 10:02
73fddcf to
6d935db
Compare
Add packages/scripts-ts/src/audit-docs-prune.ts, which enumerates every tracked file under docs/ outside guides/, deployment/, assets/, sweeps exactly the #1357 survival surfaces (AGENTS.md, DESIGN.md, docs/guides, docs/deployment, apps/, packages/, .github, justfile; root README.md deliberately excluded, as are docs/README.md and the ci-gate manifest), applies a reviewed move/delete decision table, and renders docs/records/2026-08-25-audit-docs-prune.md. Reproducibility: the audit reads a git revision - by default the merge-base of origin/develop and HEAD (the pre-prune tree being pruned), overridable with --rev <sha> - so the evidence stays byte-for-byte regenerable after the prune lands; --check enforces equality against the copy committed on this branch. Inventory result: 91 candidates - 9 moved to docs/records/, 81 deleted, 1 kept (docs/README.md, rewritten later in this change).
git mv each surviving record to the flattened YYYY-MM-DD-<type>-<topic>.md naming and rewrite every reference across the repo (AGENTS.md, DESIGN.md, guides, deployment docs, front source comments, shared-ts constants). - archived gray-ui migration design -> records (referenced by DESIGN.md source annotations) - deployment-guide -> records (superseded-by pointer from production-deployment-design.md) - old-front screen reviews -> records (bulk-action guide, front flags.ts pointers) - parity contract -> records (locked contract default in conventions + routes) - identity-scoped tenant cookie plan -> records (shared-ts constants pointer) - marketing landing bands + epic-d publishing specs -> records (AGENTS.md/DESIGN.md pointers) - kiota redirect-header-leak audit -> records (dependency-health guide + redirect-scrub test) No content edits beyond link rewrites. The inventory evidence keeps the pre-prune source paths; the decision-table script is intentionally untouched here.
docs/README.md now describes the target layout - guides/, deployment/, records/ (YYYY-MM-DD-<type>-<topic>.md), assets/ - with one filing rule per surface and records as the write-once home for specs, plans, reviews, audits, spikes and analyses (superpowers skills included). AGENTS.md's Documentation Organization section states the same closed set. Every normative-surface pointer into directories that commit 4 deletes is rewritten now: archive references in AGENTS.md, README.md, five guides and production-deployment-design.md now point at git history or the old-front-final tag instead of docs/archive/old-front. Landing this before the deletions keeps every intermediate commit free of dangling pointers from maintained surfaces.
… links Delete the 81 unreferenced records under docs/archive/, analysis/, front-migration/, implementation-plans/, misc/, reviews/, roadmaps/, spikes/ and superpowers/ per the committed inventory. Remove the archive-records guard (check-archive-records.ts + its fixture suite) whose entire subject tree is gone. Retarget .github/workflows/docs-archive.yml in place so the required docs-archive-gate context keeps reporting for every PR: the changes classifier pattern, the push paths filter and the heavy job now run the new repo-wide dead-relative-link guard (check-doc-links.ts) over tracked Markdown, with docs/records/ bodies exempt as write-once evidence. Reconcile ci-gate-manifest.json with the retargeted steps, swap package.json's check:docs-archive-records for check:doc-links, and replace the justfile ci-docs-archive-records recipe with ci-doc-links inside just ci. Housekeeping tied to the same deletions: env.test.ts's dead-env-var sweep exempts docs/records/** instead of the removed docs/archive/**; the oxlint-disables audit drops its docs/superpowers/plans ignore; project-closure-v1.json keeps requiring docs-archive-gate, which still reports. Fix three latent broken relative links the new guard caught on its first run (apps/front/e2e/README.md twice, guides/front/conventions.md).
Capture the guard's real behavior as committed evidence: GREEN (exit 0, 56 Markdown files, 132 relative links) on the clean tree, then RED (exit 1) naming a planted broken link in a guide while an exempt docs/records/ body with an equally dead link stayed silent in the same run. Both planted files were removed immediately after capture; only this record remains. The standing regression net is packages/scripts-ts/src/check-doc-links.test.ts, which executes the real guard against throwaway git repositories and pins every documented failure mode; it runs in just ci (ci-doc-links) and under the retargeted docs-archive-gate.
…r the prune mapping
…rged paid-modules spec) Round-1 review CRITICAL: the committed record was generated before develop merged #1355, so `audit-docs-prune.ts --check\) failed at the tip — the inventory omitted the very row (docs/superpowers/specs/2026-08-25- paid-modules-design.md) the tip commit lands. Regenerated mechanically: 92 candidates — 9 moved / 1 kept / 82 deleted. `--check` now exits 0 at this commit (byte equality against a fresh regeneration).
Round-1 review CRITICAL: `audit-docs-prune.ts --check` — the switch that advertises byte equality between the committed inventory and its generator — was wired into no workflow and no local recipe, so staleness could never be caught by CI (the committed record was, in fact, already stale at the reviewed tip). - docs-archive.yml: new step 'Check prune inventory freshness (--check)' runs audit-docs-prune.ts --check after the doc-links guard; a stale record now fails the required docs-archive-gate context. - The changes classifier pattern gains the audit script itself so an edit to the generator wakes the gate; pinned by a new ci-changed-paths test case extracted from the real workflow YAML (RED vs the old pattern). - Local mirror: just ci-doc-links runs the identical --check invocation. - Manifest reconciled honestly via check-ci-drift (two hash updates + one new entry whose mirrors are the identical commands). Paired proof (real run): committing an edit to one row of the record makes --check exit 1 naming the file; reverting restores exit 0.
…view r1 MAJOR) The r1 RED proof planted a broken link as an UNTRACKED file and the guard — listing `git ls-files` only — stayed green (exit 0), so the proof was not reproducible as written. The guard now scans tracked files PLUS `git ls-files --others --exclude-standard` (untracked, non-ignored), so a local pre-commit run catches an unstaged broken link; gitignored files remain out of scope. Fixture suite: kept all 7 existing tests, added 2 for the untracked case — the failing-link test is RED against the old guard (verified: old code exits 0 where the test demands 1).
…iew r1 MEDIUM) The prune inventory counted apps/, packages/, .github/, justfile, AGENTS.md and DESIGN.md among the survival surfaces, but the dead-link guard only scanned *.md — a code comment or workflow step naming a moved/deleted doc went unguarded. The guard now literal-scans the same code surfaces for docs/... paths whose target does not exist (exact file, .md completion, or directory — the same existence model as the link scan). Out of scope by design: URLs, branch names without a dotted segment, directory mentions, test/spec fixtures, and audit-docs-prune.ts's pre-prune decision table. Markdown surfaces scanned here get the same fenced-block stripping as the link scan. Fixture tests: existing literals pass; a broken literal fails naming file:line; URLs/branches/test files stay out; audit table exempt. Paired RED: with the finding-3 code, a planted broken literal in apps/api/Lib/AppEnvironment.cs kept exit 0 (58 md / 132 links OK); with this change it fails naming apps/api/Lib/AppEnvironment.cs:<line>; reverted, clean tree green.
…nt clean - Notes now state #1355 merged into develop after branching and that this lane lands its content at docs/records/2026-08-25-spec-open-core-paid-modules.md - Regenerated record via audit-docs-prune.ts itself (92 candidates unchanged) - unicorn/prefer-string-starts-ends-with: entry.endsWith('/')
…iles, untracked RED, literal scan, 13 tests)
radandevist
force-pushed
the
lane/wt-1357
branch
from
August 25, 2026 10:31
6d935db to
f727b83
Compare
radandevist
added a commit
that referenced
this pull request
Aug 25, 2026
…866) Round-1 finding 5: the design note moves out of docs/analysis ahead of the #1357/#1395 pruning (guides/deployment/records/assets only), landing at its final name docs/records/2026-08-25-analysis-email-log-actor.md so the two PRs do not conflict. Content fixes while moving (records are dated, not retro-edited — these land pre-merge so the record ships accurate): - §1 quotes K-6 with the real table name email_log_evidence_events (was email_log_events). - New paragraph records WHY this table uses two columns (actor_kind + actor_id) where the job_dead_letter_events precedent used one (detected_by): the kind/correlation split keeps both halves machine-checkable end to end. - §6 records the round-1 amendments (value type, CHECK constraints, explicit replay index) applied before merge. Refs #866.
…ename detection (review r2 MAJOR) The byte-equality --check could not see a misclassification the generator itself shared: paid-modules was an R100 rename to docs/records/ but MOVES omitted it, so the inventory said delete and regen == committed == wrong (9 moved / 82 deleted instead of 10/81). - derive ground truth from `git diff -M --name-status` merge-base..HEAD; --check fails naming any row where classification, existence, or destination disagrees with git's renames (generation fails too) - add the paid-modules row to MOVES with explicit topic 'open-core-paid-modules' (deriveTopic alone would name it -paid-modules); regenerate the inventory: 92 candidates — 10 moved, 1 kept, 81 deleted - rewrite the Notes prose honestly: no surface references it; preserved as a deliberate exception because #1355 had already merged it - new fixture suite executes the real script against throwaway repos: RED replay of the paid-modules defect plus inverse-lie and destination-mismatch cases RED transcript (.dump/red-transcript-r2.txt): old committed inventory + fidelity gate -> exit 1 naming the row. GREEN: this commit.
Resolve docs conflicts per the prune's mechanical rule: #1357's survival rule plus the paid-modules precedent. The #820 bulk-actions spec (added to develop mid-flight, same shape as paid-modules) moves to docs/records/2026-08-25-spec-820-bulk-actions-design.md via its own MOVES row added next commit; the modified-but-unreferenced DLQ triage design stays deleted (history preserved in git, matching the committed audit).
…nerate inventory (93 candidates, 11 moved / 81 deleted / 1 kept)
radandevist
added a commit
that referenced
this pull request
Aug 25, 2026
…lands The audit resolved its evidence tree via merge-base(origin/develop, HEAD), which equals HEAD once the #1395 prune lands, making --check fail on any docs/-touching lane. Walk origin/develop's first-parent history to the youngest tree still carrying every decision-table source instead (bounded, --rev override kept), and pin the rename-fidelity check to that same rev. Covered by two new tests: a post-prune lane passes --check, and a tampered record still fails. Regenerated the inventory record (94 rows: adds the email-log-actor analysis delete).
This was referenced Aug 25, 2026
radandevist
added a commit
that referenced
this pull request
Aug 25, 2026
…m committed history alone (Closes #1425, rescope after #1422) (#1426) fix(docs-archive): prune-inventory check derives its scope from committed history, never from a fetched ref The docs-prune inventory check (packages/scripts-ts/src/audit-docs-prune.ts) went red develop-wide after #1395 + #1420 because it compared against an ambient origin/develop ref that differs between PR checkouts, push events and a plain clone. The audited pre-prune tree is now derived from committed history alone — the youngest ancestor of the record-introducing commit whose tree still carries every decision-table source — so the verdict is identical on every event and on a checkout that never fetched the ref. The misleading "delete" row for docs/records/2026-08-25-analysis-email-log-actor.md (a file #1389 MOVED into the protected destination) is fixed at the cause: the candidate filter excludes docs/records/, the move target itself. A push-event fixture pins the single-squash shape. The ci-gate-manifest entry changed only its reason text; mirror invocation and gate hash are untouched. Proof: RED transcript (.dump/red-rescope-b.log) on the #1389 shape before the scope fix, GREEN after; --check exit 0 with the exact CI invocation at the tip; scripts-ts suite 693 passed under the heavy lock. Closes #1425 Model: Ox Alpha via Nous Portal (jcode), effort max. Reviewer: tencent/hy3:free (adversarial round 1, APPROVED at d10a217 — 3 notes: a deletion FROM docs/records/ is covered by the protected-destination guard, not this check; the "+2 skipped" claim in the lane's report was stale (0 skipped at tip); manifest reason-only change confirmed). Unverified: nothing beyond CI and the reviewer's re-run.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1357
What this does
Prunes
docs/to exactly four directories and wires a repo-wide dead-link guard so the prune is provably safe:Inventory method
The move/delete decision is mechanical and reproducible: a committed script (
packages/scripts-ts/src/audit-docs-prune.ts, commit 1) scans every file outside the four target directories, resolves who references it among the survival surfaces (AGENTS.md,DESIGN.md,docs/guides,docs/deployment,apps/,packages/,.github, the justfile — deliberately NOTdocs/README.mdindexes or the retired archive-guard manifest), and emits the inventory committed as evidence atdocs/records/2026-08-25-audit-docs-prune.md.The inventory carries two independent freshness controls:
audit-docs-prune.ts --checkregenerates the record from the merge-base tree and exits non-zero if the committed copy differs byte-for-byte. Enforced twice — in the requireddocs-archive-gateCI job and locally in recipeci-doc-links(manifest reconciled throughjust ci-drift). Paired proof: editing one row of the record makes--checkexit 1 naming the file; reverting restores exit 0.--checkcross-validates every rendered row againstgit diff -M --name-status <merge-base>..HEAD. Git's rename detection is an INDEPENDENT source of truth for what actually moved, so a decision-table omission — a real rename left unmapped, hence rendered as a deletion — now fails loudly instead of regenerating identically-wrong evidence. This closes the structural blind spot of control 1 alone: regenerating from the same wrong table matches byte-for-byte and proves nothing.Moved vs deleted
93 candidate files: 11 moved to
docs/records/, 1 kept in place, 81 deleted (counts after merging origin/develop — see below).docs/implementation-plans/identity-scoped-tenant-cookie.md→docs/records/2026-01-31-plan-identity-scoped-tenant-cookie.md, and every link to it was rewritten repo-wide (AGENTS.md, DESIGN.md, guides, app code comments).docs/README.mditself, rewritten as the one-page filing index.docs/archive/(history lives in git and theold-front-finaltag), plussuperpowers/,reviews/,front-migration/,implementation-plans/,spikes/,roadmaps/,misc/,audits/, and one orphan underanalysis/. No content edits beyond link rewrites on survivors.Correction to the first version of this description: it said 9 moved / 82 deleted. That under-counted by exactly one file —
docs/superpowers/specs/2026-08-25-paid-modules-design.md. Git shows it as an R100 rename todocs/records/2026-08-25-spec-open-core-paid-modules.md, but the generator's hand-kept MOVES table omitted it, so the committed inventory rendered it as a deletion and byte-equality--checkreproduced the same wrong answer. Round-2 review caught it; the fix is under "Round 2 fix" below.Honest provenance for the exception: no survival surface references the paid-modules spec. It is preserved because #1355 merged it into develop while this lane was in flight, and the lane deliberately keeps work develop already merged rather than deleting it in the prune. Its record name carries
-open-core-through an explicittopic:override in the decision table (deriveTopic() alone would yieldpaid-modules, which is not the name that landed). The committed inventory's Notes section states this plainly.Mid-flight merges, handled explicitly: develop merged #1355 while this lane branched (adding the paid-modules spec back under a directory this lane deletes) and later #1385 (adding the bulk-actions spec,
docs/superpowers/specs/2026-08-25-820-bulk-actions-design.md). This branch merged origin/develop (merge b068baa). The #820 spec follows the same precedent as paid-modules — no survival surface references it either, and it is preserved deliberately, landing atdocs/records/2026-08-25-spec-820-bulk-actions.md. Develop also MODIFIED the already-pruned DLQ triage design (docs/analysis/2026-08-24-dlq-unclassified-triage-design.md); it stays deleted per the mechanical rule (unreferenced by any survival surface, history preserved in git). With develop as ancestor, the regenerated inventory lists both mid-flight specs as their own rows (93 candidates total); both--checkcontrols pin the committed record to that regeneration AND to what git actually renamed.Round 2 fix
Adversarial review round 2 returned two MAJOR findings; both fixed with RED→GREEN proof:
listGitDocRenames+assertGitFidelitywired into--checkahead of the byte comparison).RED (against the old committed inventory and the unmapped table), captured as produced in
.dump/red-transcript-r2.txt:GREEN: the MOVES table gained the paid-modules row with its explicit topic, the inventory regenerated to the true counts (then 10 moved / 81 deleted / 1 kept), and
--checkexits 0 on the committed tip. Fix commit: 7bbf128.Live proof on real data: when this lane later merged origin/develop, the SAME gate flagged a REAL destination mismatch on the newly arrived #820: restore bulk management actions in the staff-users selection mode #1385 spec before any wrong evidence could be committed — the mapping named
…-spec-820-bulk-actions.mdwhile the merge auto-planted…-820-bulk-actions-design.md. Renaming the planted file to the canonical name turned the gate green (c0a507c).A regression suite pins the gate end-to-end:
packages/scripts-ts/src/audit-docs-prune.test.ts(commit 7b8ce98) runs the REAL generator inside throwaway git repositories across four fixtures — (1) RED replay: rename recorded as delete fails--checknaming the row; (2) a correctly mapped move passes both controls; (3) inverse lie: an inventory claiming a move git does not show fails; (4) a destination mismatch between mapping and git's rename target fails. All four pass on the committed tip.Local gates re-run before this update: full scripts-ts vitest suite (pre-merge run: 27 files, 689 passed | 2 skipped; re-run green after the develop merge), including these four fixtures and the doc-links guard fixtures — plus
node packages/scripts-ts/src/audit-docs-prune.ts --check(exit 0 post-merge), the doc-links guard itself (59 files, 133 links), andjust ci-drift(7/7). A formatting-only commit (2cef5aa) satisfiesoxfmt --checkon the new fixture file.Round 1 fix
Review round 1 returned CHANGES_REQUIRED (stale evidence, unenforced reproducibility control). All findings fixed, one commit each:
--checkexits 0 (ea66f0c).--checkwired intodocs-archive-gate+ci-doc-links, classifier pattern wakes the gate on generator edits, manifest reconciled honestly (6e744c1).docs/…literal scanner added with fixture test and paired RED (eb2fc37).Round 1 fix implemented by Ox Alpha via Nous Portal (max, jcode). Round 2 fix implemented by Ox Alpha (stealth/ox-alpha via Nous Portal, max, jcode).
docs-archive gate retarget
check-archive-records.tsenumerated files that were supposed to exist underdocs/archive/and failed when they went missing. With the archive deleted it would have asserted an empty set forever — vacuously green. Per the issue's rule ("never left passing vacuously") the script, its test, and their manifest entries are deleted, not loosened..github/workflows/docs-archive.yml, required context unchanged) now runs three things: thecheck-doc-links.tsguard, its fixture suite, and the prune-inventory freshness checks above.*.mdin the whole repo plus untracked non-ignored working-tree files (git ls-files --others --exclude-standard), so a broken link planted but never staged still fails a local run. On top of Markdown links, the guard scans the code surfaces (apps/,packages/,.github/, the justfile,AGENTS.md,DESIGN.md) fordocs/…path literals whose target does not exist — a code comment naming a deleted doc is as broken as a dead link.docs/records/bodies stay exempt (write-once evidence, same policy the old archive carried); fenced code, inline code spans, URLs, branch names and test/spec fixtures are out of scope so examples cannot trip it. Wired into the local gate identically: recipeci-doc-links, manifest entries reconciled throughjust ci-drift.docs/records/2026-08-25-analysis-doc-links-guard-proof.md. GREEN on the clean tree (58 files scanned, 132 relative links checked, exit 0). RED from a real run, captured as written:with an exempt frozen-record link planted in the same run staying silent, and a paired literal-scan RED (broken
docs/…literal inapps/api/Lib/AppEnvironment.cs→ exit 1 naming file and line). Both plantings were removed immediately after capture; the clean tree went green again. The fixture suite pins these modes permanently (13 tests), including the untracked-file case (RED against the old tracked-only code) and the literal-scan cases.Public/private rule
Business/licensing/strategy content never enters this public repo — it lives in the owner's private notes.
Ruleset note
The required check context keeps the name
docs-archive-gate: only the job's steps changed, not itsname:(conditionaldocs-archive-gateon pull_request/merge_group,docs-archive-push-checkon push). No rename, so the branch ruleset keeps matching and no PR gets spuriously blocked.Known unrelated CI failure
Build e2e imagesfails due to the org-move issue (#1396). Not caused by this PR.Implementer: Ox Alpha (stealth/ox-alpha via Nous Portal, max, jcode)
Unverified
--check,just ci-drift, doc-links guard); the remaining evidence is this PR's own checks at that tip.