@drafthq/draft 3.5.3 → 3.7.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.
Files changed (143) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/README.md +28 -49
  5. package/bin/README.md +47 -1
  6. package/cli/src/hosts/cursor.js +3 -0
  7. package/cli/src/installer.js +8 -4
  8. package/cli/src/lib/cursor-registry.js +10 -0
  9. package/cli/src/lib/fsx.js +3 -0
  10. package/cli/src/lib/marker.js +8 -3
  11. package/core/agents/architect.md +21 -9
  12. package/core/agents/debugger.md +5 -0
  13. package/core/agents/ops.md +7 -2
  14. package/core/agents/planner.md +7 -0
  15. package/core/agents/rca.md +14 -3
  16. package/core/agents/reviewer.md +1 -0
  17. package/core/agents/writer.md +6 -0
  18. package/core/knowledge-base.md +22 -3
  19. package/core/methodology.md +30 -5
  20. package/core/shared/condensation.md +28 -18
  21. package/core/shared/context-verify.md +3 -1
  22. package/core/shared/cross-skill-dispatch.md +7 -3
  23. package/core/shared/draft-context-loading.md +3 -0
  24. package/core/shared/git-report-metadata.md +2 -1
  25. package/core/shared/graph-query.md +24 -26
  26. package/core/shared/graph-usage-report.md +1 -1
  27. package/core/shared/jira-sync.md +10 -6
  28. package/core/shared/okf-retrieval.md +1 -1
  29. package/core/shared/parallel-analysis.md +14 -7
  30. package/core/shared/pattern-learning.md +2 -0
  31. package/core/shared/template-contract.md +1 -1
  32. package/core/shared/tool-resolver.md +20 -15
  33. package/core/shared/vcs-commands.md +5 -2
  34. package/core/templates/ai-context.md +3 -2
  35. package/core/templates/ai-profile.md +7 -0
  36. package/core/templates/architecture.md +6 -2
  37. package/core/templates/discovery.md +1 -1
  38. package/core/templates/guardrails.md +9 -0
  39. package/core/templates/hld.md +5 -4
  40. package/core/templates/intake-questions.md +45 -0
  41. package/core/templates/jira.md +11 -2
  42. package/core/templates/lld.md +8 -5
  43. package/core/templates/okf/concept.md +0 -1
  44. package/core/templates/plan.md +1 -1
  45. package/core/templates/product.md +11 -2
  46. package/core/templates/rca.md +6 -0
  47. package/core/templates/service-index.md +1 -0
  48. package/core/templates/spec.md +15 -0
  49. package/core/templates/tech-stack.md +12 -0
  50. package/core/templates/workflow.md +20 -0
  51. package/integrations/agents/AGENTS.md +1449 -817
  52. package/integrations/copilot/.github/copilot-instructions.md +1449 -817
  53. package/package.json +1 -1
  54. package/scripts/fetch-memory-engine.sh +25 -7
  55. package/scripts/lib.sh +90 -6
  56. package/scripts/tools/_graph_queries.sh +22 -8
  57. package/scripts/tools/_lib.sh +81 -11
  58. package/scripts/tools/adr-index.sh +1 -1
  59. package/scripts/tools/check-repo-size.sh +125 -0
  60. package/scripts/tools/check-skill-line-caps.sh +3 -4
  61. package/scripts/tools/check-template-noop.sh +0 -1
  62. package/scripts/tools/check-track-hygiene.sh +41 -21
  63. package/scripts/tools/classify-files.sh +3 -3
  64. package/scripts/tools/cycle-detect.sh +18 -17
  65. package/scripts/tools/detect-test-framework.sh +1 -1
  66. package/scripts/tools/emit-skill-metrics.sh +7 -2
  67. package/scripts/tools/fix-whitespace.sh +3 -0
  68. package/scripts/tools/freshness-check.sh +2 -2
  69. package/scripts/tools/git-metadata.sh +12 -6
  70. package/scripts/tools/graph-arch.sh +5 -11
  71. package/scripts/tools/graph-callers.sh +10 -15
  72. package/scripts/tools/graph-deps.sh +3 -10
  73. package/scripts/tools/graph-errors.sh +4 -11
  74. package/scripts/tools/graph-hierarchy.sh +4 -11
  75. package/scripts/tools/graph-impact.sh +21 -19
  76. package/scripts/tools/graph-init.sh +13 -9
  77. package/scripts/tools/graph-preflight.sh +4 -1
  78. package/scripts/tools/graph-query.sh +74 -18
  79. package/scripts/tools/graph-risk.sh +3 -10
  80. package/scripts/tools/graph-search.sh +4 -11
  81. package/scripts/tools/graph-snapshot.sh +38 -9
  82. package/scripts/tools/graph-snippet.sh +3 -10
  83. package/scripts/tools/graph-tests.sh +3 -10
  84. package/scripts/tools/graph-traces.sh +3 -10
  85. package/scripts/tools/hotspot-rank.sh +6 -12
  86. package/scripts/tools/install-smoke-test.sh +223 -0
  87. package/scripts/tools/mermaid-from-graph.sh +5 -11
  88. package/scripts/tools/migrate-track-frontmatter.sh +11 -10
  89. package/scripts/tools/okf-coverage-check.sh +8 -7
  90. package/scripts/tools/okf-emit-catalog.sh +256 -0
  91. package/scripts/tools/okf-fix-links.sh +251 -0
  92. package/scripts/tools/okf-plan-concepts.sh +242 -14
  93. package/scripts/tools/okf-render-views.sh +63 -21
  94. package/scripts/tools/okf-validate-all.sh +20 -9
  95. package/scripts/tools/okf-validate-quality.sh +15 -10
  96. package/scripts/tools/okf-validate.sh +20 -6
  97. package/scripts/tools/parse-git-log.sh +23 -5
  98. package/scripts/tools/parse-reports.sh +1 -1
  99. package/scripts/tools/render-track.sh +1 -1
  100. package/scripts/tools/resolve-tools.sh +24 -8
  101. package/scripts/tools/run-coverage.sh +1 -1
  102. package/scripts/tools/scan-markers.sh +4 -4
  103. package/scripts/tools/validate-frontmatter.sh +2 -2
  104. package/scripts/tools/verify-citations.sh +3 -4
  105. package/scripts/tools/verify-doc-anchors.sh +14 -34
  106. package/scripts/tools/verify-graph-binary.sh +15 -5
  107. package/skills/GRAPH.md +21 -10
  108. package/skills/adr/SKILL.md +25 -7
  109. package/skills/assist-review/SKILL.md +2 -1
  110. package/skills/bughunt/SKILL.md +55 -12
  111. package/skills/bughunt/references/regression-tests.md +14 -4
  112. package/skills/change/SKILL.md +19 -9
  113. package/skills/coverage/SKILL.md +28 -12
  114. package/skills/debug/SKILL.md +13 -3
  115. package/skills/decompose/SKILL.md +42 -12
  116. package/skills/deep-review/SKILL.md +19 -6
  117. package/skills/deploy-checklist/SKILL.md +10 -3
  118. package/skills/documentation/SKILL.md +12 -1
  119. package/skills/draft/SKILL.md +40 -29
  120. package/skills/draft/context-files.md +8 -1
  121. package/skills/draft/quality-guide.md +10 -2
  122. package/skills/graph/SKILL.md +2 -2
  123. package/skills/impact/SKILL.md +4 -2
  124. package/skills/implement/SKILL.md +62 -20
  125. package/skills/incident-response/SKILL.md +5 -2
  126. package/skills/init/SKILL.md +112 -30
  127. package/skills/init/references/architecture-spec.md +32 -8
  128. package/skills/init/references/okf-emitter.md +42 -78
  129. package/skills/jira/SKILL.md +33 -13
  130. package/skills/jira/references/review.md +61 -19
  131. package/skills/learn/SKILL.md +30 -7
  132. package/skills/new-track/SKILL.md +73 -18
  133. package/skills/quick-review/SKILL.md +8 -3
  134. package/skills/revert/SKILL.md +11 -5
  135. package/skills/review/SKILL.md +126 -27
  136. package/skills/review/references/zero-setup-mode.md +117 -0
  137. package/skills/standup/SKILL.md +10 -2
  138. package/skills/status/SKILL.md +7 -7
  139. package/skills/tech-debt/SKILL.md +9 -3
  140. package/skills/testing-strategy/SKILL.md +10 -2
  141. package/skills/tour/SKILL.md +2 -1
  142. package/skills/upload/SKILL.md +4 -3
  143. package/core/templates/track-architecture.md +0 -311
