@event4u/agent-config 1.15.0 → 1.16.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/bug-fix.md +1 -1
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/chat-history-checkpoint.md +1 -1
- package/.agent-src/commands/chat-history-clear.md +1 -1
- package/.agent-src/commands/chat-history.md +1 -1
- package/.agent-src/commands/check-current-md.md +1 -1
- package/.agent-src/commands/council-design.md +96 -0
- package/.agent-src/commands/council-optimize.md +115 -0
- package/.agent-src/commands/council-pr.md +123 -0
- package/.agent-src/commands/council.md +219 -0
- package/.agent-src/commands/create-pr.md +23 -0
- package/.agent-src/commands/do-and-judge.md +3 -3
- package/.agent-src/commands/do-in-steps.md +4 -4
- 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 +8 -0
- package/.agent-src/commands/feature-explore.md +6 -1
- package/.agent-src/commands/feature-plan.md +33 -2
- package/.agent-src/commands/feature-refactor.md +5 -0
- package/.agent-src/commands/feature-roadmap.md +6 -1
- package/.agent-src/commands/feature.md +58 -0
- package/.agent-src/commands/fix-ci.md +5 -0
- package/.agent-src/commands/fix-portability.md +5 -0
- package/.agent-src/commands/fix-pr-bot-comments.md +5 -0
- package/.agent-src/commands/fix-pr-comments.md +5 -0
- package/.agent-src/commands/fix-pr-developer-comments.md +5 -0
- package/.agent-src/commands/fix-references.md +5 -0
- package/.agent-src/commands/fix-seeder.md +5 -0
- package/.agent-src/commands/fix.md +60 -0
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/judge.md +1 -1
- package/.agent-src/commands/memory-add.md +3 -3
- package/.agent-src/commands/memory-full.md +2 -2
- package/.agent-src/commands/memory-promote.md +2 -2
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/onboard.md +3 -3
- package/.agent-src/commands/optimize-agents.md +6 -1
- package/.agent-src/commands/optimize-augmentignore.md +5 -0
- package/.agent-src/commands/optimize-rtk-filters.md +5 -0
- package/.agent-src/commands/optimize-skills.md +6 -1
- package/.agent-src/commands/optimize.md +54 -0
- package/.agent-src/commands/propose-memory.md +2 -2
- package/.agent-src/commands/review-changes.md +26 -1
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/roadmap-create.md +29 -2
- 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 +1 -1
- package/.agent-src/commands/upstream-contribute.md +1 -1
- package/.agent-src/contexts/authority/commit-mechanics.md +57 -0
- package/.agent-src/contexts/authority/destructive-mechanics.md +66 -0
- package/.agent-src/contexts/authority/scope-mechanics.md +87 -0
- package/.agent-src/contexts/execution/autonomy-detection.md +54 -0
- package/.agent-src/contexts/execution/autonomy-examples.md +90 -0
- package/.agent-src/contexts/execution/autonomy-mechanics.md +29 -0
- package/.agent-src/contexts/execution/verification-mechanics.md +80 -0
- package/.agent-src/personas/README.md +1 -1
- package/.agent-src/rules/agent-authority.md +24 -0
- package/.agent-src/rules/architecture.md +1 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/.agent-src/rules/artifact-engagement-recording.md +1 -1
- package/.agent-src/rules/ask-when-uncertain.md +1 -1
- package/.agent-src/rules/autonomous-execution.md +78 -114
- package/.agent-src/rules/capture-learnings.md +1 -1
- package/.agent-src/rules/chat-history-cadence.md +3 -3
- package/.agent-src/rules/chat-history-ownership.md +3 -3
- package/.agent-src/rules/chat-history-visibility.md +3 -3
- package/.agent-src/rules/{command-suggestion.md → command-suggestion-policy.md} +7 -7
- package/.agent-src/rules/commit-conventions.md +1 -1
- package/.agent-src/rules/commit-policy.md +14 -42
- package/.agent-src/rules/context-hygiene.md +3 -3
- package/.agent-src/rules/direct-answers.md +1 -1
- package/.agent-src/rules/docs-sync.md +1 -1
- package/.agent-src/rules/e2e-testing.md +1 -1
- package/.agent-src/rules/guidelines.md +4 -4
- package/.agent-src/rules/improve-before-implement.md +2 -2
- package/.agent-src/rules/language-and-tone.md +37 -96
- package/.agent-src/rules/minimal-safe-diff.md +3 -3
- package/.agent-src/rules/model-recommendation.md +4 -4
- package/.agent-src/rules/no-cheap-questions.md +89 -0
- package/.agent-src/rules/non-destructive-by-default.md +15 -49
- package/.agent-src/rules/onboarding-gate.md +5 -5
- package/.agent-src/rules/review-routing-awareness.md +9 -9
- package/.agent-src/rules/roadmap-progress-sync.md +26 -33
- package/.agent-src/rules/role-mode-adherence.md +2 -2
- package/.agent-src/rules/scope-control.md +65 -46
- package/.agent-src/rules/security-sensitive-stop.md +2 -2
- package/.agent-src/rules/size-enforcement.md +1 -1
- package/.agent-src/rules/think-before-action.md +5 -5
- package/.agent-src/rules/token-efficiency.md +4 -4
- package/.agent-src/rules/{ui-audit-before-build.md → ui-audit-gate.md} +3 -3
- package/.agent-src/rules/user-interaction.md +3 -3
- package/.agent-src/rules/verify-before-complete.md +12 -67
- package/.agent-src/scripts/update_roadmap_progress.py +9 -4
- package/.agent-src/skills/ai-council/SKILL.md +333 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/.agent-src/skills/blade-ui/SKILL.md +1 -1
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/.agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/.agent-src/skills/command-routing/SKILL.md +1 -1
- package/.agent-src/skills/command-writing/SKILL.md +1 -1
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
- package/.agent-src/skills/developer-like-execution/SKILL.md +2 -2
- package/.agent-src/skills/flux/SKILL.md +1 -1
- package/.agent-src/skills/git-workflow/SKILL.md +1 -1
- package/.agent-src/skills/guideline-writing/SKILL.md +11 -11
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +4 -4
- package/.agent-src/skills/livewire/SKILL.md +1 -1
- package/.agent-src/skills/override-management/SKILL.md +2 -2
- package/.agent-src/skills/php-coder/SKILL.md +1 -1
- package/.agent-src/skills/playwright-testing/SKILL.md +2 -2
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/readme-writing/SKILL.md +1 -1
- package/.agent-src/skills/readme-writing-package/SKILL.md +1 -1
- package/.agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/.agent-src/skills/review-routing/SKILL.md +2 -2
- package/.agent-src/skills/rule-writing/SKILL.md +1 -1
- package/.agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/skill-writing/SKILL.md +3 -3
- package/.agent-src/skills/subagent-orchestration/SKILL.md +1 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +1 -1
- package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/.agent-src/skills/validate-feature-fit/SKILL.md +2 -2
- package/.agent-src/skills/{verify-before-complete → verify-completion-evidence}/SKILL.md +2 -2
- package/.agent-src/templates/agent-settings.md +8 -8
- package/.agent-src/templates/contexts/auth-model.md +1 -1
- package/.agent-src/templates/scripts/README.md +2 -2
- package/.agent-src/templates/scripts/telemetry/aggregator.py +16 -1
- package/.agent-src/templates/scripts/telemetry/engagement.py +59 -0
- package/.agent-src/templates/scripts/telemetry/report_renderer.py +28 -1
- package/.agent-src/templates/scripts/telemetry_record.py +14 -1
- package/.claude-plugin/marketplace.json +10 -2
- package/AGENTS.md +11 -9
- package/CHANGELOG.md +123 -1
- package/README.md +28 -30
- package/config/agent-settings.template.yml +58 -1
- package/config/gitignore-block.txt +3 -0
- package/docs/architecture.md +4 -4
- package/docs/catalog.md +331 -0
- package/docs/contracts/STABILITY.md +39 -0
- package/docs/contracts/adr-command-suggestion.md +3 -3
- package/docs/contracts/adr-product-ui-track.md +2 -2
- package/docs/contracts/agent-memory-contract.md +2 -2
- package/docs/contracts/artifact-engagement-flow.md +1 -1
- package/docs/contracts/command-clusters.md +2 -2
- package/docs/contracts/command-suggestion-flow.md +3 -3
- package/docs/contracts/implement-ticket-flow.md +2 -2
- package/docs/contracts/linear-ai-rules-inclusion.md +1 -1
- package/docs/contracts/load-context-schema.md +186 -0
- package/docs/contracts/rule-interactions.yml +96 -0
- package/docs/contracts/rule-priority-hierarchy.md +87 -0
- package/docs/contracts/ui-track-flow.md +1 -1
- package/docs/customization.md +14 -0
- package/docs/end-to-end-walkthroughs.md +165 -0
- package/docs/getting-started.md +26 -8
- package/docs/github-topics.md +12 -3
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +79 -0
- package/{.agent-src → docs}/guidelines/docs/readme-size-and-splitting.md +26 -25
- package/docs/guidelines/php/git.md +164 -0
- package/docs/migrations/commands-1.15.0.md +1 -1
- package/docs/showcase.md +9 -4
- package/docs/skills-catalog.md +14 -8
- package/docs/ui-track-mental-model.md +2 -2
- package/llms.txt +13 -7
- package/package.json +1 -1
- package/scripts/agent-config +23 -0
- package/scripts/ai_council/__init__.py +39 -0
- package/scripts/ai_council/_default_prices.py +41 -0
- package/scripts/ai_council/_one_off_rebalancing_audit.py +149 -0
- package/scripts/ai_council/_one_off_roundtrip.py +106 -0
- package/scripts/ai_council/budget_guard.py +172 -0
- package/scripts/ai_council/bundler.py +261 -0
- package/scripts/ai_council/clients.py +381 -0
- package/scripts/ai_council/modes.py +127 -0
- package/scripts/ai_council/orchestrator.py +350 -0
- package/scripts/ai_council/pricing.py +213 -0
- package/scripts/ai_council/project_context.py +159 -0
- package/scripts/ai_council/prompts.py +232 -0
- package/scripts/ai_council/session.py +144 -0
- package/scripts/check_always_budget.py +126 -0
- package/scripts/check_augmentignore.py +69 -0
- package/scripts/check_command_count_messaging.py +120 -0
- package/scripts/check_portability.py +55 -0
- package/scripts/check_public_catalog_links.py +122 -0
- package/scripts/check_references.py +4 -1
- package/scripts/check_roadmap_trackable.py +111 -0
- package/scripts/command_suggester/cooldown.py +1 -1
- package/scripts/generate_index.py +266 -0
- package/scripts/install_anthropic_key.sh +5 -0
- package/scripts/install_openai_key.sh +106 -0
- package/scripts/lint_load_context.py +163 -0
- package/scripts/schemas/command.schema.json +20 -0
- package/scripts/schemas/rule.schema.json +10 -0
- package/scripts/skill_linter.py +12 -4
- package/scripts/sync_agent_settings.py +1 -1
- package/scripts/update_counts.py +9 -4
- package/scripts/update_prices.py +124 -0
- package/.agent-src/guidelines/php/git.md +0 -96
- /package/.agent-src/rules/{slash-commands.md → slash-command-routing-policy.md} +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/agent-interaction-and-decision-quality.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/break-glass-usage.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/developer-judgment.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/engineering-memory-data-format.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/layered-settings.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/memory-access.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/naming.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/output-patterns.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/review-routing-data-format.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/role-contracts.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/role-mode-router.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/runtime-layer.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/self-improvement-pipeline.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/size-and-scope.md +0 -0
- /package/{.agent-src → docs}/guidelines/agent-infra/tool-integration.md +0 -0
- /package/{.agent-src → docs}/guidelines/e2e/playwright.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/api-design.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/artisan-commands.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/blade-ui.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/controllers.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/database.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/eloquent.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/flux.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/general.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/jobs.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/livewire.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/logging.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/naming.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/dependency-injection.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/dtos.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/events.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/factory.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/pipelines.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/policies.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/repositories.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/service-layer.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns/strategy.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/patterns.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/performance.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/resources.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/security.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/sql.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/validations.md +0 -0
- /package/{.agent-src → docs}/guidelines/php/websocket.md +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate `agents/index.md` (internal) and `docs/catalog.md` (public).
|
|
3
|
+
|
|
4
|
+
Scans `.agent-src.uncompressed/{skills,rules,commands}/` plus `docs/guidelines/`
|
|
5
|
+
and renders two artefact tables — one for maintainers, one for consumers.
|
|
6
|
+
|
|
7
|
+
Both files are sync-checked in CI via `--check`; drift = build break.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python3 scripts/generate_index.py # write both files
|
|
11
|
+
python3 scripts/generate_index.py --check # exit 1 if drift
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
22
|
+
SRC = ROOT / ".agent-src.uncompressed"
|
|
23
|
+
GUIDELINES = ROOT / "docs" / "guidelines"
|
|
24
|
+
INDEX_PATH = ROOT / "agents" / "index.md"
|
|
25
|
+
CATALOG_PATH = ROOT / "docs" / "catalog.md"
|
|
26
|
+
|
|
27
|
+
# Internal-only rules — excluded from the public catalog.
|
|
28
|
+
INTERNAL_RULES = {
|
|
29
|
+
"augment-source-of-truth",
|
|
30
|
+
"augment-portability",
|
|
31
|
+
"docs-sync",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class Entry:
|
|
39
|
+
kind: str # skill | rule | command | shim | guideline
|
|
40
|
+
name: str
|
|
41
|
+
description: str
|
|
42
|
+
extra: str # rule type · cluster · sub-folder, etc.
|
|
43
|
+
path: str # repo-relative link target
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _parse_frontmatter(text: str) -> dict[str, str]:
|
|
47
|
+
m = FRONTMATTER_RE.match(text)
|
|
48
|
+
if not m:
|
|
49
|
+
return {}
|
|
50
|
+
out: dict[str, str] = {}
|
|
51
|
+
for line in m.group(1).splitlines():
|
|
52
|
+
if ":" not in line or line.startswith(" "):
|
|
53
|
+
continue
|
|
54
|
+
k, _, v = line.partition(":")
|
|
55
|
+
out[k.strip()] = v.strip().strip('"').strip("'")
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _truncate(text: str, limit: int = 200) -> str:
|
|
60
|
+
text = text.replace("|", "\\|").replace("\n", " ").strip()
|
|
61
|
+
return text if len(text) <= limit else text[: limit - 1].rstrip() + "…"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _collect_skills() -> list[Entry]:
|
|
65
|
+
out = []
|
|
66
|
+
for skill_dir in sorted((SRC / "skills").iterdir()):
|
|
67
|
+
skill_md = skill_dir / "SKILL.md"
|
|
68
|
+
if not skill_md.exists():
|
|
69
|
+
continue
|
|
70
|
+
fm = _parse_frontmatter(skill_md.read_text(encoding="utf-8"))
|
|
71
|
+
name = fm.get("name") or skill_dir.name
|
|
72
|
+
out.append(Entry(
|
|
73
|
+
kind="skill",
|
|
74
|
+
name=name,
|
|
75
|
+
description=_truncate(fm.get("description", "")),
|
|
76
|
+
extra="",
|
|
77
|
+
path=f".agent-src.uncompressed/skills/{skill_dir.name}/SKILL.md",
|
|
78
|
+
))
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _collect_rules() -> list[Entry]:
|
|
83
|
+
out = []
|
|
84
|
+
for rule_md in sorted((SRC / "rules").glob("*.md")):
|
|
85
|
+
fm = _parse_frontmatter(rule_md.read_text(encoding="utf-8"))
|
|
86
|
+
out.append(Entry(
|
|
87
|
+
kind="rule",
|
|
88
|
+
name=rule_md.stem,
|
|
89
|
+
description=_truncate(fm.get("description", "")),
|
|
90
|
+
extra=fm.get("type", "?"),
|
|
91
|
+
path=f".agent-src.uncompressed/rules/{rule_md.name}",
|
|
92
|
+
))
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _collect_commands() -> list[Entry]:
|
|
97
|
+
out = []
|
|
98
|
+
for cmd_md in sorted((SRC / "commands").glob("*.md")):
|
|
99
|
+
fm = _parse_frontmatter(cmd_md.read_text(encoding="utf-8"))
|
|
100
|
+
is_shim = bool(fm.get("superseded_by"))
|
|
101
|
+
extra = ""
|
|
102
|
+
if is_shim:
|
|
103
|
+
extra = f"shim → /{fm['superseded_by']}"
|
|
104
|
+
elif fm.get("cluster"):
|
|
105
|
+
extra = f"cluster: {fm['cluster']}"
|
|
106
|
+
out.append(Entry(
|
|
107
|
+
kind="shim" if is_shim else "command",
|
|
108
|
+
name=fm.get("name") or cmd_md.stem,
|
|
109
|
+
description=_truncate(fm.get("description", "")),
|
|
110
|
+
extra=extra,
|
|
111
|
+
path=f".agent-src.uncompressed/commands/{cmd_md.name}",
|
|
112
|
+
))
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _collect_guidelines() -> list[Entry]:
|
|
117
|
+
out = []
|
|
118
|
+
if not GUIDELINES.exists():
|
|
119
|
+
return out
|
|
120
|
+
for g_md in sorted(GUIDELINES.rglob("*.md")):
|
|
121
|
+
rel = g_md.relative_to(ROOT)
|
|
122
|
+
category = g_md.parent.name if g_md.parent != GUIDELINES else "(root)"
|
|
123
|
+
out.append(Entry(
|
|
124
|
+
kind="guideline",
|
|
125
|
+
name=g_md.stem,
|
|
126
|
+
description="",
|
|
127
|
+
extra=category,
|
|
128
|
+
path=str(rel),
|
|
129
|
+
))
|
|
130
|
+
return out
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Path rewriter for the public catalog: link to the shipped surface
|
|
134
|
+
# (`.agent-src/`) instead of the source-of-truth (`.agent-src.uncompressed/`),
|
|
135
|
+
# which is excluded from `package.json#files` and `composer.json` archives.
|
|
136
|
+
def _to_shipped_path(path: str) -> str:
|
|
137
|
+
return path.replace(".agent-src.uncompressed/", ".agent-src/", 1)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _render_table(
|
|
141
|
+
entries: list[Entry],
|
|
142
|
+
cols: list[str],
|
|
143
|
+
link_prefix: str,
|
|
144
|
+
path_rewrite=None,
|
|
145
|
+
) -> str:
|
|
146
|
+
rows = ["| " + " | ".join(cols) + " |", "|" + "|".join(["---"] * len(cols)) + "|"]
|
|
147
|
+
for e in entries:
|
|
148
|
+
path = path_rewrite(e.path) if path_rewrite else e.path
|
|
149
|
+
link = f"[`{e.name}`]({link_prefix}{path})"
|
|
150
|
+
row = [e.kind, link, e.extra, e.description]
|
|
151
|
+
rows.append("| " + " | ".join(row) + " |")
|
|
152
|
+
return "\n".join(rows)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _render_index(skills, rules, commands, guidelines) -> str:
|
|
157
|
+
total = len(skills) + len(rules) + len(commands) + len(guidelines)
|
|
158
|
+
parts = [
|
|
159
|
+
"# Agent-Config Internal Index",
|
|
160
|
+
"",
|
|
161
|
+
f"Maintainer-facing index of all **{total} artefacts** in this package.",
|
|
162
|
+
"Auto-generated from `.agent-src.uncompressed/` and `docs/guidelines/`.",
|
|
163
|
+
"",
|
|
164
|
+
"> **Regenerate:** `python3 scripts/generate_index.py`",
|
|
165
|
+
"> **Drift check:** `python3 scripts/generate_index.py --check` (runs in `task ci`)",
|
|
166
|
+
"> Do not edit manually.",
|
|
167
|
+
"",
|
|
168
|
+
f"## Skills ({len(skills)})",
|
|
169
|
+
"",
|
|
170
|
+
_render_table(skills, ["kind", "name", "extra", "description"], "../"),
|
|
171
|
+
"",
|
|
172
|
+
f"## Rules ({len(rules)})",
|
|
173
|
+
"",
|
|
174
|
+
_render_table(rules, ["kind", "name", "type", "description"], "../"),
|
|
175
|
+
"",
|
|
176
|
+
f"## Commands ({len(commands)})",
|
|
177
|
+
"",
|
|
178
|
+
_render_table(commands, ["kind", "name", "cluster/shim", "description"], "../"),
|
|
179
|
+
"",
|
|
180
|
+
f"## Guidelines ({len(guidelines)})",
|
|
181
|
+
"",
|
|
182
|
+
_render_table(guidelines, ["kind", "name", "category", "description"], "../"),
|
|
183
|
+
"",
|
|
184
|
+
]
|
|
185
|
+
return "\n".join(parts)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _render_catalog(skills, rules, commands, guidelines) -> str:
|
|
189
|
+
public_rules = [r for r in rules if r.name not in INTERNAL_RULES]
|
|
190
|
+
public_commands = [c for c in commands if c.kind == "command"]
|
|
191
|
+
total = len(skills) + len(public_rules) + len(public_commands) + len(guidelines)
|
|
192
|
+
parts = [
|
|
193
|
+
"# agent-config — Public Catalog",
|
|
194
|
+
"",
|
|
195
|
+
f"Consumer-facing catalog of all **{total} public artefacts** shipped by",
|
|
196
|
+
"this package. Internal package-maintenance rules and deprecation shims",
|
|
197
|
+
"are excluded.",
|
|
198
|
+
"",
|
|
199
|
+
"> **Regenerate:** `python3 scripts/generate_index.py`",
|
|
200
|
+
"> Auto-generated — do not edit manually.",
|
|
201
|
+
"",
|
|
202
|
+
f"## Skills ({len(skills)})",
|
|
203
|
+
"",
|
|
204
|
+
_render_table(skills, ["kind", "name", "extra", "description"], "../", _to_shipped_path),
|
|
205
|
+
"",
|
|
206
|
+
f"## Rules ({len(public_rules)})",
|
|
207
|
+
"",
|
|
208
|
+
_render_table(public_rules, ["kind", "name", "type", "description"], "../", _to_shipped_path),
|
|
209
|
+
"",
|
|
210
|
+
f"## Commands ({len(public_commands)})",
|
|
211
|
+
"",
|
|
212
|
+
_render_table(public_commands, ["kind", "name", "cluster", "description"], "../", _to_shipped_path),
|
|
213
|
+
"",
|
|
214
|
+
f"## Guidelines ({len(guidelines)})",
|
|
215
|
+
"",
|
|
216
|
+
_render_table(guidelines, ["kind", "name", "category", "description"], "../", _to_shipped_path),
|
|
217
|
+
"",
|
|
218
|
+
"---",
|
|
219
|
+
"",
|
|
220
|
+
"← [Back to README](../README.md)",
|
|
221
|
+
"",
|
|
222
|
+
]
|
|
223
|
+
return "\n".join(parts)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def main() -> int:
|
|
227
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
228
|
+
parser.add_argument("--check", action="store_true",
|
|
229
|
+
help="Exit 1 if generated content differs from on-disk files.")
|
|
230
|
+
args = parser.parse_args()
|
|
231
|
+
|
|
232
|
+
skills = _collect_skills()
|
|
233
|
+
rules = _collect_rules()
|
|
234
|
+
commands = _collect_commands()
|
|
235
|
+
guidelines = _collect_guidelines()
|
|
236
|
+
|
|
237
|
+
index_text = _render_index(skills, rules, commands, guidelines)
|
|
238
|
+
catalog_text = _render_catalog(skills, rules, commands, guidelines)
|
|
239
|
+
|
|
240
|
+
if args.check:
|
|
241
|
+
drift = []
|
|
242
|
+
if not INDEX_PATH.exists() or INDEX_PATH.read_text(encoding="utf-8") != index_text:
|
|
243
|
+
drift.append(str(INDEX_PATH.relative_to(ROOT)))
|
|
244
|
+
if not CATALOG_PATH.exists() or CATALOG_PATH.read_text(encoding="utf-8") != catalog_text:
|
|
245
|
+
drift.append(str(CATALOG_PATH.relative_to(ROOT)))
|
|
246
|
+
if drift:
|
|
247
|
+
print("❌ Index drift detected — regenerate with:")
|
|
248
|
+
print(" python3 scripts/generate_index.py")
|
|
249
|
+
for d in drift:
|
|
250
|
+
print(f" - {d}")
|
|
251
|
+
return 1
|
|
252
|
+
print("✅ Index files in sync.")
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
INDEX_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
256
|
+
CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
INDEX_PATH.write_text(index_text, encoding="utf-8")
|
|
258
|
+
CATALOG_PATH.write_text(catalog_text, encoding="utf-8")
|
|
259
|
+
print(f"✅ Wrote {INDEX_PATH.relative_to(ROOT)} ({len(skills)} skills, "
|
|
260
|
+
f"{len(rules)} rules, {len(commands)} commands, {len(guidelines)} guidelines)")
|
|
261
|
+
print(f"✅ Wrote {CATALOG_PATH.relative_to(ROOT)} (public subset)")
|
|
262
|
+
return 0
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
sys.exit(main())
|
|
@@ -99,3 +99,8 @@ echo "✅ Key installed: ${TARGET_FILE} (mode 0600)."
|
|
|
99
99
|
echo " Verify: ls -la ${TARGET_FILE}"
|
|
100
100
|
echo " Rotate: rerun this script (you'll be prompted to overwrite)."
|
|
101
101
|
echo " Remove: rm ${TARGET_FILE}"
|
|
102
|
+
echo
|
|
103
|
+
echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
|
|
104
|
+
echo " set ai_council.enabled: true and ai_council.members.anthropic.enabled: true"
|
|
105
|
+
echo " in .agent-settings.yml. Council is a 'full' cost_profile feature; under"
|
|
106
|
+
echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Interactive OpenAI-API-key installer for scripts/ai_council/clients.py.
|
|
3
|
+
#
|
|
4
|
+
# Reads the key with `read -s` so it never echoes to the terminal and
|
|
5
|
+
# never lands in shell history or scrollback. Writes atomically to
|
|
6
|
+
# ~/.config/agent-config/openai.key with mode 0600.
|
|
7
|
+
#
|
|
8
|
+
# Contract — companion to scripts/ai_council/clients.py:
|
|
9
|
+
# - File path: $HOME/.config/agent-config/openai.key
|
|
10
|
+
# - File mode: 0600 (owner read/write only)
|
|
11
|
+
# - Key format: must start with `sk-`
|
|
12
|
+
# - No --force, no --yes, no env-var bypass. Piped stdin is rejected.
|
|
13
|
+
#
|
|
14
|
+
# The runner re-checks all of the above at every live invocation and
|
|
15
|
+
# refuses to run if the file drifts from this contract.
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
TARGET_DIR="${HOME}/.config/agent-config"
|
|
20
|
+
TARGET_FILE="${TARGET_DIR}/openai.key"
|
|
21
|
+
|
|
22
|
+
# ── controlling-terminal requirement ─────────────────────────────────────
|
|
23
|
+
# We read from /dev/tty directly (fd 3), not from stdin. This is the
|
|
24
|
+
# stricter and more portable contract:
|
|
25
|
+
# - works under `task`, `script`, `sudo`, anything that reattaches stdin
|
|
26
|
+
# - forces every character to come from the user's real keyboard, so a
|
|
27
|
+
# pipe or redirected file cannot smuggle the key into the process
|
|
28
|
+
# - exits cleanly if there is no controlling terminal at all (e.g. CI,
|
|
29
|
+
# cron, agent automation)
|
|
30
|
+
if ! exec 3</dev/tty 2>/dev/null; then
|
|
31
|
+
echo "❌ install_openai_key.sh requires a controlling terminal." >&2
|
|
32
|
+
echo " /dev/tty not available — refusing to run under automation." >&2
|
|
33
|
+
exit 2
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# ── overwrite confirmation ───────────────────────────────────────────────
|
|
37
|
+
if [[ -e "${TARGET_FILE}" ]]; then
|
|
38
|
+
echo "⚠️ ${TARGET_FILE} already exists."
|
|
39
|
+
printf "Overwrite? [type 'yes' to replace, anything else aborts]: "
|
|
40
|
+
read -r -u 3 answer
|
|
41
|
+
if [[ "${answer}" != "yes" ]]; then
|
|
42
|
+
echo "Aborted. Existing key untouched."
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# ── read key (no echo, no history) ───────────────────────────────────────
|
|
48
|
+
echo "Paste your OpenAI API key (input is hidden, no echo)."
|
|
49
|
+
echo "The key should start with 'sk-'."
|
|
50
|
+
printf "Key: "
|
|
51
|
+
# -s = silent (no echo), read from fd 3 = /dev/tty, not stdin.
|
|
52
|
+
read -r -s -u 3 API_KEY
|
|
53
|
+
echo
|
|
54
|
+
|
|
55
|
+
if [[ -z "${API_KEY}" ]]; then
|
|
56
|
+
echo "❌ Empty input — no file written." >&2
|
|
57
|
+
exit 2
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [[ "${API_KEY}" != sk-* ]]; then
|
|
61
|
+
echo "❌ Input does not look like an OpenAI key (missing 'sk-' prefix)." >&2
|
|
62
|
+
echo " No file written." >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ── create config dir with 0700, atomic write with 0600 ──────────────────
|
|
67
|
+
mkdir -p "${TARGET_DIR}"
|
|
68
|
+
chmod 0700 "${TARGET_DIR}"
|
|
69
|
+
|
|
70
|
+
TMP_FILE="$(mktemp "${TARGET_DIR}/.openai.key.XXXXXX")"
|
|
71
|
+
cleanup() { rm -f "${TMP_FILE}"; }
|
|
72
|
+
trap cleanup EXIT
|
|
73
|
+
|
|
74
|
+
# chmod the tmpfile BEFORE writing the key, so there is no window where
|
|
75
|
+
# the key sits on disk with group/other-readable permissions.
|
|
76
|
+
chmod 0600 "${TMP_FILE}"
|
|
77
|
+
printf '%s\n' "${API_KEY}" > "${TMP_FILE}"
|
|
78
|
+
mv "${TMP_FILE}" "${TARGET_FILE}"
|
|
79
|
+
trap - EXIT
|
|
80
|
+
|
|
81
|
+
# Clear the variable and the `mv` positional argument — defence in depth
|
|
82
|
+
# against a crash handler that dumps the process environment.
|
|
83
|
+
API_KEY=""
|
|
84
|
+
|
|
85
|
+
# ── verify mode post-write (portable stat: BSD on macOS, GNU on Linux) ───
|
|
86
|
+
if ACTUAL_MODE=$(stat -f '%Lp' "${TARGET_FILE}" 2>/dev/null); then
|
|
87
|
+
: # macOS / BSD
|
|
88
|
+
else
|
|
89
|
+
ACTUAL_MODE=$(stat -c '%a' "${TARGET_FILE}")
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [[ "${ACTUAL_MODE}" != "600" ]]; then
|
|
93
|
+
echo "❌ Permissions verification failed: ${TARGET_FILE} has mode ${ACTUAL_MODE}, expected 600." >&2
|
|
94
|
+
echo " Delete and reinstall: rm ${TARGET_FILE} && $0" >&2
|
|
95
|
+
exit 3
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo "✅ Key installed: ${TARGET_FILE} (mode 0600)."
|
|
99
|
+
echo " Verify: ls -la ${TARGET_FILE}"
|
|
100
|
+
echo " Rotate: rerun this script (you'll be prompted to overwrite)."
|
|
101
|
+
echo " Remove: rm ${TARGET_FILE}"
|
|
102
|
+
echo
|
|
103
|
+
echo "ℹ️ Key install ≠ enable. To use this provider in /council:"
|
|
104
|
+
echo " set ai_council.enabled: true and ai_council.members.openai.enabled: true"
|
|
105
|
+
echo " in .agent-settings.yml. Council is a 'full' cost_profile feature; under"
|
|
106
|
+
echo " 'minimal' / 'balanced' the runtime hooks stay inactive."
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Lint the `load_context:` / `load_context_eager:` frontmatter schema.
|
|
3
|
+
|
|
4
|
+
Validates per docs/contracts/load-context-schema.md:
|
|
5
|
+
- Paths exist and are .md
|
|
6
|
+
- Allowed roots only (.agent-src*/contexts/, agents/contexts/)
|
|
7
|
+
- No public→project-local leak (warn)
|
|
8
|
+
- No circular refs across lazy + eager edges
|
|
9
|
+
- Combined char-budget for eager edges (rule + eager targets ≤ cap)
|
|
10
|
+
|
|
11
|
+
Exits non-zero on error; warnings are reported but do not fail.
|
|
12
|
+
Used in CI via `task lint-load-context`.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Iterable
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
23
|
+
|
|
24
|
+
SCAN_DIRS = [
|
|
25
|
+
ROOT / ".agent-src.uncompressed" / "rules",
|
|
26
|
+
ROOT / ".agent-src.uncompressed" / "contexts",
|
|
27
|
+
ROOT / "agents" / "contexts",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
ALLOWED_PREFIXES = (
|
|
31
|
+
".agent-src.uncompressed/contexts/",
|
|
32
|
+
".agent-src/contexts/",
|
|
33
|
+
"agents/contexts/",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
PUBLIC_RULE_PREFIX = ".agent-src.uncompressed/rules/"
|
|
37
|
+
PROJECT_LOCAL_PREFIX = "agents/contexts/"
|
|
38
|
+
|
|
39
|
+
HARD_FLOOR_RULES = {"non-destructive-by-default", "security-sensitive-stop"}
|
|
40
|
+
|
|
41
|
+
CAP_ALWAYS = 2_500
|
|
42
|
+
CAP_AUTO = 4_000
|
|
43
|
+
CAP_SAFETY = 5_000
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def parse_frontmatter(path: Path) -> dict:
|
|
47
|
+
text = path.read_text(encoding="utf-8")
|
|
48
|
+
if not text.startswith("---\n"):
|
|
49
|
+
return {}
|
|
50
|
+
end = text.find("\n---\n", 4)
|
|
51
|
+
if end == -1:
|
|
52
|
+
return {}
|
|
53
|
+
try:
|
|
54
|
+
data = yaml.safe_load(text[4:end])
|
|
55
|
+
except yaml.YAMLError:
|
|
56
|
+
return {}
|
|
57
|
+
return data if isinstance(data, dict) else {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def collect_files() -> Iterable[Path]:
|
|
61
|
+
for d in SCAN_DIRS:
|
|
62
|
+
if d.exists():
|
|
63
|
+
yield from d.rglob("*.md")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def rel(p: Path) -> str:
|
|
67
|
+
return p.relative_to(ROOT).as_posix()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cap_for(rule_path: Path, fm: dict) -> int:
|
|
71
|
+
if rule_path.stem in HARD_FLOOR_RULES:
|
|
72
|
+
return CAP_SAFETY
|
|
73
|
+
rtype = (fm.get("type") or "").strip('"').strip("'")
|
|
74
|
+
if rtype == "always":
|
|
75
|
+
return CAP_ALWAYS
|
|
76
|
+
if rtype == "auto":
|
|
77
|
+
return CAP_AUTO
|
|
78
|
+
return CAP_AUTO # default for non-rule contexts cited in eager (won't trigger)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def find_cycles(graph: dict[str, list[str]]) -> list[list[str]]:
|
|
82
|
+
cycles: list[list[str]] = []
|
|
83
|
+
visiting: set[str] = set()
|
|
84
|
+
visited: set[str] = set()
|
|
85
|
+
stack: list[str] = []
|
|
86
|
+
|
|
87
|
+
def dfs(node: str) -> None:
|
|
88
|
+
if node in visiting:
|
|
89
|
+
i = stack.index(node)
|
|
90
|
+
cycles.append(stack[i:] + [node])
|
|
91
|
+
return
|
|
92
|
+
if node in visited:
|
|
93
|
+
return
|
|
94
|
+
visiting.add(node)
|
|
95
|
+
stack.append(node)
|
|
96
|
+
for nxt in graph.get(node, []):
|
|
97
|
+
dfs(nxt)
|
|
98
|
+
stack.pop()
|
|
99
|
+
visiting.discard(node)
|
|
100
|
+
visited.add(node)
|
|
101
|
+
|
|
102
|
+
for n in graph:
|
|
103
|
+
dfs(n)
|
|
104
|
+
return cycles
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
errors: list[str] = []
|
|
109
|
+
warnings: list[str] = []
|
|
110
|
+
graph: dict[str, list[str]] = {}
|
|
111
|
+
|
|
112
|
+
for f in collect_files():
|
|
113
|
+
fm = parse_frontmatter(f)
|
|
114
|
+
lazy = fm.get("load_context") or []
|
|
115
|
+
eager = fm.get("load_context_eager") or []
|
|
116
|
+
if not (lazy or eager):
|
|
117
|
+
continue
|
|
118
|
+
if not isinstance(lazy, list) or not isinstance(eager, list):
|
|
119
|
+
errors.append(f"{rel(f)}: load_context* must be a list")
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
edges = list(lazy) + list(eager)
|
|
123
|
+
graph[rel(f)] = edges
|
|
124
|
+
|
|
125
|
+
for entry in edges:
|
|
126
|
+
if not isinstance(entry, str) or not entry.endswith(".md"):
|
|
127
|
+
errors.append(f"{rel(f)}: entry not str ending in .md → {entry!r}")
|
|
128
|
+
continue
|
|
129
|
+
if not entry.startswith(ALLOWED_PREFIXES):
|
|
130
|
+
errors.append(f"{rel(f)}: disallowed root → {entry}")
|
|
131
|
+
continue
|
|
132
|
+
target = ROOT / entry
|
|
133
|
+
if not target.exists():
|
|
134
|
+
errors.append(f"{rel(f)}: target missing → {entry}")
|
|
135
|
+
continue
|
|
136
|
+
if rel(f).startswith(PUBLIC_RULE_PREFIX) and entry.startswith(PROJECT_LOCAL_PREFIX):
|
|
137
|
+
warnings.append(f"{rel(f)}: public rule references project-local context → {entry}")
|
|
138
|
+
|
|
139
|
+
if eager:
|
|
140
|
+
cap = cap_for(f, fm)
|
|
141
|
+
total = len(f.read_text(encoding="utf-8"))
|
|
142
|
+
for entry in eager:
|
|
143
|
+
tgt = ROOT / entry
|
|
144
|
+
if tgt.exists():
|
|
145
|
+
total += len(tgt.read_text(encoding="utf-8"))
|
|
146
|
+
if total > cap:
|
|
147
|
+
errors.append(f"{rel(f)}: eager-load combined chars {total} > cap {cap}")
|
|
148
|
+
|
|
149
|
+
for cycle in find_cycles(graph):
|
|
150
|
+
errors.append("circular load_context: " + " → ".join(cycle))
|
|
151
|
+
|
|
152
|
+
for w in warnings:
|
|
153
|
+
print(f"⚠️ {w}")
|
|
154
|
+
for e in errors:
|
|
155
|
+
print(f"❌ {e}")
|
|
156
|
+
if errors:
|
|
157
|
+
return 1
|
|
158
|
+
print(f"✅ load_context schema clean ({len(graph)} declarer(s))")
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
sys.exit(main())
|
|
@@ -28,6 +28,26 @@
|
|
|
28
28
|
"pattern": "^[a-z][a-z0-9-]*$"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
|
+
"cluster": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
34
|
+
"description": "Locked verb cluster this command belongs to. See docs/contracts/command-clusters.md."
|
|
35
|
+
},
|
|
36
|
+
"sub": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
39
|
+
"description": "Sub-command identifier within the cluster (e.g. `ci` for `/fix ci`)."
|
|
40
|
+
},
|
|
41
|
+
"superseded_by": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)?$",
|
|
44
|
+
"description": "Set on deprecation shims. Format: '<cluster> <sub>' (e.g. 'fix ci'). See docs/contracts/command-clusters.md § Deprecation shim contract."
|
|
45
|
+
},
|
|
46
|
+
"deprecated_in": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
49
|
+
"description": "Semver release in which this command became a shim (e.g. '1.15.0')."
|
|
50
|
+
},
|
|
31
51
|
"suggestion": {
|
|
32
52
|
"type": "object",
|
|
33
53
|
"additionalProperties": false,
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"alwaysApply": {
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"description": "Optional sidecar for Cursor/Cline; by convention true when type=always, false when type=auto."
|
|
26
|
+
},
|
|
27
|
+
"load_context": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {"type": "string", "pattern": "\\.md$"},
|
|
30
|
+
"description": "Lazy on-demand context references. Path rules and budget caps enforced by scripts/lint_load_context.py. Contract: docs/contracts/load-context-schema.md."
|
|
31
|
+
},
|
|
32
|
+
"load_context_eager": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": {"type": "string", "pattern": "\\.md$"},
|
|
35
|
+
"description": "Eager auto-loaded context references. Counts against the per-rule char budget; enforced by scripts/lint_load_context.py."
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
}
|
package/scripts/skill_linter.py
CHANGED
|
@@ -136,9 +136,7 @@ def read_text(path: Path) -> str:
|
|
|
136
136
|
# --- Role-contract anchor cache (see road-to-role-modes Phase 1) ---
|
|
137
137
|
# Populated lazily so the linter stays fast when the guideline is absent.
|
|
138
138
|
_ROLE_CONTRACT_CANDIDATES = (
|
|
139
|
-
Path("
|
|
140
|
-
Path(".agent-src/guidelines/agent-infra/role-contracts.md"),
|
|
141
|
-
Path(".augment/guidelines/agent-infra/role-contracts.md"),
|
|
139
|
+
Path("docs/guidelines/agent-infra/role-contracts.md"),
|
|
142
140
|
)
|
|
143
141
|
_ROLE_CONTRACT_SLUGS_CACHE: Optional[set[str]] = None
|
|
144
142
|
|
|
@@ -373,7 +371,8 @@ def lint_skill(path: Path, text: str) -> LintResult:
|
|
|
373
371
|
|
|
374
372
|
if description:
|
|
375
373
|
if len(description) > 200:
|
|
376
|
-
issues.append(Issue("
|
|
374
|
+
issues.append(Issue("error", "description_too_long",
|
|
375
|
+
f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
377
376
|
for pattern in TRIGGER_WARNING_PATTERNS:
|
|
378
377
|
if re.search(pattern, description, re.IGNORECASE):
|
|
379
378
|
issues.append(Issue("warning", "weak_trigger", f"Description looks too generic: {description}"))
|
|
@@ -716,6 +715,12 @@ def lint_rule(path: Path, text: str) -> LintResult:
|
|
|
716
715
|
if not description:
|
|
717
716
|
issues.append(Issue("error", "auto_missing_description", "Auto rules require a 'description' field for matching"))
|
|
718
717
|
|
|
718
|
+
# description length cap (F6 — 200-char hard cap, see road-to-governance-cleanup)
|
|
719
|
+
rule_description = extract_description(text)
|
|
720
|
+
if rule_description and len(rule_description) > 200:
|
|
721
|
+
issues.append(Issue("error", "description_too_long",
|
|
722
|
+
f"Description is {len(rule_description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
723
|
+
|
|
719
724
|
# always-rules that look like auto candidates (rule-type-governance check)
|
|
720
725
|
if rule_type == "always":
|
|
721
726
|
description = extract_description(text) or ""
|
|
@@ -863,6 +868,9 @@ def lint_command(path: Path, text: str) -> LintResult:
|
|
|
863
868
|
description = extract_description(text)
|
|
864
869
|
if not description:
|
|
865
870
|
issues.append(Issue("warning", "missing_description", "Frontmatter description is missing"))
|
|
871
|
+
elif len(description) > 200:
|
|
872
|
+
issues.append(Issue("error", "description_too_long",
|
|
873
|
+
f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
866
874
|
|
|
867
875
|
# suggestion block (road-to-context-aware-command-suggestion Phase 2)
|
|
868
876
|
issues.extend(_lint_command_suggestion_block(text))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Sync `.agent-settings.yml` against the template + profile.
|
|
3
3
|
|
|
4
4
|
Applies the section-aware merge rules documented in
|
|
5
|
-
|
|
5
|
+
`docs/guidelines/agent-infra/layered-settings.md`:
|
|
6
6
|
|
|
7
7
|
- Template section order always wins — reorder keys to match.
|
|
8
8
|
- Existing user scalar values are preserved verbatim (as parsed).
|
package/scripts/update_counts.py
CHANGED
|
@@ -28,8 +28,9 @@ def count(kind: str) -> int:
|
|
|
28
28
|
if kind == "skills":
|
|
29
29
|
return sum(1 for _ in (SRC / "skills").rglob("SKILL.md"))
|
|
30
30
|
if kind == "guidelines":
|
|
31
|
-
# guidelines
|
|
32
|
-
|
|
31
|
+
# Guidelines live under docs/guidelines/{topic}/ — they are reference
|
|
32
|
+
# material, not packaged artefacts. Recursive walk to count every .md.
|
|
33
|
+
return sum(1 for _ in (REPO_ROOT / "docs" / "guidelines").rglob("*.md"))
|
|
33
34
|
if kind == "personas":
|
|
34
35
|
# personas live as flat .md files, README excluded
|
|
35
36
|
pdir = SRC / "personas"
|
|
@@ -47,12 +48,16 @@ TARGETS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
47
48
|
[
|
|
48
49
|
(r"(Browse all )(\d+)( commands\])", "commands"),
|
|
49
50
|
(r"(package \(rules \+ )(\d+)( skills)", "skills"),
|
|
50
|
-
(r"(skills \+ )(\d+)( native commands)", "commands"),
|
|
51
51
|
# Hero line: **NNN Skills** · **NNN Rules** · **NNN Commands** · **NNN Guidelines**
|
|
52
52
|
(r"(<strong>)(\d+)( Skills</strong>)", "skills"),
|
|
53
53
|
(r"(<strong>)(\d+)( Rules</strong>)", "rules"),
|
|
54
|
-
(r"(<strong>)(\d+)( Commands</strong>)", "commands"),
|
|
55
54
|
(r"(<strong>)(\d+)( Guidelines</strong>)", "guidelines"),
|
|
55
|
+
# NOTE: hero `<strong>N Commands</strong>` and tools-blurb
|
|
56
|
+
# `skills + N native commands` are owned by
|
|
57
|
+
# `check_command_count_messaging.py` (Phase-1.2 of
|
|
58
|
+
# road-to-pr-34-followups). Those surfaces advertise the
|
|
59
|
+
# **active** command count (total − deprecation shims), not
|
|
60
|
+
# the raw file count this script computes.
|
|
56
61
|
],
|
|
57
62
|
),
|
|
58
63
|
(
|