@event4u/agent-config 1.15.0 → 1.17.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-audit.md → agents/audit.md} +4 -3
- package/.agent-src/commands/{agents-cleanup.md → agents/cleanup.md} +12 -6
- package/.agent-src/commands/{agents-prepare.md → agents/prepare.md} +4 -3
- package/.agent-src/commands/agents.md +46 -0
- 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 → chat-history/checkpoint.md} +5 -5
- package/.agent-src/commands/{chat-history-clear.md → chat-history/clear.md} +5 -5
- package/.agent-src/commands/{chat-history-resume.md → chat-history/resume.md} +4 -4
- package/.agent-src/commands/chat-history/show.md +107 -0
- package/.agent-src/commands/chat-history.md +33 -89
- package/.agent-src/commands/check-current-md.md +1 -1
- package/.agent-src/commands/{commit-in-chunks.md → commit/in-chunks.md} +15 -13
- package/.agent-src/commands/commit.md +22 -2
- package/.agent-src/commands/{context-create.md → context/create.md} +4 -3
- package/.agent-src/commands/{context-refactor.md → context/refactor.md} +4 -3
- package/.agent-src/commands/context.md +44 -0
- package/.agent-src/commands/{copilot-agents-init.md → copilot-agents/init.md} +4 -3
- package/.agent-src/commands/{copilot-agents-optimize.md → copilot-agents/optimize.md} +4 -3
- package/.agent-src/commands/copilot-agents.md +44 -0
- package/.agent-src/commands/council/default.md +221 -0
- package/.agent-src/commands/council/design.md +97 -0
- package/.agent-src/commands/council/optimize.md +116 -0
- package/.agent-src/commands/council/pr.md +124 -0
- package/.agent-src/commands/council.md +54 -0
- package/.agent-src/commands/{create-pr-description.md → create-pr/description-only.md} +4 -2
- package/.agent-src/commands/create-pr.md +49 -5
- 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 → feature/dev.md} +6 -3
- package/.agent-src/commands/{feature-explore.md → feature/explore.md} +5 -4
- package/.agent-src/commands/{feature-plan.md → feature/plan.md} +32 -5
- package/.agent-src/commands/{feature-refactor.md → feature/refactor.md} +4 -3
- package/.agent-src/commands/{feature-roadmap.md → feature/roadmap.md} +7 -6
- package/.agent-src/commands/feature.md +52 -0
- package/.agent-src/commands/{fix-ci.md → fix/ci.md} +4 -3
- package/.agent-src/commands/{fix-portability.md → fix/portability.md} +4 -3
- package/.agent-src/commands/{fix-pr-bot-comments.md → fix/pr-bots.md} +4 -3
- package/.agent-src/commands/{fix-pr-developer-comments.md → fix/pr-developers.md} +4 -3
- package/.agent-src/commands/{fix-pr-comments.md → fix/pr.md} +7 -6
- package/.agent-src/commands/{fix-references.md → fix/refs.md} +4 -3
- package/.agent-src/commands/{fix-seeder.md → fix/seeder.md} +4 -3
- package/.agent-src/commands/fix.md +54 -0
- package/.agent-src/commands/jira-ticket.md +1 -1
- package/.agent-src/commands/{do-and-judge.md → judge/on-diff.md} +7 -6
- package/.agent-src/commands/judge/solo.md +90 -0
- package/.agent-src/commands/{do-in-steps.md → judge/steps.md} +8 -7
- package/.agent-src/commands/judge.md +35 -70
- package/.agent-src/commands/{memory-add.md → memory/add.md} +7 -6
- package/.agent-src/commands/{memory-full.md → memory/load.md} +6 -5
- package/.agent-src/commands/{memory-promote.md → memory/promote.md} +6 -5
- package/.agent-src/commands/{propose-memory.md → memory/propose.md} +6 -5
- package/.agent-src/commands/memory.md +48 -0
- package/.agent-src/commands/mode.md +5 -5
- package/.agent-src/commands/{module-create.md → module/create.md} +4 -3
- package/.agent-src/commands/{module-explore.md → module/explore.md} +4 -3
- package/.agent-src/commands/module.md +44 -0
- package/.agent-src/commands/onboard.md +3 -3
- package/.agent-src/commands/{optimize-agents.md → optimize/agents.md} +5 -4
- package/.agent-src/commands/{optimize-augmentignore.md → optimize/augmentignore.md} +4 -4
- package/.agent-src/commands/{optimize-rtk-filters.md → optimize/rtk.md} +4 -3
- package/.agent-src/commands/{optimize-skills.md → optimize/skills.md} +5 -4
- package/.agent-src/commands/optimize.md +48 -0
- package/.agent-src/commands/{override-create.md → override/create.md} +4 -3
- package/.agent-src/commands/{override-manage.md → override/manage.md} +4 -3
- package/.agent-src/commands/override.md +44 -0
- 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 → roadmap/create.md} +33 -5
- package/.agent-src/commands/{roadmap-execute.md → roadmap/execute.md} +4 -3
- package/.agent-src/commands/roadmap.md +44 -0
- 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 → tests/create.md} +5 -4
- package/.agent-src/commands/{tests-execute.md → tests/execute.md} +4 -3
- package/.agent-src/commands/tests.md +44 -0
- 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/communication/rules-auto/artifact-engagement-recording-mechanics.md +72 -0
- package/.agent-src/contexts/communication/rules-auto/augment-portability-mechanics.md +79 -0
- package/.agent-src/contexts/communication/rules-auto/augment-source-of-truth-mechanics.md +98 -0
- package/.agent-src/contexts/communication/rules-auto/cli-output-handling-mechanics.md +87 -0
- package/.agent-src/contexts/communication/rules-auto/command-suggestion-policy-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/docs-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/package-ci-checks-mechanics.md +85 -0
- package/.agent-src/contexts/communication/rules-auto/review-routing-awareness-mechanics.md +65 -0
- package/.agent-src/contexts/communication/rules-auto/roadmap-progress-sync-mechanics.md +78 -0
- package/.agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +62 -0
- package/.agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +55 -0
- package/.agent-src/contexts/communication/rules-auto/ui-audit-gate-mechanics.md +53 -0
- package/.agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +77 -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/contexts/judges/no-consolidate-rationale.md +102 -0
- package/.agent-src/contexts/judges/persona-voice-rubric.md +140 -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 +14 -70
- package/.agent-src/rules/ask-when-uncertain.md +28 -43
- package/.agent-src/rules/augment-portability.md +15 -61
- package/.agent-src/rules/augment-source-of-truth.md +27 -93
- 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/cli-output-handling.md +10 -76
- package/.agent-src/rules/command-suggestion-policy.md +93 -0
- package/.agent-src/rules/commit-conventions.md +17 -14
- 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 +34 -49
- package/.agent-src/rules/docker-commands.md +5 -5
- package/.agent-src/rules/docs-sync.md +16 -70
- 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 +50 -133
- package/.agent-src/rules/minimal-safe-diff.md +3 -3
- package/.agent-src/rules/missing-tool-handling.md +28 -22
- package/.agent-src/rules/model-recommendation.md +4 -4
- package/.agent-src/rules/no-cheap-questions.md +82 -0
- package/.agent-src/rules/no-roadmap-references.md +73 -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/package-ci-checks.md +21 -61
- package/.agent-src/rules/preservation-guard.md +64 -29
- package/.agent-src/rules/review-routing-awareness.md +26 -45
- package/.agent-src/rules/roadmap-progress-sync.md +28 -96
- 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 +9 -9
- package/.agent-src/rules/size-enforcement.md +1 -1
- package/.agent-src/rules/skill-quality.md +16 -48
- package/.agent-src/rules/{slash-commands.md → slash-command-routing-policy.md} +7 -4
- package/.agent-src/rules/think-before-action.md +55 -45
- package/.agent-src/rules/token-efficiency.md +4 -4
- package/.agent-src/rules/tool-safety.md +19 -16
- package/.agent-src/rules/{ui-audit-before-build.md → ui-audit-gate.md} +27 -41
- package/.agent-src/rules/user-interaction.md +16 -71
- 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 +335 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +2 -2
- package/.agent-src/skills/api-testing/SKILL.md +1 -1
- 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/check-refs/SKILL.md +59 -40
- 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 +86 -28
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +7 -7
- package/.agent-src/skills/developer-like-execution/SKILL.md +6 -6
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +101 -65
- package/.agent-src/skills/flux/SKILL.md +31 -11
- package/.agent-src/skills/git-workflow/SKILL.md +1 -1
- package/.agent-src/skills/github-ci/SKILL.md +2 -2
- package/.agent-src/skills/guideline-writing/SKILL.md +11 -11
- package/.agent-src/skills/judge-code-quality/SKILL.md +7 -8
- package/.agent-src/skills/judge-security-auditor/SKILL.md +4 -5
- package/.agent-src/skills/judge-test-coverage/SKILL.md +3 -4
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +4 -4
- package/.agent-src/skills/lint-skills/SKILL.md +57 -39
- package/.agent-src/skills/livewire/SKILL.md +1 -1
- package/.agent-src/skills/md-language-check/SKILL.md +61 -39
- package/.agent-src/skills/override-management/SKILL.md +7 -7
- package/.agent-src/skills/php-coder/SKILL.md +1 -1
- package/.agent-src/skills/playwright-testing/SKILL.md +2 -2
- package/.agent-src/skills/quality-tools/SKILL.md +2 -2
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +116 -43
- package/.agent-src/skills/readme-reviewer/SKILL.md +31 -30
- package/.agent-src/skills/readme-writing/SKILL.md +79 -54
- package/.agent-src/skills/readme-writing-package/SKILL.md +51 -48
- package/.agent-src/skills/receiving-code-review/SKILL.md +53 -48
- package/.agent-src/skills/refine-prompt/SKILL.md +0 -1
- package/.agent-src/skills/requesting-code-review/SKILL.md +35 -30
- package/.agent-src/skills/review-routing/SKILL.md +2 -2
- package/.agent-src/skills/rule-writing/SKILL.md +1 -1
- package/.agent-src/skills/security/SKILL.md +7 -2
- package/.agent-src/skills/security-audit/SKILL.md +7 -3
- 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 +69 -61
- package/.agent-src/skills/test-driven-development/SKILL.md +59 -57
- package/.agent-src/skills/test-performance/SKILL.md +0 -1
- package/.agent-src/skills/traefik/SKILL.md +4 -4
- 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 +30 -28
- 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 +31 -12
- package/AGENTS.md +11 -9
- package/CHANGELOG.md +213 -2
- package/README.md +43 -44
- package/config/agent-settings.template.yml +58 -1
- package/config/gitignore-block.txt +3 -0
- package/docs/architecture.md +5 -7
- package/docs/catalog.md +359 -0
- package/docs/contracts/STABILITY.md +46 -1
- package/docs/contracts/adr-chat-history-split.md +1 -3
- package/docs/contracts/adr-command-suggestion.md +3 -5
- package/docs/contracts/adr-implement-ticket-runtime.md +1 -2
- package/docs/contracts/adr-product-ui-track.md +5 -8
- package/docs/contracts/adr-prompt-driven-execution.md +3 -4
- package/docs/contracts/agent-memory-contract.md +8 -13
- package/docs/contracts/artifact-engagement-flow.md +7 -10
- package/docs/contracts/command-clusters.md +56 -46
- package/docs/contracts/command-suggestion-flow.md +4 -6
- package/docs/contracts/context-paths.md +99 -0
- package/docs/contracts/file-ownership-matrix.json +6722 -0
- package/docs/contracts/file-ownership-matrix.md +134 -0
- package/docs/contracts/implement-ticket-flow.md +8 -11
- package/docs/contracts/linear-ai-rules-inclusion.md +1 -2
- package/docs/contracts/linear-ai-three-layers.md +0 -2
- package/docs/contracts/load-context-budget-model.md +178 -0
- package/docs/contracts/load-context-schema.md +184 -0
- package/docs/contracts/rule-interactions.md +0 -1
- package/docs/contracts/rule-interactions.yml +96 -0
- package/docs/contracts/rule-priority-hierarchy.md +87 -0
- package/docs/contracts/ui-track-flow.md +8 -18
- package/docs/customization.md +16 -0
- package/docs/end-to-end-walkthroughs.md +165 -0
- package/docs/getting-started.md +29 -10
- package/docs/github-topics.md +12 -3
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +100 -0
- 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/_one_off_phase4_dispatch_latency.py +108 -0
- package/scripts/_one_off_phase6_trigger_jaccard.py +92 -0
- package/scripts/_phase2_shim_helper.py +109 -0
- package/scripts/agent-config +33 -0
- package/scripts/ai_council/__init__.py +39 -0
- package/scripts/ai_council/_default_prices.py +41 -0
- package/scripts/ai_council/_one_off_2a4_acceptance.py +208 -0
- package/scripts/ai_council/_one_off_context_layer_v1_estimate.py +67 -0
- package/scripts/ai_council/_one_off_context_layer_v1_review.py +292 -0
- package/scripts/ai_council/_one_off_followups_review.py +259 -0
- package/scripts/ai_council/_one_off_nondestructive_inline_audit.py +209 -0
- package/scripts/ai_council/_one_off_phase_2a_budget_rebalance.py +257 -0
- package/scripts/ai_council/_one_off_phase_2a_post_revert.py +197 -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/_one_off_rule_hardening_v1.py +251 -0
- package/scripts/ai_council/_one_off_structural_open_questions.py +232 -0
- package/scripts/ai_council/_one_off_structural_optimization.py +144 -0
- package/scripts/ai_council/_one_off_structural_v3_gaps.py +252 -0
- package/scripts/ai_council/_one_off_structural_v3_review.py +240 -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 +444 -0
- package/scripts/check_augmentignore.py +69 -0
- package/scripts/check_cluster_patterns.py +159 -0
- package/scripts/check_command_count_messaging.py +127 -0
- package/scripts/check_context_paths.py +201 -0
- package/scripts/check_no_roadmap_refs.py +155 -0
- package/scripts/check_phase_coupling.py +148 -0
- package/scripts/check_portability.py +57 -0
- package/scripts/check_public_catalog_links.py +122 -0
- package/scripts/check_references.py +33 -3
- package/scripts/check_roadmap_trackable.py +111 -0
- package/scripts/check_safety_floor_untouched.py +125 -0
- package/scripts/command_suggester/cooldown.py +1 -1
- package/scripts/command_suggester/loader.py +4 -1
- package/scripts/compress.py +59 -13
- package/scripts/generate_index.py +270 -0
- package/scripts/generate_ownership_matrix.py +323 -0
- package/scripts/hooks/augment-roadmap-progress.sh +57 -0
- package/scripts/install.py +49 -28
- 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 +12 -11
- package/scripts/requirements-evals.txt +1 -0
- package/scripts/roadmap_progress_hook.py +159 -0
- package/scripts/schemas/command.schema.json +22 -1
- package/scripts/schemas/rule.schema.json +10 -0
- package/scripts/skill_linter.py +13 -4
- package/scripts/sync_agent_settings.py +26 -3
- package/scripts/update_counts.py +16 -4
- package/scripts/update_prices.py +124 -0
- package/.agent-src/guidelines/php/git.md +0 -96
- package/.agent-src/rules/command-suggestion.md +0 -134
- /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,270 @@
|
|
|
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
|
+
cmd_dir = SRC / "commands"
|
|
99
|
+
for cmd_md in sorted(cmd_dir.rglob("*.md")):
|
|
100
|
+
if cmd_md.name == "AGENTS.md":
|
|
101
|
+
continue
|
|
102
|
+
fm = _parse_frontmatter(cmd_md.read_text(encoding="utf-8"))
|
|
103
|
+
is_shim = bool(fm.get("superseded_by"))
|
|
104
|
+
extra = ""
|
|
105
|
+
if is_shim:
|
|
106
|
+
extra = f"shim → /{fm['superseded_by']}"
|
|
107
|
+
elif fm.get("cluster"):
|
|
108
|
+
extra = f"cluster: {fm['cluster']}"
|
|
109
|
+
rel = cmd_md.relative_to(cmd_dir)
|
|
110
|
+
out.append(Entry(
|
|
111
|
+
kind="shim" if is_shim else "command",
|
|
112
|
+
name=fm.get("name") or cmd_md.stem,
|
|
113
|
+
description=_truncate(fm.get("description", "")),
|
|
114
|
+
extra=extra,
|
|
115
|
+
path=f".agent-src.uncompressed/commands/{rel}",
|
|
116
|
+
))
|
|
117
|
+
return out
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _collect_guidelines() -> list[Entry]:
|
|
121
|
+
out = []
|
|
122
|
+
if not GUIDELINES.exists():
|
|
123
|
+
return out
|
|
124
|
+
for g_md in sorted(GUIDELINES.rglob("*.md")):
|
|
125
|
+
rel = g_md.relative_to(ROOT)
|
|
126
|
+
category = g_md.parent.name if g_md.parent != GUIDELINES else "(root)"
|
|
127
|
+
out.append(Entry(
|
|
128
|
+
kind="guideline",
|
|
129
|
+
name=g_md.stem,
|
|
130
|
+
description="",
|
|
131
|
+
extra=category,
|
|
132
|
+
path=str(rel),
|
|
133
|
+
))
|
|
134
|
+
return out
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Path rewriter for the public catalog: link to the shipped surface
|
|
138
|
+
# (`.agent-src/`) instead of the source-of-truth (`.agent-src.uncompressed/`),
|
|
139
|
+
# which is excluded from `package.json#files` and `composer.json` archives.
|
|
140
|
+
def _to_shipped_path(path: str) -> str:
|
|
141
|
+
return path.replace(".agent-src.uncompressed/", ".agent-src/", 1)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _render_table(
|
|
145
|
+
entries: list[Entry],
|
|
146
|
+
cols: list[str],
|
|
147
|
+
link_prefix: str,
|
|
148
|
+
path_rewrite=None,
|
|
149
|
+
) -> str:
|
|
150
|
+
rows = ["| " + " | ".join(cols) + " |", "|" + "|".join(["---"] * len(cols)) + "|"]
|
|
151
|
+
for e in entries:
|
|
152
|
+
path = path_rewrite(e.path) if path_rewrite else e.path
|
|
153
|
+
link = f"[`{e.name}`]({link_prefix}{path})"
|
|
154
|
+
row = [e.kind, link, e.extra, e.description]
|
|
155
|
+
rows.append("| " + " | ".join(row) + " |")
|
|
156
|
+
return "\n".join(rows)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _render_index(skills, rules, commands, guidelines) -> str:
|
|
161
|
+
total = len(skills) + len(rules) + len(commands) + len(guidelines)
|
|
162
|
+
parts = [
|
|
163
|
+
"# Agent-Config Internal Index",
|
|
164
|
+
"",
|
|
165
|
+
f"Maintainer-facing index of all **{total} artefacts** in this package.",
|
|
166
|
+
"Auto-generated from `.agent-src.uncompressed/` and `docs/guidelines/`.",
|
|
167
|
+
"",
|
|
168
|
+
"> **Regenerate:** `python3 scripts/generate_index.py`",
|
|
169
|
+
"> **Drift check:** `python3 scripts/generate_index.py --check` (runs in `task ci`)",
|
|
170
|
+
"> Do not edit manually.",
|
|
171
|
+
"",
|
|
172
|
+
f"## Skills ({len(skills)})",
|
|
173
|
+
"",
|
|
174
|
+
_render_table(skills, ["kind", "name", "extra", "description"], "../"),
|
|
175
|
+
"",
|
|
176
|
+
f"## Rules ({len(rules)})",
|
|
177
|
+
"",
|
|
178
|
+
_render_table(rules, ["kind", "name", "type", "description"], "../"),
|
|
179
|
+
"",
|
|
180
|
+
f"## Commands ({len(commands)})",
|
|
181
|
+
"",
|
|
182
|
+
_render_table(commands, ["kind", "name", "cluster/shim", "description"], "../"),
|
|
183
|
+
"",
|
|
184
|
+
f"## Guidelines ({len(guidelines)})",
|
|
185
|
+
"",
|
|
186
|
+
_render_table(guidelines, ["kind", "name", "category", "description"], "../"),
|
|
187
|
+
"",
|
|
188
|
+
]
|
|
189
|
+
return "\n".join(parts)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _render_catalog(skills, rules, commands, guidelines) -> str:
|
|
193
|
+
public_rules = [r for r in rules if r.name not in INTERNAL_RULES]
|
|
194
|
+
public_commands = [c for c in commands if c.kind == "command"]
|
|
195
|
+
total = len(skills) + len(public_rules) + len(public_commands) + len(guidelines)
|
|
196
|
+
parts = [
|
|
197
|
+
"# agent-config — Public Catalog",
|
|
198
|
+
"",
|
|
199
|
+
f"Consumer-facing catalog of all **{total} public artefacts** shipped by",
|
|
200
|
+
"this package. Internal package-maintenance rules and deprecation shims",
|
|
201
|
+
"are excluded.",
|
|
202
|
+
"",
|
|
203
|
+
"> **Regenerate:** `python3 scripts/generate_index.py`",
|
|
204
|
+
"> Auto-generated — do not edit manually.",
|
|
205
|
+
"",
|
|
206
|
+
f"## Skills ({len(skills)})",
|
|
207
|
+
"",
|
|
208
|
+
_render_table(skills, ["kind", "name", "extra", "description"], "../", _to_shipped_path),
|
|
209
|
+
"",
|
|
210
|
+
f"## Rules ({len(public_rules)})",
|
|
211
|
+
"",
|
|
212
|
+
_render_table(public_rules, ["kind", "name", "type", "description"], "../", _to_shipped_path),
|
|
213
|
+
"",
|
|
214
|
+
f"## Commands ({len(public_commands)})",
|
|
215
|
+
"",
|
|
216
|
+
_render_table(public_commands, ["kind", "name", "cluster", "description"], "../", _to_shipped_path),
|
|
217
|
+
"",
|
|
218
|
+
f"## Guidelines ({len(guidelines)})",
|
|
219
|
+
"",
|
|
220
|
+
_render_table(guidelines, ["kind", "name", "category", "description"], "../", _to_shipped_path),
|
|
221
|
+
"",
|
|
222
|
+
"---",
|
|
223
|
+
"",
|
|
224
|
+
"← [Back to README](../README.md)",
|
|
225
|
+
"",
|
|
226
|
+
]
|
|
227
|
+
return "\n".join(parts)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def main() -> int:
|
|
231
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
232
|
+
parser.add_argument("--check", action="store_true",
|
|
233
|
+
help="Exit 1 if generated content differs from on-disk files.")
|
|
234
|
+
args = parser.parse_args()
|
|
235
|
+
|
|
236
|
+
skills = _collect_skills()
|
|
237
|
+
rules = _collect_rules()
|
|
238
|
+
commands = _collect_commands()
|
|
239
|
+
guidelines = _collect_guidelines()
|
|
240
|
+
|
|
241
|
+
index_text = _render_index(skills, rules, commands, guidelines)
|
|
242
|
+
catalog_text = _render_catalog(skills, rules, commands, guidelines)
|
|
243
|
+
|
|
244
|
+
if args.check:
|
|
245
|
+
drift = []
|
|
246
|
+
if not INDEX_PATH.exists() or INDEX_PATH.read_text(encoding="utf-8") != index_text:
|
|
247
|
+
drift.append(str(INDEX_PATH.relative_to(ROOT)))
|
|
248
|
+
if not CATALOG_PATH.exists() or CATALOG_PATH.read_text(encoding="utf-8") != catalog_text:
|
|
249
|
+
drift.append(str(CATALOG_PATH.relative_to(ROOT)))
|
|
250
|
+
if drift:
|
|
251
|
+
print("❌ Index drift detected — regenerate with:")
|
|
252
|
+
print(" python3 scripts/generate_index.py")
|
|
253
|
+
for d in drift:
|
|
254
|
+
print(f" - {d}")
|
|
255
|
+
return 1
|
|
256
|
+
print("✅ Index files in sync.")
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
INDEX_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
INDEX_PATH.write_text(index_text, encoding="utf-8")
|
|
262
|
+
CATALOG_PATH.write_text(catalog_text, encoding="utf-8")
|
|
263
|
+
print(f"✅ Wrote {INDEX_PATH.relative_to(ROOT)} ({len(skills)} skills, "
|
|
264
|
+
f"{len(rules)} rules, {len(commands)} commands, {len(guidelines)} guidelines)")
|
|
265
|
+
print(f"✅ Wrote {CATALOG_PATH.relative_to(ROOT)} (public subset)")
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
sys.exit(main())
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate the file-ownership matrix.
|
|
3
|
+
|
|
4
|
+
Produces:
|
|
5
|
+
|
|
6
|
+
* docs/contracts/file-ownership-matrix.json (machine, internal-locked)
|
|
7
|
+
* agents/contexts/structural/file-ownership-matrix.md (human-readable)
|
|
8
|
+
|
|
9
|
+
Walks `.agent-src.uncompressed/{rules,skills,commands,contexts,personas}/`,
|
|
10
|
+
parses frontmatter for `load_context:` / `load_context_eager:`, scans
|
|
11
|
+
markdown bodies for inline links to `.md` files inside the scanned roots,
|
|
12
|
+
and emits READ_ONLY edges plus depth-2 transitive closure of load_context
|
|
13
|
+
chains. Depth-3 chains abort the build (matches the 0.2.4 nesting cap).
|
|
14
|
+
|
|
15
|
+
Contract: docs/contracts/file-ownership-matrix.md
|
|
16
|
+
Roadmap: road-to-structural-optimization.md § 0.1
|
|
17
|
+
|
|
18
|
+
Modes:
|
|
19
|
+
--check Regenerate to memory and diff against committed JSON.
|
|
20
|
+
Exit 0 if identical, 1 if drifted.
|
|
21
|
+
(default) Regenerate JSON + MD in place; exit 0 on success.
|
|
22
|
+
|
|
23
|
+
Exit codes: 0 = ok, 1 = drift (--check), 2 = depth-3 chain, 3 = internal.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import re
|
|
30
|
+
import sys
|
|
31
|
+
from dataclasses import dataclass, field
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Iterable
|
|
34
|
+
|
|
35
|
+
import yaml
|
|
36
|
+
|
|
37
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
38
|
+
SRC_ROOT = ROOT / ".agent-src.uncompressed"
|
|
39
|
+
|
|
40
|
+
SCAN_DIRS = ("rules", "skills", "commands", "contexts", "personas")
|
|
41
|
+
|
|
42
|
+
JSON_OUT = ROOT / "docs" / "contracts" / "file-ownership-matrix.json"
|
|
43
|
+
MD_OUT = ROOT / "agents" / "contexts" / "structural" / "file-ownership-matrix.md"
|
|
44
|
+
|
|
45
|
+
LINK_RE = re.compile(r"\]\(([^)]+\.md)(?:#[^)]*)?\)")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class FileEntry:
|
|
50
|
+
path: str
|
|
51
|
+
kind: str
|
|
52
|
+
rule_type: str | None = None
|
|
53
|
+
load_context: list[str] = field(default_factory=list)
|
|
54
|
+
load_context_eager: list[str] = field(default_factory=list)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class Edge:
|
|
59
|
+
source: str
|
|
60
|
+
target: str
|
|
61
|
+
type: str
|
|
62
|
+
via: str
|
|
63
|
+
depth: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _rel(p: Path) -> str:
|
|
67
|
+
return p.relative_to(ROOT).as_posix()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _kind_for(rel: str) -> str:
|
|
71
|
+
parts = rel.split("/")
|
|
72
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed":
|
|
73
|
+
return parts[1].rstrip("s") if parts[1] != "personas" else "persona"
|
|
74
|
+
return "unknown"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _parse_frontmatter(p: Path) -> dict:
|
|
78
|
+
text = p.read_text(encoding="utf-8")
|
|
79
|
+
if not text.startswith("---\n"):
|
|
80
|
+
return {}
|
|
81
|
+
end = text.find("\n---\n", 4)
|
|
82
|
+
if end == -1:
|
|
83
|
+
return {}
|
|
84
|
+
try:
|
|
85
|
+
data = yaml.safe_load(text[4:end])
|
|
86
|
+
except yaml.YAMLError:
|
|
87
|
+
return {}
|
|
88
|
+
return data if isinstance(data, dict) else {}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _collect_files(src_root: Path) -> list[Path]:
|
|
92
|
+
out: list[Path] = []
|
|
93
|
+
for sub in SCAN_DIRS:
|
|
94
|
+
d = src_root / sub
|
|
95
|
+
if d.exists():
|
|
96
|
+
out.extend(sorted(d.rglob("*.md")))
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve(target: str, src_root: Path) -> Path | None:
|
|
101
|
+
"""Resolve a path string (repo-relative or short) into an absolute Path
|
|
102
|
+
under src_root or the repo root. Return None if not under a scanned root."""
|
|
103
|
+
cand = src_root.parent / target if "/" in target else src_root / target
|
|
104
|
+
try:
|
|
105
|
+
rel = cand.resolve().relative_to(src_root.parent)
|
|
106
|
+
except ValueError:
|
|
107
|
+
return None
|
|
108
|
+
parts = rel.parts
|
|
109
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
|
|
110
|
+
return cand if cand.exists() else None
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build_matrix(src_root: Path) -> tuple[dict[str, FileEntry], list[Edge], list[str]]:
|
|
115
|
+
"""Build the file map + edge list. Returns (files, edges, depth3_chains).
|
|
116
|
+
|
|
117
|
+
depth3_chains is non-empty iff the depth invariant is violated; the
|
|
118
|
+
caller must abort with exit code 2.
|
|
119
|
+
"""
|
|
120
|
+
files: dict[str, FileEntry] = {}
|
|
121
|
+
for f in _collect_files(src_root):
|
|
122
|
+
rel = f.relative_to(src_root.parent).as_posix()
|
|
123
|
+
fm = _parse_frontmatter(f)
|
|
124
|
+
rtype = fm.get("type")
|
|
125
|
+
if isinstance(rtype, str):
|
|
126
|
+
rtype = rtype.strip('"').strip("'")
|
|
127
|
+
else:
|
|
128
|
+
rtype = None
|
|
129
|
+
lazy = fm.get("load_context") or []
|
|
130
|
+
eager = fm.get("load_context_eager") or []
|
|
131
|
+
if not isinstance(lazy, list):
|
|
132
|
+
lazy = []
|
|
133
|
+
if not isinstance(eager, list):
|
|
134
|
+
eager = []
|
|
135
|
+
files[rel] = FileEntry(
|
|
136
|
+
path=rel,
|
|
137
|
+
kind=_kind_for(rel),
|
|
138
|
+
rule_type=rtype,
|
|
139
|
+
load_context=[str(x) for x in lazy if isinstance(x, str)],
|
|
140
|
+
load_context_eager=[str(x) for x in eager if isinstance(x, str)],
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
edges: list[Edge] = []
|
|
144
|
+
for rel, entry in files.items():
|
|
145
|
+
for tgt in entry.load_context:
|
|
146
|
+
edges.append(Edge(rel, tgt, "READ_ONLY", "load_context", 1))
|
|
147
|
+
for tgt in entry.load_context_eager:
|
|
148
|
+
edges.append(Edge(rel, tgt, "READ_ONLY", "load_context_eager", 1))
|
|
149
|
+
|
|
150
|
+
# Body markdown links — only count edges to files we know about
|
|
151
|
+
for rel, entry in files.items():
|
|
152
|
+
body = (src_root.parent / rel).read_text(encoding="utf-8")
|
|
153
|
+
body = body.split("\n---\n", 1)[-1] if body.startswith("---\n") else body
|
|
154
|
+
seen_targets: set[str] = set()
|
|
155
|
+
for m in LINK_RE.finditer(body):
|
|
156
|
+
href = m.group(1).strip()
|
|
157
|
+
if href.startswith("http"):
|
|
158
|
+
continue
|
|
159
|
+
resolved = _resolve_link(rel, href, src_root)
|
|
160
|
+
if resolved is None or resolved == rel or resolved in seen_targets:
|
|
161
|
+
continue
|
|
162
|
+
if resolved in files:
|
|
163
|
+
seen_targets.add(resolved)
|
|
164
|
+
edges.append(Edge(rel, resolved, "READ_ONLY", "body_link", 1))
|
|
165
|
+
|
|
166
|
+
# Transitive closure on load_context* edges, depth 2; depth 3 aborts.
|
|
167
|
+
lc_edges_by_src: dict[str, list[str]] = {}
|
|
168
|
+
for e in edges:
|
|
169
|
+
if e.via in ("load_context", "load_context_eager"):
|
|
170
|
+
lc_edges_by_src.setdefault(e.source, []).append(e.target)
|
|
171
|
+
|
|
172
|
+
transitive: list[Edge] = []
|
|
173
|
+
depth3: list[str] = []
|
|
174
|
+
for src, lvl1_targets in lc_edges_by_src.items():
|
|
175
|
+
for t1 in lvl1_targets:
|
|
176
|
+
for t2 in lc_edges_by_src.get(t1, []):
|
|
177
|
+
if t2 == src or t2 == t1:
|
|
178
|
+
continue
|
|
179
|
+
transitive.append(Edge(src, t2, "READ_ONLY", "load_context_transitive", 2))
|
|
180
|
+
# depth-3 probe
|
|
181
|
+
for t3 in lc_edges_by_src.get(t2, []):
|
|
182
|
+
if t3 in (src, t1, t2):
|
|
183
|
+
continue
|
|
184
|
+
depth3.append(f"{src} → {t1} → {t2} → {t3}")
|
|
185
|
+
|
|
186
|
+
edges.extend(transitive)
|
|
187
|
+
for rel in files:
|
|
188
|
+
edges.append(Edge(rel, rel, "WRITE", "self", 0))
|
|
189
|
+
|
|
190
|
+
edges.sort(key=lambda e: (e.source, e.target, e.via, e.depth))
|
|
191
|
+
return files, edges, depth3
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _resolve_link(source_rel: str, href: str, src_root: Path) -> str | None:
|
|
195
|
+
"""Resolve a markdown link href (relative to source file) to a repo-relative
|
|
196
|
+
path inside a scanned root, or None."""
|
|
197
|
+
if href.startswith(".agent-src.uncompressed/") or href.startswith("agents/"):
|
|
198
|
+
cand = (src_root.parent / href).resolve()
|
|
199
|
+
else:
|
|
200
|
+
base = (src_root.parent / source_rel).parent
|
|
201
|
+
cand = (base / href).resolve()
|
|
202
|
+
try:
|
|
203
|
+
rel = cand.relative_to(src_root.parent).as_posix()
|
|
204
|
+
except ValueError:
|
|
205
|
+
return None
|
|
206
|
+
parts = rel.split("/")
|
|
207
|
+
if len(parts) >= 3 and parts[0] == ".agent-src.uncompressed" and parts[1] in SCAN_DIRS:
|
|
208
|
+
return rel if cand.exists() else None
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _to_json(files: dict[str, FileEntry], edges: list[Edge]) -> dict:
|
|
213
|
+
return {
|
|
214
|
+
"version": 1,
|
|
215
|
+
"generated_by": "scripts/generate_ownership_matrix.py",
|
|
216
|
+
"source_of_truth": ".agent-src.uncompressed/",
|
|
217
|
+
"files": {
|
|
218
|
+
rel: {
|
|
219
|
+
"kind": e.kind,
|
|
220
|
+
"rule_type": e.rule_type,
|
|
221
|
+
"load_context": e.load_context,
|
|
222
|
+
"load_context_eager": e.load_context_eager,
|
|
223
|
+
}
|
|
224
|
+
for rel, e in sorted(files.items())
|
|
225
|
+
},
|
|
226
|
+
"edges": [
|
|
227
|
+
{
|
|
228
|
+
"source": e.source,
|
|
229
|
+
"target": e.target,
|
|
230
|
+
"type": e.type,
|
|
231
|
+
"via": e.via,
|
|
232
|
+
"depth": e.depth,
|
|
233
|
+
}
|
|
234
|
+
for e in edges
|
|
235
|
+
],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _to_markdown(payload: dict) -> str:
|
|
240
|
+
lines: list[str] = [
|
|
241
|
+
"# File-ownership matrix (regenerated)",
|
|
242
|
+
"",
|
|
243
|
+
"> **Do not edit.** Regenerated by `scripts/generate_ownership_matrix.py`.",
|
|
244
|
+
"> Schema: [`docs/contracts/file-ownership-matrix.md`](../../../docs/contracts/file-ownership-matrix.md).",
|
|
245
|
+
"",
|
|
246
|
+
f"- Schema version: `{payload['version']}`",
|
|
247
|
+
f"- Source of truth: `{payload['source_of_truth']}`",
|
|
248
|
+
f"- Files indexed: **{len(payload['files'])}**",
|
|
249
|
+
f"- Edges (incl. self-WRITE): **{len(payload['edges'])}**",
|
|
250
|
+
"",
|
|
251
|
+
"## READ_ONLY edges",
|
|
252
|
+
"",
|
|
253
|
+
"| Source | Target | Via | Depth |",
|
|
254
|
+
"|---|---|---|---:|",
|
|
255
|
+
]
|
|
256
|
+
ro = [e for e in payload["edges"] if e["type"] == "READ_ONLY"]
|
|
257
|
+
for e in ro:
|
|
258
|
+
lines.append(f"| `{e['source']}` | `{e['target']}` | `{e['via']}` | {e['depth']} |")
|
|
259
|
+
if not ro:
|
|
260
|
+
lines.append("| _(none)_ | | | |")
|
|
261
|
+
lines += [
|
|
262
|
+
"",
|
|
263
|
+
"## Files by kind",
|
|
264
|
+
"",
|
|
265
|
+
"| Kind | Count |",
|
|
266
|
+
"|---|---:|",
|
|
267
|
+
]
|
|
268
|
+
counts: dict[str, int] = {}
|
|
269
|
+
for f in payload["files"].values():
|
|
270
|
+
counts[f["kind"]] = counts.get(f["kind"], 0) + 1
|
|
271
|
+
for k in sorted(counts):
|
|
272
|
+
lines.append(f"| `{k}` | {counts[k]} |")
|
|
273
|
+
lines.append("")
|
|
274
|
+
return "\n".join(lines)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _write_outputs(payload: dict, json_out: Path, md_out: Path) -> None:
|
|
278
|
+
json_out.parent.mkdir(parents=True, exist_ok=True)
|
|
279
|
+
md_out.parent.mkdir(parents=True, exist_ok=True)
|
|
280
|
+
json_out.write_text(json.dumps(payload, indent=2, sort_keys=False) + "\n", encoding="utf-8")
|
|
281
|
+
md_out.write_text(_to_markdown(payload) + "\n", encoding="utf-8")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
285
|
+
ap = argparse.ArgumentParser(description=__doc__)
|
|
286
|
+
ap.add_argument("--check", action="store_true",
|
|
287
|
+
help="Regenerate to memory and diff against committed JSON.")
|
|
288
|
+
args = ap.parse_args(list(argv) if argv is not None else None)
|
|
289
|
+
|
|
290
|
+
if not SRC_ROOT.is_dir():
|
|
291
|
+
print(f"❌ source dir missing: {SRC_ROOT}", file=sys.stderr)
|
|
292
|
+
return 3
|
|
293
|
+
|
|
294
|
+
files, edges, depth3 = build_matrix(SRC_ROOT)
|
|
295
|
+
if depth3:
|
|
296
|
+
print("❌ load_context depth-3 chain detected (limit is 2):", file=sys.stderr)
|
|
297
|
+
for chain in depth3:
|
|
298
|
+
print(f" 🔴 {chain}", file=sys.stderr)
|
|
299
|
+
return 2
|
|
300
|
+
|
|
301
|
+
payload = _to_json(files, edges)
|
|
302
|
+
|
|
303
|
+
if args.check:
|
|
304
|
+
if not JSON_OUT.exists():
|
|
305
|
+
print(f"❌ {JSON_OUT.relative_to(ROOT)} not committed; run `task generate-ownership-matrix`",
|
|
306
|
+
file=sys.stderr)
|
|
307
|
+
return 1
|
|
308
|
+
committed = json.loads(JSON_OUT.read_text(encoding="utf-8"))
|
|
309
|
+
if committed != payload:
|
|
310
|
+
print("❌ ownership matrix is stale — run `task generate-ownership-matrix` and commit",
|
|
311
|
+
file=sys.stderr)
|
|
312
|
+
return 1
|
|
313
|
+
print(f"✅ ownership matrix in sync ({len(files)} files, {len(edges)} edges)")
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
_write_outputs(payload, JSON_OUT, MD_OUT)
|
|
317
|
+
print(f"✅ wrote {JSON_OUT.relative_to(ROOT)} ({len(files)} files, {len(edges)} edges)")
|
|
318
|
+
print(f"✅ wrote {MD_OUT.relative_to(ROOT)}")
|
|
319
|
+
return 0
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
sys.exit(main())
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Augment Code lifecycle-hook trampoline for roadmap-progress-sync.
|
|
3
|
+
#
|
|
4
|
+
# Augment requires hook scripts to use the .sh extension and live at
|
|
5
|
+
# either a system path (/etc/augment/...) or user scope
|
|
6
|
+
# (~/.augment/...). This trampoline lives at user scope and dispatches
|
|
7
|
+
# every PostToolUse event to whichever workspace fired it, so a single
|
|
8
|
+
# install covers every project that has ./agent-config available.
|
|
9
|
+
#
|
|
10
|
+
# Behaviour:
|
|
11
|
+
# - Read the JSON event from stdin into a buffer.
|
|
12
|
+
# - Extract workspace_roots[0]; bail silently when missing.
|
|
13
|
+
# - cd into that workspace; bail silently when it is not a directory
|
|
14
|
+
# or does not contain ./agent-config.
|
|
15
|
+
# - Re-pipe the original JSON into
|
|
16
|
+
# ./agent-config roadmap-progress:hook --platform augment
|
|
17
|
+
# so roadmap_progress_hook.py runs the path filter and decides
|
|
18
|
+
# whether to regenerate the dashboard.
|
|
19
|
+
# - Always exit 0 — PostToolUse hooks must never block.
|
|
20
|
+
|
|
21
|
+
set -u
|
|
22
|
+
|
|
23
|
+
EVENT_DATA="$(cat)"
|
|
24
|
+
|
|
25
|
+
# Extract workspace_roots[0] using whichever JSON tool is available.
|
|
26
|
+
WORKSPACE=""
|
|
27
|
+
if command -v jq >/dev/null 2>&1; then
|
|
28
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" \
|
|
29
|
+
| jq -r '.workspace_roots[0] // empty' 2>/dev/null)"
|
|
30
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
31
|
+
WORKSPACE="$(printf '%s' "$EVENT_DATA" | python3 -c '
|
|
32
|
+
import json, sys
|
|
33
|
+
try:
|
|
34
|
+
data = json.load(sys.stdin)
|
|
35
|
+
except Exception:
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
roots = data.get("workspace_roots") or []
|
|
38
|
+
if roots:
|
|
39
|
+
print(roots[0])
|
|
40
|
+
' 2>/dev/null)"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
cd "$WORKSPACE" 2>/dev/null || exit 0
|
|
48
|
+
|
|
49
|
+
if [ ! -x ./agent-config ]; then
|
|
50
|
+
exit 0
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
printf '%s' "$EVENT_DATA" \
|
|
54
|
+
| ./agent-config roadmap-progress:hook --platform augment \
|
|
55
|
+
>/dev/null 2>&1 || true
|
|
56
|
+
|
|
57
|
+
exit 0
|