@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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +84 -153
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +172 -6
- package/dist/agent-src/commands/fix/commit-messages.md +336 -0
- package/dist/agent-src/commands/fix.md +5 -3
- package/dist/agent-src/rules/commit-conventions.md +1 -1
- package/dist/agent-src/rules/security-sensitive-stop.md +7 -5
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +20 -2
- package/dist/agent-src/skills/aws-infrastructure/SKILL.md +41 -0
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +234 -4
- package/dist/agent-src/skills/design-intelligence/data/ux-guidelines.csv +99 -99
- package/dist/agent-src/skills/fe-design/SKILL.md +6 -3
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +28 -0
- package/dist/agent-src/skills/iconography/SKILL.md +37 -6
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/terraform/SKILL.md +29 -0
- package/dist/agent-src/skills/threat-modeling/data/reasoning.csv +1 -0
- package/dist/agent-src/skills/threat-modeling/data/surfaces.csv +1 -0
- package/dist/agent-src/skills/threat-modeling/data/threats.csv +5 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/cmd_conformance.js +158 -33
- package/dist/cli-delegate/cmd_explain.js +24 -112
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +47 -21
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +169 -41
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +160 -35
- package/dist/router.json +1 -1
- package/docs/guidelines/design-antipatterns.md +10 -2
- package/package.json +1 -1
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/web-launch-readiness.json +1 -1
- package/src/scripts/_cli/explain_last/index.ts +1 -1
- package/src/scripts/_cli/explain_last/memory.ts +8 -104
- package/src/scripts/check_artefact_count_messaging.ts +9 -0
- package/src/scripts/check_release_highlights.ts +77 -4
- package/src/scripts/check_web_launch_readiness.ts +295 -15
- package/src/scripts/design_slop_rules.ts +19 -9
- package/src/scripts/git_authorization_hook.ts +257 -35
- package/src/scripts/hooks/block_unauthorized_git.ts +16 -4
- package/src/scripts/hooks/merge_impact.ts +68 -0
- package/src/scripts/measure_release_mix.ts +384 -0
- package/src/scripts/release.ts +4 -17
- package/src/scripts/release_drill.ts +154 -1
- package/src/scripts/release_mix_taxonomy.json +172 -0
- package/src/scripts/release_publication.ts +91 -0
- 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)
|
|
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
|
|
234
|
-
|
|
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
|
|
446
|
-
|
|
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.
|
|
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.**
|
|
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
|
-
#
|
|
7
|
-
# `
|
|
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/"
|