@drafthq/draft 3.6.0 → 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 (117) 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/installer.js +1 -1
  7. package/cli/src/lib/cursor-registry.js +10 -0
  8. package/core/agents/architect.md +21 -9
  9. package/core/agents/debugger.md +5 -0
  10. package/core/agents/ops.md +7 -2
  11. package/core/agents/planner.md +7 -0
  12. package/core/agents/rca.md +14 -3
  13. package/core/agents/reviewer.md +1 -0
  14. package/core/agents/writer.md +6 -0
  15. package/core/knowledge-base.md +22 -3
  16. package/core/methodology.md +30 -5
  17. package/core/shared/condensation.md +27 -17
  18. package/core/shared/context-verify.md +3 -1
  19. package/core/shared/cross-skill-dispatch.md +7 -3
  20. package/core/shared/draft-context-loading.md +3 -0
  21. package/core/shared/git-report-metadata.md +1 -0
  22. package/core/shared/graph-query.md +0 -2
  23. package/core/shared/graph-usage-report.md +1 -1
  24. package/core/shared/jira-sync.md +10 -6
  25. package/core/shared/okf-retrieval.md +1 -1
  26. package/core/shared/parallel-analysis.md +14 -7
  27. package/core/shared/pattern-learning.md +2 -0
  28. package/core/shared/template-contract.md +1 -1
  29. package/core/shared/vcs-commands.md +5 -2
  30. package/core/templates/ai-context.md +3 -2
  31. package/core/templates/ai-profile.md +7 -0
  32. package/core/templates/architecture.md +6 -2
  33. package/core/templates/discovery.md +1 -1
  34. package/core/templates/guardrails.md +9 -0
  35. package/core/templates/hld.md +5 -4
  36. package/core/templates/intake-questions.md +45 -0
  37. package/core/templates/jira.md +11 -2
  38. package/core/templates/lld.md +8 -5
  39. package/core/templates/okf/concept.md +0 -1
  40. package/core/templates/product.md +11 -2
  41. package/core/templates/rca.md +6 -0
  42. package/core/templates/service-index.md +1 -0
  43. package/core/templates/spec.md +15 -0
  44. package/core/templates/tech-stack.md +12 -0
  45. package/core/templates/workflow.md +20 -0
  46. package/integrations/agents/AGENTS.md +1272 -292
  47. package/integrations/copilot/.github/copilot-instructions.md +1272 -292
  48. package/package.json +1 -1
  49. package/scripts/fetch-memory-engine.sh +24 -6
  50. package/scripts/lib.sh +11 -4
  51. package/scripts/tools/_graph_queries.sh +10 -3
  52. package/scripts/tools/_lib.sh +33 -11
  53. package/scripts/tools/check-repo-size.sh +125 -0
  54. package/scripts/tools/check-skill-line-caps.sh +1 -2
  55. package/scripts/tools/check-template-noop.sh +0 -1
  56. package/scripts/tools/classify-files.sh +2 -2
  57. package/scripts/tools/cycle-detect.sh +16 -8
  58. package/scripts/tools/fix-whitespace.sh +3 -0
  59. package/scripts/tools/git-metadata.sh +6 -0
  60. package/scripts/tools/graph-arch.sh +1 -1
  61. package/scripts/tools/graph-callers.sh +6 -3
  62. package/scripts/tools/graph-impact.sh +4 -2
  63. package/scripts/tools/graph-init.sh +12 -8
  64. package/scripts/tools/graph-preflight.sh +3 -0
  65. package/scripts/tools/graph-query.sh +70 -7
  66. package/scripts/tools/graph-snapshot.sh +29 -5
  67. package/scripts/tools/hotspot-rank.sh +2 -1
  68. package/scripts/tools/install-smoke-test.sh +223 -0
  69. package/scripts/tools/mermaid-from-graph.sh +2 -1
  70. package/scripts/tools/migrate-track-frontmatter.sh +9 -10
  71. package/scripts/tools/okf-coverage-check.sh +4 -3
  72. package/scripts/tools/okf-fix-links.sh +18 -93
  73. package/scripts/tools/okf-plan-concepts.sh +5 -8
  74. package/scripts/tools/okf-render-views.sh +19 -14
  75. package/scripts/tools/okf-validate-quality.sh +13 -2
  76. package/scripts/tools/okf-validate.sh +13 -2
  77. package/scripts/tools/parse-git-log.sh +19 -1
  78. package/scripts/tools/resolve-tools.sh +24 -8
  79. package/scripts/tools/verify-citations.sh +1 -2
  80. package/scripts/tools/verify-doc-anchors.sh +13 -33
  81. package/scripts/tools/verify-graph-binary.sh +13 -3
  82. package/skills/GRAPH.md +21 -10
  83. package/skills/adr/SKILL.md +24 -6
  84. package/skills/assist-review/SKILL.md +2 -1
  85. package/skills/bughunt/SKILL.md +54 -11
  86. package/skills/bughunt/references/regression-tests.md +14 -4
  87. package/skills/change/SKILL.md +19 -9
  88. package/skills/coverage/SKILL.md +26 -10
  89. package/skills/debug/SKILL.md +12 -2
  90. package/skills/decompose/SKILL.md +39 -9
  91. package/skills/deep-review/SKILL.md +17 -4
  92. package/skills/deploy-checklist/SKILL.md +9 -2
  93. package/skills/documentation/SKILL.md +12 -1
  94. package/skills/draft/SKILL.md +40 -29
  95. package/skills/draft/context-files.md +8 -1
  96. package/skills/draft/quality-guide.md +10 -2
  97. package/skills/graph/SKILL.md +1 -1
  98. package/skills/impact/SKILL.md +3 -1
  99. package/skills/implement/SKILL.md +60 -18
  100. package/skills/incident-response/SKILL.md +5 -2
  101. package/skills/init/SKILL.md +102 -23
  102. package/skills/init/references/architecture-spec.md +31 -7
  103. package/skills/init/references/okf-emitter.md +3 -3
  104. package/skills/jira/SKILL.md +33 -13
  105. package/skills/jira/references/review.md +61 -19
  106. package/skills/learn/SKILL.md +29 -6
  107. package/skills/new-track/SKILL.md +73 -18
  108. package/skills/quick-review/SKILL.md +7 -2
  109. package/skills/revert/SKILL.md +11 -5
  110. package/skills/review/SKILL.md +123 -24
  111. package/skills/review/references/zero-setup-mode.md +117 -0
  112. package/skills/standup/SKILL.md +9 -1
  113. package/skills/status/SKILL.md +6 -6
  114. package/skills/tech-debt/SKILL.md +7 -1
  115. package/skills/testing-strategy/SKILL.md +10 -2
  116. package/skills/tour/SKILL.md +2 -1
  117. package/skills/upload/SKILL.md +3 -2
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # okf-render-views.sh — render the demoted views from an OKF taxonomy bundle.
3
3
  #
