@event4u/agent-config 1.14.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/agent-handoff.md +1 -1
- package/.agent-src/commands/bug-fix.md +3 -3
- package/.agent-src/commands/bug-investigate.md +2 -2
- package/.agent-src/commands/chat-history-checkpoint.md +3 -3
- package/.agent-src/commands/chat-history-clear.md +2 -2
- package/.agent-src/commands/chat-history-resume.md +2 -2
- package/.agent-src/commands/chat-history.md +3 -3
- package/.agent-src/commands/check-current-md.md +44 -33
- package/.agent-src/commands/commit-in-chunks.md +43 -23
- package/.agent-src/commands/compress.md +34 -2
- 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 +8 -3
- package/.agent-src/commands/feature.md +58 -0
- package/.agent-src/commands/fix-ci.md +5 -0
- package/.agent-src/commands/fix-portability.md +7 -2
- 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 +17 -8
- package/.agent-src/commands/optimize-agents.md +6 -1
- package/.agent-src/commands/optimize-augmentignore.md +14 -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/refine-ticket.md +9 -7
- package/.agent-src/commands/review-changes.md +61 -9
- package/.agent-src/commands/review-routing.md +1 -1
- package/.agent-src/commands/roadmap-create.md +42 -4
- package/.agent-src/commands/roadmap-execute.md +9 -7
- package/.agent-src/commands/set-cost-profile.md +11 -3
- package/.agent-src/commands/sync-agent-settings.md +11 -2
- package/.agent-src/commands/tests-create.md +1 -1
- package/.agent-src/commands/tests-execute.md +2 -3
- 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 +2 -2
- package/.agent-src/rules/ask-when-uncertain.md +1 -1
- package/.agent-src/rules/augment-portability.md +56 -37
- 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 +109 -0
- package/.agent-src/rules/chat-history-ownership.md +123 -0
- package/.agent-src/rules/chat-history-visibility.md +96 -0
- package/.agent-src/rules/cli-output-handling.md +1 -1
- package/.agent-src/rules/{command-suggestion.md → command-suggestion-policy.md} +10 -9
- package/.agent-src/rules/commit-conventions.md +1 -1
- package/.agent-src/rules/commit-policy.md +43 -61
- package/.agent-src/rules/context-hygiene.md +3 -3
- package/.agent-src/rules/direct-answers.md +2 -2
- 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 +41 -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 +25 -59
- 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 +132 -80
- package/.agent-src/rules/role-mode-adherence.md +3 -3
- 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 +3 -2
- 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 +31 -7
- package/.agent-src/rules/verify-before-complete.md +12 -67
- package/.agent-src/scripts/update_roadmap_progress.py +65 -8
- 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 +30 -11
- 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 +16 -5
- 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/existing-ui-audit/SKILL.md +24 -9
- package/.agent-src/skills/fe-design/SKILL.md +20 -15
- package/.agent-src/skills/file-editor/SKILL.md +9 -0
- 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 +27 -8
- 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/refine-ticket/SKILL.md +30 -24
- package/.agent-src/skills/review-routing/SKILL.md +2 -2
- package/.agent-src/skills/roadmap-management/SKILL.md +22 -16
- 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 +6 -6
- 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 +3 -3
- 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 +9 -9
- package/.agent-src/templates/contexts/auth-model.md +1 -1
- package/.agent-src/templates/roadmaps.md +9 -8
- package/.agent-src/templates/scripts/README.md +2 -2
- package/.agent-src/templates/scripts/memory_lookup.py +1 -1
- 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/.agent-src/templates/scripts/work_engine/__init__.py +2 -2
- package/.agent-src/templates/scripts/work_engine/cli.py +64 -461
- package/.agent-src/templates/scripts/work_engine/cli_args.py +116 -0
- package/.agent-src/templates/scripts/work_engine/delivery_state.py +3 -3
- package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +1 -1
- package/.agent-src/templates/scripts/work_engine/directives/backend/implement.py +1 -1
- package/.agent-src/templates/scripts/work_engine/directives/backend/memory.py +1 -1
- package/.agent-src/templates/scripts/work_engine/directives/backend/plan.py +1 -1
- package/.agent-src/templates/scripts/work_engine/directives/backend/report.py +1 -1
- package/.agent-src/templates/scripts/work_engine/dispatcher.py +1 -1
- package/.agent-src/templates/scripts/work_engine/emitters.py +43 -0
- package/.agent-src/templates/scripts/work_engine/errors.py +19 -0
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +76 -0
- package/.agent-src/templates/scripts/work_engine/input_builders.py +163 -0
- package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +34 -2
- package/.agent-src/templates/scripts/work_engine/persona_policy.py +1 -1
- package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +1 -1
- package/.agent-src/templates/scripts/work_engine/state_io.py +202 -0
- package/.claude-plugin/marketplace.json +10 -2
- package/AGENTS.md +16 -12
- package/CHANGELOG.md +206 -9
- package/README.md +51 -52
- package/config/agent-settings.template.yml +58 -1
- package/config/gitignore-block.txt +3 -0
- package/docs/MIGRATION.md +122 -0
- package/docs/architecture.md +83 -34
- package/docs/catalog.md +331 -0
- package/docs/contracts/STABILITY.md +134 -0
- package/docs/contracts/adr-chat-history-split.md +132 -0
- package/docs/contracts/adr-command-suggestion.md +146 -0
- package/docs/contracts/adr-implement-ticket-runtime.md +122 -0
- package/docs/contracts/adr-product-ui-track.md +384 -0
- package/docs/contracts/adr-prompt-driven-execution.md +187 -0
- package/docs/contracts/agent-memory-contract.md +149 -0
- package/docs/contracts/artifact-engagement-flow.md +262 -0
- package/docs/contracts/command-clusters.md +126 -0
- package/docs/contracts/command-suggestion-flow.md +148 -0
- package/docs/contracts/implement-ticket-flow.md +628 -0
- package/docs/contracts/linear-ai-rules-inclusion.md +143 -0
- package/docs/contracts/linear-ai-three-layers.md +131 -0
- package/docs/contracts/load-context-schema.md +186 -0
- package/docs/contracts/rule-interactions.md +107 -0
- package/docs/contracts/rule-interactions.yml +238 -0
- package/docs/contracts/rule-priority-hierarchy.md +87 -0
- package/docs/contracts/ui-stack-extension.md +236 -0
- package/docs/contracts/ui-track-flow.md +338 -0
- package/docs/customization.md +14 -0
- package/docs/end-to-end-walkthroughs.md +165 -0
- package/docs/getting-started.md +27 -9
- 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/installation.md +42 -6
- package/docs/migrations/commands-1.15.0.md +112 -0
- package/docs/showcase.md +9 -4
- package/docs/skills-catalog.md +14 -8
- package/docs/ui-track-mental-model.md +121 -0
- 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/build_linear_digest.py +4 -4
- 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 +57 -0
- package/scripts/check_public_catalog_links.py +122 -0
- package/scripts/check_public_links.py +185 -0
- package/scripts/check_references.py +5 -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/lint_no_new_atomic_commands.py +179 -0
- package/scripts/lint_rule_interactions.py +149 -0
- package/scripts/memory_lookup.py +1 -1
- package/scripts/release.py +297 -64
- package/scripts/schemas/command.schema.json +20 -0
- package/scripts/schemas/rule.schema.json +10 -0
- package/scripts/skill_linter.py +26 -4
- package/scripts/sync_agent_settings.py +1 -1
- package/scripts/update_counts.py +19 -4
- package/scripts/update_prices.py +124 -0
- package/.agent-src/guidelines/php/git.md +0 -96
- package/.agent-src/rules/chat-history.md +0 -200
- /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,111 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CI guard for the `roadmap-progress-sync` rule's trackability Iron Law.
|
|
3
|
+
|
|
4
|
+
Every non-draft file under `agents/roadmaps/` (excluding `archive/`,
|
|
5
|
+
`skipped/`, template/README/open-questions) MUST:
|
|
6
|
+
|
|
7
|
+
1. Be parseable by the dashboard's `PHASE_RE` — i.e. contain at least
|
|
8
|
+
one `## Phase <id>` or `### Phase <id>` heading.
|
|
9
|
+
2. Have at least one trackable checkbox (`- [ ]`, `[x]`, `[~]`, `[-]`)
|
|
10
|
+
under every parsed phase.
|
|
11
|
+
|
|
12
|
+
A roadmap that fails (1) is invisible to `agents/roadmaps-progress.md`
|
|
13
|
+
and silently lies to the next reader. A phase that fails (2) shows as
|
|
14
|
+
`⬜ empty` and contributes nothing to progress percentages.
|
|
15
|
+
|
|
16
|
+
Both failure modes are rule violations per
|
|
17
|
+
`.augment/rules/roadmap-progress-sync.md` § "Iron Law — every active
|
|
18
|
+
roadmap is trackable". This script is the CI backstop so the rule
|
|
19
|
+
cannot be quietly broken again.
|
|
20
|
+
|
|
21
|
+
Exit codes:
|
|
22
|
+
0 — every active roadmap has parseable phases with at least one
|
|
23
|
+
checkbox per phase.
|
|
24
|
+
1 — at least one violation found; details printed to stdout.
|
|
25
|
+
|
|
26
|
+
Invocation (from project root):
|
|
27
|
+
python3 scripts/check_roadmap_trackable.py
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
# Reuse the dashboard's regexes and helpers — single source of truth so
|
|
36
|
+
# the linter cannot drift from what the dashboard actually parses.
|
|
37
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / ".augment" / "scripts"))
|
|
38
|
+
from update_roadmap_progress import ( # noqa: E402
|
|
39
|
+
CHECKBOX_RE,
|
|
40
|
+
PHASE_RE,
|
|
41
|
+
is_draft,
|
|
42
|
+
is_roadmap_candidate,
|
|
43
|
+
parse_frontmatter,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
ROADMAP_ROOT = Path("agents/roadmaps")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def find_active_roadmaps(root: Path) -> list[Path]:
|
|
50
|
+
"""Return every non-draft roadmap candidate under root."""
|
|
51
|
+
out: list[Path] = []
|
|
52
|
+
for path in sorted(root.rglob("*.md")):
|
|
53
|
+
if not path.is_file() or not is_roadmap_candidate(path):
|
|
54
|
+
continue
|
|
55
|
+
text = path.read_text(encoding="utf-8")
|
|
56
|
+
if is_draft(parse_frontmatter(text)):
|
|
57
|
+
continue
|
|
58
|
+
out.append(path)
|
|
59
|
+
return out
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def violations_for(path: Path) -> list[str]:
|
|
63
|
+
"""Return human-readable violation strings for a single roadmap."""
|
|
64
|
+
text = path.read_text(encoding="utf-8")
|
|
65
|
+
matches = list(PHASE_RE.finditer(text))
|
|
66
|
+
if not matches:
|
|
67
|
+
return [
|
|
68
|
+
f"{path}: no `## Phase <id>` or `### Phase <id>` heading "
|
|
69
|
+
"matched the dashboard's PHASE_RE — roadmap is invisible "
|
|
70
|
+
"to agents/roadmaps-progress.md. Either rename headings to "
|
|
71
|
+
"the canonical `Phase <id>` form or add `status: draft` to "
|
|
72
|
+
"the frontmatter."
|
|
73
|
+
]
|
|
74
|
+
out: list[str] = []
|
|
75
|
+
for i, pm in enumerate(matches):
|
|
76
|
+
start = pm.end()
|
|
77
|
+
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
|
|
78
|
+
if not CHECKBOX_RE.search(text[start:end]):
|
|
79
|
+
phase_id = pm.group(2)
|
|
80
|
+
name = (pm.group(3) or "").strip() or f"Phase {phase_id}"
|
|
81
|
+
out.append(
|
|
82
|
+
f"{path}: Phase {phase_id} ({name[:60]}) has zero "
|
|
83
|
+
"trackable checkboxes — add at least one `- [ ]` (or "
|
|
84
|
+
"`[x]`/`[~]`/`[-]`) item or remove the phase."
|
|
85
|
+
)
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
if not ROADMAP_ROOT.is_dir():
|
|
91
|
+
print(f"❌ {ROADMAP_ROOT} not found — run from project root.", file=sys.stderr)
|
|
92
|
+
return 1
|
|
93
|
+
findings: list[str] = []
|
|
94
|
+
for path in find_active_roadmaps(ROADMAP_ROOT):
|
|
95
|
+
findings.extend(violations_for(path))
|
|
96
|
+
if findings:
|
|
97
|
+
print("❌ Trackable-roadmap rule violations:\n")
|
|
98
|
+
for f in findings:
|
|
99
|
+
print(f" - {f}")
|
|
100
|
+
print(
|
|
101
|
+
"\nRule: .augment/rules/roadmap-progress-sync.md "
|
|
102
|
+
'§ "Iron Law — every active roadmap is trackable"'
|
|
103
|
+
)
|
|
104
|
+
return 1
|
|
105
|
+
count = len(find_active_roadmaps(ROADMAP_ROOT))
|
|
106
|
+
print(f"✅ {count} active roadmap(s) — all parseable, all phases have checkboxes.")
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
sys.exit(main())
|
|
@@ -31,7 +31,7 @@ def is_explicit_slash_invocation(message: str) -> bool:
|
|
|
31
31
|
|
|
32
32
|
Per the `command-suggestion` rule, explicit slash invocations
|
|
33
33
|
bypass the suggestion layer entirely \u2014 they're handled by
|
|
34
|
-
`slash-
|
|
34
|
+
`slash-command-routing-policy` directly. The engine should not score in that
|
|
35
35
|
case. Helper exposed for the runtime caller and the GT-CS4
|
|
36
36
|
golden.
|
|
37
37
|
"""
|
|
@@ -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())
|