@drafthq/draft 3.0.0 → 3.2.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.
Files changed (62) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +3 -3
  4. package/bin/README.md +4 -7
  5. package/cli/src/hosts/claude-code.js +4 -1
  6. package/cli/src/installer.js +12 -5
  7. package/core/shared/condensation.md +8 -8
  8. package/core/shared/draft-context-loading.md +5 -9
  9. package/core/shared/graph-query.md +170 -33
  10. package/core/shared/graph-usage-report.md +1 -1
  11. package/core/shared/pattern-learning.md +2 -2
  12. package/core/shared/red-flags.md +3 -3
  13. package/core/templates/ai-context.md +1 -1
  14. package/core/templates/architecture.md +3 -3
  15. package/integrations/agents/AGENTS.md +299 -145
  16. package/integrations/copilot/.github/copilot-instructions.md +299 -145
  17. package/package.json +2 -1
  18. package/scripts/lib.sh +11 -3
  19. package/scripts/tools/_graph_queries.sh +102 -0
  20. package/scripts/tools/adr-index.sh +2 -2
  21. package/scripts/tools/check-scope-conflicts.sh +2 -2
  22. package/scripts/tools/check-skill-line-caps.sh +2 -2
  23. package/scripts/tools/cycle-detect.sh +22 -15
  24. package/scripts/tools/diff-templates-vs-tracks.sh +2 -2
  25. package/scripts/tools/fix-whitespace.sh +15 -9
  26. package/scripts/tools/graph-arch.sh +72 -0
  27. package/scripts/tools/graph-callers.sh +71 -20
  28. package/scripts/tools/graph-deps.sh +76 -0
  29. package/scripts/tools/graph-errors.sh +97 -0
  30. package/scripts/tools/graph-hierarchy.sh +89 -0
  31. package/scripts/tools/graph-impact.sh +1 -0
  32. package/scripts/tools/graph-init.sh +3 -3
  33. package/scripts/tools/graph-query.sh +124 -0
  34. package/scripts/tools/graph-risk.sh +81 -0
  35. package/scripts/tools/graph-search.sh +84 -0
  36. package/scripts/tools/graph-snapshot.sh +63 -50
  37. package/scripts/tools/graph-snippet.sh +92 -0
  38. package/scripts/tools/graph-tests.sh +112 -0
  39. package/scripts/tools/graph-traces.sh +83 -0
  40. package/scripts/tools/hotspot-rank.sh +44 -15
  41. package/scripts/tools/manage-symlinks.sh +1 -1
  42. package/scripts/tools/mermaid-from-graph.sh +31 -10
  43. package/scripts/tools/parse-reports.sh +1 -1
  44. package/scripts/tools/verify-doc-anchors.sh +2 -2
  45. package/scripts/tools/verify-graph-binary.sh +1 -1
  46. package/skills/GRAPH.md +2 -2
  47. package/skills/bughunt/SKILL.md +1 -1
  48. package/skills/debug/SKILL.md +3 -3
  49. package/skills/decompose/SKILL.md +5 -5
  50. package/skills/deep-review/SKILL.md +2 -2
  51. package/skills/deploy-checklist/SKILL.md +2 -2
  52. package/skills/graph/SKILL.md +1 -1
  53. package/skills/implement/SKILL.md +1 -1
  54. package/skills/init/SKILL.md +62 -42
  55. package/skills/init/references/architecture-spec.md +12 -11
  56. package/skills/learn/SKILL.md +5 -5
  57. package/skills/quick-review/SKILL.md +3 -3
  58. package/skills/review/SKILL.md +7 -7
  59. package/skills/tech-debt/SKILL.md +2 -2
  60. package/scripts/tools/okf-bundle.sh +0 -141
  61. package/scripts/tools/okf-check.sh +0 -137
  62. package/scripts/tools/okf-emit.sh +0 -161
@@ -1,19 +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
16
- # okf/ Open Knowledge Format bundle (portable markdown mirror)
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.
17
21
  #
18
22
  # Usage: scripts/tools/graph-snapshot.sh [--repo DIR] [--out DIR]
19
23
  # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
@@ -28,17 +32,21 @@ OUT_DIR=""
28
32
 
