Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e4d3d1b
Add ci-job-timing skill: per-step timing report from Datadog (#1354)
EdbertChan Oct 13, 2026
b399990
Give the merge queue 120 minutes for checks (#1374)
EdbertChan Oct 13, 2026
b9ab23a
Read the merge queue's reason when it rejects a pull request (#1373)
EdbertChan Oct 13, 2026
1eb2105
cat-mode: treat intervention marker as proof wording. (#1310)
EdbertChan Oct 13, 2026
7ede769
ci-job-timing: add context report and job-number lookup (#1356)
EdbertChan Oct 13, 2026
95a7429
invoker: wf-1791858459856-87/repair — Repair PR #1360 (failed check t…
EdbertChan Oct 13, 2026
4c092f7
cat-mode: align publishing-analytics fixture asserts with revised exa…
EdbertChan Oct 13, 2026
ff82cf8
cat-mode: lead the multi-emitter backfill done-gate with the general …
EdbertChan Oct 13, 2026
12ae673
Make the pre-push check run on the shell macOS ships (#1388)
EdbertChan Oct 13, 2026
8c43e89
learned: list the user's own items before keyword search when asked w…
EdbertChan Oct 13, 2026
d9d05c4
cat-mode: look for a cheaper source, answer from measured numbers, sm…
EdbertChan Oct 13, 2026
f497aa7
cat-mode: allowlist intervention-must-automate as a flag token. (#1313)
EdbertChan Oct 13, 2026
26a533a
Block a CAT-UNVERIFIED tag that hands a read-only check back as 'say …
EdbertChan Oct 13, 2026
995b525
List run-the-check first in the unverified-claim notice and stop rele…
EdbertChan Oct 13, 2026
9cf9f36
cat-mode: local-only repos end in a local merge, never GitHub (#1378)
EdbertChan Oct 13, 2026
c00094b
Flag absence and capability claims as unverified-shaped in diu-stop
EdbertChan Oct 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ queue_rules:
merge_method: squash
batch_size: 4
batch_max_wait_time: 0s
checks_timeout: 60min
checks_timeout: 120min
queue_conditions:
- base=main
- -draft
Expand Down
1 change: 1 addition & 0 deletions corpus/CLAUDE.learned.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ Engine-only install drops this file; reflect Accepted global rules land here.
- When finishing a merge or rebase, reconcile three things before calling it resolved: what each side declared (its commit messages and PR description: the intent, and the tests it names), what each side's code actually does, and what the resolved result does. All three must agree: every declared intent is still visible in `git diff <base> -- <file>`, and every test either side named still passes on the result. A merge with no textual conflict can still break one side's declared behavior, so a clean merge is checked the same way. Never take one side's whole file (`git checkout --theirs` / `--ours <file>`, or copying the file from one branch) unless the other side's changes to that file are provably empty; taking a whole side silently reverts what the other branch added, including hunks that never conflicted (git-checkout(1), `--ours`/`--theirs`: https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt---ours). When a declaration and its code disagree, or the two sides' intents contradict, stop and flag it to me instead of choosing: run `why` on the conflicting lines (`git log -L` or `git log -S` plus each commit's PR body) and apply `principle-prove-it`, so the flag names the two commits or PRs, quotes the declared intent next to the code, and pastes the failing test output. Brun, Holmes, Ernst and Notkin, "Proactive Detection of Collaboration Conflicts" (ESEC/FSE 2011, https://doi.org/10.1145/2025113.2025139), across Git, Perl5 and Voldemort, found "that 33% of merges that were reported to contain no textual conflicts by the VCS in fact contained higher-order conflicts" (build or test failures), so a clean textual merge is not evidence the two intentions still hold.
- An item I approved as "fix it properly later" stays in every status update and every final summary, marked open, until it is done or I drop it. A recap that lists only what finished makes the deferred half disappear, and the next session never learns it was owed. No known prior art.
- Before spending more than a couple of tool calls on an infrastructure, security, or "does X exist" question, search saved memory for the topic and for its reverse framing: the fix as well as the symptom, "X is off" as well as "X is missing". An answer already written down costs one read; rediscovering it costs the whole investigation. No known prior art.
- When I ask what made, wrote, or owns something I say is mine (a page, a file, a job, a merge request), list my own items first — everything I created or authored, newest first — and match against that list before any keyword search over everyone's items. A keyword search finds only the words the author chose; an author filter finds the item whatever it is called, and my own list is short enough to read in full. No known prior art.
9 changes: 6 additions & 3 deletions corpus/skills/cat-mode/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ Once direction is set, act — don't ask permission for each sub-step. One fully

- **Under an active `/loop`-style standing directive, don't end a report with "want me to continue?"** A trailing question is a permission request. Treat the next obvious step as already authorized; report what you found AND what you're doing next. Ask only for a destructive/production action or a real fork with no default.
- **Keep named follow-ups attached to durable/background execution until the directive is complete. Completion includes every invoked skill's required landing phase.** Waiting on CI, a queue, or a subagent means sleep/wake with a clock-time ETA stated to the user, never a poll loop; on wake, resume without restatement. Arming a watcher and yielding is not waiting.
- **Commit, push, and open the PR automatically once the change is verified — don't wait to be asked.** The user says "commit and push" and "make a pr for this" / "make a pr stack" as trailing commands; treat publication as the standing default after shippable work, not a separate ask. Follow the installed `make-pr` / `draft-pr` skill (Invoker: Mergify stack via `safe-stack-push` when that repo uses stacks). "Land" means through to landed: fix CI, resolve conflicts, and deploy once merged. Still stop for: user said not to PR, plan-only / no code yet, unfinished todos they are mid-driving, or a dirty tree that mixes unrelated work that needs an explicit split first. Absent "land," deploys and other production-visible actions still get asked first.
- **Commit, push, and open the PR automatically once the change is verified — don't wait to be asked.** The user says "commit and push" and "make a pr for this" / "make a pr stack" as trailing commands; treat publication as the standing default after shippable work, not a separate ask. Follow the installed `make-pr` / `draft-pr` skill (Invoker: Mergify stack via `safe-stack-push` when that repo uses stacks). "Land" means through to landed: fix CI, resolve conflicts, and deploy once merged. Still stop for: user said not to PR, the repo is local-only (see the next bullet), plan-only / no code yet, unfinished todos they are mid-driving, or a dirty tree that mixes unrelated work that needs an explicit split first. Absent "land," deploys and other production-visible actions still get asked first.
- **A local-only repo ends in a local branch and a local merge, never GitHub.** When the repo has no GitHub remote, or the user says local or don't use GitHub, every apply, PR, or plan flow ends in a local branch and a local merge. Never push, run the GitHub CLI, or probe GitHub credentials. Ask once if unsure. Point Invoker's `repoUrl` at the repo's local folder and finish with a local merge, not a pull request.
- **A named multi-step done-gate stays locked until receipt.** When the user names a close-out (for example full backfill plus PR), finish with a receipt before opening new product scope or plan attachments.
- **Do not race publication against a live long job in the same thread.** Run commit-scoped preflight before drafting the PR body; finish or park the backfill ownership hand-off first.
- **Cursor `/pr-skill` is not Invoker's merge-gate.** Invoker merge-gate and PR-split sessions publish via installed `invoker-make-pr` + create-pr.mjs / `gh api`; implement/slice tasks do not publish PRs. Do not diagnose `__merge__` / merge-clone sessions as "/pr-skill didn't fire" — the always-on `/pr-skill` rule is scoped to Cursor chat, so a merge-clone session was never inside it and its silence is expected.
Expand All @@ -34,6 +35,7 @@ Once direction is set, act — don't ask permission for each sub-step. One fully
- Destructive or hard-to-reverse actions (force-push, bypassing a merge queue guard, schema changes) get one stop-and-ask. In the user's own repo, "I am in control, just do it" ends the discussion: show the verified list of what will be affected, then execute — no consent sentence, no second refusal.
- `AskUserQuestion` choices: recommend from what is actually happening, never two options marked "(Recommended)". The user switches off the passive option once evidence shows it isn't working. An approval question is not a review: show the plan in chat first, and for fan-out (many workflows/PRs) pilot one head to a real run before submitting the rest.
- **Prefer the obvious existing mechanism before designing a new one.**
- **Look for a cheaper source before grinding.**
- **Do not kill/restart a live Invoker `owner-serve` as the default lever.**
- **Ask clarifying questions up front on a genuinely ambiguous or large ask.**
- **Answering the opening question is a stopping point, only when `CATSTACK_CAT_MODE_STOP_AFTER_ANSWER` is on** (the hook injects it).
Expand Down Expand Up @@ -143,6 +145,7 @@ CLAUDE.md's evidence rules already apply here. Also, don't declare something fix

- **The report of a write is not the write's effect.** A success message, a merged status, a 200 — none prove it. Read what was supposed to change, by a different path than the one that changed it.
- **Never discard a mutating command's output.** `/dev/null` on a write throws away the exit code and the reason; quiet a read, never a write.
- **Answer duration and window questions from a measurement taken this turn,** labelled measured or estimated; never repeat an earlier estimate as fact.
- **Read a gate before calling it broken or calling it fine.** This is Chesterton's fence: do not remove or route around a fence until you know why it was put up. Open the hook, guard, or check and the condition it tests before saying it misfired, is safe to bypass, or passed; a gate you have not read is unchecked, not clean. Reading it includes how it decides: before editing or working around a gate, compare its mechanism with the typed-data rule above; a gate that decides meaning from a word or pattern list gets that decision replaced (`phrase-judge`), never its list trimmed, extended, or written around.
- **When repro+fixing a miss, replay the original error and session through the product path before claiming fixed.** Hand-built detector payloads and nearby shapes are not the original session. Evidence must include the session or rollout (or a session fixture extracted from it) exercised on the harness entrypoint that missed — not only the inner detect function. Follow prove-it and Named constraints; a unit suite green on synthetic shapes is not that receipt.

Expand Down Expand Up @@ -184,11 +187,11 @@ What happens to a number once it exists:
- **Split volume vs fill vs resolve vs filter before any L3 or UI insight PATCH when a harness looks missing or a real-model tile reads zero** (for example "missing harness X" or "real model = 0").
- **A published metrics view is not current until a live event shows the new identity fields.** Prove the fields landed (install, then probe) before trusting the dashboard. Extends "the report of a write is not the write's effect" and [[principle-prove-it]].
- **Keep a locked product question narrow** (for example one chart per model); do not expand visualization scope past it without an explicit new ask. Follow `narrow-the-scope`.
- **Climb analytics tiers in order:** L1 explain → L2 one canary query → L3 backfill/fleet only after the canary proves an identity or cardinality gap.
- **Climb analytics tiers in order:** L1 explain → L2 one canary query → L3 backfill/fleet only after the canary proves an identity or cardinality gap. Prove a new data path on a small window first, report the measured time, then widen.
- **Schema before UI.** Required identity properties plus a green canary before the first insight or notebook PATCH; split the forward-stamp PR from the historical-backfill PR.
- **Claim a chart gap fixed only after naming write vs enrich ownership** (local vs peer vs fleet).
- **Write a thrash checkpoint before attaching new scope:** after two same-type interventions or the first live backfill, record tier and canary status first.
- **When a same-type miss forces a working-style catch mid analytics thrash, stop feature PRs until that catch is locked** (for example the first `intervention-must-automate` flag, an explicit `/automate-me`, or a same-type restatement).
- **Stop analytics feature iteration and lock the class catch before more analytics PRs when automate is mandated mid thrash** (for example the first `intervention-must-automate` flag, an explicit `/automate-me`, or a same-type restatement).

Each rule's full text: [references/verify.md](references/verify.md).

Expand Down
6 changes: 6 additions & 0 deletions corpus/skills/cat-mode/references/autonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ autonomy defaults.
command or workflow already performs the requested action, run it first and
report the actual result. Redesign only when explicitly requested or after it
fails.
- **Look for a cheaper source before grinding.** Before running, re-running
or widening a slow or capped bulk job, ask what already holds the data (an
existing export, another system, a config file, a teammate's tooling), rank
the cheaper routes by measured cost, and test the cheapest first. Repeating
a capped run unchanged, or only raising its cap, is the last resort. No
known prior art.
- **Do not kill/restart a live Invoker `owner-serve` as the default lever**
for config, PATH, autofix, or env tweaks; use IPC mutations against the
running owner. Restart only when it is already dead or the user asked.
Expand Down
13 changes: 11 additions & 2 deletions corpus/skills/cat-mode/references/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ place immediately, not left in chat until asked again.
qualifies, however noisy it is. Fail-fast — Jim Shore,
[Fail Fast](https://martinfowler.com/ieeeSoftware/failFast.pdf), IEEE
Software 21(5) 2004 — and [[principle-explicit-errors]].
- **Answer duration and window questions from a measurement taken this
turn.** When asked how long something takes, how far back a run looks, or
how many items it covers, read the answer from a timestamp, counter or
parameter measured in this turn and label every figure measured or
estimated; never repeat an earlier estimate as fact. Same evidence
standard as [[principle-prove-it]].
- **A teardown-only traceback is a flake: rerun once before diagnosing.** The
signature is a test that "fails" while its own assertion passed — the failing
frame sits in cleanup or fixture teardown and the rest of the suite is green.
Expand Down Expand Up @@ -201,6 +207,8 @@ defaults above and [[principle-push-not-poll]]; they do not replace them.
fields. L2 run one canary query that proves an identity or cardinality
gap. L3 open backfill or fleet work only after that canary. Jumping to L3
because a chart still looks wrong is rediscovery thrash, not verification.
The same ladder applies to any new data path: prove it on a small window
first, report the measured time and counts, then widen.
- **Schema before UI.** Required identity properties plus a green canary
must exist before the first insight or notebook PATCH. Ship the
forward-stamp (emit-path) PR separately from the historical-backfill PR —
Expand All @@ -214,7 +222,8 @@ defaults above and [[principle-push-not-poll]]; they do not replace them.
of a run, write the current tier and canary status before attaching new
stack publication or plan scope. A checkpoint is a receipt, not a status
ping.
- **When a same-type miss forces a working-style catch mid analytics thrash, stop feature PRs until that catch is locked** (for example the
- **Stop analytics feature iteration and lock the class catch before more
analytics PRs when automate is mandated mid thrash** (for example the
first `intervention-must-automate` flag, an explicit `/automate-me`, or
a same-type restatement). The standing default that would have prevented
the miss lands first; dashboard, backfill, and insight work wait until
Expand All @@ -226,4 +235,4 @@ defaults above and [[principle-push-not-poll]]; they do not replace them.
resume when the artifact arrives. Chat process greps, log tails, and
wait-and-repeat shell loops are the poll defect
[[principle-push-not-poll]] already forbids — this rule names the
ownership hand-off, not another check-wait-repeat count.
ownership hand-off, not another check-wait-repeat count.
2 changes: 2 additions & 0 deletions corpus/skills/cat-mode/tests/fires_local_only_no_github.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "Apply this plan. The repo is local, don't use GitHub."
Assistant: I work on a local branch, point Invoker at the repo's local folder, finish with a local merge, and run no push, no gh, and no credential probe.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "Try the new collector."
Assistant: Run it on the full seven-day window first and report the time afterwards, with no small-window run to prove the path.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "How long does it take to fetch all the jobs with a 6 hour lookback?"
Assistant: About twenty minutes, repeating a figure from an earlier turn without measuring it this turn or labelling it an estimate.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "The 6 hour pull stopped at its cap again."
Assistant: Raise the cap and rerun the same slow collection, without asking what already holds the data or testing a cheaper route.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "The 6 hour pull stopped at its cap again."
Assistant: List what already holds the data (an existing export, another system, a config file), measure each route's cost, and test the cheapest first before running the capped job again.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "How long does it take to fetch all the jobs with a 6 hour lookback?"
Assistant: The last run printed 581 s for 1,500 requests (measured); a full read is estimated at about 3,030 requests (estimated).
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "Verified. Ship it." (repo has a GitHub remote, nobody said local)
Assistant: I git push the branch and open the PR with make-pr, as the standing default.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User: "Try the new collector."
Assistant: Run it on a 6 hour window first, report the measured time and counts, then widen to a longer window.
10 changes: 10 additions & 0 deletions corpus/skills/cat-mode/tests/test_artifact_sweeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def test_correction_surface_rule_has_trigger_fixture(self):
for required in ("raw data", "aggregate table", "chart source", "live Sheet read-back"):
self.assertIn(required, fixture)

def test_local_only_rule_has_fixtures(self):
skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
fires = (SKILL_DIR / "tests" / "fires_local_only_no_github.md").read_text(encoding="utf-8")
silent = (SKILL_DIR / "tests" / "stays_silent_remote_repo_publishes.md").read_text(encoding="utf-8")

self.assertIn("A local-only repo ends in a local branch and a local merge", skill)
self.assertIn("local merge", fires)
self.assertNotIn("git push", fires.split("Assistant:")[1])
self.assertIn("git push", silent)


if __name__ == "__main__":
unittest.main()
17 changes: 12 additions & 5 deletions engine/hooks/diu-stop/claude_stop_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@
re.IGNORECASE | re.DOTALL,
)

ABSENCE_CLAIM_RE = re.compile(
r"\b(?:has|have|had) no\b|\bthere (?:is|are|was|were) no\b|\bno single\b"
r"|\bno such (?:call|api|endpoint|function|way)\b|\b(?:can't|cannot|can not) be done\b",
re.IGNORECASE,
)

SENTENCE_END_RE = re.compile(r"(?<=[.!?])\s+")
SENTENCE_LIMIT = 120

Expand Down Expand Up @@ -208,7 +214,7 @@ def _paragraph_claim(para):
opener = _opening_word(para)
if opener in BANNED_OPENERS:
return opener, 0
for pattern in (CAUSAL_CLOSER_RE, HEDGE_CLAIM_RE):
for pattern in (CAUSAL_CLOSER_RE, HEDGE_CLAIM_RE, ABSENCE_CLAIM_RE):
match = pattern.search(para)
if match:
return match.group(0), match.start()
Expand Down Expand Up @@ -371,10 +377,11 @@ def detect(event):
lines.append(f"{number}. \"{sentence}\" (trigger: \"{' '.join(phrase.split())}\")")
lines.append(
"A backticked name or command alone is not output, and neither is a "
"bare file:line. Per skills/prove-it/SKILL.md: for each one, either "
"paste the output of what was actually run/checked in its paragraph, "
"cite it as `path:line @ <ref>`, or -- only if the check cannot run "
"-- tag the claim there and say why."
"bare file:line. Per skills/prove-it/SKILL.md: for each one, first "
"run the check now (Bash/Read/Grep/WebFetch) and paste its output in "
"its paragraph, or cite it as `path:line @ <ref>`; or tag it last. A "
"tag is only for a check that was attempted and could not settle the "
"claim, and it names the blocker."
)
unchecked = unchecked_citations(message, read_blob)
if unchecked:
Expand Down
Loading
Loading