@drafthq/draft 3.1.5 → 3.2.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 (49) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/cli/src/hosts/claude-code.js +4 -1
  4. package/cli/src/installer.js +20 -5
  5. package/cli/src/lib/marker.js +93 -0
  6. package/core/shared/condensation.md +3 -2
  7. package/core/shared/git-report-metadata.md +3 -2
  8. package/core/shared/graph-query.md +140 -8
  9. package/core/shared/tool-resolver.md +71 -4
  10. package/core/templates/plan.md +3 -2
  11. package/integrations/agents/AGENTS.md +481 -106
  12. package/integrations/copilot/.github/copilot-instructions.md +481 -106
  13. package/package.json +2 -1
  14. package/scripts/lib.sh +11 -0
  15. package/scripts/tools/_graph_queries.sh +102 -0
  16. package/scripts/tools/cycle-detect.sh +18 -15
  17. package/scripts/tools/graph-callers.sh +71 -20
  18. package/scripts/tools/graph-deps.sh +76 -0
  19. package/scripts/tools/graph-errors.sh +97 -0
  20. package/scripts/tools/graph-hierarchy.sh +89 -0
  21. package/scripts/tools/graph-query.sh +124 -0
  22. package/scripts/tools/graph-risk.sh +81 -0
  23. package/scripts/tools/graph-search.sh +84 -0
  24. package/scripts/tools/graph-snapshot.sh +13 -1
  25. package/scripts/tools/graph-snippet.sh +92 -0
  26. package/scripts/tools/graph-tests.sh +112 -0
  27. package/scripts/tools/graph-traces.sh +83 -0
  28. package/scripts/tools/hotspot-rank.sh +43 -16
  29. package/scripts/tools/mermaid-from-graph.sh +31 -10
  30. package/scripts/tools/resolve-tools.sh +78 -0
  31. package/skills/adr/SKILL.md +3 -2
  32. package/skills/bughunt/SKILL.md +10 -1
  33. package/skills/coverage/SKILL.md +8 -3
  34. package/skills/debug/SKILL.md +16 -5
  35. package/skills/decompose/SKILL.md +29 -12
  36. package/skills/deep-review/SKILL.md +19 -6
  37. package/skills/deploy-checklist/SKILL.md +6 -5
  38. package/skills/graph/SKILL.md +15 -6
  39. package/skills/impact/SKILL.md +12 -1
  40. package/skills/implement/SKILL.md +20 -4
  41. package/skills/init/SKILL.md +17 -10
  42. package/skills/init/references/architecture-spec.md +17 -6
  43. package/skills/learn/SKILL.md +15 -4
  44. package/skills/quick-review/SKILL.md +13 -3
  45. package/skills/review/SKILL.md +32 -8
  46. package/skills/standup/SKILL.md +3 -2
  47. package/skills/status/SKILL.md +3 -2
  48. package/skills/tech-debt/SKILL.md +20 -6
  49. package/skills/upload/SKILL.md +3 -2
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env bash
2
+ # graph-query.sh — generic read-only escape hatch to the knowledge-graph engine.
3
+ #
4
+ # The highest-leverage single addition (graph-tooling-v2 Phase 2): unlocks all 20
5
+ # edge types and ~30 pre-computed node properties with no new wrapper. Two modes:
6
+ #
7
+ # --cypher 'MATCH ...' run arbitrary read-only openCypher (write verbs
8
+ # are rejected before the engine ever sees them).
9
+ # --tool NAME --json '{...}' passthrough to any read-only engine tool
10
+ # (get_code_snippet, search_graph, get_graph_schema,
11
+ # trace_path, …). Destructive tools are rejected.
12
+ #
13
+ # Dialect limits (engine v0.8.x — see _graph_queries.sh for the full list):
14
+ # SAFE : fixed-length patterns, `=`, `<`, `STARTS WITH`, `NOT x STARTS WITH`,
15
+ # `AND`, `OR`, rel-type alternation `[:A|B]`, `count(x)`.
16
+ # UNSAFE : coalesce(), `<>`/`!=`/`<=`/`>=`, NOT EXISTS(...), NOT (pattern),
17
+ # WITH-grouping aggregation, multi-pattern joins.
18
+ # Passthrough returns the engine's raw error, not a silent empty result.
19
+ #
20
+ # Usage:
21
+ # scripts/tools/graph-query.sh --repo DIR --cypher 'MATCH (n) RETURN n LIMIT 5'
22
+ # scripts/tools/graph-query.sh --repo DIR --tool get_graph_schema --json '{}'
23
+ #
24
+ # Output: the raw engine JSON on success; {"source":"unavailable"} (exit 2) when
25
+ # the engine is unavailable. Exit 1 on invocation error / rejected write.
26
+ set -euo pipefail
27
+
28
+ # shellcheck source=_graph_queries.sh
29
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
30
+
31
+ REPO="."
32
+ CYPHER=""
33
+ TOOL=""
34
+ TOOL_JSON="{}"
35
+
36
+ usage() {
37
+ cat <<'EOF'
38
+ graph-query.sh — generic read-only passthrough to the knowledge-graph engine.
39
+
40
+ Usage:
41
+ scripts/tools/graph-query.sh --repo DIR --cypher 'MATCH ... RETURN ...'
42
+ scripts/tools/graph-query.sh --repo DIR --tool NAME [--json '{...}']
43
+
44
+ Flags:
45
+ --repo DIR Repository root (default: cwd).
46
+ --cypher STR Read-only openCypher query (write verbs CREATE/MERGE/DELETE/SET/
47
+ REMOVE/DROP/DETACH are rejected). The {project} is injected.
48
+ --tool NAME Engine tool to call (read-only allowlist). Combine with --json.
49
+ --json STR JSON args for --tool (the project is injected if absent).
50
+ --help Show this help.
51
+
52
+ Dialect: avoid coalesce(), <>, NOT EXISTS, NOT(pattern), WITH-aggregation,
53
+ multi-pattern joins. Use =, <, STARTS WITH, AND/OR, [:A|B] alternation.
54
+
55
+ Output: raw engine JSON on success; {"source":"unavailable"} (exit 2) when the
56
+ engine is unavailable; exit 1 on invocation error or a rejected write verb.
57
+ EOF
58
+ }
59
+
60
+ while [[ $# -gt 0 ]]; do
61
+ case "$1" in
62
+ --repo) REPO="$2"; shift 2;;
63
+ --cypher) CYPHER="$2"; shift 2;;
64
+ --tool) TOOL="$2"; shift 2;;
65
+ --json) TOOL_JSON="$2"; shift 2;;
66
+ --help|-h) usage; exit 0;;
67
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
68
+ esac
69
+ done
70
+
71
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
72
+ if [[ -n "$CYPHER" && -n "$TOOL" ]]; then
73
+ echo "ERROR: use either --cypher or --tool, not both" >&2; exit 1
74
+ fi
75
+ if [[ -z "$CYPHER" && -z "$TOOL" ]]; then
76
+ echo "ERROR: provide --cypher or --tool" >&2; usage >&2; exit 1
77
+ fi
78
+
79
+ # Read-only allowlist for --tool mode. Destructive/mutating tools are not exposed
80
+ # through this generic hatch (delete_project, index_repository, manage_adr,
81
+ # ingest_traces) — use the purpose-built wrappers, which guard them explicitly.
82
+ TOOL_ALLOW=" get_architecture query_graph trace_path detect_changes get_code_snippet search_graph search_code get_graph_schema index_status list_projects "
83
+
84
+ if [[ -n "$TOOL" ]]; then
85
+ [[ "$TOOL_ALLOW" == *" $TOOL "* ]] || {
86
+ echo "ERROR: tool '$TOOL' is not in the read-only allowlist" >&2
87
+ echo "Allowed:$TOOL_ALLOW" >&2
88
+ exit 1
89
+ }
90
+ fi
91
+
92
+ # Reject write verbs in --cypher BEFORE the engine sees the query.
93
+ if [[ -n "$CYPHER" ]]; then
94
+ UPPER="$(printf '%s' "$CYPHER" | tr '[:lower:]' '[:upper:]')"
95
+ if printf '%s' "$UPPER" | grep -Eqw 'CREATE|MERGE|DELETE|SET|REMOVE|DROP|DETACH'; then
96
+ echo "ERROR: write verbs are not allowed (read-only passthrough)" >&2
97
+ exit 1
98
+ fi
99
+ fi
100
+
101
+ REPO_ABS="$(cd "$REPO" && pwd)"
102
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
103
+
104
+ unavailable() { echo '{"source":"unavailable"}'; exit 2; }
105
+
106
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
107
+ command -v jq >/dev/null 2>&1 || unavailable
108
+
109
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
110
+ [[ -n "$PROJECT" ]] || unavailable
111
+
112
+ if [[ -n "$CYPHER" ]]; then
113
+ RES="$(gq_run "$PROJECT" "$CYPHER" || true)"
114
+ [[ -n "$RES" ]] || unavailable
115
+ printf '%s\n' "$RES"
116
+ else
117
+ # Inject the resolved project into the tool args unless the caller set one.
118
+ echo "$TOOL_JSON" | jq -e . >/dev/null 2>&1 || { echo "ERROR: --json is not valid JSON" >&2; exit 1; }
119
+ ARGS="$(echo "$TOOL_JSON" | jq -c --arg p "$PROJECT" 'if has("project") then . else . + {project:$p} end')"
120
+ RES="$(memory_cli "$TOOL" "$ARGS" 2>/dev/null || true)"
121
+ [[ -n "$RES" ]] || unavailable
122
+ echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable
123
+ printf '%s\n' "$RES"
124
+ fi
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env bash
2
+ # graph-risk.sh — pre-computed risk hotspots from the knowledge graph node props.
3
+ #
4
+ # graph-tooling-v2 Phase 3. The engine already computes per-symbol risk flags
5
+ # (unguarded_recursion, recursion_in_loop, alloc_in_loop, linear_scan_in_loop)
6
+ # during indexing. This wrapper surfaces them so bughunt / deep-review consume the
7
+ # engine's findings instead of re-deriving them by hand.
8
+ #
9
+ # Usage:
10
+ # scripts/tools/graph-risk.sh --repo DIR [--min-complexity N]
11
+ #
12
+ # Output: JSON {risky:[{symbol, file, complexity, flags:[...]}], total,
13
+ # truncated, source}. --min-complexity keeps only flagged symbols
14
+ # whose complexity >= N (filtered client-side; the engine dialect
15
+ # has no >= operator).
16
+ #
17
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
18
+ set -euo pipefail
19
+
20
+ # shellcheck source=_graph_queries.sh
21
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
22
+
23
+ REPO="."
24
+ MIN_COMPLEXITY=0
25
+
26
+ usage() {
27
+ cat <<'EOF'
28
+ graph-risk.sh — pre-computed risk hotspots (recursion / in-loop allocations).
29
+
30
+ Usage:
31
+ scripts/tools/graph-risk.sh --repo DIR [--min-complexity N]
32
+
33
+ Flags:
34
+ --repo DIR Repository root (default: cwd).
35
+ --min-complexity N Keep only flagged symbols with complexity >= N (default 0).
36
+ --help Show this help.
37
+
38
+ Output: JSON {risky:[{symbol, file, complexity, flags}], total, truncated,
39
+ source}. Exit 2 when engine unavailable.
40
+ EOF
41
+ }
42
+
43
+ while [[ $# -gt 0 ]]; do
44
+ case "$1" in
45
+ --repo) REPO="$2"; shift 2;;
46
+ --min-complexity) MIN_COMPLEXITY="$2"; shift 2;;
47
+ --help|-h) usage; exit 0;;
48
+ *) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
49
+ esac
50
+ done
51
+
52
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
53
+ [[ "$MIN_COMPLEXITY" =~ ^[0-9]+$ ]] || { echo "ERROR: --min-complexity must be a non-negative integer" >&2; exit 1; }
54
+
55
+ REPO_ABS="$(cd "$REPO" && pwd)"
56
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
57
+
58
+ unavailable() { echo '{"risky":[],"total":0,"source":"unavailable"}'; exit 2; }
59
+
60
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
61
+ command -v jq >/dev/null 2>&1 || unavailable
62
+
63
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
64
+ [[ -n "$PROJECT" ]] || unavailable
65
+
66
+ RES="$(gq_run "$PROJECT" "$(gq_q_risk)" || true)"
67
+ [[ -n "$RES" ]] || unavailable
68
+
69
+ # Columns: symbol, file, complexity, unguarded_recursion, alloc_in_loop,
70
+ # recursion_in_loop, linear_scan_in_loop. complexity arrives as a string.
71
+ echo "$RES" | jq --argjson minc "$MIN_COMPLEXITY" '
72
+ [ (.rows // [])[]
73
+ | {symbol:.[0], file:.[1], complexity:((.[2] // "0") | tonumber? // 0),
74
+ flags: ( [ (if (.[3]|tostring) == "true" then "unguarded_recursion" else empty end),
75
+ (if (.[4]|tostring) == "true" then "alloc_in_loop" else empty end),
76
+ (if (.[5]|tostring) == "true" then "recursion_in_loop" else empty end),
77
+ (if (.[6]|tostring) == "true" then "linear_scan_in_loop" else empty end) ] )}
78
+ | select(.complexity >= $minc) ] as $r
79
+ | {risky: $r, total: ($r | length),
80
+ truncated: (((.rows // []) | length) >= 200),
81
+ source:"memory-graph"}'
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env bash
2
+ # graph-search.sh — semantic / ranked symbol search over the knowledge graph.
3
+ #
4
+ # Wraps the engine's search_graph (graph-tooling-v2 Phase 3): "find code about X"
5
+ # returns vector/text-ranked symbols, not a literal grep. Use when the user names
6
+ # an intent or concept rather than an exact symbol.
7
+ #
8
+ # Usage:
9
+ # scripts/tools/graph-search.sh --repo DIR --query "order submission to broker" [--limit N]
10
+ #
11
+ # Output: JSON {query, results:[{name, qualified_name, label, file, start_line,
12
+ # end_line, rank}], total, source}.
13
+ #
14
+ # Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
15
+ set -euo pipefail
16
+
17
+ # shellcheck source=_graph_queries.sh
18
+ source "$(dirname "${BASH_SOURCE[0]}")/_graph_queries.sh"
19
+
20
+ REPO="."
21
+ QUERY=""
22
+ LIMIT=10
23
+
24
+ usage() {
25
+ cat <<'EOF'
26
+ graph-search.sh — semantic / ranked symbol search.
27
+
28
+ Usage:
29
+ scripts/tools/graph-search.sh --repo DIR --query "STR" [--limit N]
30
+
31
+ Flags:
32
+ --repo DIR Repository root (default: cwd).
33
+ --query STR Natural-language or keyword query (required).
34
+ --limit N Max results (default: 10).
35
+ --help Show this help.
36
+
37
+ Output: JSON {query, results, total, source}. Exit 2 when engine unavailable.
38
+ EOF
39
+ }
40
+
41
+ while [[ $# -gt 0 ]]; do
42
+ case "$1" in
43
+ --repo) REPO="$2"; shift 2;;
44
+ --query) QUERY="$2"; shift 2;;
45
+ --limit) LIMIT="$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
+ [[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
52
+ [[ -n "$QUERY" ]] || { echo "ERROR: --query is required" >&2; usage >&2; exit 1; }
53
+ [[ "$LIMIT" =~ ^[0-9]+$ ]] || { echo "ERROR: --limit must be a non-negative integer" >&2; exit 1; }
54
+
55
+ REPO_ABS="$(cd "$REPO" && pwd)"
56
+ SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
57
+
58
+ unavailable() {
59
+ jq -n --arg q "$QUERY" '{query:$q, results:[], source:"unavailable"}' 2>/dev/null \
60
+ || echo '{"results":[],"source":"unavailable"}'
61
+ exit 2
62
+ }
63
+
64
+ find_memory_bin "$REPO_ABS" "$SELF_REPO" || unavailable
65
+ command -v jq >/dev/null 2>&1 || unavailable
66
+
67
+ PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
68
+ [[ -n "$PROJECT" ]] || unavailable
69
+
70
+ ARGS="$(jq -n --arg p "$PROJECT" --arg q "$QUERY" --argjson n "$LIMIT" \
71
+ '{project:$p, query:$q, limit:$n}')"
72
+ RES="$(memory_cli search_graph "$ARGS" 2>/dev/null || true)"
73
+ [[ -n "$RES" ]] || unavailable
74
+ echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable
75
+
76
+ echo "$RES" | jq --arg q "$QUERY" '
77
+ {query:$q,
78
+ results: [ (.results // .matches // [])[]
79
+ | {name:(.name // ""), qualified_name:(.qualified_name // ""),
80
+ label:(.label // ""), file:(.file_path // .file // ""),
81
+ start_line:(.start_line // null), end_line:(.end_line // null),
82
+ rank:(.rank // .score // null)} ],
83
+ total: ((.results // .matches // []) | length),
84
+ source:"memory-graph"}'
@@ -89,6 +89,16 @@ STATUS_JSON="$(memory_cli index_status "{\"project\":\"$PROJECT\"}" || echo '{}'
89
89
  NODES="$(echo "$STATUS_JSON" | jq -r '.nodes // .node_count // .total_nodes // 0' 2>/dev/null || echo 0)"
90
90
  EDGES="$(echo "$STATUS_JSON" | jq -r '.edges // .edge_count // .total_edges // 0' 2>/dev/null || echo 0)"
91
91
  VER="$("$MEMORY_BIN" --version 2>/dev/null | awk '{print $NF}' || echo unknown)"
92
+
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)"
101
+
92
102
  cat > "$OUT/schema.yaml" <<EOF
93
103
  # Draft graph gate marker — written by scripts/tools/graph-snapshot.sh
94
104
  # Draft is engine-only: this file carries NO graph data. Its presence signals that
@@ -101,8 +111,10 @@ project: "$PROJECT"
101
111
  generated_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
102
112
  indexed_nodes: $NODES
103
113
  indexed_edges: $EDGES
114
+ changed_files: $CHANGED_FILES
115
+ impacted_symbols: $IMPACTED
104
116
  access: engine-live
105
117
  EOF
106
118
 
107
- echo "Indexed $PROJECT and wrote gate marker to $OUT/schema.yaml (nodes=$NODES edges=$EDGES)"
119
+ echo "Indexed $PROJECT and wrote gate marker to $OUT/schema.yaml (nodes=$NODES edges=$EDGES, changed_files=$CHANGED_FILES impacted_symbols=$IMPACTED)"
108
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"