29
33
  usage() {
30
34
  cat <<'EOF'
31
- 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.
32
40
 
33
41
  Usage:
34
42
  scripts/tools/graph-snapshot.sh [--repo DIR] [--out DIR]
35
43
 
36
44
  Flags:
37
45
  --repo DIR Repository root (default: cwd).
38
- --out DIR Snapshot dir (default: <repo>/draft/graph).
46
+ --out DIR Gate-marker dir (default: <repo>/draft/graph).
39
47
  --help Show this help.
40
48
 
41
- 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).
42
50
  EOF
43
51
  }
44
52
 
@@ -57,51 +65,56 @@ REPO_ABS="$(cd "$REPO" && pwd)"
57
65
  SELF_REPO="$(cd "$TOOLS_DIR/../.." && pwd)"
58
66
  OUT="${OUT_DIR:-$REPO_ABS/draft/graph}"
59
67
 
60
- find_memory_bin "$REPO_ABS" "$SELF_REPO" || { echo "graph engine unavailable — no snapshot written" >&2; exit 2; }
61
- 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; }
62
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.
63
73
  PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
64
- [[ -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; }
65
75
 
66
76
  mkdir -p "$OUT"
67
77
 
68
- # 1. architecture.json (full get_architecture)
69
- ARCH="$(memory_cli get_architecture "{\"project\":\"$PROJECT\",\"aspects\":[\"all\"]}" || echo '{}')"
70
- echo "$ARCH" | jq '.' > "$OUT/architecture.json"
71
-
72
- # 2. hotspots.jsonl (one object per line)
73
- "$TOOLS_DIR/hotspot-rank.sh" --repo "$REPO_ABS" 2>/dev/null \
74
- | jq -c '.hotspots[]?' > "$OUT/hotspots.jsonl" || true
75
-
76
- # 3. mermaid diagrams (best-effort; tools emit a stub + exit 2 if empty)
77
- "$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram module-deps > "$OUT/module-deps.mermaid" 2>/dev/null || true
78
- "$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram proto-map > "$OUT/proto-map.mermaid" 2>/dev/null || true
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)"
91
+ VER="$("$MEMORY_BIN" --version 2>/dev/null | awk '{print $NF}' || echo unknown)"
79
92
 
80
- # 4. OKF bundle (best-effort) portable Open Knowledge Format mirror of the graph.
81
- "$TOOLS_DIR/okf-emit.sh" --repo "$REPO_ABS" --snapshot "$OUT" --out "$OUT/okf" >/dev/null 2>&1 || true
93
+ # Incremental-refresh provenance (graph-tooling-v2 Phase 5): the engine indexes
94
+ # incrementally (content-based, git-aware), so re-indexing only touches changed
95
+ # files. detect_changes reports that working-tree delta — recorded as provenance
96
+ # and echoed so a refresh shows what moved. Best-effort: never aborts the write.
97
+ CHANGES_JSON="$(memory_cli detect_changes "{\"project\":\"$PROJECT\"}" 2>/dev/null || echo '{}')"
98
+ echo "$CHANGES_JSON" | jq -e . >/dev/null 2>&1 || CHANGES_JSON='{}'
99
+ CHANGED_FILES="$(echo "$CHANGES_JSON" | jq -r '.changed_count // (.changed_files | length?) // 0' 2>/dev/null || echo 0)"
100
+ IMPACTED="$(echo "$CHANGES_JSON" | jq -r '(.impacted_symbols | length?) // 0' 2>/dev/null || echo 0)"
82
101
 
83
- # 5. schema.yaml — metadata + gate for skill graph use
84
- NODES="$(echo "$ARCH" | jq -r '.total_nodes // 0')"
85
- EDGES="$(echo "$ARCH" | jq -r '.total_edges // 0')"
86
- HOTN="$(wc -l < "$OUT/hotspots.jsonl" | tr -d ' ')"
87
- VER="$("$MEMORY_BIN" --version 2>/dev/null | awk '{print $NF}' || echo unknown)"
88
102
  cat > "$OUT/schema.yaml" <<EOF
89
- # Draft knowledge-graph snapshotgenerated by scripts/tools/graph-snapshot.sh
90
- # Derived from the codebase-memory-mcp engine; git remains source of truth.
103
+ # Draft graph gate marker written by scripts/tools/graph-snapshot.sh
104
+ # Draft is engine-only: this file carries NO graph data. Its presence signals that
105
+ # the local codebase-memory-mcp engine is wired for this repo. Query the engine
106
+ # live via the graph-*.sh wrappers (or \`codebase-memory-mcp cli <tool>\`).
107
+ # Counts below are point-of-index provenance; the live engine is authoritative.
91
108
  engine: codebase-memory-mcp
