@ikon85/agent-workflow-kit 0.10.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/.agents/skills/ask-matt/SKILL.md +139 -0
- package/.agents/skills/ask-matt/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/audit-skills/SKILL.md +145 -0
- package/.agents/skills/board-to-waves/SKILL.md +135 -0
- package/.agents/skills/code-review/SKILL.md +64 -0
- package/.agents/skills/codebase-design/DEEPENING.md +37 -0
- package/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/.agents/skills/codebase-design/SKILL.md +114 -0
- package/.agents/skills/codebase-design/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/codex-adapter-sync/SKILL.md +151 -0
- package/.agents/skills/decision-gate/SKILL.md +67 -0
- package/.agents/skills/diagnose/SKILL.md +136 -0
- package/.agents/skills/diagnose/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/.agents/skills/domain-modeling/SKILL.md +76 -0
- package/.agents/skills/domain-modeling/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/git-worktree-recover/SKILL.md +176 -0
- package/.agents/skills/grill-me/SKILL.md +54 -0
- package/.agents/skills/grill-me/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/.agents/skills/grill-with-docs/CONTEXT-FORMAT.md +60 -0
- package/.agents/skills/grill-with-docs/SKILL.md +137 -0
- package/.agents/skills/grill-with-docs/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/implement/SKILL.md +15 -0
- package/.agents/skills/implement/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/improve-codebase-architecture/ADR-FORMAT.md +47 -0
- package/.agents/skills/improve-codebase-architecture/CONTEXT-FORMAT.md +60 -0
- package/.agents/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/.agents/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +46 -0
- package/.agents/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/.agents/skills/improve-codebase-architecture/SKILL.md +88 -0
- package/.agents/skills/improve-codebase-architecture/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/kit-release/SKILL.md +56 -0
- package/.agents/skills/local-ci/SKILL.md +94 -0
- package/.agents/skills/orchestrate-wave/SKILL.md +261 -0
- package/.agents/skills/orchestrate-wave/references/builder-contract.md +67 -0
- package/.agents/skills/prototype/LOGIC.md +79 -0
- package/.agents/skills/prototype/SKILL.md +30 -0
- package/.agents/skills/prototype/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/prototype/UI.md +112 -0
- package/.agents/skills/research/SKILL.md +12 -0
- package/.agents/skills/research/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/.agents/skills/resolving-merge-conflicts/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/retro/SKILL.md +209 -0
- package/.agents/skills/scale-check/SKILL.md +141 -0
- package/.agents/skills/security-audit/SKILL.md +67 -0
- package/.agents/skills/setup-workflow/SKILL.md +187 -0
- package/.agents/skills/setup-workflow/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/setup-workflow/board-sync.md +111 -0
- package/.agents/skills/setup-workflow/code-review.md +26 -0
- package/.agents/skills/setup-workflow/domain.md +51 -0
- package/.agents/skills/setup-workflow/issue-tracker-github.md +35 -0
- package/.agents/skills/setup-workflow/issue-tracker-gitlab.md +34 -0
- package/.agents/skills/setup-workflow/issue-tracker-local.md +30 -0
- package/.agents/skills/setup-workflow/orchestrate-wave-seed.md +44 -0
- package/.agents/skills/setup-workflow/spec-completeness-seed.md +44 -0
- package/.agents/skills/setup-workflow/spec-self-critique-seed.md +43 -0
- package/.agents/skills/setup-workflow/triage-labels.md +18 -0
- package/.agents/skills/setup-workflow/workflow-overview.md +33 -0
- package/.agents/skills/spec-self-critique/SKILL.md +152 -0
- package/.agents/skills/spec-self-critique/scenarios.md +82 -0
- package/.agents/skills/tdd/SKILL.md +120 -0
- package/.agents/skills/tdd/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/tdd/deep-modules.md +33 -0
- package/.agents/skills/tdd/interface-design.md +31 -0
- package/.agents/skills/tdd/mocking.md +59 -0
- package/.agents/skills/tdd/refactoring.md +10 -0
- package/.agents/skills/tdd/tests.md +61 -0
- package/.agents/skills/to-issues/SKILL.md +325 -0
- package/.agents/skills/to-issues/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/to-prd/PROGRAM-PRD-FORMAT.md +162 -0
- package/.agents/skills/to-prd/SKILL.md +180 -0
- package/.agents/skills/to-prd/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/to-waves/SKILL.md +260 -0
- package/.agents/skills/to-waves/SLICE-METADATA-FORMAT.md +74 -0
- package/.agents/skills/triage/AGENT-BRIEF.md +168 -0
- package/.agents/skills/triage/OUT-OF-SCOPE.md +101 -0
- package/.agents/skills/triage/SKILL.md +122 -0
- package/.agents/skills/triage/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/verify-spike/SKILL.md +39 -0
- package/.agents/skills/wayfinder/SKILL.md +127 -0
- package/.agents/skills/wayfinder/THIRD-PARTY-NOTICES.md +29 -0
- package/.agents/skills/wrapup/SKILL.md +71 -0
- package/.agents/skills/wrapup/SOURCES.txt +6 -0
- package/.agents/skills/zoom-out/SKILL.md +7 -0
- package/.agents/skills/zoom-out/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/hooks/_hook_utils.py +184 -0
- package/.claude/hooks/drift-guard.py +176 -0
- package/.claude/hooks/skill-drift-hint.py +122 -0
- package/.claude/hooks/sync-board-status.py +191 -0
- package/.claude/skills/ask-matt/SKILL.md +139 -0
- package/.claude/skills/ask-matt/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/audit-skills/SKILL.md +145 -0
- package/.claude/skills/board-to-waves/SKILL.md +148 -0
- package/.claude/skills/code-review/SKILL.md +64 -0
- package/.claude/skills/codebase-design/DEEPENING.md +37 -0
- package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/.claude/skills/codebase-design/SKILL.md +114 -0
- package/.claude/skills/codebase-design/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/codex-build/SKILL.md +126 -0
- package/.claude/skills/codex-build/THIRD-PARTY-NOTICES.md +35 -0
- package/.claude/skills/codex-review/SKILL.md +145 -0
- package/.claude/skills/codex-review/THIRD-PARTY-NOTICES.md +31 -0
- package/.claude/skills/decision-gate/SKILL.md +67 -0
- package/.claude/skills/diagnose/SKILL.md +136 -0
- package/.claude/skills/diagnose/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/.claude/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/.claude/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/.claude/skills/domain-modeling/SKILL.md +76 -0
- package/.claude/skills/domain-modeling/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/git-guardrails-claude-code/SKILL.md +96 -0
- package/.claude/skills/git-guardrails-claude-code/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/git-guardrails-claude-code/scripts/block-dangerous-git.sh +28 -0
- package/.claude/skills/git-worktree-recover/SKILL.md +182 -0
- package/.claude/skills/grill-me/SKILL.md +54 -0
- package/.claude/skills/grill-me/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/grill-me-codex/SKILL.md +158 -0
- package/.claude/skills/grill-me-codex/THIRD-PARTY-NOTICES.md +32 -0
- package/.claude/skills/grill-with-docs/ADR-FORMAT.md +47 -0
- package/.claude/skills/grill-with-docs/CONTEXT-FORMAT.md +60 -0
- package/.claude/skills/grill-with-docs/SKILL.md +137 -0
- package/.claude/skills/grill-with-docs/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/grill-with-docs-codex/ADR-FORMAT.md +47 -0
- package/.claude/skills/grill-with-docs-codex/CONTEXT-FORMAT.md +60 -0
- package/.claude/skills/grill-with-docs-codex/SKILL.md +233 -0
- package/.claude/skills/grill-with-docs-codex/THIRD-PARTY-NOTICES.md +33 -0
- package/.claude/skills/implement/SKILL.md +15 -0
- package/.claude/skills/implement/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/improve-codebase-architecture/ADR-FORMAT.md +47 -0
- package/.claude/skills/improve-codebase-architecture/CONTEXT-FORMAT.md +60 -0
- package/.claude/skills/improve-codebase-architecture/DEEPENING.md +37 -0
- package/.claude/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/.claude/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +46 -0
- package/.claude/skills/improve-codebase-architecture/LANGUAGE.md +53 -0
- package/.claude/skills/improve-codebase-architecture/SKILL.md +88 -0
- package/.claude/skills/improve-codebase-architecture/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/kit-release/SKILL.md +56 -0
- package/.claude/skills/local-ci/SKILL.md +94 -0
- package/.claude/skills/orchestrate-wave/SKILL.md +269 -0
- package/.claude/skills/orchestrate-wave/references/builder-contract.md +67 -0
- package/.claude/skills/prototype/LOGIC.md +79 -0
- package/.claude/skills/prototype/SKILL.md +30 -0
- package/.claude/skills/prototype/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/prototype/UI.md +112 -0
- package/.claude/skills/research/SKILL.md +12 -0
- package/.claude/skills/research/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/.claude/skills/resolving-merge-conflicts/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/retro/SKILL.md +209 -0
- package/.claude/skills/scale-check/SKILL.md +141 -0
- package/.claude/skills/security-audit/SKILL.md +67 -0
- package/.claude/skills/setup-pre-commit/SKILL.md +104 -0
- package/.claude/skills/setup-pre-commit/scripts/pre-commit.template.sh +35 -0
- package/.claude/skills/setup-workflow/SKILL.md +187 -0
- package/.claude/skills/setup-workflow/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/setup-workflow/board-sync.md +111 -0
- package/.claude/skills/setup-workflow/code-review.md +26 -0
- package/.claude/skills/setup-workflow/domain.md +51 -0
- package/.claude/skills/setup-workflow/issue-tracker-github.md +35 -0
- package/.claude/skills/setup-workflow/issue-tracker-gitlab.md +34 -0
- package/.claude/skills/setup-workflow/issue-tracker-local.md +30 -0
- package/.claude/skills/setup-workflow/orchestrate-wave-seed.md +44 -0
- package/.claude/skills/setup-workflow/spec-completeness-seed.md +44 -0
- package/.claude/skills/setup-workflow/spec-self-critique-seed.md +43 -0
- package/.claude/skills/setup-workflow/triage-labels.md +18 -0
- package/.claude/skills/setup-workflow/workflow-overview.md +33 -0
- package/.claude/skills/skill-manifest.json +389 -0
- package/.claude/skills/spec-self-critique/SKILL.md +152 -0
- package/.claude/skills/spec-self-critique/scenarios.md +82 -0
- package/.claude/skills/tdd/SKILL.md +120 -0
- package/.claude/skills/tdd/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/tdd/deep-modules.md +33 -0
- package/.claude/skills/tdd/interface-design.md +31 -0
- package/.claude/skills/tdd/mocking.md +59 -0
- package/.claude/skills/tdd/refactoring.md +10 -0
- package/.claude/skills/tdd/tests.md +61 -0
- package/.claude/skills/to-issues/SKILL.md +325 -0
- package/.claude/skills/to-issues/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/to-prd/PROGRAM-PRD-FORMAT.md +162 -0
- package/.claude/skills/to-prd/SKILL.md +180 -0
- package/.claude/skills/to-prd/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/to-waves/SKILL.md +260 -0
- package/.claude/skills/to-waves/SLICE-METADATA-FORMAT.md +74 -0
- package/.claude/skills/triage/AGENT-BRIEF.md +168 -0
- package/.claude/skills/triage/OUT-OF-SCOPE.md +101 -0
- package/.claude/skills/triage/SKILL.md +122 -0
- package/.claude/skills/triage/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/verify-spike/SKILL.md +39 -0
- package/.claude/skills/wayfinder/SKILL.md +127 -0
- package/.claude/skills/wayfinder/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/wrapup/SKILL.md +81 -0
- package/.claude/skills/wrapup/SOURCES.txt +6 -0
- package/.claude/skills/write-a-skill/GLOSSARY.md +201 -0
- package/.claude/skills/write-a-skill/SKILL.md +88 -0
- package/.claude/skills/write-a-skill/THIRD-PARTY-NOTICES.md +29 -0
- package/.claude/skills/zoom-out/SKILL.md +7 -0
- package/.claude/skills/zoom-out/THIRD-PARTY-NOTICES.md +29 -0
- package/LICENSE +28 -0
- package/PROVENANCE.md +39 -0
- package/README.md +619 -0
- package/agent-workflow-kit.package.json +1939 -0
- package/docs/agents/board-sync.md +71 -0
- package/docs/agents/code-review.md +14 -0
- package/docs/agents/domain.md +7 -0
- package/docs/agents/issue-tracker.md +12 -0
- package/docs/agents/security-audit-runbook-template.md +205 -0
- package/docs/agents/skills/orchestrate-wave.md +13 -0
- package/docs/agents/skills/spec-self-critique.md +18 -0
- package/docs/agents/triage-labels.md +10 -0
- package/docs/agents/wave-anchor-template.md +88 -0
- package/docs/conventions/spec-completeness.md +23 -0
- package/docs/index.html +56 -0
- package/docs/methodology.html +488 -0
- package/docs/methodology.svg +93 -0
- package/docs/workflow.png +0 -0
- package/package.json +40 -0
- package/scripts/anchor_table.py +343 -0
- package/scripts/board-sync.py +1232 -0
- package/scripts/board_config.py +147 -0
- package/scripts/board_fields.py +192 -0
- package/scripts/build-kit.mjs +116 -0
- package/scripts/build-kit.test.mjs +59 -0
- package/scripts/check-kit-staleness.mjs +45 -0
- package/scripts/check-kit-staleness.test.mjs +19 -0
- package/scripts/execute-ready-check.py +516 -0
- package/scripts/issue_deps.py +79 -0
- package/scripts/kit-release.mjs +100 -0
- package/scripts/kit-release.test.mjs +67 -0
- package/scripts/lib/audit-refs.mjs +146 -0
- package/scripts/lib/scrub.mjs +113 -0
- package/scripts/lib/scrub.test.mjs +187 -0
- package/scripts/loc_offender_core.py +196 -0
- package/scripts/loc_offender_gate.py +182 -0
- package/scripts/node_kind.py +68 -0
- package/scripts/portability_profile_scan.py +53 -0
- package/scripts/pr-body-check.py +263 -0
- package/scripts/program_graph.py +125 -0
- package/scripts/program_graph_parse.py +275 -0
- package/scripts/program_graph_validate.py +256 -0
- package/scripts/program_sync.py +160 -0
- package/scripts/release-delta-guard.mjs +96 -0
- package/scripts/release-delta-guard.test.mjs +53 -0
- package/scripts/test_board_sync_create_idempotency.py +104 -0
- package/scripts/test_dist_kit_smoke.py +199 -0
- package/scripts/test_kit_docs_language_census.py +178 -0
- package/scripts/test_skill_code_review_seed.py +89 -0
- package/scripts/test_skill_frontmatter_lint.py +226 -0
- package/scripts/test_skill_gh_lint.py +124 -0
- package/scripts/test_skill_language_census.py +254 -0
- package/scripts/test_skill_portability_lint.py +773 -0
- package/scripts/test_skill_publish_audit.py +300 -0
- package/scripts/test_skill_selfcontainment_lint.py +255 -0
- package/scripts/test_skill_setup_workflow_seeds.py +200 -0
- package/scripts/test_skill_stale_name_lint.py +126 -0
- package/scripts/test_skill_surface_refs.py +300 -0
- package/scripts/test_skill_trailing_artifact_lint.py +71 -0
- package/scripts/wrapup-land.py +573 -0
- package/src/cli.mjs +70 -0
- package/src/commands/diff.mjs +6 -0
- package/src/commands/init.mjs +59 -0
- package/src/commands/uninstall.mjs +43 -0
- package/src/commands/update.mjs +95 -0
- package/src/lib/atomicWrite.mjs +49 -0
- package/src/lib/bundle.mjs +133 -0
- package/src/lib/hash.mjs +13 -0
- package/src/lib/manifest.mjs +58 -0
- package/src/lib/sentinel.mjs +20 -0
- package/src/lib/settings.mjs +23 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""dist-kit smoke — proves the BUILT kit's `validate-graph` imports + runs
|
|
3
|
+
(Welle 52 / Slice 7 / #1936, PLAN Step 15).
|
|
4
|
+
|
|
5
|
+
Why: `bundle.mjs`'s HELPER_FILES allowlist is the only thing that ships
|
|
6
|
+
program_graph.py / program_graph_parse.py / program_graph_validate.py /
|
|
7
|
+
node_kind.py / board_fields.py / program_sync.py into dist-kit/scripts/ — all
|
|
8
|
+
six are imported UNCONDITIONALLY at board-sync.py's module top (not lazily
|
|
9
|
+
inside a subcommand handler), so a forgotten entry does not fail at build time,
|
|
10
|
+
it fails the FIRST time a consumer runs the shipped CLI (ImportError). This
|
|
11
|
+
test catches that class of regression by actually invoking `validate-graph`
|
|
12
|
+
against the built dist-kit/, not the scripts/ SSOT copy the rest of the suite
|
|
13
|
+
imports.
|
|
14
|
+
|
|
15
|
+
Fully offline: a throwaway consumer board profile (BOARD_SYNC_PROFILE env var)
|
|
16
|
+
stands in for a project layer, and a fake `gh` shim on PATH answers the two
|
|
17
|
+
read-only `gh` calls `validate-graph` makes (the PRD body + the empty
|
|
18
|
+
sub-issues/children query) — no network, no real gh auth, no live board.
|
|
19
|
+
|
|
20
|
+
Run: python3 scripts/test_dist_kit_smoke.py
|
|
21
|
+
"""
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import shutil
|
|
25
|
+
import stat
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import unittest
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
33
|
+
DIST = REPO / "dist-kit"
|
|
34
|
+
|
|
35
|
+
# A minimal but structurally complete consumer profile (see board_config.py's
|
|
36
|
+
# _REQUIRED_PATHS) — distinct ids from Testreporter's own, so a leak would be
|
|
37
|
+
# obvious. `fields.phase` is included (with the same shape #1936 gives the
|
|
38
|
+
# real Testreporter profile) so the fixture also exercises the Phase-aware
|
|
39
|
+
# path in validate-graph, not just its defensive None-fallback.
|
|
40
|
+
FIXTURE_PROFILE = {
|
|
41
|
+
"repo": "acme/widgets",
|
|
42
|
+
"project": {"number": 7, "owner": "acme", "nodeId": "PVT_FIXTURE"},
|
|
43
|
+
"fields": {
|
|
44
|
+
"status": {"id": "PVTSSF_FIXTURE", "options": {"Spec": "opt-spec"}},
|
|
45
|
+
"wave": "PVTF_FIXTURE_WAVE",
|
|
46
|
+
"cluster": "PVTF_FIXTURE_CLUSTER",
|
|
47
|
+
"specPath": "PVTF_FIXTURE_SPEC",
|
|
48
|
+
"planPath": "PVTF_FIXTURE_PLAN",
|
|
49
|
+
"phase": {"id": "PVTSSF_FIXTURE_PHASE",
|
|
50
|
+
"options": {"P1": "opt-p1", "P2": "opt-p2", "P3": "opt-p3"}},
|
|
51
|
+
},
|
|
52
|
+
"labels": {"readyForAgent": "ready-for-agent", "typePrefix": "type:",
|
|
53
|
+
"clusterType": "type:cluster", "waveStub": "wave-stub"},
|
|
54
|
+
"branchPrefixes": ["feat", "fix"],
|
|
55
|
+
"prMarkers": {"partOf": "Part of", "retroMarker": "**Retro:**",
|
|
56
|
+
"retroValues": ["ran", "skipped"]},
|
|
57
|
+
"headings": {"vorBau": "Clarify Before Build"},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# A minimal green Program-PRD body — one wave, two slices, full scope coverage,
|
|
61
|
+
# no cycles/backward-refs/gate warnings — the same shape as test_program_graph.py's
|
|
62
|
+
# GREEN_PRD fixture, duplicated here (not imported) so this smoke stays
|
|
63
|
+
# self-contained and independent of the SSOT test module's import side effects.
|
|
64
|
+
GREEN_PRD = (
|
|
65
|
+
"<!-- prd: program -->\n"
|
|
66
|
+
"**plan_revision:** r1\n\n"
|
|
67
|
+
"## Scope\n"
|
|
68
|
+
"- **S1:** Programm-Graph-Fundament\n"
|
|
69
|
+
"- **S2:** Execute-Ready-Erweiterung\n\n"
|
|
70
|
+
"## Wellenplan\n"
|
|
71
|
+
"<!-- wellenplan:start -->\n"
|
|
72
|
+
"| Welle | Name | Phase | Slices | Gate | covers |\n"
|
|
73
|
+
"|---|---|---|---|---|---|\n"
|
|
74
|
+
"| 1 | Fundament | P1 | 1a, 1b | — | S1,S2 |\n"
|
|
75
|
+
"<!-- wellenplan:end -->\n\n"
|
|
76
|
+
"## Phasen-Gates\n"
|
|
77
|
+
"- [ ] P1: Fundament steht\n\n"
|
|
78
|
+
"## Slices\n"
|
|
79
|
+
"#### 1a — Graph-Modul\n"
|
|
80
|
+
"<!-- wave: 1 -->\n"
|
|
81
|
+
"<!-- phase: P1 -->\n"
|
|
82
|
+
"<!-- area: scripts -->\n"
|
|
83
|
+
"<!-- gate: — -->\n"
|
|
84
|
+
"<!-- blocked_by: none -->\n\n"
|
|
85
|
+
"#### 1b — Execute-Ready\n"
|
|
86
|
+
"<!-- wave: 1 -->\n"
|
|
87
|
+
"<!-- phase: P1 -->\n"
|
|
88
|
+
"<!-- area: scripts -->\n"
|
|
89
|
+
"<!-- gate: — -->\n"
|
|
90
|
+
"<!-- blocked_by: 1a -->\n"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# A fake `gh` binary: answers the sub-issues/children GraphQL query (empty —
|
|
94
|
+
# no published stubs yet) and the PRD-body `issue view` read from a file, so
|
|
95
|
+
# `validate-graph` runs end to end without any real `gh` call.
|
|
96
|
+
FAKE_GH = """#!/usr/bin/env python3
|
|
97
|
+
import os, sys
|
|
98
|
+
args = sys.argv[1:]
|
|
99
|
+
if "subIssues" in " ".join(args):
|
|
100
|
+
sys.stdout.write('{"data":{"repository":{"issue":{"subIssues":{"nodes":[]}}}}}')
|
|
101
|
+
else:
|
|
102
|
+
with open(os.environ["DIST_KIT_SMOKE_BODY"], encoding="utf-8") as fh:
|
|
103
|
+
sys.stdout.write(fh.read())
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _build_dist_kit():
|
|
108
|
+
"""Run the real build (subprocess — the maintainer's actual publish step).
|
|
109
|
+
Returns (CompletedProcess, skip_reason); skip_reason is set only when node
|
|
110
|
+
itself is unavailable in this environment (mirrors test_skill_publish_audit.py)."""
|
|
111
|
+
build = REPO / "scripts/build-kit.mjs"
|
|
112
|
+
try:
|
|
113
|
+
r = subprocess.run(["node", str(build)], cwd=REPO,
|
|
114
|
+
capture_output=True, text=True, timeout=120)
|
|
115
|
+
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
|
|
116
|
+
return None, str(e)
|
|
117
|
+
return r, None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DistKitValidateGraphSmoke(unittest.TestCase):
|
|
121
|
+
"""Builds dist-kit/ then runs `board-sync.py validate-graph` from the
|
|
122
|
+
SHIPPED location — proves the whole program_graph* / node_kind / board_fields
|
|
123
|
+
/ program_sync import chain resolves in the actual publish artefact, and
|
|
124
|
+
that the command produces the correct counted report on a known fixture."""
|
|
125
|
+
|
|
126
|
+
def setUp(self):
|
|
127
|
+
r, skip_reason = _build_dist_kit()
|
|
128
|
+
if skip_reason:
|
|
129
|
+
self.skipTest(f"node/build unavailable: {skip_reason}")
|
|
130
|
+
self.assertEqual(r.returncode, 0, r.stderr)
|
|
131
|
+
self.board_sync_py = DIST / "scripts/board-sync.py"
|
|
132
|
+
self.assertTrue(self.board_sync_py.exists(), "dist-kit/scripts/board-sync.py missing")
|
|
133
|
+
|
|
134
|
+
self.tmp = Path(tempfile.mkdtemp())
|
|
135
|
+
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
|
136
|
+
|
|
137
|
+
profile_md = ("<!-- board-sync:profile -->\n```json\n"
|
|
138
|
+
+ json.dumps(FIXTURE_PROFILE) + "\n```\n")
|
|
139
|
+
profile_path = self.tmp / "board-sync.md"
|
|
140
|
+
profile_path.write_text(profile_md, encoding="utf-8")
|
|
141
|
+
|
|
142
|
+
body_path = self.tmp / "prd-body.md"
|
|
143
|
+
body_path.write_text(GREEN_PRD, encoding="utf-8")
|
|
144
|
+
|
|
145
|
+
fake_gh_dir = self.tmp / "bin"
|
|
146
|
+
fake_gh_dir.mkdir()
|
|
147
|
+
gh_path = fake_gh_dir / "gh"
|
|
148
|
+
gh_path.write_text(FAKE_GH, encoding="utf-8")
|
|
149
|
+
gh_path.chmod(gh_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
150
|
+
|
|
151
|
+
self.env = {
|
|
152
|
+
**os.environ,
|
|
153
|
+
"PATH": f"{fake_gh_dir}{os.pathsep}{os.environ.get('PATH', '')}",
|
|
154
|
+
"BOARD_SYNC_PROFILE": str(profile_path),
|
|
155
|
+
"DIST_KIT_SMOKE_BODY": str(body_path),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def test_validate_graph_runs_against_the_built_kit(self):
|
|
159
|
+
result = subprocess.run(
|
|
160
|
+
[sys.executable, str(self.board_sync_py), "validate-graph", "--issue", "42"],
|
|
161
|
+
capture_output=True, text=True, timeout=30, env=self.env,
|
|
162
|
+
)
|
|
163
|
+
self.assertEqual(result.returncode, 0,
|
|
164
|
+
f"stdout={result.stdout!r} stderr={result.stderr!r}")
|
|
165
|
+
self.assertIn("Scope-Abdeckung 2 von 2", result.stdout)
|
|
166
|
+
self.assertIn("Rollup-Kette ✓", result.stdout)
|
|
167
|
+
self.assertIn("zyklenfrei ✓", result.stdout)
|
|
168
|
+
self.assertIn("Kapazität ✓", result.stdout)
|
|
169
|
+
self.assertIn("Phasen-Optionen ✓", result.stdout)
|
|
170
|
+
|
|
171
|
+
def test_no_german_status_vocabulary_ships(self):
|
|
172
|
+
"""kit#26 / #1947 acceptance, verbatim: a fresh consumer install must
|
|
173
|
+
grep clean for testreporter's German board-status names. The status
|
|
174
|
+
vocabulary lives ONLY in a board's own profile (fields.status.roles) —
|
|
175
|
+
shipped scripts, skills, and doc templates speak role language. This
|
|
176
|
+
greps the whole BUILT artefact, so any future spot the word-list
|
|
177
|
+
census or the command lint can't see (a bare status name carries no
|
|
178
|
+
German function word and no board-sync.py call) still fails here."""
|
|
179
|
+
import re
|
|
180
|
+
pat = re.compile(r"\bIn Arbeit\b|\bIdee\b")
|
|
181
|
+
hits = []
|
|
182
|
+
for path in sorted(DIST.rglob("*")):
|
|
183
|
+
if not path.is_file():
|
|
184
|
+
continue
|
|
185
|
+
try:
|
|
186
|
+
text = path.read_text(encoding="utf-8")
|
|
187
|
+
except (UnicodeDecodeError, PermissionError):
|
|
188
|
+
continue
|
|
189
|
+
for n, line in enumerate(text.splitlines(), 1):
|
|
190
|
+
if pat.search(line):
|
|
191
|
+
hits.append(f"{path.relative_to(DIST)}:{n}: {line.strip()[:120]}")
|
|
192
|
+
self.assertEqual(hits, [],
|
|
193
|
+
"German board-status vocabulary in the shipped kit — speak role "
|
|
194
|
+
"language (fields.status.roles / --status-role) instead:\n"
|
|
195
|
+
+ "\n".join(hits))
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""dist-kit doc language census (#1953) — a coarse net over EVERY shipped
|
|
3
|
+
`.md` file (docs/templates, README, SKILL.md bodies alike), not just
|
|
4
|
+
`publish: true` skill bodies (that zero-tolerance census already exists in
|
|
5
|
+
`test_skill_language_census.py`). This guard is a percentage smoke test: a
|
|
6
|
+
kit doc is red once ≥10% of its lines carry German prose, so an
|
|
7
|
+
English-speaking consumer never again silently gets a majority-German
|
|
8
|
+
template the way `docs/agents/wave-anchor-template.md` shipped before this
|
|
9
|
+
slice (23/100 lines German — the census #1953 was written to catch).
|
|
10
|
+
|
|
11
|
+
Reuses `test_skill_language_census.py`'s wordlist + line-classification
|
|
12
|
+
helpers (`_line_hits`, `strip_frontmatter`, the bilingual
|
|
13
|
+
`<user-story-example lang="de">` block skip) rather than a parallel
|
|
14
|
+
heuristic — same STRONG/WEAK wordlist, same allowlist exemptions (contract
|
|
15
|
+
literals, `Welle <N>` title format, the inline `language-census: ok`
|
|
16
|
+
marker).
|
|
17
|
+
|
|
18
|
+
Two test classes:
|
|
19
|
+
- `RatioFixtures` — pure-function unit tests, no build. Proves the ratio
|
|
20
|
+
computation goes RED on an artificially-German fixture and GREEN on an
|
|
21
|
+
all-English one, independent of the real dist-kit build.
|
|
22
|
+
- `DistKitDocsCensus` — builds the REAL dist-kit (subprocess, like
|
|
23
|
+
`test_dist_kit_smoke.py`) and asserts every shipped `.md` file is under
|
|
24
|
+
the threshold.
|
|
25
|
+
|
|
26
|
+
Run: python3 scripts/test_kit_docs_language_census.py
|
|
27
|
+
"""
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
import unittest
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from test_skill_language_census import (
|
|
34
|
+
BILINGUAL_BLOCK_END,
|
|
35
|
+
BILINGUAL_BLOCK_START,
|
|
36
|
+
_line_hits,
|
|
37
|
+
strip_frontmatter,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
41
|
+
DIST = REPO / "dist-kit"
|
|
42
|
+
|
|
43
|
+
GERMAN_LINE_THRESHOLD_PCT = 10.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def german_line_ratio(text: str) -> tuple[int, int]:
|
|
47
|
+
"""(german_hit_lines, total_lines) for a doc body — frontmatter stripped,
|
|
48
|
+
bilingual `<user-story-example lang="de">...</user-story-example>` blocks
|
|
49
|
+
skipped (same exemptions as the publish:true skill census)."""
|
|
50
|
+
body = strip_frontmatter(text)
|
|
51
|
+
lines = body.splitlines()
|
|
52
|
+
total = len(lines)
|
|
53
|
+
hits = 0
|
|
54
|
+
in_bilingual_de_block = False
|
|
55
|
+
for line in lines:
|
|
56
|
+
if BILINGUAL_BLOCK_START.search(line):
|
|
57
|
+
in_bilingual_de_block = True
|
|
58
|
+
if in_bilingual_de_block:
|
|
59
|
+
if BILINGUAL_BLOCK_END.search(line):
|
|
60
|
+
in_bilingual_de_block = False
|
|
61
|
+
continue
|
|
62
|
+
if _line_hits(line):
|
|
63
|
+
hits += 1
|
|
64
|
+
return hits, total
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_below_threshold(text: str) -> tuple[bool, float]:
|
|
68
|
+
"""(clean, pct) — clean is False once pct >= GERMAN_LINE_THRESHOLD_PCT."""
|
|
69
|
+
hits, total = german_line_ratio(text)
|
|
70
|
+
if total == 0:
|
|
71
|
+
return True, 0.0
|
|
72
|
+
pct = hits / total * 100
|
|
73
|
+
return pct < GERMAN_LINE_THRESHOLD_PCT, pct
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class RatioFixtures(unittest.TestCase):
|
|
77
|
+
"""Pure-function proof: the census heuristic actually catches German
|
|
78
|
+
prose (RED) and does not false-positive on clean English (GREEN) —
|
|
79
|
+
independent of whether dist-kit can be built in this environment."""
|
|
80
|
+
|
|
81
|
+
def test_mostly_german_doc_is_red(self):
|
|
82
|
+
# Mirrors the pre-translation wave-anchor-template.md shape: a title
|
|
83
|
+
# line + a handful of English structural lines + a majority of dense
|
|
84
|
+
# German prose paragraphs — comfortably over the 10% line threshold.
|
|
85
|
+
german_doc = "\n".join(
|
|
86
|
+
["# Title", "", "## Section", ""]
|
|
87
|
+
+ ["Diese Zeile beschreibt eine Änderung und ist auf Deutsch geschrieben."] * 3
|
|
88
|
+
+ ["Der Anker wird für die Wellen-Planung genutzt und muss gepflegt werden."] * 3
|
|
89
|
+
+ [""] * 14 # padding so German lines are a clear >10% share, not 100%
|
|
90
|
+
)
|
|
91
|
+
clean, pct = is_below_threshold(german_doc)
|
|
92
|
+
self.assertFalse(clean, f"expected RED (>= {GERMAN_LINE_THRESHOLD_PCT}%), got {pct:.1f}%")
|
|
93
|
+
self.assertGreaterEqual(pct, GERMAN_LINE_THRESHOLD_PCT)
|
|
94
|
+
|
|
95
|
+
def test_all_english_doc_is_green(self):
|
|
96
|
+
english_doc = "\n".join(
|
|
97
|
+
["# Title", "", "## Section", "",
|
|
98
|
+
"This line describes a change and is written in English.",
|
|
99
|
+
"The anchor is used for wave planning and must be maintained.",
|
|
100
|
+
""]
|
|
101
|
+
)
|
|
102
|
+
clean, pct = is_below_threshold(english_doc)
|
|
103
|
+
self.assertTrue(clean, f"expected GREEN (< {GERMAN_LINE_THRESHOLD_PCT}%), got {pct:.1f}%")
|
|
104
|
+
|
|
105
|
+
def test_frontmatter_excluded_from_ratio(self):
|
|
106
|
+
# A German `description:` inside frontmatter (legitimate — quotes a
|
|
107
|
+
# German trigger phrase) must not count toward the body ratio.
|
|
108
|
+
text = (
|
|
109
|
+
"---\n"
|
|
110
|
+
"name: x\n"
|
|
111
|
+
'description: Use when "lass uns das Board durchgehen und die Wellen planen".\n'
|
|
112
|
+
"---\n\n"
|
|
113
|
+
"This body is English only, nothing else in here at all.\n"
|
|
114
|
+
)
|
|
115
|
+
clean, pct = is_below_threshold(text)
|
|
116
|
+
self.assertTrue(clean, f"frontmatter leaked into ratio: {pct:.1f}%")
|
|
117
|
+
|
|
118
|
+
def test_exempt_marker_line_does_not_count_as_a_hit(self):
|
|
119
|
+
text = "\n".join(
|
|
120
|
+
["# Title", "Dies ist deutsch und bleibt es. <!-- language-census: ok -->"]
|
|
121
|
+
+ ["English line."] * 10
|
|
122
|
+
)
|
|
123
|
+
clean, pct = is_below_threshold(text)
|
|
124
|
+
self.assertTrue(clean, f"exempt marker line still counted: {pct:.1f}%")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _build_dist_kit():
|
|
128
|
+
"""Run the real build (subprocess), mirroring test_dist_kit_smoke.py.
|
|
129
|
+
Returns (CompletedProcess, skip_reason); skip_reason set only when node
|
|
130
|
+
itself is unavailable in this environment."""
|
|
131
|
+
build = REPO / "scripts/build-kit.mjs"
|
|
132
|
+
try:
|
|
133
|
+
r = subprocess.run(["node", str(build)], cwd=REPO,
|
|
134
|
+
capture_output=True, text=True, timeout=120)
|
|
135
|
+
except (FileNotFoundError, subprocess.TimeoutExpired) as e:
|
|
136
|
+
return None, str(e)
|
|
137
|
+
return r, None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class DistKitDocsCensus(unittest.TestCase):
|
|
141
|
+
"""Smoke test over the actually-shipped dist-kit/**/*.md — the ONLY
|
|
142
|
+
thing a consumer ever reads, so this is what must stay clean."""
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def setUpClass(cls):
|
|
146
|
+
r, skip_reason = _build_dist_kit()
|
|
147
|
+
if skip_reason:
|
|
148
|
+
raise unittest.SkipTest(f"node/build unavailable: {skip_reason}")
|
|
149
|
+
if r.returncode != 0:
|
|
150
|
+
raise AssertionError(f"build-kit.mjs failed: {r.stderr}")
|
|
151
|
+
if not DIST.is_dir():
|
|
152
|
+
raise AssertionError("dist-kit/ missing after build")
|
|
153
|
+
|
|
154
|
+
def test_every_shipped_doc_is_under_the_german_line_threshold(self):
|
|
155
|
+
violations = []
|
|
156
|
+
checked = 0
|
|
157
|
+
for path in sorted(DIST.rglob("*.md")):
|
|
158
|
+
if not path.is_file():
|
|
159
|
+
continue
|
|
160
|
+
checked += 1
|
|
161
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
162
|
+
clean, pct = is_below_threshold(text)
|
|
163
|
+
if not clean:
|
|
164
|
+
violations.append((str(path.relative_to(DIST)), pct))
|
|
165
|
+
|
|
166
|
+
self.assertGreater(checked, 0, "no dist-kit/**/*.md found — build produced nothing?")
|
|
167
|
+
msg = "\n".join(f" {p}: {pct:.1f}% German lines" for p, pct in violations)
|
|
168
|
+
self.assertEqual(
|
|
169
|
+
violations, [],
|
|
170
|
+
f"Census: {checked - len(violations)} of {checked} shipped docs under "
|
|
171
|
+
f"{GERMAN_LINE_THRESHOLD_PCT}% German lines.\n\n"
|
|
172
|
+
"Translate the residual German prose (or, for a genuine intentional "
|
|
173
|
+
"literal, mark the line `<!-- language-census: ok -->`):\n" + msg,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
unittest.main(verbosity=2)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""code-review skill + its setup-workflow project-layer seed (Welle 49).
|
|
3
|
+
|
|
4
|
+
Companion to test_skill_setup_workflow_seeds.py (not touched here — a sibling
|
|
5
|
+
slice edits it concurrently). Pins the two machine-checkable parts specific to
|
|
6
|
+
this skill:
|
|
7
|
+
|
|
8
|
+
1. The seed template setup-workflow ships (`code-review.md`) is structurally
|
|
9
|
+
valid for the skill that reads it — same "structured-but-empty crust"
|
|
10
|
+
contract as the existing spec-self-critique / spec-completeness seeds.
|
|
11
|
+
2. The sentinel-based idempotency rule applies to it the same way (reference
|
|
12
|
+
implementation duplicated locally so this file has no import-order
|
|
13
|
+
dependency on the sibling-owned test module).
|
|
14
|
+
|
|
15
|
+
Run: python3 scripts/test_skill_code_review_seed.py
|
|
16
|
+
"""
|
|
17
|
+
import re
|
|
18
|
+
import unittest
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
REPO = Path(__file__).resolve().parent.parent
|
|
22
|
+
SETUP_WORKFLOW = REPO / ".claude/skills/setup-workflow"
|
|
23
|
+
CODE_REVIEW = REPO / ".claude/skills/code-review"
|
|
24
|
+
|
|
25
|
+
SENTINEL_RE = re.compile(
|
|
26
|
+
r"^<!--\s*setup-workflow:\s*state=(stub|filled|not-applicable)"
|
|
27
|
+
r"(?:;\s*mode=(github-projects-v2|none))?\s*-->\s*$"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SeedTemplateValid(unittest.TestCase):
|
|
32
|
+
def test_code_review_seed_has_both_headings(self):
|
|
33
|
+
"""The two structured-but-empty headings the `code-review` skill's
|
|
34
|
+
Standards axis expects to find once this file is filled."""
|
|
35
|
+
t = (SETUP_WORKFLOW / "code-review.md").read_text(encoding="utf-8")
|
|
36
|
+
self.assertIn("## Standards sources in this repo", t)
|
|
37
|
+
self.assertIn("## Adjacent review tooling", t)
|
|
38
|
+
|
|
39
|
+
def test_code_review_seed_is_generic(self):
|
|
40
|
+
"""No project-coupled tokens in a template shipped to every consumer."""
|
|
41
|
+
t = (SETUP_WORKFLOW / "code-review.md").read_text(encoding="utf-8")
|
|
42
|
+
for token in ("Testreporter", "iKon85", "/home/"):
|
|
43
|
+
self.assertNotIn(token, t)
|
|
44
|
+
self.assertNotRegex(t, r"#\d{3,5}\b")
|
|
45
|
+
self.assertNotRegex(t, r"\bHR\d+\b")
|
|
46
|
+
|
|
47
|
+
def test_setup_workflow_skill_registers_the_seed(self):
|
|
48
|
+
"""SKILL.md wires the target path + seed link into its Targets table
|
|
49
|
+
and process, so a reader (or the manifest-completeness lint's sibling
|
|
50
|
+
checks) can trace the seed from the skill body."""
|
|
51
|
+
skill = (SETUP_WORKFLOW / "SKILL.md").read_text(encoding="utf-8")
|
|
52
|
+
self.assertIn("docs/agents/code-review.md", skill)
|
|
53
|
+
self.assertIn("[code-review.md](./code-review.md)", skill)
|
|
54
|
+
|
|
55
|
+
def test_code_review_skill_names_its_project_layer_and_fallback(self):
|
|
56
|
+
"""The skill states the project-layer path + the /setup-workflow
|
|
57
|
+
fallback (self-containment-lint's LookupContract pattern)."""
|
|
58
|
+
body = (CODE_REVIEW / "SKILL.md").read_text(encoding="utf-8")
|
|
59
|
+
self.assertIn("docs/agents/code-review.md", body)
|
|
60
|
+
self.assertIn("/setup-workflow", body)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FreshFillProducesValidSentinel(unittest.TestCase):
|
|
64
|
+
"""Simulate the write step: prepend the documented sentinel to the seed
|
|
65
|
+
body and assert the result is a well-formed project-layer file."""
|
|
66
|
+
|
|
67
|
+
def test_prepended_sentinel_parses(self):
|
|
68
|
+
body = (SETUP_WORKFLOW / "code-review.md").read_text(encoding="utf-8")
|
|
69
|
+
written = "<!-- setup-workflow: state=filled -->\n" + body
|
|
70
|
+
self.assertRegex(written.splitlines()[0], SENTINEL_RE)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ManifestEntryValid(unittest.TestCase):
|
|
74
|
+
def test_code_review_is_own_generic_dual_surface_publish_true(self):
|
|
75
|
+
import json
|
|
76
|
+
|
|
77
|
+
manifest = json.loads(
|
|
78
|
+
(REPO / ".claude/skills/skill-manifest.json").read_text(encoding="utf-8")
|
|
79
|
+
)
|
|
80
|
+
entry = manifest["skills"].get("code-review")
|
|
81
|
+
self.assertIsNotNone(entry, "code-review missing from skill-manifest.json")
|
|
82
|
+
self.assertEqual(entry["class"], "generic")
|
|
83
|
+
self.assertIs(entry["publish"], True)
|
|
84
|
+
self.assertEqual(entry["provenance"], "own")
|
|
85
|
+
self.assertEqual(set(entry["surfaces"]), {"claude", "codex"})
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
unittest.main(verbosity=2)
|