@@ -66,7 +66,7 @@ EOF
66
66
 
67
67
  while [[ $# -gt 0 ]]; do
68
68
  case "$1" in
69
- --path-index) PATH_INDEX="$2"; shift 2;;
69
+ --path-index) PATH_INDEX="${2:?--path-index requires a value}"; shift 2;;
70
70
  --reverse) REVERSE=1; shift;;
71
71
  --structure-only) STRUCTURE_ONLY=1; shift;;
72
72
  --json) JSON=1; shift;;
@@ -176,14 +176,25 @@ while IFS= read -r -d '' page; do
176
176
  fi
177
177
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
178
178
 
179
+ # Page text the {ALL_CAPS} template-token scan is allowed to see. Fenced code
180
+ # blocks and shell-style ${VAR} expansions are removed first: both legitimately
181
+ # contain {ALL_CAPS} spans that are NOT unreplaced placeholders, and a wiki page
182
+ # quoting `${HOME}` or a CI snippet would otherwise fail the whole bundle.
183
+ template_scan_text() {
184
+ awk '
185
+ /^[[:space:]]*```/ { fence = !fence; next }
186
+ !fence { gsub(/\$\{[A-Za-z_][A-Za-z0-9_]*\}/, ""); print }
187
+ ' "$1"
188
+ }
189
+
179
190
  # --- 3b. Unreplaced template tokens (any page, including index pages) ---
180
191
  # Quality checks skip index.md pages, so a leftover {SECTION_TITLE}/{PROJECT_NAME}
181
192
  # placeholder in a hand-seeded index would otherwise survive. Match {ALL_CAPS}
182
193
  # tokens (safe: real prose almost never contains them).
183
194
  while IFS= read -r -d '' page; do
184
195
  prel="${page#"$BUNDLE/"}"
185
- if grep -qE '\{[A-Z][A-Z0-9_]+\}' "$page" 2>/dev/null; then
186
- tok="$(grep -oE '\{[A-Z][A-Z0-9_]+\}' "$page" | head -1)"
196
+ tok="$(template_scan_text "$page" 2>/dev/null | grep -oE '\{[A-Z][A-Z0-9_]+\}' | head -1 || true)"
197
+ if [[ -n "$tok" ]]; then
187
198
  add_error "$prel: unreplaced template token '$tok'"
188
199
  fi
189
200
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
@@ -237,12 +248,14 @@ if [[ -n "$PATH_INDEX" ]]; then
237
248
  # themselves end in .md (e.g. grounding to docs/INVARIANTS.md) — those are
238
249
  # not bundle pages, so we extract strings *inside* the [ ... ] value arrays
239
250
  # and ignore keys entirely. Each page must exist in the bundle.
251
+ # Flatten first: the index may be pretty-printed, so value arrays can
252
+ # span lines and a line-oriented match would silently skip them.
240
253
  while IFS= read -r ref; do
241
254
  [[ -z "$ref" ]] && continue
242
255
  if [[ ! -f "$BUNDLE/$ref" ]]; then
243
256
  add_error "path-index references missing concept page: $ref"
244
257
  fi
245
- done < <(grep -oE '\[[^]]*\]' "$PATH_INDEX" 2>/dev/null \
258
+ done < <(tr '\n' ' ' < "$PATH_INDEX" 2>/dev/null | grep -oE '\[[^]]*\]' \
246
259
  | grep -oE '"[^"]+\.md"' | tr -d '"' | sort -u)
247
260
  fi
248
261
  fi
@@ -251,9 +264,10 @@ fi
251
264
  # Forward proves the index doesn't name ghosts; reverse proves no page is an
252
265
  # orphan with no source mapping (a symptom of hand-written / off-plan pages).
253
266
  if [[ $REVERSE -eq 1 && $STRUCTURE_ONLY -eq 0 && -n "$PATH_INDEX" && -f "$PATH_INDEX" ]]; then
254
- # All pages the index maps to (its array values).
267
+ # All pages the index maps to (its array values). Flattened for the same
268
+ # pretty-printed-array reason as the forward check above.
255
269
  INDEXED_FILE="$(mktemp)"
256
- grep -oE '\[[^]]*\]' "$PATH_INDEX" 2>/dev/null \
270
+ tr '\n' ' ' < "$PATH_INDEX" 2>/dev/null | grep -oE '\[[^]]*\]' \
257
271
  | grep -oE '"[^"]+\.md"' | tr -d '"' | sort -u > "$INDEXED_FILE" || true
258
272
  while IFS= read -r -d '' page; do
259
273
  rel="${page#"$BUNDLE/"}"
@@ -49,10 +49,10 @@ EOF
49
49
 
50
50
  while [[ $# -gt 0 ]]; do
51
51
  case "$1" in
52
- --since) SINCE="$2"; shift 2;;
53
- --limit) LIMIT="$2"; shift 2;;
54
- --scope-pattern) SCOPE_PATTERN="$2"; shift 2;;
55
- --branch) BRANCH="$2"; shift 2;;
52
+ --since) SINCE="${2:?--since requires a value}"; shift 2;;
53
+ --limit) LIMIT="${2:?--limit requires a value}"; shift 2;;
54
+ --scope-pattern) SCOPE_PATTERN="${2:?--scope-pattern requires a value}"; shift 2;;
55
+ --branch) BRANCH="${2:?--branch requires a value}"; shift 2;;
56
56
  --help|-h) usage; exit 0;;
57
57
  *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
58
58
  esac
@@ -63,6 +63,13 @@ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
63
63
  exit 1
64
64
  fi
65
65
 
66
+ # `git log -n <non-numeric>` parses the count as 0 and exits 0, so a typo'd
67
+ # --limit silently yields an empty stream. Reject it here instead.
68
+ if [[ -n "$LIMIT" && ! "$LIMIT" =~ ^[0-9]+$ ]]; then
69
+ echo "ERROR: --limit must be a non-negative integer (got '$LIMIT')" >&2
70
+ exit 1
71
+ fi
72
+
66
73
  # Format: delimiter-separated metadata line, then --name-only file list, blank line separator.
67
74
  GIT_ARGS=(log --pretty=tformat:'COMMIT%x1f%H%x1f%an%x1f%aI%x1f%s' --name-only --no-merges)
68
75
  [[ -n "$SINCE" ]] && GIT_ARGS+=(--since="$SINCE")
@@ -118,6 +125,17 @@ process_commit() {
118
125
 
119
126
  # Single git log stream: each commit is `COMMIT<US>sha<US>author<US>ts<US>subject`
120
127
  # followed by its file paths (one per line) and a blank separator.
128
+ #
129
+ # Staged through a temp file rather than `< <(git ...)`: a process substitution
130
+ # swallows git's exit status, so a bad --branch/--since/--limit produced an empty
131
+ # JSONL stream and exit 0 — indistinguishable from "this range has no commits".
132
+ GIT_OUT="$(mktemp)"
133
+ trap 'rm -f "$GIT_OUT"' EXIT
134
+ if ! git "${GIT_ARGS[@]}" >"$GIT_OUT"; then
135
+ echo "ERROR: git log failed (check --branch / --since / --limit)" >&2
136
+ exit 1
137
+ fi
138
+
121
139
  cur_sha=""; cur_author=""; cur_ts=""; cur_subject=""; cur_files=0
122
140
  while IFS= read -r line; do
123
141
  if [[ "$line" == COMMIT$'\x1f'* ]]; then
@@ -129,7 +147,7 @@ while IFS= read -r line; do
129
147
  elif [[ -n "$line" ]]; then
130
148
  cur_files=$((cur_files + 1))
131
149
  fi
132
- done < <(git "${GIT_ARGS[@]}")
150
+ done < "$GIT_OUT"
133
151
  if [[ -n "$cur_sha" ]]; then
134
152
  process_commit "$cur_sha" "$cur_author" "$cur_ts" "$cur_subject" "$cur_files"
135
153
  fi
@@ -35,7 +35,7 @@ EOF
35
35
 
36
36
  while [[ $# -gt 0 ]]; do
37
37
  case "$1" in
38
- --root) ROOT="$2"; shift 2;;
38
+ --root) ROOT="${2:?--root requires a value}"; shift 2;;
39
39
  --help|-h) usage; exit 0;;
40
40
  *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
41
41
  esac
@@ -48,7 +48,7 @@ TO_STDOUT=0
48
48
  while (($#)); do
49
49
  case "$1" in
50
50
  -h|--help) USAGE_HELP_MODE=1 usage ;;
51
- --out) OUT_PATH="$2"; shift 2 ;;
51
+ --out) OUT_PATH="${2:?--out requires a value}"; shift 2 ;;
52
52
  --stdout) TO_STDOUT=1; shift ;;
53
53
  -*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
54
54
  *) TRACK_DIR="$1"; shift ;;
@@ -7,6 +7,14 @@
7
7
  # See core/shared/tool-resolver.md for the canonical procedure and the inline preamble
8
8
  # skills embed (this script is the single source of truth for the resolution order).
9
9
  #
10
+ # Resolution order is deliberately "authoritative install first, cwd last":
11
+ # skills execute whatever this prints, and the cwd is the repository under review,
12
+ # which Draft routinely runs against untrusted code. A project that ships its own
13
+ # scripts/tools/ must never shadow the installed plugin.
14
+ #
15
+ # Working on Draft itself? Export DRAFT_PLUGIN_ROOT="$PWD" — the explicit override
16
+ # is step 1 and always wins.
17
+ #
10
18
  # Usage:
11
19
  # DRAFT_TOOLS="$(scripts/tools/resolve-tools.sh)" # prints the dir, exit 0 if found
12
20
  # scripts/tools/resolve-tools.sh || echo "tools not found" # exit 1 if none exist
@@ -21,8 +29,18 @@ esac
21
29
 
22
30
  newest() {
23
31
  # Echo the lexically-newest existing match of a glob (by version sort), or nothing.
32
+ # `|| true` is load-bearing: on a glob miss `ls` exits non-zero, pipefail
33
+ # propagates it, and errexit would abort the whole resolver instead of letting
34
+ # the remaining fallbacks run.
24
35
  # shellcheck disable=SC2086
25
- ls -d $1 2>/dev/null | sort -V | tail -1
36
+ ls -d $1 2>/dev/null | sort -V | tail -1 || true
37
+ }
38
+
39
+ # Is DIR Draft's own source checkout? Checked before trusting the cwd at all.
40
+ is_draft_checkout() {
41
+ [ -f "$1/scripts/tools/resolve-tools.sh" ] || return 1
42
+ [ -f "$1/.claude-plugin/plugin.json" ] || return 1
43
+ grep -q '"name"[[:space:]]*:[[:space:]]*"draft"' "$1/.claude-plugin/plugin.json" 2>/dev/null
26
44
  }
27
45
 
28
46
  resolve() {
@@ -32,10 +50,6 @@ resolve() {
32
50
  d="${DRAFT_PLUGIN_ROOT:-}/scripts/tools"
33
51
  [ -n "${DRAFT_PLUGIN_ROOT:-}" ] && [ -d "$d" ] && { printf '%s' "$d"; return 0; }
34
52
 
35
- # 1b. Dev / dogfooding: cwd IS the draft repo. Guarded by this script's own
36
- # presence so it can never misfire in a user project (which has no resolve-tools.sh).
37
- [ -f "$PWD/scripts/tools/resolve-tools.sh" ] && { printf '%s' "$PWD/scripts/tools"; return 0; }
38
-
39
53
  # 2. Install marker written by `draft install` (authoritative).
40
54
  local marker="$HOME/.cache/draft/plugin-root"
41
55
  if [ -f "$marker" ]; then
@@ -68,9 +82,11 @@ resolve() {
68
82
  d="$HOME/.cursor/plugins/local/draft/scripts/tools"
69
83
  [ -d "$d" ] && { printf '%s' "$d"; return 0; }
70
84
 
71
- # 8. Dev / dogfooding (running inside the draft repo itself).
72
- d="$PWD/scripts/tools"
73
- [ -d "$d" ] && { printf '%s' "$d"; return 0; }
85
+ # 8. Last resort — dev / dogfooding inside Draft's own checkout. Deliberately
86
+ # after every install location, and gated on the plugin manifest rather than on
87
+ # a same-named file: a bare `$PWD/scripts/tools` test matched any project using
88
+ # that very common layout and handed it script execution.
89
+ is_draft_checkout "$PWD" && { printf '%s' "$PWD/scripts/tools"; return 0; }
74
90
 
75
91
  return 1
76
92
  }
@@ -51,7 +51,7 @@ EOF
51
51
 
52
52
  while [[ $# -gt 0 ]]; do
53
53
  case "$1" in
54
- --path) COVERAGE_PATH="$2"; shift 2;;
54
+ --path) COVERAGE_PATH="${2:?--path requires a value}"; shift 2;;
55
55
  --schema-check) SCHEMA_CHECK="true"; shift;;
56
56
  --help|-h) usage; exit 0;;
57
57
  -*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # Usage:
8
8
  # scripts/tools/scan-markers.sh [--root DIR] [--markers LIST]
9
- # [--min-age-days N] [--include-untracked]
9
+ # [--min-age-days N]
10
10
  #
11
11
  # Exit codes: 0 OK (even with zero hits), 1 invocation error, 2 not a git repo
12
12
  # (emits [] on stdout so consumers can still parse).
@@ -38,9 +38,9 @@ EOF
38
38
 
39
39
  while [[ $# -gt 0 ]]; do
40
40
  case "$1" in
41
- --root) ROOT="$2"; shift 2;;
42
- --markers) MARKERS="$2"; shift 2;;
43
- --min-age-days) MIN_AGE="$2"; shift 2;;
41
+ --root) ROOT="${2:?--root requires a value}"; shift 2;;
42
+ --markers) MARKERS="${2:?--markers requires a value}"; shift 2;;
43
+ --min-age-days) MIN_AGE="${2:?--min-age-days requires a value}"; shift 2;;
44
44
  --help|-h) usage; exit 0;;
45
45
  *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
46
46
  esac
@@ -43,8 +43,8 @@ REQUIRED="name,description"
43
43
 
44
44
  while [[ $# -gt 0 ]]; do
45
45
  case "$1" in
46
- --require) REQUIRED="$2"; shift 2;;
47
- --mode) MODE="$2"; shift 2;;
46
+ --require) REQUIRED="${2:?--require requires a value}"; shift 2;;
47
+ --mode) MODE="${2:?--mode requires a value}"; shift 2;;
48
48
  --help|-h) usage; exit 0;;
49
49
  -*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
50
50
  *)
@@ -48,7 +48,7 @@ while (($#)); do
48
48
  case "$1" in
49
49
  -h|--help) USAGE_HELP_MODE=1 usage ;;
50
50
  --json) EMIT_JSON=1; shift ;;
51
- --tolerance) TOLERANCE="$2"; shift 2 ;;
51
+ --tolerance) TOLERANCE="${2:?--tolerance requires a value}"; shift 2 ;;
52
52
  -*) printf 'Unknown flag: %s\n' "$1" >&2; usage ;;
53
53
  *) TRACK_PATHS+=("$1"); shift ;;
54
54
  esac
@@ -83,7 +83,7 @@ extract_citations() {
83
83
  # bracket expression prematurely terminates a regex literal in
84
84
  # POSIX/BSD awk ("nonterminated character class"), though gawk is
85
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]+")) {
86
+ while (match(s, "[A-Za-z0-9_][A-Za-z0-9_./-]*\\.[A-Za-z0-9]+:[0-9]+(-[0-9]+)?")) {
87
87
  cite = substr(s, RSTART, RLENGTH)
88
88
  printf("%s\t%d\t%s\n", FILENAME, NR, cite)
89
89
  s = substr(s, RSTART + RLENGTH)
@@ -189,8 +189,7 @@ scan_one_track() {
189
189
 
190
190
  while IFS= read -r f; do
191
191
  local rel_md="${f#"$track_dir/"}"
192
- while IFS=$'\t' read -r md_file lineno cite; do
193
- local md_line="$md_file"
192
+ while IFS=$'\t' read -r _md_file lineno cite; do
194
193
  local result rc=0
195
194
  # set +e to capture rc; set -e would exit on a non-zero return.
196
195
  set +e
@@ -3,12 +3,16 @@
3
3
 
4
4
  #
5
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
6
  # - <doc>.md#<anchor> markdown anchors → resolve <anchor> against the
9
7
  # target file's header slugs.
10
8
  # - (planned) / [New file ...] annotations → file MUST NOT exist locally.
11
9
  #
10
+ # Plain-prose `§X.Y` references are deliberately NOT validated: authors use them
11
+ # as shorthand for an external doc (`architecture.md §20.2`) without naming the
12
+ # file on the same line, so attribution is guesswork. A track that wants a
13
+ # machine-checkable §-reference writes it as a markdown link to the heading,
14
+ # which the anchor check above already covers.
15
+ #
12
16
  # Skips content inside <!-- VERIFIER:IGNORE START --> ... END --> blocks.
13
17
  #
14
18
  # Usage:
@@ -35,7 +39,7 @@ TRACK_PATHS=()
35
39
  usage() {
36
40
  local stream=2 code=2
37
41
  if [[ "${USAGE_HELP_MODE:-0}" == 1 ]]; then stream=1; code=0; fi
38
- sed -n '2,20p' "$0" >&$stream
42
+ sed -n '2,26p' "$0" >&$stream
39
43
  exit "$code"
40
44
  }
41
45
 
@@ -72,24 +76,15 @@ md_slugs() {
72
76
  s = tolower(s)
73
77
  # Replace spaces with dashes
74
78
  gsub(/ +/, "-", s)
75
- # Strip punctuation except dashes
76
- gsub(/[^a-z0-9-]/, "", s)
79
+ # Strip punctuation except dashes and underscores. GitHub keeps `_`
80
+ # in a heading slug (and so does _lib.sh:gfm_slug) — stripping it
81
+ # here reported every valid #foo_bar-style anchor as missing.
82
+ gsub(/[^a-z0-9_-]/, "", s)
77
83
  print s
78
84
  }
79
85
  ' "$1" | sort -u
80
86
  }
81
87
 
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
88
  scan_md() {
94
89
  local track_dir="$1" md="$2"
95
90
  local rel_track="${track_dir#"$REPO_ROOT/"}"
@@ -101,22 +96,7 @@ scan_md() {
101
96
  if [[ "$line" == *"<!-- VERIFIER:IGNORE END -->"* ]]; then ignore=0; continue; fi
102
97
  (( ignore )) && continue
103
98
 
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
99
+ # 1. Markdown anchor references like ./hld.md#section-name
120
100
  if echo "$line" | grep -qE '\([^)]*\.md#[A-Za-z0-9_-]+\)'; then
121
101
  local m
122
102
  while IFS= read -r m; do
@@ -141,7 +121,7 @@ scan_md() {
141
121
  done < <(echo "$line" | grep -oE '\([^)]*\.md#[A-Za-z0-9_-]+\)')
142
122
  fi
143
123
 
144
- # 3. (planned) / [New file ...] annotations: the path mentioned on the
124
+ # 2. (planned) / [New file ...] annotations: the path mentioned on the
145
125
  # same line must NOT yet exist relative to the track.
146
126
  #
147
127
  # Only fire when the line has EXACTLY one path-looking token. Lines
@@ -150,7 +130,7 @@ scan_md() {
150
130
  # token the `(planned)` annotation owns.
151
131
  if echo "$line" | grep -qE '\(planned\)|\[New file'; then
152
132
  local paths
153
- paths="$(echo "$line" | grep -oE '[A-Za-z][A-Za-z0-9_./-]*\.[A-Za-z]+' | sort -u)"
133
+ paths="$(echo "$line" | grep -oE '[A-Za-z][A-Za-z0-9_./-]*\.[A-Za-z]+' | sort -u || true)"
154
134
  local path_count
155
135
  path_count="$(printf '%s\n' "$paths" | grep -c . || true)"
156
136
  if (( path_count == 1 )); then
@@ -2,6 +2,7 @@
2
2
  # verify-graph-binary.sh — validate and select the Draft knowledge-graph engine.
3
3
  #
4
4
  # The engine is the codebase-memory-mcp binary. Resolution order (see _lib.sh:find_memory_bin):
5
+ # 0. DRAFT_MEMORY_DISABLE — hard off; resolution stops here and reports "none"
5
6
  # 1. DRAFT_MEMORY_BIN override
6
7
  # 2. codebase-memory-mcp on $PATH
7
8
  # 3. Draft-managed install (~/.cache/draft/bin/)
@@ -32,7 +33,8 @@ usage() {
32
33
  verify-graph-binary.sh — Draft knowledge-graph engine resolver + verifier
33
34
 
34
35
  Engine: codebase-memory-mcp
35
- Resolution: DRAFT_MEMORY_BIN > PATH > ~/.cache/draft/bin > bin/<arch>/
36
+ Resolution: DRAFT_MEMORY_DISABLE (hard off) > DRAFT_MEMORY_BIN > PATH
37
+ > ~/.cache/draft/bin > bin/<arch>/
36
38
 
37
39
  Options:
38
40
  --repo DIR Repo root for context (default .)
@@ -51,8 +53,8 @@ EOF
51
53
 
52
54
  while [[ $# -gt 0 ]]; do
53
55
  case "$1" in
54
- --repo) REPO="$2"; shift 2 ;;
55
- --plugin-root) PLUGIN_ROOT="$2"; shift 2 ;;
56
+ --repo) REPO="${2:?--repo requires a value}"; shift 2 ;;
57
+ --plugin-root) PLUGIN_ROOT="${2:?--plugin-root requires a value}"; shift 2 ;;
56
58
  --json) EMIT_JSON=1; shift ;;
57
59
  --verbose) VERBOSE=1; shift ;;
58
60
  --strict) STRICT=1; shift ;;
@@ -95,15 +97,23 @@ classify_source() {
95
97
 
96
98
  MEMORY_BIN=""
97
99
  if ! find_memory_bin "$REPO_ABS" "$SELF_REPO"; then
98
- if [[ $EMIT_JSON -eq 1 ]]; then
100
+ # DRAFT_MEMORY_DISABLE short-circuits resolution, so say so — "install it or
101
+ # put it on PATH" is the wrong remedy when an env var turned the engine off.
102
+ if [[ -n "${DRAFT_MEMORY_DISABLE:-}" ]]; then
103
+ local_msg="Graph engine disabled by DRAFT_MEMORY_DISABLE (unset it to re-enable resolution)"
104
+ else
99
105
  local_msg="No codebase-memory-mcp engine found in DRAFT_MEMORY_BIN, PATH, ~/.cache/draft/bin, or bin/<arch>/"
106
+ fi
107
+ if [[ $EMIT_JSON -eq 1 ]]; then
100
108
  if [[ $STRICT -eq 1 ]]; then
101
109
  printf '{"status":"none","engine_bin":null,"source":null,"arch":"%s","message":"strict mode: %s"}\n' "$ARCH" "$local_msg"
102
110
  else
103
111
  printf '{"status":"unavailable","engine_bin":null,"source":null,"arch":"%s","message":"%s"}\n' "$ARCH" "$local_msg"
104
112
  fi
105
113
  elif [[ $STRICT -eq 1 ]]; then
106
- echo "STRICT: No codebase-memory-mcp engine found." >&2
114
+ echo "STRICT: $local_msg" >&2
115
+ elif [[ -n "${DRAFT_MEMORY_DISABLE:-}" ]]; then
116
+ echo "ERROR: $local_msg" >&2
107
117
  else
108
118
  echo "ERROR: No Draft graph engine located (codebase-memory-mcp)." >&2
109
119
  echo " Install it (scripts/fetch-memory-engine.sh) or put it on PATH." >&2
package/skills/GRAPH.md CHANGED
@@ -8,16 +8,19 @@
8
8
  ## Two-Tier Architecture
9
9
 
10
10
  ### Primary Workflow (4 commands)
11
- ```
11
+
12
+ ```text
12
13
  init → new-track → implement → review
13
14
  ↑ |
14
15
  └───────────┘ (auto-invoked at phase boundaries)
15
16
  ```
16
17
 
17
18
  ### Routed Core Workflows (5 routers)
19
+
18
20
  The 5 routers (`/draft:plan`, `/draft:ops`, `/draft:docs`, `/draft:discover`, `/draft:jira`) provide intent analysis and dispatch to the leaf specialist skills. Primary commands and routers are the public surface; leaves remain for compatibility and direct scripting.
19
21
 
20
22
  ### Specialist Leaf Commands
23
+
21
24
  Grouped into subsystems dispatched by the routers (or primary commands).
22
25
 
23
26
  ---
@@ -215,7 +218,8 @@ graph TD
215
218
  ## Execution Chains
216
219
 
217
220
  ### Standard Development Flow
218
- ```
221
+
222
+ ```text
219
223
  init → new-track → implement → review → (git push + PR)
220
224
  ↑ |
221
225
  └───────────┘
@@ -223,33 +227,38 @@ init → new-track → implement → review → (git push + PR)
223
227
  ```
224
228
 
225
229
  ### Bug Fix Flow
226
- ```
230
+
231
+ ```text
227
232
  new-track (bug) → debug → implement → review
228
233
  ↑ |
229
234
  └──────────────────────┘ (iterate if fix incomplete)
230
235
  ```
231
236
 
232
237
  ### Incident Response Flow
233
- ```
238
+
239
+ ```text
234
240
  incident-response → debug → implement → review
235
241
  |
236
242
  └→ documentation (post-incident report)
237
243
  ```
238
244
 
239
245
  ### Operations Flow
240
- ```
246
+
247
+ ```text
241
248
  standup ←── status (reads tracks + git log)
242
249
  deep-review ──→ tech-debt ──→ new-track (prioritized items)
243
250
  ```
244
251
 
245
252
  ### Monorepo Flow
246
- ```
253
+
254
+ ```text
247
255
  init (root) → whole-repo code-graph spine + sparse root map
248
256
  init (sub-module) → module snapshot + root-link.json → cross-module context via the root spine
249
257
  ```
250
258
 
251
259
  ### Quality Audit Flow
252
- ```
260
+
261
+ ```text
253
262
  init → quick-review (fast sanity check)
254
263
  init → review (full three-stage)
255
264
  init → bughunt (14-dimension sweep)
@@ -259,14 +268,16 @@ init → decompose (optional pre-step for large modules)
259
268
  ```
260
269
 
261
270
  ### Jira Integration Flow
262
- ```
271
+
272
+ ```text
263
273
  new-track → jira (preview / create / review subcommands)
264
274
 
265
275
  bughunt + review reports (optional enrichment)
266
276
  ```
267
277
 
268
278
  ### Learning Flow
269
- ```
279
+
280
+ ```text
270
281
  init → learn → (updates guardrails.md)
271
282
 
272
283
  All quality skills read guardrails.md
@@ -290,7 +301,7 @@ init → learn → (updates guardrails.md)
290
301
 
291
302
  ## Artifact Flow
292
303
 
293
- ```
304
+ ```text
294
305
  ┌─────────────────────────────────────────────┐
295
306
  │ draft/.state/ │
296
307
  │ freshness.json signals.json run-memory │