@drafthq/draft 2.8.3 → 3.1.5

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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +5 -5
  4. package/bin/README.md +10 -0
  5. package/core/methodology.md +17 -18
  6. package/core/shared/condensation.md +9 -9
  7. package/core/shared/draft-context-loading.md +7 -9
  8. package/core/shared/graph-query.md +38 -31
  9. package/core/shared/graph-usage-report.md +1 -1
  10. package/core/shared/pattern-learning.md +2 -2
  11. package/core/shared/red-flags.md +3 -3
  12. package/core/templates/ai-context.md +2 -1
  13. package/core/templates/ai-profile.md +1 -0
  14. package/core/templates/architecture.md +4 -3
  15. package/core/templates/dependency-graph.md +2 -2
  16. package/core/templates/discovery.md +1 -0
  17. package/core/templates/guardrails.md +1 -0
  18. package/core/templates/hld.md +1 -0
  19. package/core/templates/lld.md +1 -0
  20. package/core/templates/plan.md +1 -0
  21. package/core/templates/product.md +1 -0
  22. package/core/templates/rca.md +1 -0
  23. package/core/templates/root-architecture.md +3 -3
  24. package/core/templates/root-product.md +2 -2
  25. package/core/templates/root-tech-stack.md +2 -2
  26. package/core/templates/service-index.md +3 -3
  27. package/core/templates/spec.md +1 -0
  28. package/core/templates/tech-matrix.md +2 -2
  29. package/core/templates/tech-stack.md +1 -0
  30. package/core/templates/workflow.md +1 -0
  31. package/integrations/agents/AGENTS.md +276 -1037
  32. package/integrations/copilot/.github/copilot-instructions.md +276 -1037
  33. package/package.json +1 -1
  34. package/scripts/lib.sh +2 -1
  35. package/scripts/tools/adr-index.sh +2 -2
  36. package/scripts/tools/check-scope-conflicts.sh +2 -2
  37. package/scripts/tools/check-skill-line-caps.sh +2 -2
  38. package/scripts/tools/cycle-detect.sh +5 -1
  39. package/scripts/tools/diff-templates-vs-tracks.sh +2 -2
  40. package/scripts/tools/fix-whitespace.sh +15 -9
  41. package/scripts/tools/graph-arch.sh +72 -0
  42. package/scripts/tools/graph-impact.sh +1 -0
  43. package/scripts/tools/graph-init.sh +187 -0
  44. package/scripts/tools/graph-snapshot.sh +52 -46
  45. package/scripts/tools/hotspot-rank.sh +2 -0
  46. package/scripts/tools/manage-symlinks.sh +1 -1
  47. package/scripts/tools/parse-reports.sh +1 -1
  48. package/scripts/tools/skill-caps.conf +0 -1
  49. package/scripts/tools/verify-doc-anchors.sh +2 -2
  50. package/scripts/tools/verify-graph-binary.sh +1 -1
  51. package/skills/GRAPH.md +9 -12
  52. package/skills/bughunt/SKILL.md +14 -1
  53. package/skills/debug/SKILL.md +3 -3
  54. package/skills/decompose/SKILL.md +5 -5
  55. package/skills/deep-review/SKILL.md +2 -2
  56. package/skills/deploy-checklist/SKILL.md +2 -2
  57. package/skills/discover/SKILL.md +2 -4
  58. package/skills/draft/SKILL.md +2 -2
  59. package/skills/draft/intent-mapping.md +3 -2
  60. package/skills/graph/SKILL.md +3 -3
  61. package/skills/implement/SKILL.md +1 -1
  62. package/skills/init/SKILL.md +102 -43
  63. package/skills/init/references/architecture-spec.md +17 -16
  64. package/skills/learn/SKILL.md +5 -5
  65. package/skills/quick-review/SKILL.md +3 -3
  66. package/skills/review/SKILL.md +7 -7
  67. package/skills/tech-debt/SKILL.md +2 -2
  68. package/skills/index/SKILL.md +0 -848
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drafthq/draft",
3
- "version": "2.8.3",
3
+ "version": "3.1.5",
4
4
  "description": "Context-Driven Development for AI coding agents — install Draft into Claude Code, Cursor, Codex, or opencode.",
