@event4u/agent-config 1.13.0 → 1.14.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.
Files changed (252) hide show
  1. package/.agent-src/commands/agent-handoff.md +3 -0
  2. package/.agent-src/commands/agent-status.md +3 -0
  3. package/.agent-src/commands/agents-audit.md +4 -0
  4. package/.agent-src/commands/agents-cleanup.md +6 -1
  5. package/.agent-src/commands/agents-prepare.md +3 -0
  6. package/.agent-src/commands/analyze-reference-repo.md +4 -0
  7. package/.agent-src/commands/bug-fix.md +5 -1
  8. package/.agent-src/commands/bug-investigate.md +4 -0
  9. package/.agent-src/commands/chat-history-checkpoint.md +126 -0
  10. package/.agent-src/commands/chat-history-clear.md +5 -0
  11. package/.agent-src/commands/chat-history-resume.md +5 -0
  12. package/.agent-src/commands/chat-history.md +5 -0
  13. package/.agent-src/commands/check-current-md.md +126 -0
  14. package/.agent-src/commands/commit-in-chunks.md +98 -0
  15. package/.agent-src/commands/commit.md +4 -0
  16. package/.agent-src/commands/compress.md +3 -0
  17. package/.agent-src/commands/context-create.md +4 -0
  18. package/.agent-src/commands/context-refactor.md +4 -0
  19. package/.agent-src/commands/copilot-agents-init.md +3 -0
  20. package/.agent-src/commands/copilot-agents-optimize.md +3 -0
  21. package/.agent-src/commands/create-pr-description.md +4 -0
  22. package/.agent-src/commands/create-pr.md +4 -0
  23. package/.agent-src/commands/do-and-judge.md +4 -1
  24. package/.agent-src/commands/do-in-steps.md +3 -0
  25. package/.agent-src/commands/e2e-heal.md +4 -0
  26. package/.agent-src/commands/e2e-plan.md +4 -0
  27. package/.agent-src/commands/estimate-ticket.md +4 -1
  28. package/.agent-src/commands/feature-dev.md +4 -0
  29. package/.agent-src/commands/feature-explore.md +4 -0
  30. package/.agent-src/commands/feature-plan.md +4 -0
  31. package/.agent-src/commands/feature-refactor.md +4 -0
  32. package/.agent-src/commands/feature-roadmap.md +6 -0
  33. package/.agent-src/commands/fix-ci.md +4 -0
  34. package/.agent-src/commands/fix-portability.md +3 -0
  35. package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
  36. package/.agent-src/commands/fix-pr-comments.md +4 -0
  37. package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
  38. package/.agent-src/commands/fix-references.md +3 -0
  39. package/.agent-src/commands/fix-seeder.md +4 -0
  40. package/.agent-src/commands/implement-ticket.md +39 -13
  41. package/.agent-src/commands/jira-ticket.md +4 -0
  42. package/.agent-src/commands/judge.md +3 -0
  43. package/.agent-src/commands/memory-add.md +5 -3
  44. package/.agent-src/commands/memory-full.md +5 -2
  45. package/.agent-src/commands/memory-promote.md +7 -6
  46. package/.agent-src/commands/mode.md +3 -0
  47. package/.agent-src/commands/module-create.md +4 -0
  48. package/.agent-src/commands/module-explore.md +4 -0
  49. package/.agent-src/commands/onboard.md +24 -0
  50. package/.agent-src/commands/optimize-agents.md +4 -0
  51. package/.agent-src/commands/optimize-augmentignore.md +3 -0
  52. package/.agent-src/commands/optimize-rtk-filters.md +3 -0
  53. package/.agent-src/commands/optimize-skills.md +4 -0
  54. package/.agent-src/commands/override-create.md +4 -0
  55. package/.agent-src/commands/override-manage.md +4 -0
  56. package/.agent-src/commands/package-reset.md +3 -0
  57. package/.agent-src/commands/package-test.md +3 -0
  58. package/.agent-src/commands/prepare-for-review.md +4 -0
  59. package/.agent-src/commands/project-analyze.md +4 -0
  60. package/.agent-src/commands/project-health.md +4 -0
  61. package/.agent-src/commands/propose-memory.md +6 -8
  62. package/.agent-src/commands/quality-fix.md +4 -0
  63. package/.agent-src/commands/refine-ticket.md +4 -1
  64. package/.agent-src/commands/review-changes.md +4 -0
  65. package/.agent-src/commands/review-routing.md +4 -0
  66. package/.agent-src/commands/roadmap-create.md +7 -0
  67. package/.agent-src/commands/roadmap-execute.md +12 -1
  68. package/.agent-src/commands/rule-compliance-audit.md +4 -0
  69. package/.agent-src/commands/set-cost-profile.md +3 -0
  70. package/.agent-src/commands/sync-agent-settings.md +3 -0
  71. package/.agent-src/commands/sync-gitignore.md +3 -0
  72. package/.agent-src/commands/tests-create.md +4 -0
  73. package/.agent-src/commands/tests-execute.md +4 -0
  74. package/.agent-src/commands/threat-model.md +4 -0
  75. package/.agent-src/commands/update-form-request-messages.md +4 -0
  76. package/.agent-src/commands/upstream-contribute.md +4 -0
  77. package/.agent-src/commands/work.md +161 -0
  78. package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
  79. package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
  80. package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
  81. package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
  82. package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
  83. package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
  84. package/.agent-src/personas/README.md +0 -1
  85. package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
  86. package/.agent-src/rules/artifact-engagement-recording.md +133 -0
  87. package/.agent-src/rules/ask-when-uncertain.md +18 -13
  88. package/.agent-src/rules/augment-portability.md +8 -0
  89. package/.agent-src/rules/autonomous-execution.md +158 -0
  90. package/.agent-src/rules/chat-history.md +147 -118
  91. package/.agent-src/rules/cli-output-handling.md +26 -3
  92. package/.agent-src/rules/command-suggestion.md +133 -0
  93. package/.agent-src/rules/commit-policy.md +99 -0
  94. package/.agent-src/rules/direct-answers.md +114 -0
  95. package/.agent-src/rules/docs-sync.md +36 -0
  96. package/.agent-src/rules/downstream-changes.md +10 -9
  97. package/.agent-src/rules/improve-before-implement.md +9 -6
  98. package/.agent-src/rules/language-and-tone.md +81 -6
  99. package/.agent-src/rules/non-destructive-by-default.md +117 -0
  100. package/.agent-src/rules/package-ci-checks.md +4 -0
  101. package/.agent-src/rules/preservation-guard.md +20 -0
  102. package/.agent-src/rules/roadmap-progress-sync.md +103 -30
  103. package/.agent-src/rules/scope-control.md +42 -1
  104. package/.agent-src/rules/size-enforcement.md +1 -3
  105. package/.agent-src/rules/skill-quality.md +3 -8
  106. package/.agent-src/rules/ui-audit-before-build.md +106 -0
  107. package/.agent-src/rules/user-interaction.md +82 -50
  108. package/.agent-src/scripts/update_roadmap_progress.py +17 -5
  109. package/.agent-src/skills/blade-ui/SKILL.md +30 -5
  110. package/.agent-src/skills/command-routing/SKILL.md +32 -0
  111. package/.agent-src/skills/command-writing/SKILL.md +41 -2
  112. package/.agent-src/skills/description-assist/SKILL.md +21 -0
  113. package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
  114. package/.agent-src/skills/existing-ui-audit/SKILL.md +187 -0
  115. package/.agent-src/skills/fe-design/SKILL.md +72 -60
  116. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
  117. package/.agent-src/skills/flux/SKILL.md +31 -4
  118. package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
  119. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
  120. package/.agent-src/skills/livewire/SKILL.md +30 -4
  121. package/.agent-src/skills/md-language-check/SKILL.md +103 -0
  122. package/.agent-src/skills/php-coder/SKILL.md +24 -0
  123. package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
  124. package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
  125. package/.agent-src/skills/refine-ticket/SKILL.md +2 -4
  126. package/.agent-src/skills/roadmap-management/SKILL.md +10 -3
  127. package/.agent-src/skills/rule-writing/SKILL.md +23 -1
  128. package/.agent-src/skills/skill-writing/SKILL.md +1 -3
  129. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
  130. package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
  131. package/.agent-src/templates/AGENTS.md +24 -6
  132. package/.agent-src/templates/agent-settings.md +149 -0
  133. package/.agent-src/templates/roadmaps.md +8 -2
  134. package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
  135. package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
  136. package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
  137. package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
  138. package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
  139. package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
  140. package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
  141. package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
  142. package/.agent-src/templates/scripts/telemetry_record.py +166 -0
  143. package/.agent-src/templates/scripts/telemetry_report.py +161 -0
  144. package/.agent-src/templates/scripts/telemetry_status.py +142 -0
  145. package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
  146. package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
  147. package/.agent-src/templates/scripts/work_engine/cli.py +592 -0
  148. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +7 -0
  149. package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
  150. package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
  151. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
  152. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +2 -2
  153. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +1 -1
  154. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +1 -1
  155. package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
  156. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +36 -4
  157. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
  158. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
  159. package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
  160. package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
  161. package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
  162. package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
  163. package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
  164. package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
  165. package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
  166. package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
  167. package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
  168. package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
  169. package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
  170. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
  171. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
  172. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
  173. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
  174. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
  175. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
  176. package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
  177. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
  178. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
  179. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
  180. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
  181. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
  182. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
  183. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
  184. package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
  185. package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
  186. package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
  188. package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
  189. package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
  190. package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
  191. package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
  192. package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
  193. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
  194. package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
  195. package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
  196. package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
  197. package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +199 -0
  198. package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
  199. package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
  200. package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
  201. package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
  202. package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
  203. package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
  204. package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
  205. package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
  206. package/.agent-src/templates/scripts/work_engine/state.py +641 -0
  207. package/.claude-plugin/marketplace.json +105 -2
  208. package/AGENTS.md +36 -8
  209. package/CHANGELOG.md +534 -0
  210. package/README.md +125 -4
  211. package/config/agent-settings.template.yml +45 -0
  212. package/config/gitignore-block.txt +4 -0
  213. package/docs/architecture.md +28 -1
  214. package/docs/development.md +1 -1
  215. package/docs/getting-started.md +2 -2
  216. package/docs/installation.md +86 -0
  217. package/docs/showcase.md +204 -0
  218. package/package.json +1 -1
  219. package/scripts/agent-config +199 -0
  220. package/scripts/audit_cloud_compatibility.py +288 -0
  221. package/scripts/build_cloud_bundle.py +458 -0
  222. package/scripts/build_linear_digest.py +263 -0
  223. package/scripts/chat_history.py +796 -7
  224. package/scripts/check_compression.py +139 -0
  225. package/scripts/check_iron_law_prominence.py +143 -0
  226. package/scripts/check_md_language.py +159 -0
  227. package/scripts/check_portability.py +36 -0
  228. package/scripts/check_reply_consistency.py +140 -0
  229. package/scripts/command_suggester/__init__.py +51 -0
  230. package/scripts/command_suggester/cooldown.py +132 -0
  231. package/scripts/command_suggester/loader.py +70 -0
  232. package/scripts/command_suggester/match.py +180 -0
  233. package/scripts/command_suggester/rank.py +120 -0
  234. package/scripts/command_suggester/render.py +86 -0
  235. package/scripts/command_suggester/sanitize.py +113 -0
  236. package/scripts/command_suggester/settings.py +125 -0
  237. package/scripts/command_suggester/types.py +78 -0
  238. package/scripts/hooks/augment-chat-history.sh +56 -0
  239. package/scripts/install-hooks.sh +67 -0
  240. package/scripts/install.py +150 -33
  241. package/scripts/lint_marketplace.py +27 -0
  242. package/scripts/migrate_command_suggestions.py +151 -0
  243. package/scripts/schemas/command.schema.json +41 -0
  244. package/scripts/skill_linter.py +67 -0
  245. package/scripts/sync_agent_settings.py +42 -12
  246. package/templates/consumer-settings/augment-cli-hooks.json +54 -0
  247. package/templates/consumer-settings/claude-settings.json +55 -1
  248. package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
  249. package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
  250. package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
  251. package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
  252. /package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +0 -0
