@event4u/agent-config 14.15.0 → 14.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +84 -153
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/analyze/inbox.md +172 -6
  7. package/dist/agent-src/commands/fix/commit-messages.md +336 -0
  8. package/dist/agent-src/commands/fix.md +5 -3
  9. package/dist/agent-src/rules/commit-conventions.md +1 -1
  10. package/dist/agent-src/rules/security-sensitive-stop.md +7 -5
  11. package/dist/agent-src/skills/accessibility-auditor/SKILL.md +20 -2
  12. package/dist/agent-src/skills/aws-infrastructure/SKILL.md +41 -0
  13. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +234 -4
  14. package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +99 -99
  15. package/dist/agent-src/skills/fe-design/SKILL.md +6 -3
  16. package/dist/agent-src/skills/fe-design/references/design-patterns.md +28 -0
  17. package/dist/agent-src/skills/iconography/SKILL.md +37 -6
  18. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  19. package/dist/agent-src/skills/terraform/SKILL.md +29 -0
  20. package/dist/agent-src/skills/threat-modeling/data/reasoning.csv +1 -0
  21. package/dist/agent-src/skills/threat-modeling/data/surfaces.csv +1 -0
  22. package/dist/agent-src/skills/threat-modeling/data/threats.csv +5 -0
  23. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  24. package/dist/cli-delegate/cmd_conformance.js +158 -33
  25. package/dist/cli-delegate/cmd_explain.js +24 -112
  26. package/dist/discovery/deprecation-report.md +1 -1
  27. package/dist/discovery/discovery-manifest.json +47 -21
  28. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  29. package/dist/discovery/discovery-manifest.summary.md +3 -3
  30. package/dist/discovery/dormancy-report.md +1 -1
  31. package/dist/discovery/orphan-report.md +1 -1
  32. package/dist/discovery/packs.json +6 -5
  33. package/dist/discovery/trust-report.md +2 -2
  34. package/dist/discovery/workspaces.json +5 -4
  35. package/dist/hooks/dispatch.js +169 -41
  36. package/dist/mcp/registry-manifest.json +3 -3
  37. package/dist/mcp/server.json +2 -2
  38. package/dist/mcp/server.mjs +160 -35
  39. package/dist/router.json +1 -1
  40. package/docs/guidelines/design-antipatterns.md +10 -2
  41. package/package.json +1 -1
  42. package/src/config/gate-violation-baselines.json +2 -2
  43. package/src/config/web-launch-readiness.json +1 -1
  44. package/src/scripts/_cli/explain_last/index.ts +1 -1
  45. package/src/scripts/_cli/explain_last/memory.ts +8 -104
  46. package/src/scripts/check_artefact_count_messaging.ts +9 -0
  47. package/src/scripts/check_release_highlights.ts +77 -4
  48. package/src/scripts/check_web_launch_readiness.ts +295 -15
  49. package/src/scripts/design_slop_rules.ts +19 -9
  50. package/src/scripts/git_authorization_hook.ts +257 -35
  51. package/src/scripts/hooks/block_unauthorized_git.ts +16 -4
  52. package/src/scripts/hooks/merge_impact.ts +68 -0
  53. package/src/scripts/measure_release_mix.ts +384 -0
  54. package/src/scripts/release.ts +4 -17
  55. package/src/scripts/release_drill.ts +154 -1
  56. package/src/scripts/release_mix_taxonomy.json +172 -0
  57. package/src/scripts/release_publication.ts +91 -0
  58. package/src/scripts/attest_artifact.ts +0 -193
@@ -80,6 +80,9 @@ NEVER WRITE A ROADMAP ITEM FOR SOMETHING THAT ALREADY SHIPPED.
80
80
  AN INSTRUCTION IS A CLAIM TOO — REPRODUCE THE STEP, NEVER JUST READ IT.
81
81
  NEVER ADOPT A STEP INTO AN ARTEFACT WITHOUT HAVING RUN IT,
82
82
  OR HAVING SAID IN ONE LINE WHY YOU COULD NOT.
