@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
package/scripts/install.sh
CHANGED
|
@@ -248,7 +248,7 @@ create_symlink() {
|
|
|
248
248
|
local link_dir
|
|
249
249
|
link_dir="$(dirname "$link_abs")"
|
|
250
250
|
|
|
251
|
-
mkdir -p "$link_dir"
|
|
251
|
+
$DRY_RUN || mkdir -p "$link_dir"
|
|
252
252
|
|
|
253
253
|
# Remove existing file/symlink
|
|
254
254
|
if [[ -L "$link_abs" ]] || [[ -f "$link_abs" ]]; then
|
|
@@ -336,7 +336,7 @@ sync_hybrid() {
|
|
|
336
336
|
local target_dir
|
|
337
337
|
target_dir="$(dirname "$target_file")"
|
|
338
338
|
|
|
339
|
-
mkdir -p "$target_dir"
|
|
339
|
+
$DRY_RUN || mkdir -p "$target_dir"
|
|
340
340
|
|
|
341
341
|
if should_copy "$rel_path"; then
|
|
342
342
|
# Remove existing symlink before copying
|
|
@@ -446,7 +446,7 @@ create_tool_symlinks() {
|
|
|
446
446
|
local project_root="$1"
|
|
447
447
|
local rules_dir="$project_root/.augment/rules"
|
|
448
448
|
|
|
449
|
-
[[ -d "$rules_dir" ]] || return
|
|
449
|
+
[[ -d "$rules_dir" ]] || return 0
|
|
450
450
|
|
|
451
451
|
local -a tool_ids=()
|
|
452
452
|
local -a tool_dirs=()
|
|
@@ -466,7 +466,7 @@ create_tool_symlinks() {
|
|
|
466
466
|
local rel_prefix="${rel_prefixes[$i]}"
|
|
467
467
|
local target_dir="$project_root/$dir"
|
|
468
468
|
|
|
469
|
-
mkdir -p "$target_dir"
|
|
469
|
+
$DRY_RUN || mkdir -p "$target_dir"
|
|
470
470
|
|
|
471
471
|
for rule_file in "$rules_dir"/*.md; do
|
|
472
472
|
[[ -f "$rule_file" ]] || continue
|
|
@@ -516,11 +516,11 @@ create_skill_symlinks() {
|
|
|
516
516
|
local project_root="$1"
|
|
517
517
|
local skills_dir="$project_root/.augment/skills"
|
|
518
518
|
|
|
519
|
-
[[ -d "$skills_dir" ]] || return
|
|
519
|
+
[[ -d "$skills_dir" ]] || return 0
|
|
520
520
|
is_tool_enabled "claude-code" || { log_verbose "skip .claude/skills/ (claude-code not selected)"; return 0; }
|
|
521
521
|
|
|
522
522
|
local target_dir="$project_root/.claude/skills"
|
|
523
|
-
mkdir -p "$target_dir"
|
|
523
|
+
$DRY_RUN || mkdir -p "$target_dir"
|
|
524
524
|
|
|
525
525
|
local count=0
|
|
526
526
|
for skill_dir in "$skills_dir"/*/; do
|
|
@@ -569,7 +569,7 @@ generate_windsurfrules() {
|
|
|
569
569
|
local project_root="$1"
|
|
570
570
|
local rules_dir="$project_root/.augment/rules"
|
|
571
571
|
|
|
572
|
-
[[ -d "$rules_dir" ]] || return
|
|
572
|
+
[[ -d "$rules_dir" ]] || return 0
|
|
573
573
|
is_tool_enabled "windsurf" || { log_verbose "skip .windsurfrules (windsurf not selected)"; return 0; }
|
|
574
574
|
|
|
575
575
|
local output="$project_root/.windsurfrules"
|
|
@@ -629,8 +629,8 @@ copy_if_missing() {
|
|
|
629
629
|
local source="$1"
|
|
630
630
|
local target="$2"
|
|
631
631
|
|
|
632
|
-
[[ -f "$source" ]] || return
|
|
633
|
-
[[ -f "$target" ]] && return
|
|
632
|
+
[[ -f "$source" ]] || return 0
|
|
633
|
+
[[ -f "$target" ]] && return 0
|
|
634
634
|
|
|
635
635
|
if $DRY_RUN; then
|
|
636
636
|
log_verbose "copy $(basename "$target") (missing)"
|
|
@@ -834,8 +834,26 @@ install_cli_wrapper() {
|
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
# --- Main ---
|
|
837
|
+
# Phase 6 of monorepo-phase-3-typescript-installer (ADR-016 § Distribution):
|
|
838
|
+
# direct `bash install.sh` invocations are deprecated in favor of the
|
|
839
|
+
# TypeScript installer (`npx @event4u/agent-config init`). The banner only
|
|
840
|
+
# fires when this script is invoked directly — the orchestrator
|
|
841
|
+
# (scripts/install) and the consumer `./agent-config` wrapper set
|
|
842
|
+
# AGENT_CONFIG_FROM_ORCHESTRATOR=1 to suppress the noise. Removal target:
|
|
843
|
+
# the cutover release that flips the npx entry point to the TS installer.
|
|
844
|
+
emit_deprecation_banner() {
|
|
845
|
+
$QUIET && return 0
|
|
846
|
+
[[ "${AGENT_CONFIG_FROM_ORCHESTRATOR:-0}" == "1" ]] && return 0
|
|
847
|
+
[[ "${AGENT_CONFIG_SUPPRESS_DEPRECATION:-0}" == "1" ]] && return 0
|
|
848
|
+
echo " ⚠️ Direct \`bash install.sh\` is deprecated (ADR-016 § Distribution)." >&2
|
|
849
|
+
echo " Prefer: npx @event4u/agent-config init" >&2
|
|
850
|
+
echo " Or: bash scripts/install (orchestrator, suppresses this banner)" >&2
|
|
851
|
+
echo "" >&2
|
|
852
|
+
}
|
|
853
|
+
|
|
837
854
|
main() {
|
|
838
855
|
parse_args "$@"
|
|
856
|
+
emit_deprecation_banner
|
|
839
857
|
|
|
840
858
|
# Minimal-init short-circuit (Step 7 Phase 2): skip every payload-sync
|
|
841
859
|
# stage and only install the project-local `./agent-config` wrapper.
|
|
@@ -7,19 +7,28 @@ runtime artefacts, settings, audits, and policies. Flat files at the
|
|
|
7
7
|
everything else lives in a typed subdirectory (`runtime/`, `settings/`,
|
|
8
8
|
`audits/`, `roadmaps/`, `policies/`, `contexts/`, etc.).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Two layout tiers:
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
MAINTAINER (this source repo, identified by ``.agent-src.uncompressed/``)
|
|
13
|
+
Full `agents/` tree allowed — only the flat-file whitelist is
|
|
14
|
+
enforced. Phase 4 of road-to-global-only-install keeps the
|
|
15
|
+
maintainer surface unchanged.
|
|
16
|
+
|
|
17
|
+
CONSUMER (any repo without ``.agent-src.uncompressed/``)
|
|
18
|
+
Global-only target shape: `agents/overrides/` + the bridge
|
|
19
|
+
marker `agents/.event4u-bridge.yml` are the **only** expected
|
|
20
|
+
artefacts. Anything else surfaces as a WARNING with a pointer
|
|
21
|
+
to ``agent-config settings migrate`` (exit code 0). Hard
|
|
22
|
+
violations (unknown flat files at the root) still fail.
|
|
14
23
|
|
|
15
24
|
Exit codes:
|
|
16
|
-
0 — layout is clean.
|
|
17
|
-
1 — at least one UNKNOWN file.
|
|
25
|
+
0 — layout is clean (warnings ok in consumer mode).
|
|
26
|
+
1 — at least one UNKNOWN flat-file violation.
|
|
18
27
|
|
|
19
28
|
Invocation (from project root):
|
|
20
29
|
python3 scripts/lint_agents_layout.py
|
|
21
|
-
python3 scripts/lint_agents_layout.py --strict
|
|
22
30
|
python3 scripts/lint_agents_layout.py --quiet
|
|
31
|
+
python3 scripts/lint_agents_layout.py --strict # warnings → errors
|
|
23
32
|
"""
|
|
24
33
|
|
|
25
34
|
from __future__ import annotations
|
|
@@ -40,15 +49,51 @@ ALLOWED_FLAT_FILES: frozenset[str] = frozenset(
|
|
|
40
49
|
# D1 anchor / progress dashboard — kept at root by the
|
|
41
50
|
# roadmap-progress-sync rule so consumers see it first.
|
|
42
51
|
"roadmaps-progress.md",
|
|
43
|
-
# Worked example for the ai-video pipeline. Stays adjacent to
|
|
44
|
-
# the agents/reference/ai-video/ dir as a reference template.
|
|
45
|
-
".ai-video.xml.example",
|
|
46
52
|
# Empty-tree sentinel so agents/ survives a fresh checkout
|
|
47
53
|
# before any runtime artefact lands.
|
|
48
54
|
".gitkeep",
|
|
55
|
+
# Consumer bridge marker (Phase 4 of road-to-global-only-install).
|
|
56
|
+
# Spec: docs/contracts/consumer-bridge.md (event4u-bridge/v1).
|
|
57
|
+
".event4u-bridge.yml",
|
|
49
58
|
}
|
|
50
59
|
)
|
|
51
60
|
|
|
61
|
+
# Consumer-target layout: only these top-level entries are expected in
|
|
62
|
+
# the global-only world. Anything else is a WARNING in consumer mode.
|
|
63
|
+
CONSUMER_EXPECTED_ENTRIES: frozenset[str] = frozenset(
|
|
64
|
+
{"overrides", ".event4u-bridge.yml", ".gitkeep"},
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
MIGRATE_HINT = (
|
|
68
|
+
"Run `agent-config settings migrate` (or `npx @event4u/agent-config "
|
|
69
|
+
"migrate-to-global`) to move legacy project-scope artefacts under "
|
|
70
|
+
"`~/.event4u/agent-config/` and leave `agents/overrides/` + "
|
|
71
|
+
"`agents/.event4u-bridge.yml` as the only consumer-side files."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def is_source_repo(project_root: Path) -> bool:
|
|
76
|
+
"""True when running inside the agent-config source repo.
|
|
77
|
+
|
|
78
|
+
The maintainer surface is identified by **any** of:
|
|
79
|
+
- ``.agent-src.uncompressed/`` at the workspace root (legacy / single-pack layout),
|
|
80
|
+
- ``packages/<pack>/.agent-src.uncompressed/`` (current monorepo layout — see ``AGENTS.md``),
|
|
81
|
+
- ``.agent-src/`` at the workspace root (compressed authoring tree).
|
|
82
|
+
|
|
83
|
+
Consumer repos ship none of these — they only carry the deployed
|
|
84
|
+
`.augment/`, `.claude/`, etc. plus `agents/overrides/`.
|
|
85
|
+
"""
|
|
86
|
+
if (project_root / ".agent-src.uncompressed").is_dir():
|
|
87
|
+
return True
|
|
88
|
+
if (project_root / ".agent-src").is_dir():
|
|
89
|
+
return True
|
|
90
|
+
packages = project_root / "packages"
|
|
91
|
+
if packages.is_dir():
|
|
92
|
+
for sub in packages.iterdir():
|
|
93
|
+
if (sub / ".agent-src.uncompressed").is_dir():
|
|
94
|
+
return True
|
|
95
|
+
return False
|
|
96
|
+
|
|
52
97
|
|
|
53
98
|
def find_violations(root: Path) -> list[str]:
|
|
54
99
|
"""Return UNKNOWN flat-file violations at the agents/ root."""
|
|
@@ -73,14 +118,40 @@ def find_violations(root: Path) -> list[str]:
|
|
|
73
118
|
return unknown
|
|
74
119
|
|
|
75
120
|
|
|
121
|
+
def find_consumer_warnings(root: Path) -> list[str]:
|
|
122
|
+
"""Return WARNINGs for consumer repos that hold legacy artefacts.
|
|
123
|
+
|
|
124
|
+
Consumer-target shape (Phase 4 of road-to-global-only-install):
|
|
125
|
+
`agents/overrides/` + `agents/.event4u-bridge.yml` are the only
|
|
126
|
+
expected entries. Anything else is a soft warning — the linter
|
|
127
|
+
still exits 0, but the message points the user at the migration
|
|
128
|
+
subcommand so the legacy directory can be reclaimed.
|
|
129
|
+
"""
|
|
130
|
+
warnings: list[str] = []
|
|
131
|
+
if not root.is_dir():
|
|
132
|
+
return warnings
|
|
133
|
+
|
|
134
|
+
for path in sorted(root.iterdir()):
|
|
135
|
+
if path.name in CONSUMER_EXPECTED_ENTRIES:
|
|
136
|
+
continue
|
|
137
|
+
# Flat-file UNKNOWNs are already an error — don't double-count.
|
|
138
|
+
if path.is_file() and path.name not in ALLOWED_FLAT_FILES:
|
|
139
|
+
continue
|
|
140
|
+
kind = "dir" if path.is_dir() else "file"
|
|
141
|
+
warnings.append(f"{path} ({kind}): legacy artefact outside the consumer-target shape.")
|
|
142
|
+
|
|
143
|
+
return warnings
|
|
144
|
+
|
|
145
|
+
|
|
76
146
|
def main() -> int:
|
|
77
147
|
args = sys.argv[1:]
|
|
78
|
-
|
|
79
|
-
# that the LEGACY tier is gone.
|
|
80
|
-
_ = "--strict" in args
|
|
148
|
+
strict = "--strict" in args
|
|
81
149
|
quiet = "--quiet" in args
|
|
82
150
|
|
|
151
|
+
project_root = Path.cwd()
|
|
83
152
|
unknown = find_violations(AGENTS_ROOT)
|
|
153
|
+
consumer_mode = not is_source_repo(project_root)
|
|
154
|
+
warnings = find_consumer_warnings(AGENTS_ROOT) if consumer_mode else []
|
|
84
155
|
|
|
85
156
|
if unknown:
|
|
86
157
|
print("❌ agents/ layout violations (unknown flat files):\n")
|
|
@@ -94,7 +165,16 @@ def main() -> int:
|
|
|
94
165
|
)
|
|
95
166
|
return 1
|
|
96
167
|
|
|
97
|
-
if
|
|
168
|
+
if warnings:
|
|
169
|
+
if not quiet:
|
|
170
|
+
print("⚠️ agents/ consumer-shape warnings:\n")
|
|
171
|
+
for w in warnings:
|
|
172
|
+
print(f" - {w}")
|
|
173
|
+
print(f"\n{MIGRATE_HINT}")
|
|
174
|
+
if strict:
|
|
175
|
+
return 1
|
|
176
|
+
|
|
177
|
+
if not unknown and not warnings and not quiet:
|
|
98
178
|
print("✅ agents/ layout clean.")
|
|
99
179
|
return 0
|
|
100
180
|
|
|
@@ -65,7 +65,7 @@ class Target:
|
|
|
65
65
|
TARGETS = [
|
|
66
66
|
Target(ROOT / "AGENTS.md", "package-root", 3000, 2800, template=False),
|
|
67
67
|
Target(
|
|
68
|
-
ROOT / ".agent-src.uncompressed" / "templates" / "AGENTS.md",
|
|
68
|
+
ROOT / "packages" / "core" / ".agent-src.uncompressed" / "templates" / "AGENTS.md",
|
|
69
69
|
"consumer-template", 2500, 2300, template=True,
|
|
70
70
|
),
|
|
71
71
|
]
|
|
@@ -29,8 +29,16 @@ from pathlib import Path
|
|
|
29
29
|
QUIET = "--quiet" in sys.argv
|
|
30
30
|
|
|
31
31
|
REPO = Path(__file__).resolve().parents[1]
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# Archive notes moved under agents/evidence/ in the privilege-first
|
|
33
|
+
# taxonomy refactor (commit d2ce6748).
|
|
34
|
+
ARCHIVE_DIR = REPO / "agents" / "evidence" / "archived-skills"
|
|
35
|
+
|
|
36
|
+
# Live skill directories live under every artefact root post-monorepo
|
|
37
|
+
# Phase 4 (legacy + packages/*/.agent-src.uncompressed/skills/).
|
|
38
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
39
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
40
|
+
|
|
41
|
+
SKILLS_DIRS = [root / "skills" for root in artefact_roots() if (root / "skills").is_dir()]
|
|
34
42
|
|
|
35
43
|
REQUIRED_FIELDS = ("slug", "archived_on", "last_seen_count", "reason", "replacement", "last_known_callers")
|
|
36
44
|
VALID_REASONS = frozenset({"unused", "merged", "superseded", "deprecated"})
|
|
@@ -57,7 +65,13 @@ def archived_slugs() -> list[Path]:
|
|
|
57
65
|
|
|
58
66
|
|
|
59
67
|
def live_skill_slugs() -> set[str]:
|
|
60
|
-
|
|
68
|
+
slugs: set[str] = set()
|
|
69
|
+
for skills_dir in SKILLS_DIRS:
|
|
70
|
+
slugs.update(
|
|
71
|
+
p.name for p in skills_dir.iterdir()
|
|
72
|
+
if p.is_dir() and (p / "SKILL.md").exists()
|
|
73
|
+
)
|
|
74
|
+
return slugs
|
|
61
75
|
|
|
62
76
|
|
|
63
77
|
def main() -> int:
|
|
@@ -100,15 +114,16 @@ def main() -> int:
|
|
|
100
114
|
|
|
101
115
|
replacement = fm["replacement"]
|
|
102
116
|
reason = fm["reason"]
|
|
117
|
+
skills_label = ", ".join(str(d) for d in SKILLS_DIRS) or "<no skills root>"
|
|
103
118
|
if reason in {"merged", "superseded"}:
|
|
104
119
|
if replacement == "none" or not replacement:
|
|
105
120
|
errors.append(f"{note.name}: reason={reason} requires a replacement slug, got 'none'")
|
|
106
121
|
elif replacement not in live:
|
|
107
|
-
errors.append(f"{note.name}: replacement '{replacement}' not found under {
|
|
122
|
+
errors.append(f"{note.name}: replacement '{replacement}' not found under {skills_label}")
|
|
108
123
|
elif reason in {"unused", "deprecated"}:
|
|
109
124
|
if replacement not in {"none", ""}:
|
|
110
125
|
if replacement not in live:
|
|
111
|
-
errors.append(f"{note.name}: replacement '{replacement}' not found under {
|
|
126
|
+
errors.append(f"{note.name}: replacement '{replacement}' not found under {skills_label}")
|
|
112
127
|
|
|
113
128
|
if fm["slug"] in live:
|
|
114
129
|
errors.append(f"{note.name}: slug '{fm['slug']}' still has a live SKILL.md (zombie)")
|
|
@@ -116,17 +131,18 @@ def main() -> int:
|
|
|
116
131
|
archived_keys.add(fm["slug"])
|
|
117
132
|
|
|
118
133
|
# Cross-check: live skills must not list an archived slug as replaced_by.
|
|
119
|
-
for
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
134
|
+
for skills_dir in SKILLS_DIRS:
|
|
135
|
+
for skill_dir in sorted(skills_dir.iterdir()):
|
|
136
|
+
skill_md = skill_dir / "SKILL.md"
|
|
137
|
+
if not skill_md.exists():
|
|
138
|
+
continue
|
|
139
|
+
text = skill_md.read_text(encoding="utf-8")
|
|
140
|
+
fm = parse_frontmatter(text)
|
|
141
|
+
if fm is None:
|
|
142
|
+
continue
|
|
143
|
+
rb = fm.get("replaced_by", "").strip()
|
|
144
|
+
if rb and rb in archived_keys:
|
|
145
|
+
errors.append(f"{skill_dir.name}/SKILL.md: replaced_by '{rb}' points at an archived slug")
|
|
130
146
|
|
|
131
147
|
if errors:
|
|
132
148
|
print(f"❌ lint_archived_skills: {len(errors)} violation(s) across {len(notes)} note(s)", file=sys.stderr)
|
|
@@ -38,7 +38,13 @@ REQUIRE_FULL = "--require-full" in sys.argv
|
|
|
38
38
|
|
|
39
39
|
REPO = Path(__file__).resolve().parents[1]
|
|
40
40
|
CORPUS_DIR = REPO / "tests" / "eval"
|
|
41
|
-
|
|
41
|
+
|
|
42
|
+
# Live skill directories live under every artefact root post-monorepo
|
|
43
|
+
# Phase 4 (legacy + packages/*/.agent-src.uncompressed/skills/).
|
|
44
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
45
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
46
|
+
|
|
47
|
+
SKILLS_DIRS = [root / "skills" for root in artefact_roots() if (root / "skills").is_dir()]
|
|
42
48
|
|
|
43
49
|
VALID_CATEGORIES = frozenset({"canonical", "ambiguous", "destructive", "long-context"})
|
|
44
50
|
# Non-dev corpus (pre-spec) uses legacy categories — accept them so the
|
|
@@ -51,7 +57,13 @@ FULL_COUNTS = {"canonical": 10, "ambiguous": 8, "destructive": 5, "long-context"
|
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
def live_skills() -> set[str]:
|
|
54
|
-
|
|
60
|
+
slugs: set[str] = set()
|
|
61
|
+
for skills_dir in SKILLS_DIRS:
|
|
62
|
+
slugs.update(
|
|
63
|
+
p.name for p in skills_dir.iterdir()
|
|
64
|
+
if p.is_dir() and (p / "SKILL.md").exists()
|
|
65
|
+
)
|
|
66
|
+
return slugs
|
|
55
67
|
|
|
56
68
|
|
|
57
69
|
def lint_corpus(path: Path, skills: set[str]) -> list[str]:
|
|
@@ -19,7 +19,12 @@ from pathlib import Path
|
|
|
19
19
|
QUIET = "--quiet" in sys.argv
|
|
20
20
|
|
|
21
21
|
REPO = Path(__file__).resolve().parents[1]
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
# Commands live under every artefact root post-monorepo Phase 4.
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
25
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
26
|
+
|
|
27
|
+
COMMANDS_DIRS = [root / "commands" for root in artefact_roots() if (root / "commands").is_dir()]
|
|
23
28
|
# Consumer-facing projection — must also carry tier so .augment/commands/
|
|
24
29
|
# (which symlinks to .agent-src/commands/) renders the tier filter.
|
|
25
30
|
COMMANDS_DIR_COMPRESSED = REPO / ".agent-src" / "commands"
|
|
@@ -102,7 +107,15 @@ def lint(commands_dir: Path, *, quiet: bool = False) -> int:
|
|
|
102
107
|
|
|
103
108
|
|
|
104
109
|
def main() -> int:
|
|
105
|
-
|
|
110
|
+
if not COMMANDS_DIRS:
|
|
111
|
+
print(
|
|
112
|
+
"lint_command_tiers: no commands dir found under any artefact root",
|
|
113
|
+
file=sys.stderr,
|
|
114
|
+
)
|
|
115
|
+
return 1
|
|
116
|
+
rc = 0
|
|
117
|
+
for commands_dir in COMMANDS_DIRS:
|
|
118
|
+
rc |= lint(commands_dir, quiet=QUIET)
|
|
106
119
|
# The compressed projection is the consumer-facing tree (via the
|
|
107
120
|
# .augment/commands → .agent-src/commands symlink). It must also
|
|
108
121
|
# carry tier so the surface stays uniform.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CI guard for docs/featured-skills.md entry validity.
|
|
3
|
+
|
|
4
|
+
Every artefact referenced from the three Featured tables (Founders &
|
|
5
|
+
Consultants, Content Creators, Engineering Leads) MUST resolve in
|
|
6
|
+
`dist/discovery/discovery-manifest.json`. Stale entries (renamed or
|
|
7
|
+
removed skills / commands) fail the build.
|
|
8
|
+
|
|
9
|
+
Detection:
|
|
10
|
+
|
|
11
|
+
- Scan `docs/featured-skills.md` for inline links of shape
|
|
12
|
+
`[`<token>`](../.agent-src/{skills|commands}/<path>.md)` inside the
|
|
13
|
+
Featured tables. Strip the `/` prefix on commands and the leading
|
|
14
|
+
slash on skill names.
|
|
15
|
+
- Cross-check each token against the manifest's `artefacts` array
|
|
16
|
+
(`category` in {`skill`, `command`} + `name` match, namespaced
|
|
17
|
+
commands like `video/from-script` → `video:from-script`).
|
|
18
|
+
- Verify `--pack <slug>` install hints reference packs that exist in
|
|
19
|
+
`manifest.packs[].id`.
|
|
20
|
+
|
|
21
|
+
Exit codes:
|
|
22
|
+
0 — every entry resolves; install-pack hints are valid.
|
|
23
|
+
1 — at least one stale entry or unknown pack.
|
|
24
|
+
|
|
25
|
+
Invocation:
|
|
26
|
+
python3 scripts/lint_featured_skills.py
|
|
27
|
+
python3 scripts/lint_featured_skills.py --quiet
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
DOC = Path("docs/featured-skills.md")
|
|
38
|
+
MANIFEST = Path("dist/discovery/discovery-manifest.json")
|
|
39
|
+
|
|
40
|
+
# Matches `[`token`](../.agent-src/skills/<slug>/SKILL.md)` or
|
|
41
|
+
# `[`/token`](../.agent-src/commands/<path>.md)`. Captures (category, slug-path).
|
|
42
|
+
LINK_RE = re.compile(
|
|
43
|
+
r"\[`/?[^`]+`\]\(\.\./\.agent-src/(skills|commands)/([^)]+?)\.md\)"
|
|
44
|
+
)
|
|
45
|
+
PACK_HINT_RE = re.compile(r"--pack\s+([a-z][a-z0-9-]*)")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_manifest() -> dict:
|
|
49
|
+
if not MANIFEST.exists():
|
|
50
|
+
print(f"error: manifest not found at {MANIFEST}", file=sys.stderr)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
return json.loads(MANIFEST.read_text(encoding="utf-8"))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def manifest_names(manifest: dict) -> tuple[set[str], set[str], set[str]]:
|
|
56
|
+
"""Return (skill-names, command-names, pack-ids)."""
|
|
57
|
+
skills: set[str] = set()
|
|
58
|
+
commands: set[str] = set()
|
|
59
|
+
for art in manifest.get("artefacts", []):
|
|
60
|
+
cat = art.get("category")
|
|
61
|
+
name = art.get("name")
|
|
62
|
+
if not name:
|
|
63
|
+
continue
|
|
64
|
+
if cat == "skill":
|
|
65
|
+
skills.add(name)
|
|
66
|
+
elif cat == "command":
|
|
67
|
+
commands.add(name)
|
|
68
|
+
packs = {p.get("id") for p in manifest.get("packs", []) if p.get("id")}
|
|
69
|
+
return skills, commands, packs
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def slug_from_path(category: str, raw: str) -> str:
|
|
73
|
+
"""Convert a doc-link path to the manifest `name` form.
|
|
74
|
+
|
|
75
|
+
skills/<slug>/SKILL → <slug>
|
|
76
|
+
commands/<group>/<leaf> → <group>:<leaf>
|
|
77
|
+
commands/<leaf> → <leaf>
|
|
78
|
+
"""
|
|
79
|
+
if category == "skills":
|
|
80
|
+
# raw looks like "<slug>/SKILL"; strip trailing /SKILL if present.
|
|
81
|
+
return raw.split("/", 1)[0]
|
|
82
|
+
# commands
|
|
83
|
+
parts = raw.split("/")
|
|
84
|
+
return ":".join(parts) if len(parts) > 1 else parts[0]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> int:
|
|
88
|
+
quiet = "--quiet" in sys.argv
|
|
89
|
+
if not DOC.exists():
|
|
90
|
+
print(f"error: {DOC} not found", file=sys.stderr)
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
manifest = load_manifest()
|
|
94
|
+
skills, commands, packs = manifest_names(manifest)
|
|
95
|
+
body = DOC.read_text(encoding="utf-8")
|
|
96
|
+
|
|
97
|
+
missing: list[str] = []
|
|
98
|
+
seen: set[tuple[str, str]] = set()
|
|
99
|
+
for cat, raw in LINK_RE.findall(body):
|
|
100
|
+
slug = slug_from_path(cat, raw)
|
|
101
|
+
key = (cat, slug)
|
|
102
|
+
if key in seen:
|
|
103
|
+
continue
|
|
104
|
+
seen.add(key)
|
|
105
|
+
pool = skills if cat == "skills" else commands
|
|
106
|
+
if slug not in pool:
|
|
107
|
+
missing.append(f" - {cat}/{slug} (linked path: ../.agent-src/{cat}/{raw}.md)")
|
|
108
|
+
|
|
109
|
+
unknown_packs: list[str] = []
|
|
110
|
+
for pack in PACK_HINT_RE.findall(body):
|
|
111
|
+
if pack not in packs:
|
|
112
|
+
unknown_packs.append(f" - --pack {pack}")
|
|
113
|
+
|
|
114
|
+
if missing or unknown_packs:
|
|
115
|
+
print(f"FAIL {DOC}: stale Featured Skills entries detected.")
|
|
116
|
+
if missing:
|
|
117
|
+
print("\nMissing artefacts (not in discovery-manifest.json):")
|
|
118
|
+
for line in missing:
|
|
119
|
+
print(line)
|
|
120
|
+
if unknown_packs:
|
|
121
|
+
print("\nUnknown pack ids referenced in install hints:")
|
|
122
|
+
for line in unknown_packs:
|
|
123
|
+
print(line)
|
|
124
|
+
print(
|
|
125
|
+
"\nFix: either restore the artefact, update the doc entry to a "
|
|
126
|
+
"current name, or substitute with the nearest existing artefact."
|
|
127
|
+
)
|
|
128
|
+
return 1
|
|
129
|
+
|
|
130
|
+
if not quiet:
|
|
131
|
+
print(
|
|
132
|
+
f"OK {DOC}: {len(seen)} artefact entries + "
|
|
133
|
+
f"{len(set(PACK_HINT_RE.findall(body)))} pack hints validated."
|
|
134
|
+
)
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
sys.exit(main())
|
|
@@ -33,11 +33,30 @@ from pathlib import Path
|
|
|
33
33
|
from typing import Iterable
|
|
34
34
|
|
|
35
35
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
37
|
+
from _lib.agent_src import artefact_roots # noqa: E402
|
|
38
|
+
|
|
39
|
+
# Post-ADR-017 source artefacts live under every packages/*/.agent-src.uncompressed/;
|
|
40
|
+
# pre-move the flat .agent-src.uncompressed/ root still wins. The lint walks
|
|
41
|
+
# the three artefact subtrees (skills/, rules/, commands/) under each active root.
|
|
42
|
+
_SUBDIRS = ("skills", "rules", "commands")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _default_paths() -> tuple[str, ...]:
|
|
46
|
+
out: list[str] = []
|
|
47
|
+
for root in artefact_roots():
|
|
48
|
+
try:
|
|
49
|
+
rel = root.relative_to(REPO_ROOT)
|
|
50
|
+
except ValueError:
|
|
51
|
+
continue
|
|
52
|
+
for sub in _SUBDIRS:
|
|
53
|
+
target = root / sub
|
|
54
|
+
if target.is_dir():
|
|
55
|
+
out.append((rel / sub).as_posix())
|
|
56
|
+
return tuple(out)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
DEFAULT_PATHS = _default_paths()
|
|
41
60
|
ALLOWLIST_FILE = REPO_ROOT / "scripts/lint_framework_leakage_allowlist.json"
|
|
42
61
|
|
|
43
62
|
CARVE_OUT_PATTERNS = [
|
|
@@ -185,8 +204,16 @@ def _load_allowlist() -> dict:
|
|
|
185
204
|
|
|
186
205
|
|
|
187
206
|
def _allowlisted(rel_path: str, line_no: int, allowlist: dict) -> bool:
|
|
207
|
+
# Allowlist entries cite paths under the legacy .agent-src.uncompressed/
|
|
208
|
+
# prefix; post-ADR-017 files live under packages/*/.agent-src.uncompressed/.
|
|
209
|
+
# Match either the literal repo-relative path or its logical id.
|
|
210
|
+
from _lib.agent_src import strip_source_prefix # noqa: E402
|
|
211
|
+
|
|
212
|
+
logical = strip_source_prefix(rel_path)
|
|
188
213
|
for entry in allowlist.get("entries", []):
|
|
189
|
-
|
|
214
|
+
entry_file = entry.get("file")
|
|
215
|
+
entry_logical = strip_source_prefix(entry_file) if isinstance(entry_file, str) else None
|
|
216
|
+
if entry_file != rel_path and (logical is None or entry_logical != logical):
|
|
190
217
|
continue
|
|
191
218
|
lines = entry.get("lines")
|
|
192
219
|
if lines == "*":
|