92
109
  engine_version: "$VER"
93
110
  project: "$PROJECT"
94
- generated_at: "$(date -Iseconds 2>/dev/null || date)"
95
- nodes: $NODES
96
- edges: $EDGES
97
- hotspots: $HOTN
98
- artifacts:
99
- - architecture.json
100
- - hotspots.jsonl
101
- - module-deps.mermaid
102
- - proto-map.mermaid
103
- - okf/
111
+ generated_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
112
+ indexed_nodes: $NODES
113
+ indexed_edges: $EDGES
114
+ changed_files: $CHANGED_FILES
115
+ impacted_symbols: $IMPACTED
116
+ access: engine-live
104
117
  EOF
105
118
 
106
- echo "Snapshot written to $OUT (nodes=$NODES edges=$EDGES hotspots=$HOTN)"
119
+ echo "Indexed $PROJECT and wrote gate marker to $OUT/schema.yaml (nodes=$NODES edges=$EDGES, changed_files=$CHANGED_FILES impacted_symbols=$IMPACTED)"
107
120
  exit 0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # graph-snippet.sh — verified source + caller/callee counts for a symbol.
3
+ #
4
+ # Wraps the engine's get_code_snippet (graph-tooling-v2 Phase 3). Replaces the
5
+ # grep-then-Read dance with the engine's own attributed source plus its
6
+ # pre-computed caller/callee counts and loop-depth risk signal.
7
+ #
8
+ # Usage:
9
+ # scripts/tools/graph-snippet.sh --repo DIR --qualified NAME
10
+ #
11
+ # NAME is a fully qualified_name (e.g. pkg.module.Class.method). Use
12
+ # graph-search.sh or `graph-arch.sh | jq` to discover qualified names.
13
+ #
14
+ # Output: JSON {qualified_name, file, start_line, end_line, callers, callees,
15
+ # transitive_loop_depth, complexity, code, status, source}.
16
+ # status = "ok" | "no-match" | "unavailable"
17
+ #
18
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
19
+ set -euo pipefail
20
+
21
+ # shellcheck source=_graph_queries.sh
22
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
23
+
24
+ REPO="."
25
+ QNAME=""
26
+
27
+ usage() {
28
+ cat <<'EOF'
29
+ graph-snippet.sh — verified source + caller/callee counts for a symbol.
30
+
31
+ Usage:
32
+ scripts/tools/graph-snippet.sh --repo DIR --qualified NAME
33
+
34
+ Flags:
35
+ --repo DIR Repository root (default: cwd).
36
+ --qualified NAME Fully qualified symbol name (required).
37
+ --help Show this help.
38
+
39
+ Output: JSON {qualified_name, file, start_line, end_line, callers, callees,
40
+ transitive_loop_depth, complexity, code, status, source}. Exit 2 when engine
41
+ unavailable.
42
+ EOF
43
+ }
44
+
45
+ while [[ $# -gt 0 ]]; do
46
+ case "$1" in
47
+ --repo) REPO="$2"; shift 2;;
48
+ --qualified|--symbol) QNAME="$2"; shift 2;;
49
+ --help|-h) usage; exit 0;;
50
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
51
+ esac
52
+ done
53
+
54
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
55
+ [[ -n "$QNAME" ]] || { echo "ERROR: --qualified is required" >&2; usage >&2; exit 1; }
56
+
57
+ REPO_ABS="$(cd "$REPO" && pwd)"
58
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
59
+
60
+ unavailable() {
61
+ jq -n --arg q "$QNAME" '{qualified_name:$q, status:"unavailable", source:"unavailable"}' 2>/dev/null \
62
+ || echo '{"status":"unavailable","source":"unavailable"}'
63
+ exit 2
64
+ }
65
+
66
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
67
+ command -v jq >/dev/null 2>&1 || unavailable
68
+
69
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
70
+ [[ -n "$PROJECT" ]] || unavailable
71
+
72
+ ARGS="$(jq -n --arg p "$PROJECT" --arg q "$QNAME" '{project:$p, qualified_name:$q}')"
73
+ RES="$(memory_cli get_code_snippet "$ARGS" 2>/dev/null || true)"
74
+ [[ -n "$RES" ]] || unavailable
75
+ echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable
76
+
77
+ # The engine field `source` carries the code text; rename to `code` to avoid
78
+ # colliding with our provenance `source`. no-match when the engine returns an
79
+ # object without a qualified_name/source for the requested symbol.
80
+ echo "$RES" | jq --arg q "$QNAME" '
81
+ (.qualified_name // .name // null) as $found
82
+ | {qualified_name: ($found // $q),
83
+ file: (.file_path // ""),
84
+ start_line: (.start_line // null),
85
+ end_line: (.end_line // null),
86
+ callers: (.callers // null),
87
+ callees: (.callees // null),
88
+ transitive_loop_depth: (.transitive_loop_depth // null),
89
+ complexity: (.complexity // null),
90
+ code: (.source // ""),
91
+ status: (if ($found != null or (.source // "") != "") then "ok" else "no-match" end),
92
+ source: "memory-graph"}'
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # graph-tests.sh — test→symbol coverage from the knowledge graph (TESTS edges).
3
+ #
4
+ # graph-tooling-v2 Phase 3. Two modes:
5
+ # --symbol NAME tests that cover a symbol (who tests this?).
6
+ # --untested exported symbols with NO TESTS edge (what's untested?).
7
+ #
8
+ # The engine dialect has no anti-join (NOT EXISTS / NOT(pattern) are rejected), so
9
+ # --untested is computed as a set difference in jq: exported symbols minus the
10
+ # set of TESTS targets. Honest about partiality — TESTS coverage depends on the
11
+ # engine resolving test→symbol links, which varies by language/framework.
12
+ #
13
+ # Usage:
14
+ # scripts/tools/graph-tests.sh --repo DIR --symbol NAME
15
+ # scripts/tools/graph-tests.sh --repo DIR --untested
16
+ #
17
+ # Output (--symbol): {symbol, tests:[{test,file}], status, source}
18
+ # Output (--untested): {untested:[{symbol,file}], total, truncated, source}
19
+ #
20
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
21
+ set -euo pipefail
22
+
23
+ # shellcheck source=_graph_queries.sh
24
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
25
+
26
+ REPO="."
27
+ SYMBOL=""
28
+ UNTESTED=0
29
+
30
+ usage() {
31
+ cat <<'EOF'
32
+ graph-tests.sh — test coverage edges from the knowledge graph.
33
+
34
+ Usage:
35
+ scripts/tools/graph-tests.sh --repo DIR --symbol NAME
36
+ scripts/tools/graph-tests.sh --repo DIR --untested
37
+
38
+ Flags:
39
+ --repo DIR Repository root (default: cwd).
40
+ --symbol NAME List tests covering this symbol.
41
+ --untested List exported symbols with no TESTS edge.
42
+ --help Show this help.
43
+
44
+ Output: --symbol → {symbol, tests, status, source}; --untested →
45
+ {untested, total, truncated, source}. Exit 2 when engine unavailable.
46
+ EOF
47
+ }
48
+
49
+ while [[ $# -gt 0 ]]; do
50
+ case "$1" in
51
+ --repo) REPO="$2"; shift 2;;
52
+ --symbol) SYMBOL="$2"; shift 2;;
53
+ --untested) UNTESTED=1; shift;;
54
+ --help|-h) usage; exit 0;;
55
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
56
+ esac
57
+ done
58
+
59
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
60
+ [[ -n "$SYMBOL" || "$UNTESTED" -eq 1 ]] || { echo "ERROR: provide --symbol or --untested" >&2; usage >&2; exit 1; }
61
+
62
+ REPO_ABS="$(cd "$REPO" && pwd)"
63
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
64
+
65
+ unavailable() {
66
+ if [[ "$UNTESTED" -eq 1 ]]; then
67
+ echo '{"untested":[],"total":0,"source":"unavailable"}'
68
+ else
69
+ jq -n --arg s "$SYMBOL" '{symbol:$s, tests:[], status:"unavailable", source:"unavailable"}' 2>/dev/null \
70
+ || echo '{"tests":[],"status":"unavailable","source":"unavailable"}'
71
+ fi
72
+ exit 2
73
+ }
74
+
75
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
76
+ command -v jq >/dev/null 2>&1 || unavailable
77
+
78
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
79
+ [[ -n "$PROJECT" ]] || unavailable
80
+
81
+ if [[ "$UNTESTED" -eq 1 ]]; then
82
+ EXP="$(gq_run "$PROJECT" "$(gq_q_exported)" || true)"
83
+ [[ -n "$EXP" ]] || unavailable
84
+ TST="$(gq_run "$PROJECT" "$(gq_q_tested_all)" || echo '{"rows":[]}')"
85
+ echo "$TST" | jq -e . >/dev/null 2>&1 || TST='{"rows":[]}'
86
+ # Pass results via temp files, not argv: the exported set can exceed the
87
+ # ARG_MAX limit for --argjson on large repos.
88
+ TMP_EXP="$(mktemp)"; TMP_TST="$(mktemp)"
89
+ trap 'rm -f "$TMP_EXP" "$TMP_TST"' EXIT
90
+ printf '%s' "$EXP" > "$TMP_EXP"
91
+ printf '%s' "$TST" > "$TMP_TST"
92
+ jq -n --slurpfile exp "$TMP_EXP" --slurpfile tst "$TMP_TST" '
93
+ ($exp[0] // {}) as $e | ($tst[0] // {}) as $t
94
+ | ((($t.rows) // []) | map(.[0])) as $tested
95
+ | [ (($e.rows) // [])[]
96
+ | select(((.[0]) as $q | $tested | index($q)) | not)
97
+ | {symbol:.[0], file:.[1]} ] as $u
98
+ | {untested: $u, total: ($u | length),
99
+ truncated: (((($e.rows) // []) | length) >= 2000),
100
+ source:"memory-graph"}'
101
+ exit 0
102
+ fi
103
+
104
+ SYM_ESC="$(gq_escape "$SYMBOL")"
105
+ RES="$(gq_run "$PROJECT" "$(gq_q_tests "$SYM_ESC")" || true)"
106
+ [[ -n "$RES" ]] || unavailable
107
+ STATUS="$(gq_symbol_status "$PROJECT" "$SYM_ESC" "$RES")"
108
+
109
+ echo "$RES" | jq --arg s "$SYMBOL" --arg st "$STATUS" '
110
+ {symbol:$s,
111
+ tests: [ (.rows // [])[] | {test:.[0], file:.[1]} ],
112
+ status:$st, source:"memory-graph"}'
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # graph-traces.sh — fold runtime call traces into the knowledge graph (EXPERIMENTAL).
3
+ #
4
+ # graph-tooling-v2 Phase 6. Wraps the engine's ingest_traces to close the
5
+ # static/dynamic gap — dynamic dispatch the static graph misses (e.g. closures,
6
+ # reflection, virtual calls). This is a WRITE path and is gated behind
7
+ # --experimental. NOTE: in engine v0.8.x ingest_traces is accepted but runtime
8
+ # edge creation is "not yet implemented" — the engine returns its status verbatim.
9
+ #
10
+ # Usage:
11
+ # scripts/tools/graph-traces.sh ingest --repo DIR --file TRACES.json --experimental
12
+ #
13
+ # TRACES.json is a JSON array of trace records (engine-defined shape).
14
+ #
15
+ # Output: the engine's raw ingest_traces JSON; {"source":"unavailable"} (exit 2)
16
+ # when the engine is unavailable; exit 1 on invocation error or missing
17
+ # --experimental.
18
+ set -euo pipefail
19
+
20
+ # shellcheck source=_graph_queries.sh
21
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
22
+
23
+ REPO="."
24
+ FILE=""
25
+ ACTION=""
26
+ EXPERIMENTAL=0
27
+
28
+ usage() {
29
+ cat <<'EOF'
30
+ graph-traces.sh — fold runtime traces into the graph (EXPERIMENTAL, write path).
31
+
32
+ Usage:
33
+ scripts/tools/graph-traces.sh ingest --repo DIR --file TRACES.json --experimental
34
+
35
+ Flags:
36
+ ingest The action (currently the only one).
37
+ --repo DIR Repository root (default: cwd).
38
+ --file PATH JSON array of trace records (required for ingest).
39
+ --experimental Required acknowledgement — this is a write/experimental path.
40
+ --help Show this help.
41
+
42
+ NOTE: engine v0.8.x accepts traces but runtime edge creation is not yet
43
+ implemented; the engine's status is returned verbatim.
44
+
45
+ Output: raw engine JSON; {"source":"unavailable"} (exit 2) when unavailable.
46
+ EOF
47
+ }
48
+
49
+ while [[ $# -gt 0 ]]; do
50
+ case "$1" in
51
+ ingest) ACTION="ingest"; shift;;
52
+ --repo) REPO="$2"; shift 2;;
53
+ --file) FILE="$2"; shift 2;;
54
+ --experimental) EXPERIMENTAL=1; shift;;
55
+ --help|-h) usage; exit 0;;
56
+ *) echo "Unknown argument: $1" >&2; usage >&2; exit 1;;
57
+ esac
58
+ done
59
+
60
+ [[ "$ACTION" == "ingest" ]] || { echo "ERROR: action 'ingest' is required" >&2; usage >&2; exit 1; }
61
+ [[ "$EXPERIMENTAL" -eq 1 ]] || { echo "ERROR: --experimental is required (this writes to the graph)" >&2; exit 1; }
62
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
63
+ [[ -n "$FILE" ]] || { echo "ERROR: --file is required" >&2; usage >&2; exit 1; }
64
+ [[ -f "$FILE" ]] || { echo "ERROR: --file '$FILE' does not exist" >&2; exit 1; }
65
+
66
+ REPO_ABS="$(cd "$REPO" && pwd)"
67
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
68
+
69
+ unavailable() { echo '{"source":"unavailable"}'; exit 2; }
70
+
71
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
72
+ command -v jq >/dev/null 2>&1 || unavailable
73
+
74
+ jq -e . "$FILE" >/dev/null 2>&1 || { echo "ERROR: --file is not valid JSON" >&2; exit 1; }
75
+
76
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
77
+ [[ -n "$PROJECT" ]] || unavailable
78
+
79
+ ARGS="$(jq -n --arg p "$PROJECT" --slurpfile t "$FILE" '{project:$p, traces:($t[0])}')"
80
+ RES="$(memory_cli ingest_traces "$ARGS" 2>/dev/null || true)"
81
+ [[ -n "$RES" ]] || unavailable
82
+ echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable
83
+ printf '%s\n' "$RES"
@@ -1,27 +1,33 @@
1
1
  #!/usr/bin/env bash
2
- # hotspot-rank.sh — emit complexity/fan-in-ranked symbols from the knowledge graph.
2
+ # hotspot-rank.sh — complexity-weighted hotspot ranking from the knowledge graph.
3
3
  #
4
- # Backed by the codebase-memory-mcp engine (get_architecture, server-computed
5
- # hotspot ranking by fan-in). Indexes the repo on demand if needed.
4
+ # Backed by the codebase-memory-mcp engine. Fan-in alone is skewed by
5
+ # name-collision generics (e.g. a logger `info` with fan_in 1022 but complexity 0),
6
+ # so graph-tooling-v2 Phase 4 blends the engine's pre-computed complexity and
7
+ # cognitive scores into the rank: score = fanIn + complexity + cognitive. Entry
8
+ # points are annotated. fan_in still comes from the engine's server-computed
9
+ # hotspot ranking; the per-symbol complexity/cognitive/is_entry_point come from a
10
+ # node-property query and are merged in.
6
11
  #
7
12
  # Usage:
8
13
  # scripts/tools/hotspot-rank.sh [--repo DIR] [--top N]
9
14
  #
10
- # Output: JSON {hotspots:[{id, name, fanIn}], source}.
15
+ # Output: JSON {hotspots:[{id, name, fanIn, complexity, cognitive, score,
16
+ # isEntryPoint}], source}.
11
17
  # source = "memory-graph" | "unavailable"
12
18
  #
13
19
  # Exit codes: 0 OK, 1 invocation error, 2 graph engine/data unavailable.
14
20
  set -euo pipefail
15
21
 
16
- # shellcheck source=_lib.sh
17
- source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
22
+ # shellcheck source=_graph_queries.sh
23
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
18
24
 
19
25
  REPO="."
20
26
  TOP=0
21
27
 
22
28
  usage() {
23
29
  cat <<'EOF'
24
- hotspot-rank.sh — fan-in-ranked symbols from the knowledge graph.
30
+ hotspot-rank.sh — complexity-weighted hotspot ranking from the knowledge graph.
25
31
 
26
32
  Usage:
27
33
  scripts/tools/hotspot-rank.sh [--repo DIR] [--top N]
@@ -31,8 +37,8 @@ Flags:
31
37
  --top N Keep only top N hotspots (default: 0 = all).
32
38
  --help Show this help.
33
39
 
34
- Output: JSON {hotspots:[{id, name, fanIn}], source}.
35
- source = "memory-graph" | "unavailable"
40
+ Output: JSON {hotspots:[{id, name, fanIn, complexity, cognitive, score,
41
+ isEntryPoint}], source}. Ranked by score = fanIn + complexity + cognitive.
36
42
 
37
43
  Exit 0 with results, exit 2 with {"hotspots":[],"source":"unavailable"} when the
38
44
  graph engine is unavailable.
@@ -48,6 +54,8 @@ while [[ $# -gt 0 ]]; do
48
54
  esac
49
55
  done
50
56
 
57
+ [[ "$TOP" =~ ^[0-9]+$ ]] || { echo "ERROR: --top must be a non-negative integer" >&2; exit 1; }
58
+
51
59
  if [[ ! -d "$REPO" ]]; then
52
60
  echo "ERROR: --repo '$REPO' is not a directory" >&2
53
61
  exit 1
@@ -66,10 +74,31 @@ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
66
74
 
67
75
  ARCH_JSON="$(memory_cli get_architecture "{\"project\":\"$PROJECT\",\"aspects\":[\"hotspots\"]}" || true)"
68
76
  [[ -n "$ARCH_JSON" ]] || unavailable
77
+ echo "$ARCH_JSON" | jq -e . >/dev/null 2>&1 || unavailable
78
+
79
+ # Pre-computed complexity/cognitive/is_entry_point per symbol (merged onto fan-in).
80
+ PROPS_JSON="$(gq_run "$PROJECT" "$(gq_q_node_props)" || echo '{"rows":[]}')"
81
+ echo "$PROPS_JSON" | jq -e . >/dev/null 2>&1 || PROPS_JSON='{"rows":[]}'
82
+
83
+ # Merge via temp files (the props row set can exceed argv limits on large repos).
84
+ TMP_ARCH="$(mktemp)"; TMP_PROPS="$(mktemp)"
85
+ trap 'rm -f "$TMP_ARCH" "$TMP_PROPS"' EXIT
86
+ printf '%s' "$ARCH_JSON" > "$TMP_ARCH"
87
+ printf '%s' "$PROPS_JSON" > "$TMP_PROPS"
69
88
 
70
- echo "$ARCH_JSON" | jq --argjson top "$TOP" '
71
- {
72
- hotspots: ([ (.hotspots // [])[] | {id: .qualified_name, name: .name, fanIn: .fan_in} ]
73
- | if $top > 0 then .[0:$top] else . end),
74
- source: "memory-graph"
75
- }'
89
+ jq -n --slurpfile arch "$TMP_ARCH" --slurpfile props "$TMP_PROPS" --argjson top "$TOP" '
90
+ (($props[0].rows) // []) as $prows
91
+ | (reduce $prows[] as $r ({};
92
+ .[$r[0]] = {c:((($r[1]) // "0") | tonumber? // 0),
93
+ cog:((($r[2]) // "0") | tonumber? // 0),
94
+ ep:((($r[3]) | tostring) == "true")})) as $pmap
95
+ | [ (($arch[0].hotspots) // [])[]
96
+ | (.qualified_name) as $q
97
+ | ($pmap[$q] // {c:0, cog:0, ep:false}) as $p
98
+ | {id:$q, name:.name, fanIn:(.fan_in // 0),
99
+ complexity:$p.c, cognitive:$p.cog,
100
+ score:((.fan_in // 0) + $p.c + $p.cog),
101
+ isEntryPoint:$p.ep} ]
102
+ | sort_by(-.score)
103
+ | (if $top > 0 then .[0:$top] else . end) as $h
104
+ | {hotspots:$h, source:"memory-graph"}'
@@ -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
@@ -1,21 +1,27 @@
1
1
  #!/usr/bin/env bash
2
2
  # mermaid-from-graph.sh — emit Mermaid diagrams from the knowledge graph.
3
3
  #
4
- # Backed by the codebase-memory-mcp engine. Two diagrams:
5
- # module-deps : file co-change coupling (FILE_CHANGES_WITH edges) as a flowchart.
4
+ # Backed by the codebase-memory-mcp engine. Diagrams:
5
+ # module-deps : real file/module dependency graph (IMPORTS edges) as a flowchart.
6
+ # This is the auto-derived dependency diagram for architecture.md
7
+ # §9 (graph-tooling-v2 Phase 4) — it replaces the prior co-change
8
+ # proxy with actual import edges.
9
+ # co-change : file co-change coupling (FILE_CHANGES_WITH edges) — the hidden,
10
+ # git-history dependency proxy (the prior module-deps behavior,
11
+ # still available explicitly).
6
12
  # proto-map : detected service routes (Route nodes) as a flowchart.
7
13
  #
8
14
  # When the engine is unavailable, emits an empty diagram stub and exits 2 so
9
15
  # consuming skills can degrade gracefully.
10
16
  #
11
17
  # Usage:
12
- # scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|proto-map]
18
+ # scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|co-change|proto-map]
13
19
  #
14
20
  # Exit codes: 0 OK, 1 invocation error, 2 graph engine/data unavailable.
15
21
  set -euo pipefail
16
22
 
17
- # shellcheck source=_lib.sh
18
- source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
23
+ # shellcheck source=_graph_queries.sh
24
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
19
25
 
20
26
  REPO="."
21
27
  DIAGRAM="module-deps"
@@ -25,11 +31,12 @@ usage() {
25
31
  mermaid-from-graph.sh — emit Mermaid diagrams from the knowledge graph.
26
32
 
27
33
  Usage:
28
- scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|proto-map]
34
+ scripts/tools/mermaid-from-graph.sh [--repo DIR] [--diagram module-deps|co-change|proto-map]
29
35
 
30
36
  Flags:
31
37
  --repo DIR Repository root (default: cwd).
32
- --diagram NAME module-deps (default) or proto-map.
38
+ --diagram NAME module-deps (default, IMPORTS edges), co-change
39
+ (FILE_CHANGES_WITH coupling), or proto-map (routes).
33
40
  --help Show this help.
34
41
 
35
42
  Exit 0 with diagram output, exit 2 with an empty stub when the engine is unavailable.
@@ -70,9 +77,22 @@ command -v jq >/dev/null 2>&1 || stub
70
77
  PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
71
78
  [[ -n "$PROJECT" ]] || stub
72
79
 
80
+ # module-deps: real IMPORTS edges (the auto-derived dependency graph). Self-imports
81
+ # (src == dst) are dropped so the diagram is a true cross-file graph. Capped at 40
82
+ # edges for readability.
73
83
  render_module_deps() {
74
- local q="MATCH (a:File)-[r:FILE_CHANGES_WITH]->(b:File) RETURN a.name AS src, b.name AS dst, r.coupling_score AS score ORDER BY r.coupling_score DESC LIMIT 40"
75
- local res; res="$(memory_cli query_graph "{\"project\":\"$PROJECT\",\"query\":\"$q\"}" || echo '{}')"
84
+ local res; res="$(gq_run "$PROJECT" "$(gq_q_imports)" || echo '{}')"
85
+ local edges; edges="$(echo "${res:-{\}}" | jq -r '
86
+ [ (.rows // [])[] | {s:(.[0]|tostring), d:(.[1]|tostring)}
87
+ | select(.s != "" and .d != "" and .s != .d) ]
88
+ | unique | .[0:40][] | " \"" + .s + "\" --> \"" + .d + "\""' 2>/dev/null || true)"
89
+ if [[ -z "$edges" ]]; then return 1; fi
90
+ printf '```mermaid\nflowchart LR\n%s\n```\n' "$edges"
91
+ }
92
+
93
+ # co-change: FILE_CHANGES_WITH coupling (the prior module-deps proxy).
94
+ render_co_change() {
95
+ local res; res="$(gq_run "$PROJECT" "$(gq_q_co_change)" || echo '{}')"
76
96
  local edges; edges="$(echo "${res:-{\}}" | jq -r '(.rows // [])[] | " \"" + (.[0]|tostring) + "\" --> \"" + (.[1]|tostring) + "\""' 2>/dev/null || true)"
77
97
  if [[ -z "$edges" ]]; then return 1; fi
78
98
  printf '```mermaid\nflowchart LR\n%s\n```\n' "$edges"
@@ -87,6 +107,7 @@ render_proto_map() {
87
107
 
88
108
  case "$DIAGRAM" in
89
109
  module-deps) render_module_deps || stub ;;
110
+ co-change) render_co_change || stub ;;
90
111
  proto-map) render_proto_map || stub ;;
91
- *) echo "Unknown --diagram '$DIAGRAM' (expected module-deps|proto-map)" >&2; exit 1 ;;
112
+ *) echo "Unknown --diagram '$DIAGRAM' (expected module-deps|co-change|proto-map)" >&2; exit 1 ;;
92
113
  esac
@@ -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'