@event4u/agent-config 2.24.0 → 2.26.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/bug-fix.md +1 -0
- package/.agent-src/commands/create-pr/description-only.md +39 -11
- package/.agent-src/commands/create-pr.md +59 -5
- package/.agent-src/commands/feature/roadmap.md +2 -2
- package/.agent-src/commands/fix/seeder.md +3 -2
- package/.agent-src/commands/memory/add.md +3 -3
- package/.agent-src/commands/module/create.md +1 -0
- package/.agent-src/commands/module/explore.md +10 -6
- package/.agent-src/commands/onboard.md +9 -1
- package/.agent-src/commands/optimize/augmentignore.md +52 -20
- package/.agent-src/commands/optimize/rtk.md +56 -30
- package/.agent-src/commands/package-test.md +86 -10
- package/.agent-src/commands/quality-fix.md +49 -27
- package/.agent-src/commands/update-form-request-messages.md +2 -1
- package/.agent-src/commands/video/from-script.md +5 -5
- package/.agent-src/commands/video/storyboard.md +1 -1
- package/.agent-src/contexts/augment-infrastructure.md +4 -7
- package/.agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -1
- package/.agent-src/contexts/contracts/research-schema.md +1 -1
- package/.agent-src/contexts/execution/interrupt-examples.md +34 -0
- package/.agent-src/contexts/execution/roadmap-process-loop.md +69 -14
- package/.agent-src/contexts/skills-and-commands.md +2 -2
- package/.agent-src/personas/README.md +3 -2
- package/.agent-src/personas/ai-video-technical-director.md +2 -2
- package/.agent-src/personas/hollywood-director.md +3 -3
- package/.agent-src/profiles/content_creator.yml +5 -0
- package/.agent-src/rules/architecture.md +24 -10
- package/.agent-src/rules/artifact-drafting-protocol.md +6 -0
- package/.agent-src/rules/augment-edit-discipline.md +28 -0
- package/.agent-src/rules/augment-source-of-truth.md +2 -2
- package/.agent-src/rules/autonomous-execution.md +31 -0
- package/.agent-src/rules/context-hygiene.md +1 -1
- package/.agent-src/rules/domain-adoption-policy.md +4 -5
- package/.agent-src/rules/domain-safety-disclaimer.md +114 -0
- package/.agent-src/rules/domain-safety-pii.md +142 -0
- package/.agent-src/rules/domain-safety-retention.md +86 -0
- package/.agent-src/rules/downstream-changes.md +4 -4
- package/.agent-src/rules/framework-neutrality-in-generic-skills.md +130 -0
- package/.agent-src/rules/git-history-discipline.md +99 -0
- package/.agent-src/rules/media-governance-routing.md +82 -0
- package/.agent-src/rules/minimal-safe-diff.md +6 -0
- package/.agent-src/rules/no-roadmap-references.md +4 -2
- package/.agent-src/rules/persona-governance.md +90 -0
- package/.agent-src/rules/provider-lifecycle-discipline.md +75 -0
- package/.agent-src/rules/roadmap-ci-steps-policy.md +145 -0
- package/.agent-src/rules/roadmap-progress-sync.md +11 -5
- package/.agent-src/rules/user-interrupt-priority.md +46 -0
- package/.agent-src/rules/verify-before-complete.md +11 -2
- package/.agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/.agent-src/skills/ai-council/SKILL.md +1 -0
- package/.agent-src/skills/api-endpoint/SKILL.md +58 -154
- package/.agent-src/skills/api-testing/SKILL.md +11 -0
- package/.agent-src/skills/character-consistency/SKILL.md +12 -1
- package/.agent-src/skills/code-refactoring/SKILL.md +36 -30
- package/.agent-src/skills/code-review/SKILL.md +41 -36
- package/.agent-src/skills/context-authoring/SKILL.md +1 -1
- package/.agent-src/skills/dashboard-design/SKILL.md +1 -2
- package/.agent-src/skills/database/SKILL.md +8 -3
- package/.agent-src/skills/dependency-upgrade/SKILL.md +65 -19
- package/.agent-src/skills/developer-like-execution/SKILL.md +25 -14
- package/.agent-src/skills/eloquent/SKILL.md +1 -1
- package/.agent-src/skills/feature-planning/SKILL.md +1 -1
- package/.agent-src/skills/file-editor/SKILL.md +45 -19
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +2 -2
- package/.agent-src/skills/git-workflow/SKILL.md +135 -2
- package/.agent-src/skills/laravel-api-endpoint/SKILL.md +187 -0
- package/.agent-src/skills/{dto-creator → laravel-dto}/SKILL.md +5 -4
- package/.agent-src/skills/{migration-creator → laravel-migration}/SKILL.md +11 -10
- package/.agent-src/skills/laravel-reverb/SKILL.md +3 -3
- package/.agent-src/skills/{websocket → laravel-websocket}/SKILL.md +4 -3
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
- package/.agent-src/skills/merge-conflicts/SKILL.md +49 -17
- package/.agent-src/skills/migration-architect/SKILL.md +6 -6
- package/.agent-src/skills/module-management/SKILL.md +1 -0
- package/.agent-src/skills/motion-choreographer/SKILL.md +12 -0
- package/.agent-src/skills/multi-tenancy/SKILL.md +15 -8
- package/.agent-src/skills/pest-testing/SKILL.md +18 -0
- package/.agent-src/skills/php-debugging/SKILL.md +28 -0
- package/.agent-src/skills/php-service/SKILL.md +3 -3
- package/.agent-src/skills/pixar-storyteller/SKILL.md +19 -6
- package/.agent-src/skills/playwright-testing/SKILL.md +16 -1
- package/.agent-src/skills/project-analyzer/SKILL.md +68 -42
- package/.agent-src/skills/readme-writing-package/SKILL.md +94 -23
- package/.agent-src/skills/roadmap-management/SKILL.md +1 -1
- package/.agent-src/skills/roadmap-writing/SKILL.md +10 -0
- package/.agent-src/skills/rtk-output-filtering/SKILL.md +23 -8
- package/.agent-src/skills/rule-refactor/SKILL.md +145 -0
- package/.agent-src/skills/rule-writing/SKILL.md +34 -8
- package/.agent-src/skills/scene-expander/SKILL.md +22 -7
- package/.agent-src/skills/security/SKILL.md +38 -29
- package/.agent-src/skills/skill-reviewer/SKILL.md +1 -1
- package/.agent-src/skills/test-driven-development/SKILL.md +4 -4
- package/.agent-src/skills/test-performance/SKILL.md +6 -5
- package/.agent-src/skills/verify-completion-evidence/SKILL.md +24 -27
- package/.agent-src/skills/video-director/SKILL.md +13 -0
- package/.agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/.agent-src/templates/copilot-instructions.md +2 -2
- package/.agent-src/templates/roadmaps.md +16 -0
- package/.agent-src/templates/rule.md +2 -2
- package/.claude-plugin/marketplace.json +6 -4
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +80 -133
- package/README.md +6 -4
- package/config/agent-settings.template.yml +26 -0
- package/docs/architecture.md +2 -2
- package/docs/archive/CHANGELOG-pre-2.25.0.md +191 -0
- package/docs/catalog.md +20 -12
- package/docs/contracts/file-ownership-matrix.json +588 -90
- package/docs/contracts/kernel-membership.md +17 -0
- package/docs/contracts/provider-lifecycle.md +122 -0
- package/docs/contracts/smoke-contracts.md +8 -8
- package/docs/decisions/ADR-011-domain-pack-readiness.md +213 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/getting-started-by-role.md +10 -0
- package/docs/getting-started.md +1 -1
- package/docs/guidelines/php/api-design.md +1 -1
- package/docs/guidelines/php/controllers.md +1 -1
- package/docs/guidelines/php/resources.md +1 -1
- package/docs/guidelines/php/validations.md +1 -1
- package/docs/personas.md +73 -26
- package/docs/profiles.md +9 -4
- package/package.json +1 -1
- package/scripts/_tmp_scan_framework_leakage.py +119 -0
- package/scripts/ai-video/adapters/gemini-veo.sh +5 -0
- package/scripts/ai-video/adapters/higgsfield.sh +6 -0
- package/scripts/ai-video/adapters/kling.sh +5 -0
- package/scripts/ai-video/adapters/openai-images.sh +5 -0
- package/scripts/ai-video/adapters/sora.sh +6 -0
- package/scripts/build_linear_digest.py +0 -1
- package/scripts/check_portability.py +6 -0
- package/scripts/lint_framework_leakage.py +348 -0
- package/scripts/lint_framework_leakage_allowlist.json +476 -0
- package/scripts/lint_media_policy_linkage.py +140 -0
- package/scripts/lint_persona_governance.py +164 -0
- package/scripts/lint_roadmap_ci_steps.py +182 -0
- package/scripts/measure_augment_budget.py +6 -0
- package/scripts/schemas/command.schema.json +5 -0
- package/scripts/schemas/skill.schema.json +5 -0
- package/scripts/skill_linter.py +60 -7
- package/scripts/smoke/kernel.sh +4 -4
- package/scripts/smoke/router.sh +2 -2
- package/scripts/smoke/schema.sh +1 -1
- package/.agent-src/personas/pixar-storyboard-artist.md +0 -98
- package/.agent-src/rules/agent-docs.md +0 -20
- package/.agent-src/rules/augment-portability.md +0 -23
- package/.agent-src/rules/capture-learnings.md +0 -19
- package/.agent-src/rules/docs-sync.md +0 -20
- package/.agent-src/rules/domain-safety-disclaimer-consulting.md +0 -52
- package/.agent-src/rules/domain-safety-disclaimer-financial.md +0 -54
- package/.agent-src/rules/domain-safety-disclaimer-legal.md +0 -49
- package/.agent-src/rules/domain-safety-disclaimer-medical.md +0 -56
- package/.agent-src/rules/domain-safety-export-redact.md +0 -65
- package/.agent-src/rules/domain-safety-logging-pii-floor.md +0 -55
- package/.agent-src/rules/domain-safety-pii-finance.md +0 -57
- package/.agent-src/rules/domain-safety-pii-marketing.md +0 -60
- package/.agent-src/rules/domain-safety-pii-recruiting.md +0 -56
- package/.agent-src/rules/domain-safety-pii-support.md +0 -57
- package/.agent-src/rules/domain-safety-retention-finance.md +0 -48
- package/.agent-src/rules/domain-safety-retention-support.md +0 -55
- package/.agent-src/rules/e2e-testing.md +0 -19
- package/.agent-src/rules/no-unsolicited-rebase.md +0 -107
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Scan generic skills/rules/commands for framework/language leakage.
|
|
3
|
+
|
|
4
|
+
Carve-out criterion: artifact filename or directory path matches an explicit
|
|
5
|
+
framework/language marker. Everything else MUST be framework-neutral.
|
|
6
|
+
TEMPORARY scanner — delete after audit roadmap is drafted.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
ROOT = Path(".agent-src.uncompressed")
|
|
15
|
+
|
|
16
|
+
CARVE_OUT_PATTERNS = [
|
|
17
|
+
r"laravel", r"^php-", r"^eloquent", r"^blade", r"^livewire", r"^flux",
|
|
18
|
+
r"^pest-", r"^artisan-", r"^composer-", r"^jobs-events$", r"^symfony",
|
|
19
|
+
r"^nextjs", r"^react-", r"^async-python", r"^openapi$", r"^quality-tools",
|
|
20
|
+
r"^sql-writing", r"^tailwind", r"^terraform", r"^terragrunt", r"^traefik",
|
|
21
|
+
r"^mobile-e2e",
|
|
22
|
+
r"^project-analysis-(laravel|symfony|nextjs|react|node-express|zend-laminas)",
|
|
23
|
+
r"^docker", r"^aws-", r"^grafana", r"^playwright",
|
|
24
|
+
r"^laravel-", r"^docker-", r"^symfony-", r"^copilot-", r"^devcontainer",
|
|
25
|
+
r"-routing$",
|
|
26
|
+
]
|
|
27
|
+
CARVE_OUT_RE = re.compile("|".join(CARVE_OUT_PATTERNS), re.IGNORECASE)
|
|
28
|
+
|
|
29
|
+
LEAKAGE = {
|
|
30
|
+
"Laravel": [
|
|
31
|
+
r"\bLaravel\b", r"\bEloquent\b", r"\bArtisan\b", r"\bFormRequest\b",
|
|
32
|
+
r"\bForm Request\b", r"\bBlade\b(?! Runner)", r"\bLivewire\b",
|
|
33
|
+
r"\bResource::(make|collection)\b", r"\bModel::\b",
|
|
34
|
+
r"\bapp/Http/", r"\broutes/(api|web)\.php",
|
|
35
|
+
r"\bdatabase/(migrations|seeders|factories)\b",
|
|
36
|
+
r"\bphp artisan\b", r"\bIlluminate\\\\", r"\bIlluminate\\",
|
|
37
|
+
r"\bbootstrap/app\.php",
|
|
38
|
+
],
|
|
39
|
+
"PHP": [
|
|
40
|
+
r"\bPHPStan\b", r"\bPest\b(?! Control)", r"\bPHPUnit\b", r"\bRector\b",
|
|
41
|
+
r"\bECS\b", r"\bcomposer\.json\b", r"\bvendor/bin/",
|
|
42
|
+
r"\bdeclare\(strict_types=1\)", r"\.php\b",
|
|
43
|
+
r"\bnamespace App\\\\", r"\bnamespace App\\",
|
|
44
|
+
r"\bcomposer (require|install|update|dump-autoload)\b",
|
|
45
|
+
],
|
|
46
|
+
"Symfony": [
|
|
47
|
+
r"\bSymfony\b", r"\bbin/console\b", r"\bDoctrine\b", r"\bTwig\b",
|
|
48
|
+
],
|
|
49
|
+
"JS-specific": [
|
|
50
|
+
r"\bpackage\.json\b",
|
|
51
|
+
r"\bnpm (install|run|test|ci)\b",
|
|
52
|
+
r"\byarn (install|add|test)\b",
|
|
53
|
+
r"\bpnpm (install|add|run|test)\b",
|
|
54
|
+
r"\bnode_modules\b",
|
|
55
|
+
],
|
|
56
|
+
"Python-specific": [
|
|
57
|
+
r"\bpyproject\.toml\b", r"\brequirements\.txt\b",
|
|
58
|
+
r"\bpip install\b", r"\bpytest\b",
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def is_carve_out(path: Path) -> bool:
|
|
64
|
+
for p in path.parts:
|
|
65
|
+
stem = p.removesuffix(".md")
|
|
66
|
+
if CARVE_OUT_RE.search(stem):
|
|
67
|
+
return True
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def scan_file(path: Path) -> dict:
|
|
72
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
73
|
+
lines = text.splitlines()
|
|
74
|
+
hits: dict[str, list[tuple[int, str, str]]] = {}
|
|
75
|
+
for category, patterns in LEAKAGE.items():
|
|
76
|
+
for pat in patterns:
|
|
77
|
+
rx = re.compile(pat)
|
|
78
|
+
for i, line in enumerate(lines, start=1):
|
|
79
|
+
if rx.search(line):
|
|
80
|
+
hits.setdefault(category, []).append(
|
|
81
|
+
(i, pat, line.strip()[:160])
|
|
82
|
+
)
|
|
83
|
+
return hits
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def scan_dir(subdir: str) -> list[tuple[Path, dict]]:
|
|
87
|
+
target = ROOT / subdir
|
|
88
|
+
out = []
|
|
89
|
+
for f in sorted(target.rglob("*.md")):
|
|
90
|
+
if is_carve_out(f):
|
|
91
|
+
continue
|
|
92
|
+
if f.name.startswith("_"):
|
|
93
|
+
continue
|
|
94
|
+
hits = scan_file(f)
|
|
95
|
+
if hits:
|
|
96
|
+
out.append((f, hits))
|
|
97
|
+
return out
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main():
|
|
101
|
+
for label, sub in [("SKILLS", "skills"), ("RULES", "rules"), ("COMMANDS", "commands")]:
|
|
102
|
+
print(f"\n===== {label} (generic, non-carve-out) =====")
|
|
103
|
+
results = scan_dir(sub)
|
|
104
|
+
if not results:
|
|
105
|
+
print(" (clean)")
|
|
106
|
+
continue
|
|
107
|
+
for path, hits in results:
|
|
108
|
+
total = sum(len(v) for v in hits.values())
|
|
109
|
+
print(f"\n [{total:3d}] {path}")
|
|
110
|
+
for cat, items in hits.items():
|
|
111
|
+
print(f" {cat}: {len(items)}")
|
|
112
|
+
for line_no, pat, snippet in items[:6]:
|
|
113
|
+
print(f" L{line_no:4d} /{pat}/ {snippet}")
|
|
114
|
+
if len(items) > 6:
|
|
115
|
+
print(f" ... +{len(items) - 6} more")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
main()
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
# Contract: scripts/ai-video/lib/adapter-contract.md
|
|
10
10
|
# Provider: top-level <provider id="gemini-veo" kind="video"> in
|
|
11
11
|
# agents/.ai-video.xml.
|
|
12
|
+
#
|
|
13
|
+
# Lifecycle: experimental — structural shape conformant; no maintainer
|
|
14
|
+
# real-API smoke trace captured yet. See docs/contracts/provider-lifecycle.md
|
|
15
|
+
# for promotion criteria. The agent must surface this tier and ask
|
|
16
|
+
# before defaulting to this adapter.
|
|
12
17
|
|
|
13
18
|
set -euo pipefail
|
|
14
19
|
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
# agents/.ai-video.xml. Preset → motion-choreographer profile mapping
|
|
12
12
|
# is documented in agents/ai-video/prompts/motion-choreography.md
|
|
13
13
|
# (Phase 6).
|
|
14
|
+
#
|
|
15
|
+
# Lifecycle: experimental — capability-discovery path conformant; no
|
|
16
|
+
# maintainer real-API smoke trace captured yet. See
|
|
17
|
+
# docs/contracts/provider-lifecycle.md for promotion criteria. The
|
|
18
|
+
# agent must surface this tier and ask before defaulting to this
|
|
19
|
+
# adapter.
|
|
14
20
|
|
|
15
21
|
set -euo pipefail
|
|
16
22
|
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
# Contract: scripts/ai-video/lib/adapter-contract.md
|
|
10
10
|
# Provider: top-level <provider id="kling" kind="video"> in
|
|
11
11
|
# agents/.ai-video.xml.
|
|
12
|
+
#
|
|
13
|
+
# Lifecycle: experimental — async submit/poll/fetch contract conformant;
|
|
14
|
+
# no maintainer real-API smoke trace captured yet. See
|
|
15
|
+
# docs/contracts/provider-lifecycle.md for promotion criteria. The agent
|
|
16
|
+
# must surface this tier and ask before defaulting to this adapter.
|
|
12
17
|
|
|
13
18
|
set -euo pipefail
|
|
14
19
|
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
# Contract: scripts/ai-video/lib/adapter-contract.md
|
|
10
10
|
# Provider: top-level <provider id="openai-images" kind="image"> in
|
|
11
11
|
# agents/.ai-video.xml.
|
|
12
|
+
#
|
|
13
|
+
# Lifecycle: experimental — structural shape conformant; no maintainer
|
|
14
|
+
# real-API smoke trace captured yet. See docs/contracts/provider-lifecycle.md
|
|
15
|
+
# for promotion criteria. The agent must surface this tier and ask
|
|
16
|
+
# before defaulting to this adapter.
|
|
12
17
|
|
|
13
18
|
set -euo pipefail
|
|
14
19
|
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
# Contract: scripts/ai-video/lib/adapter-contract.md
|
|
10
10
|
# Provider: top-level <provider id="sora" kind="video"> in
|
|
11
11
|
# agents/.ai-video.xml.
|
|
12
|
+
#
|
|
13
|
+
# Lifecycle: experimental — structural-prompt path conformant; no
|
|
14
|
+
# maintainer real-API smoke trace captured yet. See
|
|
15
|
+
# docs/contracts/provider-lifecycle.md for promotion criteria. The
|
|
16
|
+
# agent must surface this tier and ask before defaulting to this
|
|
17
|
+
# adapter.
|
|
12
18
|
|
|
13
19
|
set -euo pipefail
|
|
14
20
|
|
|
@@ -319,6 +319,12 @@ _TASK_DETECTOR_SKIP = (
|
|
|
319
319
|
"rules/package-ci-checks.md",
|
|
320
320
|
"contexts/communication/rules-auto/package-ci-checks-mechanics.md",
|
|
321
321
|
"contexts/contracts/agents-md-anatomy.md",
|
|
322
|
+
# roadmap-ci-steps-policy defines the gate by listing the forbidden
|
|
323
|
+
# CI-shaped literals; its mechanics doc and the execution loop +
|
|
324
|
+
# authoring skill enumerate the same literals to detect them.
|
|
325
|
+
"rules/roadmap-ci-steps-policy.md",
|
|
326
|
+
"contexts/execution/roadmap-process-loop.md",
|
|
327
|
+
"skills/roadmap-writing/SKILL.md",
|
|
322
328
|
)
|
|
323
329
|
|
|
324
330
|
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Lint generic skills/rules/commands for framework/language leakage.
|
|
3
|
+
|
|
4
|
+
Exits 1 on hit; CI-blocking. Enforces
|
|
5
|
+
`.agent-src.uncompressed/rules/framework-neutrality-in-generic-skills.md`.
|
|
6
|
+
|
|
7
|
+
Allowlist legitimate cross-stack docs in
|
|
8
|
+
`scripts/lint_framework_leakage_allowlist.json`.
|
|
9
|
+
|
|
10
|
+
Carve-out semantics: an artifact whose filename or any parent directory
|
|
11
|
+
matches an explicit framework/language marker (e.g. `laravel-*`,
|
|
12
|
+
`nextjs-*`, `pest-*`) is exempt — these are correctly framework-specific.
|
|
13
|
+
|
|
14
|
+
Inventory exemption: descriptive files that name carve-outs as
|
|
15
|
+
*catalog entries* rather than mandating them in a generic skill are
|
|
16
|
+
exempt. This covers `contexts/**/*.md` (cross-reference tables,
|
|
17
|
+
guideline indexes) and the top-level `README.md` (skills inventory).
|
|
18
|
+
A linter that targets mandate-leakage cannot meaningfully scan an
|
|
19
|
+
inventory of mandate-bearing artifacts.
|
|
20
|
+
|
|
21
|
+
Auto cross-stack detection (Step 0.5 of audit roadmap): when a hit's
|
|
22
|
+
line OR any of the ±2 surrounding lines contains a pattern from a
|
|
23
|
+
different ecosystem family (php / js / python), the hit is marked
|
|
24
|
+
`cross_stack=True` and skipped without consulting the allowlist.
|
|
25
|
+
"""
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import re
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Iterable
|
|
34
|
+
|
|
35
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
36
|
+
DEFAULT_PATHS = (
|
|
37
|
+
".agent-src.uncompressed/skills",
|
|
38
|
+
".agent-src.uncompressed/rules",
|
|
39
|
+
".agent-src.uncompressed/commands",
|
|
40
|
+
)
|
|
41
|
+
ALLOWLIST_FILE = REPO_ROOT / "scripts/lint_framework_leakage_allowlist.json"
|
|
42
|
+
|
|
43
|
+
CARVE_OUT_PATTERNS = [
|
|
44
|
+
r"laravel", r"^php-", r"^eloquent", r"^blade", r"^livewire", r"^flux",
|
|
45
|
+
r"^pest-", r"^artisan-", r"^composer-", r"^jobs-events$", r"^symfony",
|
|
46
|
+
r"^nextjs", r"^react-", r"^async-python", r"^openapi$", r"^quality-tools",
|
|
47
|
+
r"^sql-writing", r"^tailwind", r"^terraform", r"^terragrunt", r"^traefik",
|
|
48
|
+
r"^mobile-e2e",
|
|
49
|
+
r"^project-analysis-(laravel|symfony|nextjs|react|node-express|zend-laminas)",
|
|
50
|
+
r"^docker", r"^aws-", r"^grafana", r"^playwright",
|
|
51
|
+
r"^laravel-", r"^docker-", r"^symfony-", r"^copilot-", r"^devcontainer",
|
|
52
|
+
r"-routing$",
|
|
53
|
+
]
|
|
54
|
+
CARVE_OUT_RE = re.compile("|".join(CARVE_OUT_PATTERNS), re.IGNORECASE)
|
|
55
|
+
|
|
56
|
+
LEAKAGE: dict[str, list[str]] = {
|
|
57
|
+
"Laravel": [
|
|
58
|
+
r"\bLaravel\b", r"\bEloquent\b", r"\bArtisan\b", r"\bFormRequest\b",
|
|
59
|
+
r"\bForm Request\b", r"\bBlade\b(?! Runner)", r"\bLivewire\b",
|
|
60
|
+
r"\bResource::(make|collection)\b", r"\bModel::\b",
|
|
61
|
+
r"\bapp/Http/", r"\broutes/(api|web)\.php",
|
|
62
|
+
r"\bdatabase/(migrations|seeders|factories)\b",
|
|
63
|
+
r"\bphp artisan\b", r"\bIlluminate\\\\", r"\bIlluminate\\",
|
|
64
|
+
r"\bbootstrap/app\.php",
|
|
65
|
+
],
|
|
66
|
+
"PHP": [
|
|
67
|
+
r"\bPHPStan\b", r"\bPest\b(?! Control)", r"\bPHPUnit\b", r"\bRector\b",
|
|
68
|
+
r"\bECS\b", r"\bcomposer\.json\b", r"\bvendor/bin/",
|
|
69
|
+
r"\bdeclare\(strict_types=1\)", r"\.php\b",
|
|
70
|
+
r"\bnamespace App\\\\", r"\bnamespace App\\",
|
|
71
|
+
r"\bcomposer (require|install|update|dump-autoload)\b",
|
|
72
|
+
],
|
|
73
|
+
"Symfony": [
|
|
74
|
+
r"\bSymfony\b", r"\bbin/console\b", r"\bDoctrine\b", r"\bTwig\b",
|
|
75
|
+
],
|
|
76
|
+
"JS-specific": [
|
|
77
|
+
r"\bpackage\.json\b",
|
|
78
|
+
r"\bnpm (install|run|test|ci)\b",
|
|
79
|
+
r"\byarn (install|add|test)\b",
|
|
80
|
+
r"\bpnpm (install|add|run|test)\b",
|
|
81
|
+
r"\bnode_modules\b",
|
|
82
|
+
],
|
|
83
|
+
"Python-specific": [
|
|
84
|
+
r"\bpyproject\.toml\b", r"\brequirements\.txt\b",
|
|
85
|
+
r"\bpip install\b", r"\bpytest\b",
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
FAMILY: dict[str, str] = {
|
|
90
|
+
"Laravel": "php", "PHP": "php", "Symfony": "php",
|
|
91
|
+
"JS-specific": "js", "Python-specific": "python",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Cross-stack hint keywords. Their presence near a hit signals legitimate
|
|
96
|
+
# multi-stack documentation. They do NOT themselves produce hits.
|
|
97
|
+
CROSS_STACK_HINTS: dict[str, list[str]] = {
|
|
98
|
+
"ruby": [r"\bRails\b", r"\bbin/rails\b", r"\bGemfile\b", r"\bbundle exec\b"],
|
|
99
|
+
"python": [r"\bDjango\b", r"\bFastAPI\b", r"\bFlask\b", r"\bpoetry\b",
|
|
100
|
+
r"\buv (add|sync|run|pip)\b", r"\bvenv\b"],
|
|
101
|
+
"node": [r"\bExpress\b", r"\bNext\.?js\b", r"\bNode\.?js\b", r"\bnpx\b",
|
|
102
|
+
r"\bvitest\b", r"\bjest\b", r"\beslint\b", r"\bprettier\b"],
|
|
103
|
+
"go": [r"\bgo (test|build|run|mod)\b", r"\bgolangci-lint\b", r"\bGoLand\b"],
|
|
104
|
+
"rust": [r"\bcargo (test|build|run|check|fmt|clippy|add|update)\b",
|
|
105
|
+
r"\bClippy\b", r"\brustfmt\b", r"\bCargo\.toml\b"],
|
|
106
|
+
"dotnet": [r"\bdotnet (test|build|run|add|restore)\b", r"\b\.NET\b"],
|
|
107
|
+
"java": [r"\bSpring\b", r"\bmvn (test|clean|install|package)\b",
|
|
108
|
+
r"\bgradle\b", r"\bMaven\b"],
|
|
109
|
+
}
|
|
110
|
+
CROSS_STACK_RE = {fam: re.compile("|".join(pats)) for fam, pats in CROSS_STACK_HINTS.items()}
|
|
111
|
+
|
|
112
|
+
FRONTMATTER_FRAMEWORK_RE = re.compile(
|
|
113
|
+
r"^---\s*\n(.*?)\n---", re.DOTALL | re.MULTILINE
|
|
114
|
+
)
|
|
115
|
+
# Match top-level `framework:` or nested `scope.framework:` (one or more
|
|
116
|
+
# leading spaces tolerated for the nested form).
|
|
117
|
+
FRAMEWORK_KEY_RE = re.compile(
|
|
118
|
+
r"^(?:framework|\s+framework)\s*:\s*(\S+)", re.MULTILINE
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_carve_out(path: Path) -> bool:
|
|
123
|
+
for p in path.parts:
|
|
124
|
+
stem = p.removesuffix(".md")
|
|
125
|
+
if CARVE_OUT_RE.search(stem):
|
|
126
|
+
return True
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def is_inventory_file(path: Path) -> bool:
|
|
131
|
+
"""Descriptive files that name carve-outs in catalog tables.
|
|
132
|
+
|
|
133
|
+
A leakage linter targets *mandates* in generic skills/rules/commands.
|
|
134
|
+
Files that list carve-outs (skills inventory, guideline indexes,
|
|
135
|
+
cross-reference tables) name `laravel-*`, PHPStan, npm, etc. as data,
|
|
136
|
+
not as the only path. Scanning them produces structural false
|
|
137
|
+
positives that no allowlist can sensibly cover.
|
|
138
|
+
|
|
139
|
+
Exempt scopes:
|
|
140
|
+
- `<src>/contexts/**/*.md` — cross-reference tables, guideline
|
|
141
|
+
catalogs, infrastructure maps.
|
|
142
|
+
- top-level `README.md` directly under `.agent-src.uncompressed/`
|
|
143
|
+
or `.agent-src/` — package surface inventory.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
rel = path.relative_to(REPO_ROOT)
|
|
147
|
+
except ValueError:
|
|
148
|
+
return False
|
|
149
|
+
parts = rel.parts
|
|
150
|
+
if "contexts" in parts:
|
|
151
|
+
return True
|
|
152
|
+
if rel.name == "README.md" and len(parts) == 2 and parts[0] in {
|
|
153
|
+
".agent-src.uncompressed", ".agent-src",
|
|
154
|
+
}:
|
|
155
|
+
return True
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def has_framework_frontmatter(path: Path) -> str | None:
|
|
160
|
+
"""Return the framework name if the file declares one in YAML frontmatter."""
|
|
161
|
+
try:
|
|
162
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
163
|
+
except OSError:
|
|
164
|
+
return None
|
|
165
|
+
m = FRONTMATTER_FRAMEWORK_RE.match(text)
|
|
166
|
+
if not m:
|
|
167
|
+
return None
|
|
168
|
+
fm = m.group(1)
|
|
169
|
+
key = FRAMEWORK_KEY_RE.search(fm)
|
|
170
|
+
if key:
|
|
171
|
+
val = key.group(1).strip().strip('"').strip("'")
|
|
172
|
+
if val and val.lower() not in {"none", "null", "~", ""}:
|
|
173
|
+
return val
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _load_allowlist() -> dict:
|
|
178
|
+
if not ALLOWLIST_FILE.is_file():
|
|
179
|
+
return {"entries": []}
|
|
180
|
+
try:
|
|
181
|
+
data = json.loads(ALLOWLIST_FILE.read_text(encoding="utf-8"))
|
|
182
|
+
except (OSError, json.JSONDecodeError):
|
|
183
|
+
return {"entries": []}
|
|
184
|
+
return data
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _allowlisted(rel_path: str, line_no: int, allowlist: dict) -> bool:
|
|
188
|
+
for entry in allowlist.get("entries", []):
|
|
189
|
+
if entry.get("file") != rel_path:
|
|
190
|
+
continue
|
|
191
|
+
lines = entry.get("lines")
|
|
192
|
+
if lines == "*":
|
|
193
|
+
return True
|
|
194
|
+
if isinstance(lines, list) and line_no in lines:
|
|
195
|
+
return True
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _families_in_window(lines: list[str], idx: int, radius: int = 10) -> set[str]:
|
|
200
|
+
"""Families found within ±radius lines.
|
|
201
|
+
|
|
202
|
+
The radius is intentionally wider than a tight paragraph so multi-stack
|
|
203
|
+
sections (e.g. composer / npm / pip blocks separated by a few lines of
|
|
204
|
+
prose) are reliably detected as cross-stack documentation.
|
|
205
|
+
"""
|
|
206
|
+
families: set[str] = set()
|
|
207
|
+
lo = max(0, idx - radius)
|
|
208
|
+
hi = min(len(lines), idx + radius + 1)
|
|
209
|
+
for j in range(lo, hi):
|
|
210
|
+
line = lines[j]
|
|
211
|
+
for category, patterns in LEAKAGE.items():
|
|
212
|
+
fam = FAMILY[category]
|
|
213
|
+
if fam in families:
|
|
214
|
+
continue
|
|
215
|
+
for pat in patterns:
|
|
216
|
+
if re.search(pat, line):
|
|
217
|
+
families.add(fam)
|
|
218
|
+
break
|
|
219
|
+
# Cross-stack hints — keywords that signal multi-stack docs without
|
|
220
|
+
# themselves being leakage patterns (Rails, Django, Express, Go, Rust…).
|
|
221
|
+
for fam, rx in CROSS_STACK_RE.items():
|
|
222
|
+
if fam in families:
|
|
223
|
+
continue
|
|
224
|
+
if rx.search(line):
|
|
225
|
+
families.add(fam)
|
|
226
|
+
return families
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def scan_file(path: Path) -> list[dict]:
|
|
230
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
231
|
+
lines = text.splitlines()
|
|
232
|
+
hits: list[dict] = []
|
|
233
|
+
for category, patterns in LEAKAGE.items():
|
|
234
|
+
for pat in patterns:
|
|
235
|
+
rx = re.compile(pat)
|
|
236
|
+
for i, line in enumerate(lines, start=1):
|
|
237
|
+
if rx.search(line):
|
|
238
|
+
families = _families_in_window(lines, i - 1)
|
|
239
|
+
hits.append({
|
|
240
|
+
"line": i,
|
|
241
|
+
"category": category,
|
|
242
|
+
"pattern": pat,
|
|
243
|
+
"snippet": line.strip()[:160],
|
|
244
|
+
"cross_stack": len(families) >= 2,
|
|
245
|
+
})
|
|
246
|
+
return hits
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def iter_md_files(paths: Iterable[str]) -> Iterable[Path]:
|
|
250
|
+
for raw in paths:
|
|
251
|
+
target = (REPO_ROOT / raw) if not Path(raw).is_absolute() else Path(raw)
|
|
252
|
+
if not target.exists():
|
|
253
|
+
print(f"error: path does not exist: {raw}", file=sys.stderr)
|
|
254
|
+
sys.exit(2)
|
|
255
|
+
if target.is_file() and target.suffix == ".md":
|
|
256
|
+
yield target
|
|
257
|
+
continue
|
|
258
|
+
for f in sorted(target.rglob("*.md")):
|
|
259
|
+
if f.name.startswith("_"):
|
|
260
|
+
continue
|
|
261
|
+
yield f
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def main(argv: list[str] | None = None) -> int:
|
|
265
|
+
parser = argparse.ArgumentParser(
|
|
266
|
+
description="Lint generic skills/rules/commands for framework leakage."
|
|
267
|
+
)
|
|
268
|
+
parser.add_argument("--json", action="store_true", help="emit JSON to stdout")
|
|
269
|
+
parser.add_argument("--quiet", action="store_true", help="only print summary line")
|
|
270
|
+
parser.add_argument(
|
|
271
|
+
"--paths",
|
|
272
|
+
nargs="+",
|
|
273
|
+
default=list(DEFAULT_PATHS),
|
|
274
|
+
help="paths to scan (default: the three generic dirs)",
|
|
275
|
+
)
|
|
276
|
+
args = parser.parse_args(argv)
|
|
277
|
+
|
|
278
|
+
allowlist = _load_allowlist()
|
|
279
|
+
file_hits: list[tuple[Path, list[dict]]] = []
|
|
280
|
+
total_hits = 0
|
|
281
|
+
allowlisted_total = 0
|
|
282
|
+
|
|
283
|
+
for f in iter_md_files(args.paths):
|
|
284
|
+
if is_carve_out(f):
|
|
285
|
+
continue
|
|
286
|
+
if is_inventory_file(f):
|
|
287
|
+
continue
|
|
288
|
+
if has_framework_frontmatter(f):
|
|
289
|
+
continue
|
|
290
|
+
rel = str(f.relative_to(REPO_ROOT))
|
|
291
|
+
raw_hits = scan_file(f)
|
|
292
|
+
if not raw_hits:
|
|
293
|
+
continue
|
|
294
|
+
kept: list[dict] = []
|
|
295
|
+
for h in raw_hits:
|
|
296
|
+
if h["cross_stack"]:
|
|
297
|
+
continue
|
|
298
|
+
if _allowlisted(rel, h["line"], allowlist):
|
|
299
|
+
h["allowlisted"] = True
|
|
300
|
+
allowlisted_total += 1
|
|
301
|
+
continue
|
|
302
|
+
h["allowlisted"] = False
|
|
303
|
+
kept.append(h)
|
|
304
|
+
if kept:
|
|
305
|
+
file_hits.append((f, kept))
|
|
306
|
+
total_hits += len(kept)
|
|
307
|
+
|
|
308
|
+
summary = {
|
|
309
|
+
"total_hits": total_hits,
|
|
310
|
+
"files": len(file_hits),
|
|
311
|
+
"allowlisted": allowlisted_total,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if args.json:
|
|
315
|
+
out = {
|
|
316
|
+
"version": 1,
|
|
317
|
+
"hits": [
|
|
318
|
+
{
|
|
319
|
+
"file": str(p.relative_to(REPO_ROOT)),
|
|
320
|
+
**h,
|
|
321
|
+
}
|
|
322
|
+
for p, hits in file_hits
|
|
323
|
+
for h in hits
|
|
324
|
+
],
|
|
325
|
+
"summary": summary,
|
|
326
|
+
}
|
|
327
|
+
print(json.dumps(out, indent=2))
|
|
328
|
+
return 1 if total_hits else 0
|
|
329
|
+
|
|
330
|
+
if not args.quiet:
|
|
331
|
+
for path, hits in file_hits:
|
|
332
|
+
rel = path.relative_to(REPO_ROOT)
|
|
333
|
+
print(f"\n{rel}")
|
|
334
|
+
for h in hits:
|
|
335
|
+
print(
|
|
336
|
+
f" L{h['line']:4d} {h['category']:<16s}"
|
|
337
|
+
f" /{h['pattern']}/ {h['snippet']}"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
print(
|
|
341
|
+
f"\n{total_hits} hits across {len(file_hits)} files "
|
|
342
|
+
f"({allowlisted_total} allowlisted)"
|
|
343
|
+
)
|
|
344
|
+
return 1 if total_hits else 0
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if __name__ == "__main__":
|
|
348
|
+
sys.exit(main())
|