@event4u/agent-config 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/.agent-src/commands/install-via-agent.md +129 -0
  2. package/.agent-src/commands/video/from-script.md +1 -1
  3. package/.agent-src/commands/video.md +1 -1
  4. package/.agent-src/contexts/execution/cheap-question-mechanics.md +81 -0
  5. package/.agent-src/rules/caveman-speak.md +2 -2
  6. package/.agent-src/rules/context-hygiene.md +36 -0
  7. package/.agent-src/rules/engineering-safety-floor.md +102 -0
  8. package/.agent-src/rules/finance-safety-floor.md +114 -0
  9. package/.agent-src/rules/git-history-discipline.md +1 -1
  10. package/.agent-src/rules/no-cheap-questions.md +34 -32
  11. package/.agent-src/rules/provider-lifecycle-discipline.md +4 -4
  12. package/.agent-src/rules/strategy-safety-floor.md +114 -0
  13. package/.agent-src/skills/agents-md-thin-root/SKILL.md +15 -9
  14. package/.agent-src/skills/async-python-patterns/SKILL.md +1 -1
  15. package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -1
  16. package/.agent-src/skills/readme-reviewer/SKILL.md +52 -3
  17. package/.agent-src/skills/readme-writing/SKILL.md +52 -4
  18. package/.agent-src/skills/readme-writing-package/SKILL.md +48 -5
  19. package/.agent-src/skills/systematic-debugging/SKILL.md +41 -0
  20. package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  21. package/.agent-src/templates/hooks/pre-commit-frontmatter +66 -0
  22. package/.agent-src/templates/hooks/pre-commit-roadmap-progress +78 -39
  23. package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +4 -1
  24. package/.agent-src/templates/scripts/work_engine/orchestration.py +25 -11
  25. package/.claude-plugin/marketplace.json +2 -1
  26. package/AGENTS.md +10 -8
  27. package/CHANGELOG.md +223 -125
  28. package/README.md +165 -553
  29. package/config/agent-settings.template.yml +0 -7
  30. package/config/discovery/packs.yml +20 -0
  31. package/config/discovery/unassigned-artefacts.yml +2 -0
  32. package/config/gitignore-block.txt +19 -3
  33. package/dist/cli/commands/uiServe.js +13 -4
  34. package/dist/cli/commands/uiServe.js.map +1 -1
  35. package/dist/cli/registry.js +2 -0
  36. package/dist/cli/registry.js.map +1 -1
  37. package/dist/discovery/deprecation-report.md +7 -0
  38. package/dist/discovery/discovery-manifest.json +2107 -1409
  39. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  40. package/dist/discovery/discovery-manifest.summary.md +9 -9
  41. package/dist/discovery/orphan-report.md +10 -0
  42. package/dist/discovery/packs.json +1002 -0
  43. package/dist/discovery/trust-report.md +26 -0
  44. package/dist/discovery/workspaces.json +705 -0
  45. package/dist/mcp/registry-manifest.json +4 -4
  46. package/dist/router.json +1623 -0
  47. package/dist/server/app.js +11 -3
  48. package/dist/server/app.js.map +1 -1
  49. package/dist/server/io/atomicMultiWrite.js +3 -1
  50. package/dist/server/io/atomicMultiWrite.js.map +1 -1
  51. package/dist/server/io/yamlIO.js +22 -0
  52. package/dist/server/io/yamlIO.js.map +1 -1
  53. package/dist/server/routes/ping.js +8 -0
  54. package/dist/server/routes/ping.js.map +1 -1
  55. package/dist/server/routes/schema.js +2 -2
  56. package/dist/server/routes/schema.js.map +1 -1
  57. package/dist/server/routes/settings.js +104 -23
  58. package/dist/server/routes/settings.js.map +1 -1
  59. package/dist/server/routes/userMd.js +37 -27
  60. package/dist/server/routes/userMd.js.map +1 -1
  61. package/dist/server/routes/wizard.js +256 -20
  62. package/dist/server/routes/wizard.js.map +1 -1
  63. package/dist/server/schemas/settings.js +0 -1
  64. package/dist/server/schemas/settings.js.map +1 -1
  65. package/dist/server/token.js +10 -3
  66. package/dist/server/token.js.map +1 -1
  67. package/dist/server/writeRoot.js +28 -11
  68. package/dist/server/writeRoot.js.map +1 -1
  69. package/dist/server/writeRoot.test.js +22 -4
  70. package/dist/server/writeRoot.test.js.map +1 -1
  71. package/dist/shared/userMd/formAdapter.js +29 -51
  72. package/dist/shared/userMd/formAdapter.js.map +1 -1
  73. package/dist/shared/userMd/schema.js +32 -104
  74. package/dist/shared/userMd/schema.js.map +1 -1
  75. package/dist/shared/userMd/utils.js +64 -50
  76. package/dist/shared/userMd/utils.js.map +1 -1
  77. package/dist/ui/assets/index-D-DY1ywI.js +35 -0
  78. package/dist/ui/assets/index-D-DY1ywI.js.map +1 -0
  79. package/dist/ui/index.html +1 -1
  80. package/docs/adrs/router/0001-three-tier-routing.md +5 -5
  81. package/docs/adrs/smoke/0001-per-tier-smoke-scripts.md +1 -1
  82. package/docs/architecture.md +3 -3
  83. package/docs/archive/CHANGELOG-pre-3.1.0.md +167 -0
  84. package/docs/catalog.md +30 -26
  85. package/docs/contracts/CHANGELOG-conventions.md +1 -1
  86. package/docs/contracts/agent-user-schema.md +6 -9
  87. package/docs/contracts/consumer-bridge.md +79 -0
  88. package/docs/contracts/discovery-manifest.md +209 -0
  89. package/docs/contracts/discovery-manifest.schema.json +77 -4
  90. package/docs/contracts/explain-trace.schema.json +1 -1
  91. package/docs/contracts/file-ownership-matrix.json +197 -13
  92. package/docs/contracts/frontmatter-contract.md +140 -0
  93. package/docs/contracts/gui-wizard.md +223 -0
  94. package/docs/contracts/installer-agent-mode.md +137 -0
  95. package/docs/contracts/kernel-membership.md +1 -1
  96. package/docs/contracts/mcp-tool-inventory.md +9 -9
  97. package/docs/contracts/namespace.md +6 -6
  98. package/docs/contracts/provider-lifecycle.md +5 -5
  99. package/docs/contracts/rule-router.md +4 -4
  100. package/docs/contracts/settings-api.md +53 -6
  101. package/docs/contracts/smoke-contracts.md +3 -3
  102. package/docs/contracts/trust-and-safety.md +144 -0
  103. package/docs/customization.md +2 -2
  104. package/docs/decisions/ADR-007-agent-discovery-scopes.md +12 -0
  105. package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +24 -0
  106. package/docs/decisions/ADR-015-discovery-manifest-contract.md +146 -0
  107. package/docs/decisions/ADR-016-installer-architecture.md +189 -0
  108. package/docs/decisions/ADR-017-monorepo-physical-layout.md +261 -0
  109. package/docs/decisions/ADR-018-trust-and-safety-layer.md +159 -0
  110. package/docs/decisions/ADR-019-router-json-dist-location.md +124 -0
  111. package/docs/decisions/ADR-020-global-only-consumer-scope.md +123 -0
  112. package/docs/decisions/ADR-021-deployment-shape.md +153 -0
  113. package/docs/decisions/INDEX.md +7 -0
  114. package/docs/deploy/connector-setup.md +129 -0
  115. package/docs/deploy/env-vars.md +70 -0
  116. package/docs/deploy/policy-cookbook.md +130 -0
  117. package/docs/deploy/quickstart.md +112 -0
  118. package/docs/distribution/public-install-smoke.md +68 -0
  119. package/docs/distribution/registries.md +55 -0
  120. package/docs/distribution/telemetry-privacy.md +128 -0
  121. package/docs/distribution/telemetry-schema.md +174 -0
  122. package/docs/featured-skills.md +95 -0
  123. package/docs/getting-started-by-role.md +19 -1
  124. package/docs/getting-started.md +2 -2
  125. package/docs/guidelines/agent-infra/installed-tools-manifest.md +11 -8
  126. package/docs/guidelines/docs/readme-size-and-splitting.md +53 -1
  127. package/docs/installation.md +27 -14
  128. package/docs/maintainers/dev-mode.md +105 -0
  129. package/docs/setup/per-ide/claude-desktop.md +3 -2
  130. package/docs/wizard.md +39 -4
  131. package/package.json +18 -1
  132. package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
  133. package/scripts/_cli/cmd_doctor.py +150 -2
  134. package/scripts/_cli/cmd_explain.py +2 -1
  135. package/scripts/_cli/cmd_migrate_to_global.py +415 -0
  136. package/scripts/_cli/cmd_settings_migrate.py +146 -0
  137. package/scripts/_cli/explain_last/route.py +2 -1
  138. package/scripts/_dispatch.bash +36 -3
  139. package/scripts/_lib/__pycache__/__init__.cpython-312.pyc +0 -0
  140. package/scripts/_lib/__pycache__/agent_src.cpython-312.pyc +0 -0
  141. package/scripts/_lib/agent_settings.py +4 -1
  142. package/scripts/_lib/agent_src.py +157 -0
  143. package/scripts/agent-config +17 -6
  144. package/scripts/audit_skill_descriptions.py +18 -6
  145. package/scripts/build_discovery_manifest.py +373 -17
  146. package/scripts/check_artefact_checksums.py +104 -0
  147. package/scripts/check_cluster_patterns.py +20 -4
  148. package/scripts/check_command_count_messaging.py +33 -14
  149. package/scripts/check_council_references.py +43 -4
  150. package/scripts/check_overlay_cascade_subdirs.py +7 -3
  151. package/scripts/check_references.py +5 -2
  152. package/scripts/check_reply_consistency.py +32 -9
  153. package/scripts/check_template_pin_drift.py +24 -7
  154. package/scripts/check_token_optimizer_freshness.py +18 -3
  155. package/scripts/compile_router.py +34 -2
  156. package/scripts/compress.py +162 -44
  157. package/scripts/config/presets.py +19 -1
  158. package/scripts/config/profiles.py +16 -1
  159. package/scripts/discovery_stats.py +70 -0
  160. package/scripts/expected_perms.json +47 -0
  161. package/scripts/generate_index.py +78 -46
  162. package/scripts/generate_ownership_matrix.py +98 -43
  163. package/scripts/generate_pack_manifests.py +183 -0
  164. package/scripts/install +18 -1
  165. package/scripts/install.py +934 -59
  166. package/scripts/install.sh +27 -9
  167. package/scripts/lint_agents_layout.py +93 -13
  168. package/scripts/lint_agents_md.py +1 -1
  169. package/scripts/lint_archived_skills.py +32 -16
  170. package/scripts/lint_bench_corpus.py +14 -2
  171. package/scripts/lint_command_tiers.py +15 -2
  172. package/scripts/lint_featured_skills.py +139 -0
  173. package/scripts/lint_framework_leakage.py +33 -6
  174. package/scripts/lint_global_paths.py +147 -0
  175. package/scripts/lint_orchestration_dsl.py +6 -3
  176. package/scripts/lint_pack_boundaries.py +147 -0
  177. package/scripts/lint_pack_first_win.py +103 -0
  178. package/scripts/lint_readme_jargon.py +131 -0
  179. package/scripts/lint_readme_size.py +33 -0
  180. package/scripts/lint_rule_interactions.py +23 -5
  181. package/scripts/lint_rule_tiers.py +12 -3
  182. package/scripts/lint_trust_coherence.py +212 -0
  183. package/scripts/measure_rule_budget.py +22 -4
  184. package/scripts/move_artefact.py +143 -0
  185. package/scripts/new_skill.py +148 -0
  186. package/scripts/plan_physical_move.py +353 -0
  187. package/scripts/refine_ticket_detect.py +30 -7
  188. package/scripts/schemas/command.schema.json +4 -0
  189. package/scripts/skill_linter.py +248 -118
  190. package/scripts/skill_trigger_eval.py +28 -8
  191. package/scripts/smoke/kernel.sh +1 -1
  192. package/scripts/smoke/router.sh +24 -5
  193. package/scripts/smoke/skills.sh +15 -7
  194. package/scripts/smoke_quickstart.py +11 -2
  195. package/scripts/snapshot_agent_outputs.py +144 -0
  196. package/scripts/update_counts.py +45 -17
  197. package/scripts/validate_decision_engine.py +9 -1
  198. package/scripts/validate_discovery_manifest.py +94 -0
  199. package/scripts/validate_frontmatter.py +39 -20
  200. package/scripts/verify_physical_move.py +185 -0
  201. package/templates/agent-user.md +0 -1
  202. package/templates/agent-user.yml +21 -0
  203. package/templates/minimal/agents-overrides-readme.md +46 -0
  204. package/templates/minimal/overrides-gitkeep +2 -0
  205. package/dist/ui/assets/index-BTRcKDlB.js +0 -39
  206. package/dist/ui/assets/index-BTRcKDlB.js.map +0 -1
  207. 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
