@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Verbosity-aware print router for scripts/*.py.
|
|
2
|
+
|
|
3
|
+
Phase 10 of road-to-token-frugality. Single source of truth for how
|
|
4
|
+
maintenance scripts emit progress, success, warnings, and errors.
|
|
5
|
+
|
|
6
|
+
Resolution order (first wins):
|
|
7
|
+
1. AGENT_SCRIPT_VERBOSITY env var (silent | minimal | verbose)
|
|
8
|
+
2. SCRIPT_OUTPUT_VERBOSE=1 alias (== verbose)
|
|
9
|
+
3. .agent-settings.yml verbosity.script_output
|
|
10
|
+
4. Default: minimal
|
|
11
|
+
|
|
12
|
+
Once resolved, the level is exported back into AGENT_SCRIPT_VERBOSITY
|
|
13
|
+
so child processes inherit the same level (Phase 10.1c). Explicit
|
|
14
|
+
--quiet flags on the child still win at the call site.
|
|
15
|
+
|
|
16
|
+
Levels:
|
|
17
|
+
silent = stderr only; success() drops; info() drops; warn() drops
|
|
18
|
+
minimal = success() collapsed to one end-of-run summary; info() drops
|
|
19
|
+
verbose = pre-Phase-10 behaviour, every call prints
|
|
20
|
+
|
|
21
|
+
error() always writes to stderr regardless of level. Iron-Law surfaces
|
|
22
|
+
(release confirms, install secrets prompts) bypass this module and use
|
|
23
|
+
plain print() so they cannot be silenced.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Final
|
|
31
|
+
|
|
32
|
+
VALID_LEVELS: Final[tuple[str, ...]] = ("silent", "minimal", "verbose")
|
|
33
|
+
DEFAULT_LEVEL: Final[str] = "minimal"
|
|
34
|
+
ENV_VAR: Final[str] = "AGENT_SCRIPT_VERBOSITY"
|
|
35
|
+
ENV_ALIAS: Final[str] = "SCRIPT_OUTPUT_VERBOSE"
|
|
36
|
+
SETTINGS_FILE: Final[str] = ".agent-settings.yml"
|
|
37
|
+
|
|
38
|
+
_resolved_level: str | None = None
|
|
39
|
+
_pending_summary: list[str] = []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _read_settings_level(settings_path: Path) -> str | None:
|
|
43
|
+
"""Read verbosity.script_output from .agent-settings.yml.
|
|
44
|
+
|
|
45
|
+
Returns None when the file is missing, PyYAML is unavailable, or
|
|
46
|
+
the key is absent. Errors fall through to the default level.
|
|
47
|
+
"""
|
|
48
|
+
if not settings_path.is_file():
|
|
49
|
+
return None
|
|
50
|
+
try:
|
|
51
|
+
import yaml # type: ignore[import-untyped]
|
|
52
|
+
except ImportError:
|
|
53
|
+
return None
|
|
54
|
+
try:
|
|
55
|
+
with settings_path.open(encoding="utf-8") as fh:
|
|
56
|
+
data = yaml.safe_load(fh) or {}
|
|
57
|
+
except (OSError, yaml.YAMLError):
|
|
58
|
+
return None
|
|
59
|
+
section = data.get("verbosity") if isinstance(data, dict) else None
|
|
60
|
+
if not isinstance(section, dict):
|
|
61
|
+
return None
|
|
62
|
+
value = section.get("script_output")
|
|
63
|
+
if isinstance(value, str) and value in VALID_LEVELS:
|
|
64
|
+
return value
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def resolve_level(settings_path: Path | None = None) -> str:
|
|
69
|
+
"""Resolve and cache the active verbosity level.
|
|
70
|
+
|
|
71
|
+
First call wins; subsequent calls return the cached value so the
|
|
72
|
+
process is internally consistent. Tests reset via reset_level().
|
|
73
|
+
"""
|
|
74
|
+
global _resolved_level
|
|
75
|
+
if _resolved_level is not None:
|
|
76
|
+
return _resolved_level
|
|
77
|
+
|
|
78
|
+
env_value = os.environ.get(ENV_VAR, "").strip().lower()
|
|
79
|
+
if env_value in VALID_LEVELS:
|
|
80
|
+
_resolved_level = env_value
|
|
81
|
+
elif os.environ.get(ENV_ALIAS, "").strip() == "1":
|
|
82
|
+
_resolved_level = "verbose"
|
|
83
|
+
else:
|
|
84
|
+
path = settings_path or Path(SETTINGS_FILE)
|
|
85
|
+
_resolved_level = _read_settings_level(path) or DEFAULT_LEVEL
|
|
86
|
+
|
|
87
|
+
# Inheritance: export resolved level so child processes see it.
|
|
88
|
+
os.environ[ENV_VAR] = _resolved_level
|
|
89
|
+
return _resolved_level
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def reset_level() -> None:
|
|
93
|
+
"""Clear the cached level. Test helper."""
|
|
94
|
+
global _resolved_level
|
|
95
|
+
_resolved_level = None
|
|
96
|
+
_pending_summary.clear()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def info(message: str) -> None:
|
|
100
|
+
"""Per-step progress note. Drops at silent + minimal."""
|
|
101
|
+
if resolve_level() == "verbose":
|
|
102
|
+
print(message)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def success(message: str) -> None:
|
|
106
|
+
"""Per-step success. At minimal collected for end-of-run summary;
|
|
107
|
+
at verbose printed immediately; at silent dropped."""
|
|
108
|
+
level = resolve_level()
|
|
109
|
+
if level == "verbose":
|
|
110
|
+
print(message)
|
|
111
|
+
elif level == "minimal":
|
|
112
|
+
_pending_summary.append(message)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def warn(message: str) -> None:
|
|
116
|
+
"""Warning. Stderr at all levels except silent."""
|
|
117
|
+
if resolve_level() != "silent":
|
|
118
|
+
print(message, file=sys.stderr)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def error(message: str) -> None:
|
|
122
|
+
"""Error. Always stderr regardless of level."""
|
|
123
|
+
print(message, file=sys.stderr)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def flush_summary(headline: str | None = None) -> None:
|
|
127
|
+
"""Emit the pending success() summary at end-of-run.
|
|
128
|
+
|
|
129
|
+
No-op at verbose (already printed) and silent (suppressed).
|
|
130
|
+
Use the explicit `headline` arg to override the auto-pick.
|
|
131
|
+
"""
|
|
132
|
+
level = resolve_level()
|
|
133
|
+
if level != "minimal" or not _pending_summary:
|
|
134
|
+
return
|
|
135
|
+
if headline:
|
|
136
|
+
print(headline)
|
|
137
|
+
else:
|
|
138
|
+
# Default: print the last collected line as the headline.
|
|
139
|
+
print(_pending_summary[-1])
|
|
140
|
+
_pending_summary.clear()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regenerate INDEX.md for an ADR directory. Parses ADR-*.md frontmatter
|
|
3
|
+
(adr/status/date/decision/supersedes), writes INDEX.md, splits legacy
|
|
4
|
+
non-numbered ADRs into an Unnumbered table, hard-fails on duplicate
|
|
5
|
+
numbers, filename/frontmatter mismatch, or broken supersedes links."""
|
|
6
|
+
import argparse, re, sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
NAMED = re.compile(r"^ADR-(\d{3})-([a-z0-9-]+)\.md$")
|
|
10
|
+
FM = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
|
|
11
|
+
FIELD = re.compile(r"^([a-z_]+):\s*(.+?)\s*$", re.MULTILINE)
|
|
12
|
+
HEAD = "| # | Title | Status | Date | Supersedes |\n|---|---|---|---|---|"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def fm(t):
|
|
16
|
+
m = FM.search(t)
|
|
17
|
+
return {k: v.strip(" \"'") for k, v in FIELD.findall(m.group(1))} if m else {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def scan(d):
|
|
21
|
+
num, leg, errs, seen = [], [], [], {}
|
|
22
|
+
for p in sorted(d.glob("ADR-*.md")):
|
|
23
|
+
if p.name == "INDEX.md": continue
|
|
24
|
+
meta, m = fm(p.read_text(encoding="utf-8")), NAMED.match(p.name)
|
|
25
|
+
if not m:
|
|
26
|
+
leg.append({"path": p.name, **meta}); continue
|
|
27
|
+
n = m.group(1)
|
|
28
|
+
if meta.get("adr") and meta["adr"].lstrip("0") != n.lstrip("0"):
|
|
29
|
+
errs.append(f"{p.name}: adr={meta['adr']} != filename {n}")
|
|
30
|
+
if n in seen: errs.append(f"ADR-{n} duplicate: {p.name} and {seen[n]}")
|
|
31
|
+
seen[n] = p.name
|
|
32
|
+
num.append({"num": n, "slug": m.group(2), "path": p.name, **meta})
|
|
33
|
+
nums = {r["num"] for r in num}
|
|
34
|
+
for r in num:
|
|
35
|
+
s = r.get("supersedes", "—")
|
|
36
|
+
if s and s != "—":
|
|
37
|
+
t = s.replace("ADR-", "").lstrip("0").zfill(3)
|
|
38
|
+
if t not in nums: errs.append(f"{r['path']}: supersedes ADR-{t} not found")
|
|
39
|
+
return num, leg, errs
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def row(r):
|
|
43
|
+
title = r.get("decision", r.get("slug", "—")).replace("-", " ").title()
|
|
44
|
+
label = f"ADR-{r['num']}" if "num" in r else r["path"][:-3]
|
|
45
|
+
return (f"| [{label}]({r['path']}) | {title} | {r.get('status','—')} "
|
|
46
|
+
f"| {r.get('date','—')} | {r.get('supersedes','—')} |")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def render(num, leg):
|
|
50
|
+
out = ["# ADR Index", "", "_Auto-generated by `scripts/adr/regenerate_index.py`. Do not edit._", ""]
|
|
51
|
+
if not num and not leg: return "\n".join(out + ["No ADRs yet.", ""])
|
|
52
|
+
out += [HEAD, *(row(r) for r in num)]
|
|
53
|
+
if leg: out += ["", "## Unnumbered (legacy)", "", HEAD, *(row(r) for r in leg)]
|
|
54
|
+
return "\n".join(out + [""])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def main():
|
|
58
|
+
ap = argparse.ArgumentParser(description="Regenerate ADR INDEX.md")
|
|
59
|
+
ap.add_argument("--dir", default="docs/adr/")
|
|
60
|
+
ap.add_argument("--check", action="store_true", help="exit 1 if INDEX.md is stale")
|
|
61
|
+
a = ap.parse_args()
|
|
62
|
+
d = Path(a.dir)
|
|
63
|
+
if not d.is_dir():
|
|
64
|
+
print(f"adr-dir not found: {d}", file=sys.stderr); return 2
|
|
65
|
+
num, leg, errs = scan(d)
|
|
66
|
+
for e in errs: print(f"error: {e}", file=sys.stderr)
|
|
67
|
+
if errs: return 2
|
|
68
|
+
rendered, idx = render(num, leg), d / "INDEX.md"
|
|
69
|
+
if a.check:
|
|
70
|
+
cur = idx.read_text(encoding="utf-8") if idx.exists() else ""
|
|
71
|
+
if cur != rendered: print(f"stale: {idx}", file=sys.stderr); return 1
|
|
72
|
+
return 0
|
|
73
|
+
idx.write_text(rendered, encoding="utf-8")
|
|
74
|
+
print(f"wrote {idx} ({len(num)} numbered, {len(leg)} legacy)")
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
sys.exit(main())
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""One-off: add --quiet flag to every check_*/lint_* script that lacks one.
|
|
3
|
+
|
|
4
|
+
Target pattern (the canonical example is check_one_off_location.py):
|
|
5
|
+
- argparse parser exists
|
|
6
|
+
- parser.add_argument("--quiet", action="store_true", help="Only print on failure")
|
|
7
|
+
- success print lines matching `print(...✅...)` are wrapped:
|
|
8
|
+
if not args.quiet:
|
|
9
|
+
print("✅ ...")
|
|
10
|
+
|
|
11
|
+
For scripts without argparse, fall back to a minimal sys.argv probe.
|
|
12
|
+
Reports a manual-review list for anything that doesn't match the simple pattern.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
SCRIPTS = Path("scripts")
|
|
20
|
+
SUCCESS_RE = re.compile(r'^(\s*)(print\((?:f)?["\'].*\u2705.*\))\s*$')
|
|
21
|
+
# Accept any parser var name (parser, ap, p, …) — capture both lhs (args var) and rhs (parser var).
|
|
22
|
+
PARSE_ARGS_RE = re.compile(
|
|
23
|
+
r"^(\s*)([A-Za-z_][A-Za-z_0-9]*)\s*=\s*([A-Za-z_][A-Za-z_0-9]*)\.parse_args\(.*\)\s*$"
|
|
24
|
+
)
|
|
25
|
+
TOP_IMPORT_RE = re.compile(r"^(?:import |from )\S")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def has_quiet_flag(text: str) -> bool:
|
|
29
|
+
return '"--quiet"' in text or "'--quiet'" in text
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def has_argparse(text: str) -> bool:
|
|
33
|
+
return "argparse" in text and ".add_argument(" in text and ".parse_args(" in text
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def patch_argparse_script(text: str) -> tuple[str, int]:
|
|
37
|
+
"""Insert --quiet arg before parse_args() call; gate ✅ print lines.
|
|
38
|
+
|
|
39
|
+
Returns (new_text, n_prints_gated). Caller checks both for sanity.
|
|
40
|
+
"""
|
|
41
|
+
lines = text.splitlines(keepends=True)
|
|
42
|
+
out: list[str] = []
|
|
43
|
+
inserted = False
|
|
44
|
+
n_gated = 0
|
|
45
|
+
parse_args_var: str | None = None
|
|
46
|
+
|
|
47
|
+
for line in lines:
|
|
48
|
+
if not inserted:
|
|
49
|
+
m = PARSE_ARGS_RE.match(line.rstrip("\n"))
|
|
50
|
+
if m:
|
|
51
|
+
indent = m.group(1)
|
|
52
|
+
parse_args_var = m.group(2) # lhs (args var)
|
|
53
|
+
parser_var = m.group(3) # rhs (parser var: parser/ap/p/…)
|
|
54
|
+
out.append(
|
|
55
|
+
f'{indent}{parser_var}.add_argument("--quiet", action="store_true", '
|
|
56
|
+
'help="Only print on failure")\n'
|
|
57
|
+
)
|
|
58
|
+
inserted = True
|
|
59
|
+
out.append(line)
|
|
60
|
+
|
|
61
|
+
if not inserted or parse_args_var is None:
|
|
62
|
+
return text, 0
|
|
63
|
+
|
|
64
|
+
# Now rewrite: gate success prints behind `if not <var>.quiet:`
|
|
65
|
+
text2 = "".join(out)
|
|
66
|
+
lines2 = text2.splitlines(keepends=True)
|
|
67
|
+
out2: list[str] = []
|
|
68
|
+
for line in lines2:
|
|
69
|
+
m = SUCCESS_RE.match(line.rstrip("\n"))
|
|
70
|
+
if m:
|
|
71
|
+
indent, stmt = m.group(1), m.group(2)
|
|
72
|
+
out2.append(f"{indent}if not {parse_args_var}.quiet:\n")
|
|
73
|
+
out2.append(f"{indent} {stmt}\n")
|
|
74
|
+
n_gated += 1
|
|
75
|
+
else:
|
|
76
|
+
out2.append(line)
|
|
77
|
+
return "".join(out2), n_gated
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def patch_plain_script(text: str) -> tuple[str, int]:
|
|
81
|
+
"""For scripts without argparse: add a sys.argv probe near the top.
|
|
82
|
+
|
|
83
|
+
Inserts after the last `import` or `from` line. Gates ✅ prints behind QUIET.
|
|
84
|
+
"""
|
|
85
|
+
lines = text.splitlines(keepends=True)
|
|
86
|
+
last_import = -1
|
|
87
|
+
for i, line in enumerate(lines):
|
|
88
|
+
# Only TOP-LEVEL imports (column 0) — skip nested imports inside try/def.
|
|
89
|
+
if TOP_IMPORT_RE.match(line):
|
|
90
|
+
last_import = i
|
|
91
|
+
if last_import < 0:
|
|
92
|
+
return text, 0
|
|
93
|
+
if "import sys" not in text:
|
|
94
|
+
lines.insert(last_import + 1, "import sys\n")
|
|
95
|
+
last_import += 1
|
|
96
|
+
lines.insert(last_import + 1, '\nQUIET = "--quiet" in sys.argv\n')
|
|
97
|
+
|
|
98
|
+
n_gated = 0
|
|
99
|
+
out: list[str] = []
|
|
100
|
+
for line in lines:
|
|
101
|
+
m = SUCCESS_RE.match(line.rstrip("\n"))
|
|
102
|
+
if m:
|
|
103
|
+
indent, stmt = m.group(1), m.group(2)
|
|
104
|
+
out.append(f"{indent}if not QUIET:\n")
|
|
105
|
+
out.append(f"{indent} {stmt}\n")
|
|
106
|
+
n_gated += 1
|
|
107
|
+
else:
|
|
108
|
+
out.append(line)
|
|
109
|
+
return "".join(out), n_gated
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main() -> int:
|
|
113
|
+
targets = sorted(SCRIPTS.glob("check_*.py")) + sorted(SCRIPTS.glob("lint_*.py"))
|
|
114
|
+
skipped, patched, manual = [], [], []
|
|
115
|
+
|
|
116
|
+
for f in targets:
|
|
117
|
+
text = f.read_text()
|
|
118
|
+
if has_quiet_flag(text):
|
|
119
|
+
skipped.append(f.name)
|
|
120
|
+
continue
|
|
121
|
+
if has_argparse(text):
|
|
122
|
+
new, n = patch_argparse_script(text)
|
|
123
|
+
if n == 0 or new == text:
|
|
124
|
+
manual.append((f.name, f"argparse but no ✅-print gated (n={n})"))
|
|
125
|
+
continue
|
|
126
|
+
f.write_text(new)
|
|
127
|
+
patched.append((f.name, n))
|
|
128
|
+
else:
|
|
129
|
+
new, n = patch_plain_script(text)
|
|
130
|
+
if n == 0 or new == text:
|
|
131
|
+
manual.append((f.name, "plain but no ✅-print to gate"))
|
|
132
|
+
continue
|
|
133
|
+
f.write_text(new)
|
|
134
|
+
patched.append((f.name, n))
|
|
135
|
+
|
|
136
|
+
print(f"Skipped (already had --quiet): {len(skipped)}")
|
|
137
|
+
for s in skipped:
|
|
138
|
+
print(f" · {s}")
|
|
139
|
+
print(f"\nPatched: {len(patched)}")
|
|
140
|
+
for name, n in patched:
|
|
141
|
+
print(f" · {name} (gated {n} print(s))")
|
|
142
|
+
print(f"\nManual review needed: {len(manual)}")
|
|
143
|
+
for name, why in manual:
|
|
144
|
+
print(f" · {name}: {why}")
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""One-off: inject {{.QUIET_FLAG}} into Taskfile cmds for --quiet-aware scripts."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Scripts that accept --quiet (verified: 20 total = 3 pre-existing + 17 patched).
|
|
8
|
+
QUIET_AWARE = {
|
|
9
|
+
"check_always_budget", "check_one_off_location", "check_safety_floor_untouched",
|
|
10
|
+
"check_augmentignore", "check_command_count_messaging", "check_compressed_paths",
|
|
11
|
+
"check_council_layout", "check_council_references", "check_iron_law_prominence",
|
|
12
|
+
"check_md_language", "check_memory_proposal", "check_public_catalog_links",
|
|
13
|
+
"check_reply_consistency", "check_roadmap_trackable",
|
|
14
|
+
"lint_examples", "lint_handoffs", "lint_load_context", "lint_roadmap_complexity",
|
|
15
|
+
"lint_rule_interactions", "lint_rule_tiers",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
TASKFILES = sorted(Path("taskfiles").glob("*.yml")) + [Path("Taskfile.yml")]
|
|
19
|
+
patched = 0
|
|
20
|
+
for tf in TASKFILES:
|
|
21
|
+
text = tf.read_text()
|
|
22
|
+
new = text
|
|
23
|
+
for name in QUIET_AWARE:
|
|
24
|
+
# Match: cmd: python3 scripts/<name>.py [args]
|
|
25
|
+
# Insert {{.QUIET_FLAG}} after the script path, before any other args.
|
|
26
|
+
pat = re.compile(rf"(python3 scripts/{name}\.py)(\s|$)(?!.*QUIET_FLAG)")
|
|
27
|
+
new, n = pat.subn(r"\1 {{.QUIET_FLAG}}\2", new)
|
|
28
|
+
if n:
|
|
29
|
+
patched += n
|
|
30
|
+
if new != text:
|
|
31
|
+
tf.write_text(new)
|
|
32
|
+
print(f" patched: {tf}")
|
|
33
|
+
print(f"\nTotal injections: {patched}")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Phase 10.7 baseline — runs only verbosity-aware patched-script tasks.
|
|
3
|
+
# Skips broken-on-dirty-tree tasks (consistency, check-index, validate-schema).
|
|
4
|
+
# This subset is what actually demonstrates the --quiet effect.
|
|
5
|
+
TASKS="check-compressed-paths check-refs check-portability lint-roadmap-complexity check-public-catalog-links check-command-count check-cluster-patterns lint-rule-interactions lint-load-context check-context-paths check-no-roadmap-refs check-council-references lint-one-off-age check-reply-consistency check-iron-law-prominence check-always-budget check-one-off-location lint-rule-budget lint-skills lint-rule-tiers lint-handoffs lint-marketplace lint-examples"
|
|
6
|
+
|
|
7
|
+
run() {
|
|
8
|
+
local label=$1
|
|
9
|
+
local level=$2
|
|
10
|
+
local out=$3
|
|
11
|
+
AGENT_SCRIPT_VERBOSITY=$level task $TASKS > "$out" 2>&1
|
|
12
|
+
local lines=$(wc -l < "$out" | tr -d ' ')
|
|
13
|
+
local chars=$(wc -c < "$out" | tr -d ' ')
|
|
14
|
+
echo "$label: lines=$lines chars=$chars"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
echo "=== MINIMAL ==="
|
|
18
|
+
run "MINIMAL" minimal /tmp/ci-min.log
|
|
19
|
+
echo ""
|
|
20
|
+
echo "=== VERBOSE ==="
|
|
21
|
+
run "VERBOSE" verbose /tmp/ci-vrb.log
|
|
22
|
+
|
|
23
|
+
ML=$(wc -l < /tmp/ci-min.log | tr -d ' ')
|
|
24
|
+
MC=$(wc -c < /tmp/ci-min.log | tr -d ' ')
|
|
25
|
+
VL=$(wc -l < /tmp/ci-vrb.log | tr -d ' ')
|
|
26
|
+
VC=$(wc -c < /tmp/ci-vrb.log | tr -d ' ')
|
|
27
|
+
|
|
28
|
+
echo ""
|
|
29
|
+
python3 -c "
|
|
30
|
+
ml=$ML; vl=$VL; mc=$MC; vc=$VC
|
|
31
|
+
dl=(vl-ml)/vl*100 if vl else 0
|
|
32
|
+
dc=(vc-mc)/vc*100 if vc else 0
|
|
33
|
+
print(f'Lines: {ml} -> {vl}, reduction={dl:.1f}% (target >=40%)')
|
|
34
|
+
print(f'Chars: {mc} -> {vc}, reduction={dc:.1f}%')
|
|
35
|
+
print('verdict:', 'MET' if dl >= 40 else f'MISSED ({dl:.1f}% < 40%)')
|
|
36
|
+
"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Phase 10.7 baseline measurement: post-consistency task ci subset.
|
|
3
|
+
TASKS="counts-check check-index check-router check-compression check-compressed-paths check-refs check-token-optimizer-freshness check-portability check-examples-shape lint-roadmap-complexity check-public-links check-public-catalog-links check-command-count lint-no-new-atomic-commands check-cluster-patterns lint-rule-interactions lint-load-context check-context-paths check-no-roadmap-refs check-council-references lint-one-off-age check-ownership-matrix check-reply-consistency check-iron-law-prominence check-always-budget check-one-off-location validate-schema lint-rule-budget lint-skills lint-rule-tiers lint-handoffs lint-hook-manifest lint-showcase-sessions lint-marketplace"
|
|
4
|
+
|
|
5
|
+
echo "=== MINIMAL ==="
|
|
6
|
+
AGENT_SCRIPT_VERBOSITY=minimal task $TASKS 2>&1 > /tmp/ci-min.log
|
|
7
|
+
ML=$(wc -l < /tmp/ci-min.log | tr -d ' ')
|
|
8
|
+
MC=$(wc -c < /tmp/ci-min.log | tr -d ' ')
|
|
9
|
+
echo "MINIMAL: lines=$ML chars=$MC"
|
|
10
|
+
|
|
11
|
+
echo ""
|
|
12
|
+
echo "=== VERBOSE ==="
|
|
13
|
+
AGENT_SCRIPT_VERBOSITY=verbose task $TASKS 2>&1 > /tmp/ci-vrb.log
|
|
14
|
+
VL=$(wc -l < /tmp/ci-vrb.log | tr -d ' ')
|
|
15
|
+
VC=$(wc -c < /tmp/ci-vrb.log | tr -d ' ')
|
|
16
|
+
echo "VERBOSE: lines=$VL chars=$VC"
|
|
17
|
+
|
|
18
|
+
echo ""
|
|
19
|
+
python3 -c "
|
|
20
|
+
ml=$ML; vl=$VL; mc=$MC; vc=$VC
|
|
21
|
+
dl=(vl-ml)/vl*100 if vl else 0
|
|
22
|
+
dc=(vc-mc)/vc*100 if vc else 0
|
|
23
|
+
print(f'Lines: {ml} -> {vl}, reduction={dl:.1f}% (target >=40%)')
|
|
24
|
+
print(f'Chars: {mc} -> {vc}, reduction={dc:.1f}%')
|
|
25
|
+
print('verdict:', 'MET' if dl >= 40 else f'MISSED ({dl:.1f}% < 40%)')
|
|
26
|
+
"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
TASKS=(
|
|
3
|
+
check-compressed-paths
|
|
4
|
+
check-refs
|
|
5
|
+
check-portability
|
|
6
|
+
lint-roadmap-complexity
|
|
7
|
+
check-public-catalog-links
|
|
8
|
+
check-command-count
|
|
9
|
+
check-cluster-patterns
|
|
10
|
+
lint-rule-interactions
|
|
11
|
+
lint-load-context
|
|
12
|
+
check-context-paths
|
|
13
|
+
check-no-roadmap-refs
|
|
14
|
+
check-council-references
|
|
15
|
+
lint-one-off-age
|
|
16
|
+
check-reply-consistency
|
|
17
|
+
check-iron-law-prominence
|
|
18
|
+
check-always-budget
|
|
19
|
+
check-one-off-location
|
|
20
|
+
lint-rule-budget
|
|
21
|
+
lint-skills
|
|
22
|
+
lint-rule-tiers
|
|
23
|
+
lint-handoffs
|
|
24
|
+
lint-marketplace
|
|
25
|
+
check-examples-shape
|
|
26
|
+
)
|
|
27
|
+
totalmin=0
|
|
28
|
+
totalvrb=0
|
|
29
|
+
totaldelta=0
|
|
30
|
+
for t in "${TASKS[@]}"; do
|
|
31
|
+
m=$(AGENT_SCRIPT_VERBOSITY=minimal task "$t" 2>&1 | wc -l | tr -d ' ')
|
|
32
|
+
v=$(AGENT_SCRIPT_VERBOSITY=verbose task "$t" 2>&1 | wc -l | tr -d ' ')
|
|
33
|
+
d=$((v - m))
|
|
34
|
+
totalmin=$((totalmin + m))
|
|
35
|
+
totalvrb=$((totalvrb + v))
|
|
36
|
+
totaldelta=$((totaldelta + d))
|
|
37
|
+
printf "%-35s min=%-5s vrb=%-5s d=%s\n" "$t" "$m" "$v" "$d"
|
|
38
|
+
done
|
|
39
|
+
echo ""
|
|
40
|
+
echo "TOTAL: min=$totalmin vrb=$totalvrb delta=$totaldelta"
|
|
41
|
+
python3 -c "print(f'reduction: {($totalvrb - $totalmin)/$totalvrb*100:.1f}%' if $totalvrb else '')"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""One-off (Phase 10.3) — add `silent: true` to safe Taskfile tasks.
|
|
3
|
+
|
|
4
|
+
Inserts a ` silent: true` line directly after each ` <taskname>:` opener,
|
|
5
|
+
unless the task is in CARVE_OUTS or the line already exists.
|
|
6
|
+
|
|
7
|
+
Carve-outs stay loud:
|
|
8
|
+
- install / install-hooks / first-run / install-anthropic-key /
|
|
9
|
+
install-openai-key / setup-evals / test-triggers-live
|
|
10
|
+
- runtime-e2e (per roadmap explicit list)
|
|
11
|
+
- all release* tasks (release.yml — file-level skip)
|
|
12
|
+
- _ci-start / _ci-end / ci (root Taskfile orchestration)
|
|
13
|
+
|
|
14
|
+
Idempotent — running twice is a no-op.
|
|
15
|
+
|
|
16
|
+
Lifecycle: archive after Phase 10.3 lands per one-off-script-lifecycle.md.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import re
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
25
|
+
|
|
26
|
+
CARVE_OUTS = {
|
|
27
|
+
"install",
|
|
28
|
+
"install-hooks",
|
|
29
|
+
"first-run",
|
|
30
|
+
"install-anthropic-key",
|
|
31
|
+
"install-openai-key",
|
|
32
|
+
"setup-evals",
|
|
33
|
+
"test-triggers-live",
|
|
34
|
+
"runtime-e2e",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Regex matches a top-level task opener: 2 spaces + name + colon + EOL.
|
|
38
|
+
TASK_OPENER_RE = re.compile(r"^ ([a-z_][a-z0-9_:.-]*):$")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def patch_file(path: Path, skip_all: bool = False) -> tuple[int, int]:
|
|
42
|
+
"""Insert ` silent: true` after each safe task opener.
|
|
43
|
+
|
|
44
|
+
Returns (added, skipped).
|
|
45
|
+
"""
|
|
46
|
+
if skip_all:
|
|
47
|
+
return (0, 0)
|
|
48
|
+
|
|
49
|
+
text = path.read_text()
|
|
50
|
+
lines = text.splitlines(keepends=False)
|
|
51
|
+
out: list[str] = []
|
|
52
|
+
added = 0
|
|
53
|
+
skipped = 0
|
|
54
|
+
i = 0
|
|
55
|
+
while i < len(lines):
|
|
56
|
+
line = lines[i]
|
|
57
|
+
out.append(line)
|
|
58
|
+
m = TASK_OPENER_RE.match(line)
|
|
59
|
+
if m:
|
|
60
|
+
name = m.group(1)
|
|
61
|
+
# Look ahead one line — already silent?
|
|
62
|
+
next_line = lines[i + 1] if i + 1 < len(lines) else ""
|
|
63
|
+
already = next_line.strip() == "silent: true"
|
|
64
|
+
if name in CARVE_OUTS:
|
|
65
|
+
skipped += 1
|
|
66
|
+
elif already:
|
|
67
|
+
pass # idempotent
|
|
68
|
+
else:
|
|
69
|
+
out.append(" silent: true")
|
|
70
|
+
added += 1
|
|
71
|
+
i += 1
|
|
72
|
+
new_text = "\n".join(out) + ("\n" if text.endswith("\n") else "")
|
|
73
|
+
if new_text != text:
|
|
74
|
+
path.write_text(new_text)
|
|
75
|
+
return (added, skipped)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main() -> int:
|
|
79
|
+
targets = [
|
|
80
|
+
(ROOT / "taskfiles" / "content.yml", False),
|
|
81
|
+
(ROOT / "taskfiles" / "ci-fast.yml", False),
|
|
82
|
+
(ROOT / "taskfiles" / "engine.yml", False),
|
|
83
|
+
(ROOT / "taskfiles" / "release.yml", True), # all release* loud
|
|
84
|
+
]
|
|
85
|
+
total_added = 0
|
|
86
|
+
total_skipped = 0
|
|
87
|
+
for path, skip_all in targets:
|
|
88
|
+
added, skipped = patch_file(path, skip_all=skip_all)
|
|
89
|
+
rel = path.relative_to(ROOT)
|
|
90
|
+
print(f" {rel}: +{added} silent · {skipped} carved-out")
|
|
91
|
+
total_added += added
|
|
92
|
+
total_skipped += skipped
|
|
93
|
+
print(f"\n total: +{total_added} silent · {total_skipped} carved-out")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
sys.exit(main())
|
|
@@ -20,6 +20,8 @@ import sys
|
|
|
20
20
|
import time
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
|
|
23
|
+
QUIET = "--quiet" in sys.argv
|
|
24
|
+
|
|
23
25
|
STALE_DAYS = 90
|
|
24
26
|
MIN_USEFUL_LINES = 5
|
|
25
27
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
@@ -57,7 +59,8 @@ def check() -> int:
|
|
|
57
59
|
|
|
58
60
|
def _emit(notes: list[str]) -> None:
|
|
59
61
|
if not notes:
|
|
60
|
-
|
|
62
|
+
if not QUIET:
|
|
63
|
+
print("✅ .augmentignore advisory: nothing to suggest.")
|
|
61
64
|
return
|
|
62
65
|
print("📒 .augmentignore advisory (non-blocking):")
|
|
63
66
|
for n in notes:
|
|
@@ -38,6 +38,8 @@ import re
|
|
|
38
38
|
import sys
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
|
+
QUIET = "--quiet" in sys.argv
|
|
42
|
+
|
|
41
43
|
ROOT = Path(__file__).resolve().parent.parent
|
|
42
44
|
COMMANDS_DIR = ROOT / ".agent-src.uncompressed" / "commands"
|
|
43
45
|
README = ROOT / "README.md"
|
|
@@ -107,7 +109,8 @@ def main() -> int:
|
|
|
107
109
|
errors.append(err)
|
|
108
110
|
|
|
109
111
|
if not errors:
|
|
110
|
-
|
|
112
|
+
if not QUIET:
|
|
113
|
+
print("✅ All command-count messaging in sync with registry.")
|
|
111
114
|
return 0
|
|
112
115
|
|
|
113
116
|
print(f"❌ Command-count messaging drift — {len(errors)} mismatch(es):")
|
|
@@ -40,6 +40,8 @@ from pathlib import Path
|
|
|
40
40
|
|
|
41
41
|
import yaml
|
|
42
42
|
|
|
43
|
+
QUIET = "--quiet" in sys.argv
|
|
44
|
+
|
|
43
45
|
ROOT = Path(__file__).resolve().parent.parent
|
|
44
46
|
RULES_DIR = ROOT / ".agent-src" / "rules"
|
|
45
47
|
|
|
@@ -205,7 +207,8 @@ def main() -> int:
|
|
|
205
207
|
print(f"\n{len(viols)} violation(s) in .agent-src/rules/")
|
|
206
208
|
return 1
|
|
207
209
|
rule_count = len(list(RULES_DIR.glob('*.md')))
|
|
208
|
-
|
|
210
|
+
if not QUIET:
|
|
211
|
+
print(f"✅ compressed-path check clean ({rule_count} rules, {len(audited)} ignore(s) audited)")
|
|
209
212
|
return 0
|
|
210
213
|
|
|
211
214
|
|
|
@@ -32,6 +32,8 @@ import re
|
|
|
32
32
|
import sys
|
|
33
33
|
from pathlib import Path
|
|
34
34
|
|
|
35
|
+
QUIET = "--quiet" in sys.argv
|
|
36
|
+
|
|
35
37
|
AGENTS_ROOT = Path("agents")
|
|
36
38
|
CANONICAL_DIRS = {
|
|
37
39
|
"council-questions": ".md",
|
|
@@ -97,7 +99,8 @@ def main() -> int:
|
|
|
97
99
|
'§ "Output path convention"'
|
|
98
100
|
)
|
|
99
101
|
return 1
|
|
100
|
-
|
|
102
|
+
if not QUIET:
|
|
103
|
+
print("✅ Council layout clean.")
|
|
101
104
|
return 0
|
|
102
105
|
|
|
103
106
|
|