@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
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+ """Plan + apply the physical monorepo migration (Phase 4).
3
+
4
+ Reads every `.md` artefact under `.agent-src.uncompressed/`, decides
5
+ its destination under `packages/core/` or `packages/pack-<id>/` using
6
+ the deterministic rules from
7
+ `agents/roadmaps/monorepo-phase-4-physical-package-layout.md` § Mapping
8
+ rules, and emits `dist/migration/move-plan.json`.
9
+
10
+ CLI:
11
+ --dry-run (default) emit the plan JSON only; no FS changes
12
+ --apply execute the moves via `git mv` (history-preserving)
13
+
14
+ Schema: see docs/contracts/move-plan.schema.json (added in this phase).
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ import yaml
26
+
27
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
28
+ from validate_frontmatter import parse_frontmatter # noqa: E402
29
+
30
+ ROOT = Path(__file__).resolve().parents[1]
31
+ SRC = ROOT / ".agent-src.uncompressed"
32
+ PACKAGES = ROOT / "packages"
33
+ CORE = PACKAGES / "core" / ".agent-src.uncompressed"
34
+ VOCAB_DIR = ROOT / "config" / "discovery"
35
+ PLAN_OUT = ROOT / "dist" / "migration" / "move-plan.json"
36
+ UNASSIGNED_YAML = VOCAB_DIR / "unassigned-artefacts.yml"
37
+
38
+ # Locked kernel — `docs/contracts/kernel-membership.md` § 4. Pinned to
39
+ # core regardless of frontmatter (sanity check, not duplication).
40
+ KERNEL_RULES = frozenset({
41
+ "agent-authority",
42
+ "ask-when-uncertain",
43
+ "commit-policy",
44
+ "direct-answers",
45
+ "language-and-tone",
46
+ "no-cheap-questions",
47
+ "non-destructive-by-default",
48
+ "scope-control",
49
+ "verify-before-complete",
50
+ "user-interrupt-priority", # admitted post-P2.2
51
+ })
52
+
53
+ # Non-frontmatter trees that follow the host package (core) by default.
54
+ # Scaffold templates, profiles, presets, contexts, user-types, scripts,
55
+ # ghostwriter, packs — none of these carry pack metadata.
56
+ CORE_DIRS = (
57
+ "templates",
58
+ "profiles",
59
+ "presets",
60
+ "contexts",
61
+ "user-types",
62
+ "scripts",
63
+ "ghostwriter",
64
+ "packs",
65
+ "personas",
66
+ )
67
+
68
+
69
+ def _load_pack_ids() -> set[str]:
70
+ packs = yaml.safe_load((VOCAB_DIR / "packs.yml").read_text(encoding="utf-8")) or []
71
+ return {p["id"] for p in packs}
72
+
73
+
74
+ def _load_unassigned() -> dict[str, str]:
75
+ raw = yaml.safe_load(UNASSIGNED_YAML.read_text(encoding="utf-8")) or []
76
+ return {e["path"]: e["reason"] for e in raw}
77
+
78
+
79
+ def _is_core(fm: dict[str, Any] | None, stem: str) -> bool:
80
+ if stem in KERNEL_RULES:
81
+ return True
82
+ if fm is None:
83
+ return False
84
+ trust = fm.get("trust") or {}
85
+ install = fm.get("install") or {}
86
+ return (
87
+ trust.get("level") == "core"
88
+ and install.get("removable") is False
89
+ )
90
+
91
+
92
+ def _primary_pack(fm: dict[str, Any] | None) -> str | None:
93
+ """Round-2 council refinement R1: explicit ``primary_pack:`` wins over
94
+ ``packs[0]`` fallback. The ``primary_pack`` lint lands in Phase 4.4.
95
+ """
96
+ if not fm:
97
+ return None
98
+ explicit = fm.get("primary_pack")
99
+ if isinstance(explicit, str) and explicit.strip():
100
+ return explicit.strip()
101
+ packs = fm.get("packs")
102
+ if not isinstance(packs, list) or not packs:
103
+ return None
104
+ return packs[0]
105
+
106
+
107
+ def _dest_for(src: Path, fm: dict[str, Any] | None, pack_ids: set[str]) -> tuple[Path, str, str | None]:
108
+ """Return (destination_path, reason, conflict_reason_or_None)."""
109
+ rel = src.relative_to(SRC)
110
+ parts = rel.parts
111
+ top = parts[0] if parts else ""
112
+
113
+ # Non-frontmatter trees → core verbatim.
114
+ if top in CORE_DIRS:
115
+ return CORE / rel, f"core dir: {top}/", None
116
+
117
+ stem = src.stem if src.name != "SKILL.md" else src.parent.name
118
+
119
+ if _is_core(fm, stem):
120
+ reason = "kernel rule" if stem in KERNEL_RULES else "trust.level=core + install.removable=false"
121
+ return CORE / rel, reason, None
122
+
123
+ primary = _primary_pack(fm)
124
+ if primary is None:
125
+ return CORE / rel, "no primary pack — falling back to core", "missing primary pack"
126
+ if primary not in pack_ids:
127
+ return CORE / rel, f"unknown pack '{primary}' — falling back to core", f"unknown pack: {primary}"
128
+ if primary == "meta":
129
+ # meta = package-internal scaffolding; lives in core alongside the kernel.
130
+ return CORE / rel, "primary pack: meta (package internals → core)", None
131
+
132
+ dest_root = PACKAGES / f"pack-{primary}" / ".agent-src.uncompressed"
133
+ return dest_root / rel, f"primary pack: {primary}", None
134
+
135
+
136
+ # Filesystem artefacts that must never be moved (runtime caches, scratch).
137
+ _SKIP_DIR_NAMES = frozenset({".pytest_cache", "__pycache__", ".mypy_cache",
138
+ ".ruff_cache", "node_modules", ".DS_Store"})
139
+
140
+
141
+ def _should_skip(p: Path) -> bool:
142
+ return any(part in _SKIP_DIR_NAMES for part in p.parts)
143
+
144
+
145
+ def _gitignored(paths: list[Path]) -> set[Path]:
146
+ """Return the subset of paths matched by .gitignore (runtime artefacts,
147
+ eval last-run.json, caches, …). These never participate in `git mv`."""
148
+ if not paths:
149
+ return set()
150
+ rel = [p.relative_to(ROOT).as_posix() for p in paths]
151
+ result = subprocess.run(
152
+ ["git", "check-ignore", "--stdin"],
153
+ cwd=ROOT,
154
+ input="\n".join(rel),
155
+ capture_output=True,
156
+ text=True,
157
+ )
158
+ # check-ignore exits 0 when at least one path matched, 1 when none, >1 on error.
159
+ if result.returncode > 1:
160
+ return set()
161
+ ignored = {line.strip() for line in result.stdout.splitlines() if line.strip()}
162
+ return {ROOT / r for r in rel if r in ignored}
163
+
164
+
165
+ def _iter_artefacts() -> list[Path]:
166
+ """Every source file under SRC. Includes non-md so the plan covers
167
+ the full tree (templates/, scripts/, packs/, hooks, .gitattributes
168
+ fragments, …). Runtime caches and gitignored files are excluded.
169
+ """
170
+ paths: list[Path] = []
171
+ for p in sorted(SRC.rglob("*")):
172
+ if not p.is_file():
173
+ continue
174
+ if _should_skip(p):
175
+ continue
176
+ paths.append(p)
177
+ ignored = _gitignored(paths)
178
+ return [p for p in paths if p not in ignored]
179
+
180
+
181
+ def _find_owning_skill_fm(src: Path) -> dict[str, Any] | None:
182
+ """For a non-SKILL.md file under skills/<name>/, return the sibling SKILL.md frontmatter.
183
+
184
+ Looks first at the source location (pre-move) and then under
185
+ ``packages/*/.agent-src.uncompressed/skills/<name>/SKILL.md`` so the
186
+ planner can resume mid-migration when the parent SKILL.md has already
187
+ been relocated.
188
+ """
189
+ if "skills" not in src.parts:
190
+ return None
191
+ idx = src.parts.index("skills")
192
+ if idx + 1 >= len(src.parts):
193
+ return None
194
+ skill_name = src.parts[idx + 1]
195
+ candidates = [ROOT / Path(*src.parts[: idx + 2]) / "SKILL.md"]
196
+ pkgs_root = ROOT / "packages"
197
+ if pkgs_root.exists():
198
+ for pkg in pkgs_root.iterdir():
199
+ cand = pkg / ".agent-src.uncompressed" / "skills" / skill_name / "SKILL.md"
200
+ if cand.exists():
201
+ candidates.append(cand)
202
+ for cand in candidates:
203
+ if cand.exists():
204
+ parsed, _ = parse_frontmatter(cand.read_text(encoding="utf-8", errors="replace"))
205
+ if isinstance(parsed, dict):
206
+ return parsed
207
+ return None
208
+
209
+
210
+ def _build_plan() -> dict[str, Any]:
211
+ pack_ids = _load_pack_ids()
212
+ unassigned = _load_unassigned()
213
+ moves: list[dict[str, Any]] = []
214
+ stays_in_core: list[dict[str, Any]] = []
215
+ conflicts: list[dict[str, Any]] = []
216
+
217
+ for src in _iter_artefacts():
218
+ rel_src = src.relative_to(ROOT).as_posix()
219
+ fm: dict[str, Any] | None = None
220
+ # Only .md files carry artefact frontmatter. Non-md files (yaml,
221
+ # python, php, hooks, .gitattributes fragments, …) inherit their
222
+ # placement from the parent directory or sibling SKILL.md.
223
+ if src.suffix == ".md":
224
+ try:
225
+ text = src.read_text(encoding="utf-8", errors="replace")
226
+ parsed, _ = parse_frontmatter(text)
227
+ if isinstance(parsed, dict):
228
+ fm = parsed
229
+ except Exception as exc: # noqa: BLE001
230
+ conflicts.append({"path": rel_src, "reason": f"parse error: {exc}"})
231
+ continue
232
+
233
+ # Quarantined scaffolds → core, no conflict.
234
+ if rel_src in unassigned and fm is None:
235
+ dest = CORE / src.relative_to(SRC)
236
+ stays_in_core.append({
237
+ "from": rel_src,
238
+ "to": dest.relative_to(ROOT).as_posix(),
239
+ "reason": f"unassigned scaffold: {unassigned[rel_src]}",
240
+ })
241
+ continue
242
+
243
+ # Skill auxiliary files (sub-pages, prompt fragments) inherit the
244
+ # parent SKILL.md's pack/trust. They never carry their own frontmatter.
245
+ inherited = False
246
+ if fm is None and src.name != "SKILL.md":
247
+ owner_fm = _find_owning_skill_fm(src)
248
+ if owner_fm is not None:
249
+ fm = owner_fm
250
+ inherited = True
251
+
252
+ dest, reason, conflict = _dest_for(src, fm, pack_ids)
253
+ if inherited:
254
+ reason = f"inherits parent SKILL.md → {reason}"
255
+ entry = {
256
+ "from": rel_src,
257
+ "to": dest.relative_to(ROOT).as_posix(),
258
+ "reason": reason,
259
+ }
260
+ if conflict:
261
+ conflicts.append({"path": rel_src, "reason": conflict, "fallback_to": entry["to"]})
262
+ if dest.is_relative_to(CORE):
263
+ stays_in_core.append(entry)
264
+ else:
265
+ moves.append(entry)
266
+
267
+ return {
268
+ "schema_version": "1",
269
+ "source_root": SRC.relative_to(ROOT).as_posix(),
270
+ "packages_root": PACKAGES.relative_to(ROOT).as_posix(),
271
+ "totals": {
272
+ "moves": len(moves),
273
+ "stays_in_core": len(stays_in_core),
274
+ "conflicts": len(conflicts),
275
+ },
276
+ "moves": moves,
277
+ "stays_in_core": stays_in_core,
278
+ "conflicts": conflicts,
279
+ }
280
+
281
+
282
+ def _write_plan(plan: dict[str, Any]) -> None:
283
+ PLAN_OUT.parent.mkdir(parents=True, exist_ok=True)
284
+ PLAN_OUT.write_text(
285
+ json.dumps(plan, indent=2, sort_keys=False, ensure_ascii=False) + "\n",
286
+ encoding="utf-8",
287
+ )
288
+
289
+
290
+ def _apply(plan: dict[str, Any]) -> int:
291
+ """Execute every move + stay via `git mv` so history follows.
292
+
293
+ Idempotent: entries whose source is already absent and whose
294
+ destination already exists are silently skipped. This lets a
295
+ partial run be resumed without rewinding the worktree.
296
+ """
297
+ if plan["conflicts"]:
298
+ print(f"ERROR: {len(plan['conflicts'])} unresolved conflict(s); refusing --apply.", file=sys.stderr)
299
+ return 2
300
+
301
+ all_entries = plan["moves"] + plan["stays_in_core"]
302
+ moved = 0
303
+ skipped = 0
304
+ for entry in all_entries:
305
+ src = ROOT / entry["from"]
306
+ dst = ROOT / entry["to"]
307
+ if not src.exists():
308
+ if dst.exists():
309
+ skipped += 1
310
+ continue
311
+ print(f"ERROR: source missing AND destination missing: {entry['from']}", file=sys.stderr)
312
+ return 3
313
+ dst.parent.mkdir(parents=True, exist_ok=True)
314
+ result = subprocess.run(
315
+ ["git", "mv", str(src), str(dst)],
316
+ cwd=ROOT,
317
+ capture_output=True,
318
+ text=True,
319
+ )
320
+ if result.returncode != 0:
321
+ print(f"ERROR: git mv failed: {entry['from']} -> {entry['to']}\n{result.stderr}", file=sys.stderr)
322
+ return 4
323
+ moved += 1
324
+ print(f"Applied {moved} moves, skipped {skipped} already-moved entries.")
325
+ return 0
326
+
327
+
328
+ def main() -> int:
329
+ ap = argparse.ArgumentParser(description=__doc__)
330
+ ap.add_argument("--apply", action="store_true", help="Execute moves via git mv (default: dry-run only)")
331
+ ap.add_argument("--out", type=Path, default=PLAN_OUT, help="Plan JSON output path")
332
+ args = ap.parse_args()
333
+
334
+ plan = _build_plan()
335
+ PLAN_OUT.parent.mkdir(parents=True, exist_ok=True)
336
+ args.out.parent.mkdir(parents=True, exist_ok=True)
337
+ args.out.write_text(
338
+ json.dumps(plan, indent=2, sort_keys=False, ensure_ascii=False) + "\n",
339
+ encoding="utf-8",
340
+ )
341
+ print(f"Plan: {args.out.relative_to(ROOT)}")
342
+ print(f" moves : {plan['totals']['moves']}")
343
+ print(f" stays_in_core : {plan['totals']['stays_in_core']}")
344
+ print(f" conflicts : {plan['totals']['conflicts']}")
345
+
346
+ if args.apply:
347
+ return _apply(plan)
348
+
349
+ return 1 if plan["conflicts"] else 0
350
+
351
+
352
+ if __name__ == "__main__":
353
+ sys.exit(main())
@@ -32,13 +32,36 @@ except ImportError as exc:
32
32
  ) from exc
33
33
 
34
34
  REPO_ROOT = Path(__file__).resolve().parent.parent
35
- DEFAULT_MAP = (
36
- REPO_ROOT
37
- / ".agent-src.uncompressed"
38
- / "skills"
39
- / "refine-ticket"
40
- / "detection-map.yml"
41
- )
35
+
36
+ # Post-monorepo Phase 4 the detection map lives under any package's
37
+ # .agent-src.uncompressed/. Discover it via the shared helper; fall
38
+ # back to the legacy flat path so consumers and older sub-trees still
39
+ # work.
40
+ import sys as _sys # noqa: E402
41
+
42
+ _sys.path.insert(0, str(REPO_ROOT / "scripts"))
43
+ try:
44
+ from _lib.agent_src import artefact_roots as _artefact_roots
45
+ except ImportError:
46
+ _artefact_roots = None
47
+
48
+
49
+ def _discover_default_map() -> Path:
50
+ if _artefact_roots is not None:
51
+ for root in _artefact_roots():
52
+ candidate = root / "skills" / "refine-ticket" / "detection-map.yml"
53
+ if candidate.is_file():
54
+ return candidate
55
+ return (
56
+ REPO_ROOT
57
+ / ".agent-src.uncompressed"
58
+ / "skills"
59
+ / "refine-ticket"
60
+ / "detection-map.yml"
61
+ )
62
+
63
+
64
+ DEFAULT_MAP = _discover_default_map()
42
65
 
43
66
  # Composite tokens that contain a sub-skill keyword as a substring but
44
67
  # are not themselves triggers (Phase F2). Matched with word boundaries
@@ -157,6 +157,10 @@
157
157
  "removable": {"type": "boolean"}
158
158
  },
159
159
  "description": "ADR-013 install hints (default-on at consumer install; user-removable)."
160
+ },
161
+ "gui_runnable": {
162
+ "type": "boolean",
163
+ "description": "AI OS Product UI allowlist (road-to-ai-os-product-ui.md Phase 1). When true, the command is exposed in the browser /tasks surface and may be spawned by POST /api/v1/task/run. Default false — commands that touch git, secrets, or run destructive migrations must stay false (terminal only). Per-command opt-in, never opt-out."
160
164
  }
161
165
  }
162
166
  }