Skip to content

review: an onboarding playbook and a consumer-config checker for new consumer repos - #317

Merged
jwbron merged 10 commits into
mainfrom
review-onboarding-skill-and-checker
Aug 20, 2026
Merged

review: an onboarding playbook and a consumer-config checker for new consumer repos#317
jwbron merged 10 commits into
mainfrom
review-onboarding-skill-and-checker

Conversation

@jwbron

@jwbron jwbron commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

Two pieces of onboarding infrastructure for standing the shared reviewer up in a new
consumer repo, in the shape of Khan/kore-marketplace#3:

  • workflows/review/lib/check-consumer-config.ts (plus lib/frontmatter.ts): a
    checker that validates a consumer's install before its first PR.
  • .claude/skills/review-onboarding/SKILL.md: the playbook that produces the install,
    carrying the judgment calls the commands can't.

No change to the shipped review workflow. review.md, the dispatcher, and every routing
default are untouched; nothing here runs in a review.

Why a checker at all

Onboarding is five hand-written config files, two local edits to the installed
review.md, and a few admin blockers. Almost every mistake in that set fails late and
quietly:

Mistake How it surfaces today
A missing required {{#runtime-import}} Runtime import file not found, on someone's PR. Not at gh aw compile time.
add-reviewer defined in review.md too Compiles clean. The main workflow overrides the import, so the team allowlist is silently discarded.
A ${{ }} expression inside a runtime import gh-aw rejects it.
A live observability: block without both GH_AW_OTEL_SENTRY_* secrets The agent job dies at startup rather than skipping trace export (observed on #241).
The shipped max-ai-credits: 1000 on a tier=high repo Runs die at ~1001-1024 metered credits after computing a verdict, before posting it.
A well-formed ROUTING typo (plugin/** for plugins/**) Parses perfectly and routes nothing. Invisible.
A .lock.yml not marked linguist-generated The reviewer line-reviews its own compiled output.

How it stays honest

It never reimplements routing semantics. Tiers, lenses, generated-file classification
and the budget come from route(); ROUTING from parseRoutingConfig;
.gitattributes from parseGitattributesGenerated. So a release that changes those
semantics changes the checker's answers for free.

That only holds if the checker and the consumer agree on a version, so it ships in
lib/ (beside the code it calls, released under the same tag) and is documented as
run from the tag the consumer pins. A mismatch is itself one of its warnings:

[source-ref-mismatch] .github/workflows/review.md pins `review-v1.7.0`, but this checker
ran from review v1.11.0: the semantics validated here may not be the ones this repo's
reviews run.

Two views answer the question a tier map actually raises. --files-from (fed by
git ls-files) resolves every tracked file, names the files no rule matched, and names
patterns that match nothing. --explain <path> lists every matching rule in file order,
so last-match-wins ordering is visible rather than inferred:

Explanation: plugins/kore-support/README.md
  tier    trivial
  rules   (last one wins)
            plugins/**  tier=high
            **/*.md  tier=trivial
            README.md  tier=medium
            plugins/*/README.md  tier=trivial

This replaces the hand-verification kore-marketplace#3 did by importing the parser
ad hoc, which its own PR body flagged as the step it could not show.

lib/frontmatter.ts is the minimal structural reader the checker needs (indentation,
key:, - item; no YAML dependency in lib/). One behaviour is load-bearing rather
than incidental: comment lines are dropped, so a commented-out block reads as absent,
which is exactly what disabling observability: means.

The skill

The mechanical half of onboarding is gh aw add; the rest is judgment, and that is what
the skill carries: what stays the operator's call (team allowlist, enable roster,
re-review mode, every admin blocker), the preflight inventory that becomes
ci-tooling.md and skills.md, the local edits and when each applies, how to derive
tiers from the repo's own blast radius rather than another consumer's file, and a PR-body
structure that separates what is generated from what was decided (including what the
author did not verify).

Two boundaries worth flagging for review, because they are deliberate:

  • It never handles the ANTHROPIC_API_KEY value. Not from Keeper, another repo, the
    environment, or chat, and never echoed into a command. It prints gh secret set for
    the operator to run (which prompts, so the value never enters a transcript) and
    confirms afterwards by name only. Granting team access and creating the opt-out label
    are in the same print-and-stop block.
  • It does not merge the PR it opens. The blockers are the operator's to clear, and
    the PR is the install's own first live test.

What this does not do

The checker proves your rules parse, fire, and resolve where you think. It cannot tell
you a tier is wrong: plugins/** tier=low is a defensible-looking line it will
happily confirm. That judgment stays with the author and the reviewer, and Step 4 of the
skill says so.

Testing

  • 43 new tests (check-consumer-config.test.ts, frontmatter.test.ts). Each checker
    case is one way an install fails silently in production; routing semantics are not
    re-tested here, since router.test.ts owns those.
  • Verified against this repo (430 tracked files: 98 high, 1 medium, 309 low, 19
    trivial, 3 generated) and against kore-marketplace's real 48-line ROUTING over
    its actual tree (zero dead patterns, 14 high, README medium, plugin README trivial,
    3 falling to the default low).
  • The glob-dialect claims in Step 4 are pinned by a test rather than trusted from memory,
    after checking lib/glob-match.ts: a pattern with no / matches the basename in any
    directory (so README.md also matches docs/sub/README.md), and a leading / anchors
    to the root.
  • pnpm typecheck and eslint workflows/review/lib/ are clean.
  • Pre-existing local failures unrelated to this branch, confirmed by stashing these
    files: eval/live-ab.test.ts and eval/rereview-sweep.test.ts (need fetched tags) and
    lib/dispatch-runner.test.ts (Cannot find package 'zod' under lib/node_modules).

Follow-ups, not in scope here

  • This repo's own install pins review-v1.7.0 while the package is at 1.11.0. The
    checker surfaced it. Bumping a live reviewer install seemed like its own decision.
  • The mechanical half could become a script (scaffold the five config files from
    templates, delete the gh aw Copilot leftovers, write .gitattributes, apply the two
    review.md local edits), which would shrink the skill to judgment only.
  • The thumbs-sweep and live-counters workflows are listed as an explicit decision in the
    skill's optional-surfaces step rather than automated.

…and a consumer-config checker for new consumer repos

Onboarding a repo onto the shared reviewer is five hand-written config files,
two local edits to the installed review.md, and a handful of admin blockers only
a repo admin can clear. Every mistake in that set fails late and quietly, which
is what both halves of this change address.

lib/check-consumer-config.ts validates an install through the production parsers
rather than a reimplementation: route(), parseRoutingConfig, and
parseGitattributesGenerated. It ships in lib/ and is run from the tag the
consumer pins, so a release that changes those semantics changes its answers for
free (and a mismatch between checker and pin is itself one of its warnings).

lib/frontmatter.ts is the minimal structural frontmatter reader it needs; no YAML
dependency in lib/. Comment lines are dropped, so a commented-out block reads as
absent, which is exactly what disabling observability: means.

.claude/skills/review-onboarding/SKILL.md is the judgment half: what stays the
operator's call, the preflight inventory that becomes ci-tooling.md and
skills.md, the local edits and when each applies, how to derive tiers from the
repo's own blast radius, and the PR-body structure that separates what is
generated from what was decided.

No change to the shipped review workflow.
@changeset-bot

changeset-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7d1f4c1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
review Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review live A/B

No reviewable delta: review.md is byte-identical in both arms (baseline origin/main, sha 5f7bced4ba2e), so the extracted prompts and the orchestrator body match and no arms were run. Pass --force-arms for a deliberate wobble control.

@khan-actions-bot
khan-actions-bot requested review from a team, jaredly and somewhatabstract and removed request for a team August 3, 2026 19:31
@jwbron
jwbron marked this pull request as draft August 3, 2026 19:37

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes requested — see inline comments.

Comment thread workflows/review/lib/frontmatter.ts
Comment thread workflows/review/lib/check-consumer-config.ts Outdated
Comment thread workflows/review/lib/check-consumer-config.ts Outdated
Comment thread workflows/review/lib/check-consumer-config.ts Outdated
Comment thread workflows/review/lib/check-consumer-config.ts
Comment thread workflows/review/lib/check-consumer-config.ts
Comment thread workflows/review/lib/check-consumer-config.ts
Comment thread workflows/review/README.md
Comment thread workflows/review/lib/frontmatter.ts
It reads the install through the *production* parsers (`route()`,
`parseRoutingConfig`, `parseGitattributesGenerated`), so it never drifts from
what a review actually does, and it reports the whole class of mistakes that
otherwise surface as a red run on someone's PR: a missing runtime import, a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thought (non-blocking): The check catalog is restated in prose in four places (changeset, README, SKILL Step 5, module docstring). Since the issue codes are fixed tokens, a single generated enumeration (e.g. a --list-checks flag) would keep the docs from drifting as checks are added or renamed.

jwbron added 2 commits August 3, 2026 16:58
…ng team say so, and mark the other generated workflow

Two fixes from onboarding Khan/agent-settings (Khan/agent-settings#48), the first
real consumer this skill and checker drove end to end. Both are cases where the
checker was right about the mechanism and wrong about the verdict.

An empty `allowed-team-reviewers` was a hard error. But `.github/REVIEWERS` is the
router's ONLY source of team ownership, so in a repo without one, Step 8 derives no
owners and no ranked fallback and requests nobody no matter what the allowlist
says. There the empty allowlist is an accurate "this repo does not request
reviewers", not a dropped request, and erroring only pressures the author into
naming an inert team to satisfy the check -- which is what agent-settings did on
its first pass, entering a team with no access to that repo. It is now an error
only when REVIEWERS exists (requests get computed, then silently dropped) and a
`reviewer-requests-inert` warning when it does not, reported because the state is
otherwise invisible on the PR. `config-no-bot-token` goes quiet in the same case,
so a deliberate no-requests install reports one finding rather than two.
REVIEWERS_PATH is exported from router.ts so the checker asks the router for that
fact rather than restating the path.

The `.gitattributes` marker the skill prescribes covers `*.lock.yml`, but
`gh aw compile` also writes `agentics-maintenance.yml` (~600 lines, regenerated
unconditionally -- deleting it does not stick), and that name does not match. So a
repo that followed the instructions exactly still had the reviewer line-reviewing
compiler output. The checker now flags it, and the skill prescribes both markers
and warns not to mark `review.md`, which is the hand-editable source.

Also from the same onboarding, in the skill: `gh aw add` records the resolved
commit SHA rather than the tag you asked for, so `source:` needs normalising or it
reads as `source-ref-mismatch`; `git ls-files` lists only TRACKED files, so an
unstaged `.github/aw/review/` makes every rule pointing at it look like it matches
nothing; `.gitattributes` markers must sit outside any machine-managed block in
repos that have one; `gh aw compile` can stop on its safe-update secret gate; and
`gh aw add` fails outright when a placeholder file sits where it wants a directory.
… checker's rendering into its own module

The previous commit's additions pushed check-consumer-config.ts to 1026 lines,
over the repo's 1000-line max-lines budget, and carried two prettier/escape
errors in the test. CI caught all three.

Rather than shave comments to squeeze under the cap by a few lines, this takes the
split the file's own docstring already implies ("the report is data, rendered
separately"): renderReport moves to check-consumer-config-report.ts, the same
by-concern split glob-match.ts took from router.ts for the same reason. The
checker re-exports it, so it stays the single entry point and no importer changes.
That leaves 901 lines and headroom for the next check, instead of a file that
tips over again on the next addition.

Verified with the repo's own eslint and prettier this time (the earlier run could
not resolve plugins from inside a nested worktree, which is why the lint errors
reached CI): eslint clean over workflows/, utils/, actions/, config/ and types/,
prettier clean, 113 tests passing, tsc clean.
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

12 of 12 prior review threads are still unaddressed as of 3cbfc7b:

11 non-blocking threads still open
Note: skill-auditor not assessed this run (skill-auditor output unavailable). Note: test-adequacy not assessed this run (test-adequacy output unavailable). Note: divergence tripwire re-armed a full review (unreviewed share 1.00).

Comment thread workflows/review/lib/check-consumer-config.ts Outdated
Comment thread workflows/review/lib/check-consumer-config.ts Outdated
Comment thread workflows/review/lib/check-consumer-config.ts
Comment thread workflows/review/lib/check-consumer-config.ts Outdated
installed.maxAiCredits <= SHIPPED_MAX_AI_CREDITS
) {
warn(
"max-ai-credits-default",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thought (non-blocking): Two of the checker's warnings compensate for shipped defaults this repo owns and could just fix upstream. I checked the shipped workflows/review/review.md: it carries max-ai-credits: 1000 (line 313) and a live observability: block (line 163). The PR's own evidence says 1000 is known to kill every tier=high run after computing a verdict, that both known consumers raise it to 2500, and that 2500 is 'a ceiling, not a spend' — by that argument raising the shipped default is strictly safe and removes the failure class, the local edit, the env mirror, and this warning for all consumers at once. Likewise, shipping observability: commented out (or documented as opt-in) would turn 'a missing secret kills the agent job at startup' from a per-consumer trap into a non-event. The PR lists bumping this repo's own pin as a follow-up but not fixing the defaults that generate the checker's work.

A sketch, not a committable replacement:

Keep the checks as a backstop, but file/land the upstream fixes: ship `max-ai-credits: 2500` and an inert-by-default `observability:` block in review.md, so these two warnings become legacy-install-only.

…not be copied, and prefer an org secret

Two additions to the onboarding skill's secret handling, prompted by "can you
pull the key from webapp and push it to agent-settings?".

It cannot be done, and saying so explicitly is worth a paragraph: Actions secrets
are write-only (encrypted against the repo public key on write, decrypted only
into a runner), the single-secret GET returns name and timestamps with no value
field, and there is no `gh secret get`. A future reader should not spend time
looking for that path or assume the prohibition is only policy.

More usefully, the per-repo paste is avoidable. An organisation secret with
selected-repository visibility turns every future onboarding's hardest blocker
into an org admin adding one repo to a list, with no key material moving and
nothing for this skill to handle. The skill now checks what the target repo can
already see via the organization-secrets endpoint, and distinguishes three
outcomes: already provisioned (no blocker), org secret exists but this repo is
not scoped to it (an org-admin list change), or repo-level paste as the last
resort.
…to read, and document the update flow

Two gaps in the onboarding skill, both found by using it.

Step 7 asked for a "what's autogenerated" table but never told the author to say
what is LEFT. An install PR is a few thousand lines of which all but a few hundred
are compiler output, so a reviewer who is not handed the split either reviews the
wrong file or skims everything. It now requires naming the human-written part (the
five config files, the .gitattributes lines, the local edits), pointing at where
the judgment is concentrated, and flagging two counter-intuitive details: the
.gitattributes markers make GitHub collapse the generated files, so a small-looking
diff is the marker working rather than a small change; and actions-lock.json is
generated but deliberately not marked, because which third-party action code runs
in CI is worth reading.

The skill's own description claimed it covered "refreshing or auditing an existing
install" while having no steps for it. There is now an Updating section: read the
changelog between the two versions first (semver is a behaviour contract, so a
minor can change what gets reviewed), then verify each local edit survived the
3-way merge (checking max-ai-credits and its env mirror together, since a merge can
update one), then look for newly generated files needing a .gitattributes marker
(how agentics-maintenance.yml arrived), then re-run the checker from the NEW tag and
diff its tier preview against the old one, then re-read ci-tooling.md and skills.md
against the repo as it is now -- the parts that rot silently, where a stale
skills.md path degrades skill-auditor on every review.

Also: bump one minor at a time when far behind, so each changelog explains its own
diff.
jwbron added a commit that referenced this pull request Aug 4, 2026
…ng to the router's default

The consumer-config checker (#317) reported that 27 of this repo's 430 tracked
files matched no `tier=` rule and silently took the router's default `low`. Most
were harmless, but one group was not.

`workflows/autofix/lib/*.ts` was `low`. That code decides what the autofix workflow
does and it PUSHES COMMITS to PRs, so it has at least the blast radius of
`workflows/review/lib/**` sitting next to it at `high`. It was low purely because
no rule named it. Also now `high`: `.github/aw/actions-lock.json` (the SHA pins for
every third-party action our compiled workflows run), `.github-staging/**` (a
workflow staged for a human `git mv` into .github/workflows/ is CI the moment it
moves, and is reviewed here or nowhere), and `workflows/*/package{,-lock}.json`
(review.md's pre-agent step runs `npm ci` against the released lockfile inside a
consumer's CI, so a dependency added here executes in every consuming repo).

Medium for config that shapes releases, review routing, or what the reviewer can
see, without being executable: .changeset/config.json, .github/REVIEWERS (the only
source of the router's team ownership), .gitattributes (which files the reviewer
skips as generated), root package.json, and tsconfig.json.

Low, explicitly rather than by default, for the dev-only remainder: lint/format/test
wiring, type shims, .github/NOTIFIED, and eval output.

Checker now reports 0 errors and 0 warnings over all 430 files; high goes 100 -> 109.
Each tier is spot-checked with --explain rather than inferred, including that
autofix's own tests still fall to low via `**/*.test.ts`.
…alues, so valid YAML stops reading as broken

Addresses the review feedback on this PR. The blocking finding and three of the
non-blocking ones are one root cause: the frontmatter reader returned raw text, so
several valid YAML spellings read as absent or unparseable. For a checker whose
contract is "errors must be zero", a false error is the worst failure mode, and
one of these fired on the flow the skill itself prescribes.

- Inline comments are stripped from values and list items, respecting quotes and
  `url#frag`. SKILL.md Step 2 tells authors to label every local edit with a
  comment, so `max-ai-credits: 2500 # LOCAL OVERRIDE` was the expected shape --
  and it made Number() return NaN, silently suppressing the credit-ceiling check
  (NaN <= 1000 is false). A labelled `source:` reported a spurious
  source-ref-mismatch, and a labelled `imports` item produced a false
  workflow-missing-config-import error, which exits 1 on a valid install.
- scalar() strips surrounding quotes, so `max-ai-credits: "1000"` parses instead
  of yielding NaN.
- importsConfig compares through items(), so a quoted import item is not read as
  a missing import.
- New list() reads both block and flow style. `allowed-team-reviewers: [kore]` is
  the spelling the shipped review.md uses for `toolsets: [pull_requests, repos]`,
  and it was reporting a false config-empty-team-allowlist error.

list() returns undefined only when the key is ABSENT, and that distinction fixes
something the previous commit got wrong. It treated any empty allowlist as the
deliberate no-requests configuration, which meant a flow-style allowlist the
reader could not parse would have been reported as "that is a valid
configuration" -- an all-clear over an allowlist the safe output was dropping.
Now a present-but-empty key is always an error, and only an absent key defers to
.github/REVIEWERS.

13 new frontmatter tests and 5 new checker tests, each one a spelling that
previously produced a false error or a silently-suppressed check. Verified
against the real Khan/agent-settings install: still 0 errors, and the one warning
is the deliberate reviewer-requests-inert note.

Not addressed here, and left as open threads: whether lib/ should take a real
YAML dependency rather than hand-rolling this, the four-way restatement of the
check catalog, and several test-coverage notes.
jwbron added a commit that referenced this pull request Aug 5, 2026
…322)

* [review-tier-unrated-paths] review: tier the 27 paths that were falling to the router's default

The consumer-config checker (#317) reported that 27 of this repo's 430 tracked
files matched no `tier=` rule and silently took the router's default `low`. Most
were harmless, but one group was not.

`workflows/autofix/lib/*.ts` was `low`. That code decides what the autofix workflow
does and it PUSHES COMMITS to PRs, so it has at least the blast radius of
`workflows/review/lib/**` sitting next to it at `high`. It was low purely because
no rule named it. Also now `high`: `.github/aw/actions-lock.json` (the SHA pins for
every third-party action our compiled workflows run), `.github-staging/**` (a
workflow staged for a human `git mv` into .github/workflows/ is CI the moment it
moves, and is reviewed here or nowhere), and `workflows/*/package{,-lock}.json`
(review.md's pre-agent step runs `npm ci` against the released lockfile inside a
consumer's CI, so a dependency added here executes in every consuming repo).

Medium for config that shapes releases, review routing, or what the reviewer can
see, without being executable: .changeset/config.json, .github/REVIEWERS (the only
source of the router's team ownership), .gitattributes (which files the reviewer
skips as generated), root package.json, and tsconfig.json.

Low, explicitly rather than by default, for the dev-only remainder: lint/format/test
wiring, type shims, .github/NOTIFIED, and eval output.

Checker now reports 0 errors and 0 warnings over all 430 files; high goes 100 -> 109.
Each tier is spot-checked with --explain rather than inferred, including that
autofix's own tests still fall to low via `**/*.test.ts`.

* [review-tier-unrated-paths] review: re-raise staged .md workflows, and align the prose with ROUTING

Two review findings on #322.

`.github-staging/** tier=high` was silently defeated for `.md` files. Tier
resolution is last-match-wins, and the broad `**/*.md tier=trivial` rule sits
below it, so a staged agentic workflow's `.md` source (the frontmatter carrying
permissions, secrets and network, plus the prompt) routed as trivial while only
the compiled `.lock.yml` got `high`. That is backwards, and it defeats the rule's
whole reason for existing. `.github/workflows/*.md tier=high` already exists to
re-raise workflow sources after the docs rule; this adds the same re-raise for
the staging pen. Verified with the repo's own parseRoutingConfig + matchesGlob:
`.github-staging/workflows/review.md` went trivial -> high, and
`.github-staging/**/*.md` matches at depth 1 as well as deeper. The sweep over
all 430 tracked files is byte-identical before and after (111 high, or 109 after
the generated-file exemption), confirming the rule is purely prospective: nothing
under `.github-staging/` is a `.md` today.

risk-classification.md (model-facing prose, injected into the same reviewer) had
drifted from ROUTING and this PR would have codified the split. Aligned: the
autofix lib, the staging pen, `workflows/*/package{,-lock}.json` and the gh-aw
action lockfile move to High; `config/`, `types/`, `.eslintrc.js` and
`pnpm-workspace.yaml` drop to Low, where ROUTING already puts them and where
their actual contents belong (vitest setup and ambient `.d.ts` shims);
`tsconfig.json` stays Medium on its own since it decides typecheck coverage;
`.changeset/config.json`, `.github/REVIEWERS`, `.gitattributes` and the
agent-steering prose dirs are named at Medium.

Left alone deliberately: `workflows/review/eval/*.ts` is Medium in the prose and
`low` in ROUTING. That divergence predates this PR and resolving it is a real
tier decision about eval review depth, not a consistency edit.
* [review-gitattributes-negation] review: honour linguist-generated=false the way git does

The router read .gitattributes as a set of "generated globs" and asked whether a
path matched any of them. Git resolves an attribute per path by the LAST matching
line, so a negation after a broad glob is how a repo says "this subtree is
generated, except this part". Parsing discarded those negations, so the reviewer
silently skipped review of exactly the files a repo had gone out of its way to
keep visible.

parseGitattributesGenerated now returns ordered GeneratedRule[] instead of a flat
pattern list, keeping negations rather than dropping them, and isGenerated scans
in reverse and returns the first matching rule's verdict. A line that never
mentions the attribute is not a rule at all, so it cannot shadow one.
RouterConfig.generatedPatterns is renamed to generatedRules to match what it now
carries.

Found onboarding Khan/agent-settings (Khan/agent-settings#48), whose
.gitattributes marks the installer-written .claude/**, .codex/**, .cursor/** and
.pi/** output generated and then un-marks .claude/skills/** and .pi/git/** with a
comment saying to keep them visible. Its new-repo-config skill -- executable prose
steering every Claude session in that repo -- resolved to `trivial (generated)`
and was excluded from review. It now resolves to `medium`.

Blast radius is narrow and one-directional (strictly more review, never less):
only a repo that writes a negation after a broader =true glob changes at all.
Khan/actions and Khan/frontend have none, and webapp's single negation counters
Linguist's content heuristic rather than an earlier .gitattributes rule, so no
earlier rule matches that path and its classification is unchanged.

The gitattributes tests move to their own file, as credit-cap and lens-payloads
did, because adding the ordering cases put router.test.ts over its 1000-line cap.

Stacked on #317: the rename touches check-consumer-config.ts, which only exists
on that branch.

* [review-gitattributes-negation] review: treat `!linguist-generated` as a negation too

Git has four attribute states, and three of them mean "not generated":
`-attr` (Unset), `attr=false` (the value `false`), and `!attr`
(Unspecified). The parser recognised the first two but not the third, so a
`!linguist-generated` line mentioned the attribute without becoming a rule:
it was dropped, an earlier broad `=true` glob still won by last match, and
the path stayed excluded from review. That is the same silent skip this
branch fixes for the other two forms, reached by a different spelling.

Verified against `git check-attr` (2.55.0) rather than inferred: with
`.claude/** linguist-generated=true` followed by
`.claude/skills/** !linguist-generated`, git reports SKILL.md as
`unspecified` and `.claude/hooks/h.mjs` as `true`, and the router returned
`true` for both before this change.

Unspecified is not the same as false in general, since it is where Linguist
falls back to its content heuristic. This router has no content heuristic
and treats an unmatched path as source, so the two reach the same verdict
here and share a branch; the doc comment says so, because otherwise the
collapse reads as a semantic error.

The isGenerated ordering test becomes a table over all three negation forms,
so no form can regress to being silently unrecognised.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved — no blocking issues found.
Note: correctness-reviewer not assessed this run (correctness-reviewer output unavailable).
Note: test-adequacy not assessed this run (test-adequacy output unavailable).
Note: completeness not assessed this run (completeness output unavailable).
Note: holistic not assessed this run (holistic output unavailable).
Note: first-principles not assessed this run (first-principles output unavailable).
Note: conventions not assessed this run (conventions output unavailable).
Note: documentation not assessed this run (documentation output unavailable).
Note: thread reconciliation not assessed this run (thread-reconciler output unavailable).
Note: divergence tripwire re-armed a full review (unreviewed share 0.89).

…R body, and route key provisioning through IT

Two fixes from reading the PR bodies this skill actually produces
(Khan/agent-settings#48).

Step 7 prescribed seven sections and got them: the resulting install PR body
was long enough that nobody reads it, which defeats its one purpose. It now
asks for exactly three things -- what was auto-generated (the table), what was
hand-written and the rationale for each judgment call, and the ordered
blockers -- with everything else capped at a sentence.

The key handling told the operator to hand a repo admin a 'gh secret set'
command, but at Khan the key is created and installed by IT, not by whoever is
onboarding. A new 'Provisioning the ANTHROPIC_API_KEY' section at the end
states the actual flow: request in #it naming the repo, IT creates the key and
adds it to the repo's Actions secrets (or scopes the repo to an existing org
secret), operator confirms by name only. 'What stays human' now points there
instead of at the paste command.
@jaredly

jaredly commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

tested it out on the mobile repo here https://github.com/Khan/mobile/pull/4114 looks very cool!

…on feedback

Review feedback on #317 (the inline-comment/quote/flow-list normalization
was already fixed at head; this covers the rest):

- nested() reads a same-indent block sequence directly after its key, so
  a hand-restyled but valid imports list no longer raises the exit-1
  workflow-missing-config-import false error.
- New max-ai-credits-mirror-stale warning: the run budget reads only the
  REVIEW_MAX_AI_CREDITS env mirror (resolveCreditCap), so a raised cap
  with a stale or missing mirror still plans at the old ceiling; the
  checker now validates the pair.
- The CLI reads the shipped max-ai-credits from its own checkout's
  review.md; SHIPPED_MAX_AI_CREDITS is demoted to a fallback, so a
  release that raises the shipped ceiling cannot strand the check.
- lock-not-marked-generated is guarded on the lock existing: a repo the
  errors just told has no lock is not also told to mark it generated
  (and --strict no longer flips on the spurious warning).
- re-review-full message no longer claims ROUTING 'sets no re-review
  mode' when the parser cannot distinguish explicit full from absent.
- FsLike renamed ConsumerConfigFs per the module-named injected-fs
  convention.
- Test gaps closed: lens-payload warning forwarding, source-missing,
  the renamed --workflow lock-path derivation, plus the new checks.
- README records the consumer-CI gate as an explicit follow-up.
@jwbron

jwbron commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Feedback triage. The blocking finding (frontmatter.ts inline comments) and the quote/flow-list/NaN normalization suggestions were already fixed at head (stripInlineComment, unquote, flow-style list(), unquoted scalar()); the review's line references predate those commits. Commit 7d1f4c1 (push pending; branch is outside the sandbox push allowlist) covers the rest:

  • Same-indent imports item (note): nested() now reads a block sequence at the key's own indent, so that valid spelling no longer raises the exit-1 false error. Test added.
  • max-ai-credits env mirror (question at :539): real gap, now a check. New max-ai-credits-mirror-stale warning fires when the cap is raised but the REVIEW_MAX_AI_CREDITS mirror is absent or disagrees; resolveCreditCap reads only the mirror, so the pair must agree. Tests added.
  • SHIPPED_MAX_AI_CREDITS hardcoded: the CLI now reads the live shipped value from its own checkout's review.md; the constant is a fallback only. Test added.
  • .gitattributes check unconditional (note): lock-not-marked-generated is now guarded on the lock existing. Test added.
  • re-review-full wording (nitpick at :429): reworded to cover explicit-full vs absent.
  • FsLike (nitpick): renamed ConsumerConfigFs.
  • Untested paths (lensPayloadWarnings forwarding, source-missing, renamed --workflow lock derivation): tests added for all three.
  • README CI-gate suggestion (:160): recorded as an explicit follow-up in the README.
  • YAML-parser question (frontmatter.ts:5): staying hand-rolled deliberately; lib/ carries no YAML dependency by policy, and the normalizations above close every named false-positive shape. If a further valid spelling surfaces, a real parser is the next step, not another special case.
  • --list-checks thought and the upstream-defaults thought (:543): both deferred as follow-ups; changing the shipped review.md defaults is out of this PR's scope.

Also fixed CI: the failing agent job was attempt 2 of run 31027736888 reusing an expired activation artifact; a full rerun regenerates it.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

15 of 17 prior review threads resolved; 2 still unaddressed as of 7d1f4c1:

2 non-blocking threads still open

path: string,
): ReviewerRouting => {
if (!fs.existsSync(path)) {
return {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): readReviewerRouting's missing-file early return omits the required allowlistKeyPresent field of ReviewerRouting, a type error that pnpm typecheck cannot catch because tsconfig excludes workflows/**. Introduced by this change. ReviewerRouting declares allowlistKeyPresent: boolean as required (check-consumer-config.ts line 175), but the !fs.existsSync(path) early return at lines 326-331 constructs the object without it — I confirmed the field is absent on the checkout, and that downstream reads don't touch it when present is false, so the impact today is limited to the --json report shape. It survives review and CI because tsconfig.json excludes workflows/** from pnpm typecheck (a documented pre-existing gap) and vitest does not type-check; a one-line allowlistKeyPresent: false closes it and keeps the type honest before some future caller reads the field on the not-present path. Contrast: the sibling readInstalledWorkflow early return (lines 277-285) correctly supplies every required field of its type.

Suggested change
return {
return {
present: false,
definesAddReviewer: false,
allowlistKeyPresent: false,
allowedTeamReviewers: [],
hasGithubToken: false,
};
Lower-confidence observations (1)
  • workflows/review/lib/frontmatter.ts:5 suggestion (non-blocking): The 'no YAML dependency' premise is a choice, not a constraint — and the hand parser's growth pattern already shows the cost.

case "--explain":
out.explainPath = argv[++i];
break;
case "--workflow":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): parseArgs --workflow flag has no coverage. The functional path is covered via the workflowPath option (line 373 test), but the CLI flag that populates it is not. Grep of the test file confirms --workflow appears in no parseArgs case.

A sketch, not a committable replacement:

Add a parseArgs assertion including `--workflow .github/workflows/pr-review.md` and expect `workflowPath` to be set.

): boolean => generatedPatterns.some((pattern) => matchesGlob(path, pattern));
generatedRules: readonly GeneratedRule[],
): boolean => {
for (let i = generatedRules.length - 1; i >= 0; i--) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): Description says "nothing here runs in a review," but the bundled router.ts negation change alters production generated-file classification. isGenerated (reverse last-match-wins) is called from route() on the production review path; a repo that un-marks a subtree after a broad linguist-generated=true glob now gets those files reviewed where before they were skipped. It's captured in the separate gitattributes-negation changeset, but the PR description's stated intent (onboarding checker + skill, "routing defaults untouched") does not mention this shipped-behavior change and explicitly claims the opposite. Surfacing as a scope note so the reviewer weighs the production blast radius, not to block.

Also flagged by first-principles.

A sketch, not a committable replacement:

Split the gitattributes-negation router change into its own PR, or amend this PR's description to surface it as the live behavior change it is.

const warnings = report.issues.filter(
(issue) => issue.severity === "warning",
);
if (errors.length > 0 || (args.strict && warnings.length > 0)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): --strict exit-code behavior is untested. checkConsumerConfig is pure and well-tested, but the strict-vs-non-strict exit code decision is CLI glue in main() and is never asserted. Consider extracting the exit-code predicate into an exported pure helper so it can be unit-tested.

};

const main = (): void => {
/* eslint-disable-next-line no-undef */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick (non-blocking): Unnecessary eslint-disable-next-line no-undef on the require("node:fs") idiom. Every other CLI main() in this directory acquires node fs with the identical bare idiom and no disable comment — e.g. stage-pr.ts:717 const nodeFs = require("node:fs") as StagePrFs;, router.ts:970 const fs = require("node:fs") as FsLike; (11 siblings total, all lint-clean per the PR body). This file adds /* eslint-disable-next-line no-undef */ immediately above the same call: 926| /* eslint-disable-next-line no-undef */ / 927| const nodeFs = require("node:fs") as ConsumerConfigFs & {.

Suggested change
/* eslint-disable-next-line no-undef */
const nodeFs = require("node:fs") as ConsumerConfigFs & {

@jwbron
jwbron marked this pull request as ready for review August 13, 2026 20:48
@khan-actions-bot
khan-actions-bot requested a review from a team August 13, 2026 20:49
@github-actions

Copy link
Copy Markdown
Contributor

Review Guidance

github-actions (5 files)
File Reason
router.ts isGenerated/parseGitattributesGenerated decide which files every consumer PR's review skips; this moves to git's last-match-wins so .gitattributes negations are honored, and the failure direction is more review, never less.
check-consumer-config.ts New dev/onboarding checker; nothing in the review run path imports it, so a bug misleads an onboarding check rather than changing consumer PR verdicts.
check-consumer-config-report.ts Pure text renderer of the checker's report, imported by nothing in the run path, so a formatting change here cannot alter a verdict.
frontmatter.ts Minimal frontmatter reader used only by the consumer-config checker and its tests; a parsing bug produces a wrong onboarding answer, not a wrong review.
SKILL.md Executable-prompt skill choreographing gh/git commands and secret provisioning against consumer repos; its never-handle-the-key print-and-stop boundaries are load-bearing.

Common patterns

7 files: RouterConfig field renamed from generatedPatterns: string[] to generatedRules: GeneratedRule[] at every call-site that constructs a RouterConfig literal.

-    generatedPatterns: [],
+    generatedRules: [],
Excluded from review (5 files)

Not individually reviewed — generated, formatting-only, or
fully explained by a common pattern above:

  • workflows/review/eval/live-producer.ts — pattern-only
  • workflows/review/eval/live-stage.ts — pattern-only
  • workflows/review/eval/runner.ts — pattern-only
  • workflows/review/lib/credit-cap.test.ts — pattern-only
  • workflows/review/lib/investigation-cap.test.ts — pattern-only

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved — no blocking issues found.
7 of 7 prior review threads are still unaddressed as of 7d1f4c1:

7 non-blocking threads still open

@sxkosone sxkosone 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.

I didn't read the PR description or the code, as they're not readable for humans. But I'll rubber-stamp-approve this to keep the reviewer work moving forward.

@jwbron
jwbron merged commit 9243690 into main Aug 20, 2026
18 checks passed
@jwbron
jwbron deleted the review-onboarding-skill-and-checker branch August 20, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants