@event4u/agent-config 9.2.0 → 9.3.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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/fix/route.md +49 -0
- package/dist/agent-src/commands/fix.md +4 -2
- package/dist/agent-src/commands/tdd/green.md +32 -0
- package/dist/agent-src/commands/tdd/red.md +33 -0
- package/dist/agent-src/commands/tdd/refactor.md +31 -0
- package/dist/agent-src/commands/tdd.md +50 -0
- package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +81 -2
- package/dist/agent-src/rules/architecture.md +7 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -0
- package/dist/agent-src/skills/judge-code-quality/SKILL.md +1 -0
- package/dist/agent-src/skills/persona-improvement/SKILL.md +81 -0
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +10 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +12 -23
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +169 -16
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +11 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +10 -4
- package/dist/mcp/registry-manifest.json +3 -3
- package/docs/CLAIMS.md +2 -2
- package/docs/architecture.md +3 -3
- package/docs/command-flows.md +9 -3
- package/docs/contracts/command-clusters.md +2 -1
- package/docs/decisions/ADR-122-adversarial-verification-council.md +1 -1
- package/docs/development.md +10 -1
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/component-oriented-and-oop-development.md +181 -0
- package/docs/proof.md +5 -5
- package/package.json +3 -2
- package/src/scripts/ai_council/prompts.ts +7 -0
- package/src/scripts/hook_manifest.yaml +17 -6
- package/src/scripts/install-hooks.sh +32 -17
- package/src/scripts/pr_url_reminder_hook.ts +177 -0
|
@@ -33,7 +33,7 @@ the host lacks (a static host gives it no runtime attention guarantee, but a
|
|
|
33
33
|
runtime-only system gives it nothing at all here). A runtime-only system contributes *nothing* on these hosts.
|
|
34
34
|
|
|
35
35
|
### 2. Intent routing, not a 500-artefact dump
|
|
36
|
-
A prompt does not load all
|
|
36
|
+
A prompt does not load all 276 skills. `dist/router.json` (101 routed rules, intent →
|
|
37
37
|
skill, keyword/phrase/path triggers) carries the right skill on intent. The
|
|
38
38
|
governance value is *selection discipline* — the host gets the relevant rule
|
|
39
39
|
set, not the whole catalogue as context ballast.
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Component-Oriented & Object-Oriented Development
|
|
2
|
+
|
|
3
|
+
> Cross-cutting development standard: **think in reusable units, and — in an
|
|
4
|
+
> object-oriented / class-based codebase — treat object-oriented design as a
|
|
5
|
+
> high-priority default where it reduces complexity.** Split by surface below
|
|
6
|
+
> (frontend / backend), each pointing to the stack-specific carve-outs rather
|
|
7
|
+
> than restating them. Framework-neutral by construction — React / Storybook /
|
|
8
|
+
> Laravel / Symfony specifics live in their own skills, never here.
|
|
9
|
+
|
|
10
|
+
## Paradigm-appropriate first — detect, then apply
|
|
11
|
+
|
|
12
|
+
**Reuse is universal; OOP is paradigm-scoped.** Detect the codebase's paradigm
|
|
13
|
+
before applying the object-oriented half: class-based / OO code (PHP, Java, C#,
|
|
14
|
+
typical TS/React service layers) → this standard's OOP guidance is the
|
|
15
|
+
high-priority default. A **functional / pipeline / actor-model** codebase
|
|
16
|
+
(Elixir/Phoenix, Clojure, Haskell, Go-idiomatic, heavy FP-in-TS) → apply *that*
|
|
17
|
+
paradigm's principles (immutability, pure functions, pattern-matching,
|
|
18
|
+
composition of functions), and read the OOP bullets as "the same rigor, its
|
|
19
|
+
paradigm's shape." **Never impose classes on functional code to satisfy this
|
|
20
|
+
standard** — consistency with the codebase's chosen paradigm wins over OOP
|
|
21
|
+
advocacy, always.
|
|
22
|
+
|
|
23
|
+
## The standard, in one breath
|
|
24
|
+
|
|
25
|
+
- **Reuse before you build** (every paradigm). Compose from units that already
|
|
26
|
+
exist — audit first, reuse the design system / shared abstractions, extract a
|
|
27
|
+
new unit only when the same shape repeats.
|
|
28
|
+
- **Object-oriented where it fits** (in an OO codebase). Encapsulation, single
|
|
29
|
+
responsibility, composition over inheritance, and behavior-rich objects are
|
|
30
|
+
the high-priority default *when they reduce complexity* — not a quota to hit,
|
|
31
|
+
and not imposed on a codebase that has chosen a different paradigm.
|
|
32
|
+
|
|
33
|
+
## The load-bearing caveat — reuse/OOP is a tool, never a mandate to abstract
|
|
34
|
+
|
|
35
|
+
This standard **never** authorizes speculative abstraction. It is bounded by the
|
|
36
|
+
package's restraint posture and defers to it on every conflict:
|
|
37
|
+
|
|
38
|
+
- [`minimal-safe-diff`](../../src/rules/minimal-safe-diff.md) (+ its
|
|
39
|
+
anti-over-engineering mechanics) — smallest change; no premature abstraction,
|
|
40
|
+
no speculative feature, no rewrite for taste.
|
|
41
|
+
- [`senior-engineering-discipline`](../../src/rules/senior-engineering-discipline.md)
|
|
42
|
+
— reuse the *existing* abstraction; generalize, don't overfit; never invent an
|
|
43
|
+
API to look object-oriented.
|
|
44
|
+
- [`improve-before-implement`](../../src/rules/improve-before-implement.md) —
|
|
45
|
+
"does it fit the existing architecture?" before adding structure.
|
|
46
|
+
- **When NOT to use a pattern** ([`php/patterns.md`](php/patterns.md) §) — KISS &
|
|
47
|
+
YAGNI trump pattern purity: no interface for a single implementation, no
|
|
48
|
+
pattern for a five-line function, no class where a function is clearer.
|
|
49
|
+
|
|
50
|
+
Rule of thumb: **two real repetitions (or a genuine second axis of change)
|
|
51
|
+
before you extract.** One occurrence is not a component; one branch is not a
|
|
52
|
+
Strategy. A reviewer should be able to name the concrete duplication or the
|
|
53
|
+
concrete change-axis the abstraction removes.
|
|
54
|
+
|
|
55
|
+
**Precedence (explicit): `minimal-safe-diff` / YAGNI always trump OOP
|
|
56
|
+
advocacy.** When "prefer OO shape" and "smallest change" conflict, the smallest
|
|
57
|
+
change wins — this standard adds a *consideration*, never a competing objective
|
|
58
|
+
that pressures preemptive abstraction. The review lens (below) may flag OO shape
|
|
59
|
+
only where the duplication or the growing branch is **already present in the
|
|
60
|
+
diff**, never as "this could grow later."
|
|
61
|
+
|
|
62
|
+
## Frontend — think in reusable components
|
|
63
|
+
|
|
64
|
+
Component-first is the default mindset, not an afterthought: decompose a screen
|
|
65
|
+
into composable units, reuse the project's primitives, and extract shared shells
|
|
66
|
+
when they repeat. Depth and mechanics already live in dedicated skills — consult
|
|
67
|
+
them, don't duplicate:
|
|
68
|
+
|
|
69
|
+
- [`fe-design`](../../src/skills/fe-design/SKILL.md) § Component Architecture —
|
|
70
|
+
the stack-agnostic "kind, not framework" taxonomy, granularity, extract-on-3+.
|
|
71
|
+
- [`ui-component-architect`](../../src/skills/ui-component-architect/SKILL.md) —
|
|
72
|
+
how to *shape* a reusable component: composition over inheritance, slot
|
|
73
|
+
contracts, prop-API caps, controlled/uncontrolled, the componentization
|
|
74
|
+
threshold (repeat **and** real state).
|
|
75
|
+
- [`existing-ui-audit`](../../src/skills/existing-ui-audit/SKILL.md) — the
|
|
76
|
+
reuse-before-invent gate: inventory existing components/tokens/primitives
|
|
77
|
+
first (enforced by [`ui-audit-gate`](../../src/rules/ui-audit-gate.md)).
|
|
78
|
+
- [`design-tokens`](../../src/skills/design-tokens/SKILL.md) /
|
|
79
|
+
[`design-system-capture`](../../src/skills/design-system-capture/SKILL.md) —
|
|
80
|
+
the shared token/design-system layer components draw from.
|
|
81
|
+
|
|
82
|
+
**Component workshop (Storybook et al.) — when the project is large enough.**
|
|
83
|
+
For a project with a real, growing shared-component library, isolating and
|
|
84
|
+
documenting each reusable component in a component workshop (Storybook or the
|
|
85
|
+
ecosystem equivalent) pays for itself — it makes components discoverable,
|
|
86
|
+
reviewable in isolation, and reused instead of re-invented. It is **not** worth
|
|
87
|
+
the setup for a small surface with a handful of one-off components. The
|
|
88
|
+
framework-specific setup is a carve-out, not a generic mandate — React /
|
|
89
|
+
Storybook specifics: [`react-shadcn-ui`](../../src/skills/react-shadcn-ui/SKILL.md)
|
|
90
|
+
§ Component workshop.
|
|
91
|
+
|
|
92
|
+
Framework carve-outs (never restated here): React →
|
|
93
|
+
[`react-shadcn-ui`](../../src/skills/react-shadcn-ui/SKILL.md); Next.js
|
|
94
|
+
server/client component boundary →
|
|
95
|
+
[`nextjs-patterns`](../../src/skills/nextjs-patterns/SKILL.md); Laravel FE →
|
|
96
|
+
[`blade-ui`](../../src/skills/blade-ui/SKILL.md) /
|
|
97
|
+
[`livewire`](../../src/skills/livewire/SKILL.md) /
|
|
98
|
+
[`flux`](../../src/skills/flux/SKILL.md); styling →
|
|
99
|
+
[`tailwind-engineer`](../../src/skills/tailwind-engineer/SKILL.md).
|
|
100
|
+
|
|
101
|
+
## Backend — object-oriented where it reduces complexity
|
|
102
|
+
|
|
103
|
+
The structural half — thin handlers, business logic in services / use-cases,
|
|
104
|
+
behavior-rich domain models, validation at the boundary — is owned by
|
|
105
|
+
[`architecture`](../../src/rules/architecture.md) and holds across paradigms. In
|
|
106
|
+
an **object-oriented / class-based** service layer, layer the OO principles below
|
|
107
|
+
on top of it, each applied only where it earns its place. In a **functional /
|
|
108
|
+
pipeline** codebase, apply that paradigm's equivalent (pure functions +
|
|
109
|
+
composition instead of behavior-rich objects; a dispatch map / pattern-match
|
|
110
|
+
instead of a Strategy class) — the goal (encapsulate change, one responsibility,
|
|
111
|
+
compose don't duplicate) is paradigm-independent; only its *shape* is OO here:
|
|
112
|
+
|
|
113
|
+
- **Encapsulation** — objects own their data + the behavior over it; no anemic
|
|
114
|
+
bags mutated from the outside.
|
|
115
|
+
- **Single responsibility** — one class, one reason to change; no god services.
|
|
116
|
+
- **Composition over inheritance** — inject collaborators; reach for a base
|
|
117
|
+
class only for genuine is-a substitutability, not code sharing.
|
|
118
|
+
- **Behavior-rich domain objects** — push rules into the model, keep services
|
|
119
|
+
orchestration-thin.
|
|
120
|
+
- **Patterns where they earn their place** — a Strategy when a branch on a
|
|
121
|
+
discriminator will grow (`if-else` chain → polymorphism); a Factory when
|
|
122
|
+
construction is non-trivial; DI on an abstraction when there is a *real*
|
|
123
|
+
second implementation. Not before.
|
|
124
|
+
|
|
125
|
+
Language / framework carve-outs (never restated here): PHP →
|
|
126
|
+
[`php-coding-patterns`](php/php-coding-patterns.md) +
|
|
127
|
+
[`patterns.md`](php/patterns.md) (+ `patterns/strategy.md`,
|
|
128
|
+
`patterns/dependency-injection.md`, `patterns/factory.md`, `patterns/service-layer.md`)
|
|
129
|
+
+ skills [`php-coder`](../../src/skills/php-coder/SKILL.md) /
|
|
130
|
+
[`php-service`](../../src/skills/php-service/SKILL.md); Laravel →
|
|
131
|
+
[`laravel`](../../src/skills/laravel/SKILL.md); Symfony →
|
|
132
|
+
[`symfony-workflow`](../../src/skills/symfony-workflow/SKILL.md); Next.js / TS →
|
|
133
|
+
[`nextjs-patterns`](../../src/skills/nextjs-patterns/SKILL.md); cross-language
|
|
134
|
+
clean-code baseline → [`code-clarity`](code-clarity.md).
|
|
135
|
+
|
|
136
|
+
## Enums over scattered literals
|
|
137
|
+
|
|
138
|
+
A field with two or more non-boolean states is an enum, not a bare string /
|
|
139
|
+
magic number — the small OO move that keeps a codebase greppable and safe to
|
|
140
|
+
change. Full rule + the defer-and-ask flow for pre-existing literals:
|
|
141
|
+
[`prefer-enums-over-literals`](../../src/rules/prefer-enums-over-literals.md).
|
|
142
|
+
|
|
143
|
+
## How this is reviewed
|
|
144
|
+
|
|
145
|
+
"Reuse existing components + object-oriented shape (where it reduces
|
|
146
|
+
complexity)" is a check on the code-quality review lens
|
|
147
|
+
([`judge-code-quality`](../../src/skills/judge-code-quality/SKILL.md)), so it
|
|
148
|
+
reaches `/review-changes`, `/judge`, the subagent judge modes, and team-mode's
|
|
149
|
+
fallback review frame. It fires **only where the duplication or the growing
|
|
150
|
+
branch is already present in the diff** — never "this could grow later" — and
|
|
151
|
+
in the **codebase's own paradigm** (it will not tell a functional service to
|
|
152
|
+
grow a class). It stays a human-judgment call that defers to the codebase's own
|
|
153
|
+
conventions — never a generic SOLID dogma imposed over a project that has chosen
|
|
154
|
+
differently.
|
|
155
|
+
|
|
156
|
+
## Council review (2026-07-15)
|
|
157
|
+
|
|
158
|
+
Deep debate, 3 rounds, cross-vendor (`anthropic/claude-sonnet-4-5` +
|
|
159
|
+
`openai/gpt-4o`), $0.16. Split verdict; the load-bearing (anthropic) critique —
|
|
160
|
+
that an "OOP-supreme default" over-indexes on OOP for a multi-paradigm consumer
|
|
161
|
+
base and can conflict with `minimal-safe-diff` — was applied, bounded by this
|
|
162
|
+
standard's stated purpose (OOP *is* a deliberate high priority for the operator):
|
|
163
|
+
|
|
164
|
+
- **Paradigm-appropriate framing** — OOP is the high-priority default *within an
|
|
165
|
+
OO / class-based codebase*; detect and defer to a functional / pipeline /
|
|
166
|
+
actor paradigm; never impose classes on functional code (§ Paradigm-appropriate).
|
|
167
|
+
- **Explicit precedence** — `minimal-safe-diff` / YAGNI win on conflict; the
|
|
168
|
+
review lens flags only *already-present* duplication / complexity, never
|
|
169
|
+
preemptive (§ caveat, § How this is reviewed).
|
|
170
|
+
|
|
171
|
+
Divergence held: the frontend section stays (an intentional FE / backend
|
|
172
|
+
entry-point + carve-out index, per the operator's explicit "split by FE /
|
|
173
|
+
backend" ask) — against the anthropic "delete as redundant"; it is a cohesive
|
|
174
|
+
index of the existing FE surfaces, not a claim of novel guidance. Trace:
|
|
175
|
+
`agents/runtime/council/responses/component-oop-standard.json/` (gitignored, local-only).
|
|
176
|
+
|
|
177
|
+
## See also
|
|
178
|
+
|
|
179
|
+
- [`architecture`](../../src/rules/architecture.md) — the structural home this layers onto.
|
|
180
|
+
- [`code-clarity`](code-clarity.md) — the cross-language clean-code baseline.
|
|
181
|
+
- [`minimal-safe-diff`](../../src/rules/minimal-safe-diff.md) · [`senior-engineering-discipline`](../../src/rules/senior-engineering-discipline.md) — the restraint anchors this standard is bounded by.
|
package/docs/proof.md
CHANGED
|
@@ -27,7 +27,7 @@ evidence pointer, or `task check-claims` fails the build.
|
|
|
27
27
|
| Claim | Kind | Evidence | Resolves |
|
|
28
28
|
|---|---|---|---|
|
|
29
29
|
| The release process is documented as an inheritable runbook + succession doc, and the project's bus-factor (trailing-90-day distinct human reviewers) is tracked and reported truthfully — currently 1, not implied to be more. | qual | `docs/succession.md#trailing 90 days` | ✅ |
|
|
30
|
-
|
|
|
30
|
+
| 189 commands. | quant | `src/scripts/check_artefact_count_messaging.ts#Artefact-count messaging gate` | ✅ |
|
|
31
31
|
| A MEASURED-BUT-NOT-SHIPPED experiment — the thin rule projection reduced eager rule load 78,513 → 13,881 GPT-tokens (whole always-loaded projection 98,529 → 33,897, ~65.6%), but FAILED the quality gate (thin win-rate 36.2% vs required 48%) and does not ship; it un-defers only behind `discipline_profile: essential`. Shipped behavior does NOT include this reduction. Method: `agent-config benchmark` over the pinned token baseline; the baseline is the honest "what the user pays if everything loads eagerly", NOT a synthetic full-corpus strawman (council Q4); quality gate per the Phase-0 paired judge run. | quant | `internal/bench/reports/token-baseline.json#eager_rule_load` | ✅ |
|
|
32
32
|
| The first cross-vendor parity pass (5 orchestration-corpus tasks × 2 vendors × 3 repeats, identical prompts via the council transport, $0.16) measured real per-host finding-count differences — claude-sonnet-4-5 surfaced ~2× the findings of gpt-4o on the multi-file analysis task (median 11 vs 5) while both vendors were identical on the planted hollow-implementation task (2 vs 2) and perfectly silent on the clean-code negative control (0 vs 0, no spurious findings). The per-task `finding_floor` values are calibrated from the cross-host lower envelope and the gate is armed. | quant | `internal/bench/reports/parity-count.json#min over hosts of median` | ✅ |
|
|
33
33
|
| On a weak host (claude-haiku-4-5) the package produces a significant, placebo-controlled discipline lift on scope/downstream traps; on a strong host the same measurement is a published null — the package transplants discipline a weak model lacks, not model intelligence. | quant | `docs/benchmark.md#weak-host-specific` | ✅ |
|
|
@@ -45,7 +45,7 @@ evidence pointer, or `task check-claims` fails the build.
|
|
|
45
45
|
| On a deterministic multi-session recall corpus, the memory substrate produces a measured, placebo-controlled recall lift — memory-on 27/27 vs no-memory 10/27 and vs equal-byte placebo 9/27 (claude-haiku-4-5, n=9 tasks x 3 seeds, sign test p=0.031 for BOTH pairings). Scoped honestly: this is the context-value upper bound (perfect retrieval on a one-fact-per-task corpus), not retrieval precision under a large store. | quant | `internal/bench/reports/second-brain-delta.json` | ✅ |
|
|
46
46
|
| Removing the perfect-retrieval assumption, the substrate's REAL keyword retrieval recalls the needed decision into the top-5 under keyword-overlapping confusers (precision@5 9/9) and the model disambiguates it from the co-injected confusers — retrieval-on 27/27 vs no-memory 5/27 and vs equal-count placebo 5/27 (claude-haiku-4-5, 9 tasks x 3 seeds, sign test p=0.008 both). Named limit: retrieval RECALLS but does not RANK (mean tie-set 3.3, ties broken by store order, not relevance) — the discrimination gap that motivates the SQLite-FTS5 activation path (ADR-116) at larger scale. | quant | `internal/bench/reports/second-brain-retrieval.json` | ✅ |
|
|
47
47
|
| Every artifact the package ships — source AND the condensed projection that reaches consumers — is machine-scanned in CI for hidden-Unicode, mixed-script-confusable, and instruction-smuggling payloads (the rules-file-backdoor class); a finding blocks the release before `npm publish`, not just the merge. | qual | `.github/workflows/publish-npm.yml#lint_agent_security` | ✅ |
|
|
48
|
-
|
|
|
48
|
+
| 276 skills. | quant | `src/scripts/check_artefact_count_messaging.ts#Artefact-count messaging gate` | ✅ |
|
|
49
49
|
| Removes only its own keys from a shared host config (matched by JSON-pointer + SHA-256), never a neighbour tool's entries. | qual | `docs/contracts/install-layout.md#JSON-pointer` | ✅ |
|
|
50
50
|
|
|
51
51
|
**21 backed claim(s)** — all evidence pointers resolve in CI.
|
|
@@ -77,11 +77,11 @@ shipping theater; the full per-cell data is committed at
|
|
|
77
77
|
`internal/bench/reports/persona-placebo.json`.
|
|
78
78
|
|
|
79
79
|
**Behavioural-eval coverage — the honest baseline.** Skill *quality* is only
|
|
80
|
-
as good as its measurement. Today **42 of
|
|
80
|
+
as good as its measurement. Today **42 of 276** skills carry a behavioural
|
|
81
81
|
`evals.json`; the highest-traffic / highest-cost tiers (default-surface +
|
|
82
82
|
`rich` + routers) are **fully covered (35 of 35)**, the long tail
|
|
83
|
-
(7 of
|
|
84
|
-
"
|
|
83
|
+
(7 of 241) is not. We publish that gap rather than imply
|
|
84
|
+
"276 evaluated skills": coverage is measured per tier
|
|
85
85
|
(`./scripts-run src/scripts/skill_eval_coverage`), **CI-ratcheted so it can
|
|
86
86
|
only rise**, and the priority tiers carry a hard **tier floor**: every
|
|
87
87
|
rich / default-surface / router skill MUST have a behavioural eval or an
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.3.0",
|
|
4
4
|
"description": "Universal AI Agent OS — audited skills, governance rules, commands, and templates for AI coding tools (Claude Code, Cursor, Windsurf, Copilot).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"lint:ts": "eslint 'src/**/*.ts'",
|
|
71
71
|
"test:ts": "vitest run",
|
|
72
72
|
"prepack": "npm run build && npm run build:discovery && npm run build:mcp-manifest && node src/scripts/prepack-check.mjs",
|
|
73
|
-
"prepublishOnly": "./scripts-run src/scripts/check_release_includes_discovery"
|
|
73
|
+
"prepublishOnly": "./scripts-run src/scripts/check_release_includes_discovery",
|
|
74
|
+
"prepare": "[ -d .git ] && bash src/scripts/install-hooks.sh || true"
|
|
74
75
|
},
|
|
75
76
|
"publishConfig": {
|
|
76
77
|
"access": "public",
|
|
@@ -88,6 +88,13 @@ correctness lens AND a shipping-risk lens:
|
|
|
88
88
|
Is the blast radius bigger than the title implies?
|
|
89
89
|
5. Reviewer fatigue — is anything in the diff that a tired reviewer
|
|
90
90
|
would rubber-stamp but should not?
|
|
91
|
+
6. Reuse & object-oriented shape — does the diff reinvent a component
|
|
92
|
+
or abstraction the codebase likely already provides (should compose
|
|
93
|
+
/ reuse instead), and where it adds structure, is the OO shape
|
|
94
|
+
(encapsulation, composition over inheritance) justified by
|
|
95
|
+
complexity it removes rather than speculative abstraction? Judge in
|
|
96
|
+
the codebase's own paradigm — do not push classes onto functional /
|
|
97
|
+
pipeline code; smallest-change still wins over OO advocacy.
|
|
91
98
|
End with: APPROVE / REQUEST_CHANGES / REJECT, one sentence why, and
|
|
92
99
|
the single highest-leverage change the PR author should make before
|
|
93
100
|
merge.`;
|
|
@@ -145,6 +145,17 @@ concerns:
|
|
|
145
145
|
script: src/scripts/team_review_gate_hook.ts
|
|
146
146
|
args: []
|
|
147
147
|
fail_closed: false
|
|
148
|
+
# Reply-shape backstop for the direct-answers rule (reply-close): a PR
|
|
149
|
+
# created THIS turn must end the reply as the literal last raw URL. This
|
|
150
|
+
# PostToolUse hook fires right after a PR *create* (gh pr create, or a
|
|
151
|
+
# github create-pull-request tool) that produced a `…/pull/<n>` URL, and
|
|
152
|
+
# WARNS in context (exit 2) so the reminder lands before the reply closes.
|
|
153
|
+
# Never blocks; unconditional (reinforces an always-on rule, fires only on
|
|
154
|
+
# the rare create event, so no settings gate). fail_closed: false.
|
|
155
|
+
pr-url-reminder:
|
|
156
|
+
script: src/scripts/pr_url_reminder_hook.ts
|
|
157
|
+
args: []
|
|
158
|
+
fail_closed: false
|
|
148
159
|
|
|
149
160
|
platforms:
|
|
150
161
|
augment:
|
|
@@ -152,7 +163,7 @@ platforms:
|
|
|
152
163
|
session_end: [chat-history]
|
|
153
164
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
154
165
|
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
155
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
166
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
156
167
|
|
|
157
168
|
claude:
|
|
158
169
|
session_start: [chat-history, hot-context, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
@@ -160,7 +171,7 @@ platforms:
|
|
|
160
171
|
stop: [chat-history, hot-context, verify-before-complete, team-review-gate]
|
|
161
172
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
162
173
|
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
163
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
174
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
164
175
|
|
|
165
176
|
# Cowork — the Claude desktop app's local-agent-mode runtime, built
|
|
166
177
|
# on top of the Claude Code CLI. Same lifecycle vocabulary, same
|
|
@@ -182,7 +193,7 @@ platforms:
|
|
|
182
193
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
183
194
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
184
195
|
pre_tool_use: [block-no-verify, rtk-wrap, design-slop]
|
|
185
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
196
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
186
197
|
|
|
187
198
|
# Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
|
|
188
199
|
# IDE and CLI; `~/.cursor/hooks.json` (user) is opt-in via
|
|
@@ -196,7 +207,7 @@ platforms:
|
|
|
196
207
|
session_end: [chat-history]
|
|
197
208
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
198
209
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
199
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
210
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
200
211
|
|
|
201
212
|
# Phase 7.6 — Cline. Hooks live under `.clinerules/hooks/<HookName>`
|
|
202
213
|
# (project, no file extension, must be executable per Cline docs) or
|
|
@@ -211,7 +222,7 @@ platforms:
|
|
|
211
222
|
session_end: [chat-history]
|
|
212
223
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
213
224
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
214
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
225
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
215
226
|
|
|
216
227
|
# Phase 7.7 — Windsurf (Cascade). Hooks live at `.windsurf/hooks.json`
|
|
217
228
|
# (project) or `~/.codeium/windsurf/hooks.json` (user). Cascade has
|
|
@@ -245,7 +256,7 @@ platforms:
|
|
|
245
256
|
session_end: [chat-history]
|
|
246
257
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
247
258
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
248
|
-
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan]
|
|
259
|
+
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
249
260
|
|
|
250
261
|
# Phase 7.9 — Copilot has no hook surface. Concerns route through
|
|
251
262
|
# rule-only fallback; the dispatcher silently no-ops on --platform copilot.
|
|
@@ -8,32 +8,47 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
8
8
|
# This installer lives at src/scripts/install-hooks.sh, so the repo root
|
|
9
9
|
# (which owns .git/) is two levels up.
|
|
10
10
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
11
|
-
|
|
11
|
+
# Resolve the SHARED hooks dir via git so this also works from a linked worktree
|
|
12
|
+
# (where $PROJECT_ROOT/.git is a file, not a dir, and a literal .git/hooks path
|
|
13
|
+
# would break under `set -e`). --git-common-dir points at the main .git even from
|
|
14
|
+
# a worktree; fall back to the literal path only outside a git repo.
|
|
15
|
+
if COMMON_GIT_DIR="$(git -C "$PROJECT_ROOT" rev-parse --git-common-dir 2>/dev/null)"; then
|
|
16
|
+
case "$COMMON_GIT_DIR" in /*) : ;; *) COMMON_GIT_DIR="$PROJECT_ROOT/$COMMON_GIT_DIR" ;; esac
|
|
17
|
+
HOOKS_DIR="$COMMON_GIT_DIR/hooks"
|
|
18
|
+
else
|
|
19
|
+
HOOKS_DIR="$PROJECT_ROOT/.git/hooks"
|
|
20
|
+
fi
|
|
12
21
|
|
|
13
22
|
mkdir -p "$HOOKS_DIR"
|
|
14
23
|
|
|
15
24
|
cat > "$HOOKS_DIR/pre-push" << 'EOF'
|
|
16
25
|
#!/usr/bin/env bash
|
|
17
|
-
# Pre-push hook:
|
|
18
|
-
#
|
|
26
|
+
# Pre-push hook: mirror the CI "Sync + Generate Tools Consistency" gate LOCALLY
|
|
27
|
+
# so derived-output drift is caught BEFORE push, not in remote CI.
|
|
19
28
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
29
|
+
# History: the remote "Consistency" check failed in a large share of PRs because
|
|
30
|
+
# the old hook only verified dist sync + the COMMAND count — it missed
|
|
31
|
+
# guideline/skill/rule count drift and the generate-tools / router / corpus
|
|
32
|
+
# outputs (e.g. a new guideline bumping the count in README + docs/architecture).
|
|
33
|
+
# `task consistency` is the EXACT CI mirror (sync-check + sync-check-hashes +
|
|
34
|
+
# sync + generate-tools + compile-router + compile-corpus + `git diff --quiet`),
|
|
35
|
+
# so any derived-output drift blocks the push here. Runtime ~15-40s (it
|
|
36
|
+
# regenerates the tool trees) — cheaper than a red CI run and a fixup re-push.
|
|
24
37
|
|
|
25
38
|
fail=0
|
|
26
39
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
echo "
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
if ! command -v task >/dev/null 2>&1; then
|
|
41
|
+
echo "⚠️ 'task' (go-task) not found — cannot run the consistency gate locally."
|
|
42
|
+
echo " Install it (https://taskfile.dev) so pre-push can mirror the CI check;"
|
|
43
|
+
echo " skipping the consistency check for this push."
|
|
44
|
+
else
|
|
45
|
+
echo "🔍 Consistency — mirroring the CI 'Sync + Generate Tools Consistency' gate..."
|
|
46
|
+
if ! task consistency; then
|
|
47
|
+
echo "❌ Derived outputs drifted (counts / dist / generated tool trees / router / corpus)."
|
|
48
|
+
echo " 'task consistency' just regenerated them into your working tree —"
|
|
49
|
+
echo " review + 'git add' the changes (or run 'task consistency-fix'), commit, then re-push."
|
|
50
|
+
fail=1
|
|
51
|
+
fi
|
|
37
52
|
fi
|
|
38
53
|
|
|
39
54
|
echo "🔍 Checking for leftover conflict markers / unmerged paths..."
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse PR-URL reminder — reply-shape backstop for `direct-answers`.
|
|
4
|
+
*
|
|
5
|
+
* The `direct-answers` rule (reply-close) requires: a PR created THIS turn puts
|
|
6
|
+
* its raw URL as the **literal last line** of the reply. That is a behavioural
|
|
7
|
+
* rule with no deterministic backstop, so it can be silently missed — the URL
|
|
8
|
+
* ends up inline mid-reply instead of last. This hook fires right after a
|
|
9
|
+
* PR-creating tool call, extracts the created PR URL from the tool output, and
|
|
10
|
+
* WARNS in context (exit 2) so the reminder lands exactly when the PR is born,
|
|
11
|
+
* before the reply closes.
|
|
12
|
+
*
|
|
13
|
+
* Fires ONLY on an actual PR *create*:
|
|
14
|
+
* - a shell command matching `gh pr create`, OR
|
|
15
|
+
* - a GitHub tool whose name means "create a pull request"
|
|
16
|
+
* (e.g. create_pull_request / createPullRequest / pulls.create),
|
|
17
|
+
* AND only when the tool output actually contains a `…/pull/<n>` URL — so a
|
|
18
|
+
* failed / dry-run create (no URL) does not nag, and `gh pr view/checks/list/
|
|
19
|
+
* comment` (which also print PR URLs) never fire it.
|
|
20
|
+
*
|
|
21
|
+
* Warn only — never blocks (fail_closed: false). Unconditional: it reinforces
|
|
22
|
+
* an always-on rule and only fires on the rare PR-create event, so it carries
|
|
23
|
+
* no settings gate (mirrors verify-before-complete / roadmap-progress).
|
|
24
|
+
*
|
|
25
|
+
* Exit codes (dispatcher contract): 0 allow · 2 warn (+ `{decision,reason}`
|
|
26
|
+
* JSON on stdout).
|
|
27
|
+
*/
|
|
28
|
+
import fs from "node:fs";
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
31
|
+
|
|
32
|
+
const EXIT_ALLOW = 0;
|
|
33
|
+
const EXIT_WARN = 2;
|
|
34
|
+
|
|
35
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
36
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
37
|
+
|
|
38
|
+
// Tool names across platforms whose command field carries a shell command
|
|
39
|
+
// (kept in lock-step with verify_before_complete_hook.COMMAND_TOOLS).
|
|
40
|
+
const COMMAND_TOOLS: ReadonlySet<string> = new Set([
|
|
41
|
+
"launch-process",
|
|
42
|
+
"launch_process",
|
|
43
|
+
"Bash",
|
|
44
|
+
"BashTool",
|
|
45
|
+
"run-process",
|
|
46
|
+
"runProcess",
|
|
47
|
+
"shell",
|
|
48
|
+
"execute_shell",
|
|
49
|
+
"RunShellCommand",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// A shell command that CREATES a pull request. Anchored on a shell separator so
|
|
53
|
+
// it matches inside a chained command (`git push && gh pr create …`) but not a
|
|
54
|
+
// substring of another word. `gh pr view/checks/list/comment` do not match.
|
|
55
|
+
const _GH_PR_CREATE = /(?:^|[\s;&|`(])gh\s+pr\s+create\b/i;
|
|
56
|
+
|
|
57
|
+
// A non-shell tool whose NAME means "create a pull request" (github-api / MCP).
|
|
58
|
+
const _CREATE_PR_TOOL = /(?:create[_-]?pull_?request|createpullrequest|pulls[._-]create|create_pr\b)/i;
|
|
59
|
+
|
|
60
|
+
// A GitHub pull-request URL as printed by `gh pr create` / returned as html_url.
|
|
61
|
+
const _PR_URL = /https?:\/\/github\.com\/[^\s"'`<>]+\/pull\/\d+/i;
|
|
62
|
+
|
|
63
|
+
function _isObject(v: unknown): v is JsonObject {
|
|
64
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Return [tool_name, command_text] from a tool-event payload. */
|
|
68
|
+
function _extractCommand(payload: JsonObject): [string | null, string | null] {
|
|
69
|
+
const toolRaw = payload["tool_name"] ?? payload["toolName"] ?? payload["tool"];
|
|
70
|
+
const tool = typeof toolRaw === "string" ? toolRaw : null;
|
|
71
|
+
if (!(tool !== null && COMMAND_TOOLS.has(tool))) {
|
|
72
|
+
return [tool, null];
|
|
73
|
+
}
|
|
74
|
+
const ti = payload["tool_input"];
|
|
75
|
+
if (_isObject(ti)) {
|
|
76
|
+
for (const key of ["command", "cmd", "shell_command"]) {
|
|
77
|
+
const v = ti[key];
|
|
78
|
+
if (typeof v === "string" && v) {
|
|
79
|
+
return [tool, v];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const key of ["command", "cmd"]) {
|
|
84
|
+
const v = payload[key];
|
|
85
|
+
if (typeof v === "string" && v) {
|
|
86
|
+
return [tool, v];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return [tool, null];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Best-effort extraction of the tool-output text from the envelope. */
|
|
93
|
+
function _toolOutput(envelope: JsonObject): string {
|
|
94
|
+
for (const key of ["tool_response", "tool_result", "toolResponse", "output", "result", "stdout"]) {
|
|
95
|
+
const v = envelope[key];
|
|
96
|
+
if (typeof v === "string") {
|
|
97
|
+
return v;
|
|
98
|
+
}
|
|
99
|
+
if (_isObject(v) || Array.isArray(v)) {
|
|
100
|
+
return JSON.stringify(v);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return JSON.stringify(envelope);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** True when this tool event created a pull request. */
|
|
107
|
+
function _isPrCreate(tool: string | null, command: string | null): boolean {
|
|
108
|
+
if (command !== null && _GH_PR_CREATE.test(command)) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return tool !== null && _CREATE_PR_TOOL.test(tool);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function main(): number {
|
|
115
|
+
let envelope: JsonValue;
|
|
116
|
+
try {
|
|
117
|
+
const raw = _readStdin();
|
|
118
|
+
envelope = raw.trim() ? (JSON.parse(raw) as JsonValue) : {};
|
|
119
|
+
} catch {
|
|
120
|
+
return EXIT_ALLOW; // never block on a malformed envelope
|
|
121
|
+
}
|
|
122
|
+
if (!_isObject(envelope)) {
|
|
123
|
+
return EXIT_ALLOW;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const [tool, command] = _extractCommand(envelope);
|
|
127
|
+
if (!_isPrCreate(tool, command)) {
|
|
128
|
+
return EXIT_ALLOW;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const match = _PR_URL.exec(_toolOutput(envelope));
|
|
132
|
+
if (match === null) {
|
|
133
|
+
return EXIT_ALLOW; // create attempted but no URL produced (failed / dry-run) — do not nag
|
|
134
|
+
}
|
|
135
|
+
const url = match[0];
|
|
136
|
+
|
|
137
|
+
// `reason` (≤ 200 chars) goes to stderr; `additional_context` is what
|
|
138
|
+
// surfaces back to the model on platforms that support it — so the
|
|
139
|
+
// actionable instruction lives there (a stderr-only reason would not
|
|
140
|
+
// change the reply that this hook exists to shape).
|
|
141
|
+
const reason = `PR created this turn (${url}) — end your reply with this raw URL as the literal last line (direct-answers/reply-close).`;
|
|
142
|
+
const additional_context =
|
|
143
|
+
`You created a pull request this turn: ${url}\n\n` +
|
|
144
|
+
`Per the direct-answers rule (reply-close): a PR created this turn must appear ` +
|
|
145
|
+
`as the raw URL on the LITERAL LAST LINE of your reply — not inline mid-message. ` +
|
|
146
|
+
`Before ending the turn, make your final line exactly:\n${url}\n` +
|
|
147
|
+
`If it already is, no action needed.`;
|
|
148
|
+
process.stdout.write(`${JSON.stringify({ decision: "warn", reason, additional_context })}\n`);
|
|
149
|
+
return EXIT_WARN;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function _readStdin(): string {
|
|
153
|
+
return fs.readFileSync(0, "utf-8");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function _isCliEntry(): boolean {
|
|
157
|
+
if (process.argv[1] === undefined) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
161
|
+
if (import.meta.url === argvUrl) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
// Symlinked invocation (installed projection, or macOS /var → /private/var
|
|
165
|
+
// temp dirs) makes the raw URLs differ; compare realpaths so the guard fires.
|
|
166
|
+
try {
|
|
167
|
+
const here = fs.realpathSync(fileURLToPath(import.meta.url));
|
|
168
|
+
const argv = fs.realpathSync(path.resolve(process.argv[1]));
|
|
169
|
+
return here === argv;
|
|
170
|
+
} catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (_isCliEntry()) {
|
|
176
|
+
process.exit(main());
|
|
177
|
+
}
|