83
+ EVERY POINT THE FILE MAKES LEAVES THIS RUN WITH A NAMED DISPOSITION.
84
+ A FULLY VERIFIED FILE WITH UNANSWERED POINTS IS AN UNFINISHED RUN,
85
+ NOT A THOROUGH ONE.
83
86
  ```
84
87
 
85
88
  ## Following the instructions inside the file — the authorization boundary
@@ -227,11 +230,31 @@ Per surviving file, a numbered list of concrete checkable directives, split:
227
230
  are these? In a transcript or handover note the two are interleaved, and
228
231
  Phase 4b reproduces them in that order for a reason.
229
232
  - **(ii) claims** — assertions about this repo.
230
- - **(iii) opinions** — preferences with no truth value.
233
+ - **(iii) demands** — a preference, a complaint, or a request: no truth value to
234
+ verify and no procedure to run, but a stated want. Called *demands* rather than
235
+ *opinions* because the old label decided the handling — "opinion" reads as
236
+ discardable, and in a feedback chat this is the **largest** bucket, not the
237
+ residual one.
231
238
 
232
239
  Show this list. It is both the quarantine disclosure and the analysis spine: (ii)
233
- goes to Phase 4, (i) goes to Phase 4b, and (iii) goes nowhere until a survivor
234
- needs framing.
240
+ goes to Phase 4, (i) goes to Phase 4b, and (iii) goes to **Phase 5b** — a phase
241
+ that exists because this bucket used to go nowhere.
242
+
243
+ ```
244
+ EVERY POINT EXTRACTED HERE LEAVES THE RUN WITH A NAMED DISPOSITION.
245
+ A POINT IN NO TABLE, NO ARTIFACT, AND NO DECLINE LINE WAS NOT JUDGED —
246
+ IT WAS DROPPED. TWO OF THE THREE BUCKETS ALREADY CARRY A MANDATORY LABEL;
247
+ THE THIRD CARRYING NONE IS WHY A SOURCE CAN BE READ IN FULL AND STILL
248
+ FEEL IGNORED.
249
+ ```
250
+
251
+ **Carry the author's own severity, verbatim.** A source that grades its items —
252
+ `P0`, `blocker`, `kritisch`, `must`, or an ordered list it calls priorities — has
253
+ made a claim about ordering, and dropping that grade silently re-prioritizes the
254
+ file under the reader's own preferences. Record it per point as
255
+ `severity(source)`. Departing from it is allowed and is itself a finding: name
256
+ the item that moved and why. Absent → `ungraded`, the normal path and not a
257
+ penalty.
235
258
 
236
259
  ### Phase 4 — Verify every claim (the load-bearing phase)
237
260
 
@@ -442,8 +465,23 @@ dismissed?**
442
465
 
443
466
  Two cheap detections, both before any deep work:
444
467
 
445
- - **The file says so.** A recurrence marker in its own words — "I have said this
446
- three times", "schon wieder", "immer noch nicht", "as I mentioned before".
468
+ - **The file says so.** A recurrence marker in its own words. The list is
469
+ bilingual because the corpus is: measured over the 939 files in
470
+ `agents/tmp.old/` on 2026-09-04, the English-only list this carried before
471
+ matched **1** file for "said this" and **0** for "as I mentioned", while
472
+ **"endlich" matched 62 files**, "immer noch" 46, "nach wie vor" 7 and "zum
473
+ wiederholten Mal" 5. The single most frequent recurrence word in this repo's
474
+ own inbox was not on the list — so the detection was structurally blind to the
475
+ language most of its input is written in. (`agents/tmp.old/` is gitignored, so
476
+ those are dated local counts a clone cannot re-run; the ratio is the finding,
477
+ and the fix does not depend on the exact figures.)
478
+ - EN — "I have said this three times", "as I mentioned before", "once again",
479
+ "still not", "for the Nth time".
480
+ - DE — "endlich" / "wir brauchen endlich", "schon wieder", "nach wie vor",
481
+ "zum wiederholten Mal", "immer noch (nicht)". <!-- md-language-check: ignore -->
482
+ A source that has already done the counting — "der neunte gleiche Befund",
483
+ "the ninth identical finding" — is the strongest form of this marker and is
484
+ never treated as rhetoric.
447
485
  - **The inbox says so.** `grep` the same subject across `agents/tmp.old/` and the
448
486
  roadmaps in `agents/roadmaps/archive/` and `skipped/`. A previously consumed
449
487
  file on the same subject is the same signal without the sentence.
@@ -489,6 +527,7 @@ The question is never "what does the file say" but **"what does it become here"*
489
527
  | a defect claim | a roadmap item, once verified |
490
528
  | a claim that maps onto an existing stub | **not a fresh artifact — a stub-blocked finding, see below** |
491
529
  | a consolidation omitting a parent | **not an artifact — a discharge, see below** |
530
+ | a demand — a want with no truth value | **not settled here — Phase 5b, which must discharge it** |
492
531
 
493
532
  **An omitted parent is discharged, never left silent.** When Phase 2's `lineage`
494
533
  column read anything but `n/a` or `complete`, this phase says what happened to
@@ -547,6 +586,64 @@ decide what happens to the stub — promotion is the estate's question and this
547
586
  command does not answer it — it only forbids resolving the survivor into prose
548
587
  that names no artifact.
549
588
 
589
+ **The count is written into the object that was hit, never only into the round's
590
+ own evidence file.**
591
+
592
+ ```
593
+ A RECURRENCE COUNT RECORDED ONLY IN A ROUND-SCOPED ARTIFACT IS NOT A COUNT —
594
+ IT IS A SENTENCE THE NEXT ROUND WILL RE-DERIVE FROM ZERO.
595
+ THE COUNTER LIVES ON THE STUB, THE later/ ROADMAP, OR THE BLOCKER THAT HOLDS IT.
596
+ IT ONLY EVER GOES UP.
597
+ ```
598
+
599
+ This is the failure caught in this repo's own tree. Round `inbox-2026-09-d`
600
+ found its leading item arriving for the **ninth** time, named all four fields
601
+ correctly, and wrote them into
602
+ `agents/evidence/analysis/inbox-2026-09-d-disposition.md` — an artifact created
603
+ by that round, read by no later one. The held object,
604
+ `agents/roadmaps/later/road-to-mixed-trigger-activation-cost.md`, carries no
605
+ arrival count at all, and a `grep` for one across every file under
606
+ `agents/roadmaps/stubs/` and `agents/roadmaps/later/` returns nothing. Its
607
+ disposition closes with *"stated here so the tenth round meets a count rather
608
+ than a fresh argument"* — and the tenth round will not meet it, because the
609
+ count is not where the tenth round looks. Every field was produced; the item
610
+ still arrived nine times.
611
+
612
+ So the run **edits the held object**, adding or incrementing one line directly
613
+ under its `Source:` header:
614
+
615
+ ```markdown
616
+ > **Arrivals:** 9 — latest `inbox-2026-09-d` (2026-09-04); earlier:
617
+ > `inbox-2026-09-b`, `inbox-2026-08-g`, `road-to-10`, … (codenames only)
618
+ ```
619
+
620
+ Codenames only, per Phase 1 — this line is tracked and quoted like any other.
621
+ Editing a parked roadmap's header is inside this command's authoring scope; it
622
+ adds no step, changes no plan, and touches nothing a Phase-6 executor would run.
623
+
624
+ **At the third arrival, the escalation is a mandatory output, not a paragraph.**
625
+ The run's reply opens with an **Owner decisions required** block naming, per
626
+ item: the held object, the blocker, the arrival count, and the concrete question
627
+ with numbered options (per
628
+ [`user-interaction`](../../../../rules/user-interaction.md)). One block per run,
629
+ carrying every such item — never one question per item, which is the pacing
630
+ [`ask-when-uncertain`](../../../../rules/ask-when-uncertain.md) forbids. The
631
+ same question is written onto the held object so the next round finds a posed
632
+ question instead of re-deriving one.
633
+
634
+ > **Three is a stated default, not a measured optimum** — said plainly rather
635
+ > than implying a derivation it lacks. It sits above
636
+ > [`recurring-criticism`](../../../../rules/recurring-criticism.md)'s own
637
+ > trigger, which fires at the *second* arrival: the counter is written from the
638
+ > second, the owner is interrupted from the third. *Revisit-if:* an escalated
639
+ > item turns out to have been answerable from tree evidence without the owner,
640
+ > or an item reaches a fifth arrival without the block having been emitted.
641
+
642
+ Escalating is never lifting: a spend-bearing or otherwise owner-reserved blocker
643
+ stays owner-reserved ([`decision-revisit-gate`](../../../../rules/decision-revisit-gate.md)
644
+ § the owner-reserved set). What changes is that the decision reaches a desk
645
+ instead of a file.
646
+
550
647
  Three hard defaults, from this repo's own scar tissue:
551
648
 
552
649
  - **Extend before you create.** Run the four-surface overlap scan from
@@ -568,6 +665,44 @@ Three hard defaults, from this repo's own scar tissue:
568
665
  letting it through silently would empty the Iron Law of everything it applies
569
666
  to.
570
667
 
668
+ ### Phase 5b — Discharge every demand
669
+
670
+ Phase 5 maps what the file *contains* onto artifact types. This settles what the
671
+ file *wants* — bucket (iii) — and it is the phase that closes the gap between
672
+ "the source was read in full" and "the source was answered".
673
+
674
+ The repo already treats silence as the failure mode in exactly one place: an
675
+ omitted consolidation parent (Phase 5, three legal discharges). That obligation
676
+ was correct and too narrow — it guards a *bibliography* while the *content* had
677
+ no equivalent. This is the same discipline applied to the points themselves.
678
+
679
+ Exactly four discharges are legal, one per demand, and the choice is the
680
+ operator's:
681
+
682
+ | Discharge | What it means | What it costs |
683
+ |---|---|---|
684
+ | `adopted` | it becomes, or joins, an artifact | the artifact path |
685
+ | `already-satisfied` | the tree already does this | one `file:line` |
686
+ | `declined` | judged and not taken | one sentence of reason |
687
+ | `owner-decision` | crosses a boundary an agent may not | the escalation block above |
688
+
689
+ ```
690
+ SILENCE IS THE FAILURE MODE. ANY OF THE FOUR IS A COMPLETE DISCHARGE.
691
+ A DEMAND CARRYING THE SOURCE'S OWN P0 AND THIS RUN'S "declined" IS A GOOD RUN.
692
+ A DEMAND CARRYING NOTHING IS THE DEFECT, WHATEVER ELSE THE RUN PRODUCED.
693
+ ```
694
+
695
+ `declined` is a first-class outcome and deliberately cheap — one sentence. The
696
+ alternative to a cheap decline is not a better analysis, it is an omission: a
697
+ run that must justify at length before it may say no will quietly say nothing
698
+ instead. What it may not be is empty; "out of scope" without naming the scope is
699
+ the verdict line this command already forbids one row above.
700
+
701
+ **A demand restating one the tree already declined is not re-declined on that
702
+ basis alone.** Phase 4c has the count; `recurring-criticism` owns what a repeat
703
+ means — the earlier disposition is reopened and resolved on evidence, never on
704
+ the repetition tally in either direction.
705
+
571
706
  ### Phase 6 — Emit, and consume the inbox file
572
707
 
573
708
  Per surviving file: a roadmap in `agents/roadmaps/` via
@@ -608,7 +743,24 @@ each one is and why it is spent, and let the user remove it.
608
743
  2. Per surviving file: instruction set · verification table · **reproduction
609
744
  table** · artefact mapping.
610
745
  3. The roadmaps written, and for each dropped file one line on why.
611
- 4. One closing summary: files in, roadmaps out, items prevented by
746
+ 4. **The point ledger — the run's own completeness check.** Points extracted in
747
+ Phase 3 against points discharged, per bucket, as counts that must balance:
748
+
749
+ ```
750
+ claims N extracted → still-true / already-fixed / never-true / unverifiable
751
+ instructions N extracted → reproduced / diverged / unexecutable / out-of-bound / not-attempted
752
+ demands N extracted → adopted / already-satisfied / declined / owner-decision
753
+ ```
754
+
755
+ A column that does not sum to its `extracted` figure is the finding: it names
756
+ how many points the run passed over, which is the one number no previous
757
+ output carried. Files-in and roadmaps-out say nothing about coverage — a run
758
+ can read every file, verify every claim, emit three roadmaps, and answer a
759
+ third of what the source asked. **This ledger is the difference between
760
+ having read a source and having answered it.**
761
+ 5. **Owner decisions required**, if any — the escalation block from Phase 5,
762
+ at the top of the reply rather than at the end.
763
+ 6. One closing summary: files in, roadmaps out, items prevented by
612
764
  `already-fixed`, and steps corrected by reproduction — plus the reproduction
613
765
  ceiling that fired, if one did, and what it dropped.
614
766
 
@@ -633,6 +785,20 @@ each one is and why it is spent, and let the user remove it.
633
785
  execution authorization
634
786
  ([`scope-control`](../../../../rules/scope-control.md) § Authoring vs.
635
787
  implementation).
788
+ - Drop a point because it is phrased as a feeling. A complaint carries a demand
789
+ and goes through Phase 5b like any other: "this is annoying", "das nervt",
790
+ "wenn es endlich mal …". <!-- md-language-check: ignore -->
791
+ Tone is not a truth value, and the bucket a point lands in is never a
792
+ judgement about whether it deserves an answer.
793
+ - Re-prioritize a graded source silently. Moving an item the author called `P0`
794
+ is allowed; moving it without saying so replaces the author's ordering with
795
+ the reader's under cover of analysis.
796
+ - Record a recurrence count in the round's own evidence file and nowhere else.
797
+ The next round does not read it, so the count restarts and the item arrives
798
+ "for the first time" again.
799
+ - Resolve an owner-reserved recurrence into an accurate paragraph. Naming a
800
+ state that hands nobody anything to do is what produced nine arrivals of one
801
+ item; past the third, the escalation block is the output.
636
802
  - Spin up a branch, a worktree, or a PR because the run feels large. Scope is
637
803
  the operator's call; without `--worktree` or an explicit ask, the work stays
638
804
  in the checked-out branch.
@@ -0,0 +1,336 @@
1
+ ---
2
+ model_tier: high
3
+ name: fix-commit-messages
4
+ pack: engineering-base
5
+ visibility: internal
6
+ cluster: fix
7
+ sub: commit-messages
8
+ skills: [conventional-commits-writing, git-workflow]
9
+ description: Measure the repo's commit convention, ask which style to standardise on, then rewrite past commit subjects to it — own commits and full history by default
10
+ suggestion:
11
+ eligible: false
12
+ rationale: "Cluster sub-command — reached via its cluster head's routing or its explicit /cluster:sub name; not independently suggested (surface-consolidation)."
13
+ workspaces:
14
+ - agent-config-maintainer
15
+ packs:
16
+ - meta
17
+ ---
18
+
19
+ # /fix commit-messages
20
+ ## Instructions
21
+
22
+ Retro-fit past commit **subjects** (and bodies, if the user asks) to one
23
+ convention. This rewrites history: every touched commit gets a new hash, and
24
+ anything already pushed needs a force-push and a re-clone by everyone else.
25
+ Treat the whole command as a Hard-Floor operation
26
+ ([`non-destructive-by-default`](../../../rules/non-destructive-by-default.md)) —
27
+ the user invoking it authorises the *investigation*, not the rewrite.
28
+
29
+ **Author identity is preserved on both paths.** `git filter-repo` leaves author
30
+ *and* committer name, email and date untouched when only the message changes;
31
+ the `git rebase` fallback preserves the author trio and resets the committer,
32
+ because it re-commits. Say which path you are on before asking for the go-ahead
33
+ — and say only what is true of that path.
34
+
35
+ ### 1. Refuse early, on facts, not on caution
36
+
37
+ Stop and report — do not proceed to step 2 — when any holds:
38
+
39
+ - **The working tree is dirty.** `git status --porcelain` non-empty → ask the
40
+ user to commit or stash first. A rewrite over a dirty tree loses work.
41
+ - **A rebase, merge, cherry-pick or bisect is in progress**
42
+ (`.git/rebase-merge`, `.git/rebase-apply`, `MERGE_HEAD`, `CHERRY_PICK_HEAD`,
43
+ `BISECT_LOG`). Finish it first.
44
+ - **`git config user.email` is empty.** Step 5's default scope is built from
45
+ it, and an empty value makes the author filter match everything — the
46
+ opposite of the default. Ask for the address rather than inferring one.
47
+ - **`git filter-repo` is not installed** (`git filter-repo --version`). Per
48
+ [`missing-tool-handling`](../../../rules/missing-tool-handling.md): stop and
49
+ ask with numbered options — install it, or use the `git rebase --exec`
50
+ fallback in step 7b, which is slower, resets committer metadata, and stops on
51
+ every conflict. Never install it silently, and never fall back to
52
+ `git filter-branch`, which Git itself deprecates for exactly this use.
53
+ - **The range contains commits authored by someone whose work is in flight.**
54
+ There is no metadata that distinguishes "a peer session wrote this" from "you
55
+ wrote this" — both carry the same `user.email` — so the checkable form is the
56
+ one to use: `git worktree list` plus `git log <range> --format='%aE' | sort -u`.
57
+ More than one worktree on this repository, or an author in the range who is
58
+ not the invoking user, is the case to raise rather than resolve.
59
+
60
+ ### 2. Establish the target convention — measure before offering
61
+
62
+ Run `## Procedure: Establish the house convention` from
63
+ [`conventional-commits-writing`](../../../skills/conventional-commits-writing/SKILL.md)
64
+ verbatim: tier-1 sources first (opened and read, never taken from a grep hit),
65
+ then the capped-weighted consensus pass with bots, merges, reverts and release
66
+ automation excluded.
67
+
68
+ Report what it found, in one block, before offering anything:
69
+
70
+ ```
71
+ Configured : none found (no commitlint, .gitmessage, commit-msg hook, CONTRIBUTING § Commits)
72
+ Measured : ticket-prefix 84% of 118 capped commits (137 eligible, 6 authors, last 24 months)
73
+ Runner-up : conventional 11%
74
+ Coherence : stable (newer half 82%, older half 86%)
75
+ Parsers : none — no semantic-release / git-cliff / conventional-changelog / release-please
76
+ ```
77
+
78
+ The share is quoted against the **capped** total, because that is what it was
79
+ computed over; the raw eligible count sits beside it and is not the denominator.
80
+
81
+ `Parsers` is the line that changes the recommendation: release automation that
82
+ reads subjects makes Conventional Commits the answer regardless of what the
83
+ history looks like, and a rewrite away from it breaks the release pipeline.
84
+
85
+ ### 3. Ask which style — one question, custom always available
86
+
87
+ One numbered-options block (per [`user-interaction`](../../../rules/user-interaction.md)),
88
+ recommendation on its own line underneath. The measured family leads when it
89
+ cleared the evidence bar; Conventional Commits leads when it did not, or when a
90
+ parser exists:
91
+
92
+ ```
93
+ 1. Keep the house style — `[JIRA-123] Subject` (84% of commits already match)
94
+ 2. Conventional Commits — `feat(scope): subject`
95
+ 3. Plain imperative — `Add the thing` (no prefix)
96
+ 4. My own — describe or paste it (see below)
97
+ ```
98
+
99
+ **Option 4 is never omitted, whatever the measurement found.** Take the user's
100
+ answer as either a template with placeholders (`[{TICKET}] {Subject}`,
101
+ `{type}({scope}): {subject}`) or two or three example subjects, and echo back
102
+ the pattern you derived from it plus one rewritten sample from their real
103
+ history before touching anything. A custom style the user cannot recognise in a
104
+ sample of their own commits is a misread, and this is the cheap moment to catch
105
+ it.
106
+
107
+ **A tier-1 validator vetoes an incompatible choice.** `git filter-repo` does not
108
+ run `commit-msg` hooks, so a rewrite away from a format the repo *enforces*
109
+ completes silently and then fails commitlint on the next CI run or the next
110
+ local commit. When step 2 found an enforcing source, check the chosen style
111
+ against it — run the validator over one rewritten sample if it can be run at all
112
+ — and if they disagree, say so and stop rather than rewriting into a state the
113
+ repo rejects.
114
+
115
+ Whatever is chosen, these hold and are not offered as choices — they are floors
116
+ a style selection may not lower (see the skill's *What a measurement may never
117
+ lower*): no attribution or co-author trailers the user did not ask for
118
+ ([`no-attribution-footers`](../../../rules/no-attribution-footers.md)), no emoji in
119
+ subjects ([`no-decorative-emojis-in-git-surfaces`](../../../rules/no-decorative-emojis-in-git-surfaces.md)),
120
+ and no secret pulled into a message
121
+ ([`secret-vcs-guard`](../../../rules/secret-vcs-guard.md)).
122
+
123
+ ### 4. Ask how far back — default is everything
124
+
125
+ Second question, its own turn:
126
+
127
+ ```
128
+ 1. All commits (default)
129
+ 2. Since a date — e.g. 2026-01-01
130
+ 3. Since a ref — e.g. the last tag, or origin/main
131
+ ```
132
+
133
+ ```bash
134
+ BASE=$(git rev-list -1 --before='<date>' HEAD) # empty => whole history
135
+ git show -s --format='%h authored %ad committed %cd %s' "$BASE"
136
+ ```
137
+
138
+ **`--before` filters on COMMITTER date, not author date** — so on a rebased,
139
+ cherry-picked or previously-rewritten history the boundary is not where the
140
+ user thinks it is, and a second run of this command resolves against the
141
+ timestamps the *first* run wrote. That is why the second line is not optional:
142
+ show the resolved commit with both dates and let the user confirm it is the
143
+ boundary they meant. Keep the SHA from then on, never the date.
144
+
145
+ An empty `BASE` means the range reaches the root commit — say that plainly, and
146
+ carry it into step 7, where both paths need a different form for it.
147
+
148
+ ### 5. Scope by author — own commits unless told otherwise
149
+
150
+ **Default: only commits whose author email matches the current user.** Widen
151
+ only on an explicit instruction the user gave — either in the original
152
+ invocation ("fix all of them", "also [name]'s") or as the answer here. Do not
153
+ offer widening as an option when they did not raise it; rewriting a colleague's
154
+ commit message is their call, not a menu item.
155
+
156
+ ```bash
157
+ ME=$(git config user.email) # step 1 refused if this is empty
158
+ git log ${BASE:+$BASE..}HEAD --no-merges --author="<$ME>" --pretty=oneline | wc -l
159
+ ```
160
+
161
+ **The angle brackets are load-bearing.** `--author` is an unanchored regex
162
+ against `Name <email>`, so a bare `m@corp.com` also matches `Tim
163
+ <tim@corp.com>` — every colleague whose address ends with yours lands in the
164
+ default scope. `<$ME>` cannot match inside a longer address.
165
+
166
+ Also handle the mailmap case: a user with several historical addresses
167
+ (`git log --format='%aE' | sort -u`) should be shown the list and asked which
168
+ are theirs, once, rather than silently missing half their own commits. Carry the
169
+ result as a **set** of addresses — step 7b consumes the same set, and a scope
170
+ the preview and the rewrite disagree on is the defect in step 6's own table.
171
+
172
+ ### 6. Show the plan, get both authorisations
173
+
174
+ Produce a preview table — old subject → new subject — for every commit in
175
+ scope, capped at 40 rows with a count of the remainder. Include the rows where
176
+ **nothing changes**: a commit already in the target style is a row that proves
177
+ the classifier read it correctly.
178
+
179
+ The table must be built with the **same** predicate step 7b will apply — the
180
+ author set, merges excluded, the same range. A preview built from `--author`
181
+ substring matching against a rewrite keyed on exact addresses describes a
182
+ different operation than the one the user is approving.
183
+
184
+ Then, in one block, state:
185
+
186
+ - the number of commits rewritten and the number left alone;
187
+ - whether the range contains commits that are **already pushed**. Fetch first —
188
+ `git fetch origin` then `git branch -r --contains <oldest-in-range>` — because
189
+ the check reads `refs/remotes/`, and a stale tracking ref reports "local only"
190
+ for a branch a teammate already has;
191
+ - whether the range crosses the **default branch** — rewriting merged history
192
+ is a different order of disruption and gets its own sentence;
193
+ - the backup ref you are about to write.
194
+
195
+ **Ask for the rewrite and, when the range is already pushed, the force-push in
196
+ the same block.** [`git-history-discipline`](../../../rules/git-history-discipline.md)
197
+ requires a rewrite of pushed history to be re-pushed in the same turn or not
198
+ performed at all, and
199
+ [`non-destructive-by-default`](../../../rules/non-destructive-by-default.md)
200
+ forbids acting in the turn you ask. Splitting the two questions satisfies
201
+ neither: a "no" to a later push question strands the repository in exactly the
202
+ rewritten-but-unpushed state the first rule forbids. So both answers come first,
203
+ then both actions run, in the turn after the answer. A "no" to either means
204
+ nothing is rewritten at all.
205
+
206
+ Wait. Do not run anything in the turn you ask.
207
+
208
+ ### 7. Rewrite
209
+
210
+ **7a. Backup, and capture what the verification will compare against.**
211
+
212
+ ```bash
213
+ git tag "backup/pre-message-fix-$(date +%Y%m%d-%H%M%S)"
214
+ git rev-list --count HEAD > /tmp/precount.txt
215
+ git log ${BASE:+$BASE..}HEAD --pretty=format:'%aN%x09%aE%x09%ad%x09%s' > /tmp/preplan.txt
216
+ ```
217
+
218
+ The count and the subject/author list are captured **before** the rewrite
219
+ because step 7c compares against them. A verification that has no baseline is
220
+ not a verification.
221
+
222
+ **7b. Rewrite the messages.**
223
+
224
+ `git filter-repo` with a `--commit-callback`, because the callback needs the
225
+ author email to honour the scope from step 5 — `--message-callback` sees the
226
+ message alone and cannot. A worked example, normalising a ticket prefix to one
227
+ bracketed form:
228
+
229
+ ```bash
230
+ git filter-repo --force --refs "${BASE:+$BASE..}HEAD" \
231
+ --commit-callback '
232
+ import re
233
+ ME = {b"me@example.com"} # the step-5 address set, bare, no <>
234
+ PAT = re.compile(rb"^\[?([A-Z][A-Z0-9]+-[0-9]+)\]?[: ]\s*(.+)$")
235
+ if len(commit.parents) > 1:
236
+ return
237
+ if commit.author_email not in ME:
238
+ return
239
+ subject, sep, body = commit.message.partition(b"\n")
240
+ m = PAT.match(subject)
241
+ if m:
242
+ commit.message = b"[" + m.group(1) + b"] " + m.group(2) + sep + body
243
+ '
244
+ ```
245
+
246
+ Three things that make the difference between this rewriting the history and
247
+ rewriting nothing:
248
+
249
+ - **`commit.author_email` is the bare address** — `b"me@example.com"`, never
250
+ `b"<me@example.com>"`. Compared against the bracketed form it is unequal for
251
+ every commit, the callback returns on every commit, filter-repo prints
252
+ `Completely finished` and the hashes are identical. The angle brackets belong
253
+ in `--author` (step 5) and nowhere else, and the two steps are easy to
254
+ cross-wire precisely because they need opposite forms.
255
+ - **Merges are skipped in the callback too**, or the rewrite touches commits the
256
+ step-6 table did not list.
257
+ - **`PAT` and the replacement are the step-3 pattern**, substituted for real.
258
+ A callback that references a helper nothing defines is not a callback that
259
+ failed loudly — Python evaluates the name only if control reaches it, so a
260
+ wrong author filter hides the missing helper completely.
261
+
262
+ `--refs` implies partial mode, which is what keeps the `origin` remote that
263
+ plain `filter-repo` strips as a safety measure; `--partial` is not needed
264
+ alongside it. Keep bodies intact unless the user asked for body edits — the
265
+ question in step 3 was about subjects.
266
+
267
+ Fallback without `filter-repo`: a script that reads the subject, applies the
268
+ pattern and amends, run once per commit by `git rebase --exec`:
269
+
270
+ ```bash
271
+ git rebase --exec 'agents/tmp/retitle.sh' "$BASE" # whole history: --root instead of "$BASE"
272
+ ```
273
+
274
+ Not `-i` (it opens the sequence editor for nothing) and not
275
+ `--reset-author=false` (`--reset-author` takes no value — Git rejects it with an
276
+ `option ... takes no value` error, the exec fails, and the rebase stops
277
+ mid-flight; verified against git 2.x). `git commit --amend -m "<new>"` inside the script preserves
278
+ the author trio and resets the committer. `--root` does rewrite the root commit;
279
+ the constraint on the fallback is conflicts and speed, not reachability.
280
+
281
+ If an exec does fail, the repository is left detached mid-rebase — `git rebase
282
+ --abort` first, then the step-9 recovery command. `git reset --hard` alone does
283
+ not exit that state.
284
+
285
+ **7c. Verify against the baseline, not against the tree.**
286
+
287
+ ```bash
288
+ git rev-list --count HEAD # equals /tmp/precount.txt
289
+ git diff <backup-tag> HEAD --stat # MUST be empty
290
+ git log ${BASE:+$BASE..}HEAD --pretty=format:'%aN%x09%aE%x09%ad%x09%s' > /tmp/postplan.txt
291
+ diff /tmp/preplan.txt /tmp/postplan.txt
292
+ ```
293
+
294
+ The empty `git diff` proves **no content moved** — and it is equally empty when
295
+ the rewrite did nothing at all, so on its own it is a tautology, not evidence.
296
+ The `diff` of the two plan files is what carries the claim: every line that
297
+ differs must differ only in its last field, and the set of changed subjects must
298
+ match the step-6 preview. Author name, email and date are the first three
299
+ fields, so the same diff proves they survived. Report the actual output of both.
300
+
301
+ ### 8. Push
302
+
303
+ The authorisation was taken in step 6, together with the rewrite's. Push in this
304
+ turn:
305
+
306
+ ```bash
307
+ git push --force-with-lease origin <branch>
308
+ git log --oneline -3 origin/<branch>
309
+ ```
310
+
311
+ `--force-with-lease`, never bare `--force`. Leave the backup tag in place. Tell
312
+ the user its name, and that recovery is `git reset --hard <backup-tag>` — plus a
313
+ force-push, since the rewrite was already pushed.
314
+
315
+ ### 9. Report
316
+
317
+ - Convention chosen, and how it was established (measured / user-supplied / tier-1).
318
+ - Range, author scope, commits rewritten vs unchanged.
319
+ - The `diff /tmp/preplan.txt /tmp/postplan.txt` result and the
320
+ `git diff <backup-tag> HEAD --stat` result, both quoted.
321
+ - The backup tag and the one-line recovery command.
322
+ - Whether it was pushed, and to where.
323
+
324
+ ## Do NOT
325
+
326
+ - Do NOT rewrite anything before both the style answer and the step-6 yes.
327
+ - Do NOT split the rewrite and force-push authorisations across two turns.
328
+ - Do NOT widen past the invoking user's own commits without an explicit
329
+ instruction — and never by offering it as a menu option they did not raise.
330
+ - Do NOT compare `commit.author_email` against a bracketed address, or pass a
331
+ bare address to `--author`.
332
+ - Do NOT treat an empty `git diff <backup> HEAD` as proof the rewrite happened.
333
+ - Do NOT use `git filter-branch`, bare `git push --force`, or `--no-verify`.
334
+ - Do NOT rewrite into a style the repo's own validator rejects.
335
+ - Do NOT let a chosen style carry an attribution trailer, a subject emoji, or
336
+ anything `secret-vcs-guard` would stop at write time.
@@ -2,10 +2,10 @@
2
2
  model_tier: medium
