@event4u/agent-config 1.33.0 → 1.35.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/review-changes.md +13 -8
- package/.agent-src/commands/roadmap/process-full.md +17 -15
- package/.agent-src/contexts/execution/roadmap-process-loop.md +11 -10
- package/.agent-src/personas/README.md +12 -21
- package/.agent-src/personas/_template-specialist/persona.md +89 -0
- package/.agent-src/personas/backend-architect.md +96 -0
- package/.agent-src/personas/discovery-lead.md +99 -0
- package/.agent-src/personas/eloquent-tamer.md +96 -0
- package/.agent-src/personas/frontend-engineer.md +100 -0
- package/.agent-src/personas/product-owner.md +71 -52
- package/.agent-src/personas/qa.md +27 -2
- package/.agent-src/personas/revops-maintainer.md +100 -0
- package/.agent-src/personas/security-engineer.md +100 -0
- package/.agent-src/personas/tech-writer.md +99 -0
- package/.agent-src/skills/accessibility-auditor/SKILL.md +132 -0
- package/.agent-src/skills/adr-create/SKILL.md +1 -0
- package/.agent-src/skills/adversarial-review/SKILL.md +1 -0
- package/.agent-src/skills/agent-docs-writing/SKILL.md +1 -0
- package/.agent-src/skills/agents-md-thin-root/SKILL.md +1 -0
- package/.agent-src/skills/ai-council/SKILL.md +1 -0
- package/.agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -0
- package/.agent-src/skills/analysis-skill-router/SKILL.md +1 -0
- package/.agent-src/skills/api-design/SKILL.md +3 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +1 -0
- package/.agent-src/skills/api-testing/SKILL.md +1 -0
- package/.agent-src/skills/architecture-review-lens/SKILL.md +137 -0
- package/.agent-src/skills/artisan-commands/SKILL.md +1 -0
- package/.agent-src/skills/async-python-patterns/SKILL.md +1 -0
- package/.agent-src/skills/authz-review/SKILL.md +4 -0
- package/.agent-src/skills/aws-infrastructure/SKILL.md +1 -0
- package/.agent-src/skills/blade-ui/SKILL.md +1 -0
- package/.agent-src/skills/blast-radius-analyzer/SKILL.md +3 -0
- package/.agent-src/skills/bug-analyzer/SKILL.md +1 -0
- package/.agent-src/skills/check-refs/SKILL.md +1 -0
- package/.agent-src/skills/code-refactoring/SKILL.md +1 -0
- package/.agent-src/skills/code-review/SKILL.md +1 -0
- package/.agent-src/skills/command-routing/SKILL.md +1 -0
- package/.agent-src/skills/command-writing/SKILL.md +1 -0
- package/.agent-src/skills/competitive-positioning/SKILL.md +152 -0
- package/.agent-src/skills/composer-packages/SKILL.md +1 -0
- package/.agent-src/skills/context-authoring/SKILL.md +1 -0
- package/.agent-src/skills/context-document/SKILL.md +1 -0
- package/.agent-src/skills/conventional-commits-writing/SKILL.md +1 -0
- package/.agent-src/skills/copilot-agents-optimization/SKILL.md +1 -0
- package/.agent-src/skills/copilot-config/SKILL.md +1 -0
- package/.agent-src/skills/customer-research/SKILL.md +116 -0
- package/.agent-src/skills/dashboard-design/SKILL.md +1 -0
- package/.agent-src/skills/data-flow-mapper/SKILL.md +1 -0
- package/.agent-src/skills/database/SKILL.md +3 -0
- package/.agent-src/skills/dcf-modeling/SKILL.md +1 -0
- package/.agent-src/skills/decision-record/SKILL.md +218 -0
- package/.agent-src/skills/deep-reading-analyst/SKILL.md +1 -0
- package/.agent-src/skills/defense-in-depth/SKILL.md +1 -0
- package/.agent-src/skills/dependency-upgrade/SKILL.md +1 -0
- package/.agent-src/skills/description-assist/SKILL.md +1 -0
- package/.agent-src/skills/design-review/SKILL.md +1 -0
- package/.agent-src/skills/devcontainer/SKILL.md +1 -0
- package/.agent-src/skills/developer-like-execution/SKILL.md +1 -0
- package/.agent-src/skills/discovery-interview/SKILL.md +152 -0
- package/.agent-src/skills/docker/SKILL.md +1 -0
- package/.agent-src/skills/dto-creator/SKILL.md +1 -0
- package/.agent-src/skills/eloquent/SKILL.md +3 -0
- package/.agent-src/skills/error-handling-patterns/SKILL.md +1 -0
- package/.agent-src/skills/estimate-ticket/SKILL.md +1 -0
- package/.agent-src/skills/existing-ui-audit/SKILL.md +3 -0
- package/.agent-src/skills/fe-design/SKILL.md +4 -1
- package/.agent-src/skills/feature-planning/SKILL.md +1 -0
- package/.agent-src/skills/file-editor/SKILL.md +1 -0
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +1 -0
- package/.agent-src/skills/flux/SKILL.md +1 -0
- package/.agent-src/skills/form-handler/SKILL.md +145 -0
- package/.agent-src/skills/funnel-analysis/SKILL.md +1 -0
- package/.agent-src/skills/git-workflow/SKILL.md +1 -0
- package/.agent-src/skills/github-ci/SKILL.md +1 -0
- package/.agent-src/skills/grafana/SKILL.md +1 -0
- package/.agent-src/skills/guideline-writing/SKILL.md +1 -0
- package/.agent-src/skills/incident-commander/SKILL.md +140 -0
- package/.agent-src/skills/jira-integration/SKILL.md +1 -0
- package/.agent-src/skills/jobs-events/SKILL.md +1 -0
- package/.agent-src/skills/judge-bug-hunter/SKILL.md +1 -0
- package/.agent-src/skills/judge-code-quality/SKILL.md +1 -0
- package/.agent-src/skills/judge-security-auditor/SKILL.md +3 -0
- package/.agent-src/skills/judge-test-coverage/SKILL.md +1 -0
- package/.agent-src/skills/laravel/SKILL.md +1 -0
- package/.agent-src/skills/laravel-horizon/SKILL.md +1 -0
- package/.agent-src/skills/laravel-mail/SKILL.md +1 -0
- package/.agent-src/skills/laravel-middleware/SKILL.md +1 -0
- package/.agent-src/skills/laravel-notifications/SKILL.md +1 -0
- package/.agent-src/skills/laravel-pennant/SKILL.md +1 -0
- package/.agent-src/skills/laravel-pulse/SKILL.md +1 -0
- package/.agent-src/skills/laravel-reverb/SKILL.md +1 -0
- package/.agent-src/skills/laravel-scheduling/SKILL.md +1 -0
- package/.agent-src/skills/laravel-validation/SKILL.md +1 -0
- package/.agent-src/skills/launch-readiness/SKILL.md +156 -0
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -0
- package/.agent-src/skills/lint-skills/SKILL.md +1 -0
- package/.agent-src/skills/livewire/SKILL.md +1 -0
- package/.agent-src/skills/livewire-architect/SKILL.md +158 -0
- package/.agent-src/skills/logging-monitoring/SKILL.md +1 -0
- package/.agent-src/skills/markitdown/SKILL.md +1 -0
- package/.agent-src/skills/mcp/SKILL.md +1 -0
- package/.agent-src/skills/mcp-builder/SKILL.md +1 -0
- package/.agent-src/skills/md-language-check/SKILL.md +1 -0
- package/.agent-src/skills/merge-conflicts/SKILL.md +1 -0
- package/.agent-src/skills/migration-architect/SKILL.md +119 -0
- package/.agent-src/skills/migration-creator/SKILL.md +1 -0
- package/.agent-src/skills/mobile-e2e-strategy/SKILL.md +2 -1
- package/.agent-src/skills/module-management/SKILL.md +1 -0
- package/.agent-src/skills/multi-tenancy/SKILL.md +1 -0
- package/.agent-src/skills/okr-tree-modeling/SKILL.md +1 -0
- package/.agent-src/skills/openapi/SKILL.md +1 -0
- package/.agent-src/skills/override-management/SKILL.md +1 -0
- package/.agent-src/skills/performance/SKILL.md +1 -0
- package/.agent-src/skills/performance-analysis/SKILL.md +1 -0
- package/.agent-src/skills/persona-writing/SKILL.md +1 -0
- package/.agent-src/skills/pest-testing/SKILL.md +1 -0
- package/.agent-src/skills/php-coder/SKILL.md +1 -0
- package/.agent-src/skills/php-debugging/SKILL.md +1 -0
- package/.agent-src/skills/php-service/SKILL.md +1 -0
- package/.agent-src/skills/playwright-architect/SKILL.md +141 -0
- package/.agent-src/skills/playwright-testing/SKILL.md +1 -0
- package/.agent-src/skills/po-discovery/SKILL.md +127 -0
- package/.agent-src/skills/project-analysis-core/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-laravel/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-nextjs/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-node-express/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-react/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-symfony/SKILL.md +1 -0
- package/.agent-src/skills/project-analysis-zend-laminas/SKILL.md +1 -0
- package/.agent-src/skills/project-analyzer/SKILL.md +1 -0
- package/.agent-src/skills/project-docs/SKILL.md +1 -0
- package/.agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -0
- package/.agent-src/skills/prompt-optimizer/SKILL.md +1 -0
- package/.agent-src/skills/quality-tools/SKILL.md +1 -0
- package/.agent-src/skills/react-native-setup/SKILL.md +1 -0
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +1 -0
- package/.agent-src/skills/readme-reviewer/SKILL.md +1 -0
- package/.agent-src/skills/readme-writing/SKILL.md +1 -0
- package/.agent-src/skills/readme-writing-package/SKILL.md +1 -0
- package/.agent-src/skills/receiving-code-review/SKILL.md +1 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +1 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +1 -0
- package/.agent-src/skills/release-comms/SKILL.md +123 -0
- package/.agent-src/skills/repomix-packer/SKILL.md +1 -0
- package/.agent-src/skills/requesting-code-review/SKILL.md +1 -0
- package/.agent-src/skills/review-routing/SKILL.md +1 -0
- package/.agent-src/skills/rice-prioritization/SKILL.md +1 -0
- package/.agent-src/skills/risk-officer/SKILL.md +141 -0
- package/.agent-src/skills/roadmap-management/SKILL.md +1 -0
- package/.agent-src/skills/roadmap-writing/SKILL.md +2 -1
- package/.agent-src/skills/rtk-output-filtering/SKILL.md +1 -0
- package/.agent-src/skills/rule-writing/SKILL.md +1 -0
- package/.agent-src/skills/script-writing/SKILL.md +1 -0
- package/.agent-src/skills/secrets-management/SKILL.md +1 -0
- package/.agent-src/skills/security/SKILL.md +1 -0
- package/.agent-src/skills/security-audit/SKILL.md +1 -0
- package/.agent-src/skills/sentry-integration/SKILL.md +1 -0
- package/.agent-src/skills/sequential-thinking/SKILL.md +1 -0
- package/.agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -0
- package/.agent-src/skills/skill-management/SKILL.md +1 -0
- package/.agent-src/skills/skill-reviewer/SKILL.md +1 -0
- package/.agent-src/skills/skill-writing/SKILL.md +1 -0
- package/.agent-src/skills/sql-writing/SKILL.md +1 -0
- package/.agent-src/skills/stakeholder-tradeoff/SKILL.md +237 -0
- package/.agent-src/skills/subagent-orchestration/SKILL.md +13 -0
- package/.agent-src/skills/systematic-debugging/SKILL.md +1 -0
- package/.agent-src/skills/tailwind-engineer/SKILL.md +130 -0
- package/.agent-src/skills/tech-debt-tracker/SKILL.md +152 -0
- package/.agent-src/skills/technical-specification/SKILL.md +1 -0
- package/.agent-src/skills/terraform/SKILL.md +1 -0
- package/.agent-src/skills/terragrunt/SKILL.md +1 -0
- package/.agent-src/skills/test-driven-development/SKILL.md +1 -0
- package/.agent-src/skills/test-performance/SKILL.md +1 -0
- package/.agent-src/skills/testing-anti-patterns/SKILL.md +1 -0
- package/.agent-src/skills/threat-modeling/SKILL.md +3 -0
- package/.agent-src/skills/token-optimizer/SKILL.md +1 -0
- package/.agent-src/skills/traefik/SKILL.md +1 -0
- package/.agent-src/skills/ui-component-architect/SKILL.md +153 -0
- package/.agent-src/skills/unit-economics-modeling/SKILL.md +1 -0
- package/.agent-src/skills/universal-project-analysis/SKILL.md +1 -0
- package/.agent-src/skills/upstream-contribute/SKILL.md +1 -0
- package/.agent-src/skills/using-git-worktrees/SKILL.md +1 -0
- package/.agent-src/skills/validate-feature-fit/SKILL.md +1 -0
- package/.agent-src/skills/verify-completion-evidence/SKILL.md +1 -0
- package/.agent-src/skills/voc-extract/SKILL.md +164 -0
- package/.agent-src/skills/websocket/SKILL.md +1 -0
- package/.agent-src/templates/roadmaps.md +9 -0
- package/.claude-plugin/marketplace.json +21 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +75 -0
- package/README.md +2 -2
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +21 -4
- package/docs/contracts/context-spine.md +133 -0
- package/docs/contracts/file-ownership-matrix.json +616 -0
- package/docs/contracts/mental-models.md +336 -0
- package/docs/contracts/persona-schema.md +136 -0
- package/docs/contracts/skill-domains.md +143 -0
- package/docs/decisions/ADR-005-subagent-worktrees.md +120 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +114 -0
- package/docs/decisions/INDEX.md +3 -0
- package/docs/guidelines/cross-role-handoff.md +127 -0
- package/docs/personas.md +115 -0
- package/package.json +1 -1
- package/scripts/_backfill_skill_domains.py +140 -0
- package/scripts/_emit_domain_table.py +35 -0
- package/scripts/install-hooks.sh +21 -4
- package/scripts/lint_context_spine_usage.py +133 -0
- package/scripts/lint_roadmap_complexity.py +37 -0
- package/scripts/lint_skill_tools.py +168 -0
- package/scripts/schemas/skill.schema.json +15 -1
- package/scripts/skill_linter.py +19 -4
- package/scripts/skill_tools/__init__.py +22 -0
- package/scripts/skill_tools/audit_persona_coverage.py +147 -0
- package/scripts/skill_tools/run_block_d_eval.py +129 -0
- package/scripts/skill_tools/score_skill_relevance.py +169 -0
- package/scripts/skill_tools/suggest_skill_for_task.py +113 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Emit the per-domain skill list as Markdown for skill-domains.md § 4."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
10
|
+
from _backfill_skill_domains import SKILL_DOMAIN_MAP # noqa: E402
|
|
11
|
+
|
|
12
|
+
ORDER = ["engineering", "product", "quality", "devops", "process", "discovery"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
by_domain: dict[str, list[str]] = defaultdict(list)
|
|
17
|
+
for slug, dom in SKILL_DOMAIN_MAP.items():
|
|
18
|
+
by_domain[dom].append(slug)
|
|
19
|
+
|
|
20
|
+
lines: list[str] = []
|
|
21
|
+
total = 0
|
|
22
|
+
for dom in ORDER:
|
|
23
|
+
skills = sorted(by_domain[dom])
|
|
24
|
+
total += len(skills)
|
|
25
|
+
lines.append(f"### {dom} ({len(skills)})")
|
|
26
|
+
lines.append("")
|
|
27
|
+
lines.append(", ".join(f"`{s}`" for s in skills))
|
|
28
|
+
lines.append("")
|
|
29
|
+
lines.append(f"**Total: {total} skills.**")
|
|
30
|
+
print("\n".join(lines))
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
sys.exit(main())
|
package/scripts/install-hooks.sh
CHANGED
|
@@ -13,14 +13,31 @@ mkdir -p "$HOOKS_DIR"
|
|
|
13
13
|
cat > "$HOOKS_DIR/pre-push" << 'EOF'
|
|
14
14
|
#!/usr/bin/env bash
|
|
15
15
|
# Pre-push hook: verify .agent-src/ is in sync with .agent-src.uncompressed/
|
|
16
|
+
# and that the canonical command count matches README + getting-started docs.
|
|
17
|
+
#
|
|
18
|
+
# The command-count gate exists because three consecutive PRs landed
|
|
19
|
+
# post-CI count-drift fixes (e.g. f2fb0026 "bump command count
|
|
20
|
+
# 101→103"). Catching the drift pre-push stops it from flooding remote
|
|
21
|
+
# CI. Runtime ~0.1s.
|
|
22
|
+
|
|
23
|
+
fail=0
|
|
16
24
|
|
|
17
25
|
echo "🔍 Checking .agent-src/ sync..."
|
|
18
|
-
python3 scripts/compress.py --check
|
|
26
|
+
if ! python3 scripts/compress.py --check; then
|
|
27
|
+
echo "❌ .agent-src/ is out of sync. Run 'task sync' and compress changed .md files, then commit."
|
|
28
|
+
fail=1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "🔍 Checking command count messaging..."
|
|
32
|
+
if ! python3 scripts/check_command_count_messaging.py; then
|
|
33
|
+
echo "❌ Command-count drift in README / AGENTS.md / getting-started. Run 'task counts-update', stage the changes, then re-commit."
|
|
34
|
+
fail=1
|
|
35
|
+
fi
|
|
19
36
|
|
|
20
|
-
if [
|
|
37
|
+
if [ $fail -ne 0 ]; then
|
|
21
38
|
echo ""
|
|
22
|
-
echo "
|
|
23
|
-
echo "
|
|
39
|
+
echo " Push blocked — fix the failures above and re-push."
|
|
40
|
+
echo " Bypass for a WIP push: git push --no-verify"
|
|
24
41
|
exit 1
|
|
25
42
|
fi
|
|
26
43
|
EOF
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Context-spine usage linter.
|
|
3
|
+
|
|
4
|
+
Closes the lint gap left after `scripts/schemas/skill.schema.json`
|
|
5
|
+
gained the `context_spine` enum: a skill can declare
|
|
6
|
+
`context_spine: [product]` in frontmatter without ever citing the
|
|
7
|
+
slot in its body, and the schema check will not catch it.
|
|
8
|
+
|
|
9
|
+
This linter enforces the author checklist in
|
|
10
|
+
`docs/contracts/context-spine.md` § 6: for every slot declared in
|
|
11
|
+
frontmatter, the skill body MUST cite the slot at least once.
|
|
12
|
+
A citation is any of these tokens:
|
|
13
|
+
|
|
14
|
+
- the literal path `agents/context-spine/<slot>.md`
|
|
15
|
+
- the slot name in bold: ``**<slot>**``
|
|
16
|
+
- the slot name in inline code: `` `<slot>` ``
|
|
17
|
+
|
|
18
|
+
Cap: ≤ 150 LOC, stdlib only. Hooked into `task ci` via
|
|
19
|
+
`task lint-context-spine-usage`.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
QUIET = "--quiet" in sys.argv
|
|
28
|
+
|
|
29
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
30
|
+
SKILL_GLOBS = (
|
|
31
|
+
".agent-src.uncompressed/skills/**/SKILL.md",
|
|
32
|
+
".agent-src/skills/**/SKILL.md",
|
|
33
|
+
)
|
|
34
|
+
VALID_SLOTS = ("product", "team", "repo")
|
|
35
|
+
|
|
36
|
+
CONTEXT_SPINE_PAT = re.compile(
|
|
37
|
+
r"^context_spine:\s*\[([^\]]*)\]\s*$", re.MULTILINE
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _frontmatter_and_body(text: str) -> tuple[str, str]:
|
|
42
|
+
if not text.startswith("---\n"):
|
|
43
|
+
return "", text
|
|
44
|
+
end = text.find("\n---\n", 4)
|
|
45
|
+
if end == -1:
|
|
46
|
+
return "", text
|
|
47
|
+
return text[4:end], text[end + 5 :]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _read_spine(fm: str) -> list[str] | None:
|
|
51
|
+
m = CONTEXT_SPINE_PAT.search(fm)
|
|
52
|
+
if m is None:
|
|
53
|
+
return None
|
|
54
|
+
raw = m.group(1).strip()
|
|
55
|
+
if not raw:
|
|
56
|
+
return []
|
|
57
|
+
return [s.strip().strip("'\"") for s in raw.split(",") if s.strip()]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _slot_cited(body: str, slot: str) -> bool:
|
|
61
|
+
"""A slot is cited if any of three forms appears in the body."""
|
|
62
|
+
forms = (
|
|
63
|
+
f"agents/context-spine/{slot}.md",
|
|
64
|
+
f"**{slot}**",
|
|
65
|
+
f"`{slot}`",
|
|
66
|
+
)
|
|
67
|
+
return any(form in body for form in forms)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def lint_skill(path: Path) -> list[str]:
|
|
71
|
+
text = path.read_text(encoding="utf-8")
|
|
72
|
+
fm, body = _frontmatter_and_body(text)
|
|
73
|
+
if not fm:
|
|
74
|
+
return []
|
|
75
|
+
slots = _read_spine(fm)
|
|
76
|
+
if slots is None:
|
|
77
|
+
return []
|
|
78
|
+
problems: list[str] = []
|
|
79
|
+
for slot in slots:
|
|
80
|
+
if slot not in VALID_SLOTS:
|
|
81
|
+
problems.append(
|
|
82
|
+
f"unknown_context_spine_slot: '{slot}' "
|
|
83
|
+
f"(valid: {', '.join(VALID_SLOTS)})"
|
|
84
|
+
)
|
|
85
|
+
continue
|
|
86
|
+
if not _slot_cited(body, slot):
|
|
87
|
+
problems.append(
|
|
88
|
+
f"declared context_spine slot '{slot}' is never cited "
|
|
89
|
+
f"in the skill body — add `**{slot}**`, `` `{slot}` ``, "
|
|
90
|
+
f"or a link to `agents/context-spine/{slot}.md` "
|
|
91
|
+
f"(see docs/contracts/context-spine.md § 6)"
|
|
92
|
+
)
|
|
93
|
+
return problems
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main() -> int:
|
|
97
|
+
skills: list[Path] = []
|
|
98
|
+
for pattern in SKILL_GLOBS:
|
|
99
|
+
skills.extend(sorted(REPO_ROOT.glob(pattern)))
|
|
100
|
+
if not skills:
|
|
101
|
+
print("❌ no SKILL.md files matched", file=sys.stderr)
|
|
102
|
+
return 1
|
|
103
|
+
failed = 0
|
|
104
|
+
declared = 0
|
|
105
|
+
for skill in skills:
|
|
106
|
+
rel = skill.relative_to(REPO_ROOT)
|
|
107
|
+
problems = lint_skill(skill)
|
|
108
|
+
text = skill.read_text(encoding="utf-8")
|
|
109
|
+
fm, _ = _frontmatter_and_body(text)
|
|
110
|
+
if fm and CONTEXT_SPINE_PAT.search(fm):
|
|
111
|
+
declared += 1
|
|
112
|
+
if problems:
|
|
113
|
+
failed += 1
|
|
114
|
+
print(f"❌ {rel}", file=sys.stderr)
|
|
115
|
+
for p in problems:
|
|
116
|
+
print(f" - {p}", file=sys.stderr)
|
|
117
|
+
if failed:
|
|
118
|
+
print(
|
|
119
|
+
f"\n❌ {failed} skill(s) failed context-spine usage lint "
|
|
120
|
+
f"({declared} skill(s) declare a spine)",
|
|
121
|
+
file=sys.stderr,
|
|
122
|
+
)
|
|
123
|
+
return 1
|
|
124
|
+
if not QUIET:
|
|
125
|
+
print(
|
|
126
|
+
f"✅ {declared} skill(s) declare context_spine; "
|
|
127
|
+
f"all declared slots are cited in the body"
|
|
128
|
+
)
|
|
129
|
+
return 0
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
sys.exit(main())
|
|
@@ -35,6 +35,29 @@ COMPLEXITY_PAT = re.compile(
|
|
|
35
35
|
r"^complexity:\s*(lightweight|structural)\s*$", re.MULTILINE
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
# Plate / horizon detection — template rule 16 forbids time-boxed plates
|
|
39
|
+
# in roadmaps. Patterns match the authoring devices we are retiring.
|
|
40
|
+
PLATE_PATS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
41
|
+
(re.compile(r"^##\s+Horizon\b", re.MULTILINE | re.IGNORECASE),
|
|
42
|
+
"'## Horizon' section header"),
|
|
43
|
+
(re.compile(r"\b\d+-week\s+(visible\s+)?plate\b", re.IGNORECASE),
|
|
44
|
+
"'N-week (visible) plate' phrasing"),
|
|
45
|
+
(re.compile(r"\bvisible\s+plate\b", re.IGNORECASE),
|
|
46
|
+
"'visible plate' phrasing"),
|
|
47
|
+
(re.compile(r"\b(in|out)-of-plate\b", re.IGNORECASE),
|
|
48
|
+
"'in-of-plate' / 'out-of-plate' marker"),
|
|
49
|
+
(re.compile(r"\bout-of-horizon\b", re.IGNORECASE),
|
|
50
|
+
"'out-of-horizon' marker"),
|
|
51
|
+
(re.compile(r"\bIn-plate\??\b"),
|
|
52
|
+
"'In-plate' / 'In-plate?' label"),
|
|
53
|
+
(re.compile(r"\bOut-of-plate\b"),
|
|
54
|
+
"'Out-of-plate' label"),
|
|
55
|
+
(re.compile(r"inside\s+(the\s+|\d+-week\s+)?plate", re.IGNORECASE),
|
|
56
|
+
"'inside the plate' phrasing"),
|
|
57
|
+
(re.compile(r"outside\s+(the\s+|\d+-week\s+)?plate", re.IGNORECASE),
|
|
58
|
+
"'outside the plate' phrasing"),
|
|
59
|
+
)
|
|
60
|
+
|
|
38
61
|
|
|
39
62
|
def _frontmatter(text: str) -> str:
|
|
40
63
|
if not text.startswith("---\n"):
|
|
@@ -73,6 +96,19 @@ def _check_lightweight(text: str, line_count: int, problems: list[str]) -> None:
|
|
|
73
96
|
)
|
|
74
97
|
|
|
75
98
|
|
|
99
|
+
def _check_no_plate(text: str, problems: list[str]) -> None:
|
|
100
|
+
"""Detect time-boxed plate / horizon framing forbidden by template rule 16."""
|
|
101
|
+
for pat, label in PLATE_PATS:
|
|
102
|
+
m = pat.search(text)
|
|
103
|
+
if m is None:
|
|
104
|
+
continue
|
|
105
|
+
line = text.count("\n", 0, m.start()) + 1
|
|
106
|
+
problems.append(
|
|
107
|
+
f"plate/horizon convention detected ({label}) at line {line} — "
|
|
108
|
+
f"forbidden by templates/roadmaps.md rule 16"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
76
112
|
def lint_roadmap(path: Path) -> list[str]:
|
|
77
113
|
text = path.read_text(encoding="utf-8")
|
|
78
114
|
line_count = text.count("\n") + (1 if text and not text.endswith("\n") else 0)
|
|
@@ -87,6 +123,7 @@ def lint_roadmap(path: Path) -> list[str]:
|
|
|
87
123
|
return problems
|
|
88
124
|
if complexity == "lightweight":
|
|
89
125
|
_check_lightweight(text, line_count, problems)
|
|
126
|
+
_check_no_plate(text, problems)
|
|
90
127
|
return problems
|
|
91
128
|
|
|
92
129
|
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Block D · D1 — meta-linter for ``scripts/skill_tools/*.py``.
|
|
3
|
+
|
|
4
|
+
Enforces the four pilot-tool invariants locked by the Block D council
|
|
5
|
+
verdict (`agents/council-responses/block-d-python-tools-pilot-verdict.md`):
|
|
6
|
+
|
|
7
|
+
1. **stdlib-only** — no third-party imports. Internal package imports
|
|
8
|
+
(``scripts.*``) are allowed.
|
|
9
|
+
2. **--help and --json flags** — every tool must register both via
|
|
10
|
+
``argparse`` so callers can introspect and machine-read.
|
|
11
|
+
3. **naming** — ``snake_case_verb_noun.py`` (≥ 1 underscore, lowercase).
|
|
12
|
+
4. **embedded sample data** — module must define a ``_SAMPLE`` constant
|
|
13
|
+
OR contain ``if __name__ == "__main__"`` with sample-mode handling
|
|
14
|
+
(so the tool can run without external fixtures).
|
|
15
|
+
5. **size cap** — file ≤ 200 LOC (per roadmap D1, applies to D1 itself
|
|
16
|
+
and to D2/D3/D4 with their own caps validated externally).
|
|
17
|
+
|
|
18
|
+
Tool discovery is glob-based (``scripts/skill_tools/*.py`` excluding
|
|
19
|
+
``__init__.py``) per anthropic round-2 critique — never a hardcoded list.
|
|
20
|
+
|
|
21
|
+
Run:
|
|
22
|
+
python3 scripts/lint_skill_tools.py # human-readable
|
|
23
|
+
python3 scripts/lint_skill_tools.py --json # machine-readable
|
|
24
|
+
|
|
25
|
+
Exit codes: 0 clean · 1 violations found · 2 usage error.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import ast
|
|
31
|
+
import json
|
|
32
|
+
import re
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Dict, List, Tuple
|
|
36
|
+
|
|
37
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
38
|
+
TOOLS_DIR = ROOT / "scripts" / "skill_tools"
|
|
39
|
+
NAME_RE = re.compile(r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)+\.py$")
|
|
40
|
+
SIZE_CAP = 200
|
|
41
|
+
|
|
42
|
+
# Python 3.9-compatible stdlib list. Kept conservative — additions are cheap,
|
|
43
|
+
# false negatives are not. Mirrors `sys.stdlib_module_names` from 3.10+.
|
|
44
|
+
STDLIB = frozenset({
|
|
45
|
+
"__future__", "abc", "argparse", "ast", "base64", "collections", "configparser",
|
|
46
|
+
"contextlib", "copy", "csv", "dataclasses", "datetime", "decimal", "difflib",
|
|
47
|
+
"enum", "errno", "fnmatch", "functools", "glob", "gzip", "hashlib", "heapq",
|
|
48
|
+
"html", "http", "importlib", "inspect", "io", "ipaddress", "itertools", "json",
|
|
49
|
+
"logging", "math", "mimetypes", "os", "pathlib", "pickle", "platform", "posixpath",
|
|
50
|
+
"pprint", "queue", "random", "re", "shlex", "shutil", "signal", "socket",
|
|
51
|
+
"sqlite3", "ssl", "stat", "string", "struct", "subprocess", "sys", "tempfile",
|
|
52
|
+
"textwrap", "threading", "time", "tomllib", "traceback", "types", "typing",
|
|
53
|
+
"unicodedata", "unittest", "urllib", "uuid", "venv", "warnings", "weakref",
|
|
54
|
+
"xml", "zipfile", "zlib",
|
|
55
|
+
})
|
|
56
|
+
PROJECT_PACKAGES = frozenset({"scripts", "skill_tools"}) # internal imports are fine.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _violations_for(path: Path) -> List[str]:
|
|
60
|
+
"""Return a list of violation strings for one tool (empty = clean)."""
|
|
61
|
+
out: List[str] = []
|
|
62
|
+
name = path.name
|
|
63
|
+
if not NAME_RE.match(name):
|
|
64
|
+
out.append(f"naming: `{name}` is not snake_case_verb_noun.py")
|
|
65
|
+
|
|
66
|
+
text = path.read_text(encoding="utf-8")
|
|
67
|
+
loc = sum(1 for ln in text.splitlines() if ln.strip() and not ln.lstrip().startswith("#"))
|
|
68
|
+
if loc > SIZE_CAP:
|
|
69
|
+
out.append(f"size: {loc} LOC > {SIZE_CAP} cap")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
tree = ast.parse(text, filename=str(path))
|
|
73
|
+
except SyntaxError as exc:
|
|
74
|
+
out.append(f"syntax: {exc.msg} at line {exc.lineno}")
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
# Imports — flag any non-stdlib, non-project top-level module + record argparse use.
|
|
78
|
+
imported: set[str] = set()
|
|
79
|
+
for node in ast.walk(tree):
|
|
80
|
+
if isinstance(node, ast.Import):
|
|
81
|
+
for alias in node.names:
|
|
82
|
+
root = alias.name.split(".")[0]
|
|
83
|
+
imported.add(root)
|
|
84
|
+
if root not in STDLIB and root not in PROJECT_PACKAGES:
|
|
85
|
+
out.append(f"stdlib-only: imports `{alias.name}` (third-party)")
|
|
86
|
+
elif isinstance(node, ast.ImportFrom):
|
|
87
|
+
if node.module is None or node.level > 0:
|
|
88
|
+
continue # relative imports — package-internal
|
|
89
|
+
root = node.module.split(".")[0]
|
|
90
|
+
imported.add(root)
|
|
91
|
+
if root not in STDLIB and root not in PROJECT_PACKAGES:
|
|
92
|
+
out.append(f"stdlib-only: imports from `{node.module}` (third-party)")
|
|
93
|
+
|
|
94
|
+
# CLI flags — confirm argparse is imported and `--json` is registered.
|
|
95
|
+
has_argparse = "argparse" in imported
|
|
96
|
+
has_json_flag = re.search(r"['\"]--json['\"]", text) is not None
|
|
97
|
+
if not has_argparse:
|
|
98
|
+
out.append("cli: no `argparse` import detected")
|
|
99
|
+
if not has_json_flag:
|
|
100
|
+
out.append("cli: missing `--json` flag")
|
|
101
|
+
# `--help` is auto-registered by argparse; we sanity-check that
|
|
102
|
+
# add_help isn't disabled.
|
|
103
|
+
if re.search(r"add_help\s*=\s*False", text):
|
|
104
|
+
out.append("cli: `add_help=False` disables --help")
|
|
105
|
+
|
|
106
|
+
# Embedded sample data — accept either a module-level _SAMPLE constant
|
|
107
|
+
# or a __main__ block (the tool can self-demo).
|
|
108
|
+
has_sample = bool(re.search(r"^_SAMPLE\s*[:=]", text, re.MULTILINE))
|
|
109
|
+
has_main = '__name__ == "__main__"' in text or "__name__ == '__main__'" in text
|
|
110
|
+
if not (has_sample or has_main):
|
|
111
|
+
out.append("sample: no `_SAMPLE` constant or `__main__` block")
|
|
112
|
+
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def lint(tools_dir: Path) -> Tuple[int, Dict[str, List[str]]]:
|
|
117
|
+
tools_dir = tools_dir.resolve()
|
|
118
|
+
if not tools_dir.is_dir():
|
|
119
|
+
return 2, {"_error": [f"tools dir missing: {tools_dir}"]}
|
|
120
|
+
findings: Dict[str, List[str]] = {}
|
|
121
|
+
for path in sorted(tools_dir.glob("*.py")):
|
|
122
|
+
if path.name == "__init__.py":
|
|
123
|
+
continue
|
|
124
|
+
viols = _violations_for(path)
|
|
125
|
+
if viols:
|
|
126
|
+
try:
|
|
127
|
+
key = str(path.relative_to(ROOT))
|
|
128
|
+
except ValueError:
|
|
129
|
+
key = str(path)
|
|
130
|
+
findings[key] = viols
|
|
131
|
+
return (1 if findings else 0), findings
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _print_human(findings: Dict[str, List[str]]) -> None:
|
|
135
|
+
if not findings:
|
|
136
|
+
print(f"✅ scripts/skill_tools/ — all tools clean.")
|
|
137
|
+
return
|
|
138
|
+
print(f"❌ scripts/skill_tools/ — {len(findings)} tool(s) with violations:")
|
|
139
|
+
for fp, viols in findings.items():
|
|
140
|
+
print(f" {fp}:")
|
|
141
|
+
for v in viols:
|
|
142
|
+
print(f" - {v}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main(argv: List[str] | None = None) -> int:
|
|
146
|
+
parser = argparse.ArgumentParser(
|
|
147
|
+
description="Lint scripts/skill_tools/*.py against Block D pilot invariants.",
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument("--json", action="store_true", help="emit JSON instead of text")
|
|
150
|
+
parser.add_argument("--quiet", action="store_true",
|
|
151
|
+
help="suppress the clean-pass success line (errors still print)")
|
|
152
|
+
parser.add_argument("--tools-dir", default=str(TOOLS_DIR),
|
|
153
|
+
help="directory to lint (default: scripts/skill_tools)")
|
|
154
|
+
args = parser.parse_args(argv)
|
|
155
|
+
|
|
156
|
+
code, findings = lint(Path(args.tools_dir))
|
|
157
|
+
if args.json:
|
|
158
|
+
json.dump({"exit_code": code, "findings": findings}, sys.stdout, indent=2)
|
|
159
|
+
sys.stdout.write("\n")
|
|
160
|
+
elif findings or not args.quiet:
|
|
161
|
+
_print_human(findings)
|
|
162
|
+
return code
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
_SAMPLE = {"violations": ["stdlib-only: imports `requests` (third-party)"]}
|
|
166
|
+
|
|
167
|
+
if __name__ == "__main__":
|
|
168
|
+
raise SystemExit(main())
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"$comment": "Source: agents/docs/frontmatter-contract.md#skills. Keep in sync with inventory_frontmatter.py and the linter's lint_skill().",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
|
-
"required": ["name", "description", "source"],
|
|
8
|
+
"required": ["name", "description", "source", "domain"],
|
|
9
9
|
"properties": {
|
|
10
10
|
"name": {
|
|
11
11
|
"type": "string",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"type": "string",
|
|
23
23
|
"enum": ["package", "project"]
|
|
24
24
|
},
|
|
25
|
+
"domain": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"enum": ["engineering", "product", "quality", "devops", "process", "discovery"],
|
|
28
|
+
"description": "Skill classification axis; see docs/contracts/skill-domains.md. Required for every skill — exactly one of: engineering, product, quality, devops, process, discovery."
|
|
29
|
+
},
|
|
25
30
|
"status": {
|
|
26
31
|
"type": "string",
|
|
27
32
|
"enum": ["active", "deprecated", "superseded"]
|
|
@@ -62,6 +67,15 @@
|
|
|
62
67
|
"enum": ["deep"],
|
|
63
68
|
"description": "Optional reasoning-depth marker for AI Council invocations triggered by this skill. The only accepted value is 'deep'; omit the key for default depth (setting 'standard' is rejected — every frontmatter byte counts against the context window, and 'standard' is the implicit default). 'deep' instructs the host agent to pass --depth deep to council_cli, which floors rounds at max(ai_council.deep_min_rounds, ai_council.min_rounds). Use for architecture, refactoring, or bug-diagnosis skills. See .agent-src.uncompressed/skills/ai-council/SKILL.md."
|
|
64
69
|
},
|
|
70
|
+
"context_spine": {
|
|
71
|
+
"type": "array",
|
|
72
|
+
"uniqueItems": true,
|
|
73
|
+
"items": {
|
|
74
|
+
"type": "string",
|
|
75
|
+
"enum": ["product", "team", "repo"]
|
|
76
|
+
},
|
|
77
|
+
"description": "Senior-skill opt-in for the tri-slot context spine. Declares which slots under agents/context-spine/ the skill expects to read (product, team, repo). Council Q1 (KEEP-3) locks the slot count at 3; additions require ≥ 2 citing skills + ADR per docs/contracts/context-spine.md § 5."
|
|
78
|
+
},
|
|
65
79
|
"execution": {
|
|
66
80
|
"type": "object",
|
|
67
81
|
"additionalProperties": false,
|
package/scripts/skill_linter.py
CHANGED
|
@@ -41,15 +41,22 @@ from validate_frontmatter import ( # noqa: E402
|
|
|
41
41
|
Severity = Literal["error", "warning", "info"]
|
|
42
42
|
ArtifactType = Literal["skill", "rule", "command", "guideline", "persona", "unknown"]
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
REQUIRED_PERSONA_SECTIONS_CORE = [
|
|
45
45
|
"Focus",
|
|
46
46
|
"Mindset",
|
|
47
47
|
"Unique Questions",
|
|
48
48
|
"Output Expectations",
|
|
49
49
|
"Anti-Patterns",
|
|
50
50
|
]
|
|
51
|
+
REQUIRED_PERSONA_SECTIONS_SPECIALIST = REQUIRED_PERSONA_SECTIONS_CORE + [
|
|
52
|
+
"Critical Rules",
|
|
53
|
+
"Workflows",
|
|
54
|
+
]
|
|
55
|
+
# Back-compat alias — used by tier-agnostic callers; defaults to the core spine.
|
|
56
|
+
REQUIRED_PERSONA_SECTIONS = REQUIRED_PERSONA_SECTIONS_CORE
|
|
51
57
|
VALID_PERSONA_TIERS = {"core", "specialist"}
|
|
52
|
-
|
|
58
|
+
# Locked in docs/contracts/persona-schema.md § 4: core ≤ 120, specialist ≤ 100.
|
|
59
|
+
PERSONA_LINE_BUDGETS = {"core": 120, "specialist": 100}
|
|
53
60
|
|
|
54
61
|
|
|
55
62
|
REQUIRED_SKILL_SECTIONS = [
|
|
@@ -1437,9 +1444,17 @@ def lint_persona(path: Path, text: str) -> LintResult:
|
|
|
1437
1444
|
f"Persona description is {len(parsed['description'])} chars (target ≤ 160)",
|
|
1438
1445
|
))
|
|
1439
1446
|
|
|
1440
|
-
# Required sections
|
|
1447
|
+
# Required sections — tier-aware (per docs/contracts/persona-schema.md § 3).
|
|
1448
|
+
# Core: 5 sections. Specialist: Core-5 + Critical Rules + Workflows.
|
|
1441
1449
|
sections = extract_sections(text)
|
|
1442
|
-
|
|
1450
|
+
tier = parsed.get("tier")
|
|
1451
|
+
if tier == "specialist":
|
|
1452
|
+
required_sections = REQUIRED_PERSONA_SECTIONS_SPECIALIST
|
|
1453
|
+
else:
|
|
1454
|
+
# Default to core sections when tier is missing or invalid; the
|
|
1455
|
+
# tier-enum check above already raised an error in that case.
|
|
1456
|
+
required_sections = REQUIRED_PERSONA_SECTIONS_CORE
|
|
1457
|
+
for required_section in required_sections:
|
|
1443
1458
|
if required_section not in sections:
|
|
1444
1459
|
issues.append(Issue(
|
|
1445
1460
|
"error",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Block D pilot tools for skill discovery and persona auditing.
|
|
2
|
+
|
|
3
|
+
These four tools (D1 meta-linter + D2/D3/D4 functional pilots) ship under
|
|
4
|
+
this subdirectory per Block D council verdict (D-OQ2 → b). Each tool is:
|
|
5
|
+
|
|
6
|
+
- stdlib-only (no third-party imports)
|
|
7
|
+
- exposes ``--help`` and ``--json`` flags
|
|
8
|
+
- named ``snake_case_verb_noun.py``
|
|
9
|
+
- embeds sample data so it runs without external fixtures
|
|
10
|
+
|
|
11
|
+
Layout chosen to make the pilot reversible. If the D5 eval gate fails
|
|
12
|
+
(< 2 of 3 functional tools pass), the directory and its CI hook can be
|
|
13
|
+
removed in a single commit without touching the wider ``scripts/`` tree.
|
|
14
|
+
|
|
15
|
+
Public entry points:
|
|
16
|
+
|
|
17
|
+
- ``scripts/lint_skill_tools.py`` — D1 meta-linter (lives at scripts/ root
|
|
18
|
+
so ``task ci`` picks it up like other linters).
|
|
19
|
+
- ``score_skill_relevance`` — D2 (this dir).
|
|
20
|
+
- ``audit_persona_coverage`` — D3 (this dir).
|
|
21
|
+
- ``suggest_skill_for_task`` — D4 (this dir).
|
|
22
|
+
"""
|