@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.
- package/.agent-src/commands/agent-handoff.md +3 -0
- package/.agent-src/commands/agent-status.md +3 -0
- package/.agent-src/commands/agents-audit.md +4 -0
- package/.agent-src/commands/agents-cleanup.md +6 -1
- package/.agent-src/commands/agents-prepare.md +3 -0
- package/.agent-src/commands/analyze-reference-repo.md +4 -0
- package/.agent-src/commands/bug-fix.md +5 -1
- package/.agent-src/commands/bug-investigate.md +4 -0
- package/.agent-src/commands/chat-history-checkpoint.md +126 -0
- package/.agent-src/commands/chat-history-clear.md +5 -0
- package/.agent-src/commands/chat-history-resume.md +5 -0
- package/.agent-src/commands/chat-history.md +5 -0
- package/.agent-src/commands/check-current-md.md +126 -0
- package/.agent-src/commands/commit-in-chunks.md +98 -0
- package/.agent-src/commands/commit.md +4 -0
- package/.agent-src/commands/compress.md +3 -0
- package/.agent-src/commands/context-create.md +4 -0
- package/.agent-src/commands/context-refactor.md +4 -0
- package/.agent-src/commands/copilot-agents-init.md +3 -0
- package/.agent-src/commands/copilot-agents-optimize.md +3 -0
- package/.agent-src/commands/create-pr-description.md +4 -0
- package/.agent-src/commands/create-pr.md +4 -0
- package/.agent-src/commands/do-and-judge.md +4 -1
- package/.agent-src/commands/do-in-steps.md +3 -0
- package/.agent-src/commands/e2e-heal.md +4 -0
- package/.agent-src/commands/e2e-plan.md +4 -0
- package/.agent-src/commands/estimate-ticket.md +4 -1
- package/.agent-src/commands/feature-dev.md +4 -0
- package/.agent-src/commands/feature-explore.md +4 -0
- package/.agent-src/commands/feature-plan.md +4 -0
- package/.agent-src/commands/feature-refactor.md +4 -0
- package/.agent-src/commands/feature-roadmap.md +6 -0
- package/.agent-src/commands/fix-ci.md +4 -0
- package/.agent-src/commands/fix-portability.md +3 -0
- package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
- package/.agent-src/commands/fix-pr-comments.md +4 -0
- package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
- package/.agent-src/commands/fix-references.md +3 -0
- package/.agent-src/commands/fix-seeder.md +4 -0
- package/.agent-src/commands/implement-ticket.md +39 -13
- package/.agent-src/commands/jira-ticket.md +4 -0
- package/.agent-src/commands/judge.md +3 -0
- package/.agent-src/commands/memory-add.md +5 -3
- package/.agent-src/commands/memory-full.md +5 -2
- package/.agent-src/commands/memory-promote.md +7 -6
- package/.agent-src/commands/mode.md +3 -0
- package/.agent-src/commands/module-create.md +4 -0
- package/.agent-src/commands/module-explore.md +4 -0
- package/.agent-src/commands/onboard.md +24 -0
- package/.agent-src/commands/optimize-agents.md +4 -0
- package/.agent-src/commands/optimize-augmentignore.md +3 -0
- package/.agent-src/commands/optimize-rtk-filters.md +3 -0
- package/.agent-src/commands/optimize-skills.md +4 -0
- package/.agent-src/commands/override-create.md +4 -0
- package/.agent-src/commands/override-manage.md +4 -0
- package/.agent-src/commands/package-reset.md +3 -0
- package/.agent-src/commands/package-test.md +3 -0
- package/.agent-src/commands/prepare-for-review.md +4 -0
- package/.agent-src/commands/project-analyze.md +4 -0
- package/.agent-src/commands/project-health.md +4 -0
- package/.agent-src/commands/propose-memory.md +6 -8
- package/.agent-src/commands/quality-fix.md +4 -0
- package/.agent-src/commands/refine-ticket.md +4 -1
- package/.agent-src/commands/review-changes.md +4 -0
- package/.agent-src/commands/review-routing.md +4 -0
- package/.agent-src/commands/roadmap-create.md +7 -0
- package/.agent-src/commands/roadmap-execute.md +12 -1
- package/.agent-src/commands/rule-compliance-audit.md +4 -0
- package/.agent-src/commands/set-cost-profile.md +3 -0
- package/.agent-src/commands/sync-agent-settings.md +3 -0
- package/.agent-src/commands/sync-gitignore.md +3 -0
- package/.agent-src/commands/tests-create.md +4 -0
- package/.agent-src/commands/tests-execute.md +4 -0
- package/.agent-src/commands/threat-model.md +4 -0
- package/.agent-src/commands/update-form-request-messages.md +4 -0
- package/.agent-src/commands/upstream-contribute.md +4 -0
- package/.agent-src/commands/work.md +161 -0
- package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
- package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
- package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
- package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
- package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
- package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
- package/.agent-src/personas/README.md +0 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
- package/.agent-src/rules/artifact-engagement-recording.md +133 -0
- package/.agent-src/rules/ask-when-uncertain.md +18 -13
- package/.agent-src/rules/augment-portability.md +8 -0
- package/.agent-src/rules/autonomous-execution.md +158 -0
- package/.agent-src/rules/chat-history.md +147 -118
- package/.agent-src/rules/cli-output-handling.md +26 -3
- package/.agent-src/rules/command-suggestion.md +133 -0
- package/.agent-src/rules/commit-policy.md +99 -0
- package/.agent-src/rules/direct-answers.md +114 -0
- package/.agent-src/rules/docs-sync.md +36 -0
- package/.agent-src/rules/downstream-changes.md +10 -9
- package/.agent-src/rules/improve-before-implement.md +9 -6
- package/.agent-src/rules/language-and-tone.md +81 -6
- package/.agent-src/rules/non-destructive-by-default.md +117 -0
- package/.agent-src/rules/package-ci-checks.md +4 -0
- package/.agent-src/rules/preservation-guard.md +20 -0
- package/.agent-src/rules/roadmap-progress-sync.md +103 -30
- package/.agent-src/rules/scope-control.md +42 -1
- package/.agent-src/rules/size-enforcement.md +1 -3
- package/.agent-src/rules/skill-quality.md +3 -8
- package/.agent-src/rules/ui-audit-before-build.md +106 -0
- package/.agent-src/rules/user-interaction.md +82 -50
- package/.agent-src/scripts/update_roadmap_progress.py +17 -5
- package/.agent-src/skills/blade-ui/SKILL.md +30 -5
- package/.agent-src/skills/command-routing/SKILL.md +32 -0
- package/.agent-src/skills/command-writing/SKILL.md +41 -2
- package/.agent-src/skills/description-assist/SKILL.md +21 -0
- package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
- package/.agent-src/skills/existing-ui-audit/SKILL.md +187 -0
- package/.agent-src/skills/fe-design/SKILL.md +72 -60
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
- package/.agent-src/skills/flux/SKILL.md +31 -4
- package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
- package/.agent-src/skills/livewire/SKILL.md +30 -4
- package/.agent-src/skills/md-language-check/SKILL.md +103 -0
- package/.agent-src/skills/php-coder/SKILL.md +24 -0
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +2 -4
- package/.agent-src/skills/roadmap-management/SKILL.md +10 -3
- package/.agent-src/skills/rule-writing/SKILL.md +23 -1
- package/.agent-src/skills/skill-writing/SKILL.md +1 -3
- package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
- package/.agent-src/templates/AGENTS.md +24 -6
- package/.agent-src/templates/agent-settings.md +149 -0
- package/.agent-src/templates/roadmaps.md +8 -2
- package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
- package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
- package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
- package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
- package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
- package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
- package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
- package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
- package/.agent-src/templates/scripts/telemetry_record.py +166 -0
- package/.agent-src/templates/scripts/telemetry_report.py +161 -0
- package/.agent-src/templates/scripts/telemetry_status.py +142 -0
- package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
- package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
- package/.agent-src/templates/scripts/work_engine/cli.py +592 -0
- package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +7 -0
- package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
- package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +1 -1
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +1 -1
- package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +36 -4
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
- package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
- package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
- package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
- package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
- package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
- package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
- package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
- package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
- package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
- package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
- package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +199 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
- package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
- package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
- package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
- package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
- package/.agent-src/templates/scripts/work_engine/state.py +641 -0
- package/.claude-plugin/marketplace.json +105 -2
- package/AGENTS.md +36 -8
- package/CHANGELOG.md +534 -0
- package/README.md +125 -4
- package/config/agent-settings.template.yml +45 -0
- package/config/gitignore-block.txt +4 -0
- package/docs/architecture.md +28 -1
- package/docs/development.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/installation.md +86 -0
- package/docs/showcase.md +204 -0
- package/package.json +1 -1
- package/scripts/agent-config +199 -0
- package/scripts/audit_cloud_compatibility.py +288 -0
- package/scripts/build_cloud_bundle.py +458 -0
- package/scripts/build_linear_digest.py +263 -0
- package/scripts/chat_history.py +796 -7
- package/scripts/check_compression.py +139 -0
- package/scripts/check_iron_law_prominence.py +143 -0
- package/scripts/check_md_language.py +159 -0
- package/scripts/check_portability.py +36 -0
- package/scripts/check_reply_consistency.py +140 -0
- package/scripts/command_suggester/__init__.py +51 -0
- package/scripts/command_suggester/cooldown.py +132 -0
- package/scripts/command_suggester/loader.py +70 -0
- package/scripts/command_suggester/match.py +180 -0
- package/scripts/command_suggester/rank.py +120 -0
- package/scripts/command_suggester/render.py +86 -0
- package/scripts/command_suggester/sanitize.py +113 -0
- package/scripts/command_suggester/settings.py +125 -0
- package/scripts/command_suggester/types.py +78 -0
- package/scripts/hooks/augment-chat-history.sh +56 -0
- package/scripts/install-hooks.sh +67 -0
- package/scripts/install.py +150 -33
- package/scripts/lint_marketplace.py +27 -0
- package/scripts/migrate_command_suggestions.py +151 -0
- package/scripts/schemas/command.schema.json +41 -0
- package/scripts/skill_linter.py +67 -0
- package/scripts/sync_agent_settings.py +42 -12
- package/templates/consumer-settings/augment-cli-hooks.json +54 -0
- package/templates/consumer-settings/claude-settings.json +55 -1
- package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
- package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
- package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
- package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
- /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())
|