@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/bin/README.md +4 -7
- package/cli/src/hosts/claude-code.js +4 -1
- package/cli/src/installer.js +12 -5
- package/core/shared/condensation.md +8 -8
- package/core/shared/draft-context-loading.md +5 -9
- package/core/shared/graph-query.md +170 -33
- package/core/shared/graph-usage-report.md +1 -1
- package/core/shared/pattern-learning.md +2 -2
- package/core/shared/red-flags.md +3 -3
- package/core/templates/ai-context.md +1 -1
- package/core/templates/architecture.md +3 -3
- package/integrations/agents/AGENTS.md +299 -145
- package/integrations/copilot/.github/copilot-instructions.md +299 -145
- package/package.json +2 -1
- package/scripts/lib.sh +11 -3
- package/scripts/tools/_graph_queries.sh +102 -0
- package/scripts/tools/adr-index.sh +2 -2
- package/scripts/tools/check-scope-conflicts.sh +2 -2
- package/scripts/tools/check-skill-line-caps.sh +2 -2
- package/scripts/tools/cycle-detect.sh +22 -15
- package/scripts/tools/diff-templates-vs-tracks.sh +2 -2
- package/scripts/tools/fix-whitespace.sh +15 -9
- package/scripts/tools/graph-arch.sh +72 -0
- package/scripts/tools/graph-callers.sh +71 -20
- package/scripts/tools/graph-deps.sh +76 -0
- package/scripts/tools/graph-errors.sh +97 -0
- package/scripts/tools/graph-hierarchy.sh +89 -0
- package/scripts/tools/graph-impact.sh +1 -0
- package/scripts/tools/graph-init.sh +3 -3
- package/scripts/tools/graph-query.sh +124 -0
- package/scripts/tools/graph-risk.sh +81 -0
- package/scripts/tools/graph-search.sh +84 -0
- package/scripts/tools/graph-snapshot.sh +63 -50
- package/scripts/tools/graph-snippet.sh +92 -0
- package/scripts/tools/graph-tests.sh +112 -0
- package/scripts/tools/graph-traces.sh +83 -0
- package/scripts/tools/hotspot-rank.sh +44 -15
- package/scripts/tools/manage-symlinks.sh +1 -1
- package/scripts/tools/mermaid-from-graph.sh +31 -10
- package/scripts/tools/parse-reports.sh +1 -1
- package/scripts/tools/verify-doc-anchors.sh +2 -2
- package/scripts/tools/verify-graph-binary.sh +1 -1
- package/skills/GRAPH.md +2 -2
- package/skills/bughunt/SKILL.md +1 -1
- package/skills/debug/SKILL.md +3 -3
- package/skills/decompose/SKILL.md +5 -5
- package/skills/deep-review/SKILL.md +2 -2
- package/skills/deploy-checklist/SKILL.md +2 -2
- package/skills/graph/SKILL.md +1 -1
- package/skills/implement/SKILL.md +1 -1
- package/skills/init/SKILL.md +62 -42
- package/skills/init/references/architecture-spec.md +12 -11
- package/skills/learn/SKILL.md +5 -5
- package/skills/quick-review/SKILL.md +3 -3
- package/skills/review/SKILL.md +7 -7
- package/skills/tech-debt/SKILL.md +2 -2
- package/scripts/tools/okf-bundle.sh +0 -141
- package/scripts/tools/okf-check.sh +0 -137
- package/scripts/tools/okf-emit.sh +0 -161
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# graph-snapshot.sh —
|
|
2
|
+
# graph-snapshot.sh — index the repo into the local graph engine and write the
|
|
3
|
+
# committed gate marker (schema.yaml).
|
|
3
4
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
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 —
|
|
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
|
|
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 (
|
|
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 —
|
|
61
|
-
command -v jq >/dev/null 2>&1 || { echo "jq required
|
|
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 —
|
|
74
|
+
[[ -n "$PROJECT" ]] || { echo "could not index repo — nothing written" >&2; exit 2; }
|
|
65
75
|
|
|
66
76
|
mkdir -p "$OUT"
|
|
67
77
|
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
#
|
|
81
|
-
|
|
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
|
|
90
|
-
#
|
|
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 -
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 "
|
|
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 —
|
|
2
|
+
# hotspot-rank.sh — complexity-weighted hotspot ranking from the knowledge graph.
|
|
3
3
|
#
|
|
4
|
-
# Backed by the codebase-memory-mcp engine
|
|
5
|
-
#
|
|
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
|
|
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=
|
|
17
|
-
source "$(dirname "${BASH_SOURCE[0]}")/
|
|
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 —
|
|
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
|
|
35
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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"
|
|
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.
|
|
5
|
-
# module-deps : file
|
|
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=
|
|
18
|
-
source "$(dirname "${BASH_SOURCE[0]}")/
|
|
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)
|
|
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
|
|
75
|
-
local
|
|
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'
|
|
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'
|