@event4u/agent-config 1.20.0 → 1.21.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.md +1 -1
- package/.agent-src/commands/bug-fix.md +1 -1
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/chat-history/import.md +60 -64
- package/.agent-src/commands/compress.md +12 -0
- package/.agent-src/commands/context/create.md +2 -2
- package/.agent-src/commands/context.md +1 -1
- package/.agent-src/commands/copilot-agents.md +1 -1
- package/.agent-src/commands/council/default.md +17 -5
- package/.agent-src/commands/council.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/dev.md +3 -3
- package/.agent-src/commands/feature.md +1 -1
- package/.agent-src/commands/fix/seeder.md +2 -2
- package/.agent-src/commands/fix.md +1 -1
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/judge.md +2 -2
- package/.agent-src/commands/memory.md +1 -1
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/module.md +1 -1
- package/.agent-src/commands/onboard.md +4 -4
- package/.agent-src/commands/optimize/augmentignore.md +1 -1
- package/.agent-src/commands/optimize-prompt.md +61 -0
- package/.agent-src/commands/optimize.md +1 -1
- package/.agent-src/commands/override.md +1 -1
- package/.agent-src/commands/review-changes.md +1 -1
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/roadmap.md +1 -1
- package/.agent-src/commands/set-cost-profile.md +3 -3
- package/.agent-src/commands/sync-agent-settings.md +2 -2
- package/.agent-src/commands/tests/create.md +2 -2
- package/.agent-src/commands/tests.md +1 -1
- package/.agent-src/commands/threat-model.md +4 -4
- package/.agent-src/contexts/authority/commit-mechanics.md +14 -1
- package/.agent-src/contexts/authority/destructive-mechanics.md +14 -1
- package/.agent-src/contexts/authority/scope-mechanics.md +5 -0
- package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +76 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -1
- package/.agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +93 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +128 -5
- package/.agent-src/contexts/execution/autonomy-mechanics.md +44 -0
- package/.agent-src/contexts/model-recommendations.md +2 -2
- package/.agent-src/contexts/override-system.md +1 -1
- package/.agent-src/personas/product-owner.md +2 -2
- package/.agent-src/personas/qa.md +1 -1
- package/.agent-src/rules/agent-authority.md +5 -6
- package/.agent-src/rules/agent-docs.md +11 -53
- package/.agent-src/rules/analysis-skill-routing.md +10 -40
- package/.agent-src/rules/architecture.md +6 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +5 -0
- package/.agent-src/rules/artifact-engagement-recording.md +23 -59
- package/.agent-src/rules/ask-when-uncertain.md +24 -47
- package/.agent-src/rules/augment-portability.md +14 -62
- package/.agent-src/rules/augment-source-of-truth.md +10 -1
- package/.agent-src/rules/autonomous-execution.md +17 -98
- package/.agent-src/rules/capture-learnings.md +9 -80
- package/.agent-src/rules/cli-output-handling.md +12 -42
- package/.agent-src/rules/command-suggestion-policy.md +25 -73
- package/.agent-src/rules/commit-conventions.md +9 -58
- package/.agent-src/rules/commit-policy.md +16 -47
- package/.agent-src/rules/context-hygiene.md +5 -0
- package/.agent-src/rules/direct-answers.md +21 -50
- package/.agent-src/rules/docker-commands.md +11 -45
- package/.agent-src/rules/docs-sync.md +10 -56
- package/.agent-src/rules/downstream-changes.md +5 -0
- package/.agent-src/rules/e2e-testing.md +9 -44
- package/.agent-src/rules/guidelines.md +13 -75
- package/.agent-src/rules/improve-before-implement.md +10 -2
- package/.agent-src/rules/language-and-tone.md +41 -106
- package/.agent-src/rules/laravel-translations.md +11 -40
- package/.agent-src/rules/markdown-safe-codeblocks.md +4 -0
- package/.agent-src/rules/minimal-safe-diff.md +4 -0
- package/.agent-src/rules/missing-tool-handling.md +4 -0
- package/.agent-src/rules/model-recommendation.md +9 -61
- package/.agent-src/rules/no-attribution-footers.md +5 -0
- package/.agent-src/rules/no-cheap-questions.md +11 -27
- package/.agent-src/rules/no-council-references.md +76 -0
- package/.agent-src/rules/no-roadmap-references.md +7 -0
- package/.agent-src/rules/non-destructive-by-default.md +13 -43
- package/.agent-src/rules/onboarding-gate.md +9 -117
- package/.agent-src/rules/package-ci-checks.md +10 -37
- package/.agent-src/rules/php-coding.md +10 -55
- package/.agent-src/rules/preservation-guard.md +9 -0
- package/.agent-src/rules/review-routing-awareness.md +9 -97
- package/.agent-src/rules/reviewer-awareness.md +8 -83
- package/.agent-src/rules/roadmap-progress-sync.md +7 -170
- package/.agent-src/rules/role-mode-adherence.md +6 -2
- package/.agent-src/rules/rule-type-governance.md +8 -66
- package/.agent-src/rules/runtime-safety.md +5 -0
- package/.agent-src/rules/scope-control.md +17 -62
- package/.agent-src/rules/security-sensitive-stop.md +7 -1
- package/.agent-src/rules/size-enforcement.md +6 -1
- package/.agent-src/rules/skill-improvement-trigger.md +9 -49
- package/.agent-src/rules/skill-quality.md +7 -113
- package/.agent-src/rules/slash-command-routing-policy.md +11 -63
- package/.agent-src/rules/think-before-action.md +22 -87
- package/.agent-src/rules/token-efficiency.md +10 -74
- package/.agent-src/rules/token-optimizer-maintenance.md +68 -0
- package/.agent-src/rules/tool-safety.md +4 -0
- package/.agent-src/rules/ui-audit-gate.md +25 -61
- package/.agent-src/rules/upstream-proposal.md +9 -67
- package/.agent-src/rules/user-interaction.md +22 -108
- package/.agent-src/rules/verify-before-complete.md +1 -1
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +65 -0
- package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/.agent-src/skills/analysis-skill-router/SKILL.md +3 -3
- package/.agent-src/skills/artisan-commands/SKILL.md +2 -2
- package/.agent-src/skills/authz-review/SKILL.md +1 -1
- package/.agent-src/skills/aws-infrastructure/SKILL.md +5 -5
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +8 -8
- package/.agent-src/skills/bug-analyzer/SKILL.md +5 -5
- package/.agent-src/skills/code-refactoring/SKILL.md +4 -4
- package/.agent-src/skills/code-review/SKILL.md +2 -2
- package/.agent-src/skills/command-writing/SKILL.md +11 -0
- package/.agent-src/skills/composer-packages/SKILL.md +2 -2
- package/.agent-src/skills/context-authoring/SKILL.md +11 -0
- package/.agent-src/skills/context-document/SKILL.md +1 -1
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +23 -0
- package/.agent-src/skills/copilot-config/SKILL.md +1 -1
- package/.agent-src/skills/dependency-upgrade/SKILL.md +2 -2
- package/.agent-src/skills/devcontainer/SKILL.md +2 -2
- package/.agent-src/skills/developer-like-execution/SKILL.md +1 -1
- package/.agent-src/skills/docker/SKILL.md +1 -1
- package/.agent-src/skills/dto-creator/SKILL.md +1 -1
- package/.agent-src/skills/estimate-ticket/SKILL.md +2 -2
- package/.agent-src/skills/fe-design/SKILL.md +4 -4
- package/.agent-src/skills/feature-planning/SKILL.md +5 -5
- package/.agent-src/skills/funnel-analysis/SKILL.md +1 -1
- package/.agent-src/skills/laravel/SKILL.md +1 -1
- package/.agent-src/skills/laravel-notifications/SKILL.md +5 -5
- package/.agent-src/skills/laravel-pennant/SKILL.md +1 -1
- package/.agent-src/skills/laravel-pulse/SKILL.md +4 -4
- package/.agent-src/skills/laravel-reverb/SKILL.md +2 -2
- package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -1
- package/.agent-src/skills/migration-creator/SKILL.md +7 -7
- package/.agent-src/skills/multi-tenancy/SKILL.md +8 -8
- package/.agent-src/skills/performance-analysis/SKILL.md +3 -3
- package/.agent-src/skills/pest-testing/SKILL.md +6 -6
- package/.agent-src/skills/php-service/SKILL.md +2 -2
- package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
- package/.agent-src/skills/project-analysis-react/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
- package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
- package/.agent-src/skills/project-analyzer/SKILL.md +4 -4
- package/.agent-src/skills/prompt-optimizer/SKILL.md +108 -0
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/rule-writing/SKILL.md +33 -0
- package/.agent-src/skills/sentry-integration/SKILL.md +1 -1
- package/.agent-src/skills/skill-writing/SKILL.md +14 -0
- package/.agent-src/skills/terraform/SKILL.md +2 -2
- package/.agent-src/skills/terragrunt/SKILL.md +8 -8
- package/.agent-src/skills/test-performance/SKILL.md +5 -5
- package/.agent-src/skills/threat-modeling/SKILL.md +2 -2
- package/.agent-src/skills/token-optimizer/SKILL.md +110 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -1
- package/.agent-src/templates/AGENTS.md +1 -1
- package/.agent-src/templates/agent-settings.md +21 -16
- package/.agent-src/templates/contexts/tenant-boundaries.md +2 -2
- package/.agent-src/templates/contexts.md +1 -1
- package/.agent-src/templates/copilot-instructions.md +21 -0
- package/.agent-src/templates/copilot-review-instructions.md +76 -0
- package/.agent-src/templates/features.md +1 -1
- package/.agent-src/templates/rule.md +127 -0
- package/.claude-plugin/marketplace.json +4 -1
- package/AGENTS.md +32 -5
- package/CHANGELOG.md +69 -3
- package/README.md +22 -21
- package/config/agent-settings.template.yml +44 -10
- package/config/gitignore-block.txt +7 -0
- package/docs/architecture.md +86 -5
- package/docs/catalog.md +16 -6
- package/docs/contracts/agent-memory-contract.md +1 -1
- package/docs/contracts/context-paths.md +2 -1
- package/docs/contracts/file-ownership-matrix.json +354 -500
- package/docs/contracts/iron-law-overrides.txt +25 -0
- package/docs/contracts/kernel-membership.md +273 -0
- package/docs/contracts/load-context-schema.md +26 -11
- package/docs/contracts/pilot/agent-authority.md +24 -0
- package/docs/contracts/pilot/direct-answers.md +70 -0
- package/docs/contracts/pilot/language-and-tone.md +63 -0
- package/docs/contracts/rule-classification.md +170 -0
- package/docs/contracts/rule-router.md +153 -0
- package/docs/customization.md +17 -6
- package/docs/decisions/ADR-001-kernel-swap-deferred.md +109 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +124 -0
- package/docs/decisions/ADR-rule-kernel-and-router.md +122 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +176 -0
- package/docs/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/docs/guidelines/agent-infra/size-and-scope.md +13 -2
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +119 -0
- package/docs/guidelines/augment-portability-patterns.md +68 -0
- package/docs/guidelines/php/php-coding-patterns.md +62 -0
- package/package.json +1 -1
- package/scripts/_p43_bodies.py +235 -0
- package/scripts/_p43_compress.py +118 -0
- package/scripts/_p4_migrate.py +199 -0
- package/scripts/_pilot_council_question.py +57 -0
- package/scripts/_pilot_measure.py +53 -0
- package/scripts/ai_council/session.py +107 -5
- package/scripts/build_linear_digest.py +3 -5
- package/scripts/check_always_budget.py +39 -6
- package/scripts/check_compressed_paths.py +213 -0
- package/scripts/check_compression.py +15 -0
- package/scripts/check_context_paths.py +1 -0
- package/scripts/check_council_layout.py +105 -0
- package/scripts/check_council_references.py +145 -0
- package/scripts/check_portability.py +2 -0
- package/scripts/check_references.py +2 -0
- package/scripts/check_token_optimizer_freshness.py +131 -0
- package/scripts/compile_router.py +148 -0
- package/scripts/compress.py +219 -11
- package/scripts/council_cli.py +9 -5
- package/scripts/council_prune.py +81 -0
- package/scripts/count_token_optimizer_usage.sh +54 -0
- package/scripts/install.sh +44 -2
- package/scripts/iron_law_sha.py +98 -0
- package/scripts/lint_load_context.py +35 -5
- package/scripts/measure_rule_budget.py +314 -0
- package/scripts/prototype_lint_contradictions.py +150 -0
- package/scripts/schemas/rule.schema.json +55 -6
- package/scripts/skill_linter.py +196 -6
- package/scripts/smoke_path_resolution.py +93 -0
- package/scripts/validate_frontmatter.py +41 -1
- package/.agent-src/contexts/communication/rules-auto/artifact-engagement-recording-mechanics.md +0 -72
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +0 -79
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +0 -87
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +0 -62
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +0 -85
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +0 -65
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +0 -78
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +0 -53
- /package/{docs → .agent-src/contexts}/contracts/artifact-engagement-flow.md +0 -0
- /package/{docs → .agent-src/contexts}/contracts/command-suggestion-flow.md +0 -0
package/scripts/compress.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
3
|
Agent-config sync — compress .agent-src.uncompressed/ → .agent-src/
|
|
4
|
-
and project .agent-src/ → .augment/ (copies for rules
|
|
4
|
+
and project .agent-src/ → .augment/ (copies for rules by default,
|
|
5
|
+
symlinks for the rest; opt into rule symlinks via
|
|
6
|
+
augment.rules_use_symlinks in .agent-settings.yml).
|
|
5
7
|
|
|
6
8
|
Copies non-.md files as-is. Lists .md files that need compression (done by the
|
|
7
9
|
Augment agent interactively). Tracks SHA-256 hashes of source files to detect
|
|
@@ -19,6 +21,7 @@ Usage:
|
|
|
19
21
|
|
|
20
22
|
import hashlib
|
|
21
23
|
import json
|
|
24
|
+
import re
|
|
22
25
|
import shutil
|
|
23
26
|
import sys
|
|
24
27
|
from pathlib import Path
|
|
@@ -28,11 +31,41 @@ SOURCE_DIR = PROJECT_ROOT / ".agent-src.uncompressed"
|
|
|
28
31
|
TARGET_DIR = PROJECT_ROOT / ".agent-src"
|
|
29
32
|
AUGMENT_DIR = PROJECT_ROOT / ".augment"
|
|
30
33
|
HASH_FILE = PROJECT_ROOT / ".compression-hashes.json"
|
|
34
|
+
SETTINGS_FILE = PROJECT_ROOT / ".agent-settings.yml"
|
|
31
35
|
|
|
32
36
|
# Files to copy as-is even if .md (not compressed by agent)
|
|
33
37
|
COPY_AS_IS = {"README.md"}
|
|
34
38
|
|
|
35
39
|
|
|
40
|
+
def _read_augment_rules_use_symlinks() -> bool:
|
|
41
|
+
"""Read augment.rules_use_symlinks from .agent-settings.yml.
|
|
42
|
+
|
|
43
|
+
Returns True only when the setting is present under the top-level
|
|
44
|
+
``augment:`` block and resolves to a truthy YAML scalar
|
|
45
|
+
(true/yes/on/1, case-insensitive). Missing file, missing block, or
|
|
46
|
+
any other value → False (preserve copy default).
|
|
47
|
+
"""
|
|
48
|
+
if not SETTINGS_FILE.exists():
|
|
49
|
+
return False
|
|
50
|
+
try:
|
|
51
|
+
text = SETTINGS_FILE.read_text(encoding="utf-8")
|
|
52
|
+
except OSError:
|
|
53
|
+
return False
|
|
54
|
+
in_augment = False
|
|
55
|
+
for line in text.splitlines():
|
|
56
|
+
stripped = line.lstrip()
|
|
57
|
+
if not stripped or stripped.startswith("#"):
|
|
58
|
+
continue
|
|
59
|
+
if not line.startswith((" ", "\t")):
|
|
60
|
+
in_augment = stripped.startswith("augment:")
|
|
61
|
+
continue
|
|
62
|
+
if in_augment:
|
|
63
|
+
m = re.match(r"^\s+rules_use_symlinks\s*:\s*([^\s#]+)", line)
|
|
64
|
+
if m:
|
|
65
|
+
return m.group(1).strip().lower() in ("true", "yes", "on", "1")
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
36
69
|
|
|
37
70
|
|
|
38
71
|
def file_hash(filepath: Path) -> str:
|
|
@@ -59,17 +92,42 @@ def save_hashes(hashes: dict) -> None:
|
|
|
59
92
|
|
|
60
93
|
|
|
61
94
|
def mark_done(relative_path: str) -> None:
|
|
62
|
-
"""Mark a single file as compressed by storing its current source hash.
|
|
95
|
+
"""Mark a single file as compressed by storing its current source hash.
|
|
96
|
+
|
|
97
|
+
Also runs the path rewriter on the just-written `.agent-src/<path>` so
|
|
98
|
+
logical names from the source frontmatter resolve to deployment-correct
|
|
99
|
+
relative paths in the shipped layer (P1 of road-to-path-fixes.md).
|
|
100
|
+
Idempotent — re-running is a no-op.
|
|
101
|
+
"""
|
|
63
102
|
source_file = SOURCE_DIR / relative_path
|
|
64
103
|
if not source_file.exists():
|
|
65
104
|
print(f"❌ Source file not found: {relative_path}")
|
|
66
105
|
sys.exit(1)
|
|
106
|
+
apply_path_rewriter(relative_path)
|
|
67
107
|
hashes = load_hashes()
|
|
68
108
|
hashes[relative_path] = file_hash(source_file)
|
|
69
109
|
save_hashes(hashes)
|
|
70
110
|
print(f"✅ Marked as compressed: {relative_path}")
|
|
71
111
|
|
|
72
112
|
|
|
113
|
+
def apply_path_rewriter(relative_path: str) -> bool:
|
|
114
|
+
"""Apply `_rewrite_paths` to `.agent-src/<relative_path>` in-place.
|
|
115
|
+
|
|
116
|
+
Returns True if the file was modified, False otherwise. Silently
|
|
117
|
+
returns False if the target doesn't exist (compression hasn't run
|
|
118
|
+
yet) — `--mark-done` is also valid before content exists.
|
|
119
|
+
"""
|
|
120
|
+
target = TARGET_DIR / relative_path
|
|
121
|
+
if not target.exists() or not relative_path.endswith(".md"):
|
|
122
|
+
return False
|
|
123
|
+
original = target.read_text(encoding="utf-8")
|
|
124
|
+
rewritten = _rewrite_paths(original, relative_path)
|
|
125
|
+
if rewritten == original:
|
|
126
|
+
return False
|
|
127
|
+
target.write_text(rewritten, encoding="utf-8")
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
|
|
73
131
|
def mark_all_done() -> None:
|
|
74
132
|
"""Mark ALL .md files as compressed (e.g. after initial full compression)."""
|
|
75
133
|
hashes = load_hashes()
|
|
@@ -250,6 +308,144 @@ def strip_frontmatter(content: str) -> str:
|
|
|
250
308
|
return content
|
|
251
309
|
|
|
252
310
|
|
|
311
|
+
# ── Path rewriter (P1 of road-to-path-fixes.md) ───────────────────────────
|
|
312
|
+
# Source files use logical names that the rewriter resolves at compress
|
|
313
|
+
# time, so the shipped `.agent-src/` (and `.augment/` projection) carry
|
|
314
|
+
# deployment-correct relative paths without the agent author having to
|
|
315
|
+
# know how deep their file lives.
|
|
316
|
+
#
|
|
317
|
+
# Frontmatter rewrites:
|
|
318
|
+
# load_context: / load_context_eager:
|
|
319
|
+
# contexts/<area>/<file>.md (logical, preferred)
|
|
320
|
+
# .agent-src.uncompressed/contexts/<area>/<file>.md (legacy)
|
|
321
|
+
# → ../contexts/<area>/<file>.md (relative from .agent-src/rules/)
|
|
322
|
+
# triggers[].path_prefix:
|
|
323
|
+
# LEFT ALONE — `path_prefix:` is a literal match pattern, not a
|
|
324
|
+
# file reference. Source-of-truth rules that fire on edits under
|
|
325
|
+
# `.agent-src.uncompressed/` keep that prefix verbatim (see
|
|
326
|
+
# road-to-path-fixes.md P2.2 / Modified Option 1).
|
|
327
|
+
#
|
|
328
|
+
# Body-link rewrites:
|
|
329
|
+
# ../../docs/guidelines/<file>.md → ../docs/guidelines/<file>.md
|
|
330
|
+
# ../../docs/contracts/<file>.md → ../docs/contracts/<file>.md
|
|
331
|
+
#
|
|
332
|
+
# Idempotent: applying twice is a no-op (rewritten patterns no longer
|
|
333
|
+
# match the source patterns).
|
|
334
|
+
|
|
335
|
+
_LEGACY_SRC_PREFIX = ".agent-src.uncompressed/"
|
|
336
|
+
_PROJECTED_SRC_PREFIX = ".agent-src/"
|
|
337
|
+
|
|
338
|
+
# A YAML list item under load_context*: ` - some/path.md` (optionally quoted)
|
|
339
|
+
_FM_LIST_ITEM_RE = re.compile(r'^(\s*-\s*)(["\']?)([^"\'\n]+?\.md)(["\']?)\s*$')
|
|
340
|
+
|
|
341
|
+
# `path_prefix:` line — top-level or under `triggers:` (with leading dash)
|
|
342
|
+
_FM_PATH_PREFIX_RE = re.compile(
|
|
343
|
+
r'^(\s*(?:-\s+)?path_prefix:\s*)(["\']?)([^"\'\n]+?)(["\']?)\s*$'
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Body-link patterns (relative two-up to docs/) — capture the docs/... tail
|
|
347
|
+
_BODY_DOCS_RE = re.compile(r'\.\./\.\./(docs/(?:guidelines|contracts)/[^)\s]+\.md)')
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _depth_prefix(source_relative_path: str) -> str:
|
|
351
|
+
"""Return the `../` chain to climb from `<source_relative_path>` back to
|
|
352
|
+
the source root. A file at `rules/X.md` (1 dir deep) needs `../`; a
|
|
353
|
+
file at `commands/council/default.md` (2 dirs deep) needs `../../`.
|
|
354
|
+
"""
|
|
355
|
+
parts = Path(source_relative_path).parts
|
|
356
|
+
depth = max(len(parts) - 1, 1)
|
|
357
|
+
return "../" * depth
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _split_frontmatter(content: str):
|
|
361
|
+
"""Return (frontmatter_lines, body) — frontmatter_lines is None if no FM."""
|
|
362
|
+
if not content.startswith("---\n"):
|
|
363
|
+
return None, content
|
|
364
|
+
end = content.find("\n---\n", 4)
|
|
365
|
+
if end == -1:
|
|
366
|
+
return None, content
|
|
367
|
+
fm_text = content[4:end]
|
|
368
|
+
body = content[end + len("\n---\n"):]
|
|
369
|
+
return fm_text.split("\n"), body
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _rewrite_load_context_value(value: str, prefix: str) -> str:
|
|
373
|
+
"""Rewrite a single `load_context` list-item value to a deployment path."""
|
|
374
|
+
# Already relative or absolute → leave alone (idempotence).
|
|
375
|
+
if value.startswith(("../", "./", "/")):
|
|
376
|
+
return value
|
|
377
|
+
# Legacy fully-qualified source prefix.
|
|
378
|
+
if value.startswith(_LEGACY_SRC_PREFIX):
|
|
379
|
+
return prefix + value[len(_LEGACY_SRC_PREFIX):]
|
|
380
|
+
# Projected source prefix (defensive — also strip).
|
|
381
|
+
if value.startswith(_PROJECTED_SRC_PREFIX):
|
|
382
|
+
return prefix + value[len(_PROJECTED_SRC_PREFIX):]
|
|
383
|
+
# Logical name (e.g. `contexts/execution/foo.md`).
|
|
384
|
+
return prefix + value
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _rewrite_path_prefix_value(value: str) -> str:
|
|
388
|
+
"""No-op for `triggers[].path_prefix:` values.
|
|
389
|
+
|
|
390
|
+
`path_prefix:` is a literal match pattern the host evaluates against
|
|
391
|
+
the file the agent is editing — not a file reference. Rewriting it
|
|
392
|
+
breaks the workflow it was authored for: source-of-truth rules that
|
|
393
|
+
fire when the agent edits files under `.agent-src.uncompressed/`
|
|
394
|
+
keep that prefix verbatim. The prefix ban therefore applies only to
|
|
395
|
+
`load_context:` entries and body links (see road-to-path-fixes.md
|
|
396
|
+
P2.2 + the AI-Council convergence on 2026-05-06).
|
|
397
|
+
"""
|
|
398
|
+
return value
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _rewrite_frontmatter_lines(lines, prefix):
|
|
402
|
+
"""Apply load_context / path_prefix rewrites to a frontmatter line list."""
|
|
403
|
+
in_load_context = False
|
|
404
|
+
out = []
|
|
405
|
+
for line in lines:
|
|
406
|
+
bare = line.lstrip()
|
|
407
|
+
if bare.startswith(("load_context:", "load_context_eager:")):
|
|
408
|
+
in_load_context = True
|
|
409
|
+
out.append(line)
|
|
410
|
+
continue
|
|
411
|
+
if in_load_context:
|
|
412
|
+
m = _FM_LIST_ITEM_RE.match(line)
|
|
413
|
+
if m:
|
|
414
|
+
indent, q1, value, q2 = m.groups()
|
|
415
|
+
rewritten = _rewrite_load_context_value(value, prefix)
|
|
416
|
+
out.append(f"{indent}{q1}{rewritten}{q2}")
|
|
417
|
+
continue
|
|
418
|
+
in_load_context = False
|
|
419
|
+
# fall through to path_prefix / passthrough
|
|
420
|
+
m = _FM_PATH_PREFIX_RE.match(line)
|
|
421
|
+
if m:
|
|
422
|
+
head, q1, value, q2 = m.groups()
|
|
423
|
+
out.append(f"{head}{q1}{_rewrite_path_prefix_value(value)}{q2}")
|
|
424
|
+
continue
|
|
425
|
+
out.append(line)
|
|
426
|
+
return out
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _rewrite_body_links(body: str, prefix: str) -> str:
|
|
430
|
+
"""Rewrite `../../docs/{guidelines,contracts}/...` to use depth-prefix."""
|
|
431
|
+
return _BODY_DOCS_RE.sub(prefix + r"\1", body)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _rewrite_paths(content: str, source_relative_path: str) -> str:
|
|
435
|
+
"""Rewrite logical / legacy paths in `content` for a file shipped at
|
|
436
|
+
`.agent-src/{source_relative_path}`. Idempotent.
|
|
437
|
+
|
|
438
|
+
See module-level comment above for the full pattern catalog.
|
|
439
|
+
"""
|
|
440
|
+
prefix = _depth_prefix(source_relative_path)
|
|
441
|
+
fm_lines, body = _split_frontmatter(content)
|
|
442
|
+
body = _rewrite_body_links(body, prefix)
|
|
443
|
+
if fm_lines is None:
|
|
444
|
+
return body
|
|
445
|
+
new_fm = _rewrite_frontmatter_lines(fm_lines, prefix)
|
|
446
|
+
return "---\n" + "\n".join(new_fm) + "\n---\n" + body
|
|
447
|
+
|
|
448
|
+
|
|
253
449
|
def generate_rule_symlinks() -> int:
|
|
254
450
|
"""Create symlink directories for rules (.claude/rules/, .cursor/rules/, .clinerules/).
|
|
255
451
|
|
|
@@ -499,8 +695,10 @@ def generate_tools() -> None:
|
|
|
499
695
|
# ── .augment/ projection ──────────────────────────────────────────────
|
|
500
696
|
# The package uses .agent-src/ as the tool-agnostic compressed source of truth.
|
|
501
697
|
# .augment/ is a generated projection so that Augment Code (which reads from
|
|
502
|
-
# .augment/
|
|
503
|
-
#
|
|
698
|
+
# .augment/) works on the package repo itself. Rules default to copies
|
|
699
|
+
# because Augment Code historically does not load symlinked rule files;
|
|
700
|
+
# flip augment.rules_use_symlinks: true in .agent-settings.yml to switch
|
|
701
|
+
# them to symlinks (everything else is always symlinked).
|
|
504
702
|
|
|
505
703
|
# Subdirectories of .agent-src/ that map into .augment/ as symlinks.
|
|
506
704
|
AUGMENT_SYMLINK_DIRS = ("skills", "commands", "guidelines", "personas", "templates", "contexts", "scripts")
|
|
@@ -509,34 +707,44 @@ AUGMENT_SYMLINK_FILES = ("README.md",)
|
|
|
509
707
|
|
|
510
708
|
|
|
511
709
|
def project_to_augment() -> None:
|
|
512
|
-
"""Mirror .agent-src/ into .augment/.
|
|
710
|
+
"""Mirror .agent-src/ into .augment/. Symlink everything except rules,
|
|
711
|
+
which default to copies; opt into rule symlinks via
|
|
712
|
+
augment.rules_use_symlinks in .agent-settings.yml."""
|
|
513
713
|
if not TARGET_DIR.exists():
|
|
514
714
|
print(f" ⚠️ {TARGET_DIR.name}/ not found — nothing to project")
|
|
515
715
|
return
|
|
516
716
|
|
|
517
717
|
AUGMENT_DIR.mkdir(parents=True, exist_ok=True)
|
|
518
718
|
|
|
519
|
-
|
|
719
|
+
use_symlinks = _read_augment_rules_use_symlinks()
|
|
720
|
+
|
|
721
|
+
# Rules: copy by default (Augment Code historically does not load
|
|
722
|
+
# symlinked rules), or symlink when augment.rules_use_symlinks is true.
|
|
520
723
|
src_rules = TARGET_DIR / "rules"
|
|
521
724
|
dst_rules = AUGMENT_DIR / "rules"
|
|
522
725
|
dst_rules.mkdir(parents=True, exist_ok=True)
|
|
523
726
|
existing = {f.name for f in dst_rules.iterdir() if f.is_file() or f.is_symlink()}
|
|
524
727
|
current = set()
|
|
525
|
-
|
|
728
|
+
written = 0
|
|
526
729
|
if src_rules.exists():
|
|
527
730
|
for rule in sorted(src_rules.glob("*.md")):
|
|
528
731
|
target = dst_rules / rule.name
|
|
529
|
-
|
|
732
|
+
# Always remove first to avoid copy↔symlink mode mismatch.
|
|
733
|
+
if target.is_symlink() or target.exists():
|
|
530
734
|
target.unlink()
|
|
531
|
-
|
|
735
|
+
if use_symlinks:
|
|
736
|
+
target.symlink_to(Path("..") / ".." / ".agent-src" / "rules" / rule.name)
|
|
737
|
+
else:
|
|
738
|
+
shutil.copy2(rule, target)
|
|
532
739
|
current.add(rule.name)
|
|
533
|
-
|
|
740
|
+
written += 1
|
|
534
741
|
# Remove stale rule files
|
|
535
742
|
removed_rules = 0
|
|
536
743
|
for name in existing - current:
|
|
537
744
|
(dst_rules / name).unlink()
|
|
538
745
|
removed_rules += 1
|
|
539
|
-
|
|
746
|
+
mode_label = "Symlinked" if use_symlinks else "Copied"
|
|
747
|
+
print(f" ✅ {mode_label} {written} rules to .augment/rules/" + (f" ({removed_rules} stale removed)" if removed_rules else ""))
|
|
540
748
|
|
|
541
749
|
# Subdirectories: replace each with a symlink → ../.agent-src/<subdir>
|
|
542
750
|
for sub in AUGMENT_SYMLINK_DIRS:
|
package/scripts/council_cli.py
CHANGED
|
@@ -263,17 +263,19 @@ def cmd_run(
|
|
|
263
263
|
)
|
|
264
264
|
return 0
|
|
265
265
|
|
|
266
|
-
|
|
266
|
+
ai_cfg = settings.get("ai_council") or {}
|
|
267
|
+
cost_cfg = ai_cfg.get("cost_budget") or {}
|
|
267
268
|
budget = CostBudget(
|
|
268
269
|
max_input_tokens=int(cost_cfg.get("max_input_tokens", 50_000)),
|
|
269
270
|
max_output_tokens=int(cost_cfg.get("max_output_tokens", 20_000)),
|
|
270
271
|
max_calls=int(cost_cfg.get("max_calls", 10)),
|
|
271
272
|
max_total_usd=float(cost_cfg.get("max_total_usd", 0.0) or 0.0),
|
|
272
273
|
)
|
|
274
|
+
rounds = args.rounds if args.rounds is not None else int(ai_cfg.get("min_rounds", 2))
|
|
273
275
|
responses = consult(
|
|
274
276
|
members, question, budget,
|
|
275
277
|
table=table, project=project,
|
|
276
|
-
original_ask=args.original_ask, rounds=
|
|
278
|
+
original_ask=args.original_ask, rounds=rounds,
|
|
277
279
|
)
|
|
278
280
|
estimated_total = sum(e.total_usd for e in estimates)
|
|
279
281
|
actual_total = 0.0
|
|
@@ -288,7 +290,7 @@ def cmd_run(
|
|
|
288
290
|
"artefact": artefact,
|
|
289
291
|
"original_ask": args.original_ask,
|
|
290
292
|
"members": [f"{m.name}/{m.model}" for m in members],
|
|
291
|
-
"rounds":
|
|
293
|
+
"rounds": rounds,
|
|
292
294
|
"cost_usd_estimated": round(estimated_total, 6),
|
|
293
295
|
"cost_usd_actual": round(actual_total, 6),
|
|
294
296
|
"responses": _serialise_responses(responses),
|
|
@@ -374,8 +376,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
374
376
|
help="Path to write the responses JSON.")
|
|
375
377
|
p_run.add_argument("--confirm", action="store_true",
|
|
376
378
|
help="Required to actually invoke the council.")
|
|
377
|
-
p_run.add_argument("--rounds", type=int, default=
|
|
378
|
-
help="Number of debate rounds (1-3)."
|
|
379
|
+
p_run.add_argument("--rounds", type=int, default=None,
|
|
380
|
+
help="Number of debate rounds (1-3). Defaults to "
|
|
381
|
+
"ai_council.min_rounds in .agent-settings.yml "
|
|
382
|
+
"(or 2 if unset).")
|
|
379
383
|
|
|
380
384
|
p_ren = sub.add_parser("render", help="Re-render a saved responses JSON.")
|
|
381
385
|
p_ren.add_argument("responses",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Manual pruner for council artefacts.
|
|
3
|
+
|
|
4
|
+
Deletes council files older than `ai_council.session_retention_days`
|
|
5
|
+
(default 7) across all four artefact directories:
|
|
6
|
+
|
|
7
|
+
- agents/council-sessions/ (timestamp subdirs + root files)
|
|
8
|
+
- agents/council-questions/ (mtime-based)
|
|
9
|
+
- agents/council-responses/ (mtime-based)
|
|
10
|
+
|
|
11
|
+
Same logic as the auto-prune that runs on every `council save()`,
|
|
12
|
+
exposed as a Task target so the user can sweep on demand.
|
|
13
|
+
|
|
14
|
+
Invocation (from project root):
|
|
15
|
+
python3 scripts/council_prune.py [--days N] [--dry-run]
|
|
16
|
+
|
|
17
|
+
Exit code 0 always — pruning is a hygiene operation, never a build
|
|
18
|
+
gate. Disk failures are logged to stderr by the underlying pruner.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# Bootstrap import path so `python3 scripts/council_prune.py` works
|
|
28
|
+
# from the project root without an editable install.
|
|
29
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
30
|
+
|
|
31
|
+
from scripts.ai_council.session import ( # noqa: E402
|
|
32
|
+
_load_retention_days,
|
|
33
|
+
prune_all_council_artifacts,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> int:
|
|
38
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--days",
|
|
41
|
+
type=int,
|
|
42
|
+
default=None,
|
|
43
|
+
help="Override retention_days (default: from .agent-settings.yml)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--dry-run",
|
|
47
|
+
action="store_true",
|
|
48
|
+
help="List what would be deleted without removing anything.",
|
|
49
|
+
)
|
|
50
|
+
args = parser.parse_args()
|
|
51
|
+
|
|
52
|
+
days = args.days if args.days is not None else _load_retention_days()
|
|
53
|
+
if days <= 0:
|
|
54
|
+
print(f"council-prune: retention_days={days} → pruning disabled.")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
if args.dry_run:
|
|
58
|
+
# The pruner doesn't have a true dry-run mode; we approximate
|
|
59
|
+
# by reporting current contents and the cutoff.
|
|
60
|
+
print(f"council-prune: dry-run, cutoff = retention_days={days}")
|
|
61
|
+
print("council-prune: actual deletion requires omitting --dry-run")
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
print(f"council-prune: retention_days={days}")
|
|
65
|
+
result = prune_all_council_artifacts(retention_days=days)
|
|
66
|
+
total = 0
|
|
67
|
+
for label, removed in result.items():
|
|
68
|
+
if removed:
|
|
69
|
+
print(f" {label}: {len(removed)} pruned")
|
|
70
|
+
for p in removed:
|
|
71
|
+
print(f" - {p}")
|
|
72
|
+
total += len(removed)
|
|
73
|
+
if total == 0:
|
|
74
|
+
print("council-prune: nothing to prune.")
|
|
75
|
+
else:
|
|
76
|
+
print(f"council-prune: pruned {total} entries total.")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Token-Optimizer telemetry counter.
|
|
3
|
+
#
|
|
4
|
+
# Per `road-to-token-optimization.md` P1.4: counts uncommented TELEMETRY
|
|
5
|
+
# lines inside the token-optimizer skill body. Each consult bumps a line.
|
|
6
|
+
# Decision rule: <5 consults / 2 weeks → P3.1 sunset audit fires.
|
|
7
|
+
#
|
|
8
|
+
# Output: 7-day count, 30-day count, total. Stdout only, no side effects.
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
13
|
+
SKILL="$REPO_ROOT/.agent-src.uncompressed/skills/token-optimizer/SKILL.md"
|
|
14
|
+
|
|
15
|
+
if [[ ! -f "$SKILL" ]]; then
|
|
16
|
+
echo "ERROR: $SKILL not found" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Active TELEMETRY lines = those NOT inside an HTML comment.
|
|
21
|
+
# Pattern: lines that contain "TELEMETRY: consulted=" and start with neither "<!--" nor whitespace.
|
|
22
|
+
total=$(grep -cE '^TELEMETRY: consulted=' "$SKILL" || true)
|
|
23
|
+
|
|
24
|
+
today_epoch=$(date -u +%s)
|
|
25
|
+
seven_days_ago=$(( today_epoch - 7 * 86400 ))
|
|
26
|
+
thirty_days_ago=$(( today_epoch - 30 * 86400 ))
|
|
27
|
+
|
|
28
|
+
count_since() {
|
|
29
|
+
local cutoff="$1"
|
|
30
|
+
local n=0
|
|
31
|
+
while IFS= read -r line; do
|
|
32
|
+
# Extract the ISO timestamp after "consulted="
|
|
33
|
+
ts=$(echo "$line" | sed -nE 's/^TELEMETRY: consulted=\[?([0-9TZ:+\-]+)\]?.*/\1/p')
|
|
34
|
+
[[ -z "$ts" ]] && continue
|
|
35
|
+
# Convert ISO → epoch (BSD `date -j` on macOS, GNU `date -d` on Linux)
|
|
36
|
+
epoch=$(date -j -u -f "%Y-%m-%dT%H:%M:%SZ" "$ts" +%s 2>/dev/null \
|
|
37
|
+
|| date -u -d "$ts" +%s 2>/dev/null \
|
|
38
|
+
|| echo 0)
|
|
39
|
+
if [[ "$epoch" -ge "$cutoff" ]]; then
|
|
40
|
+
n=$(( n + 1 ))
|
|
41
|
+
fi
|
|
42
|
+
done < <(grep -E '^TELEMETRY: consulted=' "$SKILL" || true)
|
|
43
|
+
echo "$n"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
count_7d=$(count_since "$seven_days_ago")
|
|
47
|
+
count_30d=$(count_since "$thirty_days_ago")
|
|
48
|
+
|
|
49
|
+
echo "token-optimizer consults:"
|
|
50
|
+
echo " last 7 days: $count_7d"
|
|
51
|
+
echo " last 30 days: $count_30d"
|
|
52
|
+
echo " total active: $total"
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Decision rule: <5 consults / 2 weeks sustained → P3.1 sunset audit."
|
package/scripts/install.sh
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# install.sh — Agent-config payload sync (one of two installer stages).
|
|
3
3
|
#
|
|
4
4
|
# Reads from vendor's .agent-src/ (fallback: .augment/ for pre-2.0 packages) and
|
|
5
|
-
# writes the target project's .augment/ tree: copies rules, symlinks everything
|
|
5
|
+
# writes the target project's .augment/ tree: copies rules, symlinks everything
|
|
6
|
+
# else. When augment.rules_use_symlinks: true is set in the target's
|
|
7
|
+
# .agent-settings.yml, rules are symlinked instead of copied.
|
|
6
8
|
# Creates tool-specific directories for Claude Code, Cursor, Cline, Windsurf, Gemini.
|
|
7
9
|
#
|
|
8
10
|
# Does NOT render .agent-settings.yml or bridge JSONs — that is the job of
|
|
@@ -35,6 +37,9 @@ DRY_RUN=false
|
|
|
35
37
|
VERBOSE=false
|
|
36
38
|
QUIET=false
|
|
37
39
|
SKIP_GITIGNORE=false
|
|
40
|
+
# Resolved from <TARGET>/.agent-settings.yml in resolve_settings(); when
|
|
41
|
+
# true, .augment/rules/ files are symlinked instead of copied.
|
|
42
|
+
USE_RULES_SYMLINKS=false
|
|
38
43
|
|
|
39
44
|
# --- Logging ---
|
|
40
45
|
log_info() { $QUIET || echo " ✅ $*"; }
|
|
@@ -114,6 +119,30 @@ EOF
|
|
|
114
119
|
|
|
115
120
|
# --- Utility functions ---
|
|
116
121
|
|
|
122
|
+
# Read augment.rules_use_symlinks from <TARGET>/.agent-settings.yml.
|
|
123
|
+
# Sets USE_RULES_SYMLINKS=true|false. Missing file or absent key → false.
|
|
124
|
+
# Minimal scoped parser; avoids a hard yq/python dependency.
|
|
125
|
+
resolve_settings() {
|
|
126
|
+
USE_RULES_SYMLINKS=false
|
|
127
|
+
local settings_file="$TARGET_DIR/.agent-settings.yml"
|
|
128
|
+
[[ -f "$settings_file" ]] || return 0
|
|
129
|
+
local val
|
|
130
|
+
val=$(awk '
|
|
131
|
+
/^[^[:space:]#]/ { in_block = ($0 ~ /^augment:[[:space:]]*$/) }
|
|
132
|
+
in_block && /^[[:space:]]+rules_use_symlinks[[:space:]]*:/ {
|
|
133
|
+
line = $0
|
|
134
|
+
sub(/^[[:space:]]*rules_use_symlinks[[:space:]]*:[[:space:]]*/, "", line)
|
|
135
|
+
sub(/[[:space:]]*#.*$/, "", line)
|
|
136
|
+
gsub(/[[:space:]]/, "", line)
|
|
137
|
+
print tolower(line)
|
|
138
|
+
exit
|
|
139
|
+
}
|
|
140
|
+
' "$settings_file" 2>/dev/null || true)
|
|
141
|
+
case "$val" in
|
|
142
|
+
true|yes|on|1) USE_RULES_SYMLINKS=true ;;
|
|
143
|
+
esac
|
|
144
|
+
}
|
|
145
|
+
|
|
117
146
|
# Check if a relative path should be copied (true=copy) or symlinked (false=symlink)
|
|
118
147
|
should_copy() {
|
|
119
148
|
local rel_path="$1"
|
|
@@ -127,6 +156,10 @@ should_copy() {
|
|
|
127
156
|
# Check against COPY_DIRS
|
|
128
157
|
for dir in $COPY_DIRS; do
|
|
129
158
|
if [[ "$first_segment" == "$dir" ]]; then
|
|
159
|
+
# Honor augment.rules_use_symlinks toggle for the rules dir.
|
|
160
|
+
if [[ "$dir" == "rules" ]] && $USE_RULES_SYMLINKS; then
|
|
161
|
+
return 1
|
|
162
|
+
fi
|
|
130
163
|
return 0
|
|
131
164
|
fi
|
|
132
165
|
done
|
|
@@ -669,9 +702,17 @@ main() {
|
|
|
669
702
|
# 0. Migrate legacy infra files (root → agents/) before any content sync.
|
|
670
703
|
migrate_legacy_root_infra "$TARGET_DIR"
|
|
671
704
|
|
|
705
|
+
# 0b. Resolve settings (e.g. augment.rules_use_symlinks). On first
|
|
706
|
+
# install the file does not exist yet → defaults preserved.
|
|
707
|
+
resolve_settings
|
|
708
|
+
|
|
672
709
|
# 1. Hybrid sync payload → target/.augment/
|
|
673
710
|
sync_hybrid "$SOURCE_PAYLOAD" "$TARGET_DIR/.augment"
|
|
674
|
-
|
|
711
|
+
if $USE_RULES_SYMLINKS; then
|
|
712
|
+
log_info "Synced .augment/ (rules symlinked, rest symlinked)"
|
|
713
|
+
else
|
|
714
|
+
log_info "Synced .augment/ (rules copied, rest symlinked)"
|
|
715
|
+
fi
|
|
675
716
|
|
|
676
717
|
# 2. Copy standalone files from templates if missing on the target.
|
|
677
718
|
# We copy from templates/ (generic placeholders), NOT from the package's
|
|
@@ -680,6 +721,7 @@ main() {
|
|
|
680
721
|
# into consumer projects.
|
|
681
722
|
copy_if_missing "$SOURCE_PAYLOAD/templates/AGENTS.md" "$TARGET_DIR/AGENTS.md"
|
|
682
723
|
copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-instructions.md" "$TARGET_DIR/.github/copilot-instructions.md"
|
|
724
|
+
copy_if_missing "$SOURCE_PAYLOAD/templates/copilot-review-instructions.md" "$TARGET_DIR/.github/copilot-review-instructions.md"
|
|
683
725
|
|
|
684
726
|
# 3. Create tool-specific symlinks
|
|
685
727
|
create_tool_symlinks "$TARGET_DIR"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""SHA-256 of every triple-fence block in a rule file (Iron Law preservation).
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/iron_law_sha.py <rule-id> [<rule-id> ...]
|
|
6
|
+
python3 scripts/iron_law_sha.py --all-kernel
|
|
7
|
+
python3 scripts/iron_law_sha.py --diff <rule-id> --against <baseline-sha>
|
|
8
|
+
|
|
9
|
+
The Iron-Law block is delimited by triple-backtick fences. Every line
|
|
10
|
+
inside any fence in the file is concatenated, whitespace-normalised
|
|
11
|
+
(runs of spaces collapsed; leading / trailing whitespace stripped per
|
|
12
|
+
line), case-folded, then SHA-256-hashed. Empty fences hash to
|
|
13
|
+
SHA-256(''), which is `e3b0c442…` (the well-known empty-string hash).
|
|
14
|
+
|
|
15
|
+
Acceptance per `road-to-kernel-and-router.md` P2.2: re-runnable,
|
|
16
|
+
deterministic, stdlib-only, no network. Compression of a kernel rule
|
|
17
|
+
must preserve this SHA (or surface a deliberate ADR-tracked diff).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import hashlib
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
29
|
+
RULES_DIR = REPO_ROOT / ".agent-src.uncompressed" / "rules"
|
|
30
|
+
|
|
31
|
+
# Locked kernel set — kept in sync with measure_rule_budget.KERNEL_RULES.
|
|
32
|
+
KERNEL_RULES = (
|
|
33
|
+
"agent-authority",
|
|
34
|
+
"ask-when-uncertain",
|
|
35
|
+
"commit-policy",
|
|
36
|
+
"direct-answers",
|
|
37
|
+
"language-and-tone",
|
|
38
|
+
"no-cheap-questions",
|
|
39
|
+
"non-destructive-by-default",
|
|
40
|
+
"scope-control",
|
|
41
|
+
"verify-before-complete",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
_FENCE_RE = re.compile(r"```(?:[^\n]*\n)([\s\S]*?)```")
|
|
45
|
+
_WS_RE = re.compile(r"\s+")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def iron_law_sha(text: str) -> str:
|
|
49
|
+
"""SHA-256 of all triple-fence content, whitespace-collapsed, upper-cased.
|
|
50
|
+
|
|
51
|
+
Algorithm matches `scripts/_pilot_measure.py` exactly so the SHAs
|
|
52
|
+
recorded in `docs/contracts/kernel-membership.md` § 2 stay
|
|
53
|
+
reproducible across pre / post compression.
|
|
54
|
+
"""
|
|
55
|
+
blocks = _FENCE_RE.findall(text)
|
|
56
|
+
norm = "".join(_WS_RE.sub(" ", b).strip().upper() for b in blocks)
|
|
57
|
+
return hashlib.sha256(norm.encode("utf-8")).hexdigest()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def rule_sha(rule_id: str) -> str:
|
|
61
|
+
path = RULES_DIR / f"{rule_id}.md"
|
|
62
|
+
if not path.exists():
|
|
63
|
+
raise FileNotFoundError(path)
|
|
64
|
+
return iron_law_sha(path.read_text(encoding="utf-8"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main(argv: list[str] | None = None) -> int:
|
|
68
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
69
|
+
parser.add_argument("rules", nargs="*", help="rule ids (omit if --all-kernel)")
|
|
70
|
+
parser.add_argument("--all-kernel", action="store_true", help="hash all 9 kernel rules")
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--diff", metavar="RULE", help="hash one rule and compare to --against"
|
|
73
|
+
)
|
|
74
|
+
parser.add_argument("--against", metavar="SHA", help="expected SHA (for --diff)")
|
|
75
|
+
args = parser.parse_args(argv)
|
|
76
|
+
|
|
77
|
+
if args.diff:
|
|
78
|
+
if not args.against:
|
|
79
|
+
parser.error("--diff requires --against")
|
|
80
|
+
actual = rule_sha(args.diff)
|
|
81
|
+
match = actual == args.against
|
|
82
|
+
symbol = "✅" if match else "❌"
|
|
83
|
+
print(f"{symbol} {args.diff}: {actual} (expected {args.against})")
|
|
84
|
+
return 0 if match else 1
|
|
85
|
+
|
|
86
|
+
targets = list(KERNEL_RULES) if args.all_kernel else args.rules
|
|
87
|
+
if not targets:
|
|
88
|
+
parser.error("provide rule ids, or use --all-kernel")
|
|
89
|
+
|
|
90
|
+
width = max(len(t) for t in targets)
|
|
91
|
+
for rid in targets:
|
|
92
|
+
sha = rule_sha(rid)
|
|
93
|
+
print(f"{rid:<{width}} {sha}")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
sys.exit(main())
|