Skip to content

fix(mt#4622): Exempt the markdown compile SOURCES from Prettier, not just their outputs - #3381

Merged
edobry merged 4 commits into
mainfrom
task/mt-4622
Aug 26, 2026
Merged

edobry merged 4 commits into
mainfrom
task/mt-4622

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adding a new skill could not be committed: compile --check called its output stale immediately
after compiling it. The cause is not in the compiler.

.prettierignore has exempted the compiled OUTPUT (.claude/skills/, .claude/agents/) since
mt#2555, on the stated grounds that "the compiler emits the source markdown verbatim." That
reasoning constrains both sides of the source==output equality and only the output side was
exempted — so lint-staged went on reformatting the SOURCE, reproducing the same deadlock mt#2555
set out to end, from the other direction:

  • Step 1 — lint-staged runs prettier --write on the staged .minsky/skills/** markdown
    (.lintstagedrc.json matches *.{json,md}), rewriting *italic* → _italic_ and padding table
    cells.
  • Step 9b — runCompileCheck compiles from the now-formatted source, compares against output
    built from the pre-format source, finds it stale, and blocks the commit.

It fires at most once per artifact — on the first commit that stages its source, since Prettier is
idempotent thereafter. That is why it presented as a defect affecting only newly-added skills.

The spec's inherited claim that "no sequence of compiles clears it" is false for this mechanism —
a recompile does clear it, measured. The permanent-wedge version of the same symptom is a different
bug: listOutputFiles keys on the directory name while compile keys on the definition's name, so
a mismatch yields a stale path no compile can produce. claude-agents guards that explicitly
(claude-agents.ts:218, with a comment naming this exact symptom); claude-skills does not.
mt#1118 has owned it since 2026-04-22 and carries a vendored-skill complication this PR does not
— so it is scoped out, not fixed here.

Key changes

  • .prettierignore — exempt the markdown sources by exact filename, mirroring the mt#2555
    block directly above:

    .minsky/skills/**/SKILL.md
    .minsky/skills/**/content.md
    .minsky/agents/**/prompt.md
    
    • Markdown only. skill.ts / agent.ts are code and stay formatted; Prettier does not
      reformat template-literal contents, so formatting them cannot perturb an emitted body.
    • By filename, not **/*.md — see R1 below. A directory-wide glob also exempts markdown nobody
      has written yet.
    • Agents are covered, not just skills. loadMarkdown reads prompt.md verbatim and
      buildAgentMd emits it verbatim — the same defect class. This is the class-not-instance scan
      done at authoring time rather than after a reviewer points at one site.
    • .minsky/rules/ needs no entry — .mdc has no Prettier parser and does not match
      lint-staged's glob. Two independent reasons, both asserted.
    • Recorded as INTERIM at the patterns themselves, retiring under mt#4513 (the principal's
      ask#9976 decision to stop committing compiled trees). With no committed output there is no
      staleness check, at which point this exemption stops being load-bearing and merely leaves 46
      files unformatted. Escalation date noted inline per work-completion.mdc §Temporary mechanism budget.
  • packages/domain/src/compile/prettier-source-exemption.test.ts — the regression test, in two
    halves that are deliberately different in kind:

    • Part 1 (in-memory fs) pins the coupling: reformat a source, change nothing else, and the
      compiled output goes stale. If someone later decides the exemption is unnecessary, this is the
      test that says why it is not.
    • Part 2 (real corpus) pins the fix, asked of Prettier's own resolver (getFileInfo)
      rather than by re-implementing gitignore anchoring — semantics that file's own mt#3880 comment
      records as easy to misread.
    • Neither half alone protects the invariant: Part 1 still passes with the fix reverted, and Part 2
      would still pass if the compiler stopped emitting verbatim.

One deliberate oddity, flagged so it is not "tidied" away. The path constants are written
"./.prettierignore" and "./.lintstagedrc.json". The related-test selector's data-read edge
(mt#4224) is extracted by a regex requiring a / and a dotted extension; a bare
".prettierignore" has neither. Measured — before the ./, bun scripts/run-related-tests.ts .prettierignore selected zero tests, so CI would have been the first signal for a change to the
very file this PR is about (the mt#4367 shape). With it, the selector names this file.

Review round 1

  • BLOCKING — "getFileInfo called with ignorePath as an array, which Prettier does not
    support."
    Verified false positive; not changed. prettier 3.5.3 declares
    ignorePath?: string | URL | (string | URL)[] at node_modules/prettier/index.d.ts:802.
    Discriminating probe, since the type alone is not proof the runtime honours it:

    array [gitignore, prettierignore]: true
    array [gitignore] only           : false   <- second element is what ignores it
    single string prettierignore     : true
    no ignorePath at all             : false
    

    If the array form were dropped, the first two would both be false. The citation and the probe are
    now in the helper's docblock so a re-review need not re-derive them.

  • NON-BLOCKING — patterns may exempt future non-source markdown. Correct, and fixed. The original
    **/*.md would have silently stopped formatting a README added under either tree — no error, no
    failing test, because the census only asserted that sources ARE ignored and never that non-sources
    are not. Patterns narrowed to exact filenames, and the test grew both directions of the
    resulting sync requirement: one test fails and NAMES a new source filename added to the trees but
    not to the pattern list; another asserts a hypothetical README.md/NOTES.md under those trees is
    still formatted, with a control asserting the sibling SKILL.md at the same non-existent depth IS
    exempt (without which a typo'd root would make the whole test vacuous).

  • NON-BLOCKING — Part 2 relies on real repo layout, brittle across moves. Acknowledged and kept
    by design. A census over the real tree is the only thing that can answer "does the real
    .prettierignore cover the real sources"; an injected fs would assert something about the mock.
    This follows the existing precedent at packages/domain/src/persistence/capability-guard-idiom.test.ts,
    which carries the same file-level no-real-fs-in-tests exemption for the same reason. The
    brittleness is bounded: a layout move fails the source trees are actually populated guard loudly
    rather than passing over zero files.

Testing

Execution evidence:

SC1 — cause identified and named. Neither of the spec's two candidates. Compile IS idempotent for
a fixed source and --check compares only what the writer emits; the SOURCE changed underneath the
output, between the compile and the check, from inside pre-commit itself. Reproduced under control in
an isolated scratch workspace (no repo or session mutated), driving the target and checkStaleness
directly so the CLI and MCP surfaces could not confound it:

1. author .minsky/skills/zz-repro/SKILL.md with *italic* + an unpadded table
2. compile                                     -> wrote .claude/skills/zz-repro/SKILL.md
3. check                                       -> STALE: false
4. prettier --write on the SOURCE              -> *italic* -> _italic_, table cells padded
5. check (no recompile — pre-commit Step 9b)   -> STALE: true  .claude/skills/zz-repro/SKILL.md
6. recompile, check                            -> STALE: false      <- falsifies "no sequence clears it"
7. prettier --write again, check               -> STALE: false      <- idempotent; fires once per skill

SC2 / SC4 / AT1 / AT2 — live, against the real toolchain. A throwaway skill with
Prettier-divergent markdown, run through the same commands pre-commit runs, then deleted:

$ shasum .minsky/skills/zz-mt4622-probe/SKILL.md
bd405942529e3f9bb6fb2e5f4afa8eeb6d07f28d
$ bunx prettier --write .minsky/skills/zz-mt4622-probe/SKILL.md    # what Step 1 does
$ shasum .minsky/skills/zz-mt4622-probe/SKILL.md
bd405942529e3f9bb6fb2e5f4afa8eeb6d07f28d                            # unchanged — the fix

Re-run after R1's narrowing, against a REAL committed source rather than a throwaway:

$ shasum .minsky/skills/check-premise/SKILL.md
b56c55bde1a06449feb04adf31ebf968d0414b96
$ bunx prettier --write .minsky/skills/check-premise/SKILL.md
$ shasum .minsky/skills/check-premise/SKILL.md
b56c55bde1a06449feb04adf31ebf968d0414b96                            # unchanged

$ bun run src/cli.ts compile --check --target claude-skills | jq -r '{stale, staleFile}'
{ "stale": false, "staleFile": null }

The verdict is read as a field, not by grepping the output: --check dumps every compiled skill
body, and several of those bodies contain the literal string is STALE in their prose. A regex over
that output reports a stale match on a passing check — the mt#4121 filter trap, hit and corrected
during this work.

SC3 / AT3 — rules and agents, measured then pinned. Agents affected and covered by the fix; rules
not affected, for two independent reasons (no Prettier parser for .mdc, and lint-staged's glob does
not match it). Via prettier --file-info after the change:

.minsky/skills/check-premise/SKILL.md      {"ignored":true,  "inferredParser":null}
.minsky/skills/cockpit-design/content.md   {"ignored":true,  "inferredParser":null}
.minsky/agents/auditor/prompt.md           {"ignored":true,  "inferredParser":null}
.minsky/skills/cockpit-design/skill.ts     {"ignored":false, "inferredParser":"typescript"}
.minsky/agents/auditor/agent.ts            {"ignored":false, "inferredParser":"typescript"}
.minsky/rules/hook-files.mdc               {"ignored":false, "inferredParser":null}
README.md                                  {"ignored":false, "inferredParser":"markdown"}

SC5 — stated, NOT closed, and no follow-up task is owed. The criterion asks the implementation to
say whether the fix closes the index-vs-worktree gap. It does not — --check reads the working
tree while the commit ships the index, so an operator who regenerates without re-staging still
commits drift. The planning pass said a follow-up was owed; that was wrong, and the correction is
the more useful finding.
mt#4513 (principal decision, ask#9976) stops committing the compiled trees
at all, so with no committed output there is no index copy to diverge — the gap dissolves rather
than needing a fix. mt#4513's own SC6 already retires mt#2977 and mt#3134 on exactly this reasoning,
and runCompileCheck is in the same class. Filing a regenerate-and-re-stage task would have built a
mechanism a decided direction removes.

Unit tests:

$ bun test --preload ./tests/setup.ts packages/domain/src/compile/prettier-source-exemption.test.ts
 10 pass  0 fail  20 expect() calls

$ bun scripts/run-related-tests.ts packages/domain/src/compile/targets/claude-skills.ts \
    packages/domain/src/compile/staleness.ts
 62 pass  0 fail  127 expect() calls   (5 related test files)

$ bun scripts/run-related-tests.ts .prettierignore
 1 related test file(s) passed: packages/domain/src/compile/prettier-source-exemption.test.ts

That last run is itself the check on the data-read edge described above — it returned "no related
test files" before the ./ prefix.

Negative control — the .prettierignore exemption, re-run against the NARROWED patterns:

Reverted the entire added block (all three patterns and the comment), not just one line — per
mt#4512, a partial revert leaves a state that is neither pre-fix nor post-fix and can pass for the
wrong reason:

$ sed -i '' '/^# Markdown SOURCES for the skill and agent/,/^\.minsky\/agents\/\*\*\/prompt\.md$/d' .prettierignore
$ grep -c 'minsky/skills/\*\*\|minsky/agents/\*\*' .prettierignore
0
$ bun test --preload ./tests/setup.ts packages/domain/src/compile/prettier-source-exemption.test.ts
(fail) ... > every markdown source under .minsky/skills and .minsky/agents is ignored
(fail) ... > a NON-source .md in those trees would still be formatted
 8 pass  2 fail
$ # restored
 10 pass  0 fail

Two tests flip, and both assert the fix — the census, and the new test's own control assertion
(the sibling SKILL.md is no longer exempt once the patterns are gone). Part 1 keeps passing under
the revert by design: it pins the coupling, which the revert does not change.

What this control does and does not buy. It proves the census can fail and is observing the real
.prettierignore. It does not prove the pattern set is right for a source layout other than today's
— which is precisely why R1's narrowing added the no unrecognised markdown test, so a NEW source
filename fails loudly instead of silently regressing this defect.

validate_typecheck: 0 errors across 8 projects (session workspace; infra/ skipped with its
documented reason, covered by CI). validate_lint: 0 errors / 0 warnings across 4092 files.
format:check exit 0.

Deploy verification

This PR is deploy surface — verified by running the predicate rather than assuming, which is what
the guidance asks for and which my own intuition would have gotten wrong here:

$ bun -e 'import { isDeploySurfaceFile } from "./packages/domain/src/deployment/deploy-surface.ts";
  for (const f of process.argv.slice(1)) console.log(isDeploySurfaceFile(f), f);' \
  .prettierignore packages/domain/src/compile/prettier-source-exemption.test.ts
false  .prettierignore
true   packages/domain/src/compile/prettier-source-exemption.test.ts

A test file under packages/domain/** is application source to the predicate, so no
[no-deploy-impact] tag is claimed anywhere in this PR or its commit messages. Post-merge deploy
verification will run per §10 against the merge timestamp; it is not waived.

Live verification

The SC2/SC4 probes above are the live exercise — real Prettier, the real CLI compile, and the
real --check, run in the session workspace against the real skill corpus. No external-system
integration is added, so there is no credentialed surface to exercise.

One thing surfaced by it, worth knowing independently of this PR: the skill-listing budget is at
17953 / 18000 across 60 skills. A single throwaway probe skill pushed it to 18007 and the compile
printed EXCEEDS SKILL LISTING BUDGET. mt#3803 owns that; mt#4611's raise to 18500 is uncommitted
work sitting in another session. Anyone adding a skill before either lands will trip it.

edobry added 3 commits August 26, 2026 18:05
…just their outputs

`.prettierignore` has exempted `.claude/skills/` and `.claude/agents/` since
mt#2555, on the stated grounds that "the compiler emits the source markdown
verbatim". That reasoning constrains BOTH sides of the source==output equality
and only the output side was exempted, so lint-staged went on reformatting the
SOURCE — reproducing the same deadlock mt#2555 set out to end, from the other
direction:

  Step 1  lint-staged runs `prettier --write` on the staged `.minsky/skills/**`
          markdown, rewriting `*italic*` -> `_italic_` and padding table cells
  Step 9b runCompileCheck compiles from the NOW-formatted source, compares
          against output built from the PRE-format source, and blocks the commit

It fires at most once per artifact — on the first commit that stages its source,
since Prettier is idempotent thereafter — which is why it presented as a defect
that only affected NEWLY-ADDED skills. Reproduced under control in an isolated
workspace; a recompile DOES clear it, so the spec's inherited "no sequence of
compiles clears it" was false for this mechanism.

Fix: exempt the markdown sources, mirroring the mt#2555 block directly above.
Markdown ONLY — `skill.ts`/`agent.ts` are code and stay formatted, and Prettier
does not reformat template-literal contents, so formatting them cannot perturb
an emitted body.

Covers agents too, not just skills: `loadMarkdown` reads `prompt.md` verbatim
and `buildAgentMd` emits it verbatim, so `.minsky/agents/*/prompt.md` is the
same defect class. `.minsky/rules/` needs no entry — `.mdc` has no Prettier
parser AND does not match .lintstagedrc's `*.{json,md}` glob.

The permanent-wedge version of this symptom is a DIFFERENT bug and is not
touched here: `listOutputFiles` keys on the directory name while `compile` keys
on the definition's `name`, so a mismatch yields a stale path no compile can
produce. `claude-agents` guards it; `claude-skills` does not. mt#1118 has owned
that since 2026-04-22 and carries a vendored-skill complication this does not.

The regression test is two halves, both load-bearing: an in-memory half pinning
the COUPLING (reformat the source, nothing else, output goes stale) and a
real-corpus census pinning the FIX, asked of Prettier's own resolver rather than
re-implementing gitignore anchoring. Neither alone protects the invariant.
…e skills/agents half

Success Criterion 3 asks whether rules and agents are affected. Agents are, and
the fix covers them. Rules are NOT — for two independent reasons, and this
asserts both rather than leaving the claim in prose where a later pass could
"helpfully" widen the exemption to cover `.minsky/rules/` (inert at best; if
written as a directory pattern, it would stop formatting real markdown).

Reason 1: Prettier infers no parser for `.mdc`.
Reason 2: lint-staged's glob is `*.{json,md}`, which `.mdc` does not match.

Reason 2 is asserted against the PARSED config's glob keys rather than a
substring scan of the file text, and is paired with a positive assertion that
the markdown glob it reasons about is actually present — so a lint-staged
reconfiguration fails this loudly instead of passing vacuously.
@minsky-reviewer

minsky-reviewer Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 223K prompt, 4K completion | Duration: 63s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Aug 26, 2026

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


The PR addresses the Prettier-vs-compile staleness race by ignoring markdown sources and adds a two-part regression test. However, there are two issues that must be fixed before merge: (1) the new test misuses Prettier’s API by passing an array to getFileInfo’s ignorePath option, which expects a single string path — this risks false assertions and future breakage; and (2) Success Criterion 2 is not satisfied — there is no test or script that adds a real on-disk throwaway skill and verifies compile --check passes post-fix. Non-blocking notes: the broad ignore patterns could unintentionally exempt future non-source markdown under .minsky/skills/** and .minsky/agents/**; and Part 2’s test is brittle due to dependence on repo layout and Prettier’s resolution semantics. Once the Prettier API usage is corrected in both places and SC2 is demonstrated with an actual on-disk add-and-check, the change looks solid.

Findings

  • [BLOCKING] packages/domain/src/compile/prettier-source-exemption.test.ts:72 — Misuse of Prettier API: getFileInfo called with ignorePath as an array, which Prettier does not support
    prettier.getFileInfo(absPath, { ignorePath: [GITIGNORE, PRETTIERIGNORE] }) passes an array for ignorePath (see packages/domain/src/compile/prettier-source-exemption.test.ts, the prettierIgnores helper). Prettier's API expects ignorePath to be a single string (path to one ignore file), not an array. Depending on Prettier's argument validation, this can silently fall back to defaults (making the test assert the wrong semantics) or break when versions change. It also undermines the test's stated intent to pin resolution against the repo's root ignore files.

Fix one of:

  • Omit ignorePath entirely and rely on Prettier's documented default of consulting both root .prettierignore and .gitignore.
  • Or invoke getFileInfo twice (once per ignore file) if you must force a specific path, or combine the contents into a temporary ignore file and pass that single path.

There is a second occurrence with the same misuse later in the file (the rules test’s getFileInfo call) — please correct both.

  • [NON-BLOCKING] .prettierignore:58 — Ignore patterns may unintentionally exempt future non-source markdown (e.g., README.md) under .minsky/skills/** or .minsky/agents/**
    The new patterns /.minsky/skills/**/*.md and /.minsky/agents/**/*.md will ignore every markdown file anywhere under those trees. Today all .md there are compile sources (per the comment), but if a README.md or other authoring doc is later added in those directories, it will silently stop being formatted by Prettier. The comment notes this caveat, but consider strengthening guardrails: e.g., use more targeted patterns (SKILL.md, prompt.md, content.md) if the set is stable, or add a repo policy/test that forbids non-source .md in those trees to prevent accidental exemptions.
  • [NON-BLOCKING] packages/domain/src/compile/prettier-source-exemption.test.ts:47 — Test’s Part 2 relies on actual repo layout and Prettier resolution — can be brittle across moves or tooling changes
    Part 2 asserts the real .prettierignore covers the real source population by reading .minsky/** from disk and asking Prettier’s resolver. This is high-signal now, but it couples the test to repo layout and Prettier’s ignore semantics; unrelated reorganizations (e.g., moving source trees or renaming files) will fail this test even when the underlying invariant (sources are ignored) still holds. Consider mitigating brittleness: centralize the source-tree roots (import from a single config), narrow the census to explicit expected files (SKILL.md, prompt.md, content.md), or gate with a helper that resolves roots the same way compile targets do. Keep the data-read edge hack (./) but isolate layout assumptions.

Spec verification

Criterion Status Evidence
The cause is identified and named: compile non-idempotency, or a --check comparison that reads something the writer does not emit. Met Cause is recorded and enforced via tests: packages/domain/src/compile/prettier-source-exemption.test.ts Part 1 demonstrates that reformatting only the source produces staleness without a recompile (the SOURCE changed between compile and check), and the fix is implemented in .prettierignore to prevent Prettier from rewriting sources.
Adding a new skill, then running compile --target claude-skills, leaves compile --check passing — demonstrated by adding a throwaway skill in a test or a script, not by assertion. Not Met No test or script in the diff adds a throwaway skill under .minsky/skills/** and then asserts compile --check passes. The new test exercises an in-memory FS coupling (Part 1) and a Prettier ignore census (Part 2), but it does not create a real on-disk skill and run compile/check over it. Please add a test or script that authors a temporary skill in a temp workspace or fixture directory and proves --check passes post-fix, per the criterion.
Whether the same defect affects a new RULE and a new AGENT is checked and stated. Met packages/domain/src/compile/prettier-source-exemption.test.ts Part 2 asserts .minsky/rules/*.mdc is unaffected (no Prettier parser and not matched by lint-staged glob) and that agent .minsky/agents/*/prompt.md sources are ignored by Prettier after the change.
Staging a skill source whose markdown Prettier would reformat, then committing, does not block on a stale compile output — demonstrated by exercising the format-then-check sequence, not by assertion. Met The fix is implemented in .prettierignore (added patterns for .minsky/skills/**/*.md and .minsky/agents/**/*.md), which prevents Prettier from rewriting those sources during lint-staged. The test’s Part 1 shows the pre-fix hazard (format-then-check without recompile yields stale), and Part 2 verifies that Prettier would ignore all markdown sources under the two trees, eliminating the sequence. Together they demonstrate that the commit path would not block on this class of change.
The committed OUTPUT matches a compile of the committed SOURCE. The check reads the working tree while the commit ships the index, so a regenerate that is not re-staged still ships drift; state explicitly whether the chosen fix closes this or leaves it open. Met The PR description’s “SC5” section explicitly states the index-vs-working-tree gap remains and that this fix removes the cause (Prettier rewriting sources) but does not close the divergence’s ability to exist. No code changes attempt to alter check semantics. This satisfies the “state explicitly whether” portion of the criterion.

Documentation impact

  • no-update-needed — Change is configuration and tests only: adds Prettier ignore patterns for markdown sources and introduces a regression test. No user-facing CLI, API, or compile semantics changed (targets and behavior unchanged). Searched for docs covering Prettier ignore policy; no dedicated doc found. No invalidated statements detected in CONTRIBUTING.md or README about formatting source markdown under .minsky/skills/** or .minsky/agents/**.

…rd the retirement condition

Reviewer non-blocking, and it was right: `**/*.md` exempted markdown nobody has
written yet. A README added under `.minsky/skills/` or `.minsky/agents/` would
have silently stopped being formatted — no error, no failing test, since the
census only asserted that sources ARE ignored and never that non-sources are
not. Patterns are now the exact filenames the targets read verbatim:

    .minsky/skills/**/SKILL.md
    .minsky/skills/**/content.md
    .minsky/agents/**/prompt.md

Narrowing shifts the failure mode from silent to loud, so the test grew the
other direction of the sync requirement:

- `no unrecognised markdown ...` fails and NAMES a new source filename that was
  added to the trees but not to the pattern list — the case the narrow pattern
  would otherwise let regress.
- `a NON-source .md ... would still be formatted` asserts the reviewer's case
  directly, against hypothetical paths (`getFileInfo` resolves patterns without
  touching disk), with a control asserting the sibling SOURCE at the same
  non-existent depth IS exempt — without which a typo'd root would make the
  whole test vacuous.

Deliberately NOT reusing `SKILL_SOURCE_FILENAMES`: that constant answers "does
the repo own this skill" for the budget cap and is `["SKILL.md", "skill.ts"]` —
includes TypeScript, which must stay formatted, and omits `content.md`, which
must not. Same shape, different question.

Also records what this exemption is: INTERIM, retiring under mt#4513 (the
principal's ask#9976 decision to stop committing compiled trees). With no
committed output there is no staleness check, at which point this stops being
load-bearing and merely leaves 46 files unformatted. Escalation date noted at
the patterns per work-completion.mdc §Temporary mechanism budget.

The BLOCKING finding — "getFileInfo called with ignorePath as an array, which
Prettier does not support" — is a false positive, and NOT changed. prettier
3.5.3 declares `ignorePath?: string | URL | (string | URL)[]` at
node_modules/prettier/index.d.ts:802. Discriminating probe: `[GITIGNORE]` alone
returns ignored:false for an exempted source, `[GITIGNORE, PRETTIERIGNORE]`
returns true — if the array form were being dropped, both would be false. Both
the citation and the probe are now in the helper's docblock so a re-review does
not have to re-derive it.

Negative control re-run against the NARROWED patterns: full revert of the block
fails 2 tests (the census, and the new test's own control), 8 pass; restored,
10 pass.

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


The R2 verification confirms the prior BLOCKING finding is addressed. The ignore patterns were narrowed to the exact source filenames (SKILL.md, content.md, prompt.md) in .prettierignore, eliminating the risk flagged in R1 while preserving the intended exemption. The added test prettier-source-exemption.test.ts robustly covers both the coupling (source reformat induces staleness) and the fix (Prettier’s resolver reports sources ignored; non-sources unaffected). Rules remain unaffected for two independent reasons and are pinned in tests. No new defects or regressions are evident; no public surface changed. Documentation updates are not required. Verdict: APPROVE.

Spec verification

Criterion Status Evidence
The cause is identified and named: compile non-idempotency, or a --check comparison that reads something the writer does not emit. Met packages/domain/src/compile/prettier-source-exemption.test.ts:1-44 — test header and comments explicitly name the cause (Prettier reformats SOURCE between compile and check); Part 1 tests at lines ~120-186 reproduce the coupling and show a recompile clears it.
Adding a new skill, then running compile --target claude-skills, leaves compile --check passing — demonstrated by adding a throwaway skill in a test or a script, not by assertion. Met packages/domain/src/compile/prettier-source-exemption.test.ts:146-184 — in-memory fs creates a new skill source, compiles, and asserts checkStaleness returns stale: false (control) and remains non-stale on recompile after formatting; .prettierignore change at .prettierignore:50-92 ensures real-world --check pass by exempting sources.
Whether the same defect affects a new RULE and a new AGENT is checked and stated. Met packages/domain/src/compile/prettier-source-exemption.test.ts:232-309 — Part 2 tests assert agents are covered (sources ignored) and rules are unaffected for two independent reasons: Prettier infers no parser for .mdc and lint-staged does not target .mdc (see reads of .lintstagedrc.json). .prettierignore adds agents’ prompt.md explicitly at .prettierignore:88-92.
Staging a skill source whose markdown Prettier would reformat, then committing, does not block on a stale compile output — demonstrated by exercising the format-then-check sequence, not by assertion. Met packages/domain/src/compile/prettier-source-exemption.test.ts:166-184 — Part 1 simulates lint-staged rewriting the source without recompile and shows stale: true, then demonstrates stale: false after recompile; combined with source exemptions in .prettierignore:74-92, the pre-commit formatter becomes a no-op for sources, preventing the block.
The committed OUTPUT matches a compile of the committed SOURCE. The check reads the working tree while the commit ships the index, so a regenerate that is not re-staged still ships drift; state explicitly whether the chosen fix closes this or leaves it open. Met PR description and test header in packages/domain/src/compile/prettier-source-exemption.test.ts:1-44 state explicitly that the fix does NOT close the index-vs-worktree gap (SC5) — it removes the cause (Prettier rewriting sources) but does not change --check semantics. The .prettierignore comments at .prettierignore:54-72 reiterate this is an interim exemption.

Documentation impact

  • no-update-needed — Behavioral change is limited to Prettier ignore patterns and added tests. No public API, CLI contract, or user-facing docs are changed. The explanatory comments in .prettierignore document the rationale inline; no docs in docs/ or CONTRIBUTING appear to describe Prettier source-ignore patterns specifically.

@edobry
edobry merged commit 981ca96 into main Aug 26, 2026
20 checks passed
@edobry
edobry deleted the task/mt-4622 branch August 26, 2026 22:32

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


R3 verification: The prior BLOCKING concern (Prettier getFileInfo ignorePath usage) is addressed; the test now documents and confirms array support. The fix narrows .prettierignore to exact markdown source filenames under .minsky/skills/** and .minsky/agents/**, matching the spec and preventing lint-staged from reformatting sources. The new test prettier-source-exemption.test.ts robustly covers both the source→output staleness coupling (in-memory fs) and the real ignore semantics (via Prettier’s resolver), including controls for non-sources and rules. I found no new defects introduced by this change; compile targets remain untouched functionally and no public surface changed. Documentation updates are not required. Verdict: APPROVE.

Spec verification

Criterion Status Evidence
The cause is identified and named: compile non-idempotency, or a --check comparison that reads something the writer does not emit. Met packages/domain/src/compile/prettier-source-exemption.test.ts:22-53 documents and tests the source-reformat → stale-output coupling; Part 1 tests at lines ~121-189 reproduce that a formatted source without recompile yields stale, and a recompile clears it.
Adding a new skill, then running compile --target claude-skills, leaves compile --check passing — demonstrated by adding a throwaway skill in a test or a script, not by assertion. Met packages/domain/src/compile/prettier-source-exemption.test.ts:151-170 — control test compiles from source and asserts subsequent check is not stale; the Prettier exemption in .prettierignore prevents Step 1 from reformatting sources, preserving the pass.
Whether the same defect affects a new RULE and a new AGENT is checked and stated. Met packages/domain/src/compile/prettier-source-exemption.test.ts:245-317 — tests assert agents' prompt.md sources are ignored (covered) and rule sources .mdc are unaffected for two independent reasons (no parser; lint-staged glob excludes .mdc). .prettierignore adds .minsky/agents/**/prompt.md (lines 79-87).
Staging a skill source whose markdown Prettier would reformat, then committing, does not block on a stale compile output — demonstrated by exercising the format-then-check sequence, not by assertion. Met packages/domain/src/compile/prettier-source-exemption.test.ts:171-188 — Part 1 simulates lint-staged’s reformat (formatted source without recompile) and shows it would create staleness; combined with .prettierignore additions at lines 74-87 ensures Prettier ignores these sources so the commit path is unblocked.
The committed OUTPUT matches a compile of the committed SOURCE. The check reads the working tree while the commit ships the index, so a regenerate that is not re-staged still ships drift; state explicitly whether the chosen fix closes this or leaves it open. Met .prettierignore:58-100 comment block explicitly records that the exemption is INTERIM and does not close the index-vs-worktree gap; the test suite focuses on preventing source reformat drift. The spec’s requested statement is present in the PR description and encoded in comments.

Documentation impact

  • no-update-needed — Internal behavior change only: expands .prettierignore to cover markdown sources and adds tests. No user-facing commands, flags, or compile semantics changed. Checked CONTRIBUTING.md and related compile target docs indirectly via inline comments; no documented behavior is invalidated.

edobry added a commit that referenced this pull request Aug 30, 2026
…on, distinct from /handoff

## Summary

`/handoff` writes a durable record for the **next agent**. Nothing addressed the principal who is
**still here** and has lost the thread — stepped away, context-switched, or reading a scrollback
they can no longer see the shape of.

Asking `/handoff` to cover that produces a technically-correct summary answering the least useful
question. It opens with *"shipped mt#X, mt#Y; queue is mt#Z"* — which the principal can already read
off a task list. **What they cannot reconstruct is the chain from what they originally asked for to
whatever is on the screen now**, especially when the work drifted, which is exactly when they ask.

So the skill mandates four parts in order — what / **why** / current state / next — and requires the
WHY to name the originating request and say plainly if the work departed from it.

|          | `/handoff`                               | `/catch-up`                        |
| -------- | ---------------------------------------- | ---------------------------------- |
| Audience | the NEXT agent, or future-you            | the principal, now                 |
| Timing   | conversation end; compaction approaching | any time the thread is lost        |
| Output   | a durable memory record + a chat pointer | chat prose only, nothing persisted |
| Assumes  | the reader shares the motivation         | the reader has LOST the motivation |

## Key changes

- **`.minsky/skills/catch-up/SKILL.md`** + its compiled output. Two disciplines it inherits rather
  than invents:
  - **Re-derive statuses via `refs_status`; never recall them.** Not diligence theater — in the
    conversation that produced this skill, mt#4541 was filed mid-session and went TODO → DONE under
    another actor before the catch-up was written. A recalled status would have reported finished
    work as pending.
  - **Lead with the correction.** If something the principal was told earlier turned out wrong, the
    catch-up is where it gets fixed, near the top, not buried.
- **Explicitly NOT auto-triggered.** Unlike `/handoff`, nothing about conversation shape reveals
  that the principal has lost the thread — only they know. And it persists nothing; `/handoff` owns
  the durable path (mt#2911). Doing both is called out as the anti-pattern.
- **`LISTING_TOTAL_TARGET_CHARS` 18_000 → 18_500**, on an explicit principal decision. The old value
  left 47 chars of headroom at 60 skills, so the next skill of any kind overflowed it. Three options
  were put to him — truncate the five vendored over-cap descriptions at compile time, trim an owned
  sibling, or raise the cap — and he chose to raise it. The docblock records the rejected options and
  tells the next reader to **read the headroom, not the number**: 18_500 buys roughly one more skill,
  so raising it again is not the reflex to reach for.

## Testing

Execution evidence:

**SC1 — skill exists, `user-invocable: true`, description names real trigger phrasings.** Verified
against the COMPILED output, not the source:

```
$ grep -n 'user-invocable' .claude/skills/catch-up/SKILL.md
10:user-invocable: true
```

Its `description` carries "catch me up", "what were we doing", "where are we", "remind me why", and
"a summary request while work is in flight".

**SC2 / SC3 / SC4 — skill body content.** Prose criteria, satisfied by the shipped file and visible
in this diff: the four-part answer with WHY named as the part `/handoff` omits (§The four parts);
the re-derive-don't-recall requirement with mt#4541 as its worked example (§Re-derive statuses);
and the explicit boundary against `/handoff`, `/retrospective` and `/incident-memo` (§Why this is
not `/handoff`).

**SC5 — compiled output regenerated and committed, verified rather than trusted.**

```
$ git status --porcelain
                            # empty — nothing left uncommitted

$ head -3 .claude/skills/catch-up/SKILL.md
---
# Generated by minsky compile. Do not edit directly.
name: catch-up

$ git show --stat --oneline HEAD
69d117d feat(mt#4611): Add /catch-up — re-orient the principal mid-conversation
 .claude/skills/catch-up/SKILL.md                | 90 ++++++++++++++
 .minsky/skills/catch-up/SKILL.md                | 89 ++++++++++++
 packages/domain/src/compile/skill-listing-budget.ts | 20 ++++-
```

**One deviation from SC5's stated method, stated rather than papered over.** The criterion asks for
the per-target `Target "<name>": N file(s) written` line. This CLI invocation does not emit it — it
prints a JSON result object plus two `[compile]` report lines, and the human-readable per-target line
belongs to a different output mode. Rather than fabricate it, the stronger available check is used:

```
$ bun run src/cli.ts compile --check --target claude-skills | jq -r '{stale, staleFile}'
{ "stale": false, "staleFile": null }
```

`--check` compares output CONTENT against a fresh compile of the source. A write count only asserts
that something happened; this asserts the committed output equals what the committed source
produces, which is what SC5 is actually for.

**The budget, measured after the change** — the criterion the cap raise exists for:

```
[compile] skill listing: 18263 chars across 61 skills (target <= 18500)
[compile]   vendored over cap (upstream-owned, not enforced): impeccable (895),
            tailwind-v4-shadcn (484), motion-framer (472), seo-skill (470), plan-design-review (425)
```

18,263 is **over the old 18,000 cap**, so this change is load-bearing rather than cosmetic: without
it the compile reports `EXCEEDS SKILL LISTING BUDGET`, and an over-budget listing silently drops
descriptions — which makes a skill listed name-only unroutable. The second line is the declined
alternative, still on the table: those five vendored descriptions total ~2,746 chars.

**Nothing pinned the old constant:**

```
$ bun scripts/run-related-tests.ts packages/domain/src/compile/skill-listing-budget.ts
 46 pass  0 fail  106 expect() calls   (3 related test files)
```

**This commit was blocked for five days, and the unblocking is itself verified.** mt#4622 (merged
2026-08-26, PR #3381) fixed the cause: lint-staged reformatted the staged `SKILL.md` **source** at
pre-commit step 1, and the staleness check at step 9b then rejected output built from the pre-format
source. Confirmed here after `session_update` brought the fix into this 5-day-old session:

```
$ grep -c 'minsky/skills/\*\*/SKILL.md\|minsky/agents/\*\*/prompt.md' .prettierignore
2                       # the mt#4622 exemption is present
```

and the commit that had failed repeatedly now passes pre-commit and pushes clean. That is end-to-end
evidence for mt#4622's fix as much as for this task.

No new test file is added — this ships a skill definition and a constant, and the constant's effect
is a property of the live 61-skill corpus rather than of a fixture, so the compile output above is
the assertion.

## Deploy verification

Deploy surface — verified by running the predicate rather than assuming:

```
$ bun -e 'import { isDeploySurfaceFile } from "./packages/domain/src/deployment/deploy-surface.ts";
  for (const f of process.argv.slice(1)) console.log(isDeploySurfaceFile(f), f);' \
  .minsky/skills/catch-up/SKILL.md .claude/skills/catch-up/SKILL.md \
  packages/domain/src/compile/skill-listing-budget.ts
false  .minsky/skills/catch-up/SKILL.md
false  .claude/skills/catch-up/SKILL.md
true   packages/domain/src/compile/skill-listing-budget.ts
```

The constant lives under `packages/domain/**`, so this IS deploy surface and no `[no-deploy-impact]`
tag is claimed anywhere in this PR or its commit message. Post-merge deploy verification runs per
§10 against the merge timestamp; it is not waived.

## Live verification

The compile runs above are the live exercise — the real CLI against the real 61-skill corpus in the
session workspace. The skill's own behaviour is prose instruction to an agent, so there is no runtime
surface to probe beyond its presence in the compiled listing, which the 61-skill count confirms.

Co-Authored-By: minsky-ai[bot] <minsky-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant