Skip to content

verify-no-work-records: catch mixed-case PLAN-/DESIGN-/REPORT-/STATUS- files at any depth #89

Description

@woksin

Summary

Amend verify-no-work-records.yml and the inlined rule text so that tracked decision records are documentation, not work records, and replace the root-only name heuristic with an explicit documentation allowlist that also catches the mixed-case PLAN-* / DESIGN-* / REPORT-* files the current regexes miss.

Current behaviour

.github/workflows/verify-no-work-records.yml (one input, extra-allowed, lines 12-16) applies three rules:

  1. anything tracked under .ai-work/ (line 39);
  2. root-level SCREAMING-CASE .md files matched by ^[A-Z][A-Z0-9_.-]*[A-Z0-9]\.md$ (line 52) unless in the root allowlist at line 43 (README|LICENSE|AGENTS|CLAUDE|GEMINI|CODE_OF_CONDUCT|CONTRIBUTING|SECURITY|CHANGELOG|CREDITS|RESOURCES|BRAND|MESSAGING|PAGES|SITE|PRIVACY_POLICY|ROADMAP|START-HERE|CHRONICLE|COMPATIBILITY|NOTICE|SUPPORT|GOVERNANCE|VERSION, plus extra-allowed at line 47);
  3. HANDOVER, PROMPT-*, NEXT-SESSION, SESSION-PROMPT, SESSION[-_]HANDOVER names anywhere (line 61), skipping .ai/* .claude/* .github/* .pi/* .agents/* .ai-work/* (line 58).

Consequences:

  • A root DECISIONS.md fails rule 2; decisions/HANDOVER.md fails rule 3; nothing says decisions/** (or Documentation/decisions/**, Knowledge/Decisions/**) is documentation, so a repository adopting a decisions folder has to guess what the gate will accept. The rule text inlined in AGENTS.md:11 ("Knowledge that must outlive the session belongs in the repository's documentation structure through normal review") names no location; the canonical copy in Cratis/AI mentions ADRs, this copy does not.
  • Mixed-case root files such as PLAN-workbench-product.md or DESIGN-workbench-*.md are not SCREAMING-CASE (they contain lowercase after the prefix) and match none of rule 3's names, so a planning document with a PLAN- or DESIGN- prefix passes the gate at the root and anywhere below it.
  • The shape is a name heuristic with a growing root allowlist; every new legitimate root file needs extra-allowed in every wrapper.

Desired behaviour

  • An explicit documentation allowlist by directory: decisions/**, Documentation/**, docs/**, Knowledge/Decisions/** (plus the existing root names) are never work records regardless of case; the rule text gains "a decision log is not a work record; decisions live in decisions/ (or the repository's documented decisions folder)".
  • Rule 2 becomes case-insensitive on the prefix classes that name work records (PLAN-, DESIGN-, REPORT-, STATUS-, HANDOVER, PROMPT-, NEXT-SESSION, SESSION-) and applies below the root as well, excluding the documentation allowlist and the existing skip list.
  • AGENTS.md re-synced from the canonical rule so the ADR/decision wording is present.

Acceptance

Done when:

  • A fixture repository with decisions/D-0001-example.md, Documentation/decisions/0003-kernel-boundary.md and a root DECISIONS.md index passes; the same fixture with PLAN-foo.md at the root, docs/../notes/DESIGN-bar.md outside the allowlist, or decisions/HANDOVER.md fails, each with the offending path printed.
  • README.md documents the allowlist and the wrapper inputs.

Verify by:

act -W .github/workflows/verify-no-work-records.yml -j verify   # or the script extracted per the exit-2/self-test issue, run against tests/fixtures/work-records-{clean,dirty}

Mutation proof: add Notes/PLAN-something.md to the clean fixture; the run fails naming it.

Out of scope

Exit codes and --self-test (separate issue in this repository); the shared rule text in Cratis/AI (its own issue); recovering any repository's untracked decisions (their own issues).

Dependencies

Source: AI-Learning F-88 (gate half), F-99. Depends on: D-0001 (where decisions live). Related: the verify-decisions reusable workflow issue and the exit-2/self-test issue in this repository; the rule amendment issue in Cratis/AI.

Status

State: Done
Scope: The work-records guard is a pinned, checksummed and tested script with exit code 2 for could-not-run, a --self-test, counts and mixed-case matching.
Delivered: - #150 (merged)
Remaining: Nothing.
Follow-ups: None.

Activity

  1. changed the title [-]verify-no-work-records: treat decisions/** as documentation and replace the root-only name heuristic with an explicit allowlist[/-] [+]verify-no-work-records: catch mixed-case PLAN-/DESIGN-/REPORT-/STATUS- files at any depth[/+] on Sep 23, 2026
  2. woksin commented on Sep 23, 2026

    @woksin
    ContributorAuthor

    Numbered decisions/ records already pass the guard. What is still missing is catching mixed-case work-record names such as Plan-… or design-… and files below the repository root.

  3. cratis-direct commented on Sep 24, 2026

    @cratis-direct

    Investigation

    What I found

    Reproduced the gap. I extracted the inline script from .github/workflows/verify-no-work-records.yml and ran it against a fixture git repo. Against the issue's own fixture set, today's guard:

    File Today Wanted
    PLAN-foo.md, REPORT-weekly.md (root) passes fail
    Plan-bar.md (root, mixed case) passes fail
    notes/DESIGN-bar.md, Notes/Plan-something.md, Source/Report-weekly.md passes fail
    DECISIONS.md (root index) fails pass
    decisions/D-0001-example.md, Documentation/decisions/0003-*.md passes pass
    decisions/HANDOVER.md fails fail

    Cause, confirmed by reading verify-no-work-records.yml:52 and :61: rule 2's regex ^[A-Z][A-Z0-9_.-]*[A-Z0-9]\.md$ is root-anchored and rejects any lowercase, so PLAN-foo.md (lowercase foo) never matches; rule 3 lists only HANDOVER|PROMPT-|NEXT-SESSION|SESSION-PROMPT|SESSION[-_]HANDOVER, so no PLAN/DESIGN/REPORT/STATUS class exists at any depth. Mixed-case Handover.md also passes today.

    Measured the blast radius before proposing a regex. This reusable workflow is bootstrapped into ~50 Cratis repos, most of them at @main (Chronicle, Strategy, …), a few pinned (Chronicle.Dapr, Chronicle.Wolverine). I pulled the full file tree of all 55 non-archived org repos (73k paths) and replayed candidate rules over them:

    • Fully case-insensitive prefixes (design-… as the comment asks) → 13 new failures today: Chronicle*/Documentation/client-snippets/.../design-for-async.md (4 repos), Strategy/governance/plan-coverage-audit.md, Strategy/evidence/design-partner-agreement-checklist.md, plus 6 in AI (no wrapper installed, so harmless).
    • Case-insensitive + documentation-directory allowlist → 2 new failures, both in Strategy, which tracks @main.
    • All-caps or Titlecase prefixes (PLAN-/Plan-, DESIGN-/Design-, …) → 0 new failures org-wide, while still catching every case in the issue's fixture and the mutation proof.

    Also: a naive case-insensitive substring handover rule would newly fail a real ADR, Ensemble/Documentation/.../0006-planning-roadmap-handover-authority.md — so the session class must stay anchored, not substring, for the new casings.

    Prototype verified. I built the proposed rule and ran it: the clean fixture prints No AI work records tracked (12 markdown files checked). exit 0; adding Notes/PLAN-something.md fails naming that path; the dirty fixture flags all six missed files and decisions/HANDOVER.md, while DECISIONS.md, decisions/D-0001-example.md and Documentation/decisions/0003-*.md pass.

    QUESTION is not needed for the main body of work, but one sub-decision below needs a yes/no — I've stated my recommendation and a default so the work is not blocked.

    SUGGESTED-TIER: powerful

    Plan — #89

    Settled (do not redo)

    • The gap is reproduced, with the exact cause at verify-no-work-records.yml:52 (root-anchored, all-caps-only) and :61 (no PLAN/DESIGN/REPORT/STATUS class). No further diagnosis needed.
    • Keep the logic inline in the reusable workflow. actions/checkout in a workflow_call job fetches the caller's repo, so .github/scripts/*.sh from this repo is not on the runner — the repo already documents this deliberately (cleanup-pr-artifacts.yml:47). Do not extract to a script file or add a second checkout.
    • The repo's test pattern is: a Python test that extracts the inline run: block from the YAML and exercises it (.github/scripts/tests/cleanup-pr-artifacts.test.py), wired into a verify-*.yml gate with paths: filters. Follow it.
    • The maintainer's comment narrows the issue body: numbered decisions/ records already pass; the work is detection of mixed-case names at any depth. The body's "re-sync AGENTS.md" bullet is stale — AGENTS.md is now a pointer into the managed .cratis/ai/ corpus, which must not be hand-patched from this repo. Drop that bullet.
    • The verified rule shape (prototype run against both fixtures and all 55 org repos) is below.

    Changes

    1. .github/workflows/verify-no-work-records.yml — detection

    Add a documentation-directory allowlist used by the shape rule only, add the shape rule, and extend the session rule with anchored Titlecase forms (keeping the existing uppercase substring forms so nothing currently caught is lost):

    docs='^(decisions|Decisions|Documentation|documentation|docs|Docs|Knowledge)/'
    
    # work-record shape prefixes at any depth, all-caps or Titlecase
    shape='(^|/)(PLAN|Plan|DESIGN|Design|REPORT|Report|STATUS|Status)([-_][^/]*)?\.md$'
    # ... skip .claude/ .github/ .pi/ .agents/ .cratis/ .ai-work/ (same case list as rule 3),
    # ... then `grep -qE "$docs" && continue`, else violation "work-record-style document: $f"
    
    # session class: existing uppercase substrings PLUS anchored Titlecase names
    session='...existing...|(^|/)(Handover|Prompt|Next-Session|Session)([-_][^/]*)?\.md$'

    Constraints that matter and were verified:

    • The docs allowlist exempts the shape rule only, never the session rule — decisions/HANDOVER.md must still fail (issue acceptance).
    • The session class stays anchored for the new casings; a case-insensitive substring would flag Ensemble/Documentation/.../0006-planning-roadmap-handover-authority.md.
    • Prefixes require a -/_ separator or end-of-name, so Reporting.md, Designs.md, Planning.md are untouched. Exact PLAN.md-style names at depth: zero in the org today, safe to include.

    2. Same file — root allowlist

    Add DECISIONS to the rule-2 allow list (a root decisions index is documentation; the issue's acceptance requires it to pass). Verified: no repo in the org has a root DECISIONS.md today, so this hides nothing currently caught.

    3. New input extra-allowed-paths (optional, recommended)

    Comma-separated directory prefixes appended to docs, so a repo with a non-standard documentation folder does not need a code change here. Today's extra-allowed only affects root file names — that limitation is what the issue calls "a growing root allowlist"; two repos already carry extra-allowed values (Chronicle.Dapr: LIFECYCLE,RELEASE,THIRD-PARTY-NOTICES,VERSIONS, Chronicle.Wolverine: VERSIONS). Adding an input is backward compatible; the bootstrapped wrapper needs no change.

    4. .github/scripts/tests/verify-no-work-records.test.py

    Model on cleanup-pr-artifacts.test.py: SHELL = textwrap.dedent(SOURCE.split(" run: |\n", 1)[1] ...), then run it with bash inside tempfile git repos (git init, write files, git add, commit — the guard reads git ls-files, so files must be tracked). Cases, all confirmed against the prototype:

    • clean: README.md, DECISIONS.md, decisions/D-0001-example.md, decisions/index.md, Documentation/decisions/0003-kernel-boundary.md, Documentation/client-snippets/design-for-async.md, Source/Design/overview.md, Source/Reporting.md, Documentation/adrs/0006-planning-roadmap-handover-authority.md → exit 0
    • dirty, one assertion per path with the path named in the output: PLAN-foo.md, Plan-bar.md, REPORT-weekly.md, notes/DESIGN-bar.md, Notes/Plan-something.md, Source/Report-weekly.md, Source/Status-board.md, decisions/HANDOVER.md, docs/Handover.md, .ai-work/anything.md
    • regression: .github/PROMPT-x.md and .cratis/… stay skipped
    • mutation proof from the issue: Notes/PLAN-something.md added to the clean fixture makes it fail naming that file

    5. .github/workflows/verify-work-record-guard.yml

    New gate, copied from verify-pr-artifact-cleanup.yml (pinned actions/checkout SHA, persist-credentials: false, permissions: contents: read, timeout-minutes: 5), paths: covering verify-no-work-records.yml, the new test, itself, and README.md.

    6. README.md

    Add ### verify-no-work-records.yml under Workflows in this repository: the three rule classes, the root-name allowlist, the documentation-directory allowlist, and both inputs (runs-on, extra-allowed, extra-allowed-paths).

    Verification

    • python3 .github/scripts/tests/verify-no-work-records.test.py (offline, no GitHub contact).
    • actionlint on both changed workflows (README line 148 shows the repo's usage).
    • Re-run the org-wide replay before merge (fetch repos/Cratis/<repo>/git/trees/HEAD?recursive=1 for non-archived repos, apply the final regexes): the expected result is 0 new failures. This matters because ~50 repos consume this workflow and most track @main, so a false positive turns their main red on merge.

    Commits / PR

    One branch, separate commits (workflow rule change → tests → gate workflow → README). Label no-release — this repo ships no package and the change is CI-only. Consider landing #90 (exit 2 / --self-test / print counts when green) on the same branch: it touches the same file and the same new test harness, and pull-requests.md says related small changes are one PR. If they are done separately, land #89 first.

    Needs a human decision (one, with a safe default)

    Should all-lowercase design-… / plan-… files be flagged too? The issue comment names design-… explicitly, but the data says it is not free:

    • Recommended (my default, already prototyped): capitalized prefixes only (PLAN-, Plan-, DESIGN-, Design-, …). Catches everything in the issue's fixture and the mutation proof, 0 new failures across all 55 org repos, and matches the original author's stated intent at verify-no-work-records.yml:59 ("uppercase convention only, to spare real docs"). Misses a genuinely lowercase notes/design-bar.md.
    • Full case-insensitivity + the documentation allowlist: turns Strategy's main red today over governance/plan-coverage-audit.md and evidence/design-partner-agreement-checklist.md (that repo tracks @main), and carries standing false-positive risk for any future design-*.md outside a documentation folder. Viable only if those two files are renamed, or Strategy's wrapper gets extra-allowed-paths: governance/,evidence/, before this merges.

    Unless told otherwise, implement the capitalized variant and note the lowercase option in the PR description so it can be turned on later by widening one regex.

    Suggested tier for implementation: powerful


    Posted by Direct (AI) - an autonomous agent, not a person. Review accordingly.

  4. self-assigned this
    on Oct 6, 2026
  5. woksin commented on Oct 6, 2026

    @woksin
    ContributorAuthor

    Completed in #150. The inline check is now a tested script with exit code 2 for could-not-run, counts, mixed-case matching and per-violation annotations, pinned by commit and checksum. Follow-ups: none.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions