@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
|
@@ -35,7 +35,14 @@ from typing import Iterable
|
|
|
35
35
|
import yaml
|
|
36
36
|
|
|
37
37
|
ROOT = Path(__file__).resolve().parent.parent
|
|
38
|
-
|
|
38
|
+
sys.path.insert(0, str(ROOT / "scripts"))
|
|
39
|
+
from _lib.agent_src import artefact_roots, resolve_logical, strip_source_prefix # noqa: E402
|
|
40
|
+
|
|
41
|
+
# Canonical anchor used in the committed matrix. Paths are always
|
|
42
|
+
# emitted as ".agent-src.uncompressed/<sub>/<...>" regardless of which
|
|
43
|
+
# physical root (legacy or packages/*) contains the file, so the matrix
|
|
44
|
+
# stays stable across the monorepo migration.
|
|
45
|
+
CANONICAL_SRC_PREFIX = ".agent-src.uncompressed"
|
|
39
46
|
|
|
40
47
|
SCAN_DIRS = ("rules", "skills", "commands", "contexts", "personas")
|
|
41
48
|
|
|
@@ -88,38 +95,48 @@ def _parse_frontmatter(p: Path) -> dict:
|
|
|
88
95
|
return data if isinstance(data, dict) else {}
|
|
89
96
|
|
|
90
97
|
|
|
91
|
-
def _collect_files(
|
|
92
|
-
|
|
93
|
-
for sub in SCAN_DIRS:
|
|
94
|
-
d = src_root / sub
|
|
95
|
-
if d.exists():
|
|
96
|
-
out.extend(sorted(d.rglob("*.md")))
|
|
97
|
-
return out
|
|
98
|
+
def _collect_files(root: Path | None = None) -> list[tuple[Path, str]]:
|
|
99
|
+
"""Walk every artefact root and yield ``(physical_path, canonical_rel)``.
|
|
98
100
|
|
|
101
|
+
``canonical_rel`` is always anchored at ``.agent-src.uncompressed/`` so
|
|
102
|
+
the matrix is byte-identical pre- and post-monorepo-move. Duplicates
|
|
103
|
+
across roots resolve to the first hit (legacy first, then packages
|
|
104
|
+
alphabetically) — matches the priority in ``artefact_roots()``.
|
|
99
105
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
When ``root`` is given, only that single directory is scanned — used by
|
|
107
|
+
tests against a ``tmp_path`` fixture so they stay isolated from the
|
|
108
|
+
real package layout.
|
|
109
|
+
"""
|
|
110
|
+
roots = [root] if root is not None else list(artefact_roots())
|
|
111
|
+
out: list[tuple[Path, str]] = []
|
|
112
|
+
seen: set[str] = set()
|
|
113
|
+
for r in roots:
|
|
114
|
+
for sub in SCAN_DIRS:
|
|
115
|
+
d = r / sub
|
|
116
|
+
if not d.exists():
|
|
117
|
+
continue
|
|
118
|
+
for f in sorted(d.rglob("*.md")):
|
|
119
|
+
logical = f.relative_to(r).as_posix()
|
|
120
|
+
canonical = f"{CANONICAL_SRC_PREFIX}/{logical}"
|
|
121
|
+
if canonical in seen:
|
|
122
|
+
continue
|
|
123
|
+
seen.add(canonical)
|
|
124
|
+
out.append((f, canonical))
|
|
125
|
+
out.sort(key=lambda pair: pair[1])
|
|
126
|
+
return out
|
|
112
127
|
|
|
113
128
|
|
|
114
|
-
def build_matrix(
|
|
129
|
+
def build_matrix(root: Path | None = None) -> tuple[dict[str, FileEntry], list[Edge], list[str]]:
|
|
115
130
|
"""Build the file map + edge list. Returns (files, edges, depth3_chains).
|
|
116
131
|
|
|
117
132
|
depth3_chains is non-empty iff the depth invariant is violated; the
|
|
118
|
-
caller must abort with exit code 2.
|
|
133
|
+
caller must abort with exit code 2. When ``root`` is given, only that
|
|
134
|
+
single directory is scanned (test isolation).
|
|
119
135
|
"""
|
|
120
136
|
files: dict[str, FileEntry] = {}
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
physical_by_canonical: dict[str, Path] = {}
|
|
138
|
+
for f, rel in _collect_files(root):
|
|
139
|
+
physical_by_canonical[rel] = f
|
|
123
140
|
fm = _parse_frontmatter(f)
|
|
124
141
|
rtype = fm.get("type")
|
|
125
142
|
if isinstance(rtype, str):
|
|
@@ -149,14 +166,15 @@ def build_matrix(src_root: Path) -> tuple[dict[str, FileEntry], list[Edge], list
|
|
|
149
166
|
|
|
150
167
|
# Body markdown links — only count edges to files we know about
|
|
151
168
|
for rel, entry in files.items():
|
|
152
|
-
|
|
169
|
+
phys = physical_by_canonical[rel]
|
|
170
|
+
body = phys.read_text(encoding="utf-8")
|
|
153
171
|
body = body.split("\n---\n", 1)[-1] if body.startswith("---\n") else body
|
|
154
172
|
seen_targets: set[str] = set()
|
|
155
173
|
for m in LINK_RE.finditer(body):
|
|
156
174
|
href = m.group(1).strip()
|
|
157
175
|
if href.startswith("http"):
|
|
158
176
|
continue
|
|
159
|
-
resolved = _resolve_link(rel,
|
|
177
|
+
resolved = _resolve_link(rel, phys, href)
|
|
160
178
|
if resolved is None or resolved == rel or resolved in seen_targets:
|
|
161
179
|
continue
|
|
162
180
|
if resolved in files:
|
|
@@ -191,21 +209,57 @@ def build_matrix(src_root: Path) -> tuple[dict[str, FileEntry], list[Edge], list
|
|
|
191
209
|
return files, edges, depth3
|
|
192
210
|
|
|
193
211
|
|
|
194
|
-
def _resolve_link(source_rel: str,
|
|
195
|
-
"""Resolve a markdown link href
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
212
|
+
def _resolve_link(source_rel: str, source_phys: Path, href: str) -> str | None:
|
|
213
|
+
"""Resolve a markdown link href to a canonical scanned-root path, or None.
|
|
214
|
+
|
|
215
|
+
``source_rel`` is the canonical (``.agent-src.uncompressed/...``)
|
|
216
|
+
identity of the source file. Relative hrefs are resolved against
|
|
217
|
+
the source's *logical* directory, then looked up across every
|
|
218
|
+
artefact root via :func:`resolve_logical`. This keeps the matrix
|
|
219
|
+
stable when source and target live in different physical packages.
|
|
220
|
+
|
|
221
|
+
Repo-rooted hrefs (``agents/...``, ``packages/...``, or those
|
|
222
|
+
starting with ``.agent-src.uncompressed/``) are resolved against
|
|
223
|
+
the repo root and normalised through :func:`strip_source_prefix`.
|
|
224
|
+
"""
|
|
225
|
+
if href.startswith(".agent-src.uncompressed/") or href.startswith("agents/") \
|
|
226
|
+
or href.startswith("packages/"):
|
|
227
|
+
cand = (ROOT / href).resolve()
|
|
228
|
+
if not cand.exists():
|
|
229
|
+
return None
|
|
230
|
+
try:
|
|
231
|
+
rel = cand.relative_to(ROOT).as_posix()
|
|
232
|
+
except ValueError:
|
|
233
|
+
return None
|
|
234
|
+
logical = strip_source_prefix(rel)
|
|
235
|
+
if logical is None:
|
|
236
|
+
return None
|
|
199
237
|
else:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
# Logical resolution: walk relative hops on the canonical path
|
|
239
|
+
# so a `../skills/laravel/SKILL.md` link from
|
|
240
|
+
# `rules/architecture.md` resolves to `skills/laravel/SKILL.md`
|
|
241
|
+
# regardless of which package physically hosts either file.
|
|
242
|
+
source_logical = strip_source_prefix(source_rel)
|
|
243
|
+
if source_logical is None:
|
|
244
|
+
return None
|
|
245
|
+
base_parts = source_logical.split("/")[:-1] # drop file name
|
|
246
|
+
href_parts = href.split("/")
|
|
247
|
+
for part in href_parts:
|
|
248
|
+
if part == "" or part == ".":
|
|
249
|
+
continue
|
|
250
|
+
if part == "..":
|
|
251
|
+
if not base_parts:
|
|
252
|
+
return None
|
|
253
|
+
base_parts.pop()
|
|
254
|
+
else:
|
|
255
|
+
base_parts.append(part)
|
|
256
|
+
logical = "/".join(base_parts)
|
|
257
|
+
# Existence is validated downstream by the caller against the scanned
|
|
258
|
+
# ``files`` dict — that handles both real ``artefact_roots()`` scans
|
|
259
|
+
# and ``tmp_path`` test fixtures uniformly.
|
|
260
|
+
parts = logical.split("/")
|
|
261
|
+
if len(parts) >= 2 and parts[0] in SCAN_DIRS:
|
|
262
|
+
return f"{CANONICAL_SRC_PREFIX}/{logical}"
|
|
209
263
|
return None
|
|
210
264
|
|
|
211
265
|
|
|
@@ -287,11 +341,12 @@ def main(argv: Iterable[str] | None = None) -> int:
|
|
|
287
341
|
help="Regenerate to memory and diff against committed JSON.")
|
|
288
342
|
args = ap.parse_args(list(argv) if argv is not None else None)
|
|
289
343
|
|
|
290
|
-
if not
|
|
291
|
-
print(
|
|
344
|
+
if not artefact_roots():
|
|
345
|
+
print("❌ no artefact roots found (legacy or packages/*/.agent-src.uncompressed/)",
|
|
346
|
+
file=sys.stderr)
|
|
292
347
|
return 3
|
|
293
348
|
|
|
294
|
-
files, edges, depth3 = build_matrix(
|
|
349
|
+
files, edges, depth3 = build_matrix()
|
|
295
350
|
if depth3:
|
|
296
351
|
print("❌ load_context depth-3 chain detected (limit is 2):", file=sys.stderr)
|
|
297
352
|
for chain in depth3:
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate ``pack.yaml`` + ``README.md`` for every ``packages/*/`` tree.
|
|
3
|
+
|
|
4
|
+
Phase 4.4 of the monorepo migration (ADR-017). The manifests are
|
|
5
|
+
*derived* from frontmatter + ``config/discovery/packs.yml``; they are
|
|
6
|
+
regenerated on every ``task sync`` and must never be hand-edited.
|
|
7
|
+
|
|
8
|
+
For each package directory under ``packages/`` we emit:
|
|
9
|
+
- ``packages/<pkg>/pack.yaml`` — id, label, owner (workspaces),
|
|
10
|
+
requires, version, artefact_count
|
|
11
|
+
- ``packages/<pkg>/README.md`` — pack description + per-artefact
|
|
12
|
+
description table
|
|
13
|
+
|
|
14
|
+
CLI:
|
|
15
|
+
--check exit non-zero if any output would change (CI gate)
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
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 _lib.agent_src import iter_all_sources # noqa: E402
|
|
29
|
+
|
|
30
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
31
|
+
PACKAGES = ROOT / "packages"
|
|
32
|
+
PACKS_VOCAB = ROOT / "config" / "discovery" / "packs.yml"
|
|
33
|
+
PACKAGE_JSON = ROOT / "package.json"
|
|
34
|
+
GENERATED_HEADER = "# Generated by scripts/generate_pack_manifests.py — DO NOT EDIT BY HAND\n"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_yaml(path: Path) -> Any:
|
|
38
|
+
if not path.exists():
|
|
39
|
+
return None
|
|
40
|
+
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _read_frontmatter(path: Path) -> dict[str, Any]:
|
|
44
|
+
text = path.read_text(encoding="utf-8")
|
|
45
|
+
if not text.startswith("---"):
|
|
46
|
+
return {}
|
|
47
|
+
end = text.find("\n---", 4)
|
|
48
|
+
if end == -1:
|
|
49
|
+
return {}
|
|
50
|
+
try:
|
|
51
|
+
data = yaml.safe_load(text[4:end])
|
|
52
|
+
except yaml.YAMLError:
|
|
53
|
+
return {}
|
|
54
|
+
return data if isinstance(data, dict) else {}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _pack_id_from_dir(pkg_dir: Path) -> str:
|
|
58
|
+
name = pkg_dir.name
|
|
59
|
+
return "core" if name == "core" else name.removeprefix("pack-")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _vocab_lookup(packs_vocab: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
63
|
+
return {p["id"]: p for p in (packs_vocab or [])}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _package_version() -> str:
|
|
67
|
+
if not PACKAGE_JSON.exists():
|
|
68
|
+
return "0.0.0"
|
|
69
|
+
return json.loads(PACKAGE_JSON.read_text(encoding="utf-8")).get("version", "0.0.0")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _collect_artefacts(pkg_dir: Path) -> list[dict[str, Any]]:
|
|
73
|
+
src_root = pkg_dir / ".agent-src.uncompressed"
|
|
74
|
+
if not src_root.is_dir():
|
|
75
|
+
return []
|
|
76
|
+
items: list[dict[str, Any]] = []
|
|
77
|
+
for p in sorted(src_root.rglob("*.md")):
|
|
78
|
+
if not p.is_file():
|
|
79
|
+
continue
|
|
80
|
+
fm = _read_frontmatter(p)
|
|
81
|
+
if not fm:
|
|
82
|
+
continue
|
|
83
|
+
items.append({
|
|
84
|
+
"path": p.relative_to(src_root).as_posix(),
|
|
85
|
+
"name": fm.get("name") or (p.parent.name if p.name == "SKILL.md" else p.stem),
|
|
86
|
+
"description": (fm.get("description") or "").strip(),
|
|
87
|
+
"category": _category_for(p, src_root),
|
|
88
|
+
})
|
|
89
|
+
return items
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _category_for(path: Path, src_root: Path) -> str:
|
|
93
|
+
rel = path.relative_to(src_root).as_posix()
|
|
94
|
+
top = rel.split("/", 1)[0]
|
|
95
|
+
mapping = {"skills": "skill", "rules": "rule", "commands": "command",
|
|
96
|
+
"personas": "persona", "guidelines": "guideline",
|
|
97
|
+
"contexts": "context", "presets": "preset", "profiles": "profile"}
|
|
98
|
+
return mapping.get(top, top)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_pack_yaml(pkg_dir: Path, vocab: dict[str, dict[str, Any]],
|
|
102
|
+
artefacts: list[dict[str, Any]], version: str) -> dict[str, Any]:
|
|
103
|
+
pid = _pack_id_from_dir(pkg_dir)
|
|
104
|
+
meta = vocab.get(pid) or {}
|
|
105
|
+
label = meta.get("label") or (pid.title() if pid == "core" else pid)
|
|
106
|
+
out: dict[str, Any] = {
|
|
107
|
+
"id": pid,
|
|
108
|
+
"label": label,
|
|
109
|
+
"description": meta.get("description") or ("Core framework-neutral artefacts." if pid == "core" else ""),
|
|
110
|
+
"owner": meta.get("workspaces") or (["agent-config-maintainer"] if pid == "core" else []),
|
|
111
|
+
"requires": meta.get("requires_hint") or [],
|
|
112
|
+
"trust_level_default": meta.get("trust_level_default") or "core",
|
|
113
|
+
"version": version,
|
|
114
|
+
"artefact_count": len(artefacts),
|
|
115
|
+
}
|
|
116
|
+
if isinstance(meta.get("onboarding"), dict):
|
|
117
|
+
out["onboarding"] = meta["onboarding"]
|
|
118
|
+
return out
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _render_readme(pack_meta: dict[str, Any], artefacts: list[dict[str, Any]]) -> str:
|
|
122
|
+
lines = [GENERATED_HEADER, f"# {pack_meta['label']}", ""]
|
|
123
|
+
if pack_meta.get("description"):
|
|
124
|
+
lines += [pack_meta["description"], ""]
|
|
125
|
+
lines += [f"- **id**: `{pack_meta['id']}`",
|
|
126
|
+
f"- **version**: `{pack_meta['version']}`",
|
|
127
|
+
f"- **owner**: {', '.join(pack_meta['owner']) or '—'}",
|
|
128
|
+
f"- **requires**: {', '.join(pack_meta['requires']) or '—'}",
|
|
129
|
+
f"- **artefacts**: {pack_meta['artefact_count']}", ""]
|
|
130
|
+
by_cat: dict[str, list[dict[str, Any]]] = {}
|
|
131
|
+
for a in artefacts:
|
|
132
|
+
by_cat.setdefault(a["category"], []).append(a)
|
|
133
|
+
for cat in sorted(by_cat):
|
|
134
|
+
lines += [f"## {cat.title()}s ({len(by_cat[cat])})", ""]
|
|
135
|
+
for a in sorted(by_cat[cat], key=lambda x: x["name"]):
|
|
136
|
+
desc = a["description"] or "_(no description)_"
|
|
137
|
+
lines.append(f"- **`{a['name']}`** — {desc}")
|
|
138
|
+
lines.append("")
|
|
139
|
+
return "\n".join(lines).rstrip() + "\n"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _write_if_changed(path: Path, content: str, check: bool) -> bool:
|
|
143
|
+
existing = path.read_text(encoding="utf-8") if path.exists() else None
|
|
144
|
+
if existing == content:
|
|
145
|
+
return False
|
|
146
|
+
if check:
|
|
147
|
+
return True
|
|
148
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
149
|
+
path.write_text(content, encoding="utf-8")
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def main() -> int:
|
|
154
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
155
|
+
ap.add_argument("--check", action="store_true", help="exit non-zero on drift")
|
|
156
|
+
args = ap.parse_args()
|
|
157
|
+
vocab = _vocab_lookup(_load_yaml(PACKS_VOCAB) or [])
|
|
158
|
+
version = _package_version()
|
|
159
|
+
if not PACKAGES.exists():
|
|
160
|
+
print("packages/ does not exist — nothing to generate", file=sys.stderr)
|
|
161
|
+
return 0
|
|
162
|
+
drift = 0
|
|
163
|
+
for pkg in sorted(PACKAGES.iterdir()):
|
|
164
|
+
if not pkg.is_dir():
|
|
165
|
+
continue
|
|
166
|
+
artefacts = _collect_artefacts(pkg)
|
|
167
|
+
meta = _build_pack_yaml(pkg, vocab, artefacts, version)
|
|
168
|
+
yaml_body = GENERATED_HEADER + yaml.safe_dump(meta, sort_keys=True, allow_unicode=True)
|
|
169
|
+
readme_body = _render_readme(meta, artefacts)
|
|
170
|
+
if _write_if_changed(pkg / "pack.yaml", yaml_body, args.check):
|
|
171
|
+
drift += 1
|
|
172
|
+
print(f"{'drift' if args.check else 'wrote'}: {pkg.name}/pack.yaml")
|
|
173
|
+
if _write_if_changed(pkg / "README.md", readme_body, args.check):
|
|
174
|
+
drift += 1
|
|
175
|
+
print(f"{'drift' if args.check else 'wrote'}: {pkg.name}/README.md")
|
|
176
|
+
if args.check and drift:
|
|
177
|
+
print(f"pack manifests out of date ({drift} file(s)) — run `task generate-pack-manifests`", file=sys.stderr)
|
|
178
|
+
return 1
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
sys.exit(main())
|
package/scripts/install
CHANGED
|
@@ -183,6 +183,20 @@ if $LIST_TOOLS; then
|
|
|
183
183
|
exit 0
|
|
184
184
|
fi
|
|
185
185
|
|
|
186
|
+
# road-to-global-only-install § Phase 3.3 — consumer-floor gate.
|
|
187
|
+
#
|
|
188
|
+
# Consumer installs are global-only (ADR-020). Explicit --scope=project
|
|
189
|
+
# (and its --scope project / --scope=project / SCOPE shorthand) is the
|
|
190
|
+
# maintainer-only escape hatch behind AGENT_CONFIG_DEV_MODE=1. Without
|
|
191
|
+
# the env flag we refuse here so the bash orchestrator fails fast with
|
|
192
|
+
# a directive error pointing at the maintainer doc — install.py's
|
|
193
|
+
# Python-side _enforce_consumer_global_only still backstops the same
|
|
194
|
+
# check for direct python3 scripts/install.py invocations.
|
|
195
|
+
if [[ "${SCOPE:-}" == "project" && "${AGENT_CONFIG_DEV_MODE:-}" != "1" ]]; then
|
|
196
|
+
err "--scope=project is reserved for maintainers (ADR-020 — consumer installs are global-only). Set AGENT_CONFIG_DEV_MODE=1 to opt in. See docs/maintainers/dev-mode.md."
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
|
|
186
200
|
# Interactive --tools picker (S9). Fires only when:
|
|
187
201
|
# - --tools was not explicitly passed
|
|
188
202
|
# - --yes / -y was not passed (CI / non-interactive opt-out)
|
|
@@ -298,7 +312,10 @@ run_sync() {
|
|
|
298
312
|
$QUIET && args+=(--quiet)
|
|
299
313
|
$MINIMAL && args+=(--minimal)
|
|
300
314
|
args+=(--tools="$TOOLS")
|
|
301
|
-
|
|
315
|
+
# Suppress the install.sh deprecation banner when called through the
|
|
316
|
+
# orchestrator (this script). Direct `bash install.sh` invocations
|
|
317
|
+
# still see it. See ADR-016 § Distribution / Phase 6.
|
|
318
|
+
AGENT_CONFIG_FROM_ORCHESTRATOR=1 bash "$INSTALL_SH" "${args[@]}"
|
|
302
319
|
}
|
|
303
320
|
|
|
304
321
|
run_bridges() {
|