@event4u/agent-config 1.20.0 → 1.21.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/agents.md +1 -1
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/chat-history/import.md +60 -64
- package/.agent-src/commands/compress.md +12 -0
- package/.agent-src/commands/context/create.md +2 -2
- package/.agent-src/commands/context.md +1 -1
- package/.agent-src/commands/copilot-agents.md +1 -1
- package/.agent-src/commands/council/default.md +17 -5
- package/.agent-src/commands/council.md +1 -1
- package/.agent-src/commands/e2e-heal.md +1 -1
- package/.agent-src/commands/e2e-plan.md +1 -1
- package/.agent-src/commands/feature/dev.md +3 -3
- package/.agent-src/commands/feature.md +1 -1
- package/.agent-src/commands/fix/seeder.md +2 -2
- package/.agent-src/commands/fix.md +1 -1
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/judge.md +2 -2
- package/.agent-src/commands/memory.md +1 -1
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/module.md +1 -1
- package/.agent-src/commands/onboard.md +4 -4
- package/.agent-src/commands/optimize/augmentignore.md +1 -1
- package/.agent-src/commands/optimize-prompt.md +61 -0
- package/.agent-src/commands/optimize.md +1 -1
- package/.agent-src/commands/override.md +1 -1
- package/.agent-src/commands/review-changes.md +1 -1
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/roadmap.md +1 -1
- package/.agent-src/commands/set-cost-profile.md +3 -3
- package/.agent-src/commands/sync-agent-settings.md +2 -2
- package/.agent-src/commands/tests/create.md +2 -2
- package/.agent-src/commands/tests.md +1 -1
- package/.agent-src/commands/threat-model.md +4 -4
- package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
- package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
- package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
- package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -1
- package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +128 -5
- package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
- package/.agent-src/contexts/model-recommendations.md +2 -2
- package/.agent-src/contexts/override-system.md +1 -1
- package/.agent-src/personas/product-owner.md +2 -2
- package/.agent-src/personas/qa.md +1 -1
- package/.agent-src/rules/agent-authority.md +5 -6
- package/.agent-src/rules/agent-docs.md +11 -53
- package/.agent-src/rules/analysis-skill-routing.md +10 -40
- package/.agent-src/rules/architecture.md +6 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
- package/.agent-src/rules/artifact-engagement-recording.md +23 -59
- package/.agent-src/rules/ask-when-uncertain.md +24 -47
- package/.agent-src/rules/augment-portability.md +14 -62
- package/.agent-src/rules/augment-source-of-truth.md +10 -1
- package/.agent-src/rules/autonomous-execution.md +17 -98
- package/.agent-src/rules/capture-learnings.md +9 -80
- package/.agent-src/rules/cli-output-handling.md +12 -42
- package/.agent-src/rules/command-suggestion-policy.md +25 -73
- package/.agent-src/rules/commit-conventions.md +9 -58
- package/.agent-src/rules/commit-policy.md +16 -47
- package/.agent-src/rules/context-hygiene.md +5 -0
- package/.agent-src/rules/direct-answers.md +21 -50
- package/.agent-src/rules/docker-commands.md +11 -45
- package/.agent-src/rules/docs-sync.md +10 -56
- package/.agent-src/rules/downstream-changes.md +5 -0
- package/.agent-src/rules/e2e-testing.md +9 -44
- package/.agent-src/rules/guidelines.md +13 -75
- package/.agent-src/rules/improve-before-implement.md +10 -2
- package/.agent-src/rules/language-and-tone.md +41 -106
- package/.agent-src/rules/laravel-translations.md +11 -40
- package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
- package/.agent-src/rules/minimal-safe-diff.md +4 -0
- package/.agent-src/rules/missing-tool-handling.md +4 -0
- package/.agent-src/rules/model-recommendation.md +9 -61
- package/.agent-src/rules/no-attribution-footers.md +5 -0
- package/.agent-src/rules/no-cheap-questions.md +11 -27
- package/.agent-src/rules/no-council-references.md +76 -0
- package/.agent-src/rules/no-roadmap-references.md +7 -0
- package/.agent-src/rules/non-destructive-by-default.md +13 -43
- package/.agent-src/rules/onboarding-gate.md +9 -117
- package/.agent-src/rules/package-ci-checks.md +10 -37
- package/.agent-src/rules/php-coding.md +10 -55
- package/.agent-src/rules/preservation-guard.md +9 -0
- package/.agent-src/rules/review-routing-awareness.md +9 -97
- package/.agent-src/rules/reviewer-awareness.md +8 -83
- package/.agent-src/rules/roadmap-progress-sync.md +7 -170
- package/.agent-src/rules/role-mode-adherence.md +6 -2
- package/.agent-src/rules/rule-type-governance.md +8 -66
- package/.agent-src/rules/runtime-safety.md +5 -0
- package/.agent-src/rules/scope-control.md +17 -62
- package/.agent-src/rules/security-sensitive-stop.md +7 -1
- package/.agent-src/rules/size-enforcement.md +6 -1
- package/.agent-src/rules/skill-improvement-trigger.md +9 -49
- package/.agent-src/rules/skill-quality.md +7 -113
- package/.agent-src/rules/slash-command-routing-policy.md +11 -63
- package/.agent-src/rules/think-before-action.md +22 -87
- package/.agent-src/rules/token-efficiency.md +10 -74
- package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
- package/.agent-src/rules/tool-safety.md +4 -0
- package/.agent-src/rules/ui-audit-gate.md +25 -61
- package/.agent-src/rules/upstream-proposal.md +9 -67
- package/.agent-src/rules/user-interaction.md +22 -108
- package/.agent-src/rules/verify-before-complete.md +1 -1
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +65 -0
- package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
- package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
- package/.agent-src/skills/authz-review/SKILL.md +1 -1
- package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
- package/.agent-src/skills/bug-analyzer/SKILL.md +5 -5
- package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
- package/.agent-src/skills/code-review/SKILL.md +2 -2
- package/.agent-src/skills/command-writing/SKILL.md +11 -0
- package/.agent-src/skills/composer-packages/SKILL.md +2 -2
- package/.agent-src/skills/context-authoring/SKILL.md +11 -0
- package/.agent-src/skills/context-document/SKILL.md +1 -1
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
- package/.agent-src/skills/copilot-config/SKILL.md +1 -1
- package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
- package/.agent-src/skills/devcontainer/SKILL.md +2 -2
- package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
- package/.agent-src/skills/docker/SKILL.md +1 -1
- package/.agent-src/skills/dto-creator/SKILL.md +1 -1
- package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
- package/.agent-src/skills/fe-design/SKILL.md +4 -4
- package/.agent-src/skills/feature-planning/SKILL.md +5 -5
- package/.agent-src/skills/funnel-analysis/SKILL.md +1 -1
- package/.agent-src/skills/laravel/SKILL.md +1 -1
- package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
- package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
- package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
- package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
- package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
- package/.agent-src/skills/migration-creator/SKILL.md +7 -7
- package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
- package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
- package/.agent-src/skills/pest-testing/SKILL.md +6 -6
- package/.agent-src/skills/php-service/SKILL.md +2 -2
- package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
- package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
- package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
- package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/rule-writing/SKILL.md +33 -0
- package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
- package/.agent-src/skills/skill-writing/SKILL.md +14 -0
- package/.agent-src/skills/terraform/SKILL.md +2 -2
- package/.agent-src/skills/terragrunt/SKILL.md +8 -8
- package/.agent-src/skills/test-performance/SKILL.md +5 -5
- package/.agent-src/skills/threat-modeling/SKILL.md +2 -2
- package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/templates/AGENTS.md +1 -1
- package/.agent-src/templates/agent-settings.md +21 -16
- package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
- package/.agent-src/templates/contexts.md +1 -1
- package/.agent-src/templates/copilot-instructions.md +21 -0
- package/.agent-src/templates/copilot-review-instructions.md +76 -0
- package/.agent-src/templates/features.md +1 -1
- package/.agent-src/templates/rule.md +127 -0
- package/.claude-plugin/marketplace.json +4 -1
- package/AGENTS.md +32 -5
- package/CHANGELOG.md +69 -3
- package/README.md +22 -21
- package/config/agent-settings.template.yml +44 -10
- package/config/gitignore-block.txt +7 -0
- package/docs/architecture.md +86 -5
- package/docs/catalog.md +16 -6
- package/docs/contracts/agent-memory-contract.md +1 -1
- package/docs/contracts/context-paths.md +2 -1
- package/docs/contracts/file-ownership-matrix.json +354 -500
- package/docs/contracts/iron-law-overrides.txt +25 -0
- package/docs/contracts/kernel-membership.md +273 -0
- package/docs/contracts/load-context-schema.md +26 -11
- package/docs/contracts/pilot/agent-authority.md +24 -0
- package/docs/contracts/pilot/direct-answers.md +70 -0
- package/docs/contracts/pilot/language-and-tone.md +63 -0
- package/docs/contracts/rule-classification.md +170 -0
- package/docs/contracts/rule-router.md +153 -0
- package/docs/customization.md +17 -6
- package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
- package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
- package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
- package/docs/guidelines/augment-portability-patterns.md +68 -0
- package/docs/guidelines/php/php-coding-patterns.md +62 -0
- package/package.json +1 -1
- package/scripts/_p43_bodies.py +235 -0
- package/scripts/_p43_compress.py +118 -0
- package/scripts/_p4_migrate.py +199 -0
- package/scripts/_pilot_council_question.py +57 -0
- package/scripts/_pilot_measure.py +53 -0
- package/scripts/ai_council/session.py +107 -5
- package/scripts/build_linear_digest.py +3 -5
- package/scripts/check_always_budget.py +39 -6
- package/scripts/check_compressed_paths.py +213 -0
- package/scripts/check_compression.py +15 -0
- package/scripts/check_context_paths.py +1 -0
- package/scripts/check_council_layout.py +105 -0
- package/scripts/check_council_references.py +145 -0
- package/scripts/check_portability.py +2 -0
- package/scripts/check_references.py +2 -0
- package/scripts/check_token_optimizer_freshness.py +131 -0
- package/scripts/compile_router.py +148 -0
- package/scripts/compress.py +219 -11
- package/scripts/council_cli.py +9 -5
- package/scripts/council_prune.py +81 -0
- package/scripts/count_token_optimizer_usage.sh +54 -0
- package/scripts/install.sh +44 -2
- package/scripts/iron_law_sha.py +98 -0
- package/scripts/lint_load_context.py +35 -5
- package/scripts/measure_rule_budget.py +314 -0
- package/scripts/prototype_lint_contradictions.py +150 -0
- package/scripts/schemas/rule.schema.json +55 -6
- package/scripts/skill_linter.py +196 -6
- package/scripts/smoke_path_resolution.py +93 -0
- package/scripts/validate_frontmatter.py +41 -1
- package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
- /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
- /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CI guard for the `no-council-references` rule.
|
|
3
|
+
|
|
4
|
+
Council artefacts under `agents/council-{questions,responses,sessions}/`
|
|
5
|
+
are gitignored, local-only, and auto-pruned. A link to a specific
|
|
6
|
+
council file rots three ways: gitignored (not in cloned repo),
|
|
7
|
+
pruned after the retention window (gone even locally), and the
|
|
8
|
+
installed `.augment/` projection cannot follow a path that does not
|
|
9
|
+
exist in the consumer.
|
|
10
|
+
|
|
11
|
+
This linter scans durable artefacts for forbidden links to specific
|
|
12
|
+
council files. Directory mentions and placeholder paths
|
|
13
|
+
(`<timestamp>`, `<topic-slug>`) are allowed — they document the
|
|
14
|
+
output-path convention, not a live reference.
|
|
15
|
+
|
|
16
|
+
Forbidden hits in this codebase exist today (kernel-membership ADRs
|
|
17
|
+
cite real session JSONs as decision traces). Suppress them with an
|
|
18
|
+
inline pragma at the end of the line:
|
|
19
|
+
|
|
20
|
+
`agents/council-sessions/...json` <!-- council-ref-allowed: <reason> -->
|
|
21
|
+
|
|
22
|
+
Exit codes:
|
|
23
|
+
0 — no forbidden references.
|
|
24
|
+
1 — at least one forbidden reference found.
|
|
25
|
+
|
|
26
|
+
Invocation (from project root):
|
|
27
|
+
python3 scripts/check_council_references.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Iterable
|
|
36
|
+
|
|
37
|
+
ROOT = Path(".")
|
|
38
|
+
|
|
39
|
+
# A specific file inside a council dir: must end with .md or .json,
|
|
40
|
+
# must NOT contain `<` or `>` (placeholders), must NOT contain backticks
|
|
41
|
+
# or quotes (those are line delimiters, not path content).
|
|
42
|
+
PATTERN = re.compile(
|
|
43
|
+
r"agents/council-(?:questions|responses|sessions)/"
|
|
44
|
+
r"([^\s\"'<>)\]`]+\.(?:md|json))"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Only these durable surfaces are scanned. Archive, analysis, and the
|
|
48
|
+
# council dirs themselves are excluded by design.
|
|
49
|
+
SCAN_ROOTS = (
|
|
50
|
+
".agent-src.uncompressed",
|
|
51
|
+
"agents/roadmaps",
|
|
52
|
+
"agents/contexts",
|
|
53
|
+
"agents/docs",
|
|
54
|
+
"docs/contracts",
|
|
55
|
+
"docs/decisions",
|
|
56
|
+
"docs/guidelines",
|
|
57
|
+
)
|
|
58
|
+
SCAN_EXTS = (".md", ".yml", ".yaml", ".json", ".py")
|
|
59
|
+
|
|
60
|
+
# Files (or directory prefixes) that legitimately document the output
|
|
61
|
+
# convention or are scratch / archived. Paths are POSIX-style, repo-relative.
|
|
62
|
+
ALLOWLIST_PREFIXES: tuple[str, ...] = (
|
|
63
|
+
# Archived roadmaps — historical evidence trail.
|
|
64
|
+
"agents/roadmaps/archive/",
|
|
65
|
+
# Working comparison docs — forward-refs to planned artefacts (mirrors
|
|
66
|
+
# the SKIP_DIRS contract in scripts/check_references.py).
|
|
67
|
+
"agents/analysis/",
|
|
68
|
+
# The rule itself documents forbidden vs. allowed forms.
|
|
69
|
+
".agent-src.uncompressed/rules/no-council-references.md",
|
|
70
|
+
# ai-council skill documents the output-path schema.
|
|
71
|
+
".agent-src.uncompressed/skills/ai-council/",
|
|
72
|
+
# Council commands document the output-path schema.
|
|
73
|
+
".agent-src.uncompressed/commands/council/",
|
|
74
|
+
".agent-src.uncompressed/commands/council.md",
|
|
75
|
+
)
|
|
76
|
+
# Top-level files that are also exempt (e.g. CHANGELOG with historical entries).
|
|
77
|
+
ALLOWLIST_FILES: frozenset[str] = frozenset({
|
|
78
|
+
"CHANGELOG.md",
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
INLINE_PRAGMA = re.compile(r"<!--\s*council-ref-allowed:[^>]*-->")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _is_allowlisted(rel: str) -> bool:
|
|
85
|
+
if rel in ALLOWLIST_FILES:
|
|
86
|
+
return True
|
|
87
|
+
return any(rel.startswith(prefix) for prefix in ALLOWLIST_PREFIXES)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _scan_file(path: Path) -> list[tuple[int, str]]:
|
|
91
|
+
findings: list[tuple[int, str]] = []
|
|
92
|
+
try:
|
|
93
|
+
text = path.read_text(encoding="utf-8")
|
|
94
|
+
except (OSError, UnicodeDecodeError):
|
|
95
|
+
return findings
|
|
96
|
+
for ln, line in enumerate(text.splitlines(), 1):
|
|
97
|
+
if INLINE_PRAGMA.search(line):
|
|
98
|
+
continue
|
|
99
|
+
for m in PATTERN.finditer(line):
|
|
100
|
+
findings.append((ln, m.group(0)))
|
|
101
|
+
return findings
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _iter_files(roots: Iterable[str]) -> Iterable[Path]:
|
|
105
|
+
for root in roots:
|
|
106
|
+
base = ROOT / root
|
|
107
|
+
if not base.exists():
|
|
108
|
+
continue
|
|
109
|
+
if base.is_file():
|
|
110
|
+
yield base
|
|
111
|
+
continue
|
|
112
|
+
for path in sorted(base.rglob("*")):
|
|
113
|
+
if path.is_file() and path.suffix in SCAN_EXTS:
|
|
114
|
+
yield path
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> int:
|
|
118
|
+
violations: list[tuple[Path, int, str]] = []
|
|
119
|
+
for path in _iter_files(SCAN_ROOTS):
|
|
120
|
+
rel = path.as_posix()
|
|
121
|
+
if _is_allowlisted(rel):
|
|
122
|
+
continue
|
|
123
|
+
for ln, ref in _scan_file(path):
|
|
124
|
+
violations.append((path, ln, ref))
|
|
125
|
+
|
|
126
|
+
if not violations:
|
|
127
|
+
print("✅ No forbidden council references in durable artefacts.")
|
|
128
|
+
return 0
|
|
129
|
+
|
|
130
|
+
print(f"❌ {len(violations)} forbidden council reference(s):\n")
|
|
131
|
+
for path, ln, ref in violations:
|
|
132
|
+
print(f" - {path.as_posix()}:{ln}: {ref}")
|
|
133
|
+
print(
|
|
134
|
+
"\nRule: .agent-src/rules/no-council-references.md\n"
|
|
135
|
+
"Fix: inline the convergence summary (members + date) instead of\n"
|
|
136
|
+
"linking the file. Append "
|
|
137
|
+
"<!-- council-ref-allowed: <reason> --> on the same line to\n"
|
|
138
|
+
"suppress when the reference is genuinely required (ADR / contract\n"
|
|
139
|
+
"decision trace)."
|
|
140
|
+
)
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
sys.exit(main())
|
|
@@ -316,6 +316,8 @@ _TASK_FENCE_RE = re.compile(r"^\s*task\s+([a-z][a-z0-9:_-]*)\b")
|
|
|
316
316
|
_TASK_DETECTOR_SKIP = (
|
|
317
317
|
"rules/augment-portability.md",
|
|
318
318
|
"contexts/communication/rules-auto/augment-portability-mechanics.md",
|
|
319
|
+
"rules/package-ci-checks.md",
|
|
320
|
+
"contexts/communication/rules-auto/package-ci-checks-mechanics.md",
|
|
319
321
|
)
|
|
320
322
|
|
|
321
323
|
|
|
@@ -103,6 +103,8 @@ EXAMPLE_PATH_PATTERNS = [
|
|
|
103
103
|
re.compile(r"skills/[\w-]+/SKILL\.md"), # example skill paths in commands
|
|
104
104
|
re.compile(r"\{"), # template placeholders like {module}
|
|
105
105
|
re.compile(r"\.compression-hashes\.json"), # JSON file, not .md
|
|
106
|
+
re.compile(r"-foo\.(md|json|yml|yaml)$"), # `-foo.<ext>` placeholder examples
|
|
107
|
+
re.compile(r"-bar\.(md|json|yml|yaml)$"), # `-bar.<ext>` placeholder examples
|
|
106
108
|
# Forward references inside in-flight planning docs (road-to-
|
|
107
109
|
# structural-optimization.md and its companion spike protocols).
|
|
108
110
|
# Each pattern below is removed once the matching phase lands.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Token-Optimizer freshness validator.
|
|
4
|
+
|
|
5
|
+
Per `road-to-token-optimization.md` P1.3: parses the catalog table inside
|
|
6
|
+
`.agent-src.uncompressed/skills/token-optimizer/SKILL.md`, verifies every
|
|
7
|
+
cited internal asset exists, and `grep`s the trigger keywords against
|
|
8
|
+
each target file. Fails on missing target OR keyword mismatch.
|
|
9
|
+
|
|
10
|
+
Authoritative-link rows (upstream URLs, planned commands marked TBD) are
|
|
11
|
+
recorded but not fetched — they live and die with their upstream and are
|
|
12
|
+
out of scope for this CI gate.
|
|
13
|
+
|
|
14
|
+
Acceptance: stdlib-only, deterministic, exit 0 = clean / exit 1 = drift.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
24
|
+
SKILL = REPO_ROOT / ".agent-src.uncompressed" / "skills" / "token-optimizer" / "SKILL.md"
|
|
25
|
+
|
|
26
|
+
# Catalog row pattern: | name | path | keywords | description |
|
|
27
|
+
ROW_RE = re.compile(
|
|
28
|
+
r"^\|\s*`?(?P<name>[^`|]+?)`?\s*\|\s*"
|
|
29
|
+
r"(?P<path>[^|]+?)\s*\|\s*"
|
|
30
|
+
r"(?P<keywords>[^|]+?)\s*\|\s*"
|
|
31
|
+
r"(?P<desc>[^|]+?)\s*\|\s*$"
|
|
32
|
+
)
|
|
33
|
+
KW_RE = re.compile(r"`([^`]+)`")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_catalog(text: str) -> list[dict[str, str]]:
|
|
37
|
+
rows: list[dict[str, str]] = []
|
|
38
|
+
in_catalog = False
|
|
39
|
+
for line in text.splitlines():
|
|
40
|
+
if line.strip().startswith("## "):
|
|
41
|
+
in_catalog = line.strip() == "## Catalog"
|
|
42
|
+
continue
|
|
43
|
+
if not in_catalog:
|
|
44
|
+
continue
|
|
45
|
+
if line.startswith("|---") or line.startswith("| Asset"):
|
|
46
|
+
continue
|
|
47
|
+
m = ROW_RE.match(line)
|
|
48
|
+
if not m:
|
|
49
|
+
continue
|
|
50
|
+
rows.append(
|
|
51
|
+
{
|
|
52
|
+
"name": m["name"].strip(),
|
|
53
|
+
"path": m["path"].strip(),
|
|
54
|
+
"keywords": m["keywords"].strip(),
|
|
55
|
+
"desc": m["desc"].strip(),
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return rows
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_external(path: str) -> bool:
|
|
62
|
+
p = path.lower()
|
|
63
|
+
return (
|
|
64
|
+
p.startswith("upstream:")
|
|
65
|
+
or p.startswith("http://")
|
|
66
|
+
or p.startswith("https://")
|
|
67
|
+
or p.startswith("tbd")
|
|
68
|
+
or "github.com" in p
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def resolve(path: str) -> Path | None:
|
|
73
|
+
if is_external(path):
|
|
74
|
+
return None
|
|
75
|
+
cleaned = path.strip().lstrip("`").rstrip("`")
|
|
76
|
+
cleaned = cleaned.split(")")[0].lstrip("[(")
|
|
77
|
+
candidate = (REPO_ROOT / cleaned).resolve()
|
|
78
|
+
return candidate
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check_row(row: dict[str, str]) -> list[str]:
|
|
82
|
+
errs: list[str] = []
|
|
83
|
+
if is_external(row["path"]):
|
|
84
|
+
return errs
|
|
85
|
+
target = resolve(row["path"])
|
|
86
|
+
if target is None or not target.exists():
|
|
87
|
+
errs.append(f"[{row['name']}] target missing: {row['path']}")
|
|
88
|
+
return errs
|
|
89
|
+
body = target.read_text(encoding="utf-8", errors="replace").lower()
|
|
90
|
+
for kw in KW_RE.findall(row["keywords"]):
|
|
91
|
+
kw_lc = kw.strip().lower()
|
|
92
|
+
if not kw_lc:
|
|
93
|
+
continue
|
|
94
|
+
if kw_lc not in body:
|
|
95
|
+
errs.append(
|
|
96
|
+
f"[{row['name']}] trigger keyword '{kw}' not found in "
|
|
97
|
+
f"{row['path']} — catalog row may be stale"
|
|
98
|
+
)
|
|
99
|
+
return errs
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main() -> int:
|
|
103
|
+
if not SKILL.exists():
|
|
104
|
+
print(f"ERROR: token-optimizer skill not found at {SKILL}", file=sys.stderr)
|
|
105
|
+
return 1
|
|
106
|
+
text = SKILL.read_text(encoding="utf-8")
|
|
107
|
+
rows = parse_catalog(text)
|
|
108
|
+
if not rows:
|
|
109
|
+
print(
|
|
110
|
+
"ERROR: token-optimizer SKILL.md has no parseable catalog rows",
|
|
111
|
+
file=sys.stderr,
|
|
112
|
+
)
|
|
113
|
+
return 1
|
|
114
|
+
all_errs: list[str] = []
|
|
115
|
+
checked = 0
|
|
116
|
+
for row in rows:
|
|
117
|
+
errs = check_row(row)
|
|
118
|
+
all_errs.extend(errs)
|
|
119
|
+
if not is_external(row["path"]):
|
|
120
|
+
checked += 1
|
|
121
|
+
print(
|
|
122
|
+
f"token-optimizer freshness: {len(rows)} catalog rows, "
|
|
123
|
+
f"{checked} internal targets checked, {len(all_errs)} drift signal(s)"
|
|
124
|
+
)
|
|
125
|
+
for e in all_errs:
|
|
126
|
+
print(f" FAIL {e}")
|
|
127
|
+
return 1 if all_errs else 0
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
if __name__ == "__main__":
|
|
131
|
+
sys.exit(main())
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Compile rule frontmatter into ``router.json``.
|
|
3
|
+
|
|
4
|
+
Reads ``.agent-src.uncompressed/rules/*.md``; produces deterministic JSON
|
|
5
|
+
mapping kernel + tier-1 + tier-2 rules to their triggers and routed
|
|
6
|
+
artifacts, per ``docs/contracts/rule-router.md``.
|
|
7
|
+
|
|
8
|
+
Stdlib-only, deterministic (sorted keys + sorted lists), idempotent.
|
|
9
|
+
Wired into ``task generate-tools`` after the compress step.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
18
|
+
RULES_DIR = ROOT / ".agent-src.uncompressed" / "rules"
|
|
19
|
+
OUT_PATH = ROOT / "router.json"
|
|
20
|
+
SCHEMA_VERSION = 1
|
|
21
|
+
|
|
22
|
+
# Maps legacy tier values to the router-canonical names. See
|
|
23
|
+
# docs/contracts/rule-router.md § Backward compatibility.
|
|
24
|
+
LEGACY_TIER_MAP = {
|
|
25
|
+
"1": "tier-1",
|
|
26
|
+
"2": "tier-2",
|
|
27
|
+
"2a": "tier-2",
|
|
28
|
+
"3": "tier-1",
|
|
29
|
+
"mechanical-already": "tier-1",
|
|
30
|
+
"kernel": "kernel",
|
|
31
|
+
"tier-1": "tier-1",
|
|
32
|
+
"tier-2": "tier-2",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ALLOWED_TIERS = {"kernel", "tier-1", "tier-2"}
|
|
36
|
+
ALLOWED_PROFILES = {"minimal", "balanced", "full"}
|
|
37
|
+
ALLOWED_TRIGGER_KEYS = {"keyword", "phrase", "intent", "file_pattern",
|
|
38
|
+
"path_prefix", "command"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_frontmatter(text: str) -> dict:
|
|
42
|
+
if not text.startswith("---\n"):
|
|
43
|
+
return {}
|
|
44
|
+
end = text.find("\n---", 4)
|
|
45
|
+
if end < 0:
|
|
46
|
+
return {}
|
|
47
|
+
block = text[4:end]
|
|
48
|
+
try:
|
|
49
|
+
import yaml # type: ignore
|
|
50
|
+
data = yaml.safe_load(block) or {}
|
|
51
|
+
return data if isinstance(data, dict) else {}
|
|
52
|
+
except ImportError:
|
|
53
|
+
return _parse_frontmatter_minimal(block)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _parse_frontmatter_minimal(block: str) -> dict:
|
|
57
|
+
"""Minimal YAML parser fallback (flat scalars + simple lists)."""
|
|
58
|
+
out: dict = {}
|
|
59
|
+
cur_key = None
|
|
60
|
+
for raw in block.splitlines():
|
|
61
|
+
line = raw.rstrip()
|
|
62
|
+
if not line or line.lstrip().startswith("#"):
|
|
63
|
+
continue
|
|
64
|
+
if line.startswith(" - ") and cur_key:
|
|
65
|
+
out.setdefault(cur_key, []).append(line[4:].strip())
|
|
66
|
+
elif ":" in line and not line.startswith(" "):
|
|
67
|
+
k, _, v = line.partition(":")
|
|
68
|
+
cur_key = k.strip()
|
|
69
|
+
v = v.strip()
|
|
70
|
+
if v in ("", "[]"):
|
|
71
|
+
out[cur_key] = [] if v == "[]" else None
|
|
72
|
+
else:
|
|
73
|
+
out[cur_key] = v.strip('"').strip("'")
|
|
74
|
+
return out
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_tier(rule_type: str, raw_tier: str) -> str:
|
|
78
|
+
if rule_type == "always":
|
|
79
|
+
return "kernel"
|
|
80
|
+
return LEGACY_TIER_MAP.get(str(raw_tier), "tier-2")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _normalize_trigger(item) -> dict | None:
|
|
84
|
+
if not isinstance(item, dict):
|
|
85
|
+
return None
|
|
86
|
+
keys = [k for k in item if k in ALLOWED_TRIGGER_KEYS]
|
|
87
|
+
if len(keys) != 1:
|
|
88
|
+
return None
|
|
89
|
+
return {keys[0]: str(item[keys[0]])}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _collect(rules_dir: Path) -> dict:
|
|
93
|
+
kernel: list[str] = []
|
|
94
|
+
tiered: dict[str, list[dict]] = {"tier-1": [], "tier-2": []}
|
|
95
|
+
for path in sorted(rules_dir.glob("*.md")):
|
|
96
|
+
fm = _parse_frontmatter(path.read_text(encoding="utf-8"))
|
|
97
|
+
if not fm:
|
|
98
|
+
continue
|
|
99
|
+
rule_id = path.stem
|
|
100
|
+
rule_type = str(fm.get("type", "auto"))
|
|
101
|
+
tier = _resolve_tier(rule_type, fm.get("tier", ""))
|
|
102
|
+
if tier not in ALLOWED_TIERS:
|
|
103
|
+
continue
|
|
104
|
+
if tier == "kernel":
|
|
105
|
+
kernel.append(rule_id)
|
|
106
|
+
continue
|
|
107
|
+
triggers_raw = fm.get("triggers") or []
|
|
108
|
+
triggers = [t for t in (_normalize_trigger(x) for x in triggers_raw) if t]
|
|
109
|
+
routes_to = sorted(str(x) for x in (fm.get("routes_to") or []))
|
|
110
|
+
entry = {"id": rule_id, "triggers": triggers, "routes_to": routes_to}
|
|
111
|
+
tiered[tier].append(entry)
|
|
112
|
+
for k in tiered:
|
|
113
|
+
tiered[k].sort(key=lambda x: x["id"])
|
|
114
|
+
return {"kernel": sorted(kernel), **{k.replace("-", "_"): v for k, v in tiered.items()}}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def build() -> dict:
|
|
118
|
+
collected = _collect(RULES_DIR)
|
|
119
|
+
return {
|
|
120
|
+
"schema_version": SCHEMA_VERSION,
|
|
121
|
+
"kernel": collected["kernel"],
|
|
122
|
+
"tier_1": collected["tier_1"],
|
|
123
|
+
"tier_2": collected["tier_2"],
|
|
124
|
+
"profiles": {
|
|
125
|
+
"minimal": ["__kernel__"],
|
|
126
|
+
"balanced": ["__kernel__", "__tier_1__"],
|
|
127
|
+
"full": ["__kernel__", "__tier_1__", "__tier_2__"],
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main(argv: list[str]) -> int:
|
|
133
|
+
out = build()
|
|
134
|
+
text = json.dumps(out, indent=2, sort_keys=False) + "\n"
|
|
135
|
+
if "--check" in argv:
|
|
136
|
+
if not OUT_PATH.exists() or OUT_PATH.read_text(encoding="utf-8") != text:
|
|
137
|
+
print("router.json out of date — run scripts/compile_router.py", file=sys.stderr)
|
|
138
|
+
return 1
|
|
139
|
+
print("✅ router.json is up to date")
|
|
140
|
+
return 0
|
|
141
|
+
OUT_PATH.write_text(text, encoding="utf-8")
|
|
142
|
+
counts = (len(out["kernel"]), len(out["tier_1"]), len(out["tier_2"]))
|
|
143
|
+
print(f"✅ router.json — kernel={counts[0]} tier-1={counts[1]} tier-2={counts[2]}")
|
|
144
|
+
return 0
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
sys.exit(main(sys.argv[1:]))
|