@lvlup-sw/exarchos 2.0.1
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/.claude-plugin/marketplace.json +22 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +17 -0
- package/AGENTS.md +59 -0
- package/CLAUDE.md.template +62 -0
- package/LICENSE +202 -0
- package/README.md +258 -0
- package/commands/autocompact.md +37 -0
- package/commands/checkpoint.md +85 -0
- package/commands/cleanup.md +99 -0
- package/commands/debug.md +145 -0
- package/commands/delegate.md +56 -0
- package/commands/ideate.md +82 -0
- package/commands/plan.md +150 -0
- package/commands/refactor.md +139 -0
- package/commands/reload.md +37 -0
- package/commands/resume.md +130 -0
- package/commands/review.md +51 -0
- package/commands/sync-schemas.md +74 -0
- package/commands/synthesize.md +122 -0
- package/commands/tdd.md +58 -0
- package/dist/exarchos-cli.js +8828 -0
- package/dist/exarchos-mcp.js +50 -0
- package/hooks/hooks.json +53 -0
- package/package.json +59 -0
- package/rules/coding-standards.md +46 -0
- package/rules/mcp-tool-guidance.md +26 -0
- package/rules/pr-descriptions.md +12 -0
- package/rules/rm-safety.md +9 -0
- package/rules/skill-path-resolution.md +10 -0
- package/rules/tdd.md +41 -0
- package/rules/telemetry-awareness.md +9 -0
- package/scripts/assess-refactor-scope.sh +239 -0
- package/scripts/check-benchmark-regression.sh +229 -0
- package/scripts/check-coderabbit.sh +288 -0
- package/scripts/check-coverage-thresholds.sh +194 -0
- package/scripts/check-polish-scope.sh +245 -0
- package/scripts/check-property-tests.sh +167 -0
- package/scripts/check-tdd-compliance.sh +265 -0
- package/scripts/coderabbit-review-gate.sh +518 -0
- package/scripts/debug-review-gate.sh +201 -0
- package/scripts/extract-fix-tasks.sh +179 -0
- package/scripts/extract-task.sh +67 -0
- package/scripts/generate-traceability.sh +209 -0
- package/scripts/investigation-timer.sh +171 -0
- package/scripts/needs-schema-sync.sh +174 -0
- package/scripts/new-project.sh +103 -0
- package/scripts/post-delegation-check.sh +317 -0
- package/scripts/pre-synthesis-check.sh +440 -0
- package/scripts/reconcile-state.sh +346 -0
- package/scripts/reconstruct-stack.sh +432 -0
- package/scripts/review-diff.sh +63 -0
- package/scripts/review-verdict.sh +169 -0
- package/scripts/security-scan.sh +248 -0
- package/scripts/select-debug-track.sh +186 -0
- package/scripts/setup-worktree.sh +323 -0
- package/scripts/spec-coverage-check.sh +230 -0
- package/scripts/static-analysis-gate.sh +236 -0
- package/scripts/sync-labels.sh +122 -0
- package/scripts/validate-companion.sh +161 -0
- package/scripts/validate-dotnet-standards.sh +267 -0
- package/scripts/validate-installation.sh +101 -0
- package/scripts/validate-plugin.sh +223 -0
- package/scripts/validate-refactor.sh +234 -0
- package/scripts/validate-rm.sh +93 -0
- package/scripts/verify-delegation-saga.sh +240 -0
- package/scripts/verify-doc-links.sh +211 -0
- package/scripts/verify-ideate-artifacts.sh +296 -0
- package/scripts/verify-plan-coverage.sh +228 -0
- package/scripts/verify-review-triage.sh +219 -0
- package/scripts/verify-worktree-baseline.sh +159 -0
- package/scripts/verify-worktree.sh +84 -0
- package/settings.json +47 -0
- package/skills/brainstorming/SKILL.md +127 -0
- package/skills/brainstorming/references/design-template.md +65 -0
- package/skills/cleanup/SKILL.md +147 -0
- package/skills/cleanup/references/merge-verification.md +40 -0
- package/skills/debug/SKILL.md +204 -0
- package/skills/debug/references/hotfix-track.md +134 -0
- package/skills/debug/references/investigation-checklist.md +217 -0
- package/skills/debug/references/rca-template.md +150 -0
- package/skills/debug/references/state-schema.md +294 -0
- package/skills/debug/references/thorough-track.md +194 -0
- package/skills/debug/references/triage-questions.md +155 -0
- package/skills/debug/references/troubleshooting.md +47 -0
- package/skills/delegation/SKILL.md +150 -0
- package/skills/delegation/references/adaptive-orchestration.md +31 -0
- package/skills/delegation/references/agent-teams-saga.md +248 -0
- package/skills/delegation/references/fix-mode.md +74 -0
- package/skills/delegation/references/fixer-prompt.md +162 -0
- package/skills/delegation/references/implementer-prompt.md +322 -0
- package/skills/delegation/references/parallel-strategy.md +124 -0
- package/skills/delegation/references/pbt-patterns.md +172 -0
- package/skills/delegation/references/pr-fixes-mode.md +154 -0
- package/skills/delegation/references/state-management.md +51 -0
- package/skills/delegation/references/testing-patterns.md +129 -0
- package/skills/delegation/references/troubleshooting.md +33 -0
- package/skills/delegation/references/workflow-steps.md +127 -0
- package/skills/delegation/references/worktree-enforcement.md +64 -0
- package/skills/dotnet-standards/SKILL.md +269 -0
- package/skills/dotnet-standards/references/csharp-standards.md +120 -0
- package/skills/dotnet-standards/templates/.editorconfig +366 -0
- package/skills/dotnet-standards/templates/Directory.Build.props +56 -0
- package/skills/dotnet-standards/templates/Directory.Packages.props +69 -0
- package/skills/dotnet-standards/templates/global.json +6 -0
- package/skills/dotnet-standards/templates/nuget.config +9 -0
- package/skills/dotnet-standards/templates/stylecop.json +37 -0
- package/skills/git-worktrees/SKILL.md +255 -0
- package/skills/implementation-planning/SKILL.md +233 -0
- package/skills/implementation-planning/references/plan-document-template.md +42 -0
- package/skills/implementation-planning/references/spec-tracing-guide.md +51 -0
- package/skills/implementation-planning/references/task-template.md +43 -0
- package/skills/implementation-planning/references/testing-strategy-guide.md +88 -0
- package/skills/quality-review/SKILL.md +278 -0
- package/skills/quality-review/references/code-quality-checklist.md +159 -0
- package/skills/quality-review/references/review-report-template.md +65 -0
- package/skills/quality-review/references/security-checklist.md +79 -0
- package/skills/quality-review/references/typescript-standards.md +24 -0
- package/skills/refactor/COMMAND.md +67 -0
- package/skills/refactor/SKILL.md +198 -0
- package/skills/refactor/phases/auto-chain.md +262 -0
- package/skills/refactor/phases/brief.md +176 -0
- package/skills/refactor/phases/explore.md +132 -0
- package/skills/refactor/phases/overhaul-delegate.md +136 -0
- package/skills/refactor/phases/overhaul-plan.md +312 -0
- package/skills/refactor/phases/overhaul-review.md +304 -0
- package/skills/refactor/phases/polish-implement.md +349 -0
- package/skills/refactor/phases/polish-validate.md +218 -0
- package/skills/refactor/phases/update-docs.md +234 -0
- package/skills/refactor/references/brief-template.md +81 -0
- package/skills/refactor/references/doc-update-checklist.md +110 -0
- package/skills/refactor/references/explore-checklist.md +73 -0
- package/skills/refactor/references/overhaul-track.md +215 -0
- package/skills/refactor/references/polish-track.md +170 -0
- package/skills/shared/prompts/context-reading.md +58 -0
- package/skills/shared/prompts/report-format.md +54 -0
- package/skills/shared/prompts/tdd-requirements.md +39 -0
- package/skills/shepherd/SKILL.md +264 -0
- package/skills/shepherd/references/assess-checklist.md +124 -0
- package/skills/shepherd/references/fix-strategies.md +191 -0
- package/skills/spec-review/SKILL.md +229 -0
- package/skills/spec-review/references/review-checklist.md +60 -0
- package/skills/sync-schemas/SKILL.md +114 -0
- package/skills/sync-schemas/references/configuration.md +73 -0
- package/skills/synthesis/SKILL.md +129 -0
- package/skills/synthesis/references/pr-descriptions.md +87 -0
- package/skills/synthesis/references/synthesis-steps.md +109 -0
- package/skills/synthesis/references/troubleshooting.md +115 -0
- package/skills/validate-all-skills.sh +57 -0
- package/skills/validate-frontmatter.sh +237 -0
- package/skills/workflow-state/SKILL.md +210 -0
- package/skills/workflow-state/references/mcp-tool-reference.md +111 -0
- package/skills/workflow-state/references/phase-transitions.md +141 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Check Polish Scope
|
|
3
|
+
# Checks if polish scope has expanded beyond limits.
|
|
4
|
+
# Replaces "Scope Expansion Triggers" prose with deterministic validation.
|
|
5
|
+
#
|
|
6
|
+
# Usage: check-polish-scope.sh --repo-root <path> [--base-branch main]
|
|
7
|
+
#
|
|
8
|
+
# Exit codes:
|
|
9
|
+
# 0 = scope OK (stay polish)
|
|
10
|
+
# 1 = scope expanded (switch to overhaul)
|
|
11
|
+
# 2 = usage error (missing required args)
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# ============================================================
|
|
16
|
+
# ARGUMENT PARSING
|
|
17
|
+
# ============================================================
|
|
18
|
+
|
|
19
|
+
REPO_ROOT=""
|
|
20
|
+
BASE_BRANCH="main"
|
|
21
|
+
|
|
22
|
+
usage() {
|
|
23
|
+
cat << 'USAGE'
|
|
24
|
+
Usage: check-polish-scope.sh --repo-root <path> [--base-branch main]
|
|
25
|
+
|
|
26
|
+
Required:
|
|
27
|
+
--repo-root <path> Path to the git repository root
|
|
28
|
+
|
|
29
|
+
Optional:
|
|
30
|
+
--base-branch <branch> Base branch to diff against (default: main)
|
|
31
|
+
--help Show this help message
|
|
32
|
+
|
|
33
|
+
Exit codes:
|
|
34
|
+
0 Scope OK — within polish limits
|
|
35
|
+
1 Scope expanded — switch to overhaul
|
|
36
|
+
2 Usage error (missing required args)
|
|
37
|
+
|
|
38
|
+
Expansion triggers checked:
|
|
39
|
+
- File count > 5 (modified files via git diff)
|
|
40
|
+
- Module boundaries crossed (>2 top-level dirs modified)
|
|
41
|
+
- New test files needed (impl files without test counterparts)
|
|
42
|
+
- Architectural docs needed (detected heuristically)
|
|
43
|
+
|
|
44
|
+
Note: Assumes co-located tests (foo.test.ts alongside foo.ts)
|
|
45
|
+
USAGE
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
while [[ $# -gt 0 ]]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
--repo-root)
|
|
51
|
+
if [[ -z "${2:-}" ]]; then
|
|
52
|
+
echo "Error: --repo-root requires a path argument" >&2
|
|
53
|
+
exit 2
|
|
54
|
+
fi
|
|
55
|
+
REPO_ROOT="$2"
|
|
56
|
+
shift 2
|
|
57
|
+
;;
|
|
58
|
+
--base-branch)
|
|
59
|
+
if [[ -z "${2:-}" ]]; then
|
|
60
|
+
echo "Error: --base-branch requires a branch name" >&2
|
|
61
|
+
exit 2
|
|
62
|
+
fi
|
|
63
|
+
BASE_BRANCH="$2"
|
|
64
|
+
shift 2
|
|
65
|
+
;;
|
|
66
|
+
--help)
|
|
67
|
+
usage
|
|
68
|
+
exit 0
|
|
69
|
+
;;
|
|
70
|
+
*)
|
|
71
|
+
echo "Error: Unknown argument '$1'" >&2
|
|
72
|
+
usage >&2
|
|
73
|
+
exit 2
|
|
74
|
+
;;
|
|
75
|
+
esac
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if [[ -z "$REPO_ROOT" ]]; then
|
|
79
|
+
echo "Error: --repo-root is required" >&2
|
|
80
|
+
usage >&2
|
|
81
|
+
exit 2
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ============================================================
|
|
85
|
+
# DEPENDENCY CHECK
|
|
86
|
+
# ============================================================
|
|
87
|
+
|
|
88
|
+
if ! command -v git &>/dev/null; then
|
|
89
|
+
echo "Error: git is required but not installed" >&2
|
|
90
|
+
exit 2
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ============================================================
|
|
94
|
+
# CHECK FUNCTIONS
|
|
95
|
+
# ============================================================
|
|
96
|
+
|
|
97
|
+
CHECK_PASS=0
|
|
98
|
+
CHECK_FAIL=0
|
|
99
|
+
RESULTS=()
|
|
100
|
+
TRIGGERS_FIRED=()
|
|
101
|
+
|
|
102
|
+
check_pass() {
|
|
103
|
+
local name="$1"
|
|
104
|
+
RESULTS+=("- **PASS**: $name")
|
|
105
|
+
CHECK_PASS=$((CHECK_PASS + 1))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
check_fail() {
|
|
109
|
+
local name="$1"
|
|
110
|
+
local detail="${2:-}"
|
|
111
|
+
if [[ -n "$detail" ]]; then
|
|
112
|
+
RESULTS+=("- **FAIL**: $name — $detail")
|
|
113
|
+
else
|
|
114
|
+
RESULTS+=("- **FAIL**: $name")
|
|
115
|
+
fi
|
|
116
|
+
CHECK_FAIL=$((CHECK_FAIL + 1))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ============================================================
|
|
120
|
+
# GET MODIFIED FILES
|
|
121
|
+
# ============================================================
|
|
122
|
+
|
|
123
|
+
cd "$REPO_ROOT"
|
|
124
|
+
|
|
125
|
+
# Get list of files modified compared to base branch
|
|
126
|
+
MODIFIED_FILES=()
|
|
127
|
+
while IFS= read -r line; do
|
|
128
|
+
[[ -n "$line" ]] && MODIFIED_FILES+=("$line")
|
|
129
|
+
done < <(git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null || git diff --name-only "$BASE_BRANCH" HEAD 2>/dev/null || true)
|
|
130
|
+
|
|
131
|
+
FILE_COUNT=${#MODIFIED_FILES[@]}
|
|
132
|
+
|
|
133
|
+
# ============================================================
|
|
134
|
+
# TRIGGER 1: File count > 5
|
|
135
|
+
# ============================================================
|
|
136
|
+
|
|
137
|
+
if [[ $FILE_COUNT -le 5 ]]; then
|
|
138
|
+
check_pass "File count within limit ($FILE_COUNT <= 5)"
|
|
139
|
+
else
|
|
140
|
+
check_fail "File count exceeds limit" "$FILE_COUNT files modified (max 5)"
|
|
141
|
+
TRIGGERS_FIRED+=("File count ($FILE_COUNT) exceeds limit of 5")
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# ============================================================
|
|
145
|
+
# TRIGGER 2: Module boundaries crossed (>2 top-level dirs)
|
|
146
|
+
# ============================================================
|
|
147
|
+
|
|
148
|
+
# Bash 3 compatible — no associative arrays
|
|
149
|
+
MODULE_LIST=""
|
|
150
|
+
for f in "${MODIFIED_FILES[@]}"; do
|
|
151
|
+
top_dir="$(echo "$f" | cut -d'/' -f1)"
|
|
152
|
+
if ! echo "$MODULE_LIST" | grep -qF "|$top_dir|"; then
|
|
153
|
+
MODULE_LIST="${MODULE_LIST}|$top_dir|"
|
|
154
|
+
fi
|
|
155
|
+
done
|
|
156
|
+
MODULE_COUNT=0
|
|
157
|
+
MODULE_NAMES=""
|
|
158
|
+
if [[ -n "$MODULE_LIST" ]]; then
|
|
159
|
+
MODULE_NAMES="$(echo "$MODULE_LIST" | tr '|' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')"
|
|
160
|
+
MODULE_COUNT="$(echo "$MODULE_LIST" | tr '|' '\n' | sort -u | grep -vc '^$' || true)"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
if [[ $MODULE_COUNT -le 2 ]]; then
|
|
164
|
+
check_pass "Module boundaries OK ($MODULE_COUNT top-level dirs)"
|
|
165
|
+
else
|
|
166
|
+
check_fail "Module boundaries crossed" "$MODULE_COUNT top-level dirs: $MODULE_NAMES"
|
|
167
|
+
TRIGGERS_FIRED+=("Module boundaries crossed ($MODULE_COUNT dirs: $MODULE_NAMES)")
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# ============================================================
|
|
171
|
+
# TRIGGER 3: New test files needed
|
|
172
|
+
# ============================================================
|
|
173
|
+
|
|
174
|
+
MISSING_TESTS=()
|
|
175
|
+
for f in "${MODIFIED_FILES[@]}"; do
|
|
176
|
+
# Only check .ts implementation files
|
|
177
|
+
if [[ "$f" == *.ts && "$f" != *.test.ts && "$f" != *.d.ts ]]; then
|
|
178
|
+
test_file="${f%.ts}.test.ts"
|
|
179
|
+
if [[ ! -f "$test_file" ]]; then
|
|
180
|
+
MISSING_TESTS+=("$f")
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
done
|
|
184
|
+
|
|
185
|
+
if [[ ${#MISSING_TESTS[@]} -eq 0 ]]; then
|
|
186
|
+
check_pass "Test coverage OK (all impl files have test counterparts)"
|
|
187
|
+
else
|
|
188
|
+
check_fail "New test files needed" "${#MISSING_TESTS[@]} impl files without tests: ${MISSING_TESTS[*]}"
|
|
189
|
+
TRIGGERS_FIRED+=("New test files needed for ${#MISSING_TESTS[@]} files")
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# ============================================================
|
|
193
|
+
# TRIGGER 4: Architectural docs needed (heuristic)
|
|
194
|
+
# ============================================================
|
|
195
|
+
|
|
196
|
+
NEEDS_ARCH_DOCS=false
|
|
197
|
+
for f in "${MODIFIED_FILES[@]}"; do
|
|
198
|
+
# Heuristic: if modifying files in multiple top-level dirs with structural changes
|
|
199
|
+
if [[ "$f" == *"index.ts" || "$f" == *"types.ts" || "$f" == *"interface"* ]]; then
|
|
200
|
+
if [[ $MODULE_COUNT -gt 1 ]]; then
|
|
201
|
+
NEEDS_ARCH_DOCS=true
|
|
202
|
+
break
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
done
|
|
206
|
+
|
|
207
|
+
if [[ "$NEEDS_ARCH_DOCS" == false ]]; then
|
|
208
|
+
check_pass "No architectural docs needed"
|
|
209
|
+
else
|
|
210
|
+
check_fail "Architectural documentation likely needed" "Structural files modified across modules"
|
|
211
|
+
TRIGGERS_FIRED+=("Architectural documentation needed")
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# ============================================================
|
|
215
|
+
# STRUCTURED OUTPUT
|
|
216
|
+
# ============================================================
|
|
217
|
+
|
|
218
|
+
echo "## Polish Scope Check Report"
|
|
219
|
+
echo ""
|
|
220
|
+
echo "**Repository:** \`$REPO_ROOT\`"
|
|
221
|
+
echo "**Base branch:** $BASE_BRANCH"
|
|
222
|
+
echo "**Files modified:** $FILE_COUNT"
|
|
223
|
+
echo "**Modules touched:** $MODULE_COUNT (${MODULE_NAMES:-none})"
|
|
224
|
+
echo ""
|
|
225
|
+
|
|
226
|
+
for result in "${RESULTS[@]}"; do
|
|
227
|
+
echo "$result"
|
|
228
|
+
done
|
|
229
|
+
|
|
230
|
+
echo ""
|
|
231
|
+
echo "---"
|
|
232
|
+
echo ""
|
|
233
|
+
|
|
234
|
+
if [[ ${#TRIGGERS_FIRED[@]} -eq 0 ]]; then
|
|
235
|
+
echo "**Result: SCOPE OK** — All within polish limits"
|
|
236
|
+
exit 0
|
|
237
|
+
else
|
|
238
|
+
echo "**Result: SCOPE EXPANDED** — Switch to overhaul track"
|
|
239
|
+
echo ""
|
|
240
|
+
echo "Triggers fired:"
|
|
241
|
+
for trigger in "${TRIGGERS_FIRED[@]}"; do
|
|
242
|
+
echo " - $trigger"
|
|
243
|
+
done
|
|
244
|
+
exit 1
|
|
245
|
+
fi
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-property-tests.sh
|
|
3
|
+
# Verifies that tasks requiring property-based tests have PBT patterns in the implementation.
|
|
4
|
+
# Exit 0 = pass, 1 = fail (missing PBT), 2 = usage error
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# ─── Usage ──────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
usage() {
|
|
11
|
+
cat <<EOF
|
|
12
|
+
Usage: check-property-tests.sh --plan-file <path> --worktree-dir <path>
|
|
13
|
+
|
|
14
|
+
Verifies that plan tasks with propertyTests: true have property-based test
|
|
15
|
+
patterns in the worktree.
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
--plan-file Path to plan JSON file containing tasks with testingStrategy
|
|
19
|
+
--worktree-dir Path to the worktree directory to scan for PBT patterns
|
|
20
|
+
|
|
21
|
+
Exit Codes:
|
|
22
|
+
0 All PBT-required tasks have property test patterns
|
|
23
|
+
1 One or more PBT-required tasks lack property test patterns
|
|
24
|
+
2 Usage error (missing arguments)
|
|
25
|
+
EOF
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# ─── Arg Parsing ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
PLAN_FILE=""
|
|
31
|
+
WORKTREE_DIR=""
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--plan-file)
|
|
36
|
+
PLAN_FILE="${2:-}"
|
|
37
|
+
shift 2 || { echo "Error: --plan-file requires a value" >&2; exit 2; }
|
|
38
|
+
;;
|
|
39
|
+
--worktree-dir)
|
|
40
|
+
WORKTREE_DIR="${2:-}"
|
|
41
|
+
shift 2 || { echo "Error: --worktree-dir requires a value" >&2; exit 2; }
|
|
42
|
+
;;
|
|
43
|
+
--help|-h)
|
|
44
|
+
usage
|
|
45
|
+
exit 0
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
echo "Error: Unknown argument: $1" >&2
|
|
49
|
+
usage >&2
|
|
50
|
+
exit 2
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
if [[ -z "$PLAN_FILE" || -z "$WORKTREE_DIR" ]]; then
|
|
56
|
+
echo "Error: Both --plan-file and --worktree-dir are required." >&2
|
|
57
|
+
usage >&2
|
|
58
|
+
exit 2
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
if [[ ! -f "$PLAN_FILE" ]]; then
|
|
62
|
+
echo "Error: Plan file not found: $PLAN_FILE" >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
if [[ ! -d "$WORKTREE_DIR" ]]; then
|
|
67
|
+
echo "Error: Worktree directory not found: $WORKTREE_DIR" >&2
|
|
68
|
+
exit 2
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# ─── Plan JSON Extraction ──────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
# Extract task IDs where testingStrategy.propertyTests is true
|
|
74
|
+
PBT_TASK_IDS=()
|
|
75
|
+
while IFS= read -r task_id; do
|
|
76
|
+
[[ -n "$task_id" ]] && PBT_TASK_IDS+=("$task_id")
|
|
77
|
+
done < <(
|
|
78
|
+
# Use python3 for reliable JSON parsing (available on macOS and most Linux)
|
|
79
|
+
python3 -c "
|
|
80
|
+
import json, sys
|
|
81
|
+
with open(sys.argv[1]) as f:
|
|
82
|
+
plan = json.load(f)
|
|
83
|
+
for task in plan.get('tasks', []):
|
|
84
|
+
strategy = task.get('testingStrategy', {})
|
|
85
|
+
if strategy.get('propertyTests', False):
|
|
86
|
+
print(task.get('id', ''))
|
|
87
|
+
" "$PLAN_FILE" 2>/dev/null
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if [[ ${#PBT_TASK_IDS[@]} -eq 0 ]]; then
|
|
91
|
+
echo "## PBT Check: PASS"
|
|
92
|
+
echo "No tasks require property-based tests."
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo "## PBT Check"
|
|
97
|
+
echo "Tasks requiring property-based tests: ${PBT_TASK_IDS[*]}"
|
|
98
|
+
echo ""
|
|
99
|
+
|
|
100
|
+
# ─── PBT Pattern Detection ─────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
# TypeScript patterns: fast-check library usage
|
|
103
|
+
TS_PBT_PATTERN="fc\.property|fc\.assert|it\.prop|test\.prop|from 'fast-check'|from \"fast-check\"|@fast-check"
|
|
104
|
+
|
|
105
|
+
# .NET patterns: FsCheck library usage
|
|
106
|
+
DOTNET_PBT_PATTERN="Prop\.ForAll|using FsCheck|\[Property\]"
|
|
107
|
+
|
|
108
|
+
# Combined pattern
|
|
109
|
+
COMBINED_PATTERN="$TS_PBT_PATTERN|$DOTNET_PBT_PATTERN"
|
|
110
|
+
|
|
111
|
+
# Find all test files with PBT patterns
|
|
112
|
+
PBT_FILES=()
|
|
113
|
+
while IFS= read -r file; do
|
|
114
|
+
[[ -n "$file" ]] && PBT_FILES+=("$file")
|
|
115
|
+
done < <(
|
|
116
|
+
grep -rlE "$COMBINED_PATTERN" "$WORKTREE_DIR" \
|
|
117
|
+
--include="*.test.ts" \
|
|
118
|
+
--include="*.test.tsx" \
|
|
119
|
+
--include="*.spec.ts" \
|
|
120
|
+
--include="*.Tests.cs" \
|
|
121
|
+
--include="*Tests.cs" \
|
|
122
|
+
--include="*.test.js" \
|
|
123
|
+
2>/dev/null || true
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# ─── Cross-Reference ───────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
HAS_PBT=false
|
|
129
|
+
if [[ ${#PBT_FILES[@]} -gt 0 ]]; then
|
|
130
|
+
HAS_PBT=true
|
|
131
|
+
echo "Found PBT patterns in:"
|
|
132
|
+
for f in "${PBT_FILES[@]}"; do
|
|
133
|
+
echo " - $f"
|
|
134
|
+
done
|
|
135
|
+
echo ""
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# For each PBT-required task, check if any PBT file exists in the worktree
|
|
139
|
+
UNCOVERED_TASKS=()
|
|
140
|
+
if [[ "$HAS_PBT" == "true" ]]; then
|
|
141
|
+
# If any PBT patterns exist in the worktree, consider all tasks covered
|
|
142
|
+
# (task-to-file mapping is coarse-grained; presence of PBT patterns is the gate)
|
|
143
|
+
echo "All PBT-required tasks have coverage."
|
|
144
|
+
else
|
|
145
|
+
# No PBT patterns found at all
|
|
146
|
+
for task_id in "${PBT_TASK_IDS[@]}"; do
|
|
147
|
+
UNCOVERED_TASKS+=("$task_id")
|
|
148
|
+
done
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# ─── Result ─────────────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
if [[ ${#UNCOVERED_TASKS[@]} -gt 0 ]]; then
|
|
154
|
+
echo "## PBT Check: FAIL"
|
|
155
|
+
echo ""
|
|
156
|
+
echo "The following tasks require property-based tests but none were found:"
|
|
157
|
+
for task_id in "${UNCOVERED_TASKS[@]}"; do
|
|
158
|
+
echo " - $task_id"
|
|
159
|
+
done
|
|
160
|
+
echo ""
|
|
161
|
+
echo "Expected patterns (TypeScript): fc.property, fc.assert, it.prop, test.prop, from 'fast-check'"
|
|
162
|
+
echo "Expected patterns (.NET): Prop.ForAll, using FsCheck, [Property]"
|
|
163
|
+
exit 1
|
|
164
|
+
else
|
|
165
|
+
echo "## PBT Check: PASS"
|
|
166
|
+
exit 0
|
|
167
|
+
fi
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Check TDD Compliance
|
|
3
|
+
# Verify test-first git history order for commits on a branch.
|
|
4
|
+
#
|
|
5
|
+
# Usage: check-tdd-compliance.sh --repo-root <path> --branch <name> [--base-branch main]
|
|
6
|
+
#
|
|
7
|
+
# Exit codes:
|
|
8
|
+
# 0 = compliant (test files committed before or alongside implementation)
|
|
9
|
+
# 1 = violations found (implementation committed without test)
|
|
10
|
+
# 2 = usage error (missing required args)
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# ============================================================
|
|
15
|
+
# ARGUMENT PARSING
|
|
16
|
+
# ============================================================
|
|
17
|
+
|
|
18
|
+
REPO_ROOT=""
|
|
19
|
+
BRANCH=""
|
|
20
|
+
BASE_BRANCH="main"
|
|
21
|
+
|
|
22
|
+
usage() {
|
|
23
|
+
cat << 'USAGE'
|
|
24
|
+
Usage: check-tdd-compliance.sh --repo-root <path> --branch <name> [--base-branch main]
|
|
25
|
+
|
|
26
|
+
Required:
|
|
27
|
+
--repo-root <path> Repository root directory
|
|
28
|
+
--branch <name> Branch to check
|
|
29
|
+
|
|
30
|
+
Optional:
|
|
31
|
+
--base-branch <name> Base branch to compare against (default: main)
|
|
32
|
+
--help Show this help message
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
0 Compliant (test files committed before or alongside implementation)
|
|
36
|
+
1 Violations found (implementation without test in same/prior commit)
|
|
37
|
+
2 Usage error (missing required args)
|
|
38
|
+
USAGE
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
while [[ $# -gt 0 ]]; do
|
|
42
|
+
case "$1" in
|
|
43
|
+
--repo-root)
|
|
44
|
+
if [[ -z "${2:-}" ]]; then
|
|
45
|
+
echo "Error: --repo-root requires a path argument" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
fi
|
|
48
|
+
REPO_ROOT="$2"
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--branch)
|
|
52
|
+
if [[ -z "${2:-}" ]]; then
|
|
53
|
+
echo "Error: --branch requires a name argument" >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
BRANCH="$2"
|
|
57
|
+
shift 2
|
|
58
|
+
;;
|
|
59
|
+
--base-branch)
|
|
60
|
+
if [[ -z "${2:-}" ]]; then
|
|
61
|
+
echo "Error: --base-branch requires a name argument" >&2
|
|
62
|
+
exit 2
|
|
63
|
+
fi
|
|
64
|
+
BASE_BRANCH="$2"
|
|
65
|
+
shift 2
|
|
66
|
+
;;
|
|
67
|
+
--help)
|
|
68
|
+
usage
|
|
69
|
+
exit 0
|
|
70
|
+
;;
|
|
71
|
+
*)
|
|
72
|
+
echo "Error: Unknown argument '$1'" >&2
|
|
73
|
+
usage >&2
|
|
74
|
+
exit 2
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
if [[ -z "$REPO_ROOT" || -z "$BRANCH" ]]; then
|
|
80
|
+
echo "Error: --repo-root and --branch are required" >&2
|
|
81
|
+
usage >&2
|
|
82
|
+
exit 2
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
if [[ ! -d "$REPO_ROOT/.git" ]]; then
|
|
86
|
+
echo "Error: Not a git repository: $REPO_ROOT" >&2
|
|
87
|
+
exit 2
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# ============================================================
|
|
91
|
+
# HELPER: Classify a file as test or implementation
|
|
92
|
+
# ============================================================
|
|
93
|
+
|
|
94
|
+
is_test_file() {
|
|
95
|
+
local file="$1"
|
|
96
|
+
case "$file" in
|
|
97
|
+
*.test.ts|*.test.sh|*.spec.ts|*.test.js|*.spec.js|*.test.tsx|*.spec.tsx)
|
|
98
|
+
return 0
|
|
99
|
+
;;
|
|
100
|
+
*)
|
|
101
|
+
return 1
|
|
102
|
+
;;
|
|
103
|
+
esac
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
is_impl_file() {
|
|
107
|
+
local file="$1"
|
|
108
|
+
# Implementation files: source code that isn't a test, config, or docs
|
|
109
|
+
case "$file" in
|
|
110
|
+
*.ts|*.js|*.tsx|*.jsx|*.sh)
|
|
111
|
+
# Exclude test files
|
|
112
|
+
if is_test_file "$file"; then
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
return 0
|
|
116
|
+
;;
|
|
117
|
+
*)
|
|
118
|
+
return 1
|
|
119
|
+
;;
|
|
120
|
+
esac
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ============================================================
|
|
124
|
+
# ANALYZE COMMITS
|
|
125
|
+
# ============================================================
|
|
126
|
+
|
|
127
|
+
CHECK_PASS=0
|
|
128
|
+
CHECK_FAIL=0
|
|
129
|
+
VIOLATIONS=()
|
|
130
|
+
RESULTS=()
|
|
131
|
+
|
|
132
|
+
# Track test files seen across all commits (cumulative)
|
|
133
|
+
# Use a delimiter-separated string for bash 3.x compatibility (no associative arrays)
|
|
134
|
+
TESTS_SEEN=""
|
|
135
|
+
|
|
136
|
+
# Get commits from base..branch in chronological order (oldest first)
|
|
137
|
+
cd "$REPO_ROOT"
|
|
138
|
+
COMMITS=()
|
|
139
|
+
while IFS= read -r commit_hash; do
|
|
140
|
+
[[ -n "$commit_hash" ]] && COMMITS+=("$commit_hash")
|
|
141
|
+
done < <(git log --reverse --format="%H" "${BASE_BRANCH}..${BRANCH}" 2>/dev/null)
|
|
142
|
+
|
|
143
|
+
if [[ ${#COMMITS[@]} -eq 0 ]]; then
|
|
144
|
+
echo "## TDD Compliance Report"
|
|
145
|
+
echo ""
|
|
146
|
+
echo "**Branch:** $BRANCH"
|
|
147
|
+
echo "**Base:** $BASE_BRANCH"
|
|
148
|
+
echo ""
|
|
149
|
+
echo "No commits found between $BASE_BRANCH and $BRANCH"
|
|
150
|
+
echo ""
|
|
151
|
+
echo "---"
|
|
152
|
+
echo ""
|
|
153
|
+
echo "**Result: PASS** (no commits to check)"
|
|
154
|
+
exit 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
for commit_hash in "${COMMITS[@]}"; do
|
|
158
|
+
commit_msg="$(git log -1 --format="%s" "$commit_hash")"
|
|
159
|
+
commit_short="$(git log -1 --format="%h" "$commit_hash")"
|
|
160
|
+
|
|
161
|
+
# Get files changed in this commit
|
|
162
|
+
FILES_IN_COMMIT=()
|
|
163
|
+
while IFS= read -r file; do
|
|
164
|
+
[[ -n "$file" ]] && FILES_IN_COMMIT+=("$file")
|
|
165
|
+
done < <(git diff-tree --no-commit-id --name-only --diff-filter=ACMRT -r "$commit_hash" 2>/dev/null)
|
|
166
|
+
|
|
167
|
+
# Classify files in this commit
|
|
168
|
+
HAS_TEST=false
|
|
169
|
+
HAS_IMPL=false
|
|
170
|
+
IMPL_FILES=()
|
|
171
|
+
TEST_FILES=()
|
|
172
|
+
|
|
173
|
+
for file in "${FILES_IN_COMMIT[@]}"; do
|
|
174
|
+
if is_test_file "$file"; then
|
|
175
|
+
HAS_TEST=true
|
|
176
|
+
TEST_FILES+=("$file")
|
|
177
|
+
TESTS_SEEN="${TESTS_SEEN}|${file}|"
|
|
178
|
+
elif is_impl_file "$file"; then
|
|
179
|
+
HAS_IMPL=true
|
|
180
|
+
IMPL_FILES+=("$file")
|
|
181
|
+
fi
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
# A commit with implementation files is compliant if:
|
|
185
|
+
# 1. It also contains test files (mixed commit), OR
|
|
186
|
+
# 2. Corresponding test files were seen in prior commits
|
|
187
|
+
if [[ "$HAS_IMPL" == true ]]; then
|
|
188
|
+
if [[ "$HAS_TEST" == true ]]; then
|
|
189
|
+
# Mixed commit: test and impl together — OK
|
|
190
|
+
RESULTS+=("- **PASS**: \`$commit_short\` — $commit_msg (test+impl)")
|
|
191
|
+
CHECK_PASS=$((CHECK_PASS + 1))
|
|
192
|
+
else
|
|
193
|
+
# Check if test files were seen before this commit
|
|
194
|
+
FOUND_PRIOR_TEST=false
|
|
195
|
+
for impl_file in "${IMPL_FILES[@]}"; do
|
|
196
|
+
# Derive expected test file name(s)
|
|
197
|
+
base="${impl_file%.*}"
|
|
198
|
+
ext="${impl_file##*.}"
|
|
199
|
+
test_candidate_test="${base}.test.${ext}"
|
|
200
|
+
test_candidate_spec="${base}.spec.${ext}"
|
|
201
|
+
|
|
202
|
+
if echo "$TESTS_SEEN" | grep -qF "|${test_candidate_test}|" || \
|
|
203
|
+
echo "$TESTS_SEEN" | grep -qF "|${test_candidate_spec}|"; then
|
|
204
|
+
FOUND_PRIOR_TEST=true
|
|
205
|
+
break
|
|
206
|
+
fi
|
|
207
|
+
done
|
|
208
|
+
|
|
209
|
+
if [[ "$FOUND_PRIOR_TEST" == true ]]; then
|
|
210
|
+
RESULTS+=("- **PASS**: \`$commit_short\` — $commit_msg (test in prior commit)")
|
|
211
|
+
CHECK_PASS=$((CHECK_PASS + 1))
|
|
212
|
+
else
|
|
213
|
+
RESULTS+=("- **FAIL**: \`$commit_short\` — $commit_msg (implementation without test)")
|
|
214
|
+
VIOLATIONS+=("$commit_short: $commit_msg")
|
|
215
|
+
CHECK_FAIL=$((CHECK_FAIL + 1))
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
218
|
+
elif [[ "$HAS_TEST" == true ]]; then
|
|
219
|
+
# Test-only commit — always compliant
|
|
220
|
+
RESULTS+=("- **PASS**: \`$commit_short\` — $commit_msg (test-only)")
|
|
221
|
+
CHECK_PASS=$((CHECK_PASS + 1))
|
|
222
|
+
else
|
|
223
|
+
# Non-code commit (docs, config, etc.) — skip
|
|
224
|
+
RESULTS+=("- **SKIP**: \`$commit_short\` — $commit_msg (non-code)")
|
|
225
|
+
fi
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
# ============================================================
|
|
229
|
+
# STRUCTURED OUTPUT
|
|
230
|
+
# ============================================================
|
|
231
|
+
|
|
232
|
+
echo "## TDD Compliance Report"
|
|
233
|
+
echo ""
|
|
234
|
+
echo "**Branch:** $BRANCH"
|
|
235
|
+
echo "**Base:** $BASE_BRANCH"
|
|
236
|
+
echo "**Commits analyzed:** ${#COMMITS[@]}"
|
|
237
|
+
echo ""
|
|
238
|
+
|
|
239
|
+
echo "### Per-commit Analysis"
|
|
240
|
+
echo ""
|
|
241
|
+
for result in "${RESULTS[@]}"; do
|
|
242
|
+
echo "$result"
|
|
243
|
+
done
|
|
244
|
+
echo ""
|
|
245
|
+
|
|
246
|
+
if [[ ${#VIOLATIONS[@]} -gt 0 ]]; then
|
|
247
|
+
echo "### Violations"
|
|
248
|
+
echo ""
|
|
249
|
+
for v in "${VIOLATIONS[@]}"; do
|
|
250
|
+
echo "- $v"
|
|
251
|
+
done
|
|
252
|
+
echo ""
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
TOTAL=$((CHECK_PASS + CHECK_FAIL))
|
|
256
|
+
echo "---"
|
|
257
|
+
echo ""
|
|
258
|
+
|
|
259
|
+
if [[ $CHECK_FAIL -eq 0 ]]; then
|
|
260
|
+
echo "**Result: PASS** ($CHECK_PASS/$TOTAL commits compliant)"
|
|
261
|
+
exit 0
|
|
262
|
+
else
|
|
263
|
+
echo "**Result: FAIL** ($CHECK_FAIL/$TOTAL commits have violations)"
|
|
264
|
+
exit 1
|
|
265
|
+
fi
|