@drafthq/draft 2.7.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/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +26 -0
- package/LICENSE +21 -0
- package/README.md +272 -0
- package/bin/README.md +49 -0
- package/cli/bin/draft.js +13 -0
- package/cli/src/cli.js +113 -0
- package/cli/src/hosts/claude-code.js +46 -0
- package/cli/src/hosts/codex.js +33 -0
- package/cli/src/hosts/cursor.js +50 -0
- package/cli/src/hosts/index.js +24 -0
- package/cli/src/hosts/opencode.js +39 -0
- package/cli/src/installer.js +61 -0
- package/cli/src/lib/fsx.js +34 -0
- package/cli/src/lib/graph.js +23 -0
- package/cli/src/lib/log.js +32 -0
- package/cli/src/lib/paths.js +14 -0
- package/core/agents/architect.md +338 -0
- package/core/agents/debugger.md +193 -0
- package/core/agents/ops.md +104 -0
- package/core/agents/planner.md +158 -0
- package/core/agents/rca.md +314 -0
- package/core/agents/reviewer.md +256 -0
- package/core/agents/writer.md +110 -0
- package/core/guardrails/README.md +4 -0
- package/core/guardrails/code-quality.md +4 -0
- package/core/guardrails/dependency-triage.md +4 -0
- package/core/guardrails/design-norms.md +4 -0
- package/core/guardrails/language-standards.md +4 -0
- package/core/guardrails/review-checks.md +4 -0
- package/core/guardrails/secure-patterns.md +4 -0
- package/core/guardrails/security.md +4 -0
- package/core/guardrails.md +22 -0
- package/core/knowledge-base.md +127 -0
- package/core/methodology.md +1221 -0
- package/core/shared/condensation.md +224 -0
- package/core/shared/context-verify.md +44 -0
- package/core/shared/cross-skill-dispatch.md +127 -0
- package/core/shared/discovery-schema.md +75 -0
- package/core/shared/draft-context-loading.md +282 -0
- package/core/shared/git-report-metadata.md +106 -0
- package/core/shared/graph-query.md +239 -0
- package/core/shared/graph-usage-report.md +22 -0
- package/core/shared/jira-sync.md +170 -0
- package/core/shared/parallel-analysis.md +386 -0
- package/core/shared/parallel-fanout.md +10 -0
- package/core/shared/pattern-learning.md +146 -0
- package/core/shared/red-flags.md +58 -0
- package/core/shared/template-contract.md +22 -0
- package/core/shared/template-hygiene.md +10 -0
- package/core/shared/tool-resolver.md +10 -0
- package/core/shared/vcs-commands.md +97 -0
- package/core/shared/verification-gates.md +47 -0
- package/core/templates/CHANGELOG.md +70 -0
- package/core/templates/ai-context-export.md +8 -0
- package/core/templates/ai-context.md +270 -0
- package/core/templates/ai-profile.md +41 -0
- package/core/templates/architecture.md +203 -0
- package/core/templates/dependency-graph.md +103 -0
- package/core/templates/discovery.md +79 -0
- package/core/templates/guardrails.md +143 -0
- package/core/templates/hld.md +327 -0
- package/core/templates/intake-questions.md +403 -0
- package/core/templates/jira.md +119 -0
- package/core/templates/lld.md +283 -0
- package/core/templates/metadata.json +66 -0
- package/core/templates/plan.md +130 -0
- package/core/templates/product.md +110 -0
- package/core/templates/rca.md +86 -0
- package/core/templates/root-architecture.md +127 -0
- package/core/templates/root-product.md +53 -0
- package/core/templates/root-tech-stack.md +117 -0
- package/core/templates/service-index.md +55 -0
- package/core/templates/session-summary.md +8 -0
- package/core/templates/spec.md +165 -0
- package/core/templates/tech-matrix.md +101 -0
- package/core/templates/tech-stack.md +169 -0
- package/core/templates/track-architecture.md +311 -0
- package/core/templates/workflow.md +187 -0
- package/integrations/agents/AGENTS.md +24384 -0
- package/integrations/copilot/.github/copilot-instructions.md +24384 -0
- package/integrations/gemini/.gemini.md +26 -0
- package/package.json +53 -0
- package/scripts/fetch-memory-engine.sh +116 -0
- package/scripts/lib.sh +256 -0
- package/scripts/tools/_lib.sh +220 -0
- package/scripts/tools/adr-index.sh +117 -0
- package/scripts/tools/check-graph-usage-report.sh +95 -0
- package/scripts/tools/check-scope-conflicts.sh +139 -0
- package/scripts/tools/check-skill-line-caps.sh +115 -0
- package/scripts/tools/check-template-noop.sh +87 -0
- package/scripts/tools/check-track-hygiene.sh +230 -0
- package/scripts/tools/classify-files.sh +231 -0
- package/scripts/tools/cycle-detect.sh +75 -0
- package/scripts/tools/detect-test-framework.sh +135 -0
- package/scripts/tools/diff-templates-vs-tracks.sh +176 -0
- package/scripts/tools/emit-skill-metrics.sh +71 -0
- package/scripts/tools/fix-whitespace.sh +192 -0
- package/scripts/tools/freshness-check.sh +143 -0
- package/scripts/tools/git-metadata.sh +203 -0
- package/scripts/tools/graph-callers.sh +74 -0
- package/scripts/tools/graph-impact.sh +93 -0
- package/scripts/tools/graph-snapshot.sh +102 -0
- package/scripts/tools/hotspot-rank.sh +75 -0
- package/scripts/tools/manage-symlinks.sh +85 -0
- package/scripts/tools/mermaid-from-graph.sh +92 -0
- package/scripts/tools/migrate-track-frontmatter.sh +241 -0
- package/scripts/tools/parse-git-log.sh +135 -0
- package/scripts/tools/parse-reports.sh +114 -0
- package/scripts/tools/render-track.sh +145 -0
- package/scripts/tools/run-coverage.sh +153 -0
- package/scripts/tools/scan-markers.sh +144 -0
- package/scripts/tools/skill-caps.conf +24 -0
- package/scripts/tools/validate-frontmatter.sh +125 -0
- package/scripts/tools/verify-citations.sh +250 -0
- package/scripts/tools/verify-doc-anchors.sh +204 -0
- package/scripts/tools/verify-graph-binary.sh +154 -0
- package/skills/GRAPH.md +332 -0
- package/skills/adr/SKILL.md +374 -0
- package/skills/assist-review/SKILL.md +49 -0
- package/skills/bughunt/SKILL.md +668 -0
- package/skills/bughunt/references/regression-tests.md +399 -0
- package/skills/change/SKILL.md +267 -0
- package/skills/coverage/SKILL.md +336 -0
- package/skills/debug/SKILL.md +201 -0
- package/skills/decompose/SKILL.md +656 -0
- package/skills/deep-review/SKILL.md +326 -0
- package/skills/deploy-checklist/SKILL.md +254 -0
- package/skills/discover/SKILL.md +66 -0
- package/skills/docs/SKILL.md +42 -0
- package/skills/documentation/SKILL.md +197 -0
- package/skills/draft/SKILL.md +177 -0
- package/skills/draft/context-files.md +57 -0
- package/skills/draft/intent-mapping.md +37 -0
- package/skills/draft/quality-guide.md +51 -0
- package/skills/graph/SKILL.md +107 -0
- package/skills/impact/SKILL.md +86 -0
- package/skills/implement/SKILL.md +794 -0
- package/skills/incident-response/SKILL.md +245 -0
- package/skills/index/SKILL.md +848 -0
- package/skills/init/SKILL.md +1784 -0
- package/skills/init/references/architecture-spec.md +1259 -0
- package/skills/integrations/SKILL.md +53 -0
- package/skills/jira/SKILL.md +577 -0
- package/skills/jira/references/review.md +1322 -0
- package/skills/learn/SKILL.md +478 -0
- package/skills/new-track/SKILL.md +841 -0
- package/skills/ops/SKILL.md +57 -0
- package/skills/plan/SKILL.md +60 -0
- package/skills/quick-review/SKILL.md +216 -0
- package/skills/revert/SKILL.md +178 -0
- package/skills/review/SKILL.md +1114 -0
- package/skills/standup/SKILL.md +183 -0
- package/skills/status/SKILL.md +183 -0
- package/skills/tech-debt/SKILL.md +318 -0
- package/skills/testing-strategy/SKILL.md +195 -0
- package/skills/tour/SKILL.md +38 -0
- package/skills/upload/SKILL.md +117 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# check-track-hygiene.sh
|
|
3
|
+
#
|
|
4
|
+
# Hygiene validator for Draft tracks. Enforces WS-1 contract from
|
|
5
|
+
# core/shared/template-hygiene.md. Generic across any project, any track,
|
|
6
|
+
# any domain.
|
|
7
|
+
#
|
|
8
|
+
# Checks per track:
|
|
9
|
+
# 1. Status parity: metadata.json:status vs Markdown "Status:" lines.
|
|
10
|
+
# 2. Author resolution: no Author1 / xxx@*.com / [name] placeholders.
|
|
11
|
+
# 3. Approver placeholders: no empty cells in Approval-bearing tables.
|
|
12
|
+
# 4. TBD budget: per-doc cap depends on metadata.json:status.
|
|
13
|
+
# 5. Plan staleness (WS-6 chain): plan.md generated_at not older than HLD/LLD.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# scripts/tools/check-track-hygiene.sh # scan all ./tracks/*
|
|
17
|
+
# scripts/tools/check-track-hygiene.sh tracks/foo # scan one
|
|
18
|
+
# scripts/tools/check-track-hygiene.sh --json ... # JSON output
|
|
19
|
+
#
|
|
20
|
+
# Exit codes:
|
|
21
|
+
# 0 clean
|
|
22
|
+
# 1 hygiene violation
|
|
23
|
+
# 2 usage / runtime error
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
28
|
+
echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
33
|
+
# shellcheck source=/dev/null
|
|
34
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
35
|
+
|
|
36
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
37
|
+
|
|
38
|
+
EMIT_JSON=0
|
|
39
|
+
TRACK_PATHS=()
|
|
40
|
+
|
|
41
|
+
usage() {
|
|
42
|
+
local stream=2 code=2
|
|
43
|
+
if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
|
|
44
|
+
sed -n '2,21p' "$0" >&$stream
|
|
45
|
+
exit "$code"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
while (($#)); do
|
|
49
|
+
case "$1" in
|
|
50
|
+
-h|--help) USAGE_HELP_MODE=1 usage ;;
|
|
51
|
+
--json) EMIT_JSON=1; shift ;;
|
|
52
|
+
-*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
|
|
53
|
+
*) TRACK_PATHS+=("$1"); shift ;;
|
|
54
|
+
esac
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
if ((${#TRACK_PATHS[@]} == 0)); then
|
|
58
|
+
while IFS= read -r p; do TRACK_PATHS+=("$p"); done < <(discover_track_dirs "$REPO_ROOT")
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# Patterns
|
|
62
|
+
# Note: use `(example)?` rather than an empty alternation branch `(|example)` —
|
|
63
|
+
# strict ERE engines (BSD grep, ugrep) reject empty branches as a regex error.
|
|
64
|
+
FORBIDDEN_AUTHOR_RE='Author[0-9]+|xxx@(example)?\.(com|org)|\[name\]'
|
|
65
|
+
EMPTY_APPROVAL_ROW_RE='^\|[^|]*\|[[:space:]]*\|[[:space:]]*\|[[:space:]]*\|[[:space:]]*\|[[:space:]]*$'
|
|
66
|
+
TBD_RE='_TBD_[A-Za-z0-9_]+_'
|
|
67
|
+
|
|
68
|
+
violation_count=0
|
|
69
|
+
declare -a violations=()
|
|
70
|
+
|
|
71
|
+
record() {
|
|
72
|
+
local track="$1" kind="$2" file="$3" line="$4" detail="$5"
|
|
73
|
+
violations+=("$track|$kind|$file|$line|$detail")
|
|
74
|
+
violation_count=$((violation_count + 1))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
scan_one_track() {
|
|
78
|
+
local track_dir="$1"
|
|
79
|
+
local rel_track="${track_dir#"$REPO_ROOT/"}"
|
|
80
|
+
local meta="$track_dir/metadata.json"
|
|
81
|
+
|
|
82
|
+
local meta_status="draft"
|
|
83
|
+
if [[ -f "$meta" ]]; then
|
|
84
|
+
local s
|
|
85
|
+
s="$(read_json_str "$meta" "status")"
|
|
86
|
+
[[ -n "$s" ]] && meta_status="$s"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# 1. Status parity: search for "Status:" lines in markdown.
|
|
90
|
+
while IFS= read -r f; do
|
|
91
|
+
local rel_file="${f#"$track_dir/"}"
|
|
92
|
+
local n=0
|
|
93
|
+
while IFS= read -r line; do
|
|
94
|
+
n=$((n + 1))
|
|
95
|
+
case "$line" in
|
|
96
|
+
Status:*|*"**Status:**"*|*"Status: [x] Complete"*)
|
|
97
|
+
# Heuristic: if metadata says draft but doc says Complete, mismatch.
|
|
98
|
+
if [[ "$meta_status" != "completed" ]] && [[ "$line" == *'[x] Complete'* ]]; then
|
|
99
|
+
record "$rel_track" "status-mismatch" "$rel_file" "$n" \
|
|
100
|
+
"metadata.status=$meta_status, doc says Complete"
|
|
101
|
+
fi
|
|
102
|
+
;;
|
|
103
|
+
esac
|
|
104
|
+
done < "$f"
|
|
105
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
106
|
+
|
|
107
|
+
# 2. Forbidden author placeholders.
|
|
108
|
+
while IFS= read -r f; do
|
|
109
|
+
local rel_file="${f#"$track_dir/"}"
|
|
110
|
+
while IFS= read -r match; do
|
|
111
|
+
local n
|
|
112
|
+
n="${match%%:*}"
|
|
113
|
+
local rest="${match#*:}"
|
|
114
|
+
record "$rel_track" "forbidden-author" "$rel_file" "$n" "$rest"
|
|
115
|
+
done < <(grep -nE "$FORBIDDEN_AUTHOR_RE" "$f" 2>/dev/null || true)
|
|
116
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
117
|
+
|
|
118
|
+
# 3. Approval-bearing tables: detect empty cells.
|
|
119
|
+
while IFS= read -r f; do
|
|
120
|
+
local rel_file="${f#"$track_dir/"}"
|
|
121
|
+
local in_table=0
|
|
122
|
+
local n=0
|
|
123
|
+
while IFS= read -r line; do
|
|
124
|
+
n=$((n + 1))
|
|
125
|
+
# Detect a markdown table header starting with | Role |
|
|
126
|
+
if echo "$line" | grep -qiE '^\|[[:space:]]*role[[:space:]]*\|'; then
|
|
127
|
+
in_table=1
|
|
128
|
+
continue
|
|
129
|
+
fi
|
|
130
|
+
# Detect end of table: blank line.
|
|
131
|
+
if ((in_table)) && [[ -z "$line" ]]; then
|
|
132
|
+
in_table=0
|
|
133
|
+
continue
|
|
134
|
+
fi
|
|
135
|
+
if ((in_table)) && [[ "$line" =~ $EMPTY_APPROVAL_ROW_RE ]]; then
|
|
136
|
+
record "$rel_track" "empty-approver" "$rel_file" "$n" "$line"
|
|
137
|
+
fi
|
|
138
|
+
done < "$f"
|
|
139
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
140
|
+
|
|
141
|
+
# 4. TBD budget per-doc.
|
|
142
|
+
case "$meta_status" in
|
|
143
|
+
draft|archived) ;;
|
|
144
|
+
ready-for-review|in_progress)
|
|
145
|
+
local tbd_cap=3
|
|
146
|
+
while IFS= read -r f; do
|
|
147
|
+
local rel_file="${f#"$track_dir/"}"
|
|
148
|
+
local count
|
|
149
|
+
count="$(grep -oE "$TBD_RE" "$f" 2>/dev/null | wc -l | tr -d ' ')"
|
|
150
|
+
if (( count > tbd_cap )); then
|
|
151
|
+
record "$rel_track" "tbd-over-cap" "$rel_file" "0" \
|
|
152
|
+
"$count TBD sentinel(s) (cap $tbd_cap at status=$meta_status)"
|
|
153
|
+
fi
|
|
154
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
155
|
+
;;
|
|
156
|
+
completed)
|
|
157
|
+
while IFS= read -r f; do
|
|
158
|
+
local rel_file="${f#"$track_dir/"}"
|
|
159
|
+
local count
|
|
160
|
+
count="$(grep -oE "$TBD_RE" "$f" 2>/dev/null | wc -l | tr -d ' ')"
|
|
161
|
+
if (( count > 0 )); then
|
|
162
|
+
record "$rel_track" "tbd-in-completed" "$rel_file" "0" \
|
|
163
|
+
"$count TBD sentinel(s) at status=completed"
|
|
164
|
+
fi
|
|
165
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
166
|
+
;;
|
|
167
|
+
esac
|
|
168
|
+
|
|
169
|
+
# 5. Plan staleness vs HLD/LLD (WS-6).
|
|
170
|
+
if [[ -f "$track_dir/plan.md" ]]; then
|
|
171
|
+
local plan_ts hld_ts lld_ts
|
|
172
|
+
plan_ts="$(get_yaml_field "$track_dir/plan.md" "generated_at" || true)"
|
|
173
|
+
[[ -f "$track_dir/hld.md" ]] && hld_ts="$(get_yaml_field "$track_dir/hld.md" "generated_at" || true)"
|
|
174
|
+
[[ -f "$track_dir/lld.md" ]] && lld_ts="$(get_yaml_field "$track_dir/lld.md" "generated_at" || true)"
|
|
175
|
+
for sib_ts_pair in "hld.md|$hld_ts" "lld.md|$lld_ts"; do
|
|
176
|
+
local sib="${sib_ts_pair%%|*}"
|
|
177
|
+
local sib_ts="${sib_ts_pair#*|}"
|
|
178
|
+
[[ -z "$sib_ts" || -z "$plan_ts" ]] && continue
|
|
179
|
+
# Lexicographic compare works on ISO-8601 strings.
|
|
180
|
+
if [[ "$plan_ts" < "$sib_ts" ]]; then
|
|
181
|
+
record "$rel_track" "stale-plan" "plan.md" "0" \
|
|
182
|
+
"plan.md generated_at=$plan_ts older than $sib generated_at=$sib_ts"
|
|
183
|
+
fi
|
|
184
|
+
done
|
|
185
|
+
fi
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Self-check: git identity must be set.
|
|
189
|
+
if ! git config user.name >/dev/null 2>&1 || ! git config user.email >/dev/null 2>&1; then
|
|
190
|
+
printf 'check-track-hygiene: WARNING — git user.name/user.email not configured.\n' >&2
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
for t in "${TRACK_PATHS[@]}"; do
|
|
194
|
+
[[ -d "$t" ]] || { record "$t" "not-a-directory" "" "0" ""; continue; }
|
|
195
|
+
scan_one_track "$(cd "$t" && pwd)"
|
|
196
|
+
done
|
|
197
|
+
|
|
198
|
+
emit() {
|
|
199
|
+
if ((EMIT_JSON)); then
|
|
200
|
+
printf '{"violation_count": %d, "violations": [\n' "$violation_count"
|
|
201
|
+
local first=1 v track kind file line detail
|
|
202
|
+
# Guard the expansion: "${arr[@]}" on an empty array is an unbound-variable
|
|
203
|
+
# error under `set -u` in bash <= 4.3 (e.g. macOS).
|
|
204
|
+
if ((violation_count > 0)); then
|
|
205
|
+
for v in "${violations[@]}"; do
|
|
206
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
207
|
+
if ((first)); then first=0; else printf ',\n'; fi
|
|
208
|
+
printf ' {"track":"%s","kind":"%s","file":"%s","line":%s,"detail":"%s"}' \
|
|
209
|
+
"$(json_escape "$track")" "$(json_escape "$kind")" \
|
|
210
|
+
"$(json_escape "$file")" "${line:-0}" "$(json_escape "$detail")"
|
|
211
|
+
done
|
|
212
|
+
fi
|
|
213
|
+
printf '\n]}\n'
|
|
214
|
+
else
|
|
215
|
+
if ((violation_count == 0)); then
|
|
216
|
+
printf 'OK: clean across %d track(s).\n' "${#TRACK_PATHS[@]}"
|
|
217
|
+
else
|
|
218
|
+
printf 'HYGIENE: %d violation(s) across %d track(s).\n' \
|
|
219
|
+
"$violation_count" "${#TRACK_PATHS[@]}" >&2
|
|
220
|
+
local v track kind file line detail
|
|
221
|
+
for v in "${violations[@]}"; do
|
|
222
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
223
|
+
printf ' [%s] %s/%s:%s — %s\n' "$kind" "$track" "$file" "$line" "$detail" >&2
|
|
224
|
+
done
|
|
225
|
+
fi
|
|
226
|
+
fi
|
|
227
|
+
}
|
|
228
|
+
emit
|
|
229
|
+
|
|
230
|
+
((violation_count == 0))
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# classify-files.sh — deterministic file classification for a repo.
|
|
3
|
+
#
|
|
4
|
+
# Walks --root (default: cwd) respecting common ignore globs, emits one JSON
|
|
5
|
+
# object per line (JSONL) with language, category, test/generated flags,
|
|
6
|
+
# LOC, and top-level module.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# scripts/tools/classify-files.sh [--root DIR] [--json]
|
|
10
|
+
#
|
|
11
|
+
# Output fields: path, lang, category, is_test, is_generated, loc, module
|
|
12
|
+
# Exit codes: 0 OK (with or without results), 1 invocation error.
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# shellcheck source=_lib.sh
|
|
16
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
17
|
+
|
|
18
|
+
ROOT="."
|
|
19
|
+
FORMAT="jsonl"
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat <<'EOF'
|
|
23
|
+
classify-files.sh — deterministic file classification (JSONL).
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
scripts/tools/classify-files.sh [--root DIR] [--json]
|
|
27
|
+
|
|
28
|
+
Flags:
|
|
29
|
+
--root DIR Root directory to scan (default: cwd).
|
|
30
|
+
--json Emit a single JSON array instead of JSONL.
|
|
31
|
+
--help Show this help.
|
|
32
|
+
|
|
33
|
+
Output (per file): {path, lang, category, is_test, is_generated, loc, module}
|
|
34
|
+
|
|
35
|
+
Language is inferred from extension and shebang. A file is classified as a
|
|
36
|
+
test when its path or basename matches common test patterns. It is flagged
|
|
37
|
+
generated when the first 5 lines contain "Code generated" or "DO NOT EDIT".
|
|
38
|
+
Module is the first path segment under root.
|
|
39
|
+
EOF
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
while [[ $# -gt 0 ]]; do
|
|
43
|
+
case "$1" in
|
|
44
|
+
--root) ROOT="$2"; shift 2;;
|
|
45
|
+
--json) FORMAT="json"; shift;;
|
|
46
|
+
--jsonl) FORMAT="jsonl"; shift;;
|
|
47
|
+
--help|-h) usage; exit 0;;
|
|
48
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
49
|
+
esac
|
|
50
|
+
done
|
|
51
|
+
|
|
52
|
+
if [[ ! -d "$ROOT" ]]; then
|
|
53
|
+
echo "ERROR: --root '$ROOT' is not a directory" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Exclusions: common generated/vendor directories.
|
|
58
|
+
EXCLUDE_DIRS=(
|
|
59
|
+
.git node_modules dist build out .next .venv venv __pycache__
|
|
60
|
+
target vendor third_party .mypy_cache .pytest_cache coverage
|
|
61
|
+
.cache .tox .gradle .idea .vscode
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
lang_for() {
|
|
65
|
+
local path="$1"
|
|
66
|
+
local base; base="$(basename "$path")"
|
|
67
|
+
local ext="${base##*.}"
|
|
68
|
+
[[ "$ext" == "$base" ]] && ext=""
|
|
69
|
+
|
|
70
|
+
case "$ext" in
|
|
71
|
+
py) echo "python";;
|
|
72
|
+
js|mjs|cjs) echo "javascript";;
|
|
73
|
+
ts|tsx) echo "typescript";;
|
|
74
|
+
jsx) echo "javascript";;
|
|
75
|
+
go) echo "go";;
|
|
76
|
+
rs) echo "rust";;
|
|
77
|
+
java) echo "java";;
|
|
78
|
+
kt|kts) echo "kotlin";;
|
|
79
|
+
c|h) echo "c";;
|
|
80
|
+
cc|cpp|cxx|hpp|hh|hxx) echo "cpp";;
|
|
81
|
+
rb) echo "ruby";;
|
|
82
|
+
sh|bash) echo "bash";;
|
|
83
|
+
md|markdown) echo "markdown";;
|
|
84
|
+
yml|yaml) echo "yaml";;
|
|
85
|
+
json) echo "json";;
|
|
86
|
+
toml) echo "toml";;
|
|
87
|
+
proto) echo "proto";;
|
|
88
|
+
sql) echo "sql";;
|
|
89
|
+
html|htm) echo "html";;
|
|
90
|
+
css) echo "css";;
|
|
91
|
+
scss|sass) echo "sass";;
|
|
92
|
+
lua) echo "lua";;
|
|
93
|
+
php) echo "php";;
|
|
94
|
+
cs) echo "csharp";;
|
|
95
|
+
swift) echo "swift";;
|
|
96
|
+
*)
|
|
97
|
+
# shebang-based detection for extensionless scripts
|
|
98
|
+
if [[ -f "$path" ]] && head -1 "$path" 2>/dev/null | grep -qE '^#!'; then
|
|
99
|
+
local shebang
|
|
100
|
+
shebang="$(head -1 "$path")"
|
|
101
|
+
case "$shebang" in
|
|
102
|
+
*bash*|*zsh*|*/sh*) echo "bash";;
|
|
103
|
+
*python*) echo "python";;
|
|
104
|
+
*node*) echo "javascript";;
|
|
105
|
+
*ruby*) echo "ruby";;
|
|
106
|
+
*) echo "other";;
|
|
107
|
+
esac
|
|
108
|
+
else
|
|
109
|
+
echo "other"
|
|
110
|
+
fi
|
|
111
|
+
;;
|
|
112
|
+
esac
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
is_test_file() {
|
|
116
|
+
local path="$1"
|
|
117
|
+
local base; base="$(basename "$path")"
|
|
118
|
+
|
|
119
|
+
# Path segment
|
|
120
|
+
case "/$path/" in
|
|
121
|
+
*/tests/*|*/test/*|*/__tests__/*|*/spec/*|*/specs/*) echo "true"; return;;
|
|
122
|
+
esac
|
|
123
|
+
|
|
124
|
+
# Filename patterns
|
|
125
|
+
case "$base" in
|
|
126
|
+
test_*.py|*_test.py|tests.py|conftest.py) echo "true"; return;;
|
|
127
|
+
*_test.go) echo "true"; return;;
|
|
128
|
+
*.test.ts|*.test.tsx|*.test.js|*.test.jsx) echo "true"; return;;
|
|
129
|
+
*.spec.ts|*.spec.tsx|*.spec.js|*.spec.jsx) echo "true"; return;;
|
|
130
|
+
*Test.java|*Tests.java|*Spec.java) echo "true"; return;;
|
|
131
|
+
test-*.sh|*-test.sh|*_test.sh) echo "true"; return;;
|
|
132
|
+
esac
|
|
133
|
+
|
|
134
|
+
echo "false"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
is_generated() {
|
|
138
|
+
local path="$1"
|
|
139
|
+
[[ -f "$path" ]] || { echo "false"; return; }
|
|
140
|
+
if head -5 "$path" 2>/dev/null | grep -qE 'Code generated|DO NOT EDIT|@generated|autogenerated'; then
|
|
141
|
+
echo "true"
|
|
142
|
+
else
|
|
143
|
+
echo "false"
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module_for() {
|
|
148
|
+
local rel="$1"
|
|
149
|
+
local first="${rel%%/*}"
|
|
150
|
+
if [[ "$first" == "$rel" ]]; then
|
|
151
|
+
echo "."
|
|
152
|
+
else
|
|
153
|
+
echo "$first"
|
|
154
|
+
fi
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Build find with prunes for excluded dirs. Use absolute root so emitted paths are absolute.
|
|
158
|
+
ROOT_REAL="$(cd "$ROOT" && pwd)"
|
|
159
|
+
FIND_ARGS=("$ROOT_REAL" -type d \( )
|
|
160
|
+
first_dir=true
|
|
161
|
+
for dir in "${EXCLUDE_DIRS[@]}"; do
|
|
162
|
+
if $first_dir; then
|
|
163
|
+
FIND_ARGS+=(-name "$dir")
|
|
164
|
+
first_dir=false
|
|
165
|
+
else
|
|
166
|
+
FIND_ARGS+=(-o -name "$dir")
|
|
167
|
+
fi
|
|
168
|
+
done
|
|
169
|
+
FIND_ARGS+=(\) -prune -o -type f -print)
|
|
170
|
+
|
|
171
|
+
first_record=true
|
|
172
|
+
|
|
173
|
+
emit_record() {
|
|
174
|
+
local json="$1"
|
|
175
|
+
if [[ "$FORMAT" == "json" ]]; then
|
|
176
|
+
if $first_record; then
|
|
177
|
+
printf '[\n %s' "$json"
|
|
178
|
+
first_record=false
|
|
179
|
+
else
|
|
180
|
+
printf ',\n %s' "$json"
|
|
181
|
+
fi
|
|
182
|
+
else
|
|
183
|
+
printf '%s\n' "$json"
|
|
184
|
+
fi
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
while IFS= read -r file; do
|
|
188
|
+
# `find -type f` guarantees the entry is a regular file; no re-check needed.
|
|
189
|
+
# `find` run on an absolute ROOT emits absolute paths — strip the prefix.
|
|
190
|
+
rel="${file#"$ROOT_REAL/"}"
|
|
191
|
+
case "$rel" in
|
|
192
|
+
.git/*) continue;;
|
|
193
|
+
esac
|
|
194
|
+
|
|
195
|
+
lang="$(lang_for "$file")"
|
|
196
|
+
is_test="$(is_test_file "$rel")"
|
|
197
|
+
gen="$(is_generated "$file")"
|
|
198
|
+
module="$(module_for "$rel")"
|
|
199
|
+
|
|
200
|
+
loc="$(wc -l <"$file" 2>/dev/null | tr -d ' ' || echo 0)"
|
|
201
|
+
|
|
202
|
+
# Category: test > generated > source > config/docs
|
|
203
|
+
if [[ "$is_test" == "true" ]]; then
|
|
204
|
+
category="test"
|
|
205
|
+
elif [[ "$gen" == "true" ]]; then
|
|
206
|
+
category="generated"
|
|
207
|
+
else
|
|
208
|
+
case "$lang" in
|
|
209
|
+
markdown|yaml|json|toml) category="docs";;
|
|
210
|
+
*) category="source";;
|
|
211
|
+
esac
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
record=$(printf '{"path":"%s","lang":"%s","category":"%s","is_test":%s,"is_generated":%s,"loc":%s,"module":"%s"}' \
|
|
215
|
+
"$(json_escape "$rel")" \
|
|
216
|
+
"$(json_escape "$lang")" \
|
|
217
|
+
"$(json_escape "$category")" \
|
|
218
|
+
"$is_test" \
|
|
219
|
+
"$gen" \
|
|
220
|
+
"$loc" \
|
|
221
|
+
"$(json_escape "$module")")
|
|
222
|
+
emit_record "$record"
|
|
223
|
+
done < <(find "${FIND_ARGS[@]}" 2>/dev/null | sort)
|
|
224
|
+
|
|
225
|
+
if [[ "$FORMAT" == "json" ]]; then
|
|
226
|
+
if $first_record; then
|
|
227
|
+
printf '[]\n'
|
|
228
|
+
else
|
|
229
|
+
printf '\n]\n'
|
|
230
|
+
fi
|
|
231
|
+
fi
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# cycle-detect.sh — emit call cycles from the knowledge graph.
|
|
3
|
+
#
|
|
4
|
+
# Backed by the codebase-memory-mcp engine. Uses bounded, fixed-length CALLS
|
|
5
|
+
# patterns via openCypher (this engine's dialect handles explicit patterns
|
|
6
|
+
# reliably but not variable-length/aggregate queries). Detects 2- and 3-node
|
|
7
|
+
# call cycles, which surface mutual recursion and tight coupling.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# scripts/tools/cycle-detect.sh [--repo DIR]
|
|
11
|
+
#
|
|
12
|
+
# Output: JSON {cycles:[[a,b],[a,b,c], ...], source}.
|
|
13
|
+
# source = "memory-graph" | "unavailable"
|
|
14
|
+
#
|
|
15
|
+
# Exit codes: 0 OK, 1 invocation error, 2 graph engine/data unavailable.
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# shellcheck source=_lib.sh
|
|
19
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
20
|
+
|
|
21
|
+
REPO="."
|
|
22
|
+
|
|
23
|
+
usage() {
|
|
24
|
+
cat <<'EOF'
|
|
25
|
+
cycle-detect.sh — call-cycle detection from the knowledge graph.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
scripts/tools/cycle-detect.sh [--repo DIR]
|
|
29
|
+
|
|
30
|
+
Flags:
|
|
31
|
+
--repo DIR Repository root (default: cwd).
|
|
32
|
+
--help Show this help.
|
|
33
|
+
|
|
34
|
+
Output: JSON {cycles:[[a,b],[a,b,c]], source}. Fallback when the engine is
|
|
35
|
+
unavailable: {"cycles":[],"source":"unavailable"}, exit 2.
|
|
36
|
+
EOF
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
while [[ $# -gt 0 ]]; do
|
|
40
|
+
case "$1" in
|
|
41
|
+
--repo) REPO="$2"; shift 2;;
|
|
42
|
+
--help|-h) usage; exit 0;;
|
|
43
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
44
|
+
esac
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
if [[ ! -d "$REPO" ]]; then
|
|
48
|
+
echo "ERROR: --repo '$REPO' is not a directory" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
53
|
+
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
54
|
+
|
|
55
|
+
unavailable() { echo '{"cycles":[],"source":"unavailable"}'; exit 2; }
|
|
56
|
+
|
|
57
|
+
find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
|
|
58
|
+
command -v jq >/dev/null 2>&1 || unavailable
|
|
59
|
+
|
|
60
|
+
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
61
|
+
[[ -n "$PROJECT" ]] || unavailable
|
|
62
|
+
|
|
63
|
+
# 2-cycles: a -> b -> a (dedup with a.qualified_name < b.qualified_name).
|
|
64
|
+
Q2="MATCH (a:Function)-[:CALLS]->(b:Function)-[:CALLS]->(a) WHERE a.qualified_name < b.qualified_name RETURN a.qualified_name AS a, b.qualified_name AS b LIMIT 100"
|
|
65
|
+
# 3-cycles: a -> b -> c -> a.
|
|
66
|
+
Q3="MATCH (a:Function)-[:CALLS]->(b:Function)-[:CALLS]->(c:Function)-[:CALLS]->(a) RETURN a.qualified_name AS a, b.qualified_name AS b, c.qualified_name AS c LIMIT 100"
|
|
67
|
+
|
|
68
|
+
R2="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$Q2\"}" || echo '{}')"
|
|
69
|
+
R3="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$Q3\"}" || echo '{}')"
|
|
70
|
+
|
|
71
|
+
jq -n --argjson r2 "${R2:-{\}}" --argjson r3 "${R3:-{\}}" '
|
|
72
|
+
{
|
|
73
|
+
cycles: (((($r2.rows) // []) + (($r3.rows) // []))),
|
|
74
|
+
source: "memory-graph"
|
|
75
|
+
}'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# detect-test-framework.sh — detect test framework(s) used in a repo.
|
|
3
|
+
#
|
|
4
|
+
# Output: JSON {languages:[{language, framework, runner_command, test_globs, config_file}]}
|
|
5
|
+
#
|
|
6
|
+
# Detection rules (first match per language wins):
|
|
7
|
+
# python → pytest (pytest.ini | pyproject.toml has "[tool.pytest.ini_options]")
|
|
8
|
+
# | unittest (has tests but no pytest config)
|
|
9
|
+
# go → go test (go.mod + any *_test.go)
|
|
10
|
+
# javascript/typescript → vitest | jest | mocha (by package.json scripts / devDependencies)
|
|
11
|
+
# rust → cargo test (Cargo.toml + tests/)
|
|
12
|
+
# shell → plain bash (tests/ dir has test-*.sh)
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# scripts/tools/detect-test-framework.sh [--root DIR]
|
|
16
|
+
#
|
|
17
|
+
# Exit codes: 0 (always; empty languages array is valid output).
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# shellcheck source=_lib.sh
|
|
21
|
+
source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
|
22
|
+
|
|
23
|
+
ROOT="."
|
|
24
|
+
|
|
25
|
+
usage() {
|
|
26
|
+
cat <<'EOF'
|
|
27
|
+
detect-test-framework.sh — detect language test frameworks.
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
scripts/tools/detect-test-framework.sh [--root DIR]
|
|
31
|
+
|
|
32
|
+
Flags:
|
|
33
|
+
--root DIR Repository root (default: cwd).
|
|
34
|
+
--help Show this help.
|
|
35
|
+
|
|
36
|
+
Output: JSON {languages:[{language, framework, runner_command, test_globs, config_file}]}
|
|
37
|
+
EOF
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
while [[ $# -gt 0 ]]; do
|
|
41
|
+
case "$1" in
|
|
42
|
+
--root) ROOT="$2"; shift 2;;
|
|
43
|
+
--help|-h) usage; exit 0;;
|
|
44
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
if [[ ! -d "$ROOT" ]]; then
|
|
49
|
+
echo "ERROR: --root '$ROOT' is not a directory" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
cd "$ROOT"
|
|
54
|
+
|
|
55
|
+
emit_lang() {
|
|
56
|
+
local language="$1" framework="$2" runner="$3" globs="$4" config="$5"
|
|
57
|
+
printf '{"language":"%s","framework":"%s","runner_command":"%s","test_globs":%s,"config_file":%s}' \
|
|
58
|
+
"$(json_escape "$language")" \
|
|
59
|
+
"$(json_escape "$framework")" \
|
|
60
|
+
"$(json_escape "$runner")" \
|
|
61
|
+
"$globs" \
|
|
62
|
+
"$([[ -z "$config" ]] && echo null || echo "\"$(json_escape "$config")\"")"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
RESULTS=()
|
|
66
|
+
|
|
67
|
+
# ── Python ──
|
|
68
|
+
PY_CONFIG=""
|
|
69
|
+
PY_FRAMEWORK=""
|
|
70
|
+
if [[ -f pytest.ini ]]; then
|
|
71
|
+
PY_CONFIG="pytest.ini"; PY_FRAMEWORK="pytest"
|
|
72
|
+
elif [[ -f pyproject.toml ]] && grep -q 'tool.pytest' pyproject.toml 2>/dev/null; then
|
|
73
|
+
PY_CONFIG="pyproject.toml"; PY_FRAMEWORK="pytest"
|
|
74
|
+
elif [[ -f setup.cfg ]] && grep -qE '^\[tool:pytest\]' setup.cfg 2>/dev/null; then
|
|
75
|
+
PY_CONFIG="setup.cfg"; PY_FRAMEWORK="pytest"
|
|
76
|
+
fi
|
|
77
|
+
if [[ -z "$PY_FRAMEWORK" ]]; then
|
|
78
|
+
if find . -maxdepth 4 \( -name 'test_*.py' -o -name '*_test.py' \) \
|
|
79
|
+
-not -path './.git/*' -not -path './node_modules/*' -print -quit 2>/dev/null | grep -q .; then
|
|
80
|
+
PY_FRAMEWORK="unittest"
|
|
81
|
+
fi
|
|
82
|
+
fi
|
|
83
|
+
if [[ -n "$PY_FRAMEWORK" ]]; then
|
|
84
|
+
RESULTS+=("$(emit_lang "python" "$PY_FRAMEWORK" \
|
|
85
|
+
"$([[ "$PY_FRAMEWORK" == pytest ]] && echo 'pytest' || echo 'python -m unittest')" \
|
|
86
|
+
'["test_*.py","*_test.py"]' \
|
|
87
|
+
"$PY_CONFIG")")
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# ── Go ──
|
|
91
|
+
if [[ -f go.mod ]]; then
|
|
92
|
+
if find . -maxdepth 6 -name '*_test.go' -not -path './vendor/*' -print -quit 2>/dev/null | grep -q .; then
|
|
93
|
+
RESULTS+=("$(emit_lang "go" "go test" "go test ./..." '["*_test.go"]' "go.mod")")
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ── JS/TS ── (single file read, match in-memory)
|
|
98
|
+
if [[ -f package.json ]]; then
|
|
99
|
+
PKG_JSON="$(<package.json)"
|
|
100
|
+
JS_FRAMEWORK=""
|
|
101
|
+
if [[ "$PKG_JSON" == *'"vitest"'* ]]; then
|
|
102
|
+
JS_FRAMEWORK="vitest"
|
|
103
|
+
elif [[ "$PKG_JSON" == *'"jest"'* ]]; then
|
|
104
|
+
JS_FRAMEWORK="jest"
|
|
105
|
+
elif [[ "$PKG_JSON" == *'"mocha"'* ]]; then
|
|
106
|
+
JS_FRAMEWORK="mocha"
|
|
107
|
+
fi
|
|
108
|
+
if [[ -n "$JS_FRAMEWORK" ]]; then
|
|
109
|
+
RESULTS+=("$(emit_lang "javascript" "$JS_FRAMEWORK" \
|
|
110
|
+
"npm test" \
|
|
111
|
+
'["*.test.[jt]s","*.test.[jt]sx","*.spec.[jt]s","*.spec.[jt]sx"]' \
|
|
112
|
+
"package.json")")
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# ── Rust ──
|
|
117
|
+
if [[ -f Cargo.toml ]]; then
|
|
118
|
+
RESULTS+=("$(emit_lang "rust" "cargo test" "cargo test" '["tests/*.rs","src/**/*_test.rs"]' "Cargo.toml")")
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
# ── Shell ──
|
|
122
|
+
if find . -maxdepth 3 -type d -name tests -print -quit 2>/dev/null | grep -q .; then
|
|
123
|
+
if find tests -maxdepth 2 -name 'test-*.sh' -print -quit 2>/dev/null | grep -q .; then
|
|
124
|
+
RESULTS+=("$(emit_lang "shell" "bash" "./tests/run-all.sh" '["tests/test-*.sh"]' "")")
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Assemble
|
|
129
|
+
printf '{"languages":['
|
|
130
|
+
first=true
|
|
131
|
+
for r in ${RESULTS[@]+"${RESULTS[@]}"}; do
|
|
132
|
+
if $first; then first=false; else printf ','; fi
|
|
133
|
+
printf '%s' "$r"
|
|
134
|
+
done
|
|
135
|
+
printf ']}\n'
|