@event4u/agent-config 3.0.0 → 3.1.1
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/install-via-agent.md +129 -0
- package/.agent-src/commands/video/from-script.md +1 -1
- package/.agent-src/commands/video.md +1 -1
- package/.agent-src/contexts/execution/cheap-question-mechanics.md +81 -0
- package/.agent-src/rules/caveman-speak.md +2 -2
- package/.agent-src/rules/context-hygiene.md +36 -0
- package/.agent-src/rules/engineering-safety-floor.md +102 -0
- package/.agent-src/rules/finance-safety-floor.md +114 -0
- package/.agent-src/rules/git-history-discipline.md +1 -1
- package/.agent-src/rules/no-cheap-questions.md +34 -32
- package/.agent-src/rules/provider-lifecycle-discipline.md +4 -4
- package/.agent-src/rules/strategy-safety-floor.md +114 -0
- package/.agent-src/skills/agents-md-thin-root/SKILL.md +15 -9
- package/.agent-src/skills/async-python-patterns/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -1
- package/.agent-src/skills/readme-reviewer/SKILL.md +52 -3
- package/.agent-src/skills/readme-writing/SKILL.md +52 -4
- package/.agent-src/skills/readme-writing-package/SKILL.md +48 -5
- package/.agent-src/skills/systematic-debugging/SKILL.md +41 -0
- package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/.agent-src/templates/hooks/pre-commit-frontmatter +66 -0
- package/.agent-src/templates/hooks/pre-commit-roadmap-progress +78 -39
- package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +4 -1
- package/.agent-src/templates/scripts/work_engine/orchestration.py +25 -11
- package/.claude-plugin/marketplace.json +2 -1
- package/AGENTS.md +10 -8
- package/CHANGELOG.md +233 -123
- package/README.md +165 -553
- package/config/agent-settings.template.yml +0 -7
- package/config/discovery/packs.yml +20 -0
- package/config/discovery/unassigned-artefacts.yml +2 -0
- package/config/gitignore-block.txt +19 -3
- package/dist/cli/commands/uiServe.js +13 -4
- package/dist/cli/commands/uiServe.js.map +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +7 -0
- package/dist/discovery/discovery-manifest.json +2107 -1409
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +9 -9
- package/dist/discovery/orphan-report.md +10 -0
- package/dist/discovery/packs.json +1002 -0
- package/dist/discovery/trust-report.md +26 -0
- package/dist/discovery/workspaces.json +705 -0
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1623 -0
- package/dist/server/app.js +11 -3
- package/dist/server/app.js.map +1 -1
- package/dist/server/io/atomicMultiWrite.js +3 -1
- package/dist/server/io/atomicMultiWrite.js.map +1 -1
- package/dist/server/io/yamlIO.js +22 -0
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/ping.js +8 -0
- package/dist/server/routes/ping.js.map +1 -1
- package/dist/server/routes/schema.js +2 -2
- package/dist/server/routes/schema.js.map +1 -1
- package/dist/server/routes/settings.js +104 -23
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/userMd.js +37 -27
- package/dist/server/routes/userMd.js.map +1 -1
- package/dist/server/routes/wizard.js +256 -20
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/server/schemas/settings.js +0 -1
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/server/token.js +10 -3
- package/dist/server/token.js.map +1 -1
- package/dist/server/writeRoot.js +28 -11
- package/dist/server/writeRoot.js.map +1 -1
- package/dist/server/writeRoot.test.js +22 -4
- package/dist/server/writeRoot.test.js.map +1 -1
- package/dist/shared/userMd/formAdapter.js +29 -51
- package/dist/shared/userMd/formAdapter.js.map +1 -1
- package/dist/shared/userMd/schema.js +32 -104
- package/dist/shared/userMd/schema.js.map +1 -1
- package/dist/shared/userMd/utils.js +64 -50
- package/dist/shared/userMd/utils.js.map +1 -1
- package/dist/ui/assets/index-D-DY1ywI.js +35 -0
- package/dist/ui/assets/index-D-DY1ywI.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/adrs/router/0001-three-tier-routing.md +5 -5
- package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +1 -1
- package/docs/architecture.md +3 -3
- package/docs/archive/CHANGELOG-pre-3.1.0.md +167 -0
- package/docs/catalog.md +30 -26
- package/docs/contracts/CHANGELOG-conventions.md +1 -1
- package/docs/contracts/agent-user-schema.md +6 -9
- package/docs/contracts/consumer-bridge.md +79 -0
- package/docs/contracts/discovery-manifest.md +209 -0
- package/docs/contracts/discovery-manifest.schema.json +77 -4
- package/docs/contracts/explain-trace.schema.json +1 -1
- package/docs/contracts/file-ownership-matrix.json +197 -13
- package/docs/contracts/frontmatter-contract.md +140 -0
- package/docs/contracts/gui-wizard.md +223 -0
- package/docs/contracts/installer-agent-mode.md +137 -0
- package/docs/contracts/kernel-membership.md +1 -1
- package/docs/contracts/mcp-tool-inventory.md +9 -9
- package/docs/contracts/namespace.md +6 -6
- package/docs/contracts/provider-lifecycle.md +5 -5
- package/docs/contracts/rule-router.md +4 -4
- package/docs/contracts/settings-api.md +53 -6
- package/docs/contracts/smoke-contracts.md +3 -3
- package/docs/contracts/trust-and-safety.md +144 -0
- package/docs/customization.md +2 -2
- package/docs/decisions/ADR-007-agent-discovery-scopes.md +12 -0
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +24 -0
- package/docs/decisions/ADR-015-discovery-manifest-contract.md +146 -0
- package/docs/decisions/ADR-016-installer-architecture.md +189 -0
- package/docs/decisions/ADR-017-monorepo-physical-layout.md +261 -0
- package/docs/decisions/ADR-018-trust-and-safety-layer.md +159 -0
- package/docs/decisions/ADR-019-router-json-dist-location.md +124 -0
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +123 -0
- package/docs/decisions/ADR-021-deployment-shape.md +153 -0
- package/docs/decisions/INDEX.md +7 -0
- package/docs/deploy/connector-setup.md +129 -0
- package/docs/deploy/env-vars.md +70 -0
- package/docs/deploy/policy-cookbook.md +130 -0
- package/docs/deploy/quickstart.md +112 -0
- package/docs/distribution/public-install-smoke.md +68 -0
- package/docs/distribution/registries.md +55 -0
- package/docs/distribution/telemetry-privacy.md +128 -0
- package/docs/distribution/telemetry-schema.md +174 -0
- package/docs/featured-skills.md +95 -0
- package/docs/getting-started-by-role.md +19 -1
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/installed-tools-manifest.md +11 -8
- package/docs/guidelines/docs/readme-size-and-splitting.md +53 -1
- package/docs/installation.md +27 -14
- package/docs/maintainers/dev-mode.md +105 -0
- package/docs/setup/per-ide/claude-desktop.md +3 -2
- package/docs/wizard.md +39 -4
- package/package.json +18 -1
- package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/scripts/_cli/cmd_doctor.py +150 -2
- package/scripts/_cli/cmd_explain.py +2 -1
- package/scripts/_cli/cmd_migrate_to_global.py +415 -0
- package/scripts/_cli/cmd_settings_migrate.py +146 -0
- package/scripts/_cli/explain_last/route.py +2 -1
- package/scripts/_dispatch.bash +36 -3
- package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
- package/scripts/_lib/agent_settings.py +4 -1
- package/scripts/_lib/agent_src.py +157 -0
- package/scripts/agent-config +17 -6
- package/scripts/audit_skill_descriptions.py +18 -6
- package/scripts/build_discovery_manifest.py +373 -17
- package/scripts/check_artefact_checksums.py +104 -0
- package/scripts/check_cluster_patterns.py +20 -4
- package/scripts/check_command_count_messaging.py +33 -14
- package/scripts/check_council_references.py +43 -4
- package/scripts/check_overlay_cascade_subdirs.py +7 -3
- package/scripts/check_references.py +5 -2
- package/scripts/check_reply_consistency.py +32 -9
- package/scripts/check_template_pin_drift.py +24 -7
- package/scripts/check_token_optimizer_freshness.py +18 -3
- package/scripts/compile_router.py +34 -2
- package/scripts/compress.py +162 -44
- package/scripts/config/presets.py +19 -1
- package/scripts/config/profiles.py +16 -1
- package/scripts/discovery_stats.py +70 -0
- package/scripts/expected_perms.json +47 -0
- package/scripts/generate_index.py +78 -46
- package/scripts/generate_ownership_matrix.py +98 -43
- package/scripts/generate_pack_manifests.py +183 -0
- package/scripts/install +18 -1
- package/scripts/install.py +934 -59
- package/scripts/install.sh +27 -9
- package/scripts/lint_agents_layout.py +93 -13
- package/scripts/lint_agents_md.py +1 -1
- package/scripts/lint_archived_skills.py +32 -16
- package/scripts/lint_bench_corpus.py +14 -2
- package/scripts/lint_command_tiers.py +15 -2
- package/scripts/lint_featured_skills.py +139 -0
- package/scripts/lint_framework_leakage.py +33 -6
- package/scripts/lint_global_paths.py +147 -0
- package/scripts/lint_orchestration_dsl.py +6 -3
- package/scripts/lint_pack_boundaries.py +147 -0
- package/scripts/lint_pack_first_win.py +103 -0
- package/scripts/lint_readme_jargon.py +131 -0
- package/scripts/lint_readme_size.py +33 -0
- package/scripts/lint_rule_interactions.py +23 -5
- package/scripts/lint_rule_tiers.py +12 -3
- package/scripts/lint_trust_coherence.py +212 -0
- package/scripts/measure_rule_budget.py +22 -4
- package/scripts/move_artefact.py +143 -0
- package/scripts/new_skill.py +148 -0
- package/scripts/plan_physical_move.py +353 -0
- package/scripts/refine_ticket_detect.py +30 -7
- package/scripts/release.py +22 -2
- package/scripts/schemas/command.schema.json +4 -0
- package/scripts/skill_linter.py +248 -118
- package/scripts/skill_trigger_eval.py +28 -8
- package/scripts/smoke/kernel.sh +1 -1
- package/scripts/smoke/router.sh +24 -5
- package/scripts/smoke/skills.sh +15 -7
- package/scripts/smoke_quickstart.py +11 -2
- package/scripts/snapshot_agent_outputs.py +144 -0
- package/scripts/update_counts.py +45 -17
- package/scripts/validate_decision_engine.py +9 -1
- package/scripts/validate_discovery_manifest.py +94 -0
- package/scripts/validate_frontmatter.py +39 -20
- package/scripts/verify_physical_move.py +185 -0
- package/templates/agent-user.md +0 -1
- package/templates/agent-user.yml +21 -0
- package/templates/minimal/agents-overrides-readme.md +46 -0
- package/templates/minimal/overrides-gitkeep +2 -0
- package/dist/ui/assets/index-BTRcKDlB.js +0 -39
- package/dist/ui/assets/index-BTRcKDlB.js.map +0 -1
- package/templates/minimal/agents-gitkeep +0 -2
|
@@ -18,13 +18,13 @@ Patterns checked (per file):
|
|
|
18
18
|
|
|
19
19
|
README.md
|
|
20
20
|
hero badge "/badge/Commands-{N}-…" → active
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
tools blurb "{N} native commands" → active
|
|
21
|
+
(Prose phrasings "Browse all {N} active commands" and
|
|
22
|
+
"{N} native commands" were retired in the modernized
|
|
23
|
+
README — the badge alone now carries the count.)
|
|
25
24
|
|
|
26
25
|
AGENTS.md
|
|
27
26
|
tree "commands/ ({N} files — {A} active + {S} deprecation shims)"
|
|
27
|
+
(Thin-Root: only checked when a `commands/` tree block exists.)
|
|
28
28
|
|
|
29
29
|
docs/getting-started.md
|
|
30
30
|
browse line "Browse all {N} active commands" → active
|
|
@@ -38,10 +38,12 @@ import re
|
|
|
38
38
|
import sys
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
42
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
43
|
+
|
|
41
44
|
QUIET = "--quiet" in sys.argv
|
|
42
45
|
|
|
43
46
|
ROOT = Path(__file__).resolve().parent.parent
|
|
44
|
-
COMMANDS_DIR = ROOT / ".agent-src.uncompressed" / "commands"
|
|
45
47
|
README = ROOT / "README.md"
|
|
46
48
|
AGENTS = ROOT / "AGENTS.md"
|
|
47
49
|
GETTING_STARTED = ROOT / "docs" / "getting-started.md"
|
|
@@ -50,14 +52,33 @@ FM_RE = re.compile(r"^---\s*\n(.*?)\n---", re.DOTALL)
|
|
|
50
52
|
SUPERSEDED_RE = re.compile(r"^superseded_by:\s*\S", re.MULTILINE)
|
|
51
53
|
|
|
52
54
|
|
|
55
|
+
def _command_files() -> list[Path]:
|
|
56
|
+
"""Every command ``*.md`` file across all source roots (legacy + packages/*).
|
|
57
|
+
|
|
58
|
+
Multi-root aware per ADR-017: post-move the commands live under
|
|
59
|
+
``packages/<pack>/.agent-src.uncompressed/commands/``, and the
|
|
60
|
+
canonical count is the union across packs (deduped by logical path).
|
|
61
|
+
"""
|
|
62
|
+
seen: dict[str, Path] = {}
|
|
63
|
+
for root in artefact_roots():
|
|
64
|
+
cmd_dir = root / "commands"
|
|
65
|
+
if not cmd_dir.is_dir():
|
|
66
|
+
continue
|
|
67
|
+
for f in cmd_dir.rglob("*.md"):
|
|
68
|
+
if f.name == "AGENTS.md":
|
|
69
|
+
continue
|
|
70
|
+
rel = f.relative_to(cmd_dir).as_posix()
|
|
71
|
+
seen.setdefault(rel, f)
|
|
72
|
+
return sorted(seen.values())
|
|
73
|
+
|
|
74
|
+
|
|
53
75
|
def canonical_counts() -> tuple[int, int, int]:
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
files = _command_files()
|
|
77
|
+
if not files:
|
|
78
|
+
print("❌ no commands/ directory found under any artefact root", file=sys.stderr)
|
|
56
79
|
sys.exit(1)
|
|
57
80
|
total = shims = 0
|
|
58
|
-
for f in
|
|
59
|
-
if f.name == "AGENTS.md":
|
|
60
|
-
continue
|
|
81
|
+
for f in files:
|
|
61
82
|
total += 1
|
|
62
83
|
m = FM_RE.match(f.read_text(encoding="utf-8"))
|
|
63
84
|
fm = m.group(1) if m else ""
|
|
@@ -83,11 +104,9 @@ def main() -> int:
|
|
|
83
104
|
print(f"Canonical counts: {total} files · {shims} shims · {active} active")
|
|
84
105
|
|
|
85
106
|
checks = [
|
|
86
|
-
# README.md
|
|
107
|
+
# README.md — modernized: badge is the sole count surface
|
|
87
108
|
(README, r"/badge/Commands-(\d+)-", active, "hero badge"),
|
|
88
|
-
|
|
89
|
-
(README, r"\+ (\d+) native commands\)", active, "tools blurb"),
|
|
90
|
-
# docs/getting-started.md
|
|
109
|
+
# docs/getting-started.md — still carries the prose browse line
|
|
91
110
|
(GETTING_STARTED, r"Browse all (\d+) active commands", active, "browse line"),
|
|
92
111
|
]
|
|
93
112
|
# Shim-specific messaging only applies during a deprecation window.
|
|
@@ -39,6 +39,8 @@ from typing import Iterable
|
|
|
39
39
|
QUIET = "--quiet" in sys.argv
|
|
40
40
|
|
|
41
41
|
ROOT = Path(".")
|
|
42
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
43
|
+
from _lib.agent_src import artefact_roots, strip_source_prefix # noqa: E402
|
|
42
44
|
|
|
43
45
|
# A specific file inside a council dir: must end with .md or .json,
|
|
44
46
|
# must NOT contain `<` or `>` (placeholders), must NOT contain backticks
|
|
@@ -50,8 +52,11 @@ PATTERN = re.compile(
|
|
|
50
52
|
|
|
51
53
|
# Only these durable surfaces are scanned. Archive, analysis, and the
|
|
52
54
|
# council dirs themselves are excluded by design.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
#
|
|
56
|
+
# Source roots (legacy `.agent-src.uncompressed/` and every
|
|
57
|
+
# `packages/*/.agent-src.uncompressed/`) are discovered at runtime via
|
|
58
|
+
# `artefact_roots()` so the linter follows the monorepo physical layout.
|
|
59
|
+
FIXED_SCAN_ROOTS = (
|
|
55
60
|
"agents/roadmaps",
|
|
56
61
|
"agents/settings/contexts",
|
|
57
62
|
"agents/reference/docs",
|
|
@@ -59,6 +64,23 @@ SCAN_ROOTS = (
|
|
|
59
64
|
"docs/decisions",
|
|
60
65
|
"docs/guidelines",
|
|
61
66
|
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _scan_roots() -> tuple[str, ...]:
|
|
70
|
+
cwd = Path(".").resolve()
|
|
71
|
+
roots: list[str] = []
|
|
72
|
+
for r in artefact_roots():
|
|
73
|
+
try:
|
|
74
|
+
roots.append(r.relative_to(cwd).as_posix() if r.is_absolute() else r.as_posix())
|
|
75
|
+
except ValueError:
|
|
76
|
+
# Root lives outside the current working directory (e.g. tests
|
|
77
|
+
# chdir into a tmp tree). Skip — the test isolates its own
|
|
78
|
+
# source tree.
|
|
79
|
+
continue
|
|
80
|
+
roots.extend(FIXED_SCAN_ROOTS)
|
|
81
|
+
return tuple(roots)
|
|
82
|
+
|
|
83
|
+
|
|
62
84
|
SCAN_EXTS = (".md", ".yml", ".yaml", ".json", ".py")
|
|
63
85
|
|
|
64
86
|
# Files (or directory prefixes) that legitimately document the output
|
|
@@ -111,9 +133,26 @@ STRUCTURAL_CARVEOUTS: tuple[tuple[re.Pattern[str], re.Pattern[str]], ...] = (
|
|
|
111
133
|
|
|
112
134
|
|
|
113
135
|
def _is_allowlisted(rel: str) -> bool:
|
|
136
|
+
"""Match a repo-relative POSIX path against the allowlist.
|
|
137
|
+
|
|
138
|
+
Allowlist prefixes are written against the legacy
|
|
139
|
+
``.agent-src.uncompressed/`` layout. A physical hit under
|
|
140
|
+
``packages/*/.agent-src.uncompressed/`` is normalised to the same
|
|
141
|
+
logical path before matching so entries keep covering relocated files.
|
|
142
|
+
"""
|
|
114
143
|
if rel in ALLOWLIST_FILES:
|
|
115
144
|
return True
|
|
116
|
-
|
|
145
|
+
if any(rel.startswith(prefix) for prefix in ALLOWLIST_PREFIXES):
|
|
146
|
+
return True
|
|
147
|
+
logical = strip_source_prefix(rel)
|
|
148
|
+
if logical is not None:
|
|
149
|
+
canon = f"{_LEGACY_PREFIX_STR}{logical}"
|
|
150
|
+
if any(canon.startswith(prefix) for prefix in ALLOWLIST_PREFIXES):
|
|
151
|
+
return True
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
_LEGACY_PREFIX_STR = ".agent-src.uncompressed/"
|
|
117
156
|
|
|
118
157
|
|
|
119
158
|
def _is_structurally_allowed(source_rel: str, target_capture: str) -> bool:
|
|
@@ -156,7 +195,7 @@ def _iter_files(roots: Iterable[str]) -> Iterable[Path]:
|
|
|
156
195
|
|
|
157
196
|
def main() -> int:
|
|
158
197
|
violations: list[tuple[Path, int, str]] = []
|
|
159
|
-
for path in _iter_files(
|
|
198
|
+
for path in _iter_files(_scan_roots()):
|
|
160
199
|
rel = path.as_posix()
|
|
161
200
|
if _is_allowlisted(rel):
|
|
162
201
|
continue
|
|
@@ -30,10 +30,14 @@ from scripts._lib.agents_overlay import ( # noqa: E402
|
|
|
30
30
|
|
|
31
31
|
DOCS_PATH = REPO_ROOT / "docs" / "customization.md"
|
|
32
32
|
|
|
33
|
-
# Match `agents
|
|
34
|
-
# the ✅/❌ markers in columns 2 and 3.
|
|
33
|
+
# Match `agents/<...>/<kind>/` in the first column of the overlay
|
|
34
|
+
# table, plus the ✅/❌ markers in columns 2 and 3. Captures only the
|
|
35
|
+
# **final** path segment as the kind, so both flat (`agents/overrides/`)
|
|
36
|
+
# and nested (`agents/settings/contexts/`) rows resolve to the bare
|
|
37
|
+
# kind name that ``CASCADE_ELIGIBLE_KINDS`` carries.
|
|
35
38
|
ROW_RE = re.compile(
|
|
36
|
-
r"^\|\s*`agents/([a-z][a-z0-9_-]
|
|
39
|
+
r"^\|\s*`agents/(?:[a-z][a-z0-9_-]*/)*([a-z][a-z0-9_-]*)/`\s*\|"
|
|
40
|
+
r"\s*(✅|❌)[^|]*\|\s*(✅|❌)[^|]*\|",
|
|
37
41
|
)
|
|
38
42
|
|
|
39
43
|
|
|
@@ -81,8 +81,10 @@ SKILL_REF_PATTERN = re.compile(r'`([\w-]+)`\s+skill')
|
|
|
81
81
|
RULE_REF_PATTERN = re.compile(r'`([\w-]+)`\s+rule')
|
|
82
82
|
|
|
83
83
|
# Unchecked TODO items (roadmap checkboxes) legitimately reference files
|
|
84
|
-
# and artifacts that do not exist yet. Skip these lines.
|
|
85
|
-
|
|
84
|
+
# and artifacts that do not exist yet. Skip these lines. `[~]` marks
|
|
85
|
+
# deferred work — same semantics as `[ ]` for reference resolution
|
|
86
|
+
# (forward-looking path, will materialize when the step ships).
|
|
87
|
+
UNCHECKED_TODO_PATTERN = re.compile(r'^\s*[-*+]\s+\[[ ~]\]\s')
|
|
86
88
|
_SKIP_NAMES = {"the", "a", "an", "this", "that", "your", "my", "no", "any", "each", "one",
|
|
87
89
|
"always", "auto", "fail", "vue", "guidelines", "naming",
|
|
88
90
|
"orderBy", "no-commit", "skill-linter", "skill-validator",
|
|
@@ -110,6 +112,7 @@ EXAMPLE_PATH_PATTERNS = [
|
|
|
110
112
|
re.compile(r"agents/learnings/"), # consumer-project learning notes
|
|
111
113
|
re.compile(r"agents/proposals/"), # consumer-project self-improvement proposals
|
|
112
114
|
re.compile(r"agents/drafts/"), # consumer-project artefact drafts
|
|
115
|
+
re.compile(r"agents/\.event4u-bridge\.yml"), # consumer-project bridge marker (ADR-020)
|
|
113
116
|
re.compile(r"guidelines/php-"), # flattened override naming convention
|
|
114
117
|
re.compile(r"rules/no-commit"), # example rule in commands
|
|
115
118
|
re.compile(r"skills/[\w-]+\.md"), # short skill refs in examples (not SKILL.md path)
|
|
@@ -21,6 +21,10 @@ import re
|
|
|
21
21
|
import sys
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
25
|
+
sys.path.insert(0, str(ROOT / "scripts"))
|
|
26
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
27
|
+
|
|
24
28
|
QUIET = "--quiet" in sys.argv
|
|
25
29
|
|
|
26
30
|
OPTION_LINE_RE = re.compile(r"^\s*>?\s*(\d+)\.\s+\S")
|
|
@@ -95,23 +99,42 @@ def validate(text: str, strict: bool = False) -> tuple[int, str]:
|
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
def cmd_scan_dir(root: Path) -> int:
|
|
102
|
+
# If the requested root is the legacy ".agent-src.uncompressed" and it
|
|
103
|
+
# no longer exists (post-monorepo-move), fall back to artefact_roots()
|
|
104
|
+
# so every packages/*/.agent-src.uncompressed/ is scanned.
|
|
98
105
|
if not root.is_dir():
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
legacy = ROOT / ".agent-src.uncompressed"
|
|
107
|
+
if root.resolve() == legacy.resolve():
|
|
108
|
+
roots = artefact_roots()
|
|
109
|
+
if not roots:
|
|
110
|
+
print("error: no artefact roots found (legacy or packages/*)", file=sys.stderr)
|
|
111
|
+
return 9
|
|
112
|
+
else:
|
|
113
|
+
print(f"error: not a directory: {root}", file=sys.stderr)
|
|
114
|
+
return 9
|
|
115
|
+
else:
|
|
116
|
+
roots = [root]
|
|
101
117
|
violations: list[tuple[Path, int, str]] = []
|
|
102
|
-
for
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
for r in roots:
|
|
119
|
+
for md in sorted(r.rglob("*.md")):
|
|
120
|
+
text = md.read_text(encoding="utf-8")
|
|
121
|
+
for idx, raw in enumerate(text.splitlines(), start=1):
|
|
122
|
+
stripped = _strip_codespans(raw)
|
|
123
|
+
if OPTION_LINE_RE.match(stripped) and TAG_RE.search(stripped):
|
|
124
|
+
violations.append((md, idx, raw.strip()))
|
|
108
125
|
if violations:
|
|
109
126
|
for path, line, snippet in violations:
|
|
110
127
|
print(f" 🔴 {path}:{line} — inline-tag — {snippet}", file=sys.stderr)
|
|
111
128
|
print(f"\n❌ {len(violations)} legacy-pattern violation(s)", file=sys.stderr)
|
|
112
129
|
return 6
|
|
113
130
|
if not QUIET:
|
|
114
|
-
|
|
131
|
+
def _rel(p: Path) -> str:
|
|
132
|
+
try:
|
|
133
|
+
return p.relative_to(ROOT).as_posix()
|
|
134
|
+
except ValueError:
|
|
135
|
+
return p.as_posix()
|
|
136
|
+
scanned = ", ".join(_rel(r) for r in roots)
|
|
137
|
+
print(f"✅ No legacy (recommended) tags found under {scanned}")
|
|
115
138
|
return 0
|
|
116
139
|
|
|
117
140
|
|
|
@@ -24,12 +24,26 @@ import sys
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
|
|
26
26
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
27
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
28
|
+
from _lib.agent_src import resolve_logical # noqa: E402
|
|
27
29
|
|
|
28
30
|
PACKAGE_JSON = REPO_ROOT / "package.json"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
31
|
+
|
|
32
|
+
# Source-of-truth template lives under whichever artefact root owns it
|
|
33
|
+
# (legacy .agent-src.uncompressed/ pre-move, packages/*/.agent-src.uncompressed/
|
|
34
|
+
# post-ADR-017). Compressed twin always lands at the flat .agent-src/ surface.
|
|
35
|
+
_TEMPLATE_LOGICAL = "templates/agents/agent-project-settings.example.yml"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _template_files() -> tuple[Path, ...]:
|
|
39
|
+
src = resolve_logical(_TEMPLATE_LOGICAL)
|
|
40
|
+
files: list[Path] = []
|
|
41
|
+
if src is not None:
|
|
42
|
+
files.append(src)
|
|
43
|
+
else:
|
|
44
|
+
files.append(REPO_ROOT / ".agent-src.uncompressed" / _TEMPLATE_LOGICAL)
|
|
45
|
+
files.append(REPO_ROOT / ".agent-src" / _TEMPLATE_LOGICAL)
|
|
46
|
+
return tuple(files)
|
|
33
47
|
PIN_LINE_RE = re.compile(r"^\s*agent_config_version\s*:\s*\"?([^\"\s#]*)\"?")
|
|
34
48
|
|
|
35
49
|
|
|
@@ -70,12 +84,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
70
84
|
return 1
|
|
71
85
|
|
|
72
86
|
failures: list[str] = []
|
|
73
|
-
for template in
|
|
87
|
+
for template in _template_files():
|
|
88
|
+
try:
|
|
89
|
+
rel = template.relative_to(REPO_ROOT)
|
|
90
|
+
except ValueError:
|
|
91
|
+
rel = template
|
|
74
92
|
if not template.is_file():
|
|
75
|
-
failures.append(f"missing template file: {
|
|
93
|
+
failures.append(f"missing template file: {rel}")
|
|
76
94
|
continue
|
|
77
95
|
pin = _read_template_pin(template)
|
|
78
|
-
rel = template.relative_to(REPO_ROOT)
|
|
79
96
|
if pin is None:
|
|
80
97
|
failures.append(f"{rel}: no `agent_config_version:` line found")
|
|
81
98
|
continue
|
|
@@ -21,7 +21,16 @@ import sys
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
23
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
24
|
-
|
|
24
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
25
|
+
from _lib.agent_src import resolve_logical # noqa: E402
|
|
26
|
+
|
|
27
|
+
# Post-ADR-017 the source-of-truth lives under whichever package owns
|
|
28
|
+
# the skill; resolve_logical() walks every artefact root.
|
|
29
|
+
SKILL = resolve_logical("skills/token-optimizer/SKILL.md") or (
|
|
30
|
+
REPO_ROOT / ".agent-src.uncompressed" / "skills" / "token-optimizer" / "SKILL.md"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from _lib.agent_src import strip_source_prefix # noqa: E402
|
|
25
34
|
|
|
26
35
|
# Catalog row pattern: | name | path | keywords | description |
|
|
27
36
|
ROW_RE = re.compile(
|
|
@@ -74,8 +83,14 @@ def resolve(path: str) -> Path | None:
|
|
|
74
83
|
return None
|
|
75
84
|
cleaned = path.strip().lstrip("`").rstrip("`")
|
|
76
85
|
cleaned = cleaned.split(")")[0].lstrip("[(")
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
# Catalog rows still cite the legacy .agent-src.uncompressed/ prefix
|
|
87
|
+
# for compactness; resolve those across every packages/* root.
|
|
88
|
+
logical = strip_source_prefix(cleaned)
|
|
89
|
+
if logical is not None:
|
|
90
|
+
hit = resolve_logical(logical)
|
|
91
|
+
if hit is not None:
|
|
92
|
+
return hit
|
|
93
|
+
return (REPO_ROOT / cleaned).resolve()
|
|
79
94
|
|
|
80
95
|
|
|
81
96
|
def check_row(row: dict[str, str]) -> list[str]:
|
|
@@ -15,8 +15,11 @@ import sys
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
17
|
ROOT = Path(__file__).resolve().parent.parent
|
|
18
|
+
# ADR-017: rules now live across multiple source roots. Legacy
|
|
19
|
+
# .agent-src.uncompressed/rules/ is kept as a fallback for the
|
|
20
|
+
# pure-compressed consumer projection.
|
|
18
21
|
RULES_DIR = ROOT / ".agent-src.uncompressed" / "rules"
|
|
19
|
-
OUT_PATH = ROOT / "router.json"
|
|
22
|
+
OUT_PATH = ROOT / "dist" / "router.json"
|
|
20
23
|
SETTINGS_PATH = ROOT / ".agent-settings.yml"
|
|
21
24
|
SCHEMA_VERSION = 1
|
|
22
25
|
|
|
@@ -116,11 +119,39 @@ def _load_settings() -> dict:
|
|
|
116
119
|
return load_agent_settings(project_path=SETTINGS_PATH)
|
|
117
120
|
|
|
118
121
|
|
|
122
|
+
def _iter_rule_files() -> list[Path]:
|
|
123
|
+
"""Walk every source root for rule files. First root wins per id."""
|
|
124
|
+
try:
|
|
125
|
+
from scripts._lib.agent_src import artefact_roots # type: ignore
|
|
126
|
+
except ImportError:
|
|
127
|
+
import sys as _sys
|
|
128
|
+
from pathlib import Path as _Path
|
|
129
|
+
_sys.path.insert(0, str(_Path(__file__).resolve().parent))
|
|
130
|
+
from _lib.agent_src import artefact_roots # type: ignore[import-not-found]
|
|
131
|
+
|
|
132
|
+
seen: dict[str, Path] = {}
|
|
133
|
+
roots = artefact_roots()
|
|
134
|
+
if not roots:
|
|
135
|
+
# Pure-compressed fallback for consumer projections that vendor
|
|
136
|
+
# the flat .agent-src/ tree without sources.
|
|
137
|
+
if RULES_DIR.exists():
|
|
138
|
+
for path in sorted(RULES_DIR.glob("*.md")):
|
|
139
|
+
seen.setdefault(path.stem, path)
|
|
140
|
+
else:
|
|
141
|
+
for src_root in roots:
|
|
142
|
+
rd = src_root / "rules"
|
|
143
|
+
if not rd.exists():
|
|
144
|
+
continue
|
|
145
|
+
for path in sorted(rd.glob("*.md")):
|
|
146
|
+
seen.setdefault(path.stem, path)
|
|
147
|
+
return [seen[k] for k in sorted(seen)]
|
|
148
|
+
|
|
149
|
+
|
|
119
150
|
def _collect(rules_dir: Path) -> dict:
|
|
120
151
|
settings = _load_settings()
|
|
121
152
|
kernel: list[str] = []
|
|
122
153
|
tiered: dict[str, list[dict]] = {"tier-1": [], "tier-2": []}
|
|
123
|
-
for path in
|
|
154
|
+
for path in _iter_rule_files():
|
|
124
155
|
fm = _parse_frontmatter(path.read_text(encoding="utf-8"))
|
|
125
156
|
if not fm:
|
|
126
157
|
continue
|
|
@@ -172,6 +203,7 @@ def main(argv: list[str]) -> int:
|
|
|
172
203
|
return 1
|
|
173
204
|
print("✅ router.json is up to date")
|
|
174
205
|
return 0
|
|
206
|
+
OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
175
207
|
OUT_PATH.write_text(text, encoding="utf-8")
|
|
176
208
|
counts = (len(out["kernel"]), len(out["tier_1"]), len(out["tier_2"]))
|
|
177
209
|
print(f"✅ router.json — kernel={counts[0]} tier-1={counts[1]} tier-2={counts[2]}")
|