5
5
  "bin": {
6
6
  "draft": "cli/bin/draft.js"
package/scripts/lib.sh CHANGED
@@ -24,7 +24,6 @@ TOOLS_DIR="$ROOT_DIR/scripts/tools"
24
24
  SKILL_ORDER=(
25
25
  draft
26
26
  init
27
- index
28
27
  graph
29
28
  new-track
30
29
  decompose
@@ -158,8 +157,10 @@ TOOLS=(
158
157
  "manage-symlinks.sh"
159
158
  "mermaid-from-graph.sh"
160
159
  "graph-snapshot.sh"
160
+ "graph-init.sh"
161
161
  "graph-impact.sh"
162
162
  "graph-callers.sh"
163
+ "graph-arch.sh"
163
164
  "validate-frontmatter.sh"
164
165
  # Foundations hygiene/verification tools
165
166
  "check-graph-usage-report.sh"
@@ -84,7 +84,7 @@ while IFS= read -r -d '' file; do
84
84
 
85
85
  # If no title in frontmatter, fallback to first H1.
86
86
  if [[ -z "$title" ]]; then
87
- title="$(grep -m1 '^# ' "$file" 2>/dev/null | sed 's/^#\s*//' || true)"
87
+ title="$(grep -m1 '^# ' "$file" 2>/dev/null | sed 's/^#[[:space:]]*//' || true)"
88
88
  fi
89
89
 
90
90
  tracks=()
@@ -108,7 +108,7 @@ while IFS= read -r -d '' file; do
108
108
  "$(json_escape "$status")" \
109
109
  "$(json_escape "$file")" \
110
110
  "$tr_json"
111
- done < <(find "$ROOT" -maxdepth 2 -type f -name '*.md' -print0 2>/dev/null | sort -z)
111
+ done < <(find "$ROOT" -maxdepth 2 -type f -name '*.md' 2>/dev/null | sort | tr '\n' '\0')
112
112
 
113
113
  if $first; then
114
114
  printf ']}\n'
@@ -112,7 +112,7 @@ emit() {
112
112
  if ((EMIT_JSON)); then
113
113
  printf '{"conflict_count": %d, "conflicts": [\n' "$conflict_count"
114
114
  local first=1 c track kind detail
115
- for c in "${conflicts[@]}"; do
115
+ for c in ${conflicts[@]+"${conflicts[@]}"}; do
116
116
  IFS='|' read -r track kind detail <<< "$c"
117
117
  if ((first)); then first=0; else printf ',\n'; fi
118
118
  printf ' {"track":"%s","kind":"%s","detail":"%s"}' \
@@ -127,7 +127,7 @@ emit() {
127
127
  printf 'SCOPE: %d conflict(s) across %d track(s).\n' \
128
128
  "$conflict_count" "${#TRACK_PATHS[@]}" >&2
129
129
  local c track kind detail
130
- for c in "${conflicts[@]}"; do
130
+ for c in ${conflicts[@]+"${conflicts[@]}"}; do
131
131
  IFS='|' read -r track kind detail <<< "$c"
132
132
  printf ' [%s] %s — %s\n' "$kind" "$track" "$detail" >&2
133
133
  done
@@ -78,7 +78,7 @@ emit() {
78
78
  printf '{"over_cap_count": %d, "global_cap": %d, "findings": [\n' \
79
79
  "$over_count" "$GLOBAL_CAP"
80
80
  local first=1 v name lines cap
81
- for v in "${findings[@]}"; do
81
+ for v in ${findings[@]+"${findings[@]}"}; do
82
82
  IFS='|' read -r name lines cap <<< "$v"
83
83
  if ((first)); then first=0; else printf ',\n'; fi
84
84
  printf ' {"skill":"%s","lines":%d,"cap":%d}' \
@@ -100,7 +100,7 @@ emit() {
100
100
  "$over_count" "$mode"
101
101
  fi
102
102
  local v name lines cap
103
- for v in "${findings[@]}"; do
103
+ for v in ${findings[@]+"${findings[@]}"}; do
104
104
  IFS='|' read -r name lines cap <<< "$v"
105
105
  if ((ENFORCE)); then
106
106
  printf ' %s: %d lines (cap %d)\n' "$name" "$lines" "$cap" >&2
@@ -68,7 +68,11 @@ Q3="MATCH (a:Function)-[:CALLS]->(b:Function)-[:CALLS]->(c:Function)-[:CALLS]->(
68
68
  R2="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$Q2\"}" || echo '{}')"
69
69
  R3="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$Q3\"}" || echo '{}')"
70
70
 
71
- jq -n --argjson r2 "${R2:-{\}}" --argjson r3 "${R3:-{\}}" '
71
+ # Guard against empty/non-JSON engine output so --argjson never aborts the script.
72
+ echo "$R2" | jq -e . >/dev/null 2>&1 || R2='{}'
73
+ echo "$R3" | jq -e . >/dev/null 2>&1 || R3='{}'
74
+
75
+ jq -n --argjson r2 "$R2" --argjson r3 "$R3" '
72
76
  {
73
77
  cycles: (((($r2.rows) // []) + (($r3.rows) // []))),
74
78
  source: "memory-graph"
@@ -146,7 +146,7 @@ emit_records() {
146
146
  if ((EMIT_JSON)); then
147
147
  printf '{"drift_count": %d, "records": [\n' "$drift_count"
148
148
  local first=1
149
- for r in "${drift_records[@]}"; do
149
+ for r in ${drift_records[@]+"${drift_records[@]}"}; do
150
150
  local track kind detail
151
151
  IFS='|' read -r track kind detail <<< "$r"
152
152
  if ((first)); then first=0; else printf ',\n'; fi
@@ -163,7 +163,7 @@ emit_records() {
163
163
  printf 'DRIFT: %d defect(s) across %d track(s).\n' \
164
164
  "$drift_count" "${#TRACK_PATHS[@]}" >&2
165
165
  local r track kind detail
166
- for r in "${drift_records[@]}"; do
166
+ for r in ${drift_records[@]+"${drift_records[@]}"}; do
167
167
  IFS='|' read -r track kind detail <<< "$r"
168
168
  printf ' [%s] %s — %s\n' "$kind" "$track" "$detail" >&2
169
169
  done
@@ -54,6 +54,11 @@ fix_file() {
54
54
  return 2
55
55
  fi
56
56
 
57
+ # Empty file: nothing to normalize (avoid writing a spurious newline).
58
+ if [[ ! -s "$file" ]]; then
59
+ return 1
60
+ fi
61
+
57
62
  local original
58
63
  original="$(cat "$file")"
59
64
 
@@ -66,18 +71,19 @@ fix_file() {
66
71
  | sed -e :a -e '/^\n*$/{$d;N;ba}'
67
72
  )"$'\n'
68
73
 
69
- if [[ "$fixed" == "$original" ]]; then
70
- return 1 # already clean
71
- fi
72
-
74
+ # Compare the bytes we would write against the file on disk — NOT the
75
+ # command-substitution copies (which strip then re-add the trailing newline,
76
+ # so they could never compare equal). This keeps fix_file idempotent.
73
77
  local _tmp
74
78
  _tmp="$(mktemp "${file}.XXXXXX")"
75
- if printf '%s' "$fixed" > "$_tmp"; then
76
- mv -f "$_tmp" "$file"
77
- else
79
+ printf '%s' "$fixed" > "$_tmp" || { rm -f "$_tmp"; return 2; }
80
+
81
+ if cmp -s "$_tmp" "$file"; then
78
82
  rm -f "$_tmp"
79
- return 2
83
+ return 1 # already clean — no change on disk
80
84
  fi
85
+
86
+ mv -f "$_tmp" "$file"
81
87
  return 0
82
88
  }
83
89
 
@@ -109,7 +115,7 @@ case "$1" in
109
115
  fi
110
116
  while IFS= read -r -d '' f; do
111
117
  TARGETS+=("$f")
112
- done < <(find "$TRACK_DIR" -maxdepth 1 -name "*.md" -print0 | sort -z)
118
+ done < <(find "$TRACK_DIR" -maxdepth 1 -name "*.md" | sort | tr '\n' '\0')
113
119
  ;;
114
120
  --draft)
115
121
  REPO_ROOT="${2:-$(pwd)}"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ # graph-arch.sh — emit the architecture view from the knowledge graph.
3
+ #
4
+ # The engine-only replacement for the old committed architecture.json. Resolves
5
+ # the codebase-memory-mcp engine, indexes the repo on demand, auto-resolves the
6
+ # project, and prints the full get_architecture(all) JSON to stdout — node labels,
7
+ # edge types, languages, packages (fan-in/out), entry points, routes, hotspots,
8
+ # boundaries, layers, clusters, file tree. Pipe to jq to slice the field you need:
9
+ #
10
+ # scripts/tools/graph-arch.sh --repo . | jq '.packages'
11
+ # scripts/tools/graph-arch.sh --repo . | jq '.routes'
12
+ #
13
+ # The engine binary is usually NOT on $PATH; this wrapper resolves it (via
14
+ # _lib.sh:find_memory_bin) so callers never invoke `codebase-memory-mcp` directly.
15
+ #
16
+ # Usage: scripts/tools/graph-arch.sh [--repo DIR]
17
+ # Output: the architecture JSON object on success; {"source":"unavailable"} on failure.
18
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine/data unavailable.
19
+ set -euo pipefail
20
+
21
+ # shellcheck source=_lib.sh
22
+ source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
23
+
24
+ REPO="."
25
+
26
+ usage() {
27
+ cat <<'EOF'
28
+ graph-arch.sh — architecture view (packages, routes, layers, hotspots) from the graph.
29
+
30
+ Usage:
31
+ scripts/tools/graph-arch.sh [--repo DIR]
32
+
33
+ Flags:
34
+ --repo DIR Repository root (default: cwd).
35
+ --help Show this help.
36
+
37
+ Output: the get_architecture(all) JSON object on success. Pipe to jq to slice it
38
+ (`| jq '.packages'`, `| jq '.routes'`, …). Emits {"source":"unavailable"} and exits 2
39
+ when the graph engine is unavailable.
40
+ EOF
41
+ }
42
+
43
+ while [[ $# -gt 0 ]]; do
44
+ case "$1" in
45
+ --repo) REPO="$2"; shift 2;;
46
+ --help|-h) usage; exit 0;;
47
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
48
+ esac
49
+ done
50
+
51
+ if [[ ! -d "$REPO" ]]; then
52
+ echo "ERROR: --repo '$REPO' is not a directory" >&2
53
+ exit 1
54
+ fi
55
+
56
+ REPO_ABS="$(cd "$REPO" && pwd)"
57
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
58
+
59
+ unavailable() { echo '{"source":"unavailable"}'; exit 2; }
60
+
61
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
62
+ command -v jq >/dev/null 2>&1 || unavailable
63
+
64
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
65
+ [[ -n "$PROJECT" ]] || unavailable
66
+
67
+ ARCH_JSON="$(memory_cli get_architecture "{\"project\":\"$PROJECT\",\"aspects\":[\"all\"]}" || true)"
68
+ [[ -n "$ARCH_JSON" ]] || unavailable
69
+
70
+ # Validate it parses and looks like an architecture object before emitting.
71
+ echo "$ARCH_JSON" | jq -e '.total_nodes != null' >/dev/null 2>&1 || unavailable
72
+ echo "$ARCH_JSON" | jq '.'
@@ -54,6 +54,7 @@ done
54
54
 
55
55
  [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
56
56
  [[ -n "$FILE" || -n "$SYMBOL" ]] || { echo "ERROR: provide --file or --symbol" >&2; usage >&2; exit 1; }
57
+ [[ "$DEPTH" =~ ^[0-9]+$ ]] || { echo "ERROR: --depth must be a non-negative integer" >&2; exit 1; }
57
58
 
58
59
  REPO_ABS="$(cd "$REPO" && pwd)"
59
60
  SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ # graph-init.sh — scope-aware, root-first knowledge-graph builder for /draft:init.
3
+ #
4
+ # Ensures the whole-repo "code graph knowledge memory" exists at the repository
5
+ # ROOT (the spine — the single structural source of truth), then builds a
6
+ # scope-local snapshot and links a sub-module's graph up to the root.
7
+ #
8
+ # Model:
9
+ # - ROOT resolution: nearest ancestor ABOVE scope containing draft/ (bounded by
10
+ # the git toplevel) → git toplevel → scope itself (no git / module-local).
11
+ # - The engine is the default capability tier. If the codebase-memory-mcp binary
12
+ # is missing it is fetched (blocking) unless --no-fetch or DRAFT_MEMORY_DISABLE.
13
+ # - Root init (scope == root): build the whole-repo snapshot at <root>/draft/graph/.
14
+ # - Module init (scope != root): unless --module-only, (re)build the root snapshot
15
+ # first (the spine — index time is accepted, incremental once warm), then build
16
+ # <scope>/draft/graph/ and write root-link.json pointing up to the root snapshot.
17
+ #
18
+ # The committed snapshot (draft/graph/) is the git-tracked memory; the engine's
19
+ # ~/.cache index is a disposable accelerator and is never committed.
20
+ #
21
+ # Usage: scripts/tools/graph-init.sh [--scope DIR] [--module-only] [--no-fetch] [--json]
22
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
23
+ set -euo pipefail
24
+
25
+ TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
26
+ # shellcheck source=_lib.sh
27
+ source "$TOOLS_DIR/_lib.sh"
28
+
29
+ SCOPE="."
30
+ MODULE_ONLY=0
31
+ NO_FETCH=0
32
+ EMIT_JSON=0
33
+
34
+ usage() {
35
+ cat <<'EOF'
36
+ graph-init.sh — scope-aware, root-first knowledge-graph builder.
37
+
38
+ Usage:
39
+ scripts/tools/graph-init.sh [--scope DIR] [--module-only] [--no-fetch] [--json]
40
+
41
+ Flags:
42
+ --scope DIR Directory init was invoked in (default: cwd).
43
+ --module-only Do not touch the root; build only the module snapshot and mark
44
+ its root link "pending".
45
+ --no-fetch Never download the engine; degrade if it is absent (CI/tests).
46
+ --json Emit a machine-readable summary instead of a human report.
47
+ --help Show this help.
48
+
49
+ Exit 0 on success, 2 when the graph engine is unavailable (no snapshot built).
50
+ EOF
51
+ }
52
+
53
+ while [[ $# -gt 0 ]]; do
54
+ case "$1" in
55
+ --scope) SCOPE="$2"; shift 2;;
56
+ --module-only) MODULE_ONLY=1; shift;;
57
+ --no-fetch) NO_FETCH=1; shift;;
58
+ --json) EMIT_JSON=1; shift;;
59
+ --help|-h) usage; exit 0;;
60
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
61
+ esac
62
+ done
63
+
64
+ [[ -d "$SCOPE" ]] || { echo "ERROR: --scope '$SCOPE' is not a directory" >&2; exit 1; }
65
+ SCOPE_ABS="$(cd "$SCOPE" && pwd)"
66
+ SELF_REPO="$(cd "$TOOLS_DIR/../.." && pwd)"
67
+
68
+ # --- Resolve ROOT (bounded by the git toplevel; never escapes the repo) ---
69
+ GIT_TOP="$(git -C "$SCOPE_ABS" rev-parse --show-toplevel 2>/dev/null || true)"
70
+ resolve_root() {
71
+ if [[ -z "$GIT_TOP" ]]; then printf '%s' "$SCOPE_ABS"; return; fi # no git → module-local
72
+ local d="$SCOPE_ABS"
73
+ while [[ "$d" != "$GIT_TOP" && "$d" != "/" ]]; do
74
+ d="$(dirname "$d")"
75
+ if [[ "$d" != "$SCOPE_ABS" && -d "$d/draft" ]]; then printf '%s' "$d"; return; fi
76
+ done
77
+ printf '%s' "$GIT_TOP"
78
+ }
79
+ ROOT_ABS="$(resolve_root)"
80
+ IS_ROOT=0
81
+ [[ "$SCOPE_ABS" == "$ROOT_ABS" ]] && IS_ROOT=1
82
+
83
+ # --- Ensure the engine (the default tier); fetch when missing unless told not to ---
84
+ ensure_engine() {
85
+ [[ -z "${DRAFT_MEMORY_DISABLE:-}" ]] || return 1
86
+ find_memory_bin "$SCOPE_ABS" "$SELF_REPO" && return 0
87
+ [[ "$NO_FETCH" -eq 0 ]] || return 1
88
+ echo "Graph engine not found — fetching it (one-time download; this may take a while)..." >&2
89
+ "$SELF_REPO/scripts/fetch-memory-engine.sh" >&2 2>&1 || true
90
+ find_memory_bin "$SCOPE_ABS" "$SELF_REPO"
91
+ }
92
+
93
+ engine_unavailable() {
94
+ if [[ "$EMIT_JSON" -eq 1 ]]; then
95
+ printf '{"status":"unavailable","root":"%s","scope":"%s","is_root":%s}\n' \
96
+ "$ROOT_ABS" "$SCOPE_ABS" "$IS_ROOT"
97
+ else
98
+ echo "WARNING: knowledge-graph engine (codebase-memory-mcp) is unavailable — no graph built." >&2
99
+ echo " The engine is Draft's default capability tier. Install it with:" >&2
100
+ echo " scripts/fetch-memory-engine.sh" >&2
101
+ echo " or put codebase-memory-mcp on PATH. Set DRAFT_MEMORY_DISABLE=1 to silence this." >&2
102
+ echo " Committed draft/graph/ snapshots (if present) still provide structural context." >&2
103
+ fi
104
+ exit 2
105
+ }
106
+
107
+ # Index a repo dir and write its schema.yaml gate marker; returns graph-snapshot's exit code.
108
+ # Progress goes to stderr so stdout stays clean for --json consumers.
109
+ build_snapshot() {
110
+ local rc=0
111
+ "$TOOLS_DIR/graph-snapshot.sh" --repo "$1" 1>&2 || rc=$?
112
+ return "$rc"
113
+ }
114
+
115
+ # Path from <module>/draft/graph back to <root>/draft/graph (module is under root).
116
+ root_link_relpath() {
117
+ local sub="${SCOPE_ABS#"$ROOT_ABS"/}"
118
+ local ups=2 seg
119
+ IFS='/' read -ra seg <<< "$sub"
120
+ ups=$(( ${#seg[@]} + 2 ))
121
+ local i out=""
122
+ for ((i = 0; i < ups; i++)); do out+="../"; done
123
+ printf '%sdraft/graph' "$out"
124
+ }
125
+
126
+ write_root_link() {
127
+ local status="$1"
128
+ local mod_graph="$SCOPE_ABS/draft/graph"
129
+ mkdir -p "$mod_graph"
130
+ local rel root_project="unknown" root_commit ts schema="$ROOT_ABS/draft/graph/schema.yaml"
131
+ rel="$(root_link_relpath)"
132
+ if [[ -f "$schema" ]]; then
133
+ root_project="$(grep -m1 '^project:' "$schema" 2>/dev/null | sed 's/^project:[[:space:]]*//; s/^"//; s/"$//' || true)"
134
+ [[ -n "$root_project" ]] || root_project="unknown"
135
+ fi
136
+ root_commit="$(git -C "$ROOT_ABS" rev-parse --verify --quiet HEAD 2>/dev/null || echo none)"
137
+ ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
138
+ cat > "$mod_graph/root-link.json" <<EOF
139
+ {
140
+ "root_graph": "$rel",
141
+ "root_abs": "$ROOT_ABS/draft/graph",
142
+ "root_project": "${root_project:-unknown}",
143
+ "root_commit": "$root_commit",
144
+ "status": "$status",
145
+ "linked_at": "$ts",
146
+ "linked_by": "graph-init.sh",
147
+ "note": "Root is the authoritative whole-repo graph. Follow root_graph for cross-module understanding."
148
+ }
149
+ EOF
150
+ }
151
+
152
+ ensure_engine || engine_unavailable
153
+
154
+ ROOT_BUILT=0
155
+ MODULE_BUILT=0
156
+ LINK_STATUS="none"
157
+
158
+ if [[ "$IS_ROOT" -eq 1 ]]; then
159
+ build_snapshot "$ROOT_ABS" && ROOT_BUILT=1
160
+ else
161
+ if [[ "$MODULE_ONLY" -eq 0 ]]; then
162
+ echo "Sub-module of $ROOT_ABS — ensuring the root code-graph spine first..." >&2
163
+ build_snapshot "$ROOT_ABS" && ROOT_BUILT=1
164
+ fi
165
+ build_snapshot "$SCOPE_ABS" && MODULE_BUILT=1
166
+ if [[ -f "$ROOT_ABS/draft/graph/schema.yaml" ]]; then
167
+ LINK_STATUS="linked"
168
+ else
169
+ LINK_STATUS="pending"
170
+ fi
171
+ write_root_link "$LINK_STATUS"
172
+ fi
173
+
174
+ if [[ "$EMIT_JSON" -eq 1 ]]; then
175
+ printf '{"status":"ok","root":"%s","scope":"%s","is_root":%s,"root_built":%s,"module_built":%s,"link_status":"%s"}\n' \
176
+ "$ROOT_ABS" "$SCOPE_ABS" "$IS_ROOT" "$ROOT_BUILT" "$MODULE_BUILT" "$LINK_STATUS"
177
+ else
178
+ echo "--- graph-init ---"
179
+ echo "Root: $ROOT_ABS$([[ $IS_ROOT -eq 1 ]] && echo ' (this scope is the root)')"
180
+ [[ "$IS_ROOT" -eq 1 ]] && echo "Built whole-repo spine: $ROOT_ABS/draft/graph/"
181
+ if [[ "$IS_ROOT" -eq 0 ]]; then
182
+ [[ "$ROOT_BUILT" -eq 1 ]] && echo "Root spine: refreshed $ROOT_ABS/draft/graph/"
183
+ echo "Module: $SCOPE_ABS/draft/graph/"
184
+ echo "Root link: $LINK_STATUS ($SCOPE_ABS/draft/graph/root-link.json)"
185
+ fi
186
+ fi
187
+ exit 0
@@ -1,18 +1,23 @@
1
1
  #!/usr/bin/env bash
2
- # graph-snapshot.sh — materialize a lightweight, committed knowledge-graph snapshot.
2
+ # graph-snapshot.sh — index the repo into the local graph engine and write the
3
+ # committed gate marker (schema.yaml).
3
4
  #
4
- # In the codebase-memory-mcp model, Claude Code skills query the engine live
5
- # (on-demand indexing into ~/.cache). This snapshot exists for the two cases
6
- # that cannot run the engine: PR-reviewable graph state, and the Copilot/Gemini
7
- # integrations (which read committed files, not MCP). It is small and derived —
8
- # git remains the source of truth.
5
+ # Draft is engine-only and opinionated: structural truth lives in the local
6
+ # codebase-memory-mcp engine, queried on demand via the `graph-*.sh` wrappers
7
+ # (which shell out to `codebase-memory-mcp cli <tool>`). There is no committed
8
+ # machine-readable mirror of the graph no architecture.json, hotspots.jsonl,
9
+ # *.mermaid, or okf/ bundle. Those were lossy, went stale on the next commit, and
10
+ # duplicated what the engine serves precisely and live. Git remains the source of
11
+ # truth; the engine is the structural index over it.
9
12
  #
10
- # Writes under <repo>/draft/graph/:
11
- # schema.yaml engine + project metadata + counts (gates skill graph use)
12
- # architecture.json raw get_architecture(all) output
13
- # hotspots.jsonl fan-in-ranked symbols, one JSON object per line
14
- # module-deps.mermaid co-change coupling diagram
15
- # proto-map.mermaid detected-route diagram
13
+ # Writes one file under <repo>/draft/graph/:
14
+ # schema.yaml engine + project metadata + index counts. Its presence is the
15
+ # GATE that tells skills the graph engine is wired for this repo
16
+ # (see core/shared/graph-query.md Pre-Check). It carries no graph
17
+ # data every structural query goes to the live engine.
18
+ #
19
+ # Re-running on a repo that still has an old fat snapshot prunes the stale
20
+ # committed artifacts, migrating it to the thin model.
16
21
  #
17
22
  # Usage: scripts/tools/graph-snapshot.sh [--repo DIR] [--out DIR]
18
23
  # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
@@ -27,17 +32,21 @@ OUT_DIR=""
27
32
 
28
33
  usage() {
29
34
  cat <<'EOF'
30
- graph-snapshot.sh — write a committed knowledge-graph snapshot to draft/graph/.
35
+ graph-snapshot.sh — index the repo and write the draft/graph/ gate marker.
36
+
37
+ Indexes the repository into the local graph engine and writes draft/graph/schema.yaml
38
+ (the gate + provenance marker). It writes NO committed graph data — structural
39
+ queries run live against the engine via the graph-*.sh wrappers.
31
40
 
32
41
  Usage:
33
42
  scripts/tools/graph-snapshot.sh [--repo DIR] [--out DIR]
34
43
 
35
44
  Flags:
36
45
  --repo DIR Repository root (default: cwd).
37
- --out DIR Snapshot dir (default: <repo>/draft/graph).
46
+ --out DIR Gate-marker dir (default: <repo>/draft/graph).
38
47
  --help Show this help.
39
48
 
40
- Exit 0 on success, 2 when the graph engine is unavailable (no snapshot written).
49
+ Exit 0 on success, 2 when the graph engine is unavailable (nothing written).
41
50
  EOF
42
51
  }
43
52
 
@@ -56,47 +65,44 @@ REPO_ABS="$(cd "$REPO" && pwd)"
56
65
  SELF_REPO="$(cd "$TOOLS_DIR/../.." && pwd)"
57
66
  OUT="${OUT_DIR:-$REPO_ABS/draft/graph}"
58
67
 
59
- find_memory_bin "$REPO_ABS" "$SELF_REPO" || { echo "graph engine unavailable — no snapshot written" >&2; exit 2; }
60
- command -v jq >/dev/null 2>&1 || { echo "jq required for snapshot" >&2; exit 2; }
68
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || { echo "graph engine unavailable — nothing written" >&2; exit 2; }
69
+ command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 2; }
61
70
 
71
+ # Index on demand; this is the valuable side-effect — it ensures the engine holds
72
+ # a current index of the repo so live queries resolve.
62
73
  PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
63
- [[ -n "$PROJECT" ]] || { echo "could not index repo — no snapshot written" >&2; exit 2; }
74
+ [[ -n "$PROJECT" ]] || { echo "could not index repo — nothing written" >&2; exit 2; }
64
75
 
65
76
  mkdir -p "$OUT"
66
77
 
67
- # 1. architecture.json (full get_architecture)
68
- ARCH="$(memory_cli get_architecture "{\"project\":\"$PROJECT\",\"aspects\":[\"all\"]}" || echo '{}')"
69
- echo "$ARCH" | jq '.' > "$OUT/architecture.json"
70
-
71
- # 2. hotspots.jsonl (one object per line)
72
- "$TOOLS_DIR/hotspot-rank.sh" --repo "$REPO_ABS" 2>/dev/null \
73
- | jq -c '.hotspots[]?' > "$OUT/hotspots.jsonl" || true
74
-
75
- # 3. mermaid diagrams (best-effort; tools emit a stub + exit 2 if empty)
76
- "$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram module-deps > "$OUT/module-deps.mermaid" 2>/dev/null || true
77
- "$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram proto-map > "$OUT/proto-map.mermaid" 2>/dev/null || true
78
-
79
- # 4. schema.yaml metadata + gate for skill graph use
80
- NODES="$(echo "$ARCH" | jq -r '.total_nodes // 0')"
81
- EDGES="$(echo "$ARCH" | jq -r '.total_edges // 0')"
82
- HOTN="$(wc -l < "$OUT/hotspots.jsonl" | tr -d ' ')"
78
+ # Prune any stale fat-snapshot artifacts from a prior (pre-engine-only) run so a
79
+ # re-index migrates the repo to the thin model.
80
+ rm -f "$OUT/architecture.json" "$OUT/hotspots.jsonl" \
81
+ "$OUT/module-deps.mermaid" "$OUT/proto-map.mermaid" 2>/dev/null || true
82
+ rm -rf "$OUT/okf" 2>/dev/null || true
83
+
84
+ # schema.yaml provenance + gate. Counts are point-of-index provenance only;
85
+ # the live engine is authoritative.
86
+ STATUS_JSON="$(memory_cli index_status "{\"project\":\"$PROJECT\"}" || echo '{}')"
87
+ # Tolerate field-name variation AND non-JSON output across engine versions;
88
+ # counts are provenance only and must never abort the gate-marker write.
89
+ NODES="$(echo "$STATUS_JSON" | jq -r '.nodes // .node_count // .total_nodes // 0' 2>/dev/null || echo 0)"
90
+ EDGES="$(echo "$STATUS_JSON" | jq -r '.edges // .edge_count // .total_edges // 0' 2>/dev/null || echo 0)"
83
91
  VER="$("$MEMORY_BIN" --version 2>/dev/null | awk '{print $NF}' || echo unknown)"
84
92
  cat > "$OUT/schema.yaml" <<EOF
85
- # Draft knowledge-graph snapshotgenerated by scripts/tools/graph-snapshot.sh
86
- # Derived from the codebase-memory-mcp engine; git remains source of truth.
93
+ # Draft graph gate marker written by scripts/tools/graph-snapshot.sh
94
+ # Draft is engine-only: this file carries NO graph data. Its presence signals that
95
+ # the local codebase-memory-mcp engine is wired for this repo. Query the engine
96
+ # live via the graph-*.sh wrappers (or \`codebase-memory-mcp cli <tool>\`).
97
+ # Counts below are point-of-index provenance; the live engine is authoritative.
87
98
  engine: codebase-memory-mcp
88
99
  engine_version: "$VER"
89
100
  project: "$PROJECT"
90
- generated_at: "$(date -Iseconds 2>/dev/null || date)"
91
- nodes: $NODES
92
- edges: $EDGES
93
- hotspots: $HOTN
94
- artifacts:
95
- - architecture.json
96
- - hotspots.jsonl
97
- - module-deps.mermaid
98
- - proto-map.mermaid
101
+ generated_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
102
+ indexed_nodes: $NODES
103
+ indexed_edges: $EDGES
104
+ access: engine-live
99
105
  EOF
100
106
 
101
- echo "Snapshot written to $OUT (nodes=$NODES edges=$EDGES hotspots=$HOTN)"
107
+ echo "Indexed $PROJECT and wrote gate marker to $OUT/schema.yaml (nodes=$NODES edges=$EDGES)"
102
108
  exit 0
@@ -48,6 +48,8 @@ while [[ $# -gt 0 ]]; do
48
48
  esac
49
49
  done
50
50
 
51
+ [[ "$TOP" =~ ^[0-9]+$ ]] || { echo "ERROR: --top must be a non-negative integer" >&2; exit 1; }
52
+
51
53
  if [[ ! -d "$REPO" ]]; then
52
54
  echo "ERROR: --repo '$REPO' is not a directory" >&2
53
55
  exit 1
@@ -74,7 +74,7 @@ while IFS= read -r -d '' f; do
74
74
  # File-only (not symlink we already manage)
75
75
  if [[ -L "$f" ]]; then continue; fi
76
76
  newest="$base"
77
- done < <(find "$DIR" -maxdepth 1 -name "$pattern" -print0 | sort -z)
77
+ done < <(find "$DIR" -maxdepth 1 -name "$pattern" | sort | tr '\n' '\0')
78
78
 
79
79
  if [[ -z "$newest" ]]; then
80
80
  echo "No matching $pattern files in $DIR" >&2
@@ -105,7 +105,7 @@ while IFS= read -r -d '' file; do
105
105
  "$([[ -n "$track_id" ]] && echo "\"$(json_escape "$track_id")\"" || echo "null")" \
106
106
  "$(json_escape "$generated_at")" \
107
107
  "${crit:-0}" "${high:-0}" "${med:-0}" "${low:-0}" "${info:-0}"
108
- done < <(find "$ROOT" -type f -name '*-report-*.md' -print0 2>/dev/null | sort -z)
108
+ done < <(find "$ROOT" -type f -name '*-report-*.md' 2>/dev/null | sort | tr '\n' '\0')
109
109
 
110
110
  if $first; then
111
111
  printf ']\n'
@@ -19,6 +19,5 @@ status 1300
19
19
  bughunt 1200
20
20
  new-track 1100
21
21
  review 1000
22
- index 900
23
22
  implement 900
24
23
  decompose 700
@@ -177,7 +177,7 @@ emit() {
177
177
  if ((EMIT_JSON)); then
178
178
  printf '{"violation_count": %d, "violations": [\n' "$violation_count"
179
179
  local first=1 v track kind file line detail
180
- for v in "${violations[@]}"; do
180
+ for v in ${violations[@]+"${violations[@]}"}; do
181
181
  IFS='|' read -r track kind file line detail <<< "$v"
182
182
  if ((first)); then first=0; else printf ',\n'; fi
183
183
  printf ' {"track":"%s","kind":"%s","file":"%s","line":%s,"detail":"%s"}' \
@@ -192,7 +192,7 @@ emit() {
192
192
  printf 'ANCHORS: %d violation(s) across %d track(s).\n' \
193
193
  "$violation_count" "${#TRACK_PATHS[@]}" >&2
194
194
  local v track kind file line detail
195
- for v in "${violations[@]}"; do
195
+ for v in ${violations[@]+"${violations[@]}"}; do
196
196
  IFS='|' read -r track kind file line detail <<< "$v"
197
197
  printf ' [%s] %s/%s:%s — %s\n' "$kind" "$track" "$file" "$line" "$detail" >&2
198
198
  done
@@ -141,7 +141,7 @@ if [[ -d "$REPO/draft" ]]; then
141
141
  mkdir -p "$REPO/draft"
142
142
  cat > "$REPO/draft/.graph-binary-report.json" <<EOF
143
143
  {
144
- "detected_at": "$(date -Iseconds 2>/dev/null || date)",
144
+ "detected_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
145
145
  "engine_bin": "$(json_escape "$MEMORY_BIN")",
146
146
  "source": "$(json_escape "$SOURCE")",
147
147
  "arch": "$(json_escape "$ARCH")",