3
3
  name: fix
4
4
  disable-model-invocation: true
5
- argument-hint: "[ci|refs|portability|seeder|pr-comments|pr-comments-loop|comments|quality|route] [args]"
5
+ argument-hint: "[ci|refs|portability|seeder|pr-comments|pr-comments-loop|comments|commit-messages|quality|route] [args]"
6
6
  pack: engineering-base
7
- intent: "Fix-workflow dispatcher — ci, pr-comments, pr-comments-loop, refs, seeder, portability, comments, quality"
8
- routes_to: [fix-ci, fix-pr-comments, fix-pr-comments-loop, fix-refs, fix-seeder, fix-portability, fix-comments, fix-quality, fix-route]
7
+ intent: "Fix-workflow dispatcher — ci, pr-comments, pr-comments-loop, refs, seeder, portability, comments, commit-messages, quality"
8
+ routes_to: [fix-ci, fix-pr-comments, fix-pr-comments-loop, fix-refs, fix-seeder, fix-portability, fix-comments, fix-commit-messages, fix-quality, fix-route]
9
9
  replaces: []
10
10
  visibility: advanced
11
11
  description: Fix orchestrator — routes to ci, references, portability, seeder, pr-comments, comments, quality
@@ -38,6 +38,7 @@ with a single entry point + sub-command dispatch.
38
38
  | `/fix pr-comments` | `commands/fix/pr-comments.md` | Fix and reply to all open review comments — **bot + human, classified per comment**; dedupes by comment id + reply marker |
