@event4u/agent-config 9.0.0 → 9.1.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 +67 -20
- package/MIGRATION.md +14 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/explain-run.md +88 -0
- package/dist/agent-src/commands/pr/create/description-only.md +138 -8
- package/dist/agent-src/commands/pr/create.md +16 -5
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +146 -0
- package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +20 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +11 -0
- package/dist/agent-src/skills/token-optimizer/SKILL.md +10 -4
- package/dist/agent-src/templates/agent-settings.md +28 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +68 -14
- 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 +8 -6
- package/dist/discovery/trust-report.md +8 -8
- package/dist/discovery/workspaces.json +6 -4
- package/dist/install/install.mjs +38 -31
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/scripts/_lib/agent_settings.js +4 -1
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +2 -2
- package/docs/MIGRATION.md +20 -0
- package/docs/architecture.md +3 -3
- package/docs/command-flows.md +1 -1
- package/docs/contracts/agents-layout.md +1 -1
- package/docs/contracts/consumer-bridge.md +55 -63
- package/docs/contracts/install-layout.md +1 -1
- package/docs/contracts/migrate-command.md +1 -1
- package/docs/customization.md +22 -1
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +34 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/api-cost-levers.md +126 -0
- package/docs/installation.md +2 -1
- package/docs/maintainers/dev-mode.md +3 -2
- package/docs/proof.md +5 -5
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/src/config/agent-settings.template.yml +39 -0
- package/src/config/gitignore-block.txt +16 -6
- package/src/scripts/_cli/cmd_conformance.ts +2 -2
- package/src/scripts/_cli/cmd_doctor.ts +162 -13
- package/src/scripts/_cli/cmd_refresh.ts +35 -118
- package/src/scripts/_lib/agent_settings.ts +4 -1
- package/src/scripts/ai_council/clients.ts +68 -6
- package/src/scripts/ai_council/pricing.ts +62 -0
- package/src/scripts/council_cli.ts +19 -3
- package/src/scripts/install.ts +44 -45
- package/src/scripts/lint_agents_layout.ts +10 -4
- package/src/scripts/lint_breaking_changes_index.ts +303 -0
- package/src/scripts/mcp_server/tools.ts +1 -3
- package/src/scripts/schemas/agent-settings.schema.json +17 -0
- package/src/scripts/self_review_gate.test.ts +11 -0
- package/src/scripts/self_review_gate.ts +7 -1
- package/src/scripts/skill_eval_coverage.ts +4 -1
- package/src/server/schemas/settings.ts +15 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# API Cost Levers
|
|
2
|
+
|
|
3
|
+
Reference material for a downstream AI-coding agent that wants to cut its own
|
|
4
|
+
Claude API dollar bill. Four platform-level billing levers, the exact economics
|
|
5
|
+
of each, and how they interact. Read on demand — not always-loaded.
|
|
6
|
+
|
|
7
|
+
Facts sourced from the in-repo `claude-api` plugin skill plus platform research
|
|
8
|
+
(2026-07-14). Do not invent an API shape beyond what is stated here.
|
|
9
|
+
|
|
10
|
+
## The four levers at a glance
|
|
11
|
+
|
|
12
|
+
| Lever | Cuts | Best for | Watch out for |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| Prompt caching | Repeated input tokens | Stable prefix reused across calls (system prompt, tools, repo map) | Any byte change in the prefix invalidates the rest |
|
|
15
|
+
| Batch API | 50% input + output | Non-interactive bulk work (classification, summarization, evals) | Async, up to 24h, results unordered |
|
|
16
|
+
| Model tiering | Per-token price | Routine work on a cheaper model | Switching model is a cache miss |
|
|
17
|
+
| Effort / output sizing | Thinking + output tokens | Routine turns that don't need deep reasoning | Oversized `max_tokens` risks runaway output |
|
|
18
|
+
|
|
19
|
+
## 1. Prompt caching
|
|
20
|
+
|
|
21
|
+
Put the large, byte-**stable** context first — system prompt, tool definitions,
|
|
22
|
+
a corpus or repo map, few-shot examples — behind a
|
|
23
|
+
`cache_control: {type: 'ephemeral'}` breakpoint. Keep volatile content (the
|
|
24
|
+
varying question, timestamps, per-request IDs) **after** the last breakpoint.
|
|
25
|
+
The cache is a **prefix match**: any byte change in the cached prefix
|
|
26
|
+
invalidates everything after it. Render order is tools then system then
|
|
27
|
+
messages.
|
|
28
|
+
|
|
29
|
+
Caching is GA — **no beta header needed**.
|
|
30
|
+
|
|
31
|
+
Economics:
|
|
32
|
+
|
|
33
|
+
- Cache reads cost approximately 0.1x the base input price.
|
|
34
|
+
- Cache writes cost 1.25x (5-minute TTL) or 2x (1-hour TTL).
|
|
35
|
+
- Break-even is after **one** read on the 5-minute TTL.
|
|
36
|
+
|
|
37
|
+
Constraints:
|
|
38
|
+
|
|
39
|
+
- The minimum cacheable prefix is model-dependent (approximately 1024–4096
|
|
40
|
+
tokens). Shorter prefixes silently do not cache.
|
|
41
|
+
- The cache is workspace-isolated (since February 2026).
|
|
42
|
+
|
|
43
|
+
Verify a hit with `usage.cache_read_input_tokens > 0`.
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"system": [
|
|
48
|
+
{ "type": "text", "text": "<large stable system prompt>",
|
|
49
|
+
"cache_control": { "type": "ephemeral" } }
|
|
50
|
+
],
|
|
51
|
+
"messages": [
|
|
52
|
+
{ "role": "user", "content": "<the varying question>" }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 2. Batch API
|
|
58
|
+
|
|
59
|
+
For **non-interactive, non-latency-sensitive bulk work** — bulk classification,
|
|
60
|
+
mass file summarization, eval runs. Not chatbots or interactive assistants.
|
|
61
|
+
|
|
62
|
+
- **50% off both input and output tokens.**
|
|
63
|
+
- Async: results within 24h (usually much less), keyed by `custom_id`, arriving
|
|
64
|
+
**unordered** — key results by `custom_id`, never by position.
|
|
65
|
+
- **Stacks with prompt caching**: a cached-batch input token can reach
|
|
66
|
+
approximately 0.05x the standard input price.
|
|
67
|
+
|
|
68
|
+
## 3. Model tiering
|
|
69
|
+
|
|
70
|
+
Treat models like a team — a cheap model for mechanical or routine work, the
|
|
71
|
+
expensive model only for hard judgment. Approximate input / output price per 1M
|
|
72
|
+
tokens:
|
|
73
|
+
|
|
74
|
+
| Model | Input | Output |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| Haiku | $1 | $5 |
|
|
77
|
+
| Sonnet | $3 | $15 |
|
|
78
|
+
| Opus | $5 | $25 |
|
|
79
|
+
| Fable | $10 | $50 |
|
|
80
|
+
|
|
81
|
+
Opus input is approximately 5x Haiku. Tier by task **difficulty**, not blindly:
|
|
82
|
+
a cheaper model that gives wrong answers on judgment tasks costs correction
|
|
83
|
+
cycles, which erases the saving.
|
|
84
|
+
|
|
85
|
+
**Interaction with caching.** The prompt cache is keyed by `(model, prefix)`, so
|
|
86
|
+
switching a call to a cheaper model is a cache **miss**. Weigh the downgrade
|
|
87
|
+
saving against the lost cache-read saving before routing a cached call to a
|
|
88
|
+
different model.
|
|
89
|
+
|
|
90
|
+
## 4. Effort and output right-sizing
|
|
91
|
+
|
|
92
|
+
- `output_config.effort` (low through max) controls thinking and output depth,
|
|
93
|
+
and therefore spend. Dial effort **down** on routine turns; reserve high / max
|
|
94
|
+
for hard reasoning.
|
|
95
|
+
- Right-size `max_tokens` — do not leave a huge ceiling. An unrealistically
|
|
96
|
+
large `max_tokens` does not cost tokens by itself, but it risks runaway output
|
|
97
|
+
and defeats pre-flight budgeting.
|
|
98
|
+
|
|
99
|
+
## When to reach for which
|
|
100
|
+
|
|
101
|
+
| Situation | Lever |
|
|
102
|
+
|---|---|
|
|
103
|
+
| Same large context reused across many calls | Prompt caching |
|
|
104
|
+
| Thousands of independent, non-urgent jobs | Batch API (+ caching) |
|
|
105
|
+
| Turn is mechanical / narrow / well-specified | Cheaper model tier |
|
|
106
|
+
| Turn is routine and does not need deep thinking | Lower `effort` |
|
|
107
|
+
| Output could balloon unnecessarily | Cap `max_tokens` |
|
|
108
|
+
| Interactive latency matters | Caching + tiering; not Batch |
|
|
109
|
+
|
|
110
|
+
## Claude Code note
|
|
111
|
+
|
|
112
|
+
In Claude Code, prompt caching is automatic — resending the same context prefix
|
|
113
|
+
is cheap, so keeping a stable prefix pays off without any explicit
|
|
114
|
+
`cache_control`. The session-level hygiene levers are `/clear` and `/compact` to
|
|
115
|
+
shed accumulated context, plus subagent delegation for verbose output so it
|
|
116
|
+
never lands in the main context window.
|
|
117
|
+
|
|
118
|
+
## See also
|
|
119
|
+
|
|
120
|
+
- [`model-recommendation.md`](model-recommendation.md) — tier routing (the
|
|
121
|
+
capability band behind lever 3).
|
|
122
|
+
- [`size-and-scope.md`](size-and-scope.md) — token discipline for the artifacts
|
|
123
|
+
that become the cached prefix.
|
|
124
|
+
- The Claude Code `claude-api` plugin skill (external, not vendored here) — the
|
|
125
|
+
authoritative, versioned API reference these figures derive from; re-check it
|
|
126
|
+
when Anthropic pricing or the caching/batch surface changes.
|
package/docs/installation.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
**Principle:** Global is the only consumer scope. `init` writes to
|
|
4
4
|
user-scope paths (`~/.event4u/agent-config/`, `~/.claude/`,
|
|
5
5
|
`~/.cursor/`, …); the project tree only gets `agents/overrides/`
|
|
6
|
-
|
|
6
|
+
(the bridge marker was retired — ADR-020 amendment 2026-07-13; the
|
|
7
|
+
global root resolves from `~/.event4u/agent-config`). Opt-in `export` if a team wants
|
|
7
8
|
a tool's config committed to a repo. No Task, no Make, no build
|
|
8
9
|
tools required.
|
|
9
10
|
|
|
@@ -23,8 +23,9 @@ With the flag set, the installer:
|
|
|
23
23
|
1. Allows project-scope writes back into the repo tree (so a
|
|
24
24
|
maintainer can run `task dev:install-global` and iterate on the
|
|
25
25
|
working copy).
|
|
26
|
-
2. **Skips**
|
|
27
|
-
|
|
26
|
+
2. **Skips** consumer-surface writes into the package repo. (The
|
|
27
|
+
`agents/.event4u-bridge.yml` marker is retired — ADR-020 amendment
|
|
28
|
+
2026-07-13 — and no longer written at all.) The repo is the
|
|
28
29
|
source, not a consumer of itself.
|
|
29
30
|
3. Treats `~/.event4u/agent-config/` as a peer install — touches are
|
|
30
31
|
limited to the working-copy projection.
|
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
|
+
| 184 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
|
+
| 275 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 275** 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 240) is not. We publish that gap rather than imply
|
|
84
|
+
"275 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.1.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,
|
|
@@ -252,7 +252,10 @@ const _AGENTS_DIR_MARKERS: readonly string[] = [
|
|
|
252
252
|
'roadmaps',
|
|
253
253
|
'settings/.ai-council.yml',
|
|
254
254
|
'roadmaps-progress.md',
|
|
255
|
-
|
|
255
|
+
// `overrides/` is the guaranteed minimal-consumer surface (ADR-020).
|
|
256
|
+
// Replaced the retired `.event4u-bridge.yml` marker (ADR-020 amendment
|
|
257
|
+
// 2026-07-13) so a bare consumer `agents/` dir stays anchorable.
|
|
258
|
+
'overrides',
|
|
256
259
|
];
|
|
257
260
|
|
|
258
261
|
/**
|
|
@@ -761,6 +761,45 @@ commands:
|
|
|
761
761
|
create_pr:
|
|
762
762
|
preview_description: false
|
|
763
763
|
|
|
764
|
+
# Verbosity tier for the generated PR description body.
|
|
765
|
+
# min (default) = title + 2-3 sentence what/why/impact + linked ticket.
|
|
766
|
+
# Token-frugal. Critical info (breaking changes,
|
|
767
|
+
# migrations, security, rollback) is ALWAYS included
|
|
768
|
+
# regardless of tier — the tier governs explanatory
|
|
769
|
+
# depth, never whether a critical callout appears.
|
|
770
|
+
# med = min + grouped logical changes + tests note.
|
|
771
|
+
# max = med + how-to-test + edge cases / trade-offs +
|
|
772
|
+
# reviewer guidance (the richest, ~legacy output).
|
|
773
|
+
detail_level: min
|
|
774
|
+
|
|
775
|
+
# JSON request/response examples for API-endpoint changes.
|
|
776
|
+
# true (default) = include a fenced example ONLY when it can be
|
|
777
|
+
# grounded in a real source (response DTO/resource,
|
|
778
|
+
# OpenAPI/schema, test fixture, or an actual probe).
|
|
779
|
+
# No grounded source → a one-line pointer, never an
|
|
780
|
+
# invented example (a hallucinated contract misleads
|
|
781
|
+
# reviewers, so it is worse than none).
|
|
782
|
+
# false = never add API examples.
|
|
783
|
+
api_examples: true
|
|
784
|
+
|
|
785
|
+
# Screenshots for frontend changes. Default false (off). Capability-gated:
|
|
786
|
+
# requires the host to have browser/preview tooling (Playwright / a
|
|
787
|
+
# dev-server URL). When true + a frontend change + no such tooling, the
|
|
788
|
+
# agent emits a one-line note and leaves the template placeholder — it
|
|
789
|
+
# never fails or blocks PR creation, and never claims an embed that did
|
|
790
|
+
# not happen. Before/after + changed-region highlighting is best-effort.
|
|
791
|
+
# false (default) = never attempt screenshots.
|
|
792
|
+
# true = attempt when capable (see command contract).
|
|
793
|
+
screenshots: false
|
|
794
|
+
|
|
795
|
+
# Optional glob lists that make frontend / API-endpoint detection
|
|
796
|
+
# explicit instead of heuristic. Empty (default) = the command uses a
|
|
797
|
+
# light path/extension heuristic and fails open (no false enrichment
|
|
798
|
+
# when the surface is ambiguous). Populate to pin your project's paths,
|
|
799
|
+
# e.g. ui_paths: ["resources/views/**", "src/pages/**"].
|
|
800
|
+
ui_paths: []
|
|
801
|
+
api_paths: []
|
|
802
|
+
|
|
764
803
|
# --- Memory ---
|
|
765
804
|
#
|
|
766
805
|
# Engineering memory consolidation behaviour. Memory is entirely
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
# Agent config — tool-scope leftovers (ADR-020 § global-only consumer
|
|
2
2
|
# install). After the Phase-5 migration the entire `.augment/`, `.claude/`,
|
|
3
3
|
# `.cursor/` trees live under `~/.event4u/agent-config/`; consumers carry
|
|
4
|
-
# only `agents/overrides
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# directories stay tracked (not listed here). Pre-Phase-5 entries kept
|
|
8
|
-
# as a defence-in-depth ignore list for legacy installs that have not
|
|
9
|
-
# migrated yet — Phase 5 unstages them on first migrate run.
|
|
4
|
+
# only `agents/overrides/`. Pre-Phase-5 entries kept as a defence-in-depth
|
|
5
|
+
# ignore list for legacy installs that have not migrated yet — Phase 5
|
|
6
|
+
# unstages them on first migrate run.
|
|
10
7
|
.augment/skills/
|
|
11
8
|
.augment/commands/
|
|
12
9
|
.augment/guidelines/
|
|
@@ -21,6 +18,19 @@
|
|
|
21
18
|
.claude/CLAUDE.md
|
|
22
19
|
.cursor/rules/
|
|
23
20
|
|
|
21
|
+
# Agent config — consumer bridge marker + per-tool project anchors.
|
|
22
|
+
# The bridge marker was retired (ADR-020 amendment 2026-07-13): it carried
|
|
23
|
+
# no project-specific data (global_root resolves from `~/.event4u/agent-config`)
|
|
24
|
+
# yet was committed and rewritten by every developer's install. The global
|
|
25
|
+
# root is now the well-known path. The Windsurf / Cline / Gemini-CLI anchors
|
|
26
|
+
# remain (those tools cannot read user scope) but are gitignored — each
|
|
27
|
+
# developer regenerates them on install; they are never shared. The install
|
|
28
|
+
# deletes any legacy committed marker; this entry keeps a stale one ignored.
|
|
29
|
+
/agents/.event4u-bridge.yml
|
|
30
|
+
.windsurf/agent-config.bridge.yml
|
|
31
|
+
.clinerules/agent-config.bridge.yml
|
|
32
|
+
.gemini/agent-config.bridge.yml
|
|
33
|
+
|
|
24
34
|
# Agent config — Phase 5 migration snapshot (ADR-020). Written by
|
|
25
35
|
# `agent-config migrate-to-global` as a copy-then-move safety net so a
|
|
26
36
|
# migration can be rolled back. Local-only — never commit.
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
_run_checks_no_manifest,
|
|
48
48
|
_scan_foreign,
|
|
49
49
|
ArgparseExit,
|
|
50
|
-
|
|
50
|
+
_is_global_only_consumer,
|
|
51
51
|
STATUS_SYMBOLS,
|
|
52
52
|
} from './cmd_doctor.js';
|
|
53
53
|
|
|
@@ -630,7 +630,7 @@ function main(argv: string[] | null = null): number {
|
|
|
630
630
|
let doctorChecks: Dict[];
|
|
631
631
|
let drift: Record<string, Dict[]> = { missing: [], modified: [], foreign: [], tag_drift: [] };
|
|
632
632
|
if (manifest === null) {
|
|
633
|
-
const bridge_present =
|
|
633
|
+
const bridge_present = _is_global_only_consumer(project_root);
|
|
634
634
|
doctorChecks = _run_checks_no_manifest(project_root, bridge_present, null);
|
|
635
635
|
} else {
|
|
636
636
|
const [records, known] = _collect_manifest_entries(project_root, manifest);
|
|
@@ -100,6 +100,12 @@ import {
|
|
|
100
100
|
import * as ai_council_clients from '../ai_council/clients.js';
|
|
101
101
|
import * as ai_council_config from '../ai_council/config.js';
|
|
102
102
|
import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
|
|
103
|
+
import {
|
|
104
|
+
LEGACY_ALL,
|
|
105
|
+
excludedRuleBasenames,
|
|
106
|
+
ruleScopeFromSettings,
|
|
107
|
+
type RuleScope,
|
|
108
|
+
} from '../../install/rule_scope.js';
|
|
103
109
|
|
|
104
110
|
type Dict = Record<string, unknown>;
|
|
105
111
|
|
|
@@ -738,6 +744,7 @@ const CHECK_IDS = [
|
|
|
738
744
|
'surface-state',
|
|
739
745
|
'hook-wiring',
|
|
740
746
|
'stale-orphans',
|
|
747
|
+
'rule-scope-drift',
|
|
741
748
|
'manifest-integrity',
|
|
742
749
|
'lockfile-freshness',
|
|
743
750
|
'bridge-drift',
|
|
@@ -764,6 +771,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
|
|
|
764
771
|
'surface-state',
|
|
765
772
|
'hook-wiring',
|
|
766
773
|
'stale-orphans',
|
|
774
|
+
'rule-scope-drift',
|
|
767
775
|
'mcp-mode',
|
|
768
776
|
'mcp-beta-readiness',
|
|
769
777
|
'offline-readiness',
|
|
@@ -783,8 +791,20 @@ const MANIFEST_REQUIRED_CHECK_IDS: ReadonlySet<string> = new Set([
|
|
|
783
791
|
'unsupported-combos',
|
|
784
792
|
]);
|
|
785
793
|
|
|
786
|
-
/**
|
|
787
|
-
|
|
794
|
+
/**
|
|
795
|
+
* Signals a set-up global-only consumer. The `.event4u-bridge.yml` marker was
|
|
796
|
+
* retired (ADR-020 amendment 2026-07-13); the durable project-side signal is
|
|
797
|
+
* the install-mode marker (written on every install) or the `agents/overrides/`
|
|
798
|
+
* scaffold. Either is sufficient.
|
|
799
|
+
*/
|
|
800
|
+
const INSTALL_MODE_MARKER_RELATIVE = 'agents/.agent-state/install-mode.txt';
|
|
801
|
+
|
|
802
|
+
function _is_global_only_consumer(project_root: string): boolean {
|
|
803
|
+
return (
|
|
804
|
+
isFile(path.join(project_root, INSTALL_MODE_MARKER_RELATIVE)) ||
|
|
805
|
+
isDir(path.join(project_root, 'agents', 'overrides'))
|
|
806
|
+
);
|
|
807
|
+
}
|
|
788
808
|
|
|
789
809
|
/** Repair targets that `--repair <id>` accepts. */
|
|
790
810
|
const REPAIR_IDS = ['wizard-state'] as const;
|
|
@@ -1091,15 +1111,16 @@ function _check_global_binary(project_root: string): Dict {
|
|
|
1091
1111
|
remedy: 'agent-config upgrade (refresh the global install + plugin)',
|
|
1092
1112
|
};
|
|
1093
1113
|
}
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1114
|
+
const consumer_note = _is_global_only_consumer(project_root)
|
|
1115
|
+
? ''
|
|
1116
|
+
: ' · no project install marker';
|
|
1096
1117
|
return {
|
|
1097
1118
|
id: 'global-binary',
|
|
1098
1119
|
status: 'ok',
|
|
1099
|
-
message: `on PATH (${binary}); version ${binary_v || 'unknown'}${
|
|
1100
|
-
remedy: !
|
|
1120
|
+
message: `on PATH (${binary}); version ${binary_v || 'unknown'}${consumer_note}`,
|
|
1121
|
+
remedy: !consumer_note
|
|
1101
1122
|
? ''
|
|
1102
|
-
: 'agent-config refresh --project (scaffold
|
|
1123
|
+
: 'agent-config refresh --project (scaffold agents/overrides/)',
|
|
1103
1124
|
};
|
|
1104
1125
|
}
|
|
1105
1126
|
|
|
@@ -1590,6 +1611,130 @@ function _check_stale_orphans(): Dict {
|
|
|
1590
1611
|
};
|
|
1591
1612
|
}
|
|
1592
1613
|
|
|
1614
|
+
/**
|
|
1615
|
+
* Per-file rule projection trees a consumer install writes (basenames match
|
|
1616
|
+
* the shipped `dist/agent-src/rules` tree). The first one that exists and
|
|
1617
|
+
* holds ≥ 1 `.md` rule is the surface we diff against the consumer's scope.
|
|
1618
|
+
*/
|
|
1619
|
+
const _RULE_PROJECTION_DIRS: readonly string[] = [
|
|
1620
|
+
'.augment/rules',
|
|
1621
|
+
'.windsurf/rules',
|
|
1622
|
+
'.cursor/rules',
|
|
1623
|
+
'.clinerules',
|
|
1624
|
+
];
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* Rule-scope drift (road-to-request-scoped-rule-load, 9.0 consumer flip).
|
|
1628
|
+
*
|
|
1629
|
+
* After the 9.0 default flip to consumer-scoped rule projection, a consumer
|
|
1630
|
+
* whose settings now exclude maintainer-only workspaces may still carry a
|
|
1631
|
+
* pre-flip FULL/GLOBAL rule projection on disk. This check diffs the scope
|
|
1632
|
+
* the consumer's settings imply against the rules actually projected and
|
|
1633
|
+
* reports the leftover set as an ACTIONABLE diff (not a bare boolean).
|
|
1634
|
+
*
|
|
1635
|
+
* The scope-derived expected set reuses the ONE install-time scoping surface
|
|
1636
|
+
* (`rule_scope.ts`), so doctor's diagnosis can never drift from the install
|
|
1637
|
+
* semantics. `excludedRuleBasenames` returns exactly the rules PRESENT in the
|
|
1638
|
+
* projected tree that the current scope says should NOT arrive.
|
|
1639
|
+
*
|
|
1640
|
+
* Degrades gracefully: `skipped` in the agent-config source repo (the in-repo
|
|
1641
|
+
* `.augment/rules` is the full generated projection, not a consumer surface)
|
|
1642
|
+
* and `skipped` when no projected rule tree is present to inspect.
|
|
1643
|
+
*/
|
|
1644
|
+
function _check_rule_scope_drift(project_root: string): Dict {
|
|
1645
|
+
if (_is_source_repo(project_root)) {
|
|
1646
|
+
return {
|
|
1647
|
+
id: 'rule-scope-drift',
|
|
1648
|
+
status: 'skipped',
|
|
1649
|
+
message: 'agent-config source repo — no consumer rule projection to inspect',
|
|
1650
|
+
remedy: '',
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Consumer projection scope (absent / empty settings → legacy-all: every
|
|
1655
|
+
// rule ships, so nothing is ever out of scope).
|
|
1656
|
+
let scope: RuleScope = LEGACY_ALL;
|
|
1657
|
+
const settings_file = project_settings_path(project_root);
|
|
1658
|
+
if (isFile(settings_file)) {
|
|
1659
|
+
const loaded = yamlSafeLoad(readText0(settings_file));
|
|
1660
|
+
const raw =
|
|
1661
|
+
typeof loaded === 'object' && loaded !== null && !Array.isArray(loaded)
|
|
1662
|
+
? (loaded as Dict)
|
|
1663
|
+
: {};
|
|
1664
|
+
scope = ruleScopeFromSettings(raw);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// First projected rule tree that exists and holds a `.md` rule.
|
|
1668
|
+
let rules_dir: string | null = null;
|
|
1669
|
+
let found = 0;
|
|
1670
|
+
for (const rel of _RULE_PROJECTION_DIRS) {
|
|
1671
|
+
const cand = path.join(project_root, rel);
|
|
1672
|
+
if (!isDir(cand)) {
|
|
1673
|
+
continue;
|
|
1674
|
+
}
|
|
1675
|
+
let count = 0;
|
|
1676
|
+
try {
|
|
1677
|
+
for (const name of fs.readdirSync(cand)) {
|
|
1678
|
+
if (name.endsWith('.md') && isFile(path.join(cand, name))) {
|
|
1679
|
+
count += 1;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
} catch {
|
|
1683
|
+
continue;
|
|
1684
|
+
}
|
|
1685
|
+
if (count > 0) {
|
|
1686
|
+
rules_dir = cand;
|
|
1687
|
+
found = count;
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (rules_dir === null) {
|
|
1692
|
+
return {
|
|
1693
|
+
id: 'rule-scope-drift',
|
|
1694
|
+
status: 'skipped',
|
|
1695
|
+
message: 'no projected rule tree found (.augment/rules, .windsurf/rules, …) — nothing to inspect',
|
|
1696
|
+
remedy: '',
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
let unexpected: string[];
|
|
1701
|
+
try {
|
|
1702
|
+
unexpected = excludedRuleBasenames(rules_dir, scope);
|
|
1703
|
+
} catch (exc) {
|
|
1704
|
+
const rel = relativeTo(rules_dir, project_root) ?? rules_dir;
|
|
1705
|
+
return {
|
|
1706
|
+
id: 'rule-scope-drift',
|
|
1707
|
+
status: 'fail',
|
|
1708
|
+
message: `cannot inspect ${rel}: ${osErrorStr(exc)}`,
|
|
1709
|
+
remedy: 'check read permissions on the projected rule tree',
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const rel_dir = relativeTo(rules_dir, project_root) ?? rules_dir;
|
|
1714
|
+
if (unexpected.length === 0) {
|
|
1715
|
+
return {
|
|
1716
|
+
id: 'rule-scope-drift',
|
|
1717
|
+
status: 'ok',
|
|
1718
|
+
message: `rule projection matches scope (${found} rules in ${rel_dir})`,
|
|
1719
|
+
remedy: '',
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
const expected = found - unexpected.length;
|
|
1723
|
+
const sample = unexpected.slice(0, 8);
|
|
1724
|
+
const more = unexpected.length - sample.length;
|
|
1725
|
+
const names = sample.join(', ') + (more > 0 ? `, +${more} more` : '');
|
|
1726
|
+
return {
|
|
1727
|
+
id: 'rule-scope-drift',
|
|
1728
|
+
status: 'warn',
|
|
1729
|
+
message:
|
|
1730
|
+
`rule-scope drift in ${rel_dir}: expected ${expected} rules, found ${found} — ` +
|
|
1731
|
+
`${unexpected.length} unexpected (out of scope): ${names}`,
|
|
1732
|
+
remedy:
|
|
1733
|
+
're-run `agent-config init --project` (or `agent-config global`) to re-project rules ' +
|
|
1734
|
+
'under the current scope — these are leftover from a pre-9.0 full/global projection',
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1593
1738
|
function _check_python_runtime(): Dict {
|
|
1594
1739
|
// Post-teardown: the package runtime is TypeScript-on-`tsx`. python3 is no
|
|
1595
1740
|
// longer a runtime dependency, so this check no longer probes for an
|
|
@@ -2525,6 +2670,7 @@ function _run_checks(
|
|
|
2525
2670
|
'surface-state': _check_surface_state,
|
|
2526
2671
|
'hook-wiring': _check_hook_wiring,
|
|
2527
2672
|
'stale-orphans': _check_stale_orphans,
|
|
2673
|
+
'rule-scope-drift': () => _check_rule_scope_drift(project_root),
|
|
2528
2674
|
'manifest-integrity': () => _check_manifest_integrity(manifest),
|
|
2529
2675
|
'lockfile-freshness': () => _check_lockfile_freshness(manifest),
|
|
2530
2676
|
'bridge-drift': () =>
|
|
@@ -2582,8 +2728,8 @@ function _check_bridge_drift_no_manifest(bridge_present: boolean): Dict {
|
|
|
2582
2728
|
id: 'bridge-drift',
|
|
2583
2729
|
status: 'skipped',
|
|
2584
2730
|
message:
|
|
2585
|
-
'no project lockfile and no
|
|
2586
|
-
'not applicable',
|
|
2731
|
+
'no project lockfile and no consumer install marker → drift ' +
|
|
2732
|
+
'check not applicable',
|
|
2587
2733
|
remedy:
|
|
2588
2734
|
'run `agent-config init` (project install) or ' +
|
|
2589
2735
|
'`agent-config refresh --project` (global-only consumer)',
|
|
@@ -2604,6 +2750,7 @@ function _run_checks_no_manifest(
|
|
|
2604
2750
|
'surface-state': _check_surface_state,
|
|
2605
2751
|
'hook-wiring': _check_hook_wiring,
|
|
2606
2752
|
'stale-orphans': _check_stale_orphans,
|
|
2753
|
+
'rule-scope-drift': () => _check_rule_scope_drift(project_root),
|
|
2607
2754
|
'manifest-integrity': () => _skipped_manifest_check('manifest-integrity'),
|
|
2608
2755
|
'lockfile-freshness': () => _skipped_manifest_check('lockfile-freshness'),
|
|
2609
2756
|
'bridge-drift': () => _check_bridge_drift_no_manifest(bridge_present),
|
|
@@ -2647,7 +2794,7 @@ function _run_no_manifest(
|
|
|
2647
2794
|
print(` 📍 project_root: ${project_root} (origin: ${origin})`);
|
|
2648
2795
|
if (bridge_present) {
|
|
2649
2796
|
print(
|
|
2650
|
-
' ℹ️ global-only consumer:
|
|
2797
|
+
' ℹ️ global-only consumer: install marker present, no ' +
|
|
2651
2798
|
'project lockfile (expected under ADR-020)',
|
|
2652
2799
|
);
|
|
2653
2800
|
print(
|
|
@@ -2655,7 +2802,7 @@ function _run_no_manifest(
|
|
|
2655
2802
|
'to project-local distributed tools',
|
|
2656
2803
|
);
|
|
2657
2804
|
} else {
|
|
2658
|
-
eprint(` ⚠️ no project lockfile and no
|
|
2805
|
+
eprint(` ⚠️ no project lockfile and no consumer install marker at ${project_root}`);
|
|
2659
2806
|
eprint(
|
|
2660
2807
|
' run `agent-config init` (project install) or ' +
|
|
2661
2808
|
'`agent-config refresh --project` (global-only consumer)',
|
|
@@ -2998,7 +3145,7 @@ function main(argv: string[] | null = null): number {
|
|
|
2998
3145
|
const manifest_pth = installed_tools.manifest_path(project_root);
|
|
2999
3146
|
const manifest = installed_tools.read_manifest(manifest_pth);
|
|
3000
3147
|
if (manifest === null) {
|
|
3001
|
-
const bridge_present =
|
|
3148
|
+
const bridge_present = _is_global_only_consumer(project_root);
|
|
3002
3149
|
return _run_no_manifest(opts, project_root, origin, bridge_present);
|
|
3003
3150
|
}
|
|
3004
3151
|
|
|
@@ -3099,6 +3246,7 @@ export {
|
|
|
3099
3246
|
_check_mcp_mode,
|
|
3100
3247
|
_check_offline_readiness,
|
|
3101
3248
|
_check_stale_orphans,
|
|
3249
|
+
_check_rule_scope_drift,
|
|
3102
3250
|
_check_python_runtime,
|
|
3103
3251
|
_check_humanizer_runtime,
|
|
3104
3252
|
_check_mcp_beta_readiness,
|
|
@@ -3122,7 +3270,8 @@ export {
|
|
|
3122
3270
|
CHECK_IDS,
|
|
3123
3271
|
GLOBAL_CHECK_IDS,
|
|
3124
3272
|
MANIFEST_REQUIRED_CHECK_IDS,
|
|
3125
|
-
|
|
3273
|
+
INSTALL_MODE_MARKER_RELATIVE,
|
|
3274
|
+
_is_global_only_consumer,
|
|
3126
3275
|
REPAIR_IDS,
|
|
3127
3276
|
MCP_BETA_GATES,
|
|
3128
3277
|
STATUS_SYMBOLS,
|