4
- # The wiki/ bundle is the source of truth. This produces the two derived,
4
+ # The wiki/ bundle is the source of truth. This produces the derived,
5
5
  # human-facing views deterministically (so they never drift from the bundle and
6
6
  # carry zero extra maintenance):
7
7
  # 1. architecture.md — a single linear concatenation of every concept page,
@@ -24,6 +24,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
24
  # shellcheck source=scripts/tools/_lib.sh
25
25
  source "$SCRIPT_DIR/_lib.sh"
26
26
 
27
+ command -v python3 >/dev/null 2>&1 || {
28
+ echo "ERROR: python3 is required by ${0##*/} (link rewriting in rendered views)" >&2
29
+ exit 2
30
+ }
31
+
27
32
  BUNDLE=""
28
33
  ARCH_OUT=""
29
34
  WEB_OUT=""
@@ -56,7 +61,7 @@ Flags:
56
61
  this tool has no clock dependency).
57
62
  --help Show this help.
58
63
 
59
- Requires jq (already a Draft prereq) for --web. Exit 0 ok, 1 error, 2 bundle not found.
64
+ Requires jq (for --web) and python3 (link rewriting). Exit 0 ok, 1 error, 2 bundle not found.
60
65
  EOF
61
66
  }
62
67
 
@@ -100,8 +105,6 @@ ordered_pages() {
100
105
  done
101
106
  }