@@ -0,0 +1,458 @@
1
+ #!/usr/bin/env python3
2
+ """build_cloud_bundle.py — package skills as Anthropic Skills ZIP bundles.
3
+
4
+ # SPEC
5
+
6
+ ## Purpose
7
+
8
+ Package each skill from `.agent-src/skills/<name>/` as a ZIP file ready
9
+ for upload to the Anthropic Skills API or Claude.ai Web (Settings →
10
+ Customize → Skills). One ZIP per skill, sandbox-friendly.
11
+
12
+ ## Inputs
13
+
14
+ - `.agent-src/skills/<name>/SKILL.md` (required)
15
+ - Optional siblings: `references/`, `assets/`, `scripts/`, `evals/`
16
+ (only the first three are bundled; `evals/` is local-tooling-only)
17
+ - Tier classification from `audit_cloud_compatibility.py` (matched by
18
+ skill basename — uncompressed and compressed share names)
19
+
20
+ ## Outputs
21
+
22
+ - `dist/cloud/<skill-name>.zip` per processed skill, layout inside ZIP:
23
+ - `<skill-name>/SKILL.md` rewritten frontmatter + body
24
+ - `<skill-name>/references/...` copied verbatim if present
25
+ - `<skill-name>/assets/...` copied verbatim if present
26
+ - `dist/cloud/manifest.json` per-skill build report
27
+
28
+ ## Tier handling
29
+
30
+ | Tier | Action |
31
+ |-------|--------------------------------------------------------------|
32
+ | T1 | Bundle as-is. Pure guidance. |
33
+ | T2 | Bundle with sandbox path-swap header. |
34
+ | T3-S | Bundle with sandbox path-swap; optional script calls degrade.|
35
+ | T3-H | Skip with explicit log. Manifest records the reason. |
36
+
37
+ ## Cloud-safe markers (Phase 2)
38
+
39
+ A source file can declare a cloud variant via an HTML comment in the
40
+ body:
41
+
42
+ <!-- cloud_safe: noop --> local rule, fully inert on cloud
43
+ <!-- cloud_safe: degrade --> prose fallback provided
44
+
45
+ `audit_cloud_compatibility.py` downgrades the tier when a marker is
46
+ present (noop → T1, degrade → T3-S). The builder additionally extracts
47
+ a `## Cloud Behavior` section for `noop` artefacts so the cloud bundle
48
+ ships only the cloud-side instructions, not the full local rule.
49
+
50
+ ## Frontmatter rewriting
51
+
52
+ - Keep: `name`, `description`. Drop everything else (e.g. `source`).
53
+ - Cloud cap: `description` ≤ 200 chars (Claude.ai Web truncates there).
54
+ - `--strict-budget`: violation → exit 2
55
+ - default: truncate at last word boundary < 200, append `…`, warn
56
+ - Source-side hard error: > 1024 chars (Anthropic spec max) → exit 3
57
+
58
+ ## Sandbox path-swap
59
+
60
+ Body text is preprocessed:
61
+ - Literal `.agent-src.uncompressed/` and `.agent-src/` → `source/` note
62
+ - Literal `agents/` (path prefix only, not prose) → `(local-only)` note
63
+ - Cloud header prepended explaining the constraint
64
+
65
+ ## CLI
66
+
67
+ build_cloud_bundle.py --skill <name> # one skill
68
+ build_cloud_bundle.py --all # every eligible skill
69
+ build_cloud_bundle.py --check # validate invariants, no zip
70
+ build_cloud_bundle.py --out <dir> # default: dist/cloud
71
+ build_cloud_bundle.py --strict-budget # description > 200 → fail
72
+
73
+ ## Exit codes
74
+
75
+ 0 ok
76
+ 2 description over 200 chars (strict mode)
77
+ 3 description over 1024 chars (always fatal)
78
+ 4 skill not found (--skill <name>)
79
+ 5 T3-H skill explicitly requested with --skill
80
+ 9 usage / argparse error
81
+ """
82
+ from __future__ import annotations
83
+
84
+ import argparse
85
+ import json
86
+ import re
87
+ import shutil
88
+ import sys
89
+ import zipfile
90
+ from dataclasses import dataclass, field
91
+ from pathlib import Path
92
+
93
+ # Local import: tier classifier
94
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
95
+ import audit_cloud_compatibility as audit # noqa: E402
96
+
97
+ ROOT = Path(__file__).resolve().parent.parent
98
+ SOURCE_SKILLS = ROOT / ".agent-src" / "skills"
99
+ DEFAULT_OUT = ROOT / "dist" / "cloud"
100
+ DESC_LIMIT_WEB = 200
101
+ DESC_LIMIT_SPEC = 1024
102
+
103
+ FRONTMATTER_RE = re.compile(r"^---\s*\n(.*?\n)---\s*\n(.*)$", re.DOTALL)
104
+ NAME_RE = re.compile(r"^name:\s*(.+?)\s*$", re.MULTILINE)
105
+ DESC_RE = re.compile(r'^description:\s*"?(.+?)"?\s*$', re.MULTILINE)
106
+ CLOUD_BEHAVIOR_RE = re.compile(
107
+ r"(?ms)^##\s+Cloud Behavior\s*\n(.*?)(?=^##\s+|\Z)"
108
+ )
109
+ TITLE_RE = re.compile(r"(?m)^#\s+(.+?)\s*$")
110
+ MARKER_LINE_RE = re.compile(
111
+ r"(?m)^\s*<!--\s*cloud_safe:\s*(?:noop|degrade)\s*-->\s*\n?"
112
+ )
113
+
114
+ # Body preprocessing — sandbox path-swap.
115
+ #
116
+ # Scope: only package-internal prefixes that are unreachable from a cloud
117
+ # sandbox (`.agent-src.uncompressed/`, `.agent-src/`). `agents/` is left
118
+ # unchanged — it lives in the user's repo, the SANDBOX_NOTE header
119
+ # already tells the agent the host has no access.
120
+ PATH_SWAP_PATTERNS = [
121
+ (re.compile(r"`\.agent-src\.uncompressed/"), "`<package-source>/"),
122
+ (re.compile(r"`\.agent-src/"), "`<package-source>/"),
123
+ (re.compile(r"\(\.agent-src\.uncompressed/"), "(<package-source>/"),
124
+ (re.compile(r"\(\.agent-src/"), "(<package-source>/"),
125
+ ]
126
+
127
+ SANDBOX_NOTE = """\
128
+ > **Cloud sandbox.** This skill is running on Claude.ai Web or the
129
+ > Anthropic Skills API. The host has no access to the user's repository.
130
+ > References to `.agent-src/`, `agents/`, or local task commands are
131
+ > descriptive: emit content for the user to save, don't try to read or
132
+ > write those paths. Quality scripts (`task ci`, linters) run on the
133
+ > user's machine after they apply the suggested change.
134
+ """
135
+
136
+ NOOP_BODY_FALLBACK = """\
137
+ On platforms without persistent filesystem (Claude.ai Web, the Anthropic
138
+ Skills API), this artefact is fully inert. None of its local procedures
139
+ apply. The agent does nothing on this rule's behalf.
140
+ """
141
+
142
+
143
+ @dataclass
144
+ class BuildResult:
145
+ skill: str
146
+ status: str # "ok" | "skipped" | "error"
147
+ tier: str = ""
148
+ reason: str = ""
149
+ zip_path: str = ""
150
+ description_truncated: bool = False
151
+ cloud_marker: str = "" # "noop" | "degrade" | ""
152
+ warnings: list[str] = field(default_factory=list)
153
+
154
+
155
+ def load_tier_map() -> dict[str, dict]:
156
+ """skill-name → {tier, cloud_marker, raw_tier} from audit script."""
157
+ tier_map: dict[str, dict] = {}
158
+ for row in audit.scan():
159
+ if row["kind"] != "skills":
160
+ continue
161
+ # row["path"] = .agent-src.uncompressed/skills/<name>/SKILL.md
162
+ parts = Path(row["path"]).parts
163
+ if len(parts) >= 3:
164
+ tier_map[parts[2]] = {
165
+ "tier": row["tier"],
166
+ "cloud_marker": row.get("cloud_marker"),
167
+ "raw_tier": row.get("raw_tier", row["tier"]),
168
+ }
169
+ return tier_map
170
+
171
+
172
+
173
+ def parse_skill_md(text: str) -> tuple[dict, str]:
174
+ """Extract frontmatter (name, description) and body."""
175
+ m = FRONTMATTER_RE.match(text)
176
+ if not m:
177
+ raise ValueError("SKILL.md missing YAML frontmatter")
178
+ fm_raw, body = m.group(1), m.group(2)
179
+ nm = NAME_RE.search(fm_raw)
180
+ dm = DESC_RE.search(fm_raw)
181
+ if not nm or not dm:
182
+ raise ValueError("frontmatter requires both 'name' and 'description'")
183
+ return {"name": nm.group(1), "description": dm.group(1)}, body
184
+
185
+
186
+ def enforce_description_budget(
187
+ desc: str, *, strict: bool, warnings: list[str]
188
+ ) -> tuple[str, bool]:
189
+ """Apply 1024 hard cap and 200 cloud cap. Returns (description, truncated)."""
190
+ if len(desc) > DESC_LIMIT_SPEC:
191
+ raise SystemExit(
192
+ f"❌ description exceeds Anthropic spec limit "
193
+ f"({len(desc)} > {DESC_LIMIT_SPEC} chars). Source must be fixed."
194
+ )
195
+ if len(desc) <= DESC_LIMIT_WEB:
196
+ return desc, False
197
+ if strict:
198
+ raise SystemExit(
199
+ f"❌ description exceeds cloud cap in strict mode "
200
+ f"({len(desc)} > {DESC_LIMIT_WEB} chars)."
201
+ )
202
+ cut = desc[: DESC_LIMIT_WEB - 1].rsplit(" ", 1)[0].rstrip(",.;:—–-")
203
+ truncated = cut + "…"
204
+ warnings.append(
205
+ f"description truncated: {len(desc)} → {len(truncated)} chars"
206
+ )
207
+ return truncated, True
208
+
209
+
210
+ def swap_paths(body: str) -> str:
211
+ """Sandbox path-swap on body literals."""
212
+ for pat, repl in PATH_SWAP_PATTERNS:
213
+ body = pat.sub(repl, body)
214
+ return body
215
+
216
+
217
+ def strip_marker(body: str) -> str:
218
+ """Remove the `<!-- cloud_safe: ... -->` line from the body."""
219
+ return MARKER_LINE_RE.sub("", body, count=1)
220
+
221
+
222
+ def extract_cloud_body_for_noop(body: str, name: str) -> str:
223
+ """Build a stripped body for a noop artefact: title + Cloud Behavior section.
224
+
225
+ If the source has a `## Cloud Behavior` section, use it. Otherwise fall
226
+ back to a generic noop notice. The returned body always opens with a
227
+ title heading so the bundle reads as a self-contained skill.
228
+ """
229
+ title_match = TITLE_RE.search(body)
230
+ title = title_match.group(1) if title_match else name
231
+ section = CLOUD_BEHAVIOR_RE.search(body)
232
+ section_text = section.group(1).strip() if section else NOOP_BODY_FALLBACK
233
+ return f"# {title}\n\n## Cloud Behavior\n\n{section_text.strip()}\n"
234
+
235
+
236
+ def render_skill_md(
237
+ name: str,
238
+ description: str,
239
+ body: str,
240
+ *,
241
+ swap: bool,
242
+ cloud_marker: str | None = None,
243
+ ) -> str:
244
+ """Rebuild SKILL.md with cloud-friendly frontmatter and tier-aware body.
245
+
246
+ - cloud_marker == 'noop' → body replaced with stripped Cloud Behavior
247
+ - swap (T2 / T3-S / degrade) → sandbox note + path-swap on full body
248
+ - otherwise → body shipped verbatim (T1)
249
+ """
250
+ body = strip_marker(body)
251
+ if cloud_marker == "noop":
252
+ body = extract_cloud_body_for_noop(body, name)
253
+ body = SANDBOX_NOTE + "\n" + body
254
+ elif swap:
255
+ body = swap_paths(body)
256
+ body = SANDBOX_NOTE + "\n" + body
257
+ fm = f'---\nname: {name}\ndescription: "{description}"\n---\n'
258
+ if not body.startswith("\n"):
259
+ body = "\n" + body
260
+ return fm + body
261
+
262
+
263
+ def build_skill_zip(
264
+ skill_dir: Path,
265
+ out_dir: Path,
266
+ tier: str,
267
+ *,
268
+ strict: bool,
269
+ dry_run: bool,
270
+ cloud_marker: str | None = None,
271
+ ) -> BuildResult:
272
+ name = skill_dir.name
273
+ result = BuildResult(skill=name, status="ok", tier=tier)
274
+ if cloud_marker:
275
+ result.cloud_marker = cloud_marker
276
+ skill_md = skill_dir / "SKILL.md"
277
+ if not skill_md.is_file():
278
+ result.status = "error"
279
+ result.reason = "SKILL.md missing"
280
+ return result
281
+
282
+ text = skill_md.read_text(encoding="utf-8")
283
+ try:
284
+ meta, body = parse_skill_md(text)
285
+ except ValueError as e:
286
+ result.status = "error"
287
+ result.reason = str(e)
288
+ return result
289
+ # If caller didn't pass a marker, detect it from the raw body
290
+ # (covers ad-hoc test fixtures).
291
+ if cloud_marker is None:
292
+ cloud_marker = audit.detect_cloud_marker(text)
293
+ if cloud_marker:
294
+ result.cloud_marker = cloud_marker
295
+
296
+ desc, truncated = enforce_description_budget(
297
+ meta["description"], strict=strict, warnings=result.warnings
298
+ )
299
+ result.description_truncated = truncated
300
+
301
+ needs_swap = tier in {"T2", "T3-S"} and cloud_marker != "noop"
302
+ rendered = render_skill_md(
303
+ meta["name"], desc, body,
304
+ swap=needs_swap, cloud_marker=cloud_marker,
305
+ )
306
+
307
+ if dry_run:
308
+ return result
309
+
310
+ out_dir.mkdir(parents=True, exist_ok=True)
311
+ zip_path = out_dir / f"{name}.zip"
312
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
313
+ zf.writestr(f"{name}/SKILL.md", rendered)
314
+ for sibling in ("references", "assets", "scripts"):
315
+ sib = skill_dir / sibling
316
+ if not sib.is_dir():
317
+ continue
318
+ for f in sib.rglob("*"):
319
+ if f.is_file():
320
+ arc = f"{name}/{f.relative_to(skill_dir)}"
321
+ zf.write(f, arc)
322
+ try:
323
+ result.zip_path = str(zip_path.relative_to(ROOT))
324
+ except ValueError:
325
+ result.zip_path = str(zip_path)
326
+ return result
327
+
328
+
329
+
330
+ def build_all(
331
+ out_dir: Path,
332
+ *,
333
+ only: str | None,
334
+ strict: bool,
335
+ dry_run: bool,
336
+ ) -> tuple[list[BuildResult], list[BuildResult]]:
337
+ """Build every eligible skill (or just `only`). Returns (built, skipped)."""
338
+ tier_map = load_tier_map()
339
+ if not SOURCE_SKILLS.is_dir():
340
+ raise SystemExit(f"❌ source not found: {SOURCE_SKILLS}")
341
+
342
+ if only:
343
+ skill_dir = SOURCE_SKILLS / only
344
+ if not skill_dir.is_dir():
345
+ raise SystemExit(f"❌ skill not found: {only}")
346
+ skill_dirs = [skill_dir]
347
+ else:
348
+ skill_dirs = sorted(d for d in SOURCE_SKILLS.iterdir() if d.is_dir())
349
+
350
+ built: list[BuildResult] = []
351
+ skipped: list[BuildResult] = []
352
+ for sd in skill_dirs:
353
+ info = tier_map.get(sd.name) or {"tier": "T1", "cloud_marker": None}
354
+ tier = info["tier"]
355
+ cloud_marker = info.get("cloud_marker")
356
+ if tier == "T3-H":
357
+ sk = BuildResult(
358
+ skill=sd.name,
359
+ status="skipped",
360
+ tier=tier,
361
+ reason="T3-H — Phase 2 cloud-aware variant required",
362
+ )
363
+ if only:
364
+ # Explicit single-skill request for a T3-H — refuse loudly.
365
+ raise SystemExit(
366
+ f"❌ '{only}' is T3-H (script-hard). "
367
+ "Bundle blocked until Phase 2 ships a cloud-aware variant.",
368
+ )
369
+ skipped.append(sk)
370
+ continue
371
+ result = build_skill_zip(
372
+ sd, out_dir, tier,
373
+ strict=strict, dry_run=dry_run,
374
+ cloud_marker=cloud_marker,
375
+ )
376
+ if result.status == "ok":
377
+ built.append(result)
378
+ else:
379
+ skipped.append(result)
380
+ return built, skipped
381
+
382
+
383
+ def write_manifest(
384
+ out_dir: Path, built: list[BuildResult], skipped: list[BuildResult]
385
+ ) -> Path:
386
+ out_dir.mkdir(parents=True, exist_ok=True)
387
+ manifest = {
388
+ "summary": {
389
+ "built": len(built),
390
+ "skipped": len(skipped),
391
+ "truncated_descriptions": sum(
392
+ 1 for r in built if r.description_truncated
393
+ ),
394
+ },
395
+ "built": [r.__dict__ for r in built],
396
+ "skipped": [r.__dict__ for r in skipped],
397
+ }
398
+ path = out_dir / "manifest.json"
399
+ path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
400
+ return path
401
+
402
+
403
+ def main(argv: list[str] | None = None) -> int:
404
+ p = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
405
+ g = p.add_mutually_exclusive_group(required=True)
406
+ g.add_argument("--skill", help="bundle one skill by name")
407
+ g.add_argument("--all", action="store_true", help="bundle every eligible skill")
408
+ g.add_argument("--check", action="store_true",
409
+ help="dry-run: validate invariants, no zip output")
410
+ p.add_argument("--out", type=Path, default=DEFAULT_OUT,
411
+ help=f"output directory (default: {DEFAULT_OUT.relative_to(ROOT)})")
412
+ p.add_argument("--strict-budget", action="store_true",
413
+ help="fail when any description > 200 chars")
414
+ p.add_argument("--clean", action="store_true",
415
+ help="wipe --out before building")
416
+ args = p.parse_args(argv)
417
+
418
+ if args.clean and args.out.exists() and not args.check:
419
+ shutil.rmtree(args.out)
420
+
421
+ only = args.skill if args.skill else None
422
+ dry_run = bool(args.check)
423
+
424
+ built, skipped = build_all(
425
+ args.out, only=only, strict=args.strict_budget, dry_run=dry_run
426
+ )
427
+
428
+ if not dry_run:
429
+ write_manifest(args.out, built, skipped)
430
+
431
+ # Console report
432
+ label = "check" if dry_run else "build"
433
+ print(f"📦 cloud-bundle {label}: {len(built)} built · {len(skipped)} skipped")
434
+ truncated = [r for r in built if r.description_truncated]
435
+ if truncated:
436
+ print(f"⚠️ {len(truncated)} description(s) truncated to 200 chars:")
437
+ for r in truncated[:10]:
438
+ print(f" - {r.skill}")
439
+ if len(truncated) > 10:
440
+ print(f" …and {len(truncated) - 10} more")
441
+ t3h = [r for r in skipped if r.tier == "T3-H"]
442
+ if t3h:
443
+ print(f"🚧 {len(t3h)} T3-H skill(s) skipped (Phase 2 pending):")
444
+ for r in t3h[:5]:
445
+ print(f" - {r.skill}")
446
+ if len(t3h) > 5:
447
+ print(f" …and {len(t3h) - 5} more")
448
+ errors = [r for r in skipped if r.status == "error"]
449
+ if errors:
450
+ print(f"❌ {len(errors)} error(s):")
451
+ for r in errors:
452
+ print(f" - {r.skill}: {r.reason}")
453
+ return 1
454
+ return 0
455
+
456
+
457
+ if __name__ == "__main__":
458
+ sys.exit(main())