@event4u/agent-config 1.23.0 → 1.25.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/.agent-src/commands/analyze-reference-repo.md +3 -0
- package/.agent-src/commands/review-routing.md +7 -10
- package/.agent-src/commands/roadmap/process-full.md +41 -1
- package/.agent-src/contexts/authority/kernel-rule-edits.md +48 -0
- package/.agent-src/contexts/authority/scope-mechanics.md +15 -0
- package/.agent-src/contexts/contracts/consumer-agents-md-guide.md +127 -0
- package/.agent-src/contexts/contracts/emergency-triage-block.md +53 -0
- package/.agent-src/contexts/execution/roadmap-process-loop.md +29 -6
- package/.agent-src/rules/analysis-skill-routing.md +1 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/.agent-src/rules/artifact-engagement-recording.md +1 -1
- package/.agent-src/rules/augment-source-of-truth.md +1 -1
- package/.agent-src/rules/autonomous-execution.md +1 -1
- package/.agent-src/rules/caveman-speak.md +1 -1
- package/.agent-src/rules/cli-output-handling.md +1 -1
- package/.agent-src/rules/command-suggestion-policy.md +1 -1
- package/.agent-src/rules/docs-sync.md +1 -1
- package/.agent-src/rules/guidelines.md +1 -1
- package/.agent-src/rules/improve-before-implement.md +1 -1
- package/.agent-src/rules/invite-challenge.md +1 -1
- package/.agent-src/rules/minimal-safe-diff.md +1 -1
- package/.agent-src/rules/model-recommendation.md +1 -1
- package/.agent-src/rules/no-attribution-footers.md +1 -1
- package/.agent-src/rules/no-roadmap-references.md +56 -20
- package/.agent-src/rules/onboarding-gate.md +1 -1
- package/.agent-src/rules/package-ci-checks.md +1 -1
- package/.agent-src/rules/reviewer-awareness.md +9 -2
- package/.agent-src/rules/roadmap-progress-sync.md +37 -3
- package/.agent-src/rules/scope-control.md +6 -0
- package/.agent-src/rules/security-sensitive-stop.md +1 -1
- package/.agent-src/rules/size-enforcement.md +1 -1
- package/.agent-src/rules/token-optimizer-maintenance.md +1 -1
- package/.agent-src/rules/ui-audit-gate.md +1 -1
- package/.agent-src/skills/adr-create/SKILL.md +2 -1
- package/.agent-src/skills/agents-md-thin-root/SKILL.md +125 -0
- package/.agent-src/skills/ai-council/SKILL.md +9 -7
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +9 -0
- package/.agent-src/skills/markitdown/SKILL.md +239 -0
- package/.agent-src/skills/review-routing/SKILL.md +3 -4
- package/.agent-src/skills/universal-project-analysis/SKILL.md +8 -0
- package/.agent-src/templates/AGENTS.md +18 -148
- package/.agent-src/templates/copilot-instructions.md +41 -17
- package/.agent-src/templates/github-workflows/pr-risk-review.yml +1 -1
- package/.agent-src/templates/scripts/pr_review_routing.py +1 -1
- package/.claude-plugin/marketplace.json +7 -5
- package/AGENTS.md +18 -205
- package/CHANGELOG.md +70 -0
- package/README.md +2 -2
- package/docs/architecture.md +13 -7
- package/docs/catalog.md +45 -29
- package/docs/contracts/agents-md-tech-stack.md +74 -0
- package/docs/contracts/linear-ai-rules-inclusion.md +1 -1
- package/docs/contracts/package-self-orientation.md +135 -0
- package/docs/contracts/rule-classification.md +4 -4
- package/docs/decisions/ADR-004-rule-governance-pruning.md +240 -0
- package/docs/getting-started.md +1 -1
- package/docs/guidelines/agent-infra/review-routing-data-format.md +1 -2
- package/package.json +1 -1
- package/scripts/_p4_migrate.py +5 -5
- package/scripts/audit_auto_rules.py +159 -0
- package/scripts/audit_likelihood.py +148 -0
- package/scripts/audit_overlap.py +145 -0
- package/scripts/build_rule_trigger_matrix.py +3 -5
- package/scripts/check_augment_description_cap.py +79 -0
- package/scripts/check_council_references.py +3 -3
- package/scripts/check_kernel_rule_bundle.py +151 -0
- package/scripts/check_references.py +21 -1
- package/scripts/compile_router.py +3 -0
- package/scripts/install.sh +0 -1
- package/scripts/lint_agents_md.py +168 -0
- package/scripts/measure_augment_budget.py +208 -0
- package/scripts/measure_markitdown_lift.py +127 -0
- package/scripts/schemas/rule.schema.json +2 -1
- package/scripts/skill_linter.py +10 -4
- package/scripts/spotcheck_thin_root.py +134 -0
- package/scripts/update_counts.py +6 -10
- package/.agent-src/rules/no-council-references.md +0 -76
- package/.agent-src/rules/review-routing-awareness.md +0 -19
- package/.agent-src/templates/copilot-review-instructions.md +0 -76
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Measure the Augment workspace-guidelines budget (Phase 1.1 of
|
|
3
|
+
road-to-augment-limit-fit).
|
|
4
|
+
|
|
5
|
+
Mirrors Augment's accounting model for the workspace prompt:
|
|
6
|
+
|
|
7
|
+
1. `AGENTS.md` body (full file, including frontmatter) injected verbatim.
|
|
8
|
+
2. `always`-type rules under `.augment/rules/` — full body injected.
|
|
9
|
+
3. `auto`-type rules — only a registry stub is injected per rule:
|
|
10
|
+
|
|
11
|
+
If the user prompt matches the description "<desc>", read the
|
|
12
|
+
file located in <path>
|
|
13
|
+
|
|
14
|
+
The body of an `auto` rule is NOT counted; only the stub line is.
|
|
15
|
+
|
|
16
|
+
The 49,512-char ceiling is the empirical limit observed against the
|
|
17
|
+
Augment Code workspace prompt (2026-05-08 baseline). This script emits
|
|
18
|
+
a per-component breakdown plus the total against that ceiling.
|
|
19
|
+
|
|
20
|
+
Output:
|
|
21
|
+
- Default: stdout summary (totals + per-component breakdown).
|
|
22
|
+
- `--json`: deterministic JSON.
|
|
23
|
+
- `--trend-append`: append a snapshot record to
|
|
24
|
+
`agents/.augment-budget-history.jsonl`.
|
|
25
|
+
|
|
26
|
+
Exit codes: 0 = under fail threshold, 1 = at/above fail threshold,
|
|
27
|
+
3 = internal error.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import datetime as _dt
|
|
34
|
+
import json
|
|
35
|
+
import re
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
40
|
+
AGENTS_MD = REPO_ROOT / "AGENTS.md"
|
|
41
|
+
RULES_DIR = REPO_ROOT / ".augment" / "rules"
|
|
42
|
+
TREND_FILE = REPO_ROOT / "agents" / ".augment-budget-history.jsonl"
|
|
43
|
+
|
|
44
|
+
# Augment workspace-guidelines ceiling — empirical 2026-05-08.
|
|
45
|
+
TOTAL_CAP = 49_512
|
|
46
|
+
WARN_THRESHOLD = 0.85
|
|
47
|
+
FAIL_THRESHOLD = 0.95
|
|
48
|
+
|
|
49
|
+
# Stub template Augment injects for `type: auto` rules. Measured by
|
|
50
|
+
# subtracting variable-length fields (description, path) from a real
|
|
51
|
+
# rendered stub in the host system prompt.
|
|
52
|
+
STUB_TEMPLATE = (
|
|
53
|
+
'If the user prompt matches the description "{desc}", '
|
|
54
|
+
"read the file located in {path}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
|
|
59
|
+
if not text.startswith("---\n"):
|
|
60
|
+
return {}, text
|
|
61
|
+
end = text.find("\n---", 4)
|
|
62
|
+
if end < 0:
|
|
63
|
+
return {}, text
|
|
64
|
+
fm_block = text[4:end]
|
|
65
|
+
body = text[end + 4 :].lstrip("\n")
|
|
66
|
+
fm: dict[str, str] = {}
|
|
67
|
+
for line in fm_block.splitlines():
|
|
68
|
+
m = re.match(r"^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$", line)
|
|
69
|
+
if m:
|
|
70
|
+
fm[m.group(1)] = m.group(2).strip().strip('"').strip("'")
|
|
71
|
+
return fm, body
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def measure() -> dict:
|
|
75
|
+
components: dict[str, dict] = {}
|
|
76
|
+
|
|
77
|
+
# 1. AGENTS.md
|
|
78
|
+
agents_text = AGENTS_MD.read_text() if AGENTS_MD.exists() else ""
|
|
79
|
+
components["agents_md"] = {
|
|
80
|
+
"path": str(AGENTS_MD.relative_to(REPO_ROOT)),
|
|
81
|
+
"chars": len(agents_text),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# 2 + 3. Rules under .augment/rules/.
|
|
85
|
+
always_total = 0
|
|
86
|
+
always_rules: list[dict] = []
|
|
87
|
+
auto_total = 0
|
|
88
|
+
auto_rules: list[dict] = []
|
|
89
|
+
|
|
90
|
+
for rule_path in sorted(RULES_DIR.glob("*.md")):
|
|
91
|
+
text = rule_path.read_text()
|
|
92
|
+
fm, _body = parse_frontmatter(text)
|
|
93
|
+
rtype = fm.get("type", "")
|
|
94
|
+
rel = str(rule_path.relative_to(REPO_ROOT))
|
|
95
|
+
if rtype == "always":
|
|
96
|
+
chars = len(text)
|
|
97
|
+
always_total += chars
|
|
98
|
+
always_rules.append({"path": rel, "chars": chars})
|
|
99
|
+
elif rtype == "auto":
|
|
100
|
+
desc = fm.get("description", "")
|
|
101
|
+
stub = STUB_TEMPLATE.format(desc=desc, path=rel)
|
|
102
|
+
chars = len(stub)
|
|
103
|
+
auto_total += chars
|
|
104
|
+
auto_rules.append(
|
|
105
|
+
{"path": rel, "desc_chars": len(desc), "stub_chars": chars}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
components["always_rules"] = {
|
|
109
|
+
"count": len(always_rules),
|
|
110
|
+
"chars": always_total,
|
|
111
|
+
"rules": sorted(always_rules, key=lambda r: -r["chars"]),
|
|
112
|
+
}
|
|
113
|
+
components["auto_rules"] = {
|
|
114
|
+
"count": len(auto_rules),
|
|
115
|
+
"chars": auto_total,
|
|
116
|
+
"rules": sorted(auto_rules, key=lambda r: -r["stub_chars"]),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
total = (
|
|
120
|
+
components["agents_md"]["chars"]
|
|
121
|
+
+ always_total
|
|
122
|
+
+ auto_total
|
|
123
|
+
)
|
|
124
|
+
return {
|
|
125
|
+
"ts": _dt.datetime.now(_dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
126
|
+
"total": total,
|
|
127
|
+
"cap": TOTAL_CAP,
|
|
128
|
+
"utilisation": round(total / TOTAL_CAP, 4),
|
|
129
|
+
"components": components,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def render_text(data: dict) -> str:
|
|
134
|
+
total = data["total"]
|
|
135
|
+
cap = data["cap"]
|
|
136
|
+
util = data["utilisation"]
|
|
137
|
+
a = data["components"]["agents_md"]["chars"]
|
|
138
|
+
ar = data["components"]["always_rules"]
|
|
139
|
+
aur = data["components"]["auto_rules"]
|
|
140
|
+
lines = [
|
|
141
|
+
f"Augment workspace-guidelines budget — cap {cap:,} chars",
|
|
142
|
+
"",
|
|
143
|
+
f" AGENTS.md {a:>6,} chars ({a/cap*100:5.1f}%)",
|
|
144
|
+
f" always-rules ({ar['count']:>2}) {ar['chars']:>6,} chars ({ar['chars']/cap*100:5.1f}%)",
|
|
145
|
+
f" auto-rule stubs ({aur['count']:>2}) {aur['chars']:>6,} chars ({aur['chars']/cap*100:5.1f}%)",
|
|
146
|
+
" " + "-" * 50,
|
|
147
|
+
f" TOTAL {total:>6,} chars ({util*100:5.1f}%)",
|
|
148
|
+
"",
|
|
149
|
+
]
|
|
150
|
+
if util >= 1.0:
|
|
151
|
+
lines.append(f"❌ OVER CAP by {total - cap:,} chars")
|
|
152
|
+
elif util >= FAIL_THRESHOLD:
|
|
153
|
+
lines.append(f"❌ FAIL — utilisation {util*100:.1f}% ≥ {FAIL_THRESHOLD*100:.0f}%")
|
|
154
|
+
elif util >= WARN_THRESHOLD:
|
|
155
|
+
lines.append(f"⚠️ WARN — utilisation {util*100:.1f}% ≥ {WARN_THRESHOLD*100:.0f}%")
|
|
156
|
+
else:
|
|
157
|
+
lines.append(f"✅ OK — utilisation {util*100:.1f}%")
|
|
158
|
+
return "\n".join(lines)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main() -> int:
|
|
162
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
163
|
+
parser.add_argument("--json", action="store_true", help="Emit JSON")
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--trend-append",
|
|
166
|
+
action="store_true",
|
|
167
|
+
help="Append a snapshot record to agents/.augment-budget-history.jsonl",
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"--check",
|
|
171
|
+
action="store_true",
|
|
172
|
+
help="Exit non-zero when utilisation ≥ FAIL_THRESHOLD or over cap",
|
|
173
|
+
)
|
|
174
|
+
args = parser.parse_args()
|
|
175
|
+
|
|
176
|
+
data = measure()
|
|
177
|
+
|
|
178
|
+
if args.trend_append:
|
|
179
|
+
TREND_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
rec = {
|
|
181
|
+
"ts": data["ts"],
|
|
182
|
+
"total": data["total"],
|
|
183
|
+
"cap": data["cap"],
|
|
184
|
+
"utilisation": data["utilisation"],
|
|
185
|
+
"agents_md": data["components"]["agents_md"]["chars"],
|
|
186
|
+
"always_rules": data["components"]["always_rules"]["chars"],
|
|
187
|
+
"auto_rules": data["components"]["auto_rules"]["chars"],
|
|
188
|
+
}
|
|
189
|
+
with TREND_FILE.open("a") as fh:
|
|
190
|
+
fh.write(json.dumps(rec, sort_keys=True) + "\n")
|
|
191
|
+
|
|
192
|
+
if args.json:
|
|
193
|
+
print(json.dumps(data, indent=2, sort_keys=True))
|
|
194
|
+
else:
|
|
195
|
+
print(render_text(data))
|
|
196
|
+
|
|
197
|
+
if args.check:
|
|
198
|
+
if data["utilisation"] >= 1.0 or data["utilisation"] >= FAIL_THRESHOLD:
|
|
199
|
+
return 1
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
try:
|
|
205
|
+
sys.exit(main())
|
|
206
|
+
except Exception as exc: # pragma: no cover - defensive top-level guard
|
|
207
|
+
print(f"❌ measure_augment_budget: internal error: {exc}", file=sys.stderr)
|
|
208
|
+
sys.exit(3)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Measure markitdown's token-saving lift on the bundled corpus.
|
|
3
|
+
|
|
4
|
+
Runs against `tests/fixtures/markitdown-corpus/`. By default (no flags) the
|
|
5
|
+
script computes the baseline-only — raw byte size and a tokens-per-4-bytes
|
|
6
|
+
estimate — without calling `markitdown-mcp`. With `--convert`, the script
|
|
7
|
+
tries to invoke `markitdown` (CLI binary) via subprocess and computes the
|
|
8
|
+
converted-Markdown token estimate plus the ratio per file.
|
|
9
|
+
|
|
10
|
+
Stdlib-only. Never installs anything. Never invokes a network host. Never
|
|
11
|
+
calls `markitdown-mcp` over HTTP — only through the `markitdown` CLI on
|
|
12
|
+
the user's PATH (peer-side install per the skill's Step 1 recipes).
|
|
13
|
+
|
|
14
|
+
Exit codes:
|
|
15
|
+
0 — baseline produced (always, when fixtures exist)
|
|
16
|
+
2 — corpus not found
|
|
17
|
+
3 — `--convert` was requested but `markitdown` is not on PATH
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
CORPUS = REPO_ROOT / "tests" / "fixtures" / "markitdown-corpus"
|
|
30
|
+
TOKEN_PER_BYTES = 4 # rough OpenAI/Anthropic tokenizer-of-thumb
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _baseline_tokens(p: Path) -> int:
|
|
34
|
+
return max(1, p.stat().st_size // TOKEN_PER_BYTES)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _converted_tokens(p: Path, *, binary: str) -> int | None:
|
|
38
|
+
try:
|
|
39
|
+
out = subprocess.run(
|
|
40
|
+
[binary, str(p)],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
check=False,
|
|
43
|
+
text=True,
|
|
44
|
+
timeout=30,
|
|
45
|
+
)
|
|
46
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
47
|
+
return None
|
|
48
|
+
if out.returncode != 0:
|
|
49
|
+
return None
|
|
50
|
+
chars = len(out.stdout)
|
|
51
|
+
if chars == 0:
|
|
52
|
+
return None
|
|
53
|
+
return max(1, chars // TOKEN_PER_BYTES)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _format_ratio(baseline: int, converted: int | None) -> str:
|
|
57
|
+
if converted is None or converted == 0:
|
|
58
|
+
return "—"
|
|
59
|
+
ratio = baseline / converted
|
|
60
|
+
return f"{ratio:.1f}×"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main() -> int:
|
|
64
|
+
parser = argparse.ArgumentParser(description="Measure markitdown lift on the bundled corpus.")
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--convert",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help="Invoke `markitdown <fixture>` per file and compute the converted-token ratio.",
|
|
69
|
+
)
|
|
70
|
+
parser.add_argument(
|
|
71
|
+
"--binary",
|
|
72
|
+
default="markitdown",
|
|
73
|
+
help="Name or path of the markitdown CLI binary (default: markitdown).",
|
|
74
|
+
)
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
|
|
77
|
+
if not CORPUS.is_dir():
|
|
78
|
+
print(f"ERROR: corpus not found at {CORPUS}", file=sys.stderr)
|
|
79
|
+
print(
|
|
80
|
+
"Generate it: python3 tests/fixtures/markitdown-corpus/_generate.py",
|
|
81
|
+
file=sys.stderr,
|
|
82
|
+
)
|
|
83
|
+
return 2
|
|
84
|
+
|
|
85
|
+
fixtures = sorted(p for p in CORPUS.iterdir() if p.is_file() and p.suffix in {".pdf", ".pptx", ".docx", ".xlsx"})
|
|
86
|
+
if not fixtures:
|
|
87
|
+
print(f"ERROR: no fixtures in {CORPUS}", file=sys.stderr)
|
|
88
|
+
return 2
|
|
89
|
+
|
|
90
|
+
binary_path: str | None = None
|
|
91
|
+
if args.convert:
|
|
92
|
+
binary_path = shutil.which(args.binary)
|
|
93
|
+
if binary_path is None:
|
|
94
|
+
print(
|
|
95
|
+
f"ERROR: --convert requested but `{args.binary}` not on PATH.\n"
|
|
96
|
+
"Install peer-side per the skill's Step 1 recipes "
|
|
97
|
+
"(Docker / pipx / uv) and re-run.",
|
|
98
|
+
file=sys.stderr,
|
|
99
|
+
)
|
|
100
|
+
return 3
|
|
101
|
+
|
|
102
|
+
print(f"Corpus: {CORPUS.relative_to(REPO_ROOT)} ({len(fixtures)} files)")
|
|
103
|
+
print(f"Mode: {'convert (peer markitdown CLI)' if binary_path else 'baseline-only'}")
|
|
104
|
+
if binary_path:
|
|
105
|
+
print(f"Binary: {binary_path}")
|
|
106
|
+
print()
|
|
107
|
+
header = f"{'fixture':<32} {'bytes':>7} {'baseline tok':>13} {'converted tok':>14} {'ratio':>7}"
|
|
108
|
+
print(header)
|
|
109
|
+
print("-" * len(header))
|
|
110
|
+
for p in fixtures:
|
|
111
|
+
size = p.stat().st_size
|
|
112
|
+
base = _baseline_tokens(p)
|
|
113
|
+
converted = _converted_tokens(p, binary=binary_path) if binary_path else None
|
|
114
|
+
ratio = _format_ratio(base, converted)
|
|
115
|
+
conv_str = f"{converted}" if converted is not None else "—"
|
|
116
|
+
print(f"{p.name:<32} {size:>7} {base:>13} {conv_str:>14} {ratio:>7}")
|
|
117
|
+
print()
|
|
118
|
+
if not binary_path:
|
|
119
|
+
print(
|
|
120
|
+
"Re-run with --convert (after installing markitdown-mcp peer-side per the skill's "
|
|
121
|
+
"Step 1 recipes) for the actual ratio."
|
|
122
|
+
)
|
|
123
|
+
return 0
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
sys.exit(main())
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"properties": {
|
|
10
10
|
"type": {
|
|
11
11
|
"type": "string",
|
|
12
|
-
"enum": ["always", "auto"]
|
|
12
|
+
"enum": ["always", "auto", "manual"],
|
|
13
|
+
"description": "`always` = injected verbatim every turn (kernel). `auto` = description stub injected, body loaded on trigger match. `manual` = no auto-injection (zero workspace-budget cost); file remains as a reference document linkable from skills/contexts. Introduced by ADR-004 to demote thin pointer-rules without breaking cross-references."
|
|
13
14
|
},
|
|
14
15
|
"source": {
|
|
15
16
|
"type": "string",
|
package/scripts/skill_linter.py
CHANGED
|
@@ -115,7 +115,7 @@ ORDERED_STEP_PATTERN = re.compile(r"^(?:\s*|\#{1,4}\s*)(\d+)\.\s+", re.MULTILINE
|
|
|
115
115
|
SECTION_PATTERN = re.compile(r"^##\s+(.+?)\s*$", re.MULTILINE)
|
|
116
116
|
FRONTMATTER_PATTERN = re.compile(r"^---\n(.*?)\n---\n", re.DOTALL)
|
|
117
117
|
DESCRIPTION_PATTERN = re.compile(r'^description:\s*"?(.*?)"?\s*$', re.MULTILINE)
|
|
118
|
-
TYPE_PATTERN = re.compile(r'^type:\s*"?(always|auto)"?\s*$', re.MULTILINE)
|
|
118
|
+
TYPE_PATTERN = re.compile(r'^type:\s*"?(always|auto|manual)"?\s*$', re.MULTILINE)
|
|
119
119
|
SOURCE_PATTERN = re.compile(r'^source:\s*"?(package|project)"?\s*$', re.MULTILINE)
|
|
120
120
|
STATUS_PATTERN = re.compile(r'^status:\s*"?(active|deprecated|superseded)"?\s*$', re.MULTILINE)
|
|
121
121
|
REPLACED_BY_PATTERN = re.compile(r'^replaced_by:\s*"?([\w-]+)"?\s*$', re.MULTILINE)
|
|
@@ -133,7 +133,7 @@ SENIOR_OUTPUT_PATTERN = re.compile(r"^##\s+Output\s*$", re.MULTILINE)
|
|
|
133
133
|
H1_PATTERN = re.compile(r"^# .+", re.MULTILINE)
|
|
134
134
|
DOUBLE_BLANK_PATTERN = re.compile(r"\n{3,}")
|
|
135
135
|
|
|
136
|
-
VALID_RULE_TYPES = {"always", "auto"}
|
|
136
|
+
VALID_RULE_TYPES = {"always", "auto", "manual"}
|
|
137
137
|
VALID_RULE_SOURCES = {"package", "project"}
|
|
138
138
|
VALID_STATUSES = {"active", "deprecated", "superseded"}
|
|
139
139
|
|
|
@@ -683,6 +683,12 @@ def lint_router_frontmatter(rule_id: str, frontmatter: str,
|
|
|
683
683
|
triggers = _parse_yaml_list(frontmatter, "triggers")
|
|
684
684
|
routes_to = _parse_yaml_list(frontmatter, "routes_to")
|
|
685
685
|
|
|
686
|
+
# Manual rules are reference-only — not auto-injected, not router-routed
|
|
687
|
+
# (ADR-004). Skip router validation so legacy triggers/routes_to fields
|
|
688
|
+
# remain documented in the rule body without forcing maintenance.
|
|
689
|
+
if rule_type == "manual":
|
|
690
|
+
return issues
|
|
691
|
+
|
|
686
692
|
is_kernel = rule_id in KERNEL_RULE_IDS or rule_type == "always"
|
|
687
693
|
|
|
688
694
|
if is_kernel:
|
|
@@ -961,9 +967,9 @@ def lint_rule(path: Path, text: str) -> LintResult:
|
|
|
961
967
|
# type field
|
|
962
968
|
rule_type = extract_frontmatter_field(frontmatter, TYPE_PATTERN)
|
|
963
969
|
if rule_type is None:
|
|
964
|
-
issues.append(Issue("error", "missing_type", "Frontmatter missing 'type' field (must be 'always' or '
|
|
970
|
+
issues.append(Issue("error", "missing_type", "Frontmatter missing 'type' field (must be 'always', 'auto', or 'manual')"))
|
|
965
971
|
elif rule_type not in VALID_RULE_TYPES:
|
|
966
|
-
issues.append(Issue("error", "invalid_type", f"Invalid type '{rule_type}'; must be 'always' or '
|
|
972
|
+
issues.append(Issue("error", "invalid_type", f"Invalid type '{rule_type}'; must be 'always', 'auto', or 'manual'"))
|
|
967
973
|
|
|
968
974
|
# source field
|
|
969
975
|
rule_source = extract_frontmatter_field(frontmatter, SOURCE_PATTERN)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Phase 6.6 platform spot-check via AI council.
|
|
3
|
+
|
|
4
|
+
Sends the refactored package-root AGENTS.md and the consumer template
|
|
5
|
+
to Sonnet 4.5 + gpt-4o, asks each member to answer five questions
|
|
6
|
+
that simulate a fresh agent landing on the file. Records qualitative
|
|
7
|
+
verdicts in agents/reports/thin-root-platform-spotcheck.md.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
16
|
+
sys.path.insert(0, str(ROOT))
|
|
17
|
+
|
|
18
|
+
from scripts.ai_council.clients import ( # noqa: E402
|
|
19
|
+
AnthropicClient,
|
|
20
|
+
OpenAIClient,
|
|
21
|
+
load_anthropic_key,
|
|
22
|
+
load_openai_key,
|
|
23
|
+
)
|
|
24
|
+
from scripts.ai_council.orchestrator import ( # noqa: E402
|
|
25
|
+
CostBudget,
|
|
26
|
+
CouncilQuestion,
|
|
27
|
+
consult,
|
|
28
|
+
)
|
|
29
|
+
from scripts.ai_council.pricing import load_prices # noqa: E402
|
|
30
|
+
|
|
31
|
+
QUESTIONS = """
|
|
32
|
+
You are evaluating whether the AGENTS.md file below is a sufficient
|
|
33
|
+
entry point for an AI coding agent landing on this repository for
|
|
34
|
+
the first time. You see only the AGENTS.md content; you do NOT have
|
|
35
|
+
file-system access. Answer the following five questions in JSON
|
|
36
|
+
shape `{"q1": {...}, ..., "q5": {...}}` where each value is
|
|
37
|
+
`{"answer": <string>, "confidence": "high"|"medium"|"low",
|
|
38
|
+
"pointer_used": <one of the linked paths from AGENTS.md, or null>}`.
|
|
39
|
+
|
|
40
|
+
Q1. Where do I edit content in this repo / project? (a path)
|
|
41
|
+
Q2. What command do I run to verify everything is green before opening a PR?
|
|
42
|
+
Q3. Where would I find the always-active behavioural rules?
|
|
43
|
+
Q4. If only this file is reachable, what five things must I assume to be true to act safely? (cite the emergency-triage block)
|
|
44
|
+
Q5. What outboard target document would I open to learn the package-self-orientation / the consumer-fill-out guide? (a path)
|
|
45
|
+
|
|
46
|
+
After the JSON, add a short prose verdict (≤ 5 sentences) on:
|
|
47
|
+
- Whether the pointer-following worked (could you cite a path for Q1, Q3, Q5?)
|
|
48
|
+
- Whether the emergency-triage block answered Q4 unambiguously.
|
|
49
|
+
- One concrete improvement you'd make to the AGENTS.md.
|
|
50
|
+
|
|
51
|
+
Do not invent file paths. If a question cannot be answered from the
|
|
52
|
+
file alone, set `"pointer_used": null` and lower confidence.
|
|
53
|
+
""".strip()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main() -> int:
|
|
57
|
+
package_root = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
|
|
58
|
+
consumer_template = (
|
|
59
|
+
ROOT / ".agent-src.uncompressed" / "templates" / "AGENTS.md"
|
|
60
|
+
).read_text(encoding="utf-8")
|
|
61
|
+
|
|
62
|
+
artefact = (
|
|
63
|
+
"## Artefact A — package-root AGENTS.md\n\n"
|
|
64
|
+
f"```markdown\n{package_root}\n```\n\n"
|
|
65
|
+
"## Artefact B — consumer-template AGENTS.md\n\n"
|
|
66
|
+
f"```markdown\n{consumer_template}\n```\n\n"
|
|
67
|
+
f"{QUESTIONS}\n"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
members = [
|
|
71
|
+
AnthropicClient(model="claude-sonnet-4-5", api_key=load_anthropic_key()),
|
|
72
|
+
OpenAIClient(model="gpt-4o", api_key=load_openai_key()),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
question = CouncilQuestion(
|
|
76
|
+
mode="files",
|
|
77
|
+
user_prompt=artefact,
|
|
78
|
+
max_tokens=1500,
|
|
79
|
+
)
|
|
80
|
+
budget = CostBudget(max_total_usd=2.00, max_calls=4)
|
|
81
|
+
table = load_prices()
|
|
82
|
+
|
|
83
|
+
print("Running spot-check council …", file=sys.stderr)
|
|
84
|
+
responses = consult(members, question, budget, table=table, rounds=1)
|
|
85
|
+
|
|
86
|
+
out_dir = ROOT / "agents" / "reports"
|
|
87
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
md_path = out_dir / "thin-root-platform-spotcheck.md"
|
|
89
|
+
json_path = out_dir / "thin-root-platform-spotcheck.json"
|
|
90
|
+
|
|
91
|
+
md_lines = [
|
|
92
|
+
"# Thin-Root platform spot-check (Phase 6.6)",
|
|
93
|
+
"",
|
|
94
|
+
"> AI-council proxy for the manual platform spot-check. Two",
|
|
95
|
+
"> external reviewers (Sonnet 4.5, gpt-4o) simulate a fresh",
|
|
96
|
+
"> agent landing on the refactored AGENTS.md and answer five",
|
|
97
|
+
"> orientation questions from the file alone.",
|
|
98
|
+
"",
|
|
99
|
+
"## Verdicts",
|
|
100
|
+
"",
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
raw = []
|
|
104
|
+
for r in responses:
|
|
105
|
+
body = r.text or f"<error: {r.error}>"
|
|
106
|
+
raw.append({
|
|
107
|
+
"provider": r.provider,
|
|
108
|
+
"model": r.model,
|
|
109
|
+
"tokens_in": r.input_tokens,
|
|
110
|
+
"tokens_out": r.output_tokens,
|
|
111
|
+
"latency_ms": r.latency_ms,
|
|
112
|
+
"error": r.error,
|
|
113
|
+
"text": body,
|
|
114
|
+
})
|
|
115
|
+
md_lines.append(f"### {r.provider} ({r.model})")
|
|
116
|
+
md_lines.append("")
|
|
117
|
+
md_lines.append(f"- tokens in: {r.input_tokens} · out: {r.output_tokens} · latency: {r.latency_ms}ms")
|
|
118
|
+
if r.error:
|
|
119
|
+
md_lines.append(f"- error: `{r.error}`")
|
|
120
|
+
md_lines.append("")
|
|
121
|
+
md_lines.append("```")
|
|
122
|
+
md_lines.append(body[:8000])
|
|
123
|
+
md_lines.append("```")
|
|
124
|
+
md_lines.append("")
|
|
125
|
+
|
|
126
|
+
md_path.write_text("\n".join(md_lines), encoding="utf-8")
|
|
127
|
+
json_path.write_text(json.dumps(raw, indent=2), encoding="utf-8")
|
|
128
|
+
print(f"✅ Wrote {md_path}", file=sys.stderr)
|
|
129
|
+
print(f"✅ Wrote {json_path}", file=sys.stderr)
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
raise SystemExit(main())
|
package/scripts/update_counts.py
CHANGED
|
@@ -67,16 +67,12 @@ TARGETS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
67
67
|
# the raw file count this script computes.
|
|
68
68
|
],
|
|
69
69
|
),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(r"(guidelines/\s+\()(\d+)( guidelines\))", "guidelines"),
|
|
77
|
-
(r"(personas/\s+\()(\d+)( personas\))", "personas"),
|
|
78
|
-
],
|
|
79
|
-
),
|
|
70
|
+
# Note: AGENTS.md previously held the per-directory count annotations
|
|
71
|
+
# (`skills/ (N skills)`, `rules/ (N rules)`, ...). The Thin-Root
|
|
72
|
+
# refactor (Phase 6, road-to-augment-limit-fit, 2026-05-08) made
|
|
73
|
+
# AGENTS.md a navigation-only surface — counts now live in README.md
|
|
74
|
+
# and docs/architecture.md. The corresponding pytest sentinel lives
|
|
75
|
+
# in tests/test_readme_hero_counts.py::test_agents_md_is_thin_root_navigation_surface.
|
|
80
76
|
(
|
|
81
77
|
"docs/getting-started.md",
|
|
82
78
|
[
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: "auto"
|
|
3
|
-
tier: "mechanical-already"
|
|
4
|
-
description: "Linking a specific file in agents/council-{questions,responses,sessions}/ from any artifact — council files are gitignored, local-only, auto-pruned; inline the convergence instead"
|
|
5
|
-
alwaysApply: false
|
|
6
|
-
source: package
|
|
7
|
-
triggers:
|
|
8
|
-
- path_prefix: "agents/council-questions/"
|
|
9
|
-
- path_prefix: "agents/council-responses/"
|
|
10
|
-
- path_prefix: "agents/council-sessions/"
|
|
11
|
-
- intent: "link to council artefact"
|
|
12
|
-
routes_to:
|
|
13
|
-
- "skill:ai-council"
|
|
14
|
-
validator_ignore:
|
|
15
|
-
- type: "substring"
|
|
16
|
-
pattern: ".agent-src.uncompressed/"
|
|
17
|
-
reason: "Rule references the authoring tree when contrasting transient council files."
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
# No Council References from Any Artifact
|
|
21
|
-
|
|
22
|
-
Council artefacts under `agents/council-{questions,responses,sessions}/`
|
|
23
|
-
are **gitignored, local-only, and auto-pruned** after
|
|
24
|
-
`ai_council.session_retention_days` (default 7). They are
|
|
25
|
-
disposable scratch — never part of the repo, never visible to a
|
|
26
|
-
reviewer who clones, never durable across the retention window.
|
|
27
|
-
|
|
28
|
-
A link to a specific council file rots three ways: gitignored
|
|
29
|
-
(not in cloned repo), pruned after retention window (gone even
|
|
30
|
-
locally), and the installed `.augment/` projection cannot follow a
|
|
31
|
-
path that does not exist in the consumer.
|
|
32
|
-
|
|
33
|
-
## The Iron Law
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
NEVER LINK TO A SPECIFIC FILE INSIDE
|
|
37
|
-
agents/council-{questions,responses,sessions}/
|
|
38
|
-
FROM ANY ARTIFACT — ROADMAPS INCLUDED.
|
|
39
|
-
INLINE THE CONVERGENCE WITH DATE + MEMBERS, NEVER THE PATH.
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Applies to **every** artifact. Council artefacts are more transient
|
|
43
|
-
than roadmaps — the local copy disappears too.
|
|
44
|
-
|
|
45
|
-
## Forbidden vs allowed
|
|
46
|
-
|
|
47
|
-
**Forbidden** in any `*.md` / `*.yml` / `*.json` / `*.py`:
|
|
48
|
-
`agents/council-questions/<file>.md`,
|
|
49
|
-
`agents/council-responses/<file>.json`,
|
|
50
|
-
`agents/council-sessions/<file>.json` or `<timestamp>/...`.
|
|
51
|
-
|
|
52
|
-
**Allowed**: directory mentions (talking about the output convention,
|
|
53
|
-
not a specific file); the `ai-council` skill and `/council:*` commands
|
|
54
|
-
documenting the output path schema; inline convergence summary —
|
|
55
|
-
e.g. *"Council (claude-sonnet-4-5 + gpt-4o, 2026-05-06) converged
|
|
56
|
-
on …"* with date + members, no filepath.
|
|
57
|
-
|
|
58
|
-
## What to do instead
|
|
59
|
-
|
|
60
|
-
Identify the durable conclusion (decision, contract, lesson),
|
|
61
|
-
inline a convergence-summary block (members, date, cost if relevant
|
|
62
|
-
— see `ai-council` § Output format), and optionally promote the
|
|
63
|
-
lesson to `agents/contexts/`. The context is durable; the council
|
|
64
|
-
file was the catalyst.
|
|
65
|
-
|
|
66
|
-
Failure mode: *"I'll just link to the session JSON, it's evidence."*
|
|
67
|
-
The session is gone in 7 days. **Inline first, link never.**
|
|
68
|
-
|
|
69
|
-
## See also
|
|
70
|
-
|
|
71
|
-
- [`no-roadmap-references`](no-roadmap-references.md) — sibling rule
|
|
72
|
-
for the roadmap layer
|
|
73
|
-
- [`augment-source-of-truth`](augment-source-of-truth.md) — edit
|
|
74
|
-
`.agent-src.uncompressed/`
|
|
75
|
-
- [`ai-council`](../skills/ai-council/SKILL.md) — output path
|
|
76
|
-
convention and convergence-summary format
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
type: "auto"
|
|
3
|
-
tier: "2a"
|
|
4
|
-
description: "When routing reviewers or flagging risk hotspots — consult ownership-map and historical-bug-patterns before suggesting reviewers or claiming a change is safe"
|
|
5
|
-
source: package
|
|
6
|
-
triggers:
|
|
7
|
-
- keyword: "reviewer"
|
|
8
|
-
- phrase: "risk hotspot"
|
|
9
|
-
- phrase: "ownership map"
|
|
10
|
-
routes_to:
|
|
11
|
-
- "skill:review-routing"
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# Review Routing Awareness
|
|
15
|
-
|
|
16
|
-
**Iron Law.** Consult ownership-map and historical-bug-patterns before suggesting reviewers or claiming a change is safe.
|
|
17
|
-
|
|
18
|
-
Body migrated to `skill:review-routing` (per P4 of `road-to-kernel-and-router.md`).
|
|
19
|
-
Trigger-set above activates this routing under the `balanced` and `full` profiles.
|