@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,250 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-citations.sh
|
|
3
|
+
#
|
|
4
|
+
# Resolve every `path:line` and `path:line-range` citation in a track's
|
|
5
|
+
# Markdown against the commit pinned in `metadata.json:synced_to_commit`,
|
|
6
|
+
# and confirm the cited content still resides within the line window.
|
|
7
|
+
#
|
|
8
|
+
# Drift tolerance is configurable (default ±5 lines). Citations wrapped
|
|
9
|
+
# inside <!-- VERIFIER:IGNORE START --> ... <!-- VERIFIER:IGNORE END -->
|
|
10
|
+
# blocks are skipped. Citations annotated as `(planned)` or carrying a
|
|
11
|
+
# `[New file ...]` annotation are skipped (must not exist yet).
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# scripts/tools/verify-citations.sh # scan ./tracks/*
|
|
15
|
+
# scripts/tools/verify-citations.sh tracks/foo # scan one
|
|
16
|
+
# scripts/tools/verify-citations.sh --tolerance 10 --json tracks/foo
|
|
17
|
+
#
|
|
18
|
+
# Exit codes:
|
|
19
|
+
# 0 clean
|
|
20
|
+
# 1 drift detected
|
|
21
|
+
# 2 usage / runtime error
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
26
|
+
echo "${0##*/} — Foundations quality tool (see core/ docs for full behavior)"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
31
|
+
# shellcheck source=/dev/null
|
|
32
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
33
|
+
|
|
34
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
35
|
+
|
|
36
|
+
EMIT_JSON=0
|
|
37
|
+
TOLERANCE=5
|
|
38
|
+
TRACK_PATHS=()
|
|
39
|
+
|
|
40
|
+
usage() {
|
|
41
|
+
local stream=2 code=2
|
|
42
|
+
if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
|
|
43
|
+
sed -n '2,20p' "$0" >&$stream
|
|
44
|
+
exit "$code"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
while (($#)); do
|
|
48
|
+
case "$1" in
|
|
49
|
+
-h|--help) USAGE_HELP_MODE=1 usage ;;
|
|
50
|
+
--json) EMIT_JSON=1; shift ;;
|
|
51
|
+
--tolerance) TOLERANCE="$2"; shift 2 ;;
|
|
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
|
+
violation_count=0
|
|
62
|
+
declare -a violations=()
|
|
63
|
+
|
|
64
|
+
record() {
|
|
65
|
+
violations+=("$1|$2|$3|$4|$5")
|
|
66
|
+
violation_count=$((violation_count + 1))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Extract citations from a markdown file, skipping VERIFIER:IGNORE blocks.
|
|
70
|
+
# Output: file<TAB>line<TAB>cite (cite is path:LINE or path:LINE-LINE).
|
|
71
|
+
extract_citations() {
|
|
72
|
+
local md="$1"
|
|
73
|
+
awk '
|
|
74
|
+
BEGIN { ignore = 0 }
|
|
75
|
+
/<!-- VERIFIER:IGNORE START -->/ { ignore = 1; next }
|
|
76
|
+
/<!-- VERIFIER:IGNORE END -->/ { ignore = 0; next }
|
|
77
|
+
ignore == 1 { next }
|
|
78
|
+
/\(planned\)|\[New file/ { next }
|
|
79
|
+
{
|
|
80
|
+
s = $0
|
|
81
|
+
gsub(/https?:\/\/[^[:space:]]*/, "", s)
|
|
82
|
+
# Use a string regex (not a /.../ literal): a literal "/" inside a
|
|
83
|
+
# bracket expression prematurely terminates a regex literal in
|
|
84
|
+
# POSIX/BSD awk ("nonterminated character class"), though gawk is
|
|
85
|
+
# lenient. The string form is portable across awk implementations.
|
|
86
|
+
while (match(s, "[A-Za-z0-9_][A-Za-z0-9_./-]*\\.[A-Za-z0-9]+:[0-9]+")) {
|
|
87
|
+
cite = substr(s, RSTART, RLENGTH)
|
|
88
|
+
printf("%s\t%d\t%s\n", FILENAME, NR, cite)
|
|
89
|
+
s = substr(s, RSTART + RLENGTH)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
' "$md"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Resolve cite against the synced_to_commit content using git show.
|
|
96
|
+
# Returns 0 if match within tolerance, 1 if past-EOF, 2 if file truly missing.
|
|
97
|
+
#
|
|
98
|
+
# When the cite is a bare basename (no slash) and the literal path doesn't
|
|
99
|
+
# exist at the commit, fall back to a basename search across the tree — this
|
|
100
|
+
# accepts narrative citations like `foo.cc:42` that mean `path/to/foo.cc:42`.
|
|
101
|
+
# Skipped when multiple files share the basename (ambiguous → reject).
|
|
102
|
+
verify_one_citation() {
|
|
103
|
+
local repo_root="$1" commit="$2" cite="$3"
|
|
104
|
+
local path lo hi
|
|
105
|
+
path="${cite%%:*}"
|
|
106
|
+
local range="${cite#*:}"
|
|
107
|
+
if [[ "$range" == *-* ]]; then
|
|
108
|
+
lo="${range%%-*}"
|
|
109
|
+
hi="${range##*-}"
|
|
110
|
+
else
|
|
111
|
+
lo="$range"
|
|
112
|
+
hi="$range"
|
|
113
|
+
fi
|
|
114
|
+
path="${path#./}"
|
|
115
|
+
|
|
116
|
+
local resolved_path="$path"
|
|
117
|
+
|
|
118
|
+
# Probe via git show.
|
|
119
|
+
if ! git -C "$repo_root" cat-file -e "$commit":"$path" 2>/dev/null; then
|
|
120
|
+
# Bare-basename fallback: if the cite carries no slash, search the
|
|
121
|
+
# tree for files whose basename matches and accept when unique.
|
|
122
|
+
if [[ "$path" != */* ]]; then
|
|
123
|
+
local matches
|
|
124
|
+
matches="$(git -C "$repo_root" ls-tree -r --name-only "$commit" 2>/dev/null \
|
|
125
|
+
| awk -v b="$path" 'BEGIN{FS="/"} $NF==b{print}')"
|
|
126
|
+
local match_count
|
|
127
|
+
match_count="$(printf '%s\n' "$matches" | grep -c . || true)"
|
|
128
|
+
if (( match_count == 1 )); then
|
|
129
|
+
resolved_path="$matches"
|
|
130
|
+
elif (( match_count > 1 )); then
|
|
131
|
+
printf 'ambiguous-basename|%s has %d candidates: %s\n' \
|
|
132
|
+
"$path" "$match_count" \
|
|
133
|
+
"$(printf '%s' "$matches" | tr '\n' ',' | sed 's/,$//')"
|
|
134
|
+
return 1
|
|
135
|
+
else
|
|
136
|
+
return 2
|
|
137
|
+
fi
|
|
138
|
+
else
|
|
139
|
+
return 2
|
|
140
|
+
fi
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
local total_lines
|
|
144
|
+
total_lines="$(git -C "$repo_root" show "$commit":"$resolved_path" 2>/dev/null | wc -l | tr -d ' ')"
|
|
145
|
+
if (( lo < 1 )); then
|
|
146
|
+
printf 'invalid-line|cite %s has line < 1\n' "$cite"
|
|
147
|
+
return 1
|
|
148
|
+
fi
|
|
149
|
+
if (( hi > total_lines + TOLERANCE )); then
|
|
150
|
+
printf 'past-eof|file %s has %d lines, cite %s exceeds (+tol %d)\n' \
|
|
151
|
+
"$resolved_path" "$total_lines" "$cite" "$TOLERANCE"
|
|
152
|
+
return 1
|
|
153
|
+
fi
|
|
154
|
+
if (( lo > total_lines + TOLERANCE )); then
|
|
155
|
+
printf 'past-eof|file %s has %d lines, cite %s start exceeds (+tol %d)\n' \
|
|
156
|
+
"$resolved_path" "$total_lines" "$cite" "$TOLERANCE"
|
|
157
|
+
return 1
|
|
158
|
+
fi
|
|
159
|
+
return 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
scan_one_track() {
|
|
163
|
+
local track_dir="$1"
|
|
164
|
+
local rel_track="${track_dir#"$REPO_ROOT/"}"
|
|
165
|
+
local meta="$track_dir/metadata.json"
|
|
166
|
+
local commit=""
|
|
167
|
+
if [[ -f "$meta" ]]; then
|
|
168
|
+
commit="$(read_json_str "$meta" "synced_to_commit")"
|
|
169
|
+
fi
|
|
170
|
+
if [[ -z "$commit" ]]; then
|
|
171
|
+
# Fall back to YAML frontmatter on spec.md if present.
|
|
172
|
+
[[ -f "$track_dir/spec.md" ]] && commit="$(get_yaml_field "$track_dir/spec.md" "synced_to_commit")"
|
|
173
|
+
fi
|
|
174
|
+
if [[ -z "$commit" ]]; then
|
|
175
|
+
record "$rel_track" "no-pinned-commit" "metadata.json" "0" \
|
|
176
|
+
"no synced_to_commit; cannot verify citations"
|
|
177
|
+
return
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
# Determine which git repo to query. Prefer the track's enclosing repo
|
|
181
|
+
# (first git dir above the track_dir). Fall back to Draft repo.
|
|
182
|
+
local probe="$track_dir"
|
|
183
|
+
local target_repo=""
|
|
184
|
+
while [[ "$probe" != "/" ]]; do
|
|
185
|
+
if [[ -e "$probe/.git" ]]; then target_repo="$probe"; break; fi
|
|
186
|
+
probe="$(dirname "$probe")"
|
|
187
|
+
done
|
|
188
|
+
[[ -z "$target_repo" ]] && target_repo="$REPO_ROOT"
|
|
189
|
+
|
|
190
|
+
while IFS= read -r f; do
|
|
191
|
+
local rel_md="${f#"$track_dir/"}"
|
|
192
|
+
while IFS=$'\t' read -r md_file lineno cite; do
|
|
193
|
+
local md_line="$md_file"
|
|
194
|
+
local result rc=0
|
|
195
|
+
# set +e to capture rc; set -e would exit on a non-zero return.
|
|
196
|
+
set +e
|
|
197
|
+
result="$(verify_one_citation "$target_repo" "$commit" "$cite" 2>&1)"
|
|
198
|
+
rc=$?
|
|
199
|
+
set -e
|
|
200
|
+
if (( rc == 1 )); then
|
|
201
|
+
record "$rel_track" "cite-drift" "$rel_md" "$lineno" \
|
|
202
|
+
"$cite — $result"
|
|
203
|
+
elif (( rc == 2 )); then
|
|
204
|
+
record "$rel_track" "cite-missing-file" "$rel_md" "$lineno" \
|
|
205
|
+
"$cite — file not present at commit $commit"
|
|
206
|
+
fi
|
|
207
|
+
done < <(extract_citations "$f")
|
|
208
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for t in "${TRACK_PATHS[@]}"; do
|
|
212
|
+
[[ -d "$t" ]] || { record "$t" "not-a-directory" "" "0" ""; continue; }
|
|
213
|
+
scan_one_track "$(cd "$t" && pwd)"
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
emit() {
|
|
217
|
+
if ((EMIT_JSON)); then
|
|
218
|
+
printf '{"violation_count": %d, "tolerance": %d, "violations": [\n' \
|
|
219
|
+
"$violation_count" "$TOLERANCE"
|
|
220
|
+
local first=1 v track kind file line detail
|
|
221
|
+
# Guard the expansion: "${arr[@]}" on an empty array is an unbound-variable
|
|
222
|
+
# error under `set -u` in bash <= 4.3 (e.g. macOS).
|
|
223
|
+
if ((violation_count > 0)); then
|
|
224
|
+
for v in "${violations[@]}"; do
|
|
225
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
226
|
+
if ((first)); then first=0; else printf ',\n'; fi
|
|
227
|
+
printf ' {"track":"%s","kind":"%s","file":"%s","line":%s,"detail":"%s"}' \
|
|
228
|
+
"$(json_escape "$track")" "$(json_escape "$kind")" \
|
|
229
|
+
"$(json_escape "$file")" "${line:-0}" "$(json_escape "$detail")"
|
|
230
|
+
done
|
|
231
|
+
fi
|
|
232
|
+
printf '\n]}\n'
|
|
233
|
+
else
|
|
234
|
+
if ((violation_count == 0)); then
|
|
235
|
+
printf 'OK: citations clean across %d track(s) (tol=±%d).\n' \
|
|
236
|
+
"${#TRACK_PATHS[@]}" "$TOLERANCE"
|
|
237
|
+
else
|
|
238
|
+
printf 'CITATIONS: %d violation(s) across %d track(s) (tol=±%d).\n' \
|
|
239
|
+
"$violation_count" "${#TRACK_PATHS[@]}" "$TOLERANCE" >&2
|
|
240
|
+
local v track kind file line detail
|
|
241
|
+
for v in "${violations[@]}"; do
|
|
242
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
243
|
+
printf ' [%s] %s/%s:%s — %s\n' "$kind" "$track" "$file" "$line" "$detail" >&2
|
|
244
|
+
done
|
|
245
|
+
fi
|
|
246
|
+
fi
|
|
247
|
+
}
|
|
248
|
+
emit
|
|
249
|
+
|
|
250
|
+
((violation_count == 0))
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-doc-anchors.sh
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Verify cross-document references in a track:
|
|
6
|
+
# - §X.Y or §X numbered-section references → target document must contain
|
|
7
|
+
# a heading whose text starts with that number.
|
|
8
|
+
# - <doc>.md#<anchor> markdown anchors → resolve <anchor> against the
|
|
9
|
+
# target file's header slugs.
|
|
10
|
+
# - (planned) / [New file ...] annotations → file MUST NOT exist locally.
|
|
11
|
+
#
|
|
12
|
+
# Skips content inside <!-- VERIFIER:IGNORE START --> ... END --> blocks.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# scripts/tools/verify-doc-anchors.sh # scan ./tracks/*
|
|
16
|
+
# scripts/tools/verify-doc-anchors.sh tracks/foo # scan one
|
|
17
|
+
# scripts/tools/verify-doc-anchors.sh --json ...
|
|
18
|
+
#
|
|
19
|
+
# Exit codes:
|
|
20
|
+
# 0 clean
|
|
21
|
+
# 1 anchor violation
|
|
22
|
+
# 2 usage / runtime error
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
# shellcheck source=/dev/null
|
|
28
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
29
|
+
|
|
30
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
31
|
+
|
|
32
|
+
EMIT_JSON=0
|
|
33
|
+
TRACK_PATHS=()
|
|
34
|
+
|
|
35
|
+
usage() {
|
|
36
|
+
local stream=2 code=2
|
|
37
|
+
if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
|
|
38
|
+
sed -n '2,20p' "$0" >&$stream
|
|
39
|
+
exit "$code"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
while (($#)); do
|
|
43
|
+
case "$1" in
|
|
44
|
+
-h|--help) USAGE_HELP_MODE=1 usage ;;
|
|
45
|
+
--json) EMIT_JSON=1; shift ;;
|
|
46
|
+
-*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
|
|
47
|
+
*) TRACK_PATHS+=("$1"); shift ;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
if ((${#TRACK_PATHS[@]} == 0)); then
|
|
52
|
+
while IFS= read -r p; do TRACK_PATHS+=("$p"); done < <(
|
|
53
|
+
find "$REPO_ROOT" -type d -path '*/tracks/*' -maxdepth 4 -mindepth 2 \
|
|
54
|
+
-not -path '*/.*' 2>/dev/null | sort
|
|
55
|
+
)
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
violation_count=0
|
|
59
|
+
declare -a violations=()
|
|
60
|
+
record() { violations+=("$1|$2|$3|$4|$5"); violation_count=$((violation_count + 1)); }
|
|
61
|
+
|
|
62
|
+
# Build a deduplicated list of header slugs from a markdown file.
|
|
63
|
+
# Standard GitHub-flavored slug: lowercase, spaces → '-', strip punctuation.
|
|
64
|
+
md_slugs() {
|
|
65
|
+
awk '
|
|
66
|
+
/^#{1,6} +/ {
|
|
67
|
+
s = $0
|
|
68
|
+
sub(/^#+ +/, "", s)
|
|
69
|
+
# Strip trailing whitespace
|
|
70
|
+
sub(/[[:space:]]+$/, "", s)
|
|
71
|
+
# Lowercase
|
|
72
|
+
s = tolower(s)
|
|
73
|
+
# Replace spaces with dashes
|
|
74
|
+
gsub(/ +/, "-", s)
|
|
75
|
+
# Strip punctuation except dashes
|
|
76
|
+
gsub(/[^a-z0-9-]/, "", s)
|
|
77
|
+
print s
|
|
78
|
+
}
|
|
79
|
+
' "$1" | sort -u
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Extract numbered headers (e.g. "## 7 Parallel SST transfer" or "## 20.2 ...").
|
|
83
|
+
md_numbered_headers() {
|
|
84
|
+
awk '
|
|
85
|
+
/^#{1,6} +[0-9]+(\.[0-9]+)*[ .]/ {
|
|
86
|
+
s = $0
|
|
87
|
+
sub(/^#+ +/, "", s)
|
|
88
|
+
print s
|
|
89
|
+
}
|
|
90
|
+
' "$1"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
scan_md() {
|
|
94
|
+
local track_dir="$1" md="$2"
|
|
95
|
+
local rel_track="${track_dir#"$REPO_ROOT/"}"
|
|
96
|
+
local rel_md="${md#"$track_dir/"}"
|
|
97
|
+
local ignore=0 lineno=0
|
|
98
|
+
while IFS= read -r line; do
|
|
99
|
+
lineno=$((lineno + 1))
|
|
100
|
+
if [[ "$line" == *"<!-- VERIFIER:IGNORE START -->"* ]]; then ignore=1; continue; fi
|
|
101
|
+
if [[ "$line" == *"<!-- VERIFIER:IGNORE END -->"* ]]; then ignore=0; continue; fi
|
|
102
|
+
(( ignore )) && continue
|
|
103
|
+
|
|
104
|
+
# 1. §-references inside a markdown link.
|
|
105
|
+
# Plain-prose `§X.Y` is too ambiguous in practice — authors commonly
|
|
106
|
+
# use it as shorthand for an external doc (e.g. `architecture.md §20.2`)
|
|
107
|
+
# without naming the file on the same line. To keep this validator
|
|
108
|
+
# focused on machine-verifiable cross-references, we only check
|
|
109
|
+
# §X.Y when it appears INSIDE a markdown link target — i.e. the
|
|
110
|
+
# author has structurally committed to an in-track reference.
|
|
111
|
+
# Example that IS validated:
|
|
112
|
+
# [see §3.1](./hld.md#detailed-design) ← anchor check below
|
|
113
|
+
# Example that is NOT validated (prose only):
|
|
114
|
+
# The §20.2 layout matches architecture.md.
|
|
115
|
+
# If a track wants stricter intra-track §-checks, it can use
|
|
116
|
+
# markdown links pointing to numbered headers explicitly.
|
|
117
|
+
: # §-ref prose validation disabled — see comment above.
|
|
118
|
+
|
|
119
|
+
# 2. Markdown anchor references like ./hld.md#section-name
|
|
120
|
+
if echo "$line" | grep -qE '\([^)]*\.md#[A-Za-z0-9_-]+\)'; then
|
|
121
|
+
local m
|
|
122
|
+
while IFS= read -r m; do
|
|
123
|
+
m="${m#(}"; m="${m%)}"
|
|
124
|
+
local path="${m%%#*}"
|
|
125
|
+
local anchor="${m##*#}"
|
|
126
|
+
local target
|
|
127
|
+
if [[ "$path" == /* ]]; then
|
|
128
|
+
target="$path"
|
|
129
|
+
else
|
|
130
|
+
target="$track_dir/${path#./}"
|
|
131
|
+
fi
|
|
132
|
+
if [[ ! -f "$target" ]]; then
|
|
133
|
+
record "$rel_track" "missing-doc" "$rel_md" "$lineno" \
|
|
134
|
+
"anchor target file not found: $path"
|
|
135
|
+
continue
|
|
136
|
+
fi
|
|
137
|
+
if ! md_slugs "$target" | grep -Fxq "$anchor"; then
|
|
138
|
+
record "$rel_track" "missing-anchor" "$rel_md" "$lineno" \
|
|
139
|
+
"$path has no slug '$anchor'"
|
|
140
|
+
fi
|
|
141
|
+
done < <(echo "$line" | grep -oE '\([^)]*\.md#[A-Za-z0-9_-]+\)')
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# 3. (planned) / [New file ...] annotations: the path mentioned on the
|
|
145
|
+
# same line must NOT yet exist relative to the track.
|
|
146
|
+
#
|
|
147
|
+
# Only fire when the line has EXACTLY one path-looking token. Lines
|
|
148
|
+
# with multiple paths (e.g. "Flag X (planned) — wired per `spec.md`")
|
|
149
|
+
# make attribution ambiguous; the validator should not guess which
|
|
150
|
+
# token the `(planned)` annotation owns.
|
|
151
|
+
if echo "$line" | grep -qE '\(planned\)|\[New file'; then
|
|
152
|
+
local paths
|
|
153
|
+
paths="$(echo "$line" | grep -oE '[A-Za-z][A-Za-z0-9_./-]*\.[A-Za-z]+' | sort -u)"
|
|
154
|
+
local path_count
|
|
155
|
+
path_count="$(printf '%s\n' "$paths" | grep -c . || true)"
|
|
156
|
+
if (( path_count == 1 )); then
|
|
157
|
+
local p="$paths"
|
|
158
|
+
local rel="${p#./}"
|
|
159
|
+
if [[ -e "$track_dir/$rel" ]]; then
|
|
160
|
+
record "$rel_track" "planned-file-exists" "$rel_md" "$lineno" \
|
|
161
|
+
"$p annotated planned but exists at $track_dir/$rel"
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
done < "$md"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for t in "${TRACK_PATHS[@]}"; do
|
|
169
|
+
[[ -d "$t" ]] || { record "$t" "not-a-directory" "" "0" ""; continue; }
|
|
170
|
+
track_dir="$(cd "$t" && pwd)"
|
|
171
|
+
while IFS= read -r f; do
|
|
172
|
+
scan_md "$track_dir" "$f"
|
|
173
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
emit() {
|
|
177
|
+
if ((EMIT_JSON)); then
|
|
178
|
+
printf '{"violation_count": %d, "violations": [\n' "$violation_count"
|
|
179
|
+
local first=1 v track kind file line detail
|
|
180
|
+
for v in "${violations[@]}"; do
|
|
181
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
182
|
+
if ((first)); then first=0; else printf ',\n'; fi
|
|
183
|
+
printf ' {"track":"%s","kind":"%s","file":"%s","line":%s,"detail":"%s"}' \
|
|
184
|
+
"$(json_escape "$track")" "$(json_escape "$kind")" \
|
|
185
|
+
"$(json_escape "$file")" "${line:-0}" "$(json_escape "$detail")"
|
|
186
|
+
done
|
|
187
|
+
printf '\n]}\n'
|
|
188
|
+
else
|
|
189
|
+
if ((violation_count == 0)); then
|
|
190
|
+
printf 'OK: anchors clean across %d track(s).\n' "${#TRACK_PATHS[@]}"
|
|
191
|
+
else
|
|
192
|
+
printf 'ANCHORS: %d violation(s) across %d track(s).\n' \
|
|
193
|
+
"$violation_count" "${#TRACK_PATHS[@]}" >&2
|
|
194
|
+
local v track kind file line detail
|
|
195
|
+
for v in "${violations[@]}"; do
|
|
196
|
+
IFS='|' read -r track kind file line detail <<< "$v"
|
|
197
|
+
printf ' [%s] %s/%s:%s — %s\n' "$kind" "$track" "$file" "$line" "$detail" >&2
|
|
198
|
+
done
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
}
|
|
202
|
+
emit
|
|
203
|
+
|
|
204
|
+
((violation_count == 0))
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-graph-binary.sh — validate and select the Draft knowledge-graph engine.
|
|
3
|
+
#
|
|
4
|
+
# The engine is the codebase-memory-mcp binary. Resolution order (see _lib.sh:find_memory_bin):
|
|
5
|
+
# 1. DRAFT_MEMORY_BIN override
|
|
6
|
+
# 2. codebase-memory-mcp on $PATH
|
|
7
|
+
# 3. Draft-managed install (~/.cache/draft/bin/)
|
|
8
|
+
# 4. Vendored arch-specific under bin/<arch>/
|
|
9
|
+
#
|
|
10
|
+
# Emits JSON or a human report. Exit 0 = found+usable, 2 = none (graceful for skills).
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# scripts/tools/verify-graph-binary.sh [--repo <dir>] [--plugin-root <dir>] [--json] [--verbose] [--strict]
|
|
14
|
+
#
|
|
15
|
+
# --strict : fail (exit 2) when no engine is found (release/CI gates)
|
|
16
|
+
# Integrates with install/package and the skills/init graph step.
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# shellcheck source=_lib.sh
|
|
22
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
23
|
+
|
|
24
|
+
REPO="."
|
|
25
|
+
PLUGIN_ROOT=""
|
|
26
|
+
EMIT_JSON=0
|
|
27
|
+
VERBOSE=0
|
|
28
|
+
STRICT=0
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat <<'EOF'
|
|
32
|
+
verify-graph-binary.sh — Draft knowledge-graph engine resolver + verifier
|
|
33
|
+
|
|
34
|
+
Engine: codebase-memory-mcp
|
|
35
|
+
Resolution: DRAFT_MEMORY_BIN > PATH > ~/.cache/draft/bin > bin/<arch>/
|
|
36
|
+
|
|
37
|
+
Options:
|
|
38
|
+
--repo DIR Repo root for context (default .)
|
|
39
|
+
--plugin-root DIR Explicit Draft plugin install root
|
|
40
|
+
--json Emit JSON report
|
|
41
|
+
--verbose Human progress
|
|
42
|
+
--strict Exit 2 if no engine found
|
|
43
|
+
--help This message
|
|
44
|
+
|
|
45
|
+
Exit codes:
|
|
46
|
+
0 Usable engine found and responsive to --version
|
|
47
|
+
1 Bad args
|
|
48
|
+
2 No engine located
|
|
49
|
+
EOF
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
while [[ $# -gt 0 ]]; do
|
|
53
|
+
case "$1" in
|
|
54
|
+
--repo) REPO="$2"; shift 2 ;;
|
|
55
|
+
--plugin-root) PLUGIN_ROOT="$2"; shift 2 ;;
|
|
56
|
+
--json) EMIT_JSON=1; shift ;;
|
|
57
|
+
--verbose) VERBOSE=1; shift ;;
|
|
58
|
+
--strict) STRICT=1; shift ;;
|
|
59
|
+
--help|-h) usage; exit 0 ;;
|
|
60
|
+
*) echo "Unknown: $1" >&2; usage; exit 1 ;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
log() { [[ $VERBOSE -eq 1 ]] && echo "[verify-graph] $*" >&2 || true; }
|
|
65
|
+
|
|
66
|
+
# Resolve architecture string for the report (linux-amd64, darwin-arm64, ...).
|
|
67
|
+
resolve_arch() {
|
|
68
|
+
local os arch
|
|
69
|
+
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
70
|
+
arch=$(uname -m)
|
|
71
|
+
case "$arch" in
|
|
72
|
+
x86_64|amd64) arch="amd64" ;;
|
|
73
|
+
aarch64|arm64) arch="arm64" ;;
|
|
74
|
+
armv7l) arch="arm" ;;
|
|
75
|
+
esac
|
|
76
|
+
echo "${os}-${arch}"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ARCH="$(resolve_arch)"
|
|
80
|
+
log "Resolved arch: $ARCH"
|
|
81
|
+
|
|
82
|
+
REPO_ABS="$(cd "$REPO" 2>/dev/null && pwd || echo "$REPO")"
|
|
83
|
+
SELF_REPO="${PLUGIN_ROOT:-$SCRIPT_DIR/../..}"
|
|
84
|
+
|
|
85
|
+
# Classify the resolution source for reporting.
|
|
86
|
+
classify_source() {
|
|
87
|
+
case "$MEMORY_BIN" in
|
|
88
|
+
"$HOME/.cache/draft/bin/"*) echo "managed" ;;
|
|
89
|
+
*/bin/"$ARCH"/*) echo "bundled:$ARCH" ;;
|
|
90
|
+
codebase-memory-mcp) echo "path" ;;
|
|
91
|
+
"${DRAFT_MEMORY_BIN:-__none__}") echo "override" ;;
|
|
92
|
+
*) echo "path" ;;
|
|
93
|
+
esac
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
MEMORY_BIN=""
|
|
97
|
+
if ! find_memory_bin "$REPO_ABS" "$SELF_REPO"; then
|
|
98
|
+
if [[ $EMIT_JSON -eq 1 ]]; then
|
|
99
|
+
local_msg="No codebase-memory-mcp engine found in DRAFT_MEMORY_BIN, PATH, ~/.cache/draft/bin, or bin/<arch>/"
|
|
100
|
+
if [[ $STRICT -eq 1 ]]; then
|
|
101
|
+
printf '{"status":"none","engine_bin":null,"source":null,"arch":"%s","message":"strict mode: %s"}\n' "$ARCH" "$local_msg"
|
|
102
|
+
else
|
|
103
|
+
printf '{"status":"unavailable","engine_bin":null,"source":null,"arch":"%s","message":"%s"}\n' "$ARCH" "$local_msg"
|
|
104
|
+
fi
|
|
105
|
+
elif [[ $STRICT -eq 1 ]]; then
|
|
106
|
+
echo "STRICT: No codebase-memory-mcp engine found." >&2
|
|
107
|
+
else
|
|
108
|
+
echo "ERROR: No Draft graph engine located (codebase-memory-mcp)." >&2
|
|
109
|
+
echo " Install it (scripts/fetch-memory-engine.sh) or put it on PATH." >&2
|
|
110
|
+
fi
|
|
111
|
+
exit 2
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
SOURCE="$(classify_source)"
|
|
115
|
+
log "Selected engine: $MEMORY_BIN (source=$SOURCE)"
|
|
116
|
+
|
|
117
|
+
# Liveness: must respond to --version.
|
|
118
|
+
if ! "$MEMORY_BIN" --version >/dev/null 2>&1; then
|
|
119
|
+
log "Selected engine failed --version"
|
|
120
|
+
echo "ERROR: engine $MEMORY_BIN found but failed --version (wrong OS/arch or corrupt?)." >&2
|
|
121
|
+
if [[ $EMIT_JSON -eq 1 ]]; then
|
|
122
|
+
printf '{"status":"unusable","engine_bin":"%s","source":"%s","arch":"%s"}\n' \
|
|
123
|
+
"$(json_escape "$MEMORY_BIN")" "$(json_escape "$SOURCE")" "$ARCH"
|
|
124
|
+
fi
|
|
125
|
+
exit 2
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
STATUS="ok"
|
|
129
|
+
|
|
130
|
+
if [[ $EMIT_JSON -eq 1 ]]; then
|
|
131
|
+
printf '{"status":"%s","engine_bin":"%s","source":"%s","arch":"%s"}\n' \
|
|
132
|
+
"$(json_escape "$STATUS")" "$(json_escape "$MEMORY_BIN")" "$(json_escape "$SOURCE")" "$(json_escape "$ARCH")"
|
|
133
|
+
else
|
|
134
|
+
echo "Draft graph engine: $MEMORY_BIN"
|
|
135
|
+
echo " source: $SOURCE (arch=$ARCH)"
|
|
136
|
+
echo " status: $STATUS"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Usage report side-effect when in a draft/ context (graph-usage-report contract).
|
|
140
|
+
if [[ -d "$REPO/draft" ]]; then
|
|
141
|
+
mkdir -p "$REPO/draft"
|
|
142
|
+
cat > "$REPO/draft/.graph-binary-report.json" <<EOF
|
|
143
|
+
{
|
|
144
|
+
"detected_at": "$(date -Iseconds 2>/dev/null || date)",
|
|
145
|
+
"engine_bin": "$(json_escape "$MEMORY_BIN")",
|
|
146
|
+
"source": "$(json_escape "$SOURCE")",
|
|
147
|
+
"arch": "$(json_escape "$ARCH")",
|
|
148
|
+
"status": "$(json_escape "$STATUS")"
|
|
149
|
+
}
|
|
150
|
+
EOF
|
|
151
|
+
log "Wrote draft/.graph-binary-report.json (usage report contract)"
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
exit 0
|