39
39
  | `/fix pr-comments-loop` | `commands/fix/pr-comments-loop.md` | Autonomous loop over `pr-comments` (auto mode): fix → commit+push → re-request Copilot review → wait — repeats until Copilot has no new comments; requires a PR URL |
40
40
  | `/fix comments` | `commands/fix/comments.md` | Review the **code comments** in the current branch's diff and simplify, shorten, or remove each one (≠ `pr-comments`, which targets GitHub review threads) |
41
+ | `/fix commit-messages` | `commands/fix/commit-messages.md` | Measure the repo's commit convention, ask which style (custom always offered) and how far back, then rewrite past commit **subjects** to it — own commits by default, author identity preserved, backup tag + explicit rewrite/push gates |
41
42
  | `/fix quality` | `commands/fix/quality.md` | Run the quality pipeline (type-checker / linter / formatter, PHP and/or JS/TS) and fix every error — auto-detects language from changed files |
42
43
  | `/fix route` | `commands/fix/route.md` | Classify a vaguely-described problem and dispatch to the right fix sub (or name the specialist when it is not a fix task) — the `/smart-fix` front door, folded into the `fix` verb |
43
44
 
@@ -60,6 +61,7 @@ the `auto_detect` kill-switch, rollback). Detection table:
60
61
  | Source-code comments are the target ("simplify/clean up the comments in my branch", "trim comment noise") | `fix/comments` | HIGH |