102
107
 
103
- # Strip YAML frontmatter from a page (leading --- ... --- block on line 1).
104
-
105
108
  # Rewrite markdown links so concatenated architecture.md (at draft/) resolves them.
106
109
  # stdin body; $1 = bundle-relative path e.g. systems/foo.md
107
110
  rewrite_body_links() {
@@ -133,6 +136,7 @@ sys.stdout.write(re.sub(r"\[([^\]]*)\]\(([^)]+)\)", repl, text))
133
136
  ' "$sec"
134
137
  }
135
138
 
139
+ # Strip YAML frontmatter from a page (leading --- ... --- block on line 1).
136
140
  strip_frontmatter() {
137
141
  awk '
138
142
  NR==1 && /^---$/ { fm=1; next }
@@ -217,6 +221,7 @@ render_architecture() {
217
221
  strip_frontmatter "$BUNDLE/$rel" | rewrite_body_links "$rel"
218
222
  done < <(ordered_pages)
219
223
  } >"$tmp"
224
+ apply_dest_mode "$tmp" "$out" # mktemp is 0600; mv would strip the dest's mode
220
225
  mv "$tmp" "$out"
221
226
  if [[ -x "$SCRIPT_DIR/okf-fix-links.sh" ]]; then
222
227
  draft_dir="$(cd "$(dirname "$out")" && pwd)"
@@ -243,12 +248,7 @@ build_concept_map() {
243
248
  [[ -n "$type" ]] || continue
244
249
  title="$(get_yaml_field "$page" title)"
245
250
  [[ -n "$title" ]] || title="$rel"
246
- # description may be a folded (>) block — take the first non-empty body line.
247
- desc="$(awk '
248
- NR==1&&/^---$/{fm=1;next} fm&&/^---$/{exit}
249
- fm && /^description:/ { collect=1; sub(/^description:[[:space:]]*>?[[:space:]]*/,""); if($0!=""){print; exit} next }
250
- fm && collect { sub(/^[[:space:]]+/,""); if($0!=""){print; exit} }
251
- ' "$page")"
251
+ desc="$(page_desc "$page")"
252
252
  echo "| [${title}](${rel}) | ${type} | ${desc} |"
253
253
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
254
254
  }
@@ -313,14 +313,18 @@ inject_concept_map() {
313
313
  /<!-- CONCEPT-MAP:END -->/ { skip=0 }
314
314
  !skip { print }
315
315
  ' "$target" >"$tmp"
316
+ apply_dest_mode "$tmp" "$target" # mktemp is 0600; mv would strip the dest's mode
316
317
  mv "$tmp" "$target"
317
318
  echo "injected Concept Map → $target"
318
319
  }
319
320
 
320
321
  # --- 3. Render a self-contained offline HTML viewer (single file) ---
321
322
  # All pages are inlined as JSON; a small built-in markdown renderer draws them in
322
- # the browser. No server, no internet, no CDN. jq encodes page content safely
323
- # (and we neutralize any literal </ so embedded "</script>" can't break parsing).
323
+ # the browser. No server, no internet, no CDN. jq encodes page content safely,
324
+ # and the whole emitted data block is passed through a `</` → `<\/` filter: JSON
325
+ # escaping does NOT stop a literal "</script>" from closing the <script> element,
326
+ # and page titles/types/paths come from repo-derived frontmatter, so every field
327
+ # has to be neutralized, not just the markdown body.
324
328
  render_web() {
325
329
  local out="$1"
326
330
  command -v jq >/dev/null 2>&1 || { echo "ERROR: --web requires jq" >&2; return 1; }
@@ -388,14 +392,14 @@ HTML_HEAD
388
392
  "$(jq -Rn --arg v "$rel" '$v')" \
389
393
  "$(jq -Rn --arg v "$title" '$v')" \
390
394
  "$(jq -Rn --arg v "$type" '$v')" \
391
- "$(strip_frontmatter "$page" | jq -Rs . | sed 's#</#<\\/#g')"
395
+ "$(strip_frontmatter "$page" | jq -Rs .)"
392
396
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
393
397
  echo "};"
394
398
  # ORDER: bundle root index.md first, then everything else sorted.
395
399
  echo "const ORDER = Object.keys(PAGES).sort(function(a,b){"
396
400
  echo " if(a==='index.md') return -1; if(b==='index.md') return 1;"
397
401
  echo " return a<b?-1:a>b?1:0; });"
398
- } >>"$tmp"
402
+ } | sed 's#</#<\\/#g' >>"$tmp"
399
403
 
