@drafthq/draft 3.5.3 → 3.7.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +28 -49
- package/bin/README.md +47 -1
- package/cli/src/hosts/cursor.js +3 -0
- package/cli/src/installer.js +8 -4
- package/cli/src/lib/cursor-registry.js +10 -0
- package/cli/src/lib/fsx.js +3 -0
- package/cli/src/lib/marker.js +8 -3
- package/core/agents/architect.md +21 -9
- package/core/agents/debugger.md +5 -0
- package/core/agents/ops.md +7 -2
- package/core/agents/planner.md +7 -0
- package/core/agents/rca.md +14 -3
- package/core/agents/reviewer.md +1 -0
- package/core/agents/writer.md +6 -0
- package/core/knowledge-base.md +22 -3
- package/core/methodology.md +30 -5
- package/core/shared/condensation.md +28 -18
- package/core/shared/context-verify.md +3 -1
- package/core/shared/cross-skill-dispatch.md +7 -3
- package/core/shared/draft-context-loading.md +3 -0
- package/core/shared/git-report-metadata.md +2 -1
- package/core/shared/graph-query.md +24 -26
- package/core/shared/graph-usage-report.md +1 -1
- package/core/shared/jira-sync.md +10 -6
- package/core/shared/okf-retrieval.md +1 -1
- package/core/shared/parallel-analysis.md +14 -7
- package/core/shared/pattern-learning.md +2 -0
- package/core/shared/template-contract.md +1 -1
- package/core/shared/tool-resolver.md +20 -15
- package/core/shared/vcs-commands.md +5 -2
- package/core/templates/ai-context.md +3 -2
- package/core/templates/ai-profile.md +7 -0
- package/core/templates/architecture.md +6 -2
- package/core/templates/discovery.md +1 -1
- package/core/templates/guardrails.md +9 -0
- package/core/templates/hld.md +5 -4
- package/core/templates/intake-questions.md +45 -0
- package/core/templates/jira.md +11 -2
- package/core/templates/lld.md +8 -5
- package/core/templates/okf/concept.md +0 -1
- package/core/templates/plan.md +1 -1
- package/core/templates/product.md +11 -2
- package/core/templates/rca.md +6 -0
- package/core/templates/service-index.md +1 -0
- package/core/templates/spec.md +15 -0
- package/core/templates/tech-stack.md +12 -0
- package/core/templates/workflow.md +20 -0
- package/integrations/agents/AGENTS.md +1449 -817
- package/integrations/copilot/.github/copilot-instructions.md +1449 -817
- package/package.json +1 -1
- package/scripts/fetch-memory-engine.sh +25 -7
- package/scripts/lib.sh +90 -6
- package/scripts/tools/_graph_queries.sh +22 -8
- package/scripts/tools/_lib.sh +81 -11
- package/scripts/tools/adr-index.sh +1 -1
- package/scripts/tools/check-repo-size.sh +125 -0
- package/scripts/tools/check-skill-line-caps.sh +3 -4
- package/scripts/tools/check-template-noop.sh +0 -1
- package/scripts/tools/check-track-hygiene.sh +41 -21
- package/scripts/tools/classify-files.sh +3 -3
- package/scripts/tools/cycle-detect.sh +18 -17
- package/scripts/tools/detect-test-framework.sh +1 -1
- package/scripts/tools/emit-skill-metrics.sh +7 -2
- package/scripts/tools/fix-whitespace.sh +3 -0
- package/scripts/tools/freshness-check.sh +2 -2
- package/scripts/tools/git-metadata.sh +12 -6
- package/scripts/tools/graph-arch.sh +5 -11
- package/scripts/tools/graph-callers.sh +10 -15
- package/scripts/tools/graph-deps.sh +3 -10
- package/scripts/tools/graph-errors.sh +4 -11
- package/scripts/tools/graph-hierarchy.sh +4 -11
- package/scripts/tools/graph-impact.sh +21 -19
- package/scripts/tools/graph-init.sh +13 -9
- package/scripts/tools/graph-preflight.sh +4 -1
- package/scripts/tools/graph-query.sh +74 -18
- package/scripts/tools/graph-risk.sh +3 -10
- package/scripts/tools/graph-search.sh +4 -11
- package/scripts/tools/graph-snapshot.sh +38 -9
- package/scripts/tools/graph-snippet.sh +3 -10
- package/scripts/tools/graph-tests.sh +3 -10
- package/scripts/tools/graph-traces.sh +3 -10
- package/scripts/tools/hotspot-rank.sh +6 -12
- package/scripts/tools/install-smoke-test.sh +223 -0
- package/scripts/tools/mermaid-from-graph.sh +5 -11
- package/scripts/tools/migrate-track-frontmatter.sh +11 -10
- package/scripts/tools/okf-coverage-check.sh +8 -7
- package/scripts/tools/okf-emit-catalog.sh +256 -0
- package/scripts/tools/okf-fix-links.sh +251 -0
- package/scripts/tools/okf-plan-concepts.sh +242 -14
- package/scripts/tools/okf-render-views.sh +63 -21
- package/scripts/tools/okf-validate-all.sh +20 -9
- package/scripts/tools/okf-validate-quality.sh +15 -10
- package/scripts/tools/okf-validate.sh +20 -6
- package/scripts/tools/parse-git-log.sh +23 -5
- package/scripts/tools/parse-reports.sh +1 -1
- package/scripts/tools/render-track.sh +1 -1
- package/scripts/tools/resolve-tools.sh +24 -8
- package/scripts/tools/run-coverage.sh +1 -1
- package/scripts/tools/scan-markers.sh +4 -4
- package/scripts/tools/validate-frontmatter.sh +2 -2
- package/scripts/tools/verify-citations.sh +3 -4
- package/scripts/tools/verify-doc-anchors.sh +14 -34
- package/scripts/tools/verify-graph-binary.sh +15 -5
- package/skills/GRAPH.md +21 -10
- package/skills/adr/SKILL.md +25 -7
- package/skills/assist-review/SKILL.md +2 -1
- package/skills/bughunt/SKILL.md +55 -12
- package/skills/bughunt/references/regression-tests.md +14 -4
- package/skills/change/SKILL.md +19 -9
- package/skills/coverage/SKILL.md +28 -12
- package/skills/debug/SKILL.md +13 -3
- package/skills/decompose/SKILL.md +42 -12
- package/skills/deep-review/SKILL.md +19 -6
- package/skills/deploy-checklist/SKILL.md +10 -3
- package/skills/documentation/SKILL.md +12 -1
- package/skills/draft/SKILL.md +40 -29
- package/skills/draft/context-files.md +8 -1
- package/skills/draft/quality-guide.md +10 -2
- package/skills/graph/SKILL.md +2 -2
- package/skills/impact/SKILL.md +4 -2
- package/skills/implement/SKILL.md +62 -20
- package/skills/incident-response/SKILL.md +5 -2
- package/skills/init/SKILL.md +112 -30
- package/skills/init/references/architecture-spec.md +32 -8
- package/skills/init/references/okf-emitter.md +42 -78
- package/skills/jira/SKILL.md +33 -13
- package/skills/jira/references/review.md +61 -19
- package/skills/learn/SKILL.md +30 -7
- package/skills/new-track/SKILL.md +73 -18
- package/skills/quick-review/SKILL.md +8 -3
- package/skills/revert/SKILL.md +11 -5
- package/skills/review/SKILL.md +126 -27
- package/skills/review/references/zero-setup-mode.md +117 -0
- package/skills/standup/SKILL.md +10 -2
- package/skills/status/SKILL.md +7 -7
- package/skills/tech-debt/SKILL.md +9 -3
- package/skills/testing-strategy/SKILL.md +10 -2
- package/skills/tour/SKILL.md +2 -1
- package/skills/upload/SKILL.md +4 -3
- package/core/templates/track-architecture.md +0 -311
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
# are rejected before the engine ever sees them).
|
|
9
9
|
# --tool NAME --json '{...}' passthrough to any read-only engine tool
|
|
10
10
|
# (get_code_snippet, search_graph, get_graph_schema,
|
|
11
|
-
# trace_path, …). Destructive tools are rejected
|
|
11
|
+
# trace_path, …). Destructive tools are rejected, and
|
|
12
|
+
# a `query` field in --json is scanned for write
|
|
13
|
+
# verbs exactly like --cypher.
|
|
12
14
|
#
|
|
13
15
|
# Dialect limits (engine v0.8.x — see _graph_queries.sh for the full list):
|
|
14
16
|
# SAFE : fixed-length patterns, `=`, `<`, `STARTS WITH`, `NOT x STARTS WITH`,
|
|
@@ -46,7 +48,8 @@ Flags:
|
|
|
46
48
|
--cypher STR Read-only openCypher query (write verbs CREATE/MERGE/DELETE/SET/
|
|
47
49
|
REMOVE/DROP/DETACH are rejected). The {project} is injected.
|
|
48
50
|
--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).
|
|
51
|
+
--json STR JSON args for --tool (the project is injected if absent). A
|
|
52
|
+
`query` field is write-verb checked like --cypher.
|
|
50
53
|
--help Show this help.
|
|
51
54
|
|
|
52
55
|
Dialect: avoid coalesce(), <>, NOT EXISTS, NOT(pattern), WITH-aggregation,
|
|
@@ -59,10 +62,10 @@ EOF
|
|
|
59
62
|
|
|
60
63
|
while [[ $# -gt 0 ]]; do
|
|
61
64
|
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;;
|
|
65
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
66
|
+
--cypher) CYPHER="${2:?--cypher requires a value}"; shift 2;;
|
|
67
|
+
--tool) TOOL="${2:?--tool requires a value}"; shift 2;;
|
|
68
|
+
--json) TOOL_JSON="${2:?--json requires a value}"; shift 2;;
|
|
66
69
|
--help|-h) usage; exit 0;;
|
|
67
70
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
68
71
|
esac
|
|
@@ -89,25 +92,79 @@ if [[ -n "$TOOL" ]]; then
|
|
|
89
92
|
}
|
|
90
93
|
fi
|
|
91
94
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
# Drop the contents of every quoted span before scanning for write verbs below.
|
|
96
|
+
# A raw substring grep can't tell a CREATE/SET/DELETE clause from a symbol
|
|
97
|
+
# literally named 'create', 'set', 'delete', etc. — exactly the method names
|
|
98
|
+
# real codebases are full of — so quoted spans must be blanked out first or
|
|
99
|
+
# every query about one of them gets wrongly rejected. Cypher quotes strings
|
|
100
|
+
# with either ' or " and identifiers with `, and the engine accepts all three,
|
|
101
|
+
# so all three must be handled. Escaped quotes (`\'`, produced by gq_escape in
|
|
102
|
+
# _graph_queries.sh) must not be read as closing a span early, and an
|
|
103
|
+
# unterminated span fails closed (rejected) rather than letting the rest of the
|
|
104
|
+
# query — write verbs included — go unscanned.
|
|
105
|
+
strip_quoted_spans() {
|
|
106
|
+
local s="$1" out="" i=0 len ch quote
|
|
107
|
+
len="${#s}"
|
|
108
|
+
while (( i < len )); do
|
|
109
|
+
ch="${s:i:1}"
|
|
110
|
+
if [[ "$ch" != "'" && "$ch" != '"' && "$ch" != '`' ]]; then
|
|
111
|
+
out+="$ch"
|
|
112
|
+
i=$((i + 1))
|
|
113
|
+
continue
|
|
114
|
+
fi
|
|
115
|
+
quote="$ch"
|
|
116
|
+
out+="$quote"
|
|
117
|
+
i=$((i + 1))
|
|
118
|
+
while :; do
|
|
119
|
+
(( i < len )) || return 1
|
|
120
|
+
ch="${s:i:1}"
|
|
121
|
+
if [[ "$ch" == '\' ]]; then
|
|
122
|
+
i=$((i + 2))
|
|
123
|
+
continue
|
|
124
|
+
fi
|
|
125
|
+
i=$((i + 1))
|
|
126
|
+
if [[ "$ch" == "$quote" ]]; then
|
|
127
|
+
out+="$quote"
|
|
128
|
+
break
|
|
129
|
+
fi
|
|
130
|
+
done
|
|
131
|
+
done
|
|
132
|
+
printf '%s' "$out"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Reject write verbs BEFORE the engine sees the query.
|
|
136
|
+
reject_write_verbs() {
|
|
137
|
+
local stripped upper
|
|
138
|
+
stripped="$(strip_quoted_spans "$1")" || {
|
|
139
|
+
echo "ERROR: write verbs are not allowed (read-only passthrough)" >&2
|
|
140
|
+
exit 1
|
|
141
|
+
}
|
|
142
|
+
upper="$(printf '%s' "$stripped" | tr '[:lower:]' '[:upper:]')"
|
|
143
|
+
if printf '%s' "$upper" | grep -Eqw 'CREATE|MERGE|DELETE|SET|REMOVE|DROP|DETACH'; then
|
|
96
144
|
echo "ERROR: write verbs are not allowed (read-only passthrough)" >&2
|
|
97
145
|
exit 1
|
|
98
146
|
fi
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if [[ -n "$CYPHER" ]]; then
|
|
150
|
+
reject_write_verbs "$CYPHER"
|
|
99
151
|
fi
|
|
100
152
|
|
|
101
|
-
|
|
102
|
-
|
|
153
|
+
# --tool carries Cypher too: query_graph takes it in the payload's `query` field,
|
|
154
|
+
# so guarding only --cypher left the read-only contract fully bypassable via
|
|
155
|
+
# `--tool query_graph --json '{"query":"MATCH (n) DETACH DELETE n"}'`. Scan any
|
|
156
|
+
# `query` a tool payload carries, not just query_graph's, so a future
|
|
157
|
+
# query-bearing tool is covered by construction. jq-gated: without jq nothing can
|
|
158
|
+
# reach the engine anyway (graph_bootstrap fails first).
|
|
159
|
+
if [[ -n "$TOOL" ]] && command -v jq >/dev/null 2>&1; then
|
|
160
|
+
echo "$TOOL_JSON" | jq -e . >/dev/null 2>&1 || { echo "ERROR: --json is not valid JSON" >&2; exit 1; }
|
|
161
|
+
TOOL_QUERY="$(echo "$TOOL_JSON" | jq -r '.query // empty' 2>/dev/null || true)"
|
|
162
|
+
[[ -z "$TOOL_QUERY" ]] || reject_write_verbs "$TOOL_QUERY"
|
|
163
|
+
fi
|
|
103
164
|
|
|
104
165
|
unavailable() { echo '{"source":"unavailable"}'; exit 2; }
|
|
105
166
|
|
|
106
|
-
|
|
107
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
108
|
-
|
|
109
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
110
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
167
|
+
graph_bootstrap "$REPO" || unavailable
|
|
111
168
|
|
|
112
169
|
if [[ -n "$CYPHER" ]]; then
|
|
113
170
|
RES="$(gq_run "$PROJECT" "$CYPHER" || true)"
|
|
@@ -115,7 +172,6 @@ if [[ -n "$CYPHER" ]]; then
|
|
|
115
172
|
printf '%s\n' "$RES"
|
|
116
173
|
else
|
|
117
174
|
# 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
175
|
ARGS="$(echo "$TOOL_JSON" | jq -c --arg p "$PROJECT" 'if has("project") then . else . + {project:$p} end')"
|
|
120
176
|
RES="$(memory_cli "$TOOL" "$ARGS" 2>/dev/null || true)"
|
|
121
177
|
[[ -n "$RES" ]] || unavailable
|
|
@@ -42,8 +42,8 @@ EOF
|
|
|
42
42
|
|
|
43
43
|
while [[ $# -gt 0 ]]; do
|
|
44
44
|
case "$1" in
|
|
45
|
-
--repo) REPO="$2"; shift 2;;
|
|
46
|
-
--min-complexity) MIN_COMPLEXITY="$2"; shift 2;;
|
|
45
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
46
|
+
--min-complexity) MIN_COMPLEXITY="${2:?--min-complexity requires a value}"; shift 2;;
|
|
47
47
|
--help|-h) usage; exit 0;;
|
|
48
48
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
49
49
|
esac
|
|
@@ -52,16 +52,9 @@ done
|
|
|
52
52
|
[[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
|
|
53
53
|
[[ "$MIN_COMPLEXITY" =~ ^[0-9]+$ ]] || { echo "ERROR: --min-complexity must be a non-negative integer" >&2; exit 1; }
|
|
54
54
|
|
|
55
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
56
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
57
|
-
|
|
58
55
|
unavailable() { echo '{"risky":[],"total":0,"source":"unavailable"}'; exit 2; }
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
62
|
-
|
|
63
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
64
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
57
|
+
graph_bootstrap "$REPO" || unavailable
|
|
65
58
|
|
|
66
59
|
RES="$(gq_run "$PROJECT" "$(gq_q_risk)" || true)"
|
|
67
60
|
[[ -n "$RES" ]] || unavailable
|
|
@@ -40,9 +40,9 @@ EOF
|
|
|
40
40
|
|
|
41
41
|
while [[ $# -gt 0 ]]; do
|
|
42
42
|
case "$1" in
|
|
43
|
-
--repo) REPO="$2"; shift 2;;
|
|
44
|
-
--query) QUERY="$2"; shift 2;;
|
|
45
|
-
--limit) LIMIT="$2"; shift 2;;
|
|
43
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
44
|
+
--query) QUERY="${2:?--query requires a value}"; shift 2;;
|
|
45
|
+
--limit) LIMIT="${2:?--limit requires a value}"; shift 2;;
|
|
46
46
|
--help|-h) usage; exit 0;;
|
|
47
47
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
48
48
|
esac
|
|
@@ -52,20 +52,13 @@ done
|
|
|
52
52
|
[[ -n "$QUERY" ]] || { echo "ERROR: --query is required" >&2; usage >&2; exit 1; }
|
|
53
53
|
[[ "$LIMIT" =~ ^[0-9]+$ ]] || { echo "ERROR: --limit must be a non-negative integer" >&2; exit 1; }
|
|
54
54
|
|
|
55
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
56
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
57
|
-
|
|
58
55
|
unavailable() {
|
|
59
56
|
jq -n --arg q "$QUERY" '{query:$q, results:[], source:"unavailable"}' 2>/dev/null \
|
|
60
57
|
|| echo '{"results":[],"source":"unavailable"}'
|
|
61
58
|
exit 2
|
|
62
59
|
}
|
|
63
60
|
|
|
64
|
-
|
|
65
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
66
|
-
|
|
67
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
68
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
61
|
+
graph_bootstrap "$REPO" || unavailable
|
|
69
62
|
|
|
70
63
|
ARGS="$(jq -n --arg p "$PROJECT" --arg q "$QUERY" --argjson n "$LIMIT" \
|
|
71
64
|
'{project:$p, query:$q, limit:$n}')"
|
|
@@ -52,8 +52,8 @@ EOF
|
|
|
52
52
|
|
|
53
53
|
while [[ $# -gt 0 ]]; do
|
|
54
54
|
case "$1" in
|
|
55
|
-
--repo) REPO="$2"; shift 2;;
|
|
56
|
-
--out) OUT_DIR="$2"; shift 2;;
|
|
55
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
56
|
+
--out) OUT_DIR="${2:?--out requires a value}"; shift 2;;
|
|
57
57
|
--help|-h) usage; exit 0;;
|
|
58
58
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
59
59
|
esac
|
|
@@ -73,17 +73,41 @@ command -v jq >/dev/null 2>&1 || { echo "jq required" >&2; exit 2; }
|
|
|
73
73
|
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
74
74
|
[[ -n "$PROJECT" ]] || { echo "could not index repo — nothing written" >&2; exit 2; }
|
|
75
75
|
|
|
76
|
+
# ...then ALWAYS re-index. memory_ensure_index calls index_repository only when the
|
|
77
|
+
# project is ABSENT — correct for the graph-*.sh query wrappers, which must stay
|
|
78
|
+
# cheap — so on an already-indexed repo this tool used to write a gate marker with a
|
|
79
|
+
# fresh `generated_at` over a frozen index: a deleted symbol stayed resolvable, a new
|
|
80
|
+
# one never appeared, and nothing in the output said so. Refreshing is this tool's
|
|
81
|
+
# entire job. The engine indexes incrementally, so the repeat call is cheap.
|
|
82
|
+
REFRESHED="$(memory_index_bounded "$REPO_ABS" 2>/dev/null | jq -r '.project // empty' 2>/dev/null || true)"
|
|
83
|
+
[[ -n "$REFRESHED" ]] && PROJECT="$REFRESHED"
|
|
84
|
+
|
|
76
85
|
mkdir -p "$OUT"
|
|
77
86
|
|
|
78
87
|
# Prune any stale fat-snapshot artifacts from a prior (pre-engine-only) run so a
|
|
79
88
|
# re-index migrates the repo to the thin model.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
89
|
+
#
|
|
90
|
+
# Gated on $OUT carrying positive evidence that Draft owns it. --out is
|
|
91
|
+
# caller-supplied and the mkdir -p above will happily create a typo'd path, so an
|
|
92
|
+
# ungated `rm -rf "$OUT/okf"` turns a mistyped flag into data loss. Evidence is
|
|
93
|
+
# the default location, a marker from a previous run, or a prior fat snapshot
|
|
94
|
+
# (which always carried architecture.json / hotspots.jsonl).
|
|
95
|
+
draft_owns_out_dir() {
|
|
96
|
+
[[ "$OUT" == "$REPO_ABS/draft/graph" ]] && return 0
|
|
97
|
+
[[ -f "$OUT/schema.yaml" ]] && return 0
|
|
98
|
+
[[ -f "$OUT/architecture.json" || -f "$OUT/hotspots.jsonl" ]] && return 0
|
|
99
|
+
return 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if draft_owns_out_dir; then
|
|
103
|
+
rm -f "$OUT/architecture.json" "$OUT/hotspots.jsonl" \
|
|
104
|
+
"$OUT/module-deps.mermaid" "$OUT/proto-map.mermaid" 2>/dev/null || true
|
|
105
|
+
rm -rf "$OUT/okf" 2>/dev/null || true
|
|
106
|
+
fi
|
|
83
107
|
|
|
84
108
|
# schema.yaml — provenance + gate. Counts are point-of-index provenance only;
|
|
85
109
|
# the live engine is authoritative.
|
|
86
|
-
STATUS_JSON="$(memory_cli index_status "
|
|
110
|
+
STATUS_JSON="$(memory_cli index_status "$(jq -n --arg p "$PROJECT" '{project:$p}')" || echo '{}')"
|
|
87
111
|
# Tolerate field-name variation AND non-JSON output across engine versions;
|
|
88
112
|
# counts are provenance only and must never abort the gate-marker write.
|
|
89
113
|
NODES="$(echo "$STATUS_JSON" | jq -r '.nodes // .node_count // .total_nodes // 0' 2>/dev/null || echo 0)"
|
|
@@ -94,11 +118,16 @@ VER="$("$MEMORY_BIN" --version 2>/dev/null | awk '{print $NF}' || echo unknown)"
|
|
|
94
118
|
# incrementally (content-based, git-aware), so re-indexing only touches changed
|
|
95
119
|
# files. detect_changes reports that working-tree delta — recorded as provenance
|
|
96
120
|
# and echoed so a refresh shows what moved. Best-effort: never aborts the write.
|
|
97
|
-
CHANGES_JSON="$(memory_cli detect_changes "
|
|
121
|
+
CHANGES_JSON="$(memory_cli detect_changes "$(jq -n --arg p "$PROJECT" '{project:$p}')" 2>/dev/null || echo '{}')"
|
|
98
122
|
echo "$CHANGES_JSON" | jq -e . >/dev/null 2>&1 || CHANGES_JSON='{}'
|
|
99
123
|
CHANGED_FILES="$(echo "$CHANGES_JSON" | jq -r '.changed_count // (.changed_files | length?) // 0' 2>/dev/null || echo 0)"
|
|
100
124
|
IMPACTED="$(echo "$CHANGES_JSON" | jq -r '(.impacted_symbols | length?) // 0' 2>/dev/null || echo 0)"
|
|
101
125
|
|
|
126
|
+
# YAML double-quoted scalars: escape backslashes then quotes so an unusual
|
|
127
|
+
# project name or engine version string can never corrupt the marker.
|
|
128
|
+
PROJECT_Y="${PROJECT//\\/\\\\}"; PROJECT_Y="${PROJECT_Y//\"/\\\"}"
|
|
129
|
+
VER_Y="${VER//\\/\\\\}"; VER_Y="${VER_Y//\"/\\\"}"
|
|
130
|
+
|
|
102
131
|
cat > "$OUT/schema.yaml" <<EOF
|
|
103
132
|
# Draft graph gate marker — written by scripts/tools/graph-snapshot.sh
|
|
104
133
|
# Draft is engine-only: this file carries NO graph data. Its presence signals that
|
|
@@ -106,8 +135,8 @@ cat > "$OUT/schema.yaml" <<EOF
|
|
|
106
135
|
# live via the graph-*.sh wrappers (or \`codebase-memory-mcp cli <tool>\`).
|
|
107
136
|
# Counts below are point-of-index provenance; the live engine is authoritative.
|
|
108
137
|
engine: codebase-memory-mcp
|
|
109
|
-
engine_version: "$
|
|
110
|
-
project: "$
|
|
138
|
+
engine_version: "$VER_Y"
|
|
139
|
+
project: "$PROJECT_Y"
|
|
111
140
|
generated_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
112
141
|
indexed_nodes: $NODES
|
|
113
142
|
indexed_edges: $EDGES
|
|
@@ -44,8 +44,8 @@ EOF
|
|
|
44
44
|
|
|
45
45
|
while [[ $# -gt 0 ]]; do
|
|
46
46
|
case "$1" in
|
|
47
|
-
--repo) REPO="$2"; shift 2;;
|
|
48
|
-
--qualified|--symbol) QNAME="$2"; shift 2;;
|
|
47
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
48
|
+
--qualified|--symbol) QNAME="${2:?--qualified|--symbol requires a value}"; shift 2;;
|
|
49
49
|
--help|-h) usage; exit 0;;
|
|
50
50
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
51
51
|
esac
|
|
@@ -54,20 +54,13 @@ done
|
|
|
54
54
|
[[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
|
|
55
55
|
[[ -n "$QNAME" ]] || { echo "ERROR: --qualified is required" >&2; usage >&2; exit 1; }
|
|
56
56
|
|
|
57
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
58
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
59
|
-
|
|
60
57
|
unavailable() {
|
|
61
58
|
jq -n --arg q "$QNAME" '{qualified_name:$q, status:"unavailable", source:"unavailable"}' 2>/dev/null \
|
|
62
59
|
|| echo '{"status":"unavailable","source":"unavailable"}'
|
|
63
60
|
exit 2
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
68
|
-
|
|
69
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
70
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
63
|
+
graph_bootstrap "$REPO" || unavailable
|
|
71
64
|
|
|
72
65
|
ARGS="$(jq -n --arg p "$PROJECT" --arg q "$QNAME" '{project:$p, qualified_name:$q}')"
|
|
73
66
|
RES="$(memory_cli get_code_snippet "$ARGS" 2>/dev/null || true)"
|
|
@@ -48,8 +48,8 @@ EOF
|
|
|
48
48
|
|
|
49
49
|
while [[ $# -gt 0 ]]; do
|
|
50
50
|
case "$1" in
|
|
51
|
-
--repo) REPO="$2"; shift 2;;
|
|
52
|
-
--symbol) SYMBOL="$2"; shift 2;;
|
|
51
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
52
|
+
--symbol) SYMBOL="${2:?--symbol requires a value}"; shift 2;;
|
|
53
53
|
--untested) UNTESTED=1; shift;;
|
|
54
54
|
--help|-h) usage; exit 0;;
|
|
55
55
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
@@ -59,9 +59,6 @@ done
|
|
|
59
59
|
[[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
|
|
60
60
|
[[ -n "$SYMBOL" || "$UNTESTED" -eq 1 ]] || { echo "ERROR: provide --symbol or --untested" >&2; usage >&2; exit 1; }
|
|
61
61
|
|
|
62
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
63
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
64
|
-
|
|
65
62
|
unavailable() {
|
|
66
63
|
if [[ "$UNTESTED" -eq 1 ]]; then
|
|
67
64
|
echo '{"untested":[],"total":0,"source":"unavailable"}'
|
|
@@ -72,11 +69,7 @@ unavailable() {
|
|
|
72
69
|
exit 2
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
77
|
-
|
|
78
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
79
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
72
|
+
graph_bootstrap "$REPO" || unavailable
|
|
80
73
|
|
|
81
74
|
if [[ "$UNTESTED" -eq 1 ]]; then
|
|
82
75
|
EXP="$(gq_run "$PROJECT" "$(gq_q_exported)" || true)"
|
|
@@ -49,8 +49,8 @@ EOF
|
|
|
49
49
|
while [[ $# -gt 0 ]]; do
|
|
50
50
|
case "$1" in
|
|
51
51
|
ingest) ACTION="ingest"; shift;;
|
|
52
|
-
--repo) REPO="$2"; shift 2;;
|
|
53
|
-
--file) FILE="$2"; shift 2;;
|
|
52
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
53
|
+
--file) FILE="${2:?--file requires a value}"; shift 2;;
|
|
54
54
|
--experimental) EXPERIMENTAL=1; shift;;
|
|
55
55
|
--help|-h) usage; exit 0;;
|
|
56
56
|
*) echo "Unknown argument: $1" >&2; usage >&2; exit 1;;
|
|
@@ -63,19 +63,12 @@ done
|
|
|
63
63
|
[[ -n "$FILE" ]] || { echo "ERROR: --file is required" >&2; usage >&2; exit 1; }
|
|
64
64
|
[[ -f "$FILE" ]] || { echo "ERROR: --file '$FILE' does not exist" >&2; exit 1; }
|
|
65
65
|
|
|
66
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
67
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
68
|
-
|
|
69
66
|
unavailable() { echo '{"source":"unavailable"}'; exit 2; }
|
|
70
67
|
|
|
71
|
-
|
|
72
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
68
|
+
graph_bootstrap "$REPO" || unavailable
|
|
73
69
|
|
|
74
70
|
jq -e . "$FILE" >/dev/null 2>&1 || { echo "ERROR: --file is not valid JSON" >&2; exit 1; }
|
|
75
71
|
|
|
76
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
77
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
78
|
-
|
|
79
72
|
ARGS="$(jq -n --arg p "$PROJECT" --slurpfile t "$FILE" '{project:$p, traces:($t[0])}')"
|
|
80
73
|
RES="$(memory_cli ingest_traces "$ARGS" 2>/dev/null || true)"
|
|
81
74
|
[[ -n "$RES" ]] || unavailable
|
|
@@ -47,8 +47,8 @@ EOF
|
|
|
47
47
|
|
|
48
48
|
while [[ $# -gt 0 ]]; do
|
|
49
49
|
case "$1" in
|
|
50
|
-
--repo) REPO="$2"; shift 2;;
|
|
51
|
-
--top) TOP="$2"; shift 2;;
|
|
50
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
51
|
+
--top) TOP="${2:?--top requires a value}"; shift 2;;
|
|
52
52
|
--help|-h) usage; exit 0;;
|
|
53
53
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
54
54
|
esac
|
|
@@ -61,18 +61,12 @@ if [[ ! -d "$REPO" ]]; then
|
|
|
61
61
|
exit 1
|
|
62
62
|
fi
|
|
63
63
|
|
|
64
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
65
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
66
|
-
|
|
67
64
|
unavailable() { echo '{"hotspots":[],"source":"unavailable"}'; exit 2; }
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
71
|
-
|
|
72
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
73
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
66
|
+
graph_bootstrap "$REPO" || unavailable
|
|
74
67
|
|
|
75
|
-
ARCH_JSON="$(memory_cli get_architecture
|
|
68
|
+
ARCH_JSON="$(memory_cli get_architecture \
|
|
69
|
+
"$(jq -n --arg p "$PROJECT" '{project:$p, aspects:["hotspots"]}')" || true)"
|
|
76
70
|
[[ -n "$ARCH_JSON" ]] || unavailable
|
|
77
71
|
echo "$ARCH_JSON" | jq -e . >/dev/null 2>&1 || unavailable
|
|
78
72
|
|
|
@@ -87,7 +81,7 @@ printf '%s' "$ARCH_JSON" > "$TMP_ARCH"
|
|
|
87
81
|
printf '%s' "$PROPS_JSON" > "$TMP_PROPS"
|
|
88
82
|
|
|
89
83
|
jq -n --slurpfile arch "$TMP_ARCH" --slurpfile props "$TMP_PROPS" --argjson top "$TOP" '
|
|
90
|
-
(($props[0].rows) // []) as $prows
|
|
84
|
+
((($props[0].rows) // []) | map(select(.[0] != null))) as $prows
|
|
91
85
|
| (reduce $prows[] as $r ({};
|
|
92
86
|
.[$r[0]] = {c:((($r[1]) // "0") | tonumber? // 0),
|
|
93
87
|
cog:((($r[2]) // "0") | tonumber? // 0),
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-smoke-test.sh
|
|
3
|
+
#
|
|
4
|
+
# Exercises the real first-install path on a throwaway clone.
|
|
5
|
+
#
|
|
6
|
+
# The v2.8.3 install hang was invisible to `make test` because every suite runs
|
|
7
|
+
# against the working tree, and the working tree is never what a new user gets.
|
|
8
|
+
# A new user gets `git clone --depth 1` followed by manifest discovery and a
|
|
9
|
+
# writer pass over an empty HOME. This reproduces exactly that, in that order,
|
|
10
|
+
# so a regression in any of those steps fails a build instead of a user.
|
|
11
|
+
#
|
|
12
|
+
# Requires: git, node 18+. Does NOT require the `claude` CLI and never touches
|
|
13
|
+
# the network — the graph-engine fetch is stubbed out via --no-graph.
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# scripts/tools/install-smoke-test.sh # clone this repo, run all checks
|
|
17
|
+
# scripts/tools/install-smoke-test.sh --repo <path> # smoke-test another checkout
|
|
18
|
+
# scripts/tools/install-smoke-test.sh --keep # leave the sandbox for inspection
|
|
19
|
+
# scripts/tools/install-smoke-test.sh --json
|
|
20
|
+
#
|
|
21
|
+
# Exit codes:
|
|
22
|
+
# 0 install path is healthy
|
|
23
|
+
# 1 a check failed
|
|
24
|
+
# 2 usage / runtime error
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
usage() {
|
|
29
|
+
cat <<'EOF'
|
|
30
|
+
install-smoke-test.sh — clean-clone install smoke test
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--repo <path> Repository to clone (default: this repo)
|
|
34
|
+
--keep Do not delete the sandbox on exit
|
|
35
|
+
--json Emit JSON instead of human-readable output
|
|
36
|
+
--help, -h Show this message
|
|
37
|
+
EOF
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
41
|
+
usage
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
46
|
+
# shellcheck source=_lib.sh
|
|
47
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
48
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
49
|
+
KEEP=0
|
|
50
|
+
EMIT_JSON=0
|
|
51
|
+
|
|
52
|
+
while [[ $# -gt 0 ]]; do
|
|
53
|
+
case "$1" in
|
|
54
|
+
--repo)
|
|
55
|
+
[[ -n "${2:-}" ]] || { echo "install-smoke-test: --repo requires a value" >&2; exit 2; }
|
|
56
|
+
REPO_ROOT="$2"; shift 2 ;;
|
|
57
|
+
--keep) KEEP=1; shift ;;
|
|
58
|
+
--json) EMIT_JSON=1; shift ;;
|
|
59
|
+
--help|-h) usage; exit 0 ;;
|
|
60
|
+
*) echo "install-smoke-test: unknown argument '$1'" >&2; usage >&2; exit 2 ;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
[[ -d "$REPO_ROOT" ]] || { echo "install-smoke-test: no such directory: $REPO_ROOT" >&2; exit 2; }
|
|
65
|
+
command -v git >/dev/null || { echo "install-smoke-test: git not found" >&2; exit 2; }
|
|
66
|
+
command -v node >/dev/null || { echo "install-smoke-test: node not found" >&2; exit 2; }
|
|
67
|
+
|
|
68
|
+
SANDBOX="$(mktemp -d "${TMPDIR:-/tmp}/draft-install-smoke.XXXXXX")"
|
|
69
|
+
cleanup() { [[ "$KEEP" -eq 1 ]] || rm -rf "$SANDBOX"; }
|
|
70
|
+
trap cleanup EXIT
|
|
71
|
+
|
|
72
|
+
CLONE="$SANDBOX/clone"
|
|
73
|
+
FAKE_HOME="$SANDBOX/home"
|
|
74
|
+
PROJECT="$SANDBOX/project"
|
|
75
|
+
mkdir -p "$FAKE_HOME" "$PROJECT"
|
|
76
|
+
|
|
77
|
+
FAILURES=()
|
|
78
|
+
RESULTS=()
|
|
79
|
+
|
|
80
|
+
# Collapse a file to one truncated line. Done in-shell rather than with
|
|
81
|
+
# `| cut -c1-200` because an early-closing reader plus `pipefail` turns a
|
|
82
|
+
# harmless truncation into a SIGPIPE failure of the whole script.
|
|
83
|
+
oneline() {
|
|
84
|
+
local text
|
|
85
|
+
text="$(tr '\n' ' ' < "$1")"
|
|
86
|
+
printf '%s' "${text:0:200}"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lastlines() {
|
|
90
|
+
local text
|
|
91
|
+
text="$(tail -n "$2" "$1" | tr '\n' ' ')"
|
|
92
|
+
printf '%s' "${text:0:200}"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
record() {
|
|
96
|
+
local name="$1" ok="$2" detail="${3:-}"
|
|
97
|
+
RESULTS+=("$name|$ok|$detail")
|
|
98
|
+
if [[ "$ok" == "true" ]]; then
|
|
99
|
+
[[ "$EMIT_JSON" -eq 1 ]] || echo " PASS: $name${detail:+ — $detail}"
|
|
100
|
+
else
|
|
101
|
+
[[ "$EMIT_JSON" -eq 1 ]] || echo " FAIL: $name${detail:+ — $detail}"
|
|
102
|
+
FAILURES+=("$name")
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
[[ "$EMIT_JSON" -eq 1 ]] || echo "=== Install smoke test (sandbox: $SANDBOX) ==="
|
|
107
|
+
|
|
108
|
+
# 1. Shallow clone — the literal first step of `plugin marketplace add`.
|
|
109
|
+
# file:// forces the real transfer path; a plain local path would hardlink
|
|
110
|
+
# and hide size problems.
|
|
111
|
+
if git clone --depth 1 --quiet "file://$REPO_ROOT" "$CLONE" 2>"$SANDBOX/clone.err"; then
|
|
112
|
+
record "shallow clone succeeds" "true"
|
|
113
|
+
else
|
|
114
|
+
record "shallow clone succeeds" "false" "$(oneline "$SANDBOX/clone.err")"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [[ -d "$CLONE" ]]; then
|
|
118
|
+
clone_mb="$(du -sm "$CLONE" | cut -f1)"
|
|
119
|
+
record "clone size recorded" "true" "${clone_mb} MB on disk"
|
|
120
|
+
|
|
121
|
+
# 2. Manifest discovery — install fails here if plugin.json is malformed or
|
|
122
|
+
# points at a skills directory the clone does not contain.
|
|
123
|
+
if node -e '
|
|
124
|
+
const fs = require("fs"), path = require("path");
|
|
125
|
+
const root = process.argv[1];
|
|
126
|
+
const plugin = JSON.parse(fs.readFileSync(path.join(root, ".claude-plugin/plugin.json"), "utf8"));
|
|
127
|
+
const market = JSON.parse(fs.readFileSync(path.join(root, ".claude-plugin/marketplace.json"), "utf8"));
|
|
128
|
+
if (!plugin.name) throw new Error("plugin.json has no name");
|
|
129
|
+
if (!Array.isArray(market.plugins) || market.plugins.length === 0) throw new Error("marketplace.json lists no plugins");
|
|
130
|
+
const skills = typeof plugin.skills === "string" ? plugin.skills : "./skills/";
|
|
131
|
+
const dir = path.join(root, skills);
|
|
132
|
+
if (!fs.existsSync(dir)) throw new Error("skills dir missing from clone: " + skills);
|
|
133
|
+
if (fs.readdirSync(dir).length === 0) throw new Error("skills dir is empty in clone");
|
|
134
|
+
' "$CLONE" 2>"$SANDBOX/manifest.err"; then
|
|
135
|
+
record "plugin manifests resolve in the clone" "true"
|
|
136
|
+
else
|
|
137
|
+
record "plugin manifests resolve in the clone" "false" "$(oneline "$SANDBOX/manifest.err")"
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# 3. Every shipped skill must carry name+description frontmatter, or the
|
|
141
|
+
# host silently drops it at discovery time.
|
|
142
|
+
bad_skills=0
|
|
143
|
+
skill_count=0
|
|
144
|
+
while IFS= read -r skill; do
|
|
145
|
+
skill_count=$((skill_count + 1))
|
|
146
|
+
head -n 1 "$skill" | grep -q '^---$' || { bad_skills=$((bad_skills + 1)); continue; }
|
|
147
|
+
fm="$(sed -n '2,/^---$/p' "$skill")"
|
|
148
|
+
grep -q '^name:' <<< "$fm" || { bad_skills=$((bad_skills + 1)); continue; }
|
|
149
|
+
grep -q '^description:' <<< "$fm" || bad_skills=$((bad_skills + 1))
|
|
150
|
+
done < <(find "$CLONE/skills" -name SKILL.md -type f 2>/dev/null | sort)
|
|
151
|
+
|
|
152
|
+
if [[ "$skill_count" -eq 0 ]]; then
|
|
153
|
+
record "shipped skills discoverable" "false" "no SKILL.md files in clone"
|
|
154
|
+
else
|
|
155
|
+
record "shipped skills discoverable" \
|
|
156
|
+
"$([[ "$bad_skills" -eq 0 ]] && echo true || echo false)" \
|
|
157
|
+
"$skill_count skills, $bad_skills malformed"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
# 4. Writer pass against an empty HOME, per host. --dry-run plans every
|
|
161
|
+
# write without touching disk; --no-graph keeps it offline.
|
|
162
|
+
hosts="$(cd "$CLONE" && node -e '
|
|
163
|
+
const { hosts } = require("./cli/src/hosts");
|
|
164
|
+
console.log(hosts.map((h) => h.id).join(" "));
|
|
165
|
+
' 2>/dev/null || true)"
|
|
166
|
+
|
|
167
|
+
if [[ -z "$hosts" ]]; then
|
|
168
|
+
record "host list enumerable" "false" "cli/src/hosts did not load"
|
|
169
|
+
else
|
|
170
|
+
record "host list enumerable" "true" "$hosts"
|
|
171
|
+
for host in $hosts; do
|
|
172
|
+
if (cd "$PROJECT" && HOME="$FAKE_HOME" node "$CLONE/cli/bin/draft.js" \
|
|
173
|
+
install "$host" --dry-run --no-graph >"$SANDBOX/$host.log" 2>&1); then
|
|
174
|
+
record "install --dry-run: $host" "true"
|
|
175
|
+
else
|
|
176
|
+
record "install --dry-run: $host" "false" "$(lastlines "$SANDBOX/$host.log" 3)"
|
|
177
|
+
fi
|
|
178
|
+
done
|
|
179
|
+
# A dry run that writes is worse than one that fails — it means the real
|
|
180
|
+
# installer's plan/apply split leaks. Both destinations must be checked:
|
|
181
|
+
# codex and opencode default to project scope and write AGENTS.md into the
|
|
182
|
+
# cwd, so a HOME-only assertion misses the likeliest leak.
|
|
183
|
+
record "dry run left HOME untouched" \
|
|
184
|
+
"$([[ -z "$(ls -A "$FAKE_HOME")" ]] && echo true || echo false)" \
|
|
185
|
+
"$(ls -A "$FAKE_HOME" | tr '\n' ' ')"
|
|
186
|
+
record "dry run left the project dir untouched" \
|
|
187
|
+
"$([[ -z "$(ls -A "$PROJECT")" ]] && echo true || echo false)" \
|
|
188
|
+
"$(ls -A "$PROJECT" | tr '\n' ' ')"
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
# 5. Engine fetcher is present and self-documenting (never invoked here —
|
|
192
|
+
# it downloads).
|
|
193
|
+
if [[ -x "$CLONE/scripts/fetch-memory-engine.sh" ]]; then
|
|
194
|
+
record "graph engine fetcher shipped and executable" "true"
|
|
195
|
+
else
|
|
196
|
+
record "graph engine fetcher shipped and executable" "false" "scripts/fetch-memory-engine.sh missing or not executable"
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
if [[ "$EMIT_JSON" -eq 1 ]]; then
|
|
201
|
+
printf '{"sandbox":"%s","failures":%d,"checks":[' "$SANDBOX" "${#FAILURES[@]}"
|
|
202
|
+
sep=""
|
|
203
|
+
for r in "${RESULTS[@]}"; do
|
|
204
|
+
IFS='|' read -r name ok detail <<< "$r"
|
|
205
|
+
# json_escape (from _lib.sh) also handles backslashes and control chars —
|
|
206
|
+
# a Windows path or a stack trace in a captured stderr line used to emit
|
|
207
|
+
# invalid JSON.
|
|
208
|
+
printf '%s{"name":"%s","ok":%s,"detail":"%s"}' "$sep" "$(json_escape "$name")" "$ok" "$(json_escape "$detail")"
|
|
209
|
+
sep=","
|
|
210
|
+
done
|
|
211
|
+
printf ']}\n'
|
|
212
|
+
else
|
|
213
|
+
echo ""
|
|
214
|
+
if [[ "${#FAILURES[@]}" -eq 0 ]]; then
|
|
215
|
+
echo "Install path healthy — ${#RESULTS[@]} checks passed."
|
|
216
|
+
else
|
|
217
|
+
echo "Install path BROKEN — ${#FAILURES[@]} of ${#RESULTS[@]} checks failed:"
|
|
218
|
+
printf ' - %s\n' "${FAILURES[@]}"
|
|
219
|
+
fi
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
[[ "${#FAILURES[@]}" -eq 0 ]] || exit 1
|
|
223
|
+
exit 0
|