61
62
  | Seeder / FK breakage named | `fix/seeder` | HIGH |
62
63
  | Type-checker / linter / formatter errors in context ("fix the quality errors", PHPStan/tsc/eslint output) | `fix/quality` | HIGH |
64
+ | Past commit **messages** are the target ("clean up my commit messages", "make the history conventional") | `fix/commit-messages` | HIGH |
63
65
  | Project-specific leakage in shared package named | `fix/portability` | MEDIUM |
64
66
  | No clear signal, or ≥ 2 conflict | — | LOW → menu (interactive) / `ambiguous_routing` (CI) |
65
67
 
@@ -18,7 +18,7 @@ obligation_frequency: "per-commit"
18
18
 
19
19
  # Commit Conventions
20
20
 
21
- **Iron Law.** Use Conventional Commits (`feat:`, `fix:`, `chore:` …); branches `<type>/<short-slug>`; never invent your own format. Subjects + branch names stay emoji-free per [`no-decorative-emojis-in-git-surfaces`](no-decorative-emojis-in-git-surfaces.md); body emojis allowed only with an in-artifact legend.
21
+ **Iron Law.** Match the repo's configured or approved convention; else Conventional Commits (`feat:`, `fix:` …), branches `<type>/<short-slug>`; never invent one. Subjects + branch names stay emoji-free per [`no-decorative-emojis-in-git-surfaces`](no-decorative-emojis-in-git-surfaces.md); body emojis need an in-artifact legend.
22
22
 
