@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
package/scripts/release.py
CHANGED
|
@@ -20,10 +20,13 @@ Pipeline:
|
|
|
20
20
|
push the tag (this triggers publish-npm.yml).
|
|
21
21
|
9. GitHub Release — `gh release create X.Y.Z --notes <changelog>`.
|
|
22
22
|
|
|
23
|
-
Idempotency
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
Idempotency: pass `--resume` to recover from a partial failure. Each
|
|
24
|
+
step then probes existing state (branch, commit, PR, tag, GitHub
|
|
25
|
+
Release) and skips work that is already done, instead of erroring out.
|
|
26
|
+
Without `--resume` the pipeline still mutates git/network state, so
|
|
27
|
+
re-running on a dirty tree needs `--resume` (or a manual cleanup).
|
|
28
|
+
Each step prints what it's about to do before doing it, so a crash
|
|
29
|
+
leaves a recoverable trail.
|
|
27
30
|
|
|
28
31
|
Stdlib-only (Python 3.10+). No third-party runtime dependencies.
|
|
29
32
|
"""
|
|
@@ -183,6 +186,61 @@ def have(bin: str) -> bool:
|
|
|
183
186
|
)
|
|
184
187
|
|
|
185
188
|
|
|
189
|
+
# ─── resume-mode state probes ────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _branch_exists_local(branch: str) -> bool:
|
|
193
|
+
r = run(
|
|
194
|
+
"git", "rev-parse", "--verify", "--quiet", f"refs/heads/{branch}",
|
|
195
|
+
check=False, capture=True,
|
|
196
|
+
)
|
|
197
|
+
return r.returncode == 0
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _branch_exists_remote(branch: str) -> bool:
|
|
201
|
+
r = run(
|
|
202
|
+
"git", "ls-remote", "--exit-code", "--heads", REMOTE, branch,
|
|
203
|
+
check=False, capture=True,
|
|
204
|
+
)
|
|
205
|
+
return r.returncode == 0
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _tag_exists_local(tag: str) -> bool:
|
|
209
|
+
return tag in git("tag", "-l", tag, capture=True).splitlines()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _tag_exists_remote(tag: str) -> bool:
|
|
213
|
+
r = run(
|
|
214
|
+
"git", "ls-remote", "--exit-code", "--tags", REMOTE, tag,
|
|
215
|
+
check=False, capture=True,
|
|
216
|
+
)
|
|
217
|
+
return r.returncode == 0
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _pr_for_branch(branch: str) -> dict | None:
|
|
221
|
+
"""Most recent PR (any state) with `release/X.Y.Z` as head, or None."""
|
|
222
|
+
r = run(
|
|
223
|
+
"gh", "pr", "list",
|
|
224
|
+
"--head", branch,
|
|
225
|
+
"--state", "all",
|
|
226
|
+
"--json", "number,state,url",
|
|
227
|
+
"--limit", "1",
|
|
228
|
+
check=False, capture=True,
|
|
229
|
+
)
|
|
230
|
+
if r.returncode != 0:
|
|
231
|
+
return None
|
|
232
|
+
try:
|
|
233
|
+
items = json.loads(r.stdout or "[]")
|
|
234
|
+
except json.JSONDecodeError:
|
|
235
|
+
return None
|
|
236
|
+
return items[0] if items else None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _release_exists(tag: str) -> bool:
|
|
240
|
+
r = run("gh", "release", "view", tag, check=False, capture=True)
|
|
241
|
+
return r.returncode == 0
|
|
242
|
+
|
|
243
|
+
|
|
186
244
|
# ─── version math ─────────────────────────────────────────────────────────────
|
|
187
245
|
|
|
188
246
|
|
|
@@ -353,8 +411,21 @@ def set_marketplace_version(path: Path, version: str) -> None:
|
|
|
353
411
|
# ─── preflight ────────────────────────────────────────────────────────────────
|
|
354
412
|
|
|
355
413
|
|
|
356
|
-
def preflight(target: str) -> None:
|
|
357
|
-
"""Fail fast on conditions that would break the release mid-flight.
|
|
414
|
+
def preflight(target: str, *, resume: bool = False) -> None:
|
|
415
|
+
"""Fail fast on conditions that would break the release mid-flight.
|
|
416
|
+
|
|
417
|
+
In ``--resume`` mode two invariants are relaxed:
|
|
418
|
+
|
|
419
|
+
* The starting branch may be ``release/{target}`` in addition to
|
|
420
|
+
``main`` — both are valid resume positions (mid-pipeline crash
|
|
421
|
+
after step 1 leaves you on the release branch).
|
|
422
|
+
* The target-tag-exists check is dropped — execute() probes for
|
|
423
|
+
existing tags/releases and skips them.
|
|
424
|
+
|
|
425
|
+
Tree cleanliness, gh auth, and ``main`` in-sync with origin are
|
|
426
|
+
still enforced, so resuming has the same starting posture as a
|
|
427
|
+
fresh run; only step-level outcomes differ.
|
|
428
|
+
"""
|
|
358
429
|
for b in ("git", "gh"):
|
|
359
430
|
if not have(b):
|
|
360
431
|
die(f"{b!r} not found on PATH")
|
|
@@ -368,7 +439,14 @@ def preflight(target: str) -> None:
|
|
|
368
439
|
die("gh is not authenticated; run `gh auth login` first")
|
|
369
440
|
|
|
370
441
|
branch = git("rev-parse", "--abbrev-ref", "HEAD", capture=True)
|
|
371
|
-
|
|
442
|
+
release_branch = f"release/{target}"
|
|
443
|
+
allowed = {MAIN_BRANCH, release_branch} if resume else {MAIN_BRANCH}
|
|
444
|
+
if branch not in allowed:
|
|
445
|
+
if resume:
|
|
446
|
+
die(
|
|
447
|
+
f"resume must run from {MAIN_BRANCH!r} or {release_branch!r}, "
|
|
448
|
+
f"currently on {branch!r}"
|
|
449
|
+
)
|
|
372
450
|
die(f"release must run from {MAIN_BRANCH!r}, currently on {branch!r}")
|
|
373
451
|
|
|
374
452
|
porcelain = git("status", "--porcelain", capture=True)
|
|
@@ -380,17 +458,24 @@ def preflight(target: str) -> None:
|
|
|
380
458
|
# about to create a new tag anyway — local drift (e.g. from renamed
|
|
381
459
|
# release-please tags) should not block the fetch.
|
|
382
460
|
run("git", "fetch", REMOTE, "--tags", "--prune", "--force", capture=True)
|
|
383
|
-
local = git("rev-parse", "HEAD", capture=True)
|
|
384
|
-
remote = git("rev-parse", f"{REMOTE}/{MAIN_BRANCH}", capture=True)
|
|
385
|
-
if local != remote:
|
|
386
|
-
die(
|
|
387
|
-
f"local {MAIN_BRANCH} is not in sync with {REMOTE}/{MAIN_BRANCH}; "
|
|
388
|
-
"pull or push first"
|
|
389
|
-
)
|
|
390
461
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
462
|
+
# The local-in-sync-with-origin check only applies to main; if we're
|
|
463
|
+
# already on the release branch in resume mode, the relevant invariant
|
|
464
|
+
# is "main hasn't moved beyond what release/X.Y.Z branched off", which
|
|
465
|
+
# `git pull --ff-only` enforces in step 8 anyway.
|
|
466
|
+
if branch == MAIN_BRANCH:
|
|
467
|
+
local = git("rev-parse", "HEAD", capture=True)
|
|
468
|
+
remote = git("rev-parse", f"{REMOTE}/{MAIN_BRANCH}", capture=True)
|
|
469
|
+
if local != remote:
|
|
470
|
+
die(
|
|
471
|
+
f"local {MAIN_BRANCH} is not in sync with "
|
|
472
|
+
f"{REMOTE}/{MAIN_BRANCH}; pull or push first"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if not resume:
|
|
476
|
+
tags = git("tag", "-l", target, capture=True).splitlines()
|
|
477
|
+
if target in tags:
|
|
478
|
+
die(f"tag {target!r} already exists; nothing to release")
|
|
394
479
|
|
|
395
480
|
|
|
396
481
|
# ─── plan ─────────────────────────────────────────────────────────────────────
|
|
@@ -436,7 +521,13 @@ def _step(n: int, total: int, msg: str) -> None:
|
|
|
436
521
|
print(f"[{n}/{total}] {msg}")
|
|
437
522
|
|
|
438
523
|
|
|
439
|
-
def execute(
|
|
524
|
+
def execute(
|
|
525
|
+
plan: Plan,
|
|
526
|
+
*,
|
|
527
|
+
wait_for_checks: bool,
|
|
528
|
+
dry_run: bool,
|
|
529
|
+
resume: bool = False,
|
|
530
|
+
) -> None:
|
|
440
531
|
branch = f"release/{plan.target}"
|
|
441
532
|
total = 9
|
|
442
533
|
|
|
@@ -444,57 +535,129 @@ def execute(plan: Plan, *, wait_for_checks: bool, dry_run: bool) -> None:
|
|
|
444
535
|
print("(dry-run) no git/gh mutations will be performed.")
|
|
445
536
|
return
|
|
446
537
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
538
|
+
# Probe the world once at the top so each step skip-decision is cheap.
|
|
539
|
+
pr_info = _pr_for_branch(branch) if resume else None
|
|
540
|
+
pr_state = (pr_info or {}).get("state")
|
|
541
|
+
pr_merged = pr_state == "MERGED"
|
|
542
|
+
|
|
543
|
+
# ─── 1. branch ──────────────────────────────────────────────────────────
|
|
544
|
+
if pr_merged:
|
|
545
|
+
_step(1, total, f"PR for {branch} already merged — staying on {MAIN_BRANCH}")
|
|
546
|
+
if git("rev-parse", "--abbrev-ref", "HEAD", capture=True) != MAIN_BRANCH:
|
|
547
|
+
run("git", "checkout", MAIN_BRANCH)
|
|
548
|
+
run("git", "pull", "--ff-only", REMOTE, MAIN_BRANCH)
|
|
549
|
+
elif resume and _branch_exists_local(branch):
|
|
550
|
+
_step(1, total, f"Branch {branch} exists locally — checkout")
|
|
551
|
+
run("git", "checkout", branch)
|
|
552
|
+
elif resume and _branch_exists_remote(branch):
|
|
553
|
+
_step(1, total, f"Branch {branch} exists on {REMOTE} — fetch + checkout")
|
|
554
|
+
run("git", "fetch", REMOTE, branch)
|
|
555
|
+
run("git", "checkout", "-b", branch, f"{REMOTE}/{branch}")
|
|
556
|
+
else:
|
|
557
|
+
_step(1, total, f"Create branch {branch}")
|
|
558
|
+
run("git", "checkout", "-b", branch)
|
|
461
559
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
560
|
+
# ─── 2. file mutations ──────────────────────────────────────────────────
|
|
561
|
+
if pr_merged:
|
|
562
|
+
_step(2, total, "PR already merged — skip file bumps")
|
|
563
|
+
else:
|
|
564
|
+
current_pkg = json.loads(PACKAGE_JSON.read_text(encoding="utf-8")).get("version")
|
|
565
|
+
if resume and current_pkg == plan.target:
|
|
566
|
+
_step(2, total, f"Files already at {plan.target} — skip bump")
|
|
567
|
+
else:
|
|
568
|
+
_step(2, total, "Bump package.json + marketplace.json, prepend CHANGELOG")
|
|
569
|
+
set_package_version(PACKAGE_JSON, plan.target)
|
|
570
|
+
set_marketplace_version(MARKETPLACE_JSON, plan.target)
|
|
571
|
+
prepend_changelog(CHANGELOG, plan.changelog_entry)
|
|
572
|
+
|
|
573
|
+
# ─── 3. commit ──────────────────────────────────────────────────────────
|
|
574
|
+
if pr_merged:
|
|
575
|
+
_step(3, total, "PR already merged — skip commit")
|
|
576
|
+
else:
|
|
577
|
+
last_msg = git("log", "-1", "--format=%s", capture=True)
|
|
578
|
+
porcelain = git("status", "--porcelain", capture=True)
|
|
579
|
+
if resume and last_msg == f"release: {plan.target}" and not porcelain:
|
|
580
|
+
_step(3, total, f"Last commit already `release: {plan.target}` and tree clean — skip")
|
|
581
|
+
else:
|
|
582
|
+
_step(3, total, f"Commit `release: {plan.target}`")
|
|
583
|
+
run("git", "add", str(PACKAGE_JSON), str(MARKETPLACE_JSON), str(CHANGELOG))
|
|
584
|
+
run("git", "commit", "-m", f"release: {plan.target}")
|
|
585
|
+
|
|
586
|
+
# ─── 4. push ────────────────────────────────────────────────────────────
|
|
587
|
+
if pr_merged:
|
|
588
|
+
_step(4, total, "PR already merged — skip push")
|
|
589
|
+
else:
|
|
590
|
+
# `git push -u` is naturally idempotent — it prints "Everything
|
|
591
|
+
# up-to-date" when remote already matches. No probe needed.
|
|
592
|
+
_step(4, total, f"Push {branch} to {REMOTE}")
|
|
593
|
+
run("git", "push", "-u", REMOTE, branch)
|
|
594
|
+
|
|
595
|
+
# ─── 5. PR ──────────────────────────────────────────────────────────────
|
|
596
|
+
if pr_merged:
|
|
597
|
+
_step(5, total, f"PR #{pr_info.get('number')} already merged — skip")
|
|
598
|
+
elif resume and pr_state == "OPEN":
|
|
599
|
+
_step(5, total, f"PR already open: {pr_info.get('url')}")
|
|
600
|
+
else:
|
|
601
|
+
_step(5, total, "Open pull request")
|
|
602
|
+
pr_body = (
|
|
603
|
+
f"Release {plan.target}.\n\n"
|
|
604
|
+
f"{plan.changelog_body}\n\n"
|
|
605
|
+
"Created by `scripts/release.py`."
|
|
606
|
+
)
|
|
607
|
+
run(
|
|
608
|
+
"gh", "pr", "create",
|
|
609
|
+
"--base", MAIN_BRANCH,
|
|
610
|
+
"--head", branch,
|
|
611
|
+
"--title", f"release: {plan.target}",
|
|
612
|
+
"--body", pr_body,
|
|
613
|
+
)
|
|
475
614
|
|
|
476
|
-
|
|
615
|
+
# ─── 6. wait for checks ─────────────────────────────────────────────────
|
|
616
|
+
if pr_merged:
|
|
617
|
+
_step(6, total, "PR already merged — skip checks wait")
|
|
618
|
+
elif wait_for_checks:
|
|
477
619
|
_step(6, total, "Wait for PR checks")
|
|
478
620
|
watch_pr_checks()
|
|
479
621
|
else:
|
|
480
622
|
_step(6, total, "Skip waiting for checks (--no-wait)")
|
|
481
623
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
624
|
+
# ─── 7. merge ───────────────────────────────────────────────────────────
|
|
625
|
+
if pr_merged:
|
|
626
|
+
_step(7, total, f"PR #{pr_info.get('number')} already merged — skip")
|
|
627
|
+
else:
|
|
628
|
+
_step(7, total, "Merge pull request (merge commit) and delete branch")
|
|
629
|
+
run("gh", "pr", "merge", "--merge", "--delete-branch")
|
|
630
|
+
|
|
631
|
+
# ─── 8. tag main + push tag ─────────────────────────────────────────────
|
|
632
|
+
# Always idempotent — even outside resume mode this prevents a mid-flight
|
|
633
|
+
# crash on step 9 from leaving a half-tagged release that subsequent
|
|
634
|
+
# `task release` invocations can't recover from without `--resume`.
|
|
635
|
+
if git("rev-parse", "--abbrev-ref", "HEAD", capture=True) != MAIN_BRANCH:
|
|
636
|
+
run("git", "checkout", MAIN_BRANCH)
|
|
487
637
|
run("git", "pull", "--ff-only", REMOTE, MAIN_BRANCH)
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
638
|
+
|
|
639
|
+
if _tag_exists_local(plan.target):
|
|
640
|
+
if _tag_exists_remote(plan.target):
|
|
641
|
+
_step(8, total, f"Tag {plan.target} already on {REMOTE} — skip")
|
|
642
|
+
else:
|
|
643
|
+
_step(8, total, f"Tag {plan.target} exists locally — push only")
|
|
644
|
+
run("git", "push", REMOTE, plan.target)
|
|
645
|
+
else:
|
|
646
|
+
_step(8, total, f"Tag merge commit and push {plan.target}")
|
|
647
|
+
run("git", "tag", plan.target)
|
|
648
|
+
run("git", "push", REMOTE, plan.target)
|
|
649
|
+
|
|
650
|
+
# ─── 9. GitHub Release ──────────────────────────────────────────────────
|
|
651
|
+
if _release_exists(plan.target):
|
|
652
|
+
_step(9, total, f"GitHub Release {plan.target} already exists — skip")
|
|
653
|
+
else:
|
|
654
|
+
_step(9, total, "Create GitHub Release (triggers publish-npm on the tag)")
|
|
655
|
+
notes = plan.changelog_body or f"Release {plan.target}"
|
|
656
|
+
run(
|
|
657
|
+
"gh", "release", "create", plan.target,
|
|
658
|
+
"--title", plan.target,
|
|
659
|
+
"--notes", notes,
|
|
660
|
+
)
|
|
498
661
|
|
|
499
662
|
print()
|
|
500
663
|
print(f"✅ Released {plan.target}")
|
|
@@ -535,6 +698,15 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
|
535
698
|
"--no-wait", action="store_true",
|
|
536
699
|
help="Merge immediately without waiting for PR checks to pass.",
|
|
537
700
|
)
|
|
701
|
+
p.add_argument(
|
|
702
|
+
"--resume", action="store_true",
|
|
703
|
+
help=(
|
|
704
|
+
"Recover from a partial run. Each step probes existing state "
|
|
705
|
+
"(branch, commit, PR, tag, GitHub Release) and skips work that "
|
|
706
|
+
"is already done. Use this when an earlier `task release` "
|
|
707
|
+
"crashed mid-pipeline."
|
|
708
|
+
),
|
|
709
|
+
)
|
|
538
710
|
return p.parse_args(argv)
|
|
539
711
|
|
|
540
712
|
|
|
@@ -545,6 +717,49 @@ def resolve_bump(override: str | None, commits: list[Commit]) -> str:
|
|
|
545
717
|
return infer_bump(commits)
|
|
546
718
|
|
|
547
719
|
|
|
720
|
+
_RELEASE_BRANCH_RE = re.compile(r"^release/(\d+\.\d+\.\d+)$")
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _detect_in_flight_target() -> str | None:
|
|
724
|
+
"""Find the in-flight release target from existing release branches.
|
|
725
|
+
|
|
726
|
+
Resume mode needs to know which `release/X.Y.Z` is being recovered,
|
|
727
|
+
not what the next bump would be. The release branch name is the
|
|
728
|
+
canonical anchor: it was committed by step 1 of an earlier run and
|
|
729
|
+
is the only state guaranteed to survive a partial pipeline.
|
|
730
|
+
|
|
731
|
+
Local branches win over remote, current-branch wins over both — if
|
|
732
|
+
you ran `git checkout release/1.15.0`, that's the target. Returns
|
|
733
|
+
None if no release branch exists; caller falls back to the regular
|
|
734
|
+
bump-inference path.
|
|
735
|
+
"""
|
|
736
|
+
head = git("rev-parse", "--abbrev-ref", "HEAD", capture=True)
|
|
737
|
+
m = _RELEASE_BRANCH_RE.match(head)
|
|
738
|
+
if m:
|
|
739
|
+
return m.group(1)
|
|
740
|
+
|
|
741
|
+
local_raw = git("for-each-ref", "--format=%(refname:short)", "refs/heads/release/", capture=True)
|
|
742
|
+
candidates = [
|
|
743
|
+
m.group(1)
|
|
744
|
+
for line in local_raw.splitlines()
|
|
745
|
+
if (m := _RELEASE_BRANCH_RE.match(line.strip()))
|
|
746
|
+
]
|
|
747
|
+
remote_raw = git(
|
|
748
|
+
"for-each-ref", "--format=%(refname:short)",
|
|
749
|
+
f"refs/remotes/{REMOTE}/release/", capture=True,
|
|
750
|
+
)
|
|
751
|
+
for line in remote_raw.splitlines():
|
|
752
|
+
bare = line.strip().removeprefix(f"{REMOTE}/")
|
|
753
|
+
if (m := _RELEASE_BRANCH_RE.match(bare)):
|
|
754
|
+
candidates.append(m.group(1))
|
|
755
|
+
|
|
756
|
+
if not candidates:
|
|
757
|
+
return None
|
|
758
|
+
# Sort semver-aware so 1.10.0 > 1.9.0 (lexicographic would lose).
|
|
759
|
+
candidates.sort(key=parse_version)
|
|
760
|
+
return candidates[-1]
|
|
761
|
+
|
|
762
|
+
|
|
548
763
|
def main(argv: list[str] | None = None) -> int:
|
|
549
764
|
args = _parse_args(list(sys.argv[1:] if argv is None else argv))
|
|
550
765
|
|
|
@@ -554,11 +769,22 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
554
769
|
prev = latest_tag()
|
|
555
770
|
commits = commits_since(prev)
|
|
556
771
|
bump = resolve_bump(args.bump_override, commits)
|
|
557
|
-
|
|
772
|
+
|
|
773
|
+
# Resume mode: prefer an existing `release/X.Y.Z` over computed bump,
|
|
774
|
+
# so we don't accidentally start a 1.16.0 release while 1.15.0 is
|
|
775
|
+
# still in flight. Explicit --version still wins.
|
|
776
|
+
in_flight = _detect_in_flight_target() if args.resume else None
|
|
777
|
+
if args.explicit:
|
|
778
|
+
target = args.explicit
|
|
779
|
+
elif in_flight:
|
|
780
|
+
target = in_flight
|
|
781
|
+
print(f"(resume) detected in-flight release branch release/{in_flight}")
|
|
782
|
+
else:
|
|
783
|
+
target = bump_version(current, bump)
|
|
558
784
|
parse_version(target)
|
|
559
785
|
|
|
560
786
|
if not args.dry_run:
|
|
561
|
-
preflight(target)
|
|
787
|
+
preflight(target, resume=args.resume)
|
|
562
788
|
|
|
563
789
|
today = _date.today().isoformat()
|
|
564
790
|
full, body = render_changelog_entry(target, prev, commits, today)
|
|
@@ -572,6 +798,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
572
798
|
changelog_entry=full,
|
|
573
799
|
)
|
|
574
800
|
print_preview(plan)
|
|
801
|
+
if args.resume:
|
|
802
|
+
print("(resume) probing existing state — completed steps will be skipped.")
|
|
575
803
|
|
|
576
804
|
if args.dry_run:
|
|
577
805
|
return 0
|
|
@@ -580,7 +808,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
580
808
|
print("aborted.")
|
|
581
809
|
return 1
|
|
582
810
|
|
|
583
|
-
execute(
|
|
811
|
+
execute(
|
|
812
|
+
plan,
|
|
813
|
+
wait_for_checks=not args.no_wait,
|
|
814
|
+
dry_run=False,
|
|
815
|
+
resume=args.resume,
|
|
816
|
+
)
|
|
584
817
|
return 0
|
|
585
818
|
|
|
586
819
|
|
|
@@ -28,6 +28,26 @@
|
|
|
28
28
|
"pattern": "^[a-z][a-z0-9-]*$"
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
|
+
"cluster": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
34
|
+
"description": "Locked verb cluster this command belongs to. See docs/contracts/command-clusters.md."
|
|
35
|
+
},
|
|
36
|
+
"sub": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"pattern": "^[a-z][a-z0-9-]*$",
|
|
39
|
+
"description": "Sub-command identifier within the cluster (e.g. `ci` for `/fix ci`)."
|
|
40
|
+
},
|
|
41
|
+
"superseded_by": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^[a-z][a-z0-9-]*( [a-z][a-z0-9-]*)?$",
|
|
44
|
+
"description": "Set on deprecation shims. Format: '<cluster> <sub>' (e.g. 'fix ci'). See docs/contracts/command-clusters.md § Deprecation shim contract."
|
|
45
|
+
},
|
|
46
|
+
"deprecated_in": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$",
|
|
49
|
+
"description": "Semver release in which this command became a shim (e.g. '1.15.0')."
|
|
50
|
+
},
|
|
31
51
|
"suggestion": {
|
|
32
52
|
"type": "object",
|
|
33
53
|
"additionalProperties": false,
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"alwaysApply": {
|
|
24
24
|
"type": "boolean",
|
|
25
25
|
"description": "Optional sidecar for Cursor/Cline; by convention true when type=always, false when type=auto."
|
|
26
|
+
},
|
|
27
|
+
"load_context": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {"type": "string", "pattern": "\\.md$"},
|
|
30
|
+
"description": "Lazy on-demand context references. Path rules and budget caps enforced by scripts/lint_load_context.py. Contract: docs/contracts/load-context-schema.md."
|
|
31
|
+
},
|
|
32
|
+
"load_context_eager": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": {"type": "string", "pattern": "\\.md$"},
|
|
35
|
+
"description": "Eager auto-loaded context references. Counts against the per-rule char budget; enforced by scripts/lint_load_context.py."
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
}
|
package/scripts/skill_linter.py
CHANGED
|
@@ -136,9 +136,7 @@ def read_text(path: Path) -> str:
|
|
|
136
136
|
# --- Role-contract anchor cache (see road-to-role-modes Phase 1) ---
|
|
137
137
|
# Populated lazily so the linter stays fast when the guideline is absent.
|
|
138
138
|
_ROLE_CONTRACT_CANDIDATES = (
|
|
139
|
-
Path("
|
|
140
|
-
Path(".agent-src/guidelines/agent-infra/role-contracts.md"),
|
|
141
|
-
Path(".augment/guidelines/agent-infra/role-contracts.md"),
|
|
139
|
+
Path("docs/guidelines/agent-infra/role-contracts.md"),
|
|
142
140
|
)
|
|
143
141
|
_ROLE_CONTRACT_SLUGS_CACHE: Optional[set[str]] = None
|
|
144
142
|
|
|
@@ -373,7 +371,8 @@ def lint_skill(path: Path, text: str) -> LintResult:
|
|
|
373
371
|
|
|
374
372
|
if description:
|
|
375
373
|
if len(description) > 200:
|
|
376
|
-
issues.append(Issue("
|
|
374
|
+
issues.append(Issue("error", "description_too_long",
|
|
375
|
+
f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
377
376
|
for pattern in TRIGGER_WARNING_PATTERNS:
|
|
378
377
|
if re.search(pattern, description, re.IGNORECASE):
|
|
379
378
|
issues.append(Issue("warning", "weak_trigger", f"Description looks too generic: {description}"))
|
|
@@ -716,6 +715,12 @@ def lint_rule(path: Path, text: str) -> LintResult:
|
|
|
716
715
|
if not description:
|
|
717
716
|
issues.append(Issue("error", "auto_missing_description", "Auto rules require a 'description' field for matching"))
|
|
718
717
|
|
|
718
|
+
# description length cap (F6 — 200-char hard cap, see road-to-governance-cleanup)
|
|
719
|
+
rule_description = extract_description(text)
|
|
720
|
+
if rule_description and len(rule_description) > 200:
|
|
721
|
+
issues.append(Issue("error", "description_too_long",
|
|
722
|
+
f"Description is {len(rule_description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
723
|
+
|
|
719
724
|
# always-rules that look like auto candidates (rule-type-governance check)
|
|
720
725
|
if rule_type == "always":
|
|
721
726
|
description = extract_description(text) or ""
|
|
@@ -863,10 +868,27 @@ def lint_command(path: Path, text: str) -> LintResult:
|
|
|
863
868
|
description = extract_description(text)
|
|
864
869
|
if not description:
|
|
865
870
|
issues.append(Issue("warning", "missing_description", "Frontmatter description is missing"))
|
|
871
|
+
elif len(description) > 200:
|
|
872
|
+
issues.append(Issue("error", "description_too_long",
|
|
873
|
+
f"Description is {len(description)} chars (hard cap: 200) — see road-to-governance-cleanup F6"))
|
|
866
874
|
|
|
867
875
|
# suggestion block (road-to-context-aware-command-suggestion Phase 2)
|
|
868
876
|
issues.extend(_lint_command_suggestion_block(text))
|
|
869
877
|
|
|
878
|
+
# deprecation-shim warning line (P0.8b — command-clusters contract)
|
|
879
|
+
if "superseded_by:" in frontmatter:
|
|
880
|
+
shim_warning = re.search(
|
|
881
|
+
r"⚠️\s+/[a-z][a-z0-9-]*\s+is deprecated;\s+use\s+/[a-z][a-z0-9 -]+\s+instead",
|
|
882
|
+
text,
|
|
883
|
+
)
|
|
884
|
+
if not shim_warning:
|
|
885
|
+
issues.append(Issue(
|
|
886
|
+
"error", "shim_missing_warning",
|
|
887
|
+
"Deprecation shim must contain a one-line warning matching "
|
|
888
|
+
"'⚠️ /<old-name> is deprecated; use /<cluster> <sub> instead.'"
|
|
889
|
+
" (see docs/contracts/command-clusters.md § Deprecation shim contract)"
|
|
890
|
+
))
|
|
891
|
+
|
|
870
892
|
# --- Structure checks ---
|
|
871
893
|
if not H1_PATTERN.search(text):
|
|
872
894
|
issues.append(Issue("error", "missing_h1", "Command is missing an H1 heading (# Title)"))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""Sync `.agent-settings.yml` against the template + profile.
|
|
3
3
|
|
|
4
4
|
Applies the section-aware merge rules documented in
|
|
5
|
-
|
|
5
|
+
`docs/guidelines/agent-infra/layered-settings.md`:
|
|
6
6
|
|
|
7
7
|
- Template section order always wins — reorder keys to match.
|
|
8
8
|
- Existing user scalar values are preserved verbatim (as parsed).
|
package/scripts/update_counts.py
CHANGED
|
@@ -28,8 +28,9 @@ def count(kind: str) -> int:
|
|
|
28
28
|
if kind == "skills":
|
|
29
29
|
return sum(1 for _ in (SRC / "skills").rglob("SKILL.md"))
|
|
30
30
|
if kind == "guidelines":
|
|
31
|
-
# guidelines
|
|
32
|
-
|
|
31
|
+
# Guidelines live under docs/guidelines/{topic}/ — they are reference
|
|
32
|
+
# material, not packaged artefacts. Recursive walk to count every .md.
|
|
33
|
+
return sum(1 for _ in (REPO_ROOT / "docs" / "guidelines").rglob("*.md"))
|
|
33
34
|
if kind == "personas":
|
|
34
35
|
# personas live as flat .md files, README excluded
|
|
35
36
|
pdir = SRC / "personas"
|
|
@@ -47,12 +48,16 @@ TARGETS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
47
48
|
[
|
|
48
49
|
(r"(Browse all )(\d+)( commands\])", "commands"),
|
|
49
50
|
(r"(package \(rules \+ )(\d+)( skills)", "skills"),
|
|
50
|
-
(r"(skills \+ )(\d+)( native commands)", "commands"),
|
|
51
51
|
# Hero line: **NNN Skills** · **NNN Rules** · **NNN Commands** · **NNN Guidelines**
|
|
52
52
|
(r"(<strong>)(\d+)( Skills</strong>)", "skills"),
|
|
53
53
|
(r"(<strong>)(\d+)( Rules</strong>)", "rules"),
|
|
54
|
-
(r"(<strong>)(\d+)( Commands</strong>)", "commands"),
|
|
55
54
|
(r"(<strong>)(\d+)( Guidelines</strong>)", "guidelines"),
|
|
55
|
+
# NOTE: hero `<strong>N Commands</strong>` and tools-blurb
|
|
56
|
+
# `skills + N native commands` are owned by
|
|
57
|
+
# `check_command_count_messaging.py` (Phase-1.2 of
|
|
58
|
+
# road-to-pr-34-followups). Those surfaces advertise the
|
|
59
|
+
# **active** command count (total − deprecation shims), not
|
|
60
|
+
# the raw file count this script computes.
|
|
56
61
|
],
|
|
57
62
|
),
|
|
58
63
|
(
|
|
@@ -72,6 +77,16 @@ TARGETS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
72
77
|
(r"(Browse all )(\d+)( commands\])", "commands"),
|
|
73
78
|
],
|
|
74
79
|
),
|
|
80
|
+
(
|
|
81
|
+
"docs/architecture.md",
|
|
82
|
+
[
|
|
83
|
+
# "What's inside" table: | **Skills** | NNN | … |
|
|
84
|
+
(r"(\| \*\*Skills\*\* \| )(\d+)( \|)", "skills"),
|
|
85
|
+
(r"(\| \*\*Rules\*\* \| )(\d+)( \|)", "rules"),
|
|
86
|
+
(r"(\| \*\*Commands\*\* \| )(\d+)( \|)", "commands"),
|
|
87
|
+
(r"(\| \*\*Guidelines\*\* \| )(\d+)( \|)", "guidelines"),
|
|
88
|
+
],
|
|
89
|
+
),
|
|
75
90
|
# Note: ``agents/roadmaps/road-to-stronger-skills.md`` was previously
|
|
76
91
|
# tracked here with a living ``baseline N as of`` pattern. The roadmap
|
|
77
92
|
# was moved to ``skipped/`` on 2026-04-23 (Q35 superseded), so its
|