@event4u/agent-config 1.16.0 → 1.18.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/{agents-audit.md → agents/audit.md} +4 -3
- package/.agent-src/commands/{agents-cleanup.md → agents/cleanup.md} +12 -6
- package/.agent-src/commands/{agents-prepare.md → agents/prepare.md} +4 -3
- package/.agent-src/commands/agents.md +46 -0
- package/.agent-src/commands/{chat-history-checkpoint.md → chat-history/checkpoint.md} +4 -4
- package/.agent-src/commands/{chat-history-clear.md → chat-history/clear.md} +4 -4
- package/.agent-src/commands/{chat-history-resume.md → chat-history/resume.md} +4 -4
- package/.agent-src/commands/chat-history/show.md +107 -0
- package/.agent-src/commands/chat-history.md +33 -89
- package/.agent-src/commands/{commit-in-chunks.md → commit/in-chunks.md} +15 -13
- package/.agent-src/commands/commit.md +22 -2
- package/.agent-src/commands/{context-create.md → context/create.md} +4 -3
- package/.agent-src/commands/{context-refactor.md → context/refactor.md} +4 -3
- package/.agent-src/commands/context.md +44 -0
- package/.agent-src/commands/{copilot-agents-init.md → copilot-agents/init.md} +4 -3
- package/.agent-src/commands/{copilot-agents-optimize.md → copilot-agents/optimize.md} +4 -3
- package/.agent-src/commands/copilot-agents.md +44 -0
- package/.agent-src/commands/council/default.md +221 -0
- package/.agent-src/commands/{council-design.md → council/design.md} +6 -5
- package/.agent-src/commands/{council-optimize.md → council/optimize.md} +7 -6
- package/.agent-src/commands/{council-pr.md → council/pr.md} +6 -5
- package/.agent-src/commands/council.md +47 -212
- package/.agent-src/commands/{create-pr-description.md → create-pr/description-only.md} +4 -2
- package/.agent-src/commands/create-pr.md +26 -5
- package/.agent-src/commands/{feature-dev.md → feature/dev.md} +5 -10
- package/.agent-src/commands/{feature-explore.md → feature/explore.md} +4 -8
- package/.agent-src/commands/{feature-plan.md → feature/plan.md} +4 -8
- package/.agent-src/commands/{feature-refactor.md → feature/refactor.md} +4 -8
- package/.agent-src/commands/{feature-roadmap.md → feature/roadmap.md} +6 -10
- package/.agent-src/commands/feature.md +6 -12
- package/.agent-src/commands/{fix-ci.md → fix/ci.md} +4 -8
- package/.agent-src/commands/{fix-portability.md → fix/portability.md} +4 -8
- package/.agent-src/commands/{fix-pr-bot-comments.md → fix/pr-bots.md} +4 -8
- package/.agent-src/commands/{fix-pr-developer-comments.md → fix/pr-developers.md} +4 -8
- package/.agent-src/commands/{fix-pr-comments.md → fix/pr.md} +7 -11
- package/.agent-src/commands/{fix-references.md → fix/refs.md} +4 -8
- package/.agent-src/commands/{fix-seeder.md → fix/seeder.md} +4 -8
- package/.agent-src/commands/fix.md +7 -13
- package/.agent-src/commands/{do-and-judge.md → judge/on-diff.md} +4 -3
- package/.agent-src/commands/judge/solo.md +90 -0
- package/.agent-src/commands/{do-in-steps.md → judge/steps.md} +4 -3
- package/.agent-src/commands/judge.md +35 -70
- package/.agent-src/commands/{memory-add.md → memory/add.md} +4 -3
- package/.agent-src/commands/{memory-full.md → memory/load.md} +4 -3
- package/.agent-src/commands/{memory-promote.md → memory/promote.md} +4 -3
- package/.agent-src/commands/{propose-memory.md → memory/propose.md} +4 -3
- package/.agent-src/commands/memory.md +48 -0
- package/.agent-src/commands/{module-create.md → module/create.md} +4 -3
- package/.agent-src/commands/{module-explore.md → module/explore.md} +4 -3
- package/.agent-src/commands/module.md +44 -0
- package/.agent-src/commands/{optimize-agents.md → optimize/agents.md} +4 -8
- package/.agent-src/commands/{optimize-augmentignore.md → optimize/augmentignore.md} +4 -9
- package/.agent-src/commands/{optimize-rtk-filters.md → optimize/rtk.md} +4 -8
- package/.agent-src/commands/{optimize-skills.md → optimize/skills.md} +4 -8
- package/.agent-src/commands/optimize.md +4 -10
- package/.agent-src/commands/{override-create.md → override/create.md} +4 -3
- package/.agent-src/commands/{override-manage.md → override/manage.md} +4 -3
- package/.agent-src/commands/override.md +44 -0
- package/.agent-src/commands/{roadmap-create.md → roadmap/create.md} +4 -3
- package/.agent-src/commands/{roadmap-execute.md → roadmap/execute.md} +4 -3
- package/.agent-src/commands/roadmap.md +44 -0
- package/.agent-src/commands/{tests-create.md → tests/create.md} +4 -3
- package/.agent-src/commands/{tests-execute.md → tests/execute.md} +4 -3
- package/.agent-src/commands/tests.md +44 -0
- package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +72 -0
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +79 -0
- package/.agent-src/contexts/communication/rules-auto/augment-source-of-truth-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +87 -0
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +85 -0
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +65 -0
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +55 -0
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +53 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +77 -0
- package/.agent-src/contexts/judges/no-consolidate-rationale.md +102 -0
- package/.agent-src/contexts/judges/persona-voice-rubric.md +140 -0
- package/.agent-src/rules/artifact-engagement-recording.md +13 -69
- package/.agent-src/rules/ask-when-uncertain.md +27 -42
- package/.agent-src/rules/augment-portability.md +15 -61
- package/.agent-src/rules/augment-source-of-truth.md +27 -93
- package/.agent-src/rules/cli-output-handling.md +10 -76
- package/.agent-src/rules/command-suggestion-policy.md +18 -59
- package/.agent-src/rules/commit-conventions.md +17 -14
- package/.agent-src/rules/context-hygiene.md +6 -0
- package/.agent-src/rules/direct-answers.md +35 -59
- package/.agent-src/rules/docker-commands.md +5 -5
- package/.agent-src/rules/docs-sync.md +15 -69
- package/.agent-src/rules/language-and-tone.md +48 -72
- package/.agent-src/rules/missing-tool-handling.md +28 -22
- package/.agent-src/rules/no-cheap-questions.md +39 -53
- package/.agent-src/rules/no-roadmap-references.md +73 -0
- package/.agent-src/rules/onboarding-gate.md +7 -0
- package/.agent-src/rules/package-ci-checks.md +21 -61
- package/.agent-src/rules/preservation-guard.md +64 -29
- package/.agent-src/rules/review-routing-awareness.md +24 -43
- package/.agent-src/rules/roadmap-progress-sync.md +31 -65
- package/.agent-src/rules/rule-type-governance.md +28 -0
- package/.agent-src/rules/security-sensitive-stop.md +8 -8
- package/.agent-src/rules/skill-quality.md +16 -48
- package/.agent-src/rules/slash-command-routing-policy.md +7 -4
- package/.agent-src/rules/think-before-action.md +52 -42
- package/.agent-src/rules/tool-safety.md +19 -16
- package/.agent-src/rules/ui-audit-gate.md +24 -38
- package/.agent-src/rules/user-interaction.md +13 -68
- package/.agent-src/skills/ai-council/SKILL.md +2 -0
- package/.agent-src/skills/api-testing/SKILL.md +1 -1
- package/.agent-src/skills/check-refs/SKILL.md +59 -40
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +86 -28
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +5 -5
- package/.agent-src/skills/developer-like-execution/SKILL.md +4 -4
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +101 -65
- package/.agent-src/skills/flux/SKILL.md +30 -10
- package/.agent-src/skills/github-ci/SKILL.md +2 -2
- package/.agent-src/skills/judge-code-quality/SKILL.md +7 -8
- package/.agent-src/skills/judge-security-auditor/SKILL.md +4 -5
- package/.agent-src/skills/judge-test-coverage/SKILL.md +3 -4
- package/.agent-src/skills/lint-skills/SKILL.md +57 -39
- package/.agent-src/skills/md-language-check/SKILL.md +61 -39
- package/.agent-src/skills/override-management/SKILL.md +5 -5
- package/.agent-src/skills/quality-tools/SKILL.md +2 -2
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +116 -43
- package/.agent-src/skills/readme-reviewer/SKILL.md +30 -29
- package/.agent-src/skills/readme-writing/SKILL.md +78 -53
- package/.agent-src/skills/readme-writing-package/SKILL.md +50 -47
- package/.agent-src/skills/receiving-code-review/SKILL.md +52 -47
- package/.agent-src/skills/refine-prompt/SKILL.md +0 -1
- package/.agent-src/skills/requesting-code-review/SKILL.md +35 -30
- package/.agent-src/skills/security/SKILL.md +7 -2
- package/.agent-src/skills/security-audit/SKILL.md +7 -3
- package/.agent-src/skills/systematic-debugging/SKILL.md +68 -60
- package/.agent-src/skills/test-driven-development/SKILL.md +59 -57
- package/.agent-src/skills/test-performance/SKILL.md +0 -1
- package/.agent-src/skills/traefik/SKILL.md +4 -4
- package/.agent-src/skills/verify-completion-evidence/SKILL.md +28 -26
- package/.agent-src/templates/roadmaps.md +4 -0
- package/.claude-plugin/marketplace.json +22 -11
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +125 -1
- package/README.md +18 -17
- package/docs/architecture.md +4 -6
- package/docs/catalog.md +67 -39
- package/docs/contracts/STABILITY.md +13 -7
- package/docs/contracts/adr-chat-history-split.md +1 -3
- package/docs/contracts/adr-command-suggestion.md +0 -2
- package/docs/contracts/adr-implement-ticket-runtime.md +1 -2
- package/docs/contracts/adr-product-ui-track.md +3 -6
- package/docs/contracts/adr-prompt-driven-execution.md +3 -4
- package/docs/contracts/agent-memory-contract.md +6 -11
- package/docs/contracts/artifact-engagement-flow.md +6 -9
- package/docs/contracts/command-clusters.md +56 -46
- package/docs/contracts/command-suggestion-flow.md +1 -3
- package/docs/contracts/context-paths.md +99 -0
- package/docs/contracts/file-ownership-matrix.json +6722 -0
- package/docs/contracts/file-ownership-matrix.md +134 -0
- package/docs/contracts/implement-ticket-flow.md +6 -9
- package/docs/contracts/linear-ai-rules-inclusion.md +0 -1
- package/docs/contracts/linear-ai-three-layers.md +0 -2
- package/docs/contracts/load-context-budget-model.md +258 -0
- package/docs/contracts/load-context-schema.md +21 -3
- package/docs/contracts/roadmap-complexity-standard.md +137 -0
- package/docs/contracts/rule-interactions.md +0 -1
- package/docs/contracts/rule-priority-hierarchy.md +1 -1
- package/docs/contracts/ui-track-flow.md +7 -17
- package/docs/customization.md +2 -0
- package/docs/getting-started.md +5 -4
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +100 -0
- package/docs/guidelines/agent-infra/direct-answers-demos.md +145 -0
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
- package/package.json +1 -1
- package/scripts/_phase2_shim_helper.py +109 -0
- package/scripts/agent-config +30 -0
- package/scripts/ai_council/one_off_archive/2026-05/README.md +45 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_2a4_acceptance.py +208 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_budget_v2_audit.py +206 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_context_layer_v1_estimate.py +67 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_context_layer_v1_review.py +292 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_followups_review.py +259 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_nondestructive_inline_audit.py +209 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase4_dispatch_latency.py +108 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase6_trigger_jaccard.py +92 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase_2a_budget_rebalance.py +257 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_phase_2a_post_revert.py +197 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_rule_hardening_v1.py +251 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_open_questions.py +232 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_optimization.py +144 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_v3_gaps.py +252 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_structural_v3_review.py +240 -0
- package/scripts/build_rule_trigger_matrix.py +360 -0
- package/scripts/check_always_budget.py +402 -45
- package/scripts/check_cluster_patterns.py +159 -0
- package/scripts/check_command_count_messaging.py +14 -7
- package/scripts/check_context_paths.py +201 -0
- package/scripts/check_no_roadmap_refs.py +155 -0
- package/scripts/check_one_off_location.py +81 -0
- package/scripts/check_phase_coupling.py +148 -0
- package/scripts/check_portability.py +2 -0
- package/scripts/check_references.py +35 -2
- package/scripts/check_safety_floor_untouched.py +125 -0
- package/scripts/command_suggester/loader.py +4 -1
- package/scripts/compress.py +64 -15
- package/scripts/context_hygiene_hook.py +173 -0
- package/scripts/generate_index.py +6 -2
- package/scripts/generate_ownership_matrix.py +323 -0
- package/scripts/hooks/augment-context-hygiene.sh +55 -0
- package/scripts/hooks/augment-onboarding-gate.sh +55 -0
- package/scripts/hooks/augment-roadmap-progress.sh +57 -0
- package/scripts/install.py +105 -45
- package/scripts/lint_examples.py +98 -0
- package/scripts/lint_no_new_atomic_commands.py +12 -11
- package/scripts/lint_roadmap_complexity.py +127 -0
- package/scripts/onboarding_gate_hook.py +137 -0
- package/scripts/requirements-evals.txt +1 -0
- package/scripts/roadmap_progress_hook.py +159 -0
- package/scripts/schemas/command.schema.json +4 -3
- package/scripts/schemas/rule.schema.json +5 -0
- package/scripts/skill_linter.py +1 -0
- package/scripts/sync_agent_settings.py +25 -2
- package/scripts/update_counts.py +7 -0
- /package/scripts/ai_council/{_one_off_rebalancing_audit.py → one_off_archive/2026-05/_one_off_rebalancing_audit.py} +0 -0
- /package/scripts/ai_council/{_one_off_roundtrip.py → one_off_archive/2026-05/_one_off_roundtrip.py} +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate the file-ownership matrix.
|
|
3
|
+
|
|
4
|
+
Produces:
|
|
5
|
+
|
|
6
|
+
* docs/contracts/file-ownership-matrix.json (machine, internal-locked)
|
|
7
|
+
* agents/contexts/structural/file-ownership-matrix.md (human-readable)
|
|
8
|
+
|
|
9
|
+
Walks `.agent-src.uncompressed/{rules,skills,commands,contexts,personas}/`,
|
|
10
|
+
parses frontmatter for `load_context:` / `load_context_eager:`, scans
|
|
11
|
+
markdown bodies for inline links to `.md` files inside the scanned roots,
|
|
12
|
+
and emits READ_ONLY edges plus depth-2 transitive closure of load_context
|
|
13
|
+
chains. Depth-3 chains abort the build (matches the 0.2.4 nesting cap).
|
|
14
|
+
|
|
15
|
+
Contract: docs/contracts/file-ownership-matrix.md
|
|
16
|
+
Roadmap: road-to-structural-optimization.md § 0.1
|
|
17
|
+
|
|
18
|
+
Modes:
|
|
19
|
+
--check Regenerate to memory and diff against committed JSON.
|
|
20
|
+
Exit 0 if identical, 1 if drifted.
|
|
21
|
+
(default) Regenerate JSON + MD in place; exit 0 on success.
|
|
22
|
+
|
|
23
|
+
Exit codes: 0 = ok, 1 = drift (--check), 2 = depth-3 chain, 3 = internal.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Iterable
|
|
34
|
+
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
38
|
+
SRC_ROOT = ROOT / ".agent-src.uncompressed"
|
|
39
|
+
|
|
40
|
+
SCAN_DIRS = ("rules", "skills", "commands", "contexts", "personas")
|
|
41
|
+
|
|
42
|
+
JSON_OUT = ROOT / "docs" / "contracts" / "file-ownership-matrix.json"
|
|
43
|
+
MD_OUT = ROOT / "agents" / "contexts" / "structural" / "file-ownership-matrix.md"
|
|
44
|
+
|
|
45
|
+
LINK_RE = re.compile(r"\]\(([^)]+\.md)(?:#[^)]*)?\)")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class FileEntry:
|
|
50
|
+
path: str
|
|
51
|
+
kind: str
|
|
52
|
+
rule_type: str | None = None
|
|
53
|
+
load_context: list[str] = field(default_factory=list)
|
|
54
|
+
load_context_eager: list[str] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Edge:
|
|
59
|
+
source: str
|
|
60
|
+
target: str
|
|
61
|
+
type: str
|
|
62
|
+
via: str
|
|
63
|
+
depth: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _rel(p: Path) -> str:
|
|
67
|
+
return p.relative_to(ROOT).as_posix()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _kind_for(rel: str) -> str:
|
|
71
|
+
parts = rel.split("/")
|
|
72
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed":
|
|
73
|
+
return parts[1].rstrip("s") if parts[1] != "personas" else "persona"
|
|
74
|
+
return "unknown"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _parse_frontmatter(p: Path) -> dict:
|
|
78
|
+
text = p.read_text(encoding="utf-8")
|
|
79
|
+
if not text.startswith("---\n"):
|
|
80
|
+
return {}
|
|
81
|
+
end = text.find("\n---\n", 4)
|
|
82
|
+
if end == -1:
|
|
83
|
+
return {}
|
|
84
|
+
try:
|
|
85
|
+
data = yaml.safe_load(text[4:end])
|
|
86
|
+
except yaml.YAMLError:
|
|
87
|
+
return {}
|
|
88
|
+
return data if isinstance(data, dict) else {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _collect_files(src_root: Path) -> list[Path]:
|
|
92
|
+
out: list[Path] = []
|
|
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
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve(target: str, src_root: Path) -> Path | None:
|
|
101
|
+
"""Resolve a path string (repo-relative or short) into an absolute Path
|
|
102
|
+
under src_root or the repo root. Return None if not under a scanned root."""
|
|
103
|
+
cand = src_root.parent / target if "/" in target else src_root / target
|
|
104
|
+
try:
|
|
105
|
+
rel = cand.resolve().relative_to(src_root.parent)
|
|
106
|
+
except ValueError:
|
|
107
|
+
return None
|
|
108
|
+
parts = rel.parts
|
|
109
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
|
|
110
|
+
return cand if cand.exists() else None
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build_matrix(src_root: Path) -> tuple[dict[str, FileEntry], list[Edge], list[str]]:
|
|
115
|
+
"""Build the file map + edge list. Returns (files, edges, depth3_chains).
|
|
116
|
+
|
|
117
|
+
depth3_chains is non-empty iff the depth invariant is violated; the
|
|
118
|
+
caller must abort with exit code 2.
|
|
119
|
+
"""
|
|
120
|
+
files: dict[str, FileEntry] = {}
|
|
121
|
+
for f in _collect_files(src_root):
|
|
122
|
+
rel = f.relative_to(src_root.parent).as_posix()
|
|
123
|
+
fm = _parse_frontmatter(f)
|
|
124
|
+
rtype = fm.get("type")
|
|
125
|
+
if isinstance(rtype, str):
|
|
126
|
+
rtype = rtype.strip('"').strip("'")
|
|
127
|
+
else:
|
|
128
|
+
rtype = None
|
|
129
|
+
lazy = fm.get("load_context") or []
|
|
130
|
+
eager = fm.get("load_context_eager") or []
|
|
131
|
+
if not isinstance(lazy, list):
|
|
132
|
+
lazy = []
|
|
133
|
+
if not isinstance(eager, list):
|
|
134
|
+
eager = []
|
|
135
|
+
files[rel] = FileEntry(
|
|
136
|
+
path=rel,
|
|
137
|
+
kind=_kind_for(rel),
|
|
138
|
+
rule_type=rtype,
|
|
139
|
+
load_context=[str(x) for x in lazy if isinstance(x, str)],
|
|
140
|
+
load_context_eager=[str(x) for x in eager if isinstance(x, str)],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
edges: list[Edge] = []
|
|
144
|
+
for rel, entry in files.items():
|
|
145
|
+
for tgt in entry.load_context:
|
|
146
|
+
edges.append(Edge(rel, tgt, "READ_ONLY", "load_context", 1))
|
|
147
|
+
for tgt in entry.load_context_eager:
|
|
148
|
+
edges.append(Edge(rel, tgt, "READ_ONLY", "load_context_eager", 1))
|
|
149
|
+
|
|
150
|
+
# Body markdown links — only count edges to files we know about
|
|
151
|
+
for rel, entry in files.items():
|
|
152
|
+
body = (src_root.parent / rel).read_text(encoding="utf-8")
|
|
153
|
+
body = body.split("\n---\n", 1)[-1] if body.startswith("---\n") else body
|
|
154
|
+
seen_targets: set[str] = set()
|
|
155
|
+
for m in LINK_RE.finditer(body):
|
|
156
|
+
href = m.group(1).strip()
|
|
157
|
+
if href.startswith("http"):
|
|
158
|
+
continue
|
|
159
|
+
resolved = _resolve_link(rel, href, src_root)
|
|
160
|
+
if resolved is None or resolved == rel or resolved in seen_targets:
|
|
161
|
+
continue
|
|
162
|
+
if resolved in files:
|
|
163
|
+
seen_targets.add(resolved)
|
|
164
|
+
edges.append(Edge(rel, resolved, "READ_ONLY", "body_link", 1))
|
|
165
|
+
|
|
166
|
+
# Transitive closure on load_context* edges, depth 2; depth 3 aborts.
|
|
167
|
+
lc_edges_by_src: dict[str, list[str]] = {}
|
|
168
|
+
for e in edges:
|
|
169
|
+
if e.via in ("load_context", "load_context_eager"):
|
|
170
|
+
lc_edges_by_src.setdefault(e.source, []).append(e.target)
|
|
171
|
+
|
|
172
|
+
transitive: list[Edge] = []
|
|
173
|
+
depth3: list[str] = []
|
|
174
|
+
for src, lvl1_targets in lc_edges_by_src.items():
|
|
175
|
+
for t1 in lvl1_targets:
|
|
176
|
+
for t2 in lc_edges_by_src.get(t1, []):
|
|
177
|
+
if t2 == src or t2 == t1:
|
|
178
|
+
continue
|
|
179
|
+
transitive.append(Edge(src, t2, "READ_ONLY", "load_context_transitive", 2))
|
|
180
|
+
# depth-3 probe
|
|
181
|
+
for t3 in lc_edges_by_src.get(t2, []):
|
|
182
|
+
if t3 in (src, t1, t2):
|
|
183
|
+
continue
|
|
184
|
+
depth3.append(f"{src} → {t1} → {t2} → {t3}")
|
|
185
|
+
|
|
186
|
+
edges.extend(transitive)
|
|
187
|
+
for rel in files:
|
|
188
|
+
edges.append(Edge(rel, rel, "WRITE", "self", 0))
|
|
189
|
+
|
|
190
|
+
edges.sort(key=lambda e: (e.source, e.target, e.via, e.depth))
|
|
191
|
+
return files, edges, depth3
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _resolve_link(source_rel: str, href: str, src_root: Path) -> str | None:
|
|
195
|
+
"""Resolve a markdown link href (relative to source file) to a repo-relative
|
|
196
|
+
path inside a scanned root, or None."""
|
|
197
|
+
if href.startswith(".agent-src.uncompressed/") or href.startswith("agents/"):
|
|
198
|
+
cand = (src_root.parent / href).resolve()
|
|
199
|
+
else:
|
|
200
|
+
base = (src_root.parent / source_rel).parent
|
|
201
|
+
cand = (base / href).resolve()
|
|
202
|
+
try:
|
|
203
|
+
rel = cand.relative_to(src_root.parent).as_posix()
|
|
204
|
+
except ValueError:
|
|
205
|
+
return None
|
|
206
|
+
parts = rel.split("/")
|
|
207
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
|
|
208
|
+
return rel if cand.exists() else None
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _to_json(files: dict[str, FileEntry], edges: list[Edge]) -> dict:
|
|
213
|
+
return {
|
|
214
|
+
"version": 1,
|
|
215
|
+
"generated_by": "scripts/generate_ownership_matrix.py",
|
|
216
|
+
"source_of_truth": ".agent-src.uncompressed/",
|
|
217
|
+
"files": {
|
|
218
|
+
rel: {
|
|
219
|
+
"kind": e.kind,
|
|
220
|
+
"rule_type": e.rule_type,
|
|
221
|
+
"load_context": e.load_context,
|
|
222
|
+
"load_context_eager": e.load_context_eager,
|
|
223
|
+
}
|
|
224
|
+
for rel, e in sorted(files.items())
|
|
225
|
+
},
|
|
226
|
+
"edges": [
|
|
227
|
+
{
|
|
228
|
+
"source": e.source,
|
|
229
|
+
"target": e.target,
|
|
230
|
+
"type": e.type,
|
|
231
|
+
"via": e.via,
|
|
232
|
+
"depth": e.depth,
|
|
233
|
+
}
|
|
234
|
+
for e in edges
|
|
235
|
+
],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _to_markdown(payload: dict) -> str:
|
|
240
|
+
lines: list[str] = [
|
|
241
|
+
"# File-ownership matrix (regenerated)",
|
|
242
|
+
"",
|
|
243
|
+
"> **Do not edit.** Regenerated by `scripts/generate_ownership_matrix.py`.",
|
|
244
|
+
"> Schema: [`docs/contracts/file-ownership-matrix.md`](../../../docs/contracts/file-ownership-matrix.md).",
|
|
245
|
+
"",
|
|
246
|
+
f"- Schema version: `{payload['version']}`",
|
|
247
|
+
f"- Source of truth: `{payload['source_of_truth']}`",
|
|
248
|
+
f"- Files indexed: **{len(payload['files'])}**",
|
|
249
|
+
f"- Edges (incl. self-WRITE): **{len(payload['edges'])}**",
|
|
250
|
+
"",
|
|
251
|
+
"## READ_ONLY edges",
|
|
252
|
+
"",
|
|
253
|
+
"| Source | Target | Via | Depth |",
|
|
254
|
+
"|---|---|---|---:|",
|
|
255
|
+
]
|
|
256
|
+
ro = [e for e in payload["edges"] if e["type"] == "READ_ONLY"]
|
|
257
|
+
for e in ro:
|
|
258
|
+
lines.append(f"| `{e['source']}` | `{e['target']}` | `{e['via']}` | {e['depth']} |")
|
|
259
|
+
if not ro:
|
|
260
|
+
lines.append("| _(none)_ | | | |")
|
|
261
|
+
lines += [
|
|
262
|
+
"",
|
|
263
|
+
"## Files by kind",
|
|
264
|
+
"",
|
|
265
|
+
"| Kind | Count |",
|
|
266
|
+
"|---|---:|",
|
|
267
|
+
]
|
|
268
|
+
counts: dict[str, int] = {}
|
|
269
|
+
for f in payload["files"].values():
|
|
270
|
+
counts[f["kind"]] = counts.get(f["kind"], 0) + 1
|
|
271
|
+
for k in sorted(counts):
|
|
272
|
+
lines.append(f"| `{k}` | {counts[k]} |")
|
|
273
|
+
lines.append("")
|
|
274
|
+
return "\n".join(lines)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _write_outputs(payload: dict, json_out: Path, md_out: Path) -> None:
|
|
278
|
+
json_out.parent.mkdir(parents=True, exist_ok=True)
|
|
279
|
+
md_out.parent.mkdir(parents=True, exist_ok=True)
|
|
280
|
+
json_out.write_text(json.dumps(payload, indent=2, sort_keys=False) + "\n", encoding="utf-8")
|
|
281
|
+
md_out.write_text(_to_markdown(payload) + "\n", encoding="utf-8")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
285
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
286
|
+
ap.add_argument("--check", action="store_true",
|
|
287
|
+
help="Regenerate to memory and diff against committed JSON.")
|
|
288
|
+
args = ap.parse_args(list(argv) if argv is not None else None)
|
|
289
|
+
|
|
290
|
+
if not SRC_ROOT.is_dir():
|
|
291
|
+
print(f"❌ source dir missing: {SRC_ROOT}", file=sys.stderr)
|
|
292
|
+
return 3
|
|
293
|
+
|
|
294
|
+
files, edges, depth3 = build_matrix(SRC_ROOT)
|
|
295
|
+
if depth3:
|
|
296
|
+
print("❌ load_context depth-3 chain detected (limit is 2):", file=sys.stderr)
|
|
297
|
+
for chain in depth3:
|
|
298
|
+
print(f" 🔴 {chain}", file=sys.stderr)
|
|
299
|
+
return 2
|
|
300
|
+
|
|
301
|
+
payload = _to_json(files, edges)
|
|
302
|
+
|
|
303
|
+
if args.check:
|
|
304
|
+
if not JSON_OUT.exists():
|
|
305
|
+
print(f"❌ {JSON_OUT.relative_to(ROOT)} not committed; run `task generate-ownership-matrix`",
|
|
306
|
+
file=sys.stderr)
|
|
307
|
+
return 1
|
|
308
|
+
committed = json.loads(JSON_OUT.read_text(encoding="utf-8"))
|
|
309
|
+
if committed != payload:
|
|
310
|
+
print("❌ ownership matrix is stale — run `task generate-ownership-matrix` and commit",
|
|
311
|
+
file=sys.stderr)
|
|
312
|
+
return 1
|
|
313
|
+
print(f"✅ ownership matrix in sync ({len(files)} files, {len(edges)} edges)")
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
_write_outputs(payload, JSON_OUT, MD_OUT)
|
|
317
|
+
print(f"✅ wrote {JSON_OUT.relative_to(ROOT)} ({len(files)} files, {len(edges)} edges)")
|
|
318
|
+
print(f"✅ wrote {MD_OUT.relative_to(ROOT)}")
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
sys.exit(main())
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Augment Code lifecycle-hook trampoline for context-hygiene.
|
|
3
|
+
#
|
|
4
|
+
# Augment requires hook scripts to use the .sh extension and live at
|
|
5
|
+
# either a system path (/etc/augment/...) or user scope
|
|
6
|
+
# (~/.augment/...). This trampoline lives at user scope and dispatches
|
|
7
|
+
# every PostToolUse event to whichever workspace fired it, so a single
|
|
8
|
+
# install covers every project that has ./agent-config available.
|
|
9
|
+
#
|
|
10
|
+
# Behaviour:
|
|
11
|
+
# - Read the JSON event from stdin into a buffer.
|
|
12
|
+
# - Extract workspace_roots[0]; bail silently when missing.
|
|
13
|
+
# - cd into that workspace; bail silently when it is not a directory
|
|
14
|
+
# or does not contain ./agent-config.
|
|
15
|
+
# - Re-pipe the original JSON into
|
|
16
|
+
# ./agent-config context-hygiene:hook --platform augment
|
|
17
|
+
# so context_hygiene_hook.py can update the per-turn tracker.
|
|
18
|
+
# - Always exit 0 — PostToolUse hooks must never block.
|
|
19
|
+
|
|
20
|
+
set -u
|
|
21
|
+
|
|
22
|
+
EVENT_DATA="$(cat)"
|
|
23
|
+
|
|
24
|
+
WORKSPACE=""
|
|
25
|
+
if command -v jq >/dev/null 2>&1; then
|
|
26
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" \
|
|
27
|
+
| jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
|
|
28
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
29
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
|
|
30
|
+
import json, sys
|
|
31
|
+
try:
|
|
32
|
+
data = json.load(sys.stdin)
|
|
33
|
+
except Exception:
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
roots = data.get("workspace_roots") or []
|
|
36
|
+
if roots:
|
|
37
|
+
print(roots[0])
|
|
38
|
+
' 2>/dev/null)"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
cd "$WORKSPACE" 2>/dev/null || exit 0
|
|
46
|
+
|
|
47
|
+
if [ ! -x ./agent-config ]; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
printf '%s' "$EVENT_DATA" \
|
|
52
|
+
| ./agent-config context-hygiene:hook --platform augment \
|
|
53
|
+
>/dev/null 2>&1 || true
|
|
54
|
+
|
|
55
|
+
exit 0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Augment Code lifecycle-hook trampoline for onboarding-gate.
|
|
3
|
+
#
|
|
4
|
+
# Augment requires hook scripts to use the .sh extension and live at
|
|
5
|
+
# either a system path (/etc/augment/...) or user scope
|
|
6
|
+
# (~/.augment/...). This trampoline lives at user scope and dispatches
|
|
7
|
+
# every event to whichever workspace fired it, so a single install
|
|
8
|
+
# covers every project that has ./agent-config available.
|
|
9
|
+
#
|
|
10
|
+
# Behaviour:
|
|
11
|
+
# - Read the JSON event from stdin into a buffer.
|
|
12
|
+
# - Extract workspace_roots[0]; bail silently when missing.
|
|
13
|
+
# - cd into that workspace; bail silently when it is not a directory
|
|
14
|
+
# or does not contain ./agent-config.
|
|
15
|
+
# - Re-pipe the original JSON into
|
|
16
|
+
# ./agent-config onboarding-gate:hook --platform augment
|
|
17
|
+
# so onboarding_gate_hook.py can refresh the state file.
|
|
18
|
+
# - Always exit 0 — onboarding-gate must never block the agent loop.
|
|
19
|
+
|
|
20
|
+
set -u
|
|
21
|
+
|
|
22
|
+
EVENT_DATA="$(cat)"
|
|
23
|
+
|
|
24
|
+
WORKSPACE=""
|
|
25
|
+
if command -v jq >/dev/null 2>&1; then
|
|
26
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" \
|
|
27
|
+
| jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
|
|
28
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
29
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
|
|
30
|
+
import json, sys
|
|
31
|
+
try:
|
|
32
|
+
data = json.load(sys.stdin)
|
|
33
|
+
except Exception:
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
roots = data.get("workspace_roots") or []
|
|
36
|
+
if roots:
|
|
37
|
+
print(roots[0])
|
|
38
|
+
' 2>/dev/null)"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
cd "$WORKSPACE" 2>/dev/null || exit 0
|
|
46
|
+
|
|
47
|
+
if [ ! -x ./agent-config ]; then
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
printf '%s' "$EVENT_DATA" \
|
|
52
|
+
| ./agent-config onboarding-gate:hook --platform augment \
|
|
53
|
+
>/dev/null 2>&1 || true
|
|
54
|
+
|
|
55
|
+
exit 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Augment Code lifecycle-hook trampoline for roadmap-progress-sync.
|
|
3
|
+
#
|
|
4
|
+
# Augment requires hook scripts to use the .sh extension and live at
|
|
5
|
+
# either a system path (/etc/augment/...) or user scope
|
|
6
|
+
# (~/.augment/...). This trampoline lives at user scope and dispatches
|
|
7
|
+
# every PostToolUse event to whichever workspace fired it, so a single
|
|
8
|
+
# install covers every project that has ./agent-config available.
|
|
9
|
+
#
|
|
10
|
+
# Behaviour:
|
|
11
|
+
# - Read the JSON event from stdin into a buffer.
|
|
12
|
+
# - Extract workspace_roots[0]; bail silently when missing.
|
|
13
|
+
# - cd into that workspace; bail silently when it is not a directory
|
|
14
|
+
# or does not contain ./agent-config.
|
|
15
|
+
# - Re-pipe the original JSON into
|
|
16
|
+
# ./agent-config roadmap-progress:hook --platform augment
|
|
17
|
+
# so roadmap_progress_hook.py runs the path filter and decides
|
|
18
|
+
# whether to regenerate the dashboard.
|
|
19
|
+
# - Always exit 0 — PostToolUse hooks must never block.
|
|
20
|
+
|
|
21
|
+
set -u
|
|
22
|
+
|
|
23
|
+
EVENT_DATA="$(cat)"
|
|
24
|
+
|
|
25
|
+
# Extract workspace_roots[0] using whichever JSON tool is available.
|
|
26
|
+
WORKSPACE=""
|
|
27
|
+
if command -v jq >/dev/null 2>&1; then
|
|
28
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" \
|
|
29
|
+
| jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
|
|
30
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
31
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
|
|
32
|
+
import json, sys
|
|
33
|
+
try:
|
|
34
|
+
data = json.load(sys.stdin)
|
|
35
|
+
except Exception:
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
roots = data.get("workspace_roots") or []
|
|
38
|
+
if roots:
|
|
39
|
+
print(roots[0])
|
|
40
|
+
' 2>/dev/null)"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
cd "$WORKSPACE" 2>/dev/null || exit 0
|
|
48
|
+
|
|
49
|
+
if [ ! -x ./agent-config ]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
printf '%s' "$EVENT_DATA" \
|
|
54
|
+
| ./agent-config roadmap-progress:hook --platform augment \
|
|
55
|
+
>/dev/null 2>&1 || true
|
|
56
|
+
|
|
57
|
+
exit 0
|
package/scripts/install.py
CHANGED
|
@@ -464,44 +464,76 @@ def ensure_augment_bridge(project_root: Path, force: bool) -> None:
|
|
|
464
464
|
# .augment/settings.json is plugin enablement, not hooks.
|
|
465
465
|
AUGMENT_USER_DIR = Path.home() / ".augment"
|
|
466
466
|
AUGMENT_USER_HOOKS_DIR = AUGMENT_USER_DIR / "hooks"
|
|
467
|
-
|
|
468
|
-
|
|
467
|
+
AUGMENT_CHAT_HISTORY_TRAMPOLINE = "augment-chat-history.sh"
|
|
468
|
+
AUGMENT_ROADMAP_PROGRESS_TRAMPOLINE = "augment-roadmap-progress.sh"
|
|
469
|
+
AUGMENT_ONBOARDING_GATE_TRAMPOLINE = "augment-onboarding-gate.sh"
|
|
470
|
+
AUGMENT_CONTEXT_HYGIENE_TRAMPOLINE = "augment-context-hygiene.sh"
|
|
471
|
+
# (trampoline name, list of events it should fire on). Each trampoline
|
|
472
|
+
# is a self-contained workspace router; mapping them per-event keeps the
|
|
473
|
+
# wiring explicit and lets a future hook bind to a different surface
|
|
474
|
+
# without touching the chat-history one.
|
|
475
|
+
AUGMENT_HOOK_BINDINGS = (
|
|
476
|
+
(AUGMENT_CHAT_HISTORY_TRAMPOLINE,
|
|
477
|
+
("SessionStart", "SessionEnd", "Stop", "PostToolUse")),
|
|
478
|
+
(AUGMENT_ROADMAP_PROGRESS_TRAMPOLINE,
|
|
479
|
+
("PostToolUse",)),
|
|
480
|
+
(AUGMENT_ONBOARDING_GATE_TRAMPOLINE,
|
|
481
|
+
("SessionStart",)),
|
|
482
|
+
(AUGMENT_CONTEXT_HYGIENE_TRAMPOLINE,
|
|
483
|
+
("PostToolUse",)),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _deploy_augment_trampoline(package_root: Path, name: str, force: bool) -> Path | None:
|
|
488
|
+
src = package_root / "scripts" / "hooks" / name
|
|
489
|
+
if not src.exists():
|
|
490
|
+
skip(f"augment trampoline missing in package: {src}")
|
|
491
|
+
return None
|
|
492
|
+
AUGMENT_USER_HOOKS_DIR.mkdir(parents=True, exist_ok=True)
|
|
493
|
+
dst = AUGMENT_USER_HOOKS_DIR / name
|
|
494
|
+
src_text = src.read_text(encoding="utf-8")
|
|
495
|
+
if dst.exists() and dst.read_text(encoding="utf-8") == src_text and not force:
|
|
496
|
+
skip(f"~/.augment/hooks/{name} already up to date")
|
|
497
|
+
else:
|
|
498
|
+
dst.write_text(src_text, encoding="utf-8")
|
|
499
|
+
dst.chmod(0o755)
|
|
500
|
+
success(f"~/.augment/hooks/{name} installed")
|
|
501
|
+
return dst
|
|
469
502
|
|
|
470
503
|
|
|
471
504
|
def ensure_augment_user_hooks(package_root: Path, force: bool) -> None:
|
|
472
|
-
"""Deploy the Augment lifecycle-hook
|
|
505
|
+
"""Deploy the Augment lifecycle-hook trampolines at user scope.
|
|
473
506
|
|
|
474
507
|
Augment hook scripts must use the .sh extension and be referenced by
|
|
475
508
|
absolute path; user scope is the only surface that fires for both the
|
|
476
509
|
CLI and the IDE plugins. This installs once per developer (not per
|
|
477
|
-
project) —
|
|
478
|
-
and dispatches into whichever project is active at hook-fire
|
|
510
|
+
project) — each trampoline reads workspace_roots from the event
|
|
511
|
+
payload and dispatches into whichever project is active at hook-fire
|
|
512
|
+
time.
|
|
513
|
+
|
|
514
|
+
Trampolines deployed (see AUGMENT_HOOK_BINDINGS for the source of
|
|
515
|
+
truth):
|
|
516
|
+
- augment-chat-history.sh → SessionStart/SessionEnd/Stop/PostToolUse
|
|
517
|
+
- augment-roadmap-progress.sh → PostToolUse (path-filtered to
|
|
518
|
+
agents/roadmaps/ — see scripts/roadmap_progress_hook.py)
|
|
519
|
+
- augment-onboarding-gate.sh → SessionStart (refresh
|
|
520
|
+
agents/state/onboarding-gate.json from .agent-settings.yml)
|
|
521
|
+
- augment-context-hygiene.sh → PostToolUse (per-turn counter,
|
|
522
|
+
loop detection, freshness milestones)
|
|
479
523
|
"""
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
524
|
+
per_event: dict[str, list] = {}
|
|
525
|
+
for name, events in AUGMENT_HOOK_BINDINGS:
|
|
526
|
+
dst = _deploy_augment_trampoline(package_root, name, force)
|
|
527
|
+
if dst is None:
|
|
528
|
+
continue
|
|
529
|
+
entry = {"hooks": [{"type": "command", "command": str(dst)}]}
|
|
530
|
+
for event in events:
|
|
531
|
+
per_event.setdefault(event, []).append(entry)
|
|
487
532
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
skip(f"~/.augment/hooks/{AUGMENT_TRAMPOLINE_NAME} already up to date")
|
|
491
|
-
else:
|
|
492
|
-
dst.write_text(src_text, encoding="utf-8")
|
|
493
|
-
dst.chmod(0o755)
|
|
494
|
-
success(f"~/.augment/hooks/{AUGMENT_TRAMPOLINE_NAME} installed")
|
|
533
|
+
if not per_event:
|
|
534
|
+
return
|
|
495
535
|
|
|
496
|
-
|
|
497
|
-
"hooks": [
|
|
498
|
-
{
|
|
499
|
-
"type": "command",
|
|
500
|
-
"command": str(dst),
|
|
501
|
-
},
|
|
502
|
-
],
|
|
503
|
-
}
|
|
504
|
-
settings_patch: dict = {"hooks": {event: [hook_entry] for event in AUGMENT_HOOK_EVENTS}}
|
|
536
|
+
settings_patch: dict = {"hooks": per_event}
|
|
505
537
|
merge_json_file(
|
|
506
538
|
AUGMENT_USER_DIR / "settings.json",
|
|
507
539
|
settings_patch,
|
|
@@ -510,36 +542,64 @@ def ensure_augment_user_hooks(package_root: Path, force: bool) -> None:
|
|
|
510
542
|
)
|
|
511
543
|
|
|
512
544
|
|
|
513
|
-
def
|
|
514
|
-
"""Single hook entry that calls ./agent-config
|
|
545
|
+
def _claude_hook_block(subcommand: str) -> dict:
|
|
546
|
+
"""Single hook entry that calls ./agent-config <subcommand> --platform claude."""
|
|
515
547
|
return {
|
|
516
548
|
"hooks": [
|
|
517
549
|
{
|
|
518
550
|
"type": "command",
|
|
519
|
-
"command": f"./agent-config
|
|
551
|
+
"command": f"./agent-config {subcommand} --platform claude",
|
|
520
552
|
},
|
|
521
553
|
],
|
|
522
554
|
}
|
|
523
555
|
|
|
524
556
|
|
|
525
|
-
|
|
526
|
-
|
|
557
|
+
# Claude Code Tier 1 hook bindings — keep in sync with AUGMENT_HOOK_BINDINGS.
|
|
558
|
+
# `chat-history:hook` is the cross-cutting transcript hook; the three
|
|
559
|
+
# rule-specific hooks are the Phase 4 Tier 1 set from
|
|
560
|
+
# `road-to-rule-hardening.md`.
|
|
561
|
+
CLAUDE_HOOK_SUBCOMMANDS = {
|
|
562
|
+
"chat-history": "chat-history:hook",
|
|
563
|
+
"roadmap-progress": "roadmap-progress:hook",
|
|
564
|
+
"onboarding-gate": "onboarding-gate:hook",
|
|
565
|
+
"context-hygiene": "context-hygiene:hook",
|
|
566
|
+
}
|
|
567
|
+
# (subcommand-key, list of Claude Code lifecycle events). Mirrors
|
|
568
|
+
# AUGMENT_HOOK_BINDINGS so each rule fires on the same logical surface
|
|
569
|
+
# on both platforms — the contract from
|
|
570
|
+
# `agents/contexts/hardening-pattern.md` § Cross-platform parity.
|
|
571
|
+
CLAUDE_HOOK_BINDINGS = (
|
|
572
|
+
("chat-history",
|
|
573
|
+
("SessionStart", "UserPromptSubmit", "PostToolUse", "Stop", "SessionEnd")),
|
|
574
|
+
("roadmap-progress",
|
|
575
|
+
("PostToolUse",)),
|
|
576
|
+
("onboarding-gate",
|
|
577
|
+
("SessionStart",)),
|
|
578
|
+
("context-hygiene",
|
|
579
|
+
("PostToolUse",)),
|
|
580
|
+
)
|
|
527
581
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
.
|
|
531
|
-
|
|
582
|
+
|
|
583
|
+
def ensure_claude_bridge(project_root: Path, force: bool) -> None:
|
|
584
|
+
"""Deploy .claude/settings.json with plugin enablement and Tier 1 hooks.
|
|
585
|
+
|
|
586
|
+
Hooks dispatch to the project-root ./agent-config wrapper, which routes
|
|
587
|
+
to the per-rule Python implementation (chat_history.py,
|
|
588
|
+
roadmap_progress_hook.py, onboarding_gate_hook.py,
|
|
589
|
+
context_hygiene_hook.py). They are no-ops when the relevant feature is
|
|
590
|
+
disabled in .agent-settings.yml. Idempotent: reruns merge cleanly
|
|
591
|
+
without duplicating entries (deep_merge replaces hook arrays rather
|
|
592
|
+
than appending).
|
|
532
593
|
"""
|
|
533
|
-
|
|
594
|
+
per_event: dict[str, list] = {}
|
|
595
|
+
for key, events in CLAUDE_HOOK_BINDINGS:
|
|
596
|
+
block = _claude_hook_block(CLAUDE_HOOK_SUBCOMMANDS[key])
|
|
597
|
+
for event in events:
|
|
598
|
+
per_event.setdefault(event, []).append(block)
|
|
599
|
+
|
|
534
600
|
bridge = {
|
|
535
601
|
"enabledPlugins": {"agent-conf@event4u": True},
|
|
536
|
-
"hooks":
|
|
537
|
-
"SessionStart": [claude_hook],
|
|
538
|
-
"UserPromptSubmit": [claude_hook],
|
|
539
|
-
"PostToolUse": [claude_hook],
|
|
540
|
-
"Stop": [claude_hook],
|
|
541
|
-
"SessionEnd": [claude_hook],
|
|
542
|
-
},
|
|
602
|
+
"hooks": per_event,
|
|
543
603
|
}
|
|
544
604
|
merge_json_file(project_root / ".claude" / "settings.json", bridge, force, ".claude/settings.json")
|
|
545
605
|
|