23
23
  Body migrated to `skill:conventional-commits-writing` (per P4 of `road-to-kernel-and-router.md`).
24
24
  Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
@@ -3,11 +3,8 @@ type: "auto"
3
3
  tier: "2a"
4
4
  alwaysApply: false
5
5
  description: "Security-sensitive paths (auth, billing, tenants, secrets, uploads, webhooks) — threat-model BEFORE editing"
6
- # Trigger set disjoint from secret-vcs-guard by design (2026-08-04): bare
7
- # `secret` / `password` belong to the VCS-write surface (secret-vcs-guard);
8
- # this rule keeps the conversational security surface (editing auth/billing/
9
- # tenant/webhook paths and secrets *infrastructure*, not committing a
10
- # credential).
6
+ # Disjoint from secret-vcs-guard by design (2026-08-04): bare `secret` /
7
+ # `password` are that rule's VCS-write surface, not this rule's.
11
8
  triggers:
12
9
  - keyword: "auth"
13
10
  - keyword: "billing"
@@ -15,6 +12,11 @@ triggers:
15
12
  - keyword: "webhook"
16
13
  - keyword: "oauth"
17
14
  - keyword: "signing key"
15
+ - keyword: "file upload"
16
+ - keyword: "ssrf"
17
+ - keyword: "api resource"
18
+ - keyword: "public endpoint"
19
+ - keyword: "status endpoint"
18
20
  validator_ignore:
19
21
  - type: "substring"
20
22
  pattern: "../../docs/"