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