- browse line "Browse all {N} active commands" active
22
- browse meta "{N} files total" total
23
- browse meta "{N} are deprecation shims" → shims
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
- if not COMMANDS_DIR.is_dir():
55
- print(f"❌ {COMMANDS_DIR.relative_to(ROOT)} not found", file=sys.stderr)
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 COMMANDS_DIR.rglob("*.md"):
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
- (README, r"Browse all (\d+) active commands", active, "browse line"),
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
- SCAN_ROOTS = (
54
- ".agent-src.uncompressed",
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
- return any(rel.startswith(prefix) for prefix in ALLOWLIST_PREFIXES)
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(SCAN_ROOTS):
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/<kind>/` in the first column of the overlay table, plus
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_-]*)/`\s*\|\s*(✅|❌)[^|]*\|\s*(✅|❌)[^|]*\|",
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
- UNCHECKED_TODO_PATTERN = re.compile(r'^\s*[-*+]\s+\[ \]\s')
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
- print(f"error: not a directory: {root}", file=sys.stderr)
100
- return 9
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 md in sorted(root.rglob("*.md")):
103
- text = md.read_text(encoding="utf-8")
104
- for idx, raw in enumerate(text.splitlines(), start=1):
105
- stripped = _strip_codespans(raw)
106
- if OPTION_LINE_RE.match(stripped) and TAG_RE.search(stripped):
107
- violations.append((md, idx, raw.strip()))
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
- print(f"✅ No legacy (recommended) tags found under {root}")
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
- TEMPLATE_FILES = (
30
- REPO_ROOT / ".agent-src.uncompressed" / "templates" / "agents" / "agent-project-settings.example.yml",
31
- REPO_ROOT / ".agent-src" / "templates" / "agents" / "agent-project-settings.example.yml",
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 TEMPLATE_FILES:
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: {template.relative_to(REPO_ROOT)}")
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
- SKILL = REPO_ROOT / ".agent-src.uncompressed" / "skills" / "token-optimizer" / "SKILL.md"
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
- candidate = (REPO_ROOT / cleaned).resolve()
78
- return candidate
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 sorted(rules_dir.glob("*.md")):
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]}")