400
404
  cat >>"$tmp" <<'HTML_TAIL'
401
405
  function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
@@ -487,6 +491,7 @@ show(decodeURIComponent(location.hash.slice(1)) || ORDER[0]);
487
491
  HTML_TAIL
488
492
 
489
493
  mkdir -p "$(dirname "$out")"
494
+ apply_dest_mode "$tmp" "$out" # mktemp is 0600; mv would strip the dest's mode
490
495
  mv "$tmp" "$out"
491
496
  echo "rendered offline HTML viewer → $out ($(find "$BUNDLE" -type f -name '*.md' | grep -c .) pages)"
492
497
  }
@@ -73,6 +73,17 @@ body_of() {
73
73
  awk 'NR==1&&/^---$/{fm=1;next} fm&&/^---$/{fm=0;next} !fm{print}' "$1"
74
74
  }
75
75
 
76
+ # Body text the Q-TEMPLATE scan is allowed to see: fenced code blocks and
77
+ # shell-style ${VAR} expansions removed. Both legitimately carry {ALL_CAPS}
78
+ # spans that are not unreplaced placeholders (a page quoting `${HOME}` or a CI
79
+ # snippet would otherwise fail).
80
+ template_scan_body() {
81
+ body_of "$1" | awk '
82
+ /^[[:space:]]*```/ { fence = !fence; next }
83
+ !fence { gsub(/\$\{[A-Za-z_][A-Za-z0-9_]*\}/, ""); print }
84
+ '
85
+ }
86
+
76
87
  # Count body lines after the frontmatter close (Q-LEN = "lines after frontmatter").
77
88
  body_lines() { body_of "$1" | wc -l | tr -d ' '; }
78
89
 
@@ -114,7 +125,7 @@ mermaid_lint() {
114
125
  echo "unicode arrow in mermaid (use --> not →)"; return 1
115
126
  fi
116
127
  # '&' node chaining (common breaker).
117
- if printf '%s' "$blocks" | grep -qE '[A-Za-z0-9_]\s*&\s*[A-Za-z0-9_]'; then
128
+ if printf '%s' "$blocks" | grep -qE '[A-Za-z0-9_][[:space:]]*&[[:space:]]*[A-Za-z0-9_]'; then
118
129
  echo "'&' node chaining in mermaid"; return 1
119
130
  fi
120
131
  # Reserved bareword node ids.
@@ -235,7 +246,7 @@ while IFS= read -r -d '' page; do
235
246
  fi
236
247
 
237
248
  # Q-TEMPLATE: unreplaced {TOKEN} placeholders.
238
- if body_of "$page" | grep -qE "$TOKEN_RE"; then
249
+ if template_scan_body "$page" | grep -qE "$TOKEN_RE"; then
239
250
  fail "$rel" "Q-TEMPLATE" "unreplaced template token {PLACEHOLDER}"
240
251
  fi
241
252
 
@@ -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)
@@ -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
@@ -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
  }
@@ -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
@@ -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 .)
@@ -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 │