@event4u/agent-config 5.6.1 → 5.8.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.
- package/.agent-src/commands/agent-handoff.md +1 -1
- package/.agent-src/commands/agent-status.md +1 -1
- package/.agent-src/commands/agents/audit.md +1 -1
- package/.agent-src/commands/agents/init.md +1 -1
- package/.agent-src/commands/agents/user/accept.md +3 -3
- package/.agent-src/commands/agents/user/init.md +4 -4
- package/.agent-src/commands/agents/user/show.md +3 -3
- package/.agent-src/commands/agents/user/update.md +3 -3
- package/.agent-src/commands/agents/user.md +1 -1
- package/.agent-src/commands/agents.md +1 -1
- package/.agent-src/commands/analytics/prune.md +1 -1
- package/.agent-src/commands/analytics/show.md +1 -1
- package/.agent-src/commands/analytics.md +1 -1
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/challenge-me.md +1 -1
- package/.agent-src/commands/chat-history/import.md +1 -1
- package/.agent-src/commands/chat-history/learn.md +1 -1
- package/.agent-src/commands/chat-history/show.md +1 -1
- package/.agent-src/commands/chat-history.md +1 -1
- package/.agent-src/commands/check-current-md.md +1 -1
- package/.agent-src/commands/condense.md +1 -1
- package/.agent-src/commands/context.md +1 -1
- package/.agent-src/commands/cost-report.md +13 -8
- package/.agent-src/commands/council.md +3 -3
- package/.agent-src/commands/create-pr/description-only.md +1 -1
- package/.agent-src/commands/create-pr.md +1 -1
- package/.agent-src/commands/e2e-heal.md +1 -1
- package/.agent-src/commands/e2e-plan.md +1 -1
- package/.agent-src/commands/feature.md +1 -1
- package/.agent-src/commands/fix/ci.md +1 -1
- package/.agent-src/commands/fix/portability.md +1 -1
- package/.agent-src/commands/fix/pr-bot-comments.md +1 -1
- package/.agent-src/commands/fix/pr-comments.md +1 -1
- package/.agent-src/commands/fix/pr-developer-comments.md +1 -1
- package/.agent-src/commands/fix/refs.md +1 -1
- package/.agent-src/commands/fix/seeder.md +1 -1
- package/.agent-src/commands/fix.md +1 -1
- package/.agent-src/commands/judge.md +1 -1
- package/.agent-src/commands/knowledge/cross-repo.md +1 -1
- package/.agent-src/commands/knowledge/forget.md +1 -1
- package/.agent-src/commands/knowledge/ingest.md +1 -1
- package/.agent-src/commands/knowledge/list.md +1 -1
- package/.agent-src/commands/knowledge.md +1 -1
- package/.agent-src/commands/memory/add.md +1 -1
- package/.agent-src/commands/memory/learn-low-impact.md +1 -1
- package/.agent-src/commands/memory/load.md +1 -1
- package/.agent-src/commands/memory/mine-session.md +1 -1
- package/.agent-src/commands/memory/promote.md +1 -1
- package/.agent-src/commands/memory/propose.md +1 -1
- package/.agent-src/commands/memory.md +1 -1
- package/.agent-src/commands/mode.md +1 -1
- package/.agent-src/commands/optimize/agents-dir.md +1 -1
- package/.agent-src/commands/optimize/augmentignore.md +1 -1
- package/.agent-src/commands/optimize/rtk.md +1 -1
- package/.agent-src/commands/optimize/skills.md +1 -1
- package/.agent-src/commands/optimize.md +1 -1
- package/.agent-src/commands/orchestrate.md +1 -1
- package/.agent-src/commands/override/create.md +1 -1
- package/.agent-src/commands/override/manage.md +1 -1
- package/.agent-src/commands/override.md +1 -1
- package/.agent-src/commands/package-reset.md +1 -1
- package/.agent-src/commands/prediction-pool.md +234 -0
- package/.agent-src/commands/profile/activate.md +81 -0
- package/.agent-src/commands/profile/deactivate.md +68 -0
- package/.agent-src/commands/profile/show.md +70 -0
- package/.agent-src/commands/profile.md +68 -0
- package/.agent-src/commands/project-health.md +1 -1
- package/.agent-src/commands/quality-fix.md +1 -1
- package/.agent-src/commands/roadmap/process-full.md +1 -1
- package/.agent-src/commands/roadmap/process-phase.md +1 -1
- package/.agent-src/commands/roadmap/process-step.md +1 -1
- package/.agent-src/commands/roadmap.md +1 -1
- package/.agent-src/commands/set-cost-profile.md +9 -9
- package/.agent-src/commands/skill/preview.md +3 -3
- package/.agent-src/commands/skill.md +1 -1
- package/.agent-src/commands/skills/discover.md +1 -1
- package/.agent-src/commands/skills.md +1 -1
- package/.agent-src/commands/sync-agent-settings.md +3 -3
- package/.agent-src/commands/sync-gitignore/fix.md +1 -1
- package/.agent-src/commands/sync-gitignore.md +1 -1
- package/.agent-src/commands/update-form-request-messages.md +1 -1
- package/.agent-src/presets/README.md +1 -1
- package/.agent-src/profiles/README.md +1 -1
- package/.agent-src/rules/non-destructive-by-default.md +2 -1
- package/.agent-src/skills/check-refs/SKILL.md +1 -1
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/.agent-src/skills/git-workflow/SKILL.md +1 -1
- package/.agent-src/skills/jira-integration/SKILL.md +1 -1
- package/.agent-src/skills/markitdown/SKILL.md +1 -1
- package/.agent-src/skills/prediction-pool-optimizer/SKILL.md +314 -0
- package/.agent-src/skills/prediction-pool-optimizer/evals/triggers.json +20 -0
- package/.agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +175 -0
- package/.agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +109 -0
- package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -1
- package/.agent-src/skills/script-writing/SKILL.md +1 -1
- package/.agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/.agent-src/templates/agent-settings.md +7 -7
- package/.agent-src/templates/agents/agent-project-settings.example.yml +2 -2
- package/.agent-src/templates/scripts/work_engine/_lib/agent_settings.py +54 -6
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +1 -1
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.py +9 -7
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +9 -10
- package/.agent-src/templates/scripts/work_engine/scoring/memory_visibility.py +17 -4
- package/.claude-plugin/marketplace.json +370 -364
- package/CHANGELOG.md +108 -0
- package/README.md +2 -2
- package/config/agent-settings.template.yml +11 -2
- package/config/discovery/packs.yml +11 -0
- package/config/discovery/session-profiles.yml +37 -0
- package/config/discovery/workspaces.yml +1 -1
- package/config/profiles/balanced.ini +1 -1
- package/config/profiles/full.ini +1 -1
- package/config/profiles/minimal.ini +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +254 -100
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +41 -6
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +19 -6
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/server/io/substituteTemplate.js +3 -3
- package/dist/server/io/substituteTemplate.js.map +1 -1
- package/dist/server/routes/settings.js +2 -2
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-DVsyUMZe.js → index-5lFqAKL0.js} +2 -2
- package/dist/ui/assets/index-5lFqAKL0.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/architecture/current-onboard-baseline.md +3 -3
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +11 -5
- package/docs/contracts/adr-level-6-productization.md +1 -1
- package/docs/contracts/command-clusters.md +2 -0
- package/docs/contracts/config-presets.md +2 -2
- package/docs/contracts/cost-profile-defaults.md +5 -5
- package/docs/contracts/discovery-manifest.schema.json +1 -1
- package/docs/contracts/explain-trace.schema.json +3 -3
- package/docs/contracts/memory-visibility-v1.md +15 -7
- package/docs/contracts/profile-system.md +2 -2
- package/docs/contracts/session-profile-overlay.md +120 -0
- package/docs/contracts/settings-api.md +3 -3
- package/docs/contracts/value-report-schema.md +14 -1
- package/docs/customization.md +47 -5
- package/docs/decisions/ADR-010-profile-pack-preset-boundary.md +47 -11
- package/docs/decisions/ADR-013-discovery-frontmatter-contract.md +16 -2
- package/docs/decisions/ADR-034-per-skill-model-recommendation-transport.md +1 -1
- package/docs/decisions/ADR-036-global-install-browser-wizard-handoff.md +106 -0
- package/docs/decisions/ADR-037-cost-profile-untangle.md +117 -0
- package/docs/decisions/ADR-038-canonical-settings-path.md +66 -0
- package/docs/decisions/ADR-039-claude-skills-untracked.md +139 -0
- package/docs/decisions/ADR-rule-kernel-and-router.md +1 -1
- package/docs/decisions/INDEX.md +4 -0
- package/docs/development.md +12 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/layered-settings.md +10 -4
- package/docs/installation.md +3 -3
- package/docs/setup/mcp-client-config.md +1 -1
- package/docs/skills-catalog.md +5 -1
- package/docs/value.md +9 -7
- package/docs/wizard.md +1 -1
- package/llms.txt +4 -0
- package/package.json +1 -1
- package/scripts/__pycache__/validate_frontmatter.cpython-312.pyc +0 -0
- package/scripts/_cli/cmd_doctor.py +3 -2
- package/scripts/_cli/cmd_explain.py +1 -1
- package/scripts/_cli/cmd_versions.py +2 -2
- package/scripts/_cli/explain_last/inputs.py +11 -8
- package/scripts/_cli/explain_last/sections/inputs.py +1 -1
- 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 +54 -6
- package/scripts/_lib/agent_src.py +30 -0
- package/scripts/_lib/value_ladder.py +99 -2
- package/scripts/_lib/value_report.py +30 -16
- package/scripts/ai_council/modes.py +1 -1
- package/scripts/ai_council/session.py +5 -1
- package/scripts/audit_command_surface.py +7 -1
- package/scripts/audit_initial_context.py +26 -2
- package/scripts/check_gate_paths.py +117 -0
- package/scripts/check_references.py +51 -2
- package/scripts/check_skill_requires.py +143 -0
- package/scripts/check_test_coverage_diff.py +180 -0
- package/scripts/compile_router.py +5 -1
- package/scripts/condense.py +92 -4
- package/scripts/config/session_profiles.py +492 -0
- package/scripts/council_cli.py +5 -1
- package/scripts/first-run.sh +11 -11
- package/scripts/hook_manifest.yaml +15 -7
- package/scripts/hooks/dispatch_hook.py +8 -0
- package/scripts/install +14 -1
- package/scripts/install-hooks.sh +2 -1
- package/scripts/install.py +203 -433
- package/scripts/install_anthropic_key.sh +1 -1
- package/scripts/install_openai_key.sh +1 -1
- package/scripts/inventory_abstraction_budget.py +6 -1
- package/scripts/lint_agents_md.py +11 -4
- package/scripts/lint_discovery_vocabulary.py +5 -5
- package/scripts/lint_hook_concern_budget.py +5 -1
- package/scripts/lint_marketplace.py +18 -7
- package/scripts/lint_roadmap_ci_steps.py +5 -1
- package/scripts/lint_roadmap_complexity.py +5 -1
- package/scripts/lint_value_dashboard.py +1 -1
- package/scripts/mcp_server/prompts.py +5 -1
- package/scripts/prediction-pool/adapters/_schema.md +42 -0
- package/scripts/prediction-pool/adapters/kicktipp.yml +23 -0
- package/scripts/prediction-pool/poisson_sim.py +167 -0
- package/scripts/prediction-pool/pool_winsim.py +236 -0
- package/scripts/prediction-pool/score_ev.py +188 -0
- package/scripts/profile_staleness_hook.py +69 -0
- package/scripts/render_value_md.py +1 -0
- package/scripts/roadmap_progress_hook.py +56 -6
- package/scripts/schemas/agent-settings.schema.json +77 -0
- package/scripts/schemas/skill.schema.json +7 -0
- package/scripts/smoke_quickstart.py +7 -6
- package/scripts/sync_agent_settings.py +12 -5
- package/scripts/validate_agent_settings.py +124 -0
- package/scripts/validate_decision_engine.py +5 -1
- package/templates/minimal/.agent-settings.yml +1 -1
- package/dist/ui/assets/index-DVsyUMZe.js.map +0 -1
- package/scripts/measure_roadmap_trajectory.py +0 -112
- package/scripts/verify_roadmap_closure.py +0 -327
|
@@ -43,10 +43,12 @@ try:
|
|
|
43
43
|
condense_rung_from_telegraph_v2,
|
|
44
44
|
destructive_stops_metric,
|
|
45
45
|
load_rung_from_frugality,
|
|
46
|
+
load_rung_from_projection,
|
|
46
47
|
load_rung_from_router,
|
|
47
48
|
rtk_rung_from_report,
|
|
48
49
|
selection_metric_from_dev_reports,
|
|
49
50
|
terse_rung_from_telegraph_v1,
|
|
51
|
+
thin_rung_from_projection,
|
|
50
52
|
)
|
|
51
53
|
except ImportError:
|
|
52
54
|
from scripts._lib.value_ladder import ( # type: ignore[no-redef]
|
|
@@ -59,15 +61,18 @@ except ImportError:
|
|
|
59
61
|
condense_rung_from_telegraph_v2,
|
|
60
62
|
destructive_stops_metric,
|
|
61
63
|
load_rung_from_frugality,
|
|
64
|
+
load_rung_from_projection,
|
|
62
65
|
load_rung_from_router,
|
|
63
66
|
rtk_rung_from_report,
|
|
64
67
|
selection_metric_from_dev_reports,
|
|
65
68
|
terse_rung_from_telegraph_v1,
|
|
69
|
+
thin_rung_from_projection,
|
|
66
70
|
)
|
|
67
71
|
|
|
68
72
|
|
|
69
73
|
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
70
74
|
ROUTER_JSON = REPO_ROOT / "dist" / "router.json"
|
|
75
|
+
PROJECTION_COST = REPO_ROOT / "internal" / "bench" / "reports" / "projection-cost.json"
|
|
71
76
|
RULES_DIR = REPO_ROOT / ".agent-src" / "rules"
|
|
72
77
|
CHARTER_PATH = REPO_ROOT / ".agent-src" / "contexts" / "contracts" / "frugality-charter.md"
|
|
73
78
|
FRUGALITY_BASELINE = REPO_ROOT / "agents" / "runtime" / "frugality" / "baseline.jsonl"
|
|
@@ -240,28 +245,37 @@ def assemble_value_v1(
|
|
|
240
245
|
# Load rung — prefer the canonical kernel list from dist/router.json
|
|
241
246
|
# (real always-loaded footprint), fall back to the frugality canon
|
|
242
247
|
# baseline only when the router is missing on disk.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
248
|
+
# Prefer the REAL eager footprint (projection-cost.json) — 0B.6 confirmed
|
|
249
|
+
# the primary tool eager-loads every rule body. Fall back to the
|
|
250
|
+
# kernel-only router rung, then the frugality canon, when the projection
|
|
251
|
+
# report is missing.
|
|
252
|
+
projection = safe_load_json(PROJECTION_COST)
|
|
253
|
+
load_rung = load_rung_from_projection(projection, ref, pricing_row)
|
|
254
|
+
if load_rung is None:
|
|
255
|
+
router = safe_load_json(ROUTER_JSON)
|
|
256
|
+
if router and "kernel" in router:
|
|
257
|
+
rule_chars = {
|
|
258
|
+
p.stem: len(p.read_text())
|
|
259
|
+
for p in RULES_DIR.glob("*.md")
|
|
260
|
+
} if RULES_DIR.exists() else {}
|
|
261
|
+
charter_chars = (
|
|
262
|
+
len(CHARTER_PATH.read_text()) if CHARTER_PATH.exists() else 0
|
|
263
|
+
)
|
|
264
|
+
load_rung = load_rung_from_router(
|
|
265
|
+
router, rule_chars, charter_chars, ref, pricing_row
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
load_rung = load_rung_from_frugality(
|
|
269
|
+
latest_frugality_record(), ref, pricing_row
|
|
270
|
+
)
|
|
271
|
+
thin_rung = thin_rung_from_projection(projection, ref, pricing_row)
|
|
259
272
|
t2 = safe_load_json(TELEGRAPH_V2)
|
|
260
273
|
t1 = safe_load_json(TELEGRAPH_V1)
|
|
261
274
|
rtk = safe_load_json(RTK_LATEST)
|
|
262
275
|
ladder: List[Dict[str, Any]] = [
|
|
263
276
|
baseline_rung(ref),
|
|
264
277
|
load_rung,
|
|
278
|
+
thin_rung,
|
|
265
279
|
condense_rung_from_telegraph_v2(t2, baseline_input_tokens, ref, pricing_row),
|
|
266
280
|
rtk_rung_from_report(rtk, ref, pricing_row),
|
|
267
281
|
terse_rung_from_telegraph_v1(t1, ref, pricing_row),
|
|
@@ -17,7 +17,7 @@ Resolution precedence — first non-empty wins:
|
|
|
17
17
|
3. Global setting ``ai_council.mode``
|
|
18
18
|
4. Built-in default ``manual``
|
|
19
19
|
|
|
20
|
-
This mirrors how ``
|
|
20
|
+
This mirrors how ``rule_loading_tier`` resolves in
|
|
21
21
|
``.augment/guidelines/agent-infra/layered-settings.md``.
|
|
22
22
|
|
|
23
23
|
The resolver is pure — it never touches the filesystem or environment.
|
|
@@ -28,6 +28,10 @@ import shutil
|
|
|
28
28
|
import sys
|
|
29
29
|
from dataclasses import dataclass, field
|
|
30
30
|
from pathlib import Path
|
|
31
|
+
try: # invocation-agnostic import (repo-root-on-path vs scripts-on-path)
|
|
32
|
+
from scripts._lib.agent_settings import project_settings_path
|
|
33
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
34
|
+
from _lib.agent_settings import project_settings_path
|
|
31
35
|
from typing import Iterable
|
|
32
36
|
|
|
33
37
|
from scripts.ai_council.clients import CouncilResponse
|
|
@@ -37,7 +41,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
37
41
|
SESSIONS_DIR = REPO_ROOT / "agents" / "runtime" / "council" / "sessions"
|
|
38
42
|
QUESTIONS_DIR = REPO_ROOT / "agents" / "runtime" / "council" / "questions"
|
|
39
43
|
RESPONSES_DIR = REPO_ROOT / "agents" / "runtime" / "council" / "responses"
|
|
40
|
-
SETTINGS_FILE = REPO_ROOT
|
|
44
|
+
SETTINGS_FILE = project_settings_path(REPO_ROOT)
|
|
41
45
|
|
|
42
46
|
# Default retention for all council artefacts (questions, responses,
|
|
43
47
|
# sessions). Overridden by `ai_council.session_retention_days`
|
|
@@ -37,12 +37,18 @@ from pathlib import Path
|
|
|
37
37
|
from typing import List
|
|
38
38
|
|
|
39
39
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
40
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
41
|
+
from _lib.agent_src import resolve_package_core_path # noqa: E402
|
|
42
|
+
|
|
40
43
|
# Pre-monorepo: REPO_ROOT/.agent-src.uncondensed/commands. Post-move (ADR-017)
|
|
41
44
|
# the core command surface lives under packages/core/.agent-src.uncondensed.
|
|
42
45
|
# Fall back to the legacy path only if the packages layout is absent.
|
|
43
|
-
_CORE_COMMANDS =
|
|
46
|
+
_CORE_COMMANDS = resolve_package_core_path(".agent-src.uncondensed/commands")
|
|
44
47
|
_LEGACY_COMMANDS = REPO_ROOT / ".agent-src.uncondensed" / "commands"
|
|
45
48
|
DEFAULT_ROOT = _CORE_COMMANDS if _CORE_COMMANDS.is_dir() else _LEGACY_COMMANDS
|
|
49
|
+
# Enforced packages/core target — read by scripts/check_gate_paths.py so a
|
|
50
|
+
# future move that desyncs this path fails CI instead of silently no-opping.
|
|
51
|
+
GATE_CORE_PATHS = (_CORE_COMMANDS,)
|
|
46
52
|
REPORT_DIR = REPO_ROOT / "agents" / "reports"
|
|
47
53
|
OUT_JSON = REPORT_DIR / "command-surface.json"
|
|
48
54
|
OUT_MD = REPORT_DIR / "command-surface.md"
|
|
@@ -35,6 +35,13 @@ from pathlib import Path
|
|
|
35
35
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
36
36
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
37
37
|
from _lib import token_count # noqa: E402
|
|
38
|
+
from _lib.agent_src import resolve_package_core_path # noqa: E402
|
|
39
|
+
|
|
40
|
+
_CORE_SRC = resolve_package_core_path(".agent-src.uncondensed")
|
|
41
|
+
# Enforced packages/core targets — the skills + commands dirs the
|
|
42
|
+
# description-catalog globs scan. Read by scripts/check_gate_paths.py so a
|
|
43
|
+
# future move that desyncs them fails CI instead of silently no-opping.
|
|
44
|
+
GATE_CORE_PATHS = (_CORE_SRC / "skills", _CORE_SRC / "commands")
|
|
38
45
|
|
|
39
46
|
try:
|
|
40
47
|
import yaml
|
|
@@ -111,10 +118,11 @@ def _catalog(glob_pat: str) -> dict:
|
|
|
111
118
|
|
|
112
119
|
def description_catalog() -> dict:
|
|
113
120
|
"""0B.4 — description-catalog cost (eager progressive-disclosure surface)."""
|
|
121
|
+
core_rel = _CORE_SRC.relative_to(REPO_ROOT).as_posix()
|
|
114
122
|
return {
|
|
115
123
|
"skills_projected": _catalog(".claude/skills/*/SKILL.md"),
|
|
116
|
-
"skills_core_source": _catalog("
|
|
117
|
-
"commands_core_source": _catalog("
|
|
124
|
+
"skills_core_source": _catalog(f"{core_rel}/skills/*/SKILL.md"),
|
|
125
|
+
"commands_core_source": _catalog(f"{core_rel}/commands/**/*.md"),
|
|
118
126
|
}
|
|
119
127
|
|
|
120
128
|
|
|
@@ -132,11 +140,27 @@ def longest_rules(top: int = 10) -> list[dict]:
|
|
|
132
140
|
return rows[:top]
|
|
133
141
|
|
|
134
142
|
|
|
143
|
+
def thin_projection() -> dict:
|
|
144
|
+
"""Eager-vs-thin rule-layer footprint (Phase 3.1 lever).
|
|
145
|
+
|
|
146
|
+
Reuses `scripts/project_thin_rules.py::measure` so the value dashboard can
|
|
147
|
+
cite a single persisted source for both the eager always-on cost and the
|
|
148
|
+
thin-projection saving. Returns an empty dict if the measurer is
|
|
149
|
+
unavailable, so the audit never hard-fails on it.
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
from project_thin_rules import measure as _measure # noqa: E402
|
|
153
|
+
return _measure()
|
|
154
|
+
except Exception: # pragma: no cover — best-effort enrichment
|
|
155
|
+
return {}
|
|
156
|
+
|
|
157
|
+
|
|
135
158
|
def build() -> dict:
|
|
136
159
|
return {
|
|
137
160
|
"generated": _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds"),
|
|
138
161
|
"token_method": token_count.method_note(),
|
|
139
162
|
"rule_footprint": rule_footprint(),
|
|
163
|
+
"thin_projection": thin_projection(),
|
|
140
164
|
"description_catalog": description_catalog(),
|
|
141
165
|
"longest_rules": longest_rules(),
|
|
142
166
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Gate path-integrity check (R2 of road-to-test-and-gate-integrity).
|
|
3
|
+
|
|
4
|
+
Asserts that every security/quality gate which enforces something against
|
|
5
|
+
a fixed ``packages/core/`` target still resolves that target on disk. A
|
|
6
|
+
``packages/core/`` move that desyncs a gate's hard-coded path fails CI here
|
|
7
|
+
instead of silently no-opping (the ``aab5755`` class: the Iron-Law SHA gate
|
|
8
|
+
pointed at a stale path and enforced nothing while CI stayed green).
|
|
9
|
+
|
|
10
|
+
Design (AI council, claude-sonnet-4-5 + gpt-4o, 2026-06-02):
|
|
11
|
+
|
|
12
|
+
- The check reads each gate's ACTUAL enforced paths via its module-level
|
|
13
|
+
``GATE_CORE_PATHS`` attribute — it does NOT re-declare a copy of the path
|
|
14
|
+
strings. A hand-maintained path registry would reintroduce the very
|
|
15
|
+
desync risk this guards against, one layer down.
|
|
16
|
+
- Scope is strictly the single-root hard-coders. Multi-root gates that
|
|
17
|
+
resolve via ``artefact_roots()`` (e.g. ``iron_law_sha``) are excluded:
|
|
18
|
+
asserting a single ``packages/core/`` path for them would false-pass on a
|
|
19
|
+
legacy layout or false-fail on a pack-only layout.
|
|
20
|
+
|
|
21
|
+
The input set is this gate list (no separate config file).
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
python3 scripts/check_gate_paths.py
|
|
25
|
+
Exit codes: 0 = all enforced targets resolve under packages/core/ ·
|
|
26
|
+
1 = at least one missing / out-of-tree target · 2 = a gate failed to import.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import importlib
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
35
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
36
|
+
from _lib.agent_src import resolve_package_core_path # noqa: E402
|
|
37
|
+
|
|
38
|
+
PACKAGE_CORE = resolve_package_core_path("")
|
|
39
|
+
|
|
40
|
+
# Single-root gates that enforce against a fixed packages/core/ target and
|
|
41
|
+
# expose it via a module-level GATE_CORE_PATHS tuple. Adding a gate here is
|
|
42
|
+
# the only manual step; its paths are read from the gate, never copied.
|
|
43
|
+
GATES: tuple[str, ...] = (
|
|
44
|
+
"inventory_abstraction_budget",
|
|
45
|
+
"audit_command_surface",
|
|
46
|
+
"lint_agents_md",
|
|
47
|
+
"audit_initial_context",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _is_under_core(p: Path) -> bool:
|
|
52
|
+
try:
|
|
53
|
+
p.resolve().relative_to(PACKAGE_CORE.resolve())
|
|
54
|
+
return True
|
|
55
|
+
except ValueError:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def collect_gate_paths(gate_modules: tuple[str, ...]) -> dict[str, list[Path]]:
|
|
60
|
+
"""Import each gate and read its declared ``GATE_CORE_PATHS``.
|
|
61
|
+
|
|
62
|
+
Raises ``ImportError`` (surfaced as exit 2 by ``main``) if a gate cannot
|
|
63
|
+
be imported — a gate whose path logic broke at import time is itself a
|
|
64
|
+
failure this check should not swallow.
|
|
65
|
+
"""
|
|
66
|
+
out: dict[str, list[Path]] = {}
|
|
67
|
+
for name in gate_modules:
|
|
68
|
+
mod = importlib.import_module(name)
|
|
69
|
+
paths = getattr(mod, "GATE_CORE_PATHS", None)
|
|
70
|
+
if not paths:
|
|
71
|
+
raise AttributeError(
|
|
72
|
+
f"{name} has no non-empty GATE_CORE_PATHS — gate cannot be "
|
|
73
|
+
f"checked. Declare the packages/core targets it enforces."
|
|
74
|
+
)
|
|
75
|
+
out[name] = [Path(p) for p in paths]
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def check_paths(named: dict[str, list[Path]]) -> list[tuple[str, str, Path]]:
|
|
80
|
+
"""Return ``(gate, reason, path)`` for every target that fails.
|
|
81
|
+
|
|
82
|
+
Pure (no import side effects) so tests can drive it with fixtures.
|
|
83
|
+
A target fails when it does not resolve under ``packages/core/`` or
|
|
84
|
+
does not exist on disk.
|
|
85
|
+
"""
|
|
86
|
+
failures: list[tuple[str, str, Path]] = []
|
|
87
|
+
for gate, paths in named.items():
|
|
88
|
+
for p in paths:
|
|
89
|
+
if not _is_under_core(p):
|
|
90
|
+
failures.append((gate, "not under packages/core/", p))
|
|
91
|
+
elif not p.exists():
|
|
92
|
+
failures.append((gate, "target does not exist", p))
|
|
93
|
+
return failures
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main() -> int:
|
|
97
|
+
try:
|
|
98
|
+
named = collect_gate_paths(GATES)
|
|
99
|
+
except (ImportError, AttributeError) as exc:
|
|
100
|
+
print(f"❌ check-gate-paths: {exc}", file=sys.stderr)
|
|
101
|
+
return 2
|
|
102
|
+
failures = check_paths(named)
|
|
103
|
+
if failures:
|
|
104
|
+
print("❌ check-gate-paths: gate target(s) do not resolve under packages/core/:")
|
|
105
|
+
for gate, reason, path in failures:
|
|
106
|
+
print(f" {gate}: {reason} → {path}")
|
|
107
|
+
print("\n A packages/core/ move likely desynced a gate. Fix the gate's")
|
|
108
|
+
print(" GATE_CORE_PATHS (built via resolve_package_core_path) or the move.")
|
|
109
|
+
return 1
|
|
110
|
+
total = sum(len(v) for v in named.values())
|
|
111
|
+
print(f"✅ check-gate-paths: {total} enforced target(s) across "
|
|
112
|
+
f"{len(named)} gate(s) resolve under packages/core/.")
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
raise SystemExit(main())
|
|
@@ -133,6 +133,53 @@ EXAMPLE_PATH_PATTERNS = [
|
|
|
133
133
|
]
|
|
134
134
|
|
|
135
135
|
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class AllowlistPattern:
|
|
138
|
+
"""A token-class allowlist entry. `reason` is mandatory and auditable."""
|
|
139
|
+
pattern: "re.Pattern[str]"
|
|
140
|
+
reason: str
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Content-class allowlist for known NON-reference token shapes.
|
|
144
|
+
#
|
|
145
|
+
# The skill/rule prose patterns (`X` skill / `X` rule) occasionally match
|
|
146
|
+
# a backtick token that is not an artifact id — an execution-type enum
|
|
147
|
+
# value, a pack identifier, or a bare meta-qualifier keyword. Historically
|
|
148
|
+
# each such false positive was dodged by *rewording the prose per file*
|
|
149
|
+
# (e.g. dc84ed01 "reword execution-type mentions to dodge check-refs
|
|
150
|
+
# false positive", bd02ef0b "avoid check-refs false-positive on pack
|
|
151
|
+
# name"), a treadmill that distorts natural wording release after release.
|
|
152
|
+
# This layer matches the token *class* centrally instead, so the natural
|
|
153
|
+
# wording passes without per-file edits. It is distinct from:
|
|
154
|
+
# - SKIP_DIRS (path-level, whole-directory)
|
|
155
|
+
# - FILE_SKIP_MARKER (file-level opt-out)
|
|
156
|
+
# - LINE_IGNORE_MARKER (per-line opt-out)
|
|
157
|
+
# Every entry carries a mandatory `reason` so the allowlist stays
|
|
158
|
+
# auditable and a future reader can tell why a class is exempt.
|
|
159
|
+
ALLOWLIST_PATTERNS: List[AllowlistPattern] = [
|
|
160
|
+
AllowlistPattern(
|
|
161
|
+
re.compile(r"^(?:manual|assisted|automated)$"),
|
|
162
|
+
"execution-type enum value (runtime-safety frontmatter), e.g. a "
|
|
163
|
+
"`manual` skill — not a skill/rule id (dc84ed01)",
|
|
164
|
+
),
|
|
165
|
+
AllowlistPattern(
|
|
166
|
+
re.compile(r"^pack-[\w-]+$"),
|
|
167
|
+
"pack / workspace identifier, e.g. `pack-ai-video` skills — not a "
|
|
168
|
+
"skill/rule id (bd02ef0b)",
|
|
169
|
+
),
|
|
170
|
+
AllowlistPattern(
|
|
171
|
+
re.compile(r"^(?:skill|rule|command|guideline|persona|context|pack|workspace)$"),
|
|
172
|
+
"bare meta-qualifier keyword used in prose (the `command` vs "
|
|
173
|
+
"`skill` distinction, etc.) — not an artifact id",
|
|
174
|
+
),
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_allowlisted(name: str) -> bool:
|
|
179
|
+
"""True when `name` matches a known non-reference token class."""
|
|
180
|
+
return any(entry.pattern.match(name) for entry in ALLOWLIST_PATTERNS)
|
|
181
|
+
|
|
182
|
+
|
|
136
183
|
def collect_artifacts(root: Path) -> dict[str, set[str]]:
|
|
137
184
|
"""Build lookup sets for skills, rules, commands, guidelines, personas."""
|
|
138
185
|
arts: dict[str, set[str]] = {
|
|
@@ -345,7 +392,8 @@ def check_file(filepath: Path, artifacts: dict[str, set[str]], root: Path) -> Li
|
|
|
345
392
|
# Skill name references
|
|
346
393
|
for m in SKILL_REF_PATTERN.finditer(line):
|
|
347
394
|
name = m.group(1)
|
|
348
|
-
if name not in artifacts["skills"] and name not in _SKIP_NAMES
|
|
395
|
+
if name not in artifacts["skills"] and name not in _SKIP_NAMES \
|
|
396
|
+
and not _is_allowlisted(name):
|
|
349
397
|
broken.append(BrokenRef(
|
|
350
398
|
file=str(filepath), line=i, ref=name,
|
|
351
399
|
ref_type="skill", severity="warning",
|
|
@@ -355,7 +403,8 @@ def check_file(filepath: Path, artifacts: dict[str, set[str]], root: Path) -> Li
|
|
|
355
403
|
# Rule name references
|
|
356
404
|
for m in RULE_REF_PATTERN.finditer(line):
|
|
357
405
|
name = m.group(1)
|
|
358
|
-
if name not in artifacts["rules"] and name not in _SKIP_NAMES
|
|
406
|
+
if name not in artifacts["rules"] and name not in _SKIP_NAMES \
|
|
407
|
+
and not _is_allowlisted(name):
|
|
359
408
|
broken.append(BrokenRef(
|
|
360
409
|
file=str(filepath), line=i, ref=name,
|
|
361
410
|
ref_type="rule", severity="warning",
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Skill-composition graph gate (roadmap 3.4).
|
|
3
|
+
|
|
4
|
+
Validates the `requires_skills:` frontmatter field (the skill→skill
|
|
5
|
+
composition graph) against two invariants:
|
|
6
|
+
|
|
7
|
+
1. **Referential integrity** — every `requires_skills` target names a real
|
|
8
|
+
skill in the suite.
|
|
9
|
+
2. **Co-availability** — whenever a parent skill ships, every sub-skill its
|
|
10
|
+
body assumes must ship too. A sub-skill is co-available under a parent's
|
|
11
|
+
pack `P` iff one of the sub-skill's packs is in `{P}` ∪ the transitive
|
|
12
|
+
`requires_hint` closure of `P` (from `config/discovery/packs.yml`), or
|
|
13
|
+
the sub-skill is always-on (no pack). A parent with no pack (always-on)
|
|
14
|
+
may only require always-on sub-skills.
|
|
15
|
+
|
|
16
|
+
This is distinct from the ADR-015 artefact→pack `requires` field; this gate
|
|
17
|
+
operates on `requires_skills` (skill→skill) only.
|
|
18
|
+
|
|
19
|
+
Exit 0 = clean · 1 = at least one violation.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
27
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent / "_lib"))
|
|
28
|
+
|
|
29
|
+
import yaml # noqa: E402
|
|
30
|
+
|
|
31
|
+
from _lib.agent_src import ROOT, iter_artefacts # noqa: E402
|
|
32
|
+
from validate_frontmatter import parse_frontmatter # noqa: E402
|
|
33
|
+
|
|
34
|
+
PACKS_YML = ROOT / "config" / "discovery" / "packs.yml"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_pack_closure() -> dict[str, set[str]]:
|
|
38
|
+
"""pack_id → transitive set of {self} ∪ requires_hint closure."""
|
|
39
|
+
raw = yaml.safe_load(PACKS_YML.read_text(encoding="utf-8")) or []
|
|
40
|
+
direct: dict[str, set[str]] = {}
|
|
41
|
+
for entry in raw:
|
|
42
|
+
pid = entry["id"]
|
|
43
|
+
direct[pid] = set(entry.get("requires_hint") or [])
|
|
44
|
+
|
|
45
|
+
closure: dict[str, set[str]] = {}
|
|
46
|
+
|
|
47
|
+
def resolve(pid: str, seen: set[str]) -> set[str]:
|
|
48
|
+
if pid in closure:
|
|
49
|
+
return closure[pid]
|
|
50
|
+
acc = {pid}
|
|
51
|
+
for dep in direct.get(pid, set()):
|
|
52
|
+
if dep in seen:
|
|
53
|
+
continue
|
|
54
|
+
acc |= resolve(dep, seen | {pid})
|
|
55
|
+
closure[pid] = acc
|
|
56
|
+
return acc
|
|
57
|
+
|
|
58
|
+
for pid in direct:
|
|
59
|
+
resolve(pid, set())
|
|
60
|
+
return closure
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _collect_skills() -> dict[str, dict]:
|
|
64
|
+
"""skill_id (directory name) → {packs: set[str], requires_skills: list[str], path}."""
|
|
65
|
+
skills: dict[str, dict] = {}
|
|
66
|
+
for path in iter_artefacts("SKILL.md"):
|
|
67
|
+
# logical id = the skill's directory name
|
|
68
|
+
skill_id = path.parent.name
|
|
69
|
+
fm, _ = parse_frontmatter(path.read_text(encoding="utf-8"))
|
|
70
|
+
if fm is None:
|
|
71
|
+
continue
|
|
72
|
+
skills[skill_id] = {
|
|
73
|
+
"packs": set(fm.get("packs") or []),
|
|
74
|
+
"requires_skills": list(fm.get("requires_skills") or []),
|
|
75
|
+
"path": path.relative_to(ROOT).as_posix(),
|
|
76
|
+
}
|
|
77
|
+
return skills
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main() -> int:
|
|
81
|
+
closure = _load_pack_closure()
|
|
82
|
+
skills = _collect_skills()
|
|
83
|
+
errors: list[str] = []
|
|
84
|
+
|
|
85
|
+
for skill_id, info in sorted(skills.items()):
|
|
86
|
+
reqs = info["requires_skills"]
|
|
87
|
+
if not reqs:
|
|
88
|
+
continue
|
|
89
|
+
parent_packs: set[str] = info["packs"]
|
|
90
|
+
for req in reqs:
|
|
91
|
+
target = skills.get(req)
|
|
92
|
+
# (1) referential integrity
|
|
93
|
+
if target is None:
|
|
94
|
+
errors.append(
|
|
95
|
+
f"{info['path']}: requires_skills → unknown skill '{req}' "
|
|
96
|
+
f"(no skills/{req}/SKILL.md in the suite)."
|
|
97
|
+
)
|
|
98
|
+
continue
|
|
99
|
+
# (2) co-availability
|
|
100
|
+
req_packs: set[str] = target["packs"]
|
|
101
|
+
if not req_packs:
|
|
102
|
+
# always-on sub-skill is reachable from anywhere
|
|
103
|
+
continue
|
|
104
|
+
if not parent_packs:
|
|
105
|
+
# always-on parent may only require an always-on sub-skill
|
|
106
|
+
errors.append(
|
|
107
|
+
f"{info['path']}: always-on skill '{skill_id}' requires "
|
|
108
|
+
f"'{req}' which is pack-gated ({sorted(req_packs)}); a base "
|
|
109
|
+
f"install would ship '{skill_id}' without '{req}'."
|
|
110
|
+
)
|
|
111
|
+
continue
|
|
112
|
+
for p in sorted(parent_packs):
|
|
113
|
+
reachable = closure.get(p, {p})
|
|
114
|
+
if req_packs & reachable:
|
|
115
|
+
continue
|
|
116
|
+
hint = sorted(req_packs - reachable)
|
|
117
|
+
errors.append(
|
|
118
|
+
f"{info['path']}: skill '{skill_id}' (pack '{p}') requires "
|
|
119
|
+
f"'{req}' (pack {sorted(req_packs)}), but '{p}' does not reach "
|
|
120
|
+
f"it. Add requires_hint: {hint} to pack '{p}' in "
|
|
121
|
+
f"config/discovery/packs.yml, or move '{req}' into a reachable pack."
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if errors:
|
|
125
|
+
print("❌ check_skill_requires: skill-composition graph has unmet edges:")
|
|
126
|
+
for e in errors:
|
|
127
|
+
print(f" 🔴 {e}")
|
|
128
|
+
print(
|
|
129
|
+
"\nEvery sub-skill a parent's body invokes must ship wherever the "
|
|
130
|
+
"parent ships. Declare the missing pack dependency or co-locate the skill."
|
|
131
|
+
)
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
n_edges = sum(len(i["requires_skills"]) for i in skills.values())
|
|
135
|
+
print(
|
|
136
|
+
f"✅ check_skill_requires: {n_edges} composition edge(s) across "
|
|
137
|
+
f"{sum(1 for i in skills.values() if i['requires_skills'])} skill(s) — all sub-skills co-available."
|
|
138
|
+
)
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
raise SystemExit(main())
|