@event4u/agent-config 1.22.0 → 1.23.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/cleanup.md +31 -17
- package/.agent-src/commands/commit/in-chunks.md +30 -10
- package/.agent-src/commands/commit.md +46 -6
- package/.agent-src/commands/compress.md +19 -13
- package/.agent-src/commands/cost-report.md +120 -0
- package/.agent-src/commands/create-pr/description-only.md +8 -0
- package/.agent-src/commands/create-pr.md +95 -80
- package/.agent-src/commands/feature/plan.md +13 -7
- package/.agent-src/commands/memory/add.md +16 -8
- package/.agent-src/commands/memory/promote.md +17 -9
- package/.agent-src/commands/optimize/rtk.md +16 -11
- package/.agent-src/commands/prepare-for-review.md +12 -6
- package/.agent-src/commands/project-analyze.md +31 -20
- package/.agent-src/commands/review-changes.md +24 -15
- package/.agent-src/commands/roadmap/create.md +14 -9
- package/.agent-src/contexts/contracts/frugality-charter.md +57 -0
- package/.agent-src/rules/architecture.md +9 -0
- package/.agent-src/rules/ask-when-uncertain.md +3 -13
- package/.agent-src/rules/caveman-speak.md +78 -0
- package/.agent-src/rules/direct-answers.md +5 -14
- package/.agent-src/rules/markdown-safe-codeblocks.md +6 -7
- package/.agent-src/rules/no-cheap-questions.md +4 -14
- package/.agent-src/rules/token-efficiency.md +5 -7
- package/.agent-src/skills/adr-create/SKILL.md +197 -0
- package/.agent-src/skills/agent-docs-writing/SKILL.md +23 -1
- package/.agent-src/skills/command-writing/SKILL.md +23 -0
- package/.agent-src/skills/context-authoring/SKILL.md +23 -0
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +23 -0
- package/.agent-src/skills/guideline-writing/SKILL.md +22 -0
- package/.agent-src/skills/persona-writing/SKILL.md +153 -0
- package/.agent-src/skills/readme-writing/SKILL.md +20 -0
- package/.agent-src/skills/readme-writing-package/SKILL.md +19 -0
- package/.agent-src/skills/roadmap-writing/SKILL.md +157 -0
- package/.agent-src/skills/rule-writing/SKILL.md +22 -0
- package/.agent-src/skills/script-writing/SKILL.md +226 -0
- package/.agent-src/skills/skill-writing/SKILL.md +23 -0
- package/.agent-src/skills/test-driven-development/SKILL.md +24 -0
- package/.agent-src/templates/agent-settings.md +73 -0
- package/.agent-src/templates/command.md +15 -10
- package/.agent-src/templates/rule.md +6 -0
- package/.agent-src/templates/skill.md +32 -0
- package/.claude-plugin/marketplace.json +6 -1
- package/AGENTS.md +3 -3
- package/CHANGELOG.md +35 -0
- package/README.md +5 -5
- package/docs/architecture.md +4 -4
- package/docs/customization.md +72 -0
- package/docs/decisions/INDEX.md +15 -0
- package/docs/getting-started.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +27 -19
- package/docs/guidelines/agent-infra/carve-out-predicates.md +17 -0
- package/docs/guidelines/agent-infra/mcp-request-signing.md +199 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +11 -4
- package/package.json +1 -1
- package/scripts/_lib/__init__.py +5 -0
- package/scripts/_lib/script_output.py +140 -0
- package/scripts/adr/regenerate_index.py +79 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_add_quiet.py +149 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_inject_quiet_flag.py +33 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_measure_v2.sh +36 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_measure_verbosity.sh +26 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_per_task.sh +41 -0
- package/scripts/ai_council/one_off_archive/2026-05/_one_off_silent_taskfiles.py +98 -0
- package/scripts/check_augmentignore.py +4 -1
- package/scripts/check_command_count_messaging.py +4 -1
- package/scripts/check_compressed_paths.py +4 -1
- package/scripts/check_council_layout.py +4 -1
- package/scripts/check_council_references.py +4 -1
- package/scripts/check_iron_law_prominence.py +3 -1
- package/scripts/check_md_language.py +3 -1
- package/scripts/check_memory_proposal.py +3 -1
- package/scripts/check_public_catalog_links.py +4 -1
- package/scripts/check_reply_consistency.py +8 -2
- package/scripts/check_roadmap_trackable.py +4 -1
- package/scripts/compile_router.py +27 -0
- package/scripts/compress.py +33 -19
- package/scripts/cost/budget.mjs +152 -0
- package/scripts/cost/track.mjs +144 -0
- package/scripts/first-run.sh +3 -9
- package/scripts/install-hooks.sh +19 -1
- package/scripts/install.py +17 -12
- package/scripts/install.sh +19 -8
- package/scripts/lint_examples.py +6 -2
- package/scripts/lint_handoffs.py +4 -1
- package/scripts/lint_load_context.py +4 -1
- package/scripts/lint_roadmap_complexity.py +6 -2
- package/scripts/lint_rule_interactions.py +4 -1
- package/scripts/lint_rule_tiers.py +4 -1
- package/scripts/measure_frugality_savings.py +164 -0
- package/scripts/runtime_dispatcher.py +11 -0
- package/scripts/skill_linter.py +207 -2
|
@@ -34,6 +34,8 @@ import sys
|
|
|
34
34
|
from pathlib import Path
|
|
35
35
|
from typing import Iterable
|
|
36
36
|
|
|
37
|
+
QUIET = "--quiet" in sys.argv
|
|
38
|
+
|
|
37
39
|
ROOT = Path(".")
|
|
38
40
|
|
|
39
41
|
# A specific file inside a council dir: must end with .md or .json,
|
|
@@ -124,7 +126,8 @@ def main() -> int:
|
|
|
124
126
|
violations.append((path, ln, ref))
|
|
125
127
|
|
|
126
128
|
if not violations:
|
|
127
|
-
|
|
129
|
+
if not QUIET:
|
|
130
|
+
print("✅ No forbidden council references in durable artefacts.")
|
|
128
131
|
return 0
|
|
129
132
|
|
|
130
133
|
print(f"❌ {len(violations)} forbidden council reference(s):\n")
|
|
@@ -111,6 +111,7 @@ def main() -> int:
|
|
|
111
111
|
help="Files or directories to scan (default: .agent-src.uncompressed/rules)",
|
|
112
112
|
)
|
|
113
113
|
parser.add_argument("--format", choices=["text", "json"], default="text")
|
|
114
|
+
parser.add_argument("--quiet", action="store_true", help="Only print on failure")
|
|
114
115
|
args = parser.parse_args()
|
|
115
116
|
|
|
116
117
|
targets = _resolve_targets(args.paths)
|
|
@@ -125,7 +126,8 @@ def main() -> int:
|
|
|
125
126
|
print(json.dumps([asdict(v) for v in all_violations], indent=2, ensure_ascii=False))
|
|
126
127
|
else:
|
|
127
128
|
if not all_violations:
|
|
128
|
-
|
|
129
|
+
if not args.quiet:
|
|
130
|
+
print(f"✅ Iron Law prominence clean ({len(targets)} file(s) scanned).")
|
|
129
131
|
else:
|
|
130
132
|
print(f"❌ {len(all_violations)} Iron-Law prominence violation(s):\n")
|
|
131
133
|
for v in all_violations:
|
|
@@ -124,6 +124,7 @@ def main() -> int:
|
|
|
124
124
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
125
125
|
parser.add_argument("paths", nargs="+", help="One or more .md files to scan")
|
|
126
126
|
parser.add_argument("--format", choices=["text", "json"], default="text")
|
|
127
|
+
parser.add_argument("--quiet", action="store_true", help="Only print on failure")
|
|
127
128
|
args = parser.parse_args()
|
|
128
129
|
|
|
129
130
|
all_violations: list[Violation] = []
|
|
@@ -141,7 +142,8 @@ def main() -> int:
|
|
|
141
142
|
print(json.dumps([asdict(v) for v in all_violations], indent=2, ensure_ascii=False))
|
|
142
143
|
else:
|
|
143
144
|
if not all_violations:
|
|
144
|
-
|
|
145
|
+
if not args.quiet:
|
|
146
|
+
print("✅ No German content detected.")
|
|
145
147
|
else:
|
|
146
148
|
print(f"❌ {len(all_violations)} violation(s) found:\n")
|
|
147
149
|
for v in all_violations:
|
|
@@ -152,6 +152,7 @@ def main() -> int:
|
|
|
152
152
|
grp.add_argument("--intake-id", help="Promote an intake record by id")
|
|
153
153
|
grp.add_argument("--proposal", help="Promote a proposal YAML file")
|
|
154
154
|
ap.add_argument("--format", choices=["text", "json"], default="text")
|
|
155
|
+
ap.add_argument("--quiet", action="store_true", help="Only print on failure")
|
|
155
156
|
args = ap.parse_args()
|
|
156
157
|
if args.intake_id:
|
|
157
158
|
record = _find_intake(args.intake_id)
|
|
@@ -172,7 +173,8 @@ def main() -> int:
|
|
|
172
173
|
for f in failures:
|
|
173
174
|
print(f" 🔴 {f}")
|
|
174
175
|
else:
|
|
175
|
-
|
|
176
|
+
if not args.quiet:
|
|
177
|
+
print(f"✅ {source} — gate passed")
|
|
176
178
|
return 1 if failures else 0
|
|
177
179
|
|
|
178
180
|
|
|
@@ -27,6 +27,8 @@ import re
|
|
|
27
27
|
import sys
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
|
|
30
|
+
QUIET = "--quiet" in sys.argv
|
|
31
|
+
|
|
30
32
|
ROOT = Path(__file__).resolve().parent.parent
|
|
31
33
|
CATALOG = ROOT / "docs" / "catalog.md"
|
|
32
34
|
PACKAGE_JSON = ROOT / "package.json"
|
|
@@ -95,7 +97,8 @@ def main() -> int:
|
|
|
95
97
|
|
|
96
98
|
total_violations = len(forbidden) + len(missing) + len(unshipped)
|
|
97
99
|
if not total_violations:
|
|
98
|
-
|
|
100
|
+
if not QUIET:
|
|
101
|
+
print(f"✅ docs/catalog.md — all links resolve to shipped surfaces.")
|
|
99
102
|
return 0
|
|
100
103
|
|
|
101
104
|
print(f"❌ docs/catalog.md — {total_violations} violation(s):")
|
|
@@ -21,6 +21,8 @@ import re
|
|
|
21
21
|
import sys
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
+
QUIET = "--quiet" in sys.argv
|
|
25
|
+
|
|
24
26
|
OPTION_LINE_RE = re.compile(r"^\s*>?\s*(\d+)\.\s+\S")
|
|
25
27
|
REC_LINE_RE = re.compile(
|
|
26
28
|
r"(?:Recommendation|Empfehlung)\s*:\s*(\d+)\b", re.IGNORECASE
|
|
@@ -108,7 +110,8 @@ def cmd_scan_dir(root: Path) -> int:
|
|
|
108
110
|
print(f" 🔴 {path}:{line} — inline-tag — {snippet}", file=sys.stderr)
|
|
109
111
|
print(f"\n❌ {len(violations)} legacy-pattern violation(s)", file=sys.stderr)
|
|
110
112
|
return 6
|
|
111
|
-
|
|
113
|
+
if not QUIET:
|
|
114
|
+
print(f"✅ No legacy (recommended) tags found under {root}")
|
|
112
115
|
return 0
|
|
113
116
|
|
|
114
117
|
|
|
@@ -121,6 +124,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
121
124
|
p.add_argument("--strict", action="store_true",
|
|
122
125
|
help="numbered options REQUIRE recommendation line (rule 5)")
|
|
123
126
|
p.add_argument("-v", "--verbose", action="store_true")
|
|
127
|
+
p.add_argument("--quiet", action="store_true",
|
|
128
|
+
help="suppress success messages (P10.5)")
|
|
124
129
|
args = p.parse_args(argv)
|
|
125
130
|
|
|
126
131
|
if args.scan_dir:
|
|
@@ -130,7 +135,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
130
135
|
code, msg = validate(text, strict=args.strict)
|
|
131
136
|
if code == 0:
|
|
132
137
|
if args.verbose:
|
|
133
|
-
|
|
138
|
+
if not QUIET:
|
|
139
|
+
print(f"✅ {msg}")
|
|
134
140
|
return 0
|
|
135
141
|
print(f"❌ [exit {code}] {msg}", file=sys.stderr)
|
|
136
142
|
return code
|
|
@@ -43,6 +43,8 @@ from update_roadmap_progress import ( # noqa: E402
|
|
|
43
43
|
parse_frontmatter,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
QUIET = "--quiet" in sys.argv
|
|
47
|
+
|
|
46
48
|
ROADMAP_ROOT = Path("agents/roadmaps")
|
|
47
49
|
|
|
48
50
|
|
|
@@ -103,7 +105,8 @@ def main() -> int:
|
|
|
103
105
|
)
|
|
104
106
|
return 1
|
|
105
107
|
count = len(find_active_roadmaps(ROADMAP_ROOT))
|
|
106
|
-
|
|
108
|
+
if not QUIET:
|
|
109
|
+
print(f"✅ {count} active roadmap(s) — all parseable, all phases have checkboxes.")
|
|
107
110
|
return 0
|
|
108
111
|
|
|
109
112
|
|
|
@@ -17,8 +17,18 @@ from pathlib import Path
|
|
|
17
17
|
ROOT = Path(__file__).resolve().parent.parent
|
|
18
18
|
RULES_DIR = ROOT / ".agent-src.uncompressed" / "rules"
|
|
19
19
|
OUT_PATH = ROOT / "router.json"
|
|
20
|
+
SETTINGS_PATH = ROOT / ".agent-settings.yml"
|
|
20
21
|
SCHEMA_VERSION = 1
|
|
21
22
|
|
|
23
|
+
# Compile-time rule toggles. Maps rule-id → settings predicate.
|
|
24
|
+
# Rule omitted from router.json when predicate returns False.
|
|
25
|
+
# Per road-to-token-frugality § Phase 8.2 — caveman.speak compile-time toggle.
|
|
26
|
+
COMPILE_TIME_TOGGLES = {
|
|
27
|
+
"caveman-speak": lambda s: bool(
|
|
28
|
+
s.get("caveman", {}).get("enabled", True)
|
|
29
|
+
) and bool(s.get("caveman", {}).get("speak", True)),
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
# Maps legacy tier values to the router-canonical names. See
|
|
23
33
|
# docs/contracts/rule-router.md § Backward compatibility.
|
|
24
34
|
LEGACY_TIER_MAP = {
|
|
@@ -89,7 +99,21 @@ def _normalize_trigger(item) -> dict | None:
|
|
|
89
99
|
return {keys[0]: str(item[keys[0]])}
|
|
90
100
|
|
|
91
101
|
|
|
102
|
+
def _load_settings() -> dict:
|
|
103
|
+
"""Read .agent-settings.yml for compile-time toggles. Stdlib-only fallback."""
|
|
104
|
+
if not SETTINGS_PATH.exists():
|
|
105
|
+
return {}
|
|
106
|
+
text = SETTINGS_PATH.read_text(encoding="utf-8")
|
|
107
|
+
try:
|
|
108
|
+
import yaml # type: ignore
|
|
109
|
+
data = yaml.safe_load(text) or {}
|
|
110
|
+
return data if isinstance(data, dict) else {}
|
|
111
|
+
except ImportError:
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
|
|
92
115
|
def _collect(rules_dir: Path) -> dict:
|
|
116
|
+
settings = _load_settings()
|
|
93
117
|
kernel: list[str] = []
|
|
94
118
|
tiered: dict[str, list[dict]] = {"tier-1": [], "tier-2": []}
|
|
95
119
|
for path in sorted(rules_dir.glob("*.md")):
|
|
@@ -97,6 +121,9 @@ def _collect(rules_dir: Path) -> dict:
|
|
|
97
121
|
if not fm:
|
|
98
122
|
continue
|
|
99
123
|
rule_id = path.stem
|
|
124
|
+
if rule_id in COMPILE_TIME_TOGGLES:
|
|
125
|
+
if not COMPILE_TIME_TOGGLES[rule_id](settings):
|
|
126
|
+
continue
|
|
100
127
|
rule_type = str(fm.get("type", "auto"))
|
|
101
128
|
tier = _resolve_tier(rule_type, fm.get("tier", ""))
|
|
102
129
|
if tier not in ALLOWED_TIERS:
|
package/scripts/compress.py
CHANGED
|
@@ -26,6 +26,9 @@ import shutil
|
|
|
26
26
|
import sys
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
30
|
+
from _lib.script_output import info, success, flush_summary, resolve_level # noqa: E402
|
|
31
|
+
|
|
29
32
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
30
33
|
SOURCE_DIR = PROJECT_ROOT / ".agent-src.uncompressed"
|
|
31
34
|
TARGET_DIR = PROJECT_ROOT / ".agent-src"
|
|
@@ -479,11 +482,11 @@ def generate_rule_symlinks() -> int:
|
|
|
479
482
|
if tool_count != source_count:
|
|
480
483
|
print(f" ⚠️ {tool_dir}: {tool_count} rules (expected {source_count})")
|
|
481
484
|
|
|
482
|
-
|
|
485
|
+
info(f" ✅ Created {total} rule symlinks across {len(TOOL_DIRS)} tool directories ({source_count} rules each)")
|
|
483
486
|
return total
|
|
484
487
|
|
|
485
488
|
|
|
486
|
-
def generate_windsurfrules() ->
|
|
489
|
+
def generate_windsurfrules() -> int:
|
|
487
490
|
"""Generate .windsurfrules by concatenating all rules (no frontmatter).
|
|
488
491
|
"""
|
|
489
492
|
rules = sorted([f.name for f in RULES_SOURCE.glob("*.md")])
|
|
@@ -496,7 +499,8 @@ def generate_windsurfrules() -> None:
|
|
|
496
499
|
|
|
497
500
|
output = PROJECT_ROOT / ".windsurfrules"
|
|
498
501
|
output.write_text("\n".join(parts) + "\n")
|
|
499
|
-
|
|
502
|
+
info(f" ✅ Generated .windsurfrules ({len(rules)} rules)")
|
|
503
|
+
return len(rules)
|
|
500
504
|
|
|
501
505
|
|
|
502
506
|
def generate_gemini_md() -> None:
|
|
@@ -505,7 +509,7 @@ def generate_gemini_md() -> None:
|
|
|
505
509
|
if link.exists() or link.is_symlink():
|
|
506
510
|
link.unlink()
|
|
507
511
|
link.symlink_to("AGENTS.md")
|
|
508
|
-
|
|
512
|
+
info(" ✅ Created GEMINI.md → AGENTS.md symlink")
|
|
509
513
|
|
|
510
514
|
|
|
511
515
|
def _command_slug(source_file: Path) -> str:
|
|
@@ -531,12 +535,12 @@ def _iter_commands():
|
|
|
531
535
|
yield source_file, _command_slug(source_file)
|
|
532
536
|
|
|
533
537
|
|
|
534
|
-
def generate_claude_skills() ->
|
|
538
|
+
def generate_claude_skills() -> int:
|
|
535
539
|
"""Create .claude/skills/ symlinks for ALL skills in .agent-src/skills/.
|
|
536
540
|
"""
|
|
537
541
|
if not SKILLS_SOURCE.exists():
|
|
538
|
-
print(" ⚠️ .agent-src/skills/ not found — skipping skills")
|
|
539
|
-
return
|
|
542
|
+
print(" ⚠️ .agent-src/skills/ not found — skipping skills", file=sys.stderr)
|
|
543
|
+
return 0
|
|
540
544
|
|
|
541
545
|
# All skill directories in .agent-src/skills/
|
|
542
546
|
skills = sorted([d.name for d in SKILLS_SOURCE.iterdir() if d.is_dir()])
|
|
@@ -559,7 +563,8 @@ def generate_claude_skills() -> None:
|
|
|
559
563
|
link.symlink_to(rel_target)
|
|
560
564
|
count += 1
|
|
561
565
|
|
|
562
|
-
|
|
566
|
+
info(f" ✅ Created {count} skill symlinks in .claude/skills/")
|
|
567
|
+
return count
|
|
563
568
|
|
|
564
569
|
|
|
565
570
|
def extract_description_from_md(content: str) -> str:
|
|
@@ -573,7 +578,7 @@ def extract_description_from_md(content: str) -> str:
|
|
|
573
578
|
return ""
|
|
574
579
|
|
|
575
580
|
|
|
576
|
-
def generate_claude_commands() ->
|
|
581
|
+
def generate_claude_commands() -> int:
|
|
577
582
|
"""Create .claude/skills/{slug}/SKILL.md symlinks for ALL Augment commands.
|
|
578
583
|
|
|
579
584
|
Commands in .agent-src/commands/ are the single source of truth.
|
|
@@ -585,8 +590,8 @@ def generate_claude_commands() -> None:
|
|
|
585
590
|
to `council-default` so directories never collide in `.claude/skills/`.
|
|
586
591
|
"""
|
|
587
592
|
if not COMMANDS_SOURCE.exists():
|
|
588
|
-
print(" ⚠️ .agent-src/commands/ not found — skipping commands")
|
|
589
|
-
return
|
|
593
|
+
print(" ⚠️ .agent-src/commands/ not found — skipping commands", file=sys.stderr)
|
|
594
|
+
return 0
|
|
590
595
|
|
|
591
596
|
CLAUDE_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
|
|
592
597
|
|
|
@@ -642,7 +647,8 @@ def generate_claude_commands() -> None:
|
|
|
642
647
|
msg += f" ({skipped} skipped — same-name skill exists)"
|
|
643
648
|
if removed_dirs:
|
|
644
649
|
msg += f" ({removed_dirs} stale dirs removed)"
|
|
645
|
-
|
|
650
|
+
info(msg)
|
|
651
|
+
return count
|
|
646
652
|
|
|
647
653
|
|
|
648
654
|
def generate_persona_symlinks() -> int:
|
|
@@ -676,20 +682,28 @@ def generate_persona_symlinks() -> int:
|
|
|
676
682
|
link.symlink_to(target)
|
|
677
683
|
total += 1
|
|
678
684
|
|
|
679
|
-
|
|
685
|
+
info(f" ✅ Created {total} persona symlinks across {len(PERSONA_TOOL_DIRS)} tool directories ({len(personas)} personas each)")
|
|
680
686
|
return total
|
|
681
687
|
|
|
682
688
|
|
|
683
689
|
def generate_tools() -> None:
|
|
684
690
|
"""Generate all tool-specific directories and files."""
|
|
685
|
-
|
|
686
|
-
generate_rule_symlinks()
|
|
691
|
+
info("🔧 Generating multi-agent tool directories...\n")
|
|
692
|
+
rules = generate_rule_symlinks()
|
|
687
693
|
generate_windsurfrules()
|
|
688
694
|
generate_gemini_md()
|
|
689
|
-
generate_claude_skills()
|
|
690
|
-
generate_claude_commands()
|
|
691
|
-
generate_persona_symlinks()
|
|
692
|
-
|
|
695
|
+
skills = generate_claude_skills()
|
|
696
|
+
commands = generate_claude_commands()
|
|
697
|
+
personas = generate_persona_symlinks()
|
|
698
|
+
summary = (
|
|
699
|
+
f"✅ generate-tools — rules={rules} skills={skills} "
|
|
700
|
+
f"commands={commands} personas={personas}"
|
|
701
|
+
)
|
|
702
|
+
if resolve_level() == "verbose":
|
|
703
|
+
print(f"\n{summary}")
|
|
704
|
+
else:
|
|
705
|
+
success(summary)
|
|
706
|
+
flush_summary()
|
|
693
707
|
|
|
694
708
|
|
|
695
709
|
# ── .augment/ projection ──────────────────────────────────────────────
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cost-budget — set / get / check the project's cost budget against
|
|
3
|
+
// accumulated session spend in agents/cost-tracking/sessions.jsonl.
|
|
4
|
+
//
|
|
5
|
+
// Forked from ruvnet/ruflo plugins/ruflo-cost-tracker/scripts/budget.mjs.
|
|
6
|
+
// Local-JSONL swap replaces the upstream `mcp__claude-flow__memory_store`
|
|
7
|
+
// dependency. Budget config lives next to the sessions store as budget.json.
|
|
8
|
+
//
|
|
9
|
+
// Usage: node scripts/cost/budget.mjs {set <usd>|get|check}
|
|
10
|
+
// Env: BUDGET_STORE, BUDGET_CONFIG, BUDGET_PERIOD={today|week|month|all}, BUDGET_QUIET=1
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { dirname } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const STORE = process.env.BUDGET_STORE || 'agents/cost-tracking/sessions.jsonl';
|
|
16
|
+
const CONFIG = process.env.BUDGET_CONFIG || 'agents/cost-tracking/budget.json';
|
|
17
|
+
|
|
18
|
+
function loadConfig() {
|
|
19
|
+
if (!existsSync(CONFIG)) return null;
|
|
20
|
+
try { return JSON.parse(readFileSync(CONFIG, 'utf-8')); } catch { return null; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function saveConfig(cfg) {
|
|
24
|
+
mkdirSync(dirname(CONFIG), { recursive: true });
|
|
25
|
+
writeFileSync(CONFIG, JSON.stringify(cfg, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadSessions() {
|
|
29
|
+
if (!existsSync(STORE)) return [];
|
|
30
|
+
const out = [];
|
|
31
|
+
for (const line of readFileSync(STORE, 'utf-8').split('\n')) {
|
|
32
|
+
if (!line.trim()) continue;
|
|
33
|
+
try { out.push(JSON.parse(line)); } catch { /* skip malformed line */ }
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function periodFilter(period) {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const day = 24 * 3600 * 1000;
|
|
41
|
+
if (period === 'today') return (ts) => ts && new Date(ts).toDateString() === new Date().toDateString();
|
|
42
|
+
if (period === 'week') return (ts) => ts && (now - new Date(ts).getTime()) < 7 * day;
|
|
43
|
+
if (period === 'month') return (ts) => ts && (now - new Date(ts).getTime()) < 30 * day;
|
|
44
|
+
return () => true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function alertLevel(u) {
|
|
48
|
+
if (u >= 1.00) return { level: 'HARD_STOP', emoji: '🛑', threshold: 100 };
|
|
49
|
+
if (u >= 0.90) return { level: 'CRITICAL', emoji: '🔴', threshold: 90 };
|
|
50
|
+
if (u >= 0.75) return { level: 'WARNING', emoji: '🟠', threshold: 75 };
|
|
51
|
+
if (u >= 0.50) return { level: 'INFO', emoji: '🟡', threshold: 50 };
|
|
52
|
+
return { level: 'OK', emoji: '🟢', threshold: 0 };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function recommendedAction(level) {
|
|
56
|
+
return ({
|
|
57
|
+
OK: 'within budget — no action.',
|
|
58
|
+
INFO: '50% consumed — log notification, no UX disruption.',
|
|
59
|
+
WARNING: '75% consumed — suggest /set-cost-profile balanced→minimal.',
|
|
60
|
+
CRITICAL: '90% consumed — recommend model downgrades, consider /set-cost-profile minimal.',
|
|
61
|
+
HARD_STOP: '100% consumed — halt non-essential work; review /cost:report before continuing.',
|
|
62
|
+
}[level]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function cmdSet(args) {
|
|
66
|
+
const amount = parseFloat(args[0]);
|
|
67
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
68
|
+
console.error('usage: budget.mjs set <usd-amount> (positive number)');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
}
|
|
71
|
+
const config = {
|
|
72
|
+
budget_usd: amount,
|
|
73
|
+
setAt: new Date().toISOString(),
|
|
74
|
+
thresholds: { info: 0.50, warning: 0.75, critical: 0.90, hard_stop: 1.00 },
|
|
75
|
+
};
|
|
76
|
+
saveConfig(config);
|
|
77
|
+
if (process.env.BUDGET_QUIET === '1') {
|
|
78
|
+
console.log(JSON.stringify(config));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(`✓ Budget set: $${amount.toFixed(2)} (config: ${CONFIG})`);
|
|
81
|
+
console.log(' Alerts: 50% INFO · 75% WARNING · 90% CRITICAL · 100% HARD_STOP');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cmdGet() {
|
|
86
|
+
const cfg = loadConfig();
|
|
87
|
+
if (process.env.BUDGET_QUIET === '1') {
|
|
88
|
+
console.log(JSON.stringify(cfg || { error: 'no budget configured' }));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!cfg) {
|
|
92
|
+
console.log(`No budget configured (config: ${CONFIG}).`);
|
|
93
|
+
console.log('Set one with: node scripts/cost/budget.mjs set <usd>');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log(`Budget: $${cfg.budget_usd?.toFixed(2)} (set ${cfg.setAt})`);
|
|
97
|
+
console.log('Thresholds: 50/75/90/100%');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function cmdCheck() {
|
|
101
|
+
const cfg = loadConfig();
|
|
102
|
+
const period = process.env.BUDGET_PERIOD || 'all';
|
|
103
|
+
const filt = periodFilter(period);
|
|
104
|
+
const filtered = loadSessions().filter((r) => filt(r.capturedAt || r.endedAt));
|
|
105
|
+
const totalSpend = filtered.reduce((s, r) => s + (r.total_cost_usd || 0), 0);
|
|
106
|
+
if (!cfg || !Number.isFinite(cfg.budget_usd)) {
|
|
107
|
+
const out = { period, totalSpend, recordCount: filtered.length, error: 'no budget configured' };
|
|
108
|
+
if (process.env.BUDGET_QUIET === '1') return console.log(JSON.stringify(out));
|
|
109
|
+
console.log(`Period: ${period}`);
|
|
110
|
+
console.log(`Spent so far: $${totalSpend.toFixed(2)} across ${filtered.length} sessions`);
|
|
111
|
+
console.log('No budget set — run `node scripts/cost/budget.mjs set <usd>` to enable alerts.');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const utilization = totalSpend / cfg.budget_usd;
|
|
115
|
+
const alert = alertLevel(utilization);
|
|
116
|
+
const out = {
|
|
117
|
+
period,
|
|
118
|
+
budget_usd: cfg.budget_usd,
|
|
119
|
+
spent_usd: totalSpend,
|
|
120
|
+
remaining_usd: Math.max(0, cfg.budget_usd - totalSpend),
|
|
121
|
+
utilization_pct: utilization * 100,
|
|
122
|
+
level: alert.level,
|
|
123
|
+
threshold: alert.threshold,
|
|
124
|
+
recommended_action: recommendedAction(alert.level),
|
|
125
|
+
sessionCount: filtered.length,
|
|
126
|
+
};
|
|
127
|
+
if (process.env.BUDGET_QUIET === '1') return console.log(JSON.stringify(out));
|
|
128
|
+
console.log(`# Budget check (period: ${period})\n`);
|
|
129
|
+
console.log('| Metric | Value |\n|---|---:|');
|
|
130
|
+
console.log(`| Budget | $${cfg.budget_usd.toFixed(2)} |`);
|
|
131
|
+
console.log(`| Spent | $${totalSpend.toFixed(2)} |`);
|
|
132
|
+
console.log(`| Remaining | $${out.remaining_usd.toFixed(2)} |`);
|
|
133
|
+
console.log(`| Utilization | ${out.utilization_pct.toFixed(1)}% |`);
|
|
134
|
+
console.log(`| Sessions counted | ${filtered.length} |`);
|
|
135
|
+
console.log(`| **Alert** | **${alert.emoji} ${alert.level}** |`);
|
|
136
|
+
console.log(`\nAction: ${out.recommended_action}`);
|
|
137
|
+
if (alert.level === 'HARD_STOP') process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function main() {
|
|
141
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
142
|
+
switch (cmd) {
|
|
143
|
+
case 'set': return cmdSet(rest);
|
|
144
|
+
case 'get': return cmdGet();
|
|
145
|
+
case 'check': return cmdCheck();
|
|
146
|
+
default:
|
|
147
|
+
console.error('usage: budget.mjs {set <usd>|get|check}');
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
main();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cost-track — auto-capture token usage from a Claude Code session jsonl
|
|
3
|
+
// and append a structured record to agents/cost-tracking/sessions.jsonl.
|
|
4
|
+
//
|
|
5
|
+
// Forked from ruvnet/ruflo plugins/ruflo-cost-tracker/scripts/track.mjs.
|
|
6
|
+
// Local-JSONL swap replaces the upstream `mcp__claude-flow__memory_store`
|
|
7
|
+
// dependency. Pricing constants are kept in sync with REFERENCE.md.
|
|
8
|
+
//
|
|
9
|
+
// Env:
|
|
10
|
+
// TRACK_CWD=<path> override which project's sessions to scan
|
|
11
|
+
// TRACK_SESSION=<file> pin to a specific session jsonl
|
|
12
|
+
// TRACK_OUT=<path> also write the JSON summary to this path
|
|
13
|
+
// TRACK_DRY_RUN=1 skip the JSONL append
|
|
14
|
+
// TRACK_QUIET=1 suppress markdown summary
|
|
15
|
+
// TRACK_STORE=<path> override (default: agents/cost-tracking/sessions.jsonl)
|
|
16
|
+
|
|
17
|
+
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
|
|
21
|
+
const PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
22
|
+
const DEFAULT_STORE = 'agents/cost-tracking/sessions.jsonl';
|
|
23
|
+
|
|
24
|
+
// USD per 1M tokens.
|
|
25
|
+
const PRICING = {
|
|
26
|
+
haiku: { input: 0.25, output: 1.25, cache_write: 0.30, cache_read: 0.03 },
|
|
27
|
+
sonnet: { input: 3.00, output: 15.00, cache_write: 3.75, cache_read: 0.30 },
|
|
28
|
+
opus: { input: 15.00, output: 75.00, cache_write: 18.75, cache_read: 1.50 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function modelTier(model) {
|
|
32
|
+
if (!model) return 'unknown';
|
|
33
|
+
const m = String(model).toLowerCase();
|
|
34
|
+
if (m.includes('haiku')) return 'haiku';
|
|
35
|
+
if (m.includes('sonnet')) return 'sonnet';
|
|
36
|
+
if (m.includes('opus')) return 'opus';
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function costForUsage(tier, u) {
|
|
41
|
+
const p = PRICING[tier];
|
|
42
|
+
if (!p || !u) return 0;
|
|
43
|
+
return (u.input_tokens || 0) / 1e6 * p.input
|
|
44
|
+
+ (u.output_tokens || 0) / 1e6 * p.output
|
|
45
|
+
+ (u.cache_creation_input_tokens || 0) / 1e6 * p.cache_write
|
|
46
|
+
+ (u.cache_read_input_tokens || 0) / 1e6 * p.cache_read;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function encodeProjectPath(cwd) { return cwd.replace(/\//g, '-'); }
|
|
50
|
+
|
|
51
|
+
function findProjectDir(cwd) {
|
|
52
|
+
const c = join(PROJECTS_DIR, encodeProjectPath(cwd));
|
|
53
|
+
return existsSync(c) ? c : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findActiveSession(dir) {
|
|
57
|
+
const e = readdirSync(dir).filter((f) => f.endsWith('.jsonl'))
|
|
58
|
+
.map((f) => ({ f, mtime: statSync(join(dir, f)).mtimeMs }))
|
|
59
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
60
|
+
return e[0] ? join(dir, e[0].f) : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function summarizeSession(jsonlPath) {
|
|
64
|
+
const lines = readFileSync(jsonlPath, 'utf-8').split('\n').filter(Boolean);
|
|
65
|
+
const byModel = {};
|
|
66
|
+
const byTier = { haiku: 0, sonnet: 0, opus: 0, unknown: 0 };
|
|
67
|
+
let messageCount = 0, totalCost = 0, firstTs = null, lastTs = null;
|
|
68
|
+
let sessionId = null, cwd = null;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
let m; try { m = JSON.parse(line); } catch { continue; }
|
|
71
|
+
if (!sessionId && m.sessionId) sessionId = m.sessionId;
|
|
72
|
+
if (!cwd && m.cwd) cwd = m.cwd;
|
|
73
|
+
if (m.timestamp) {
|
|
74
|
+
if (!firstTs || m.timestamp < firstTs) firstTs = m.timestamp;
|
|
75
|
+
if (!lastTs || m.timestamp > lastTs) lastTs = m.timestamp;
|
|
76
|
+
}
|
|
77
|
+
if (m.type !== 'assistant' || !m.message?.usage) continue;
|
|
78
|
+
messageCount++;
|
|
79
|
+
const model = m.message.model || 'unknown';
|
|
80
|
+
const tier = modelTier(model);
|
|
81
|
+
const u = m.message.usage;
|
|
82
|
+
const cost = costForUsage(tier, u);
|
|
83
|
+
const slot = byModel[model] || { tier, input_tokens: 0, output_tokens: 0,
|
|
84
|
+
cache_creation_input_tokens: 0, cache_read_input_tokens: 0, messages: 0, cost_usd: 0 };
|
|
85
|
+
slot.input_tokens += u.input_tokens || 0;
|
|
86
|
+
slot.output_tokens += u.output_tokens || 0;
|
|
87
|
+
slot.cache_creation_input_tokens += u.cache_creation_input_tokens || 0;
|
|
88
|
+
slot.cache_read_input_tokens += u.cache_read_input_tokens || 0;
|
|
89
|
+
slot.messages++; slot.cost_usd += cost;
|
|
90
|
+
byModel[model] = slot; byTier[tier] += cost; totalCost += cost;
|
|
91
|
+
}
|
|
92
|
+
return { sessionId, cwd, startedAt: firstTs, endedAt: lastTs, messageCount,
|
|
93
|
+
byModel, byTier, total_cost_usd: totalCost, capturedAt: new Date().toISOString() };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function persistJsonl(summary, store) {
|
|
97
|
+
mkdirSync(dirname(store), { recursive: true });
|
|
98
|
+
appendFileSync(store, JSON.stringify(summary) + '\n');
|
|
99
|
+
return { ok: true, path: store };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function main() {
|
|
103
|
+
const targetCwd = process.env.TRACK_CWD || process.cwd();
|
|
104
|
+
const projectDir = findProjectDir(targetCwd);
|
|
105
|
+
if (!projectDir) {
|
|
106
|
+
console.error(`cost-track: no Claude Code project dir for cwd=${targetCwd}`);
|
|
107
|
+
console.error(`looked under ${PROJECTS_DIR}/${encodeProjectPath(targetCwd)}`);
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
const sessionPath = process.env.TRACK_SESSION || findActiveSession(projectDir);
|
|
111
|
+
if (!sessionPath || !existsSync(sessionPath)) {
|
|
112
|
+
console.error(`cost-track: no session jsonl in ${projectDir}`); process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
const summary = summarizeSession(sessionPath);
|
|
115
|
+
if (process.env.TRACK_OUT) writeFileSync(process.env.TRACK_OUT, JSON.stringify(summary, null, 2));
|
|
116
|
+
const store = process.env.TRACK_STORE || DEFAULT_STORE;
|
|
117
|
+
let res = { ok: false, reason: 'dry-run' };
|
|
118
|
+
if (process.env.TRACK_DRY_RUN !== '1') res = persistJsonl(summary, store);
|
|
119
|
+
if (process.env.TRACK_QUIET === '1') return;
|
|
120
|
+
|
|
121
|
+
console.log(`# cost-track — session ${(summary.sessionId || '').slice(0, 8) || 'unknown'}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
console.log('| Metric | Value |\n|---|---:|');
|
|
124
|
+
console.log(`| Session ID | \`${summary.sessionId}\` |`);
|
|
125
|
+
console.log(`| Project | \`${summary.cwd}\` |`);
|
|
126
|
+
console.log(`| First message | ${summary.startedAt} |`);
|
|
127
|
+
console.log(`| Last message | ${summary.endedAt} |`);
|
|
128
|
+
console.log(`| Assistant messages | ${summary.messageCount} |`);
|
|
129
|
+
console.log(`| **Total cost** | **$${summary.total_cost_usd.toFixed(6)}** |`);
|
|
130
|
+
console.log(`| Persisted | ${res.ok ? `\`${res.path}\`` : `**FAILED** (${res.reason})`} |`);
|
|
131
|
+
console.log('\n## Per-model breakdown\n');
|
|
132
|
+
console.log('| Model | Tier | Messages | Input | Output | Cache write | Cache read | Cost |');
|
|
133
|
+
console.log('|---|---|---:|---:|---:|---:|---:|---:|');
|
|
134
|
+
for (const [m, s] of Object.entries(summary.byModel).sort((a, b) => b[1].cost_usd - a[1].cost_usd)) {
|
|
135
|
+
console.log(`| \`${m}\` | ${s.tier} | ${s.messages} | ${s.input_tokens} | ${s.output_tokens} | ${s.cache_creation_input_tokens} | ${s.cache_read_input_tokens} | $${s.cost_usd.toFixed(6)} |`);
|
|
136
|
+
}
|
|
137
|
+
console.log('\n## Per-tier breakdown\n');
|
|
138
|
+
console.log('| Tier | Cost |\n|---|---:|');
|
|
139
|
+
for (const [t, c] of Object.entries(summary.byTier).sort((a, b) => b[1] - a[1])) {
|
|
140
|
+
if (c > 0) console.log(`| ${t} | $${c.toFixed(6)} |`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main();
|
package/scripts/first-run.sh
CHANGED
|
@@ -5,9 +5,7 @@ SETTINGS_FILE=".agent-settings.yml"
|
|
|
5
5
|
LEGACY_SETTINGS_FILE=".agent-settings"
|
|
6
6
|
|
|
7
7
|
echo ""
|
|
8
|
-
echo "
|
|
9
|
-
echo " Agent Config — First Run"
|
|
10
|
-
echo "========================================"
|
|
8
|
+
echo "Agent Config — First Run"
|
|
11
9
|
echo ""
|
|
12
10
|
|
|
13
11
|
# --- Profile detection ---
|
|
@@ -63,9 +61,7 @@ echo " ✅ Zero token overhead in minimal mode"
|
|
|
63
61
|
echo ""
|
|
64
62
|
|
|
65
63
|
# --- 3 test prompts ---
|
|
66
|
-
echo "
|
|
67
|
-
echo " Try these 3 prompts now"
|
|
68
|
-
echo "========================================"
|
|
64
|
+
echo "Try these 3 prompts now:"
|
|
69
65
|
echo ""
|
|
70
66
|
|
|
71
67
|
echo "1️⃣ Refactoring check"
|
|
@@ -94,9 +90,7 @@ echo " → Agent challenges weak requirements"
|
|
|
94
90
|
echo ""
|
|
95
91
|
|
|
96
92
|
# --- Next steps ---
|
|
97
|
-
echo "
|
|
98
|
-
echo " Next steps"
|
|
99
|
-
echo "========================================"
|
|
93
|
+
echo "Next steps:"
|
|
100
94
|
echo ""
|
|
101
95
|
echo "Cost profiles:"
|
|
102
96
|
echo " minimal rules, skills, commands only"
|