@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
|
@@ -74,6 +74,29 @@ record() {
|
|
|
74
74
|
violation_count=$((violation_count + 1))
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
# Numeric reader for metadata.json:hygiene_budget.* (read_json_str is string-only).
|
|
78
|
+
read_json_num() {
|
|
79
|
+
local file="$1" key="$2"
|
|
80
|
+
[[ -f "$file" ]] || return 0
|
|
81
|
+
grep -oE "\"$key\"[[:space:]]*:[[:space:]]*-?[0-9]+" "$file" 2>/dev/null \
|
|
82
|
+
| head -1 | grep -oE '\-?[0-9]+$' || true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# TBD budget for every top-level doc of one track; cap < 0 means unlimited.
|
|
86
|
+
check_tbd_budget() {
|
|
87
|
+
local track_dir="$1" rel_track="$2" cap="$3" kind="$4" detail_suffix="$5"
|
|
88
|
+
(( cap >= 0 )) || return 0
|
|
89
|
+
while IFS= read -r f; do
|
|
90
|
+
local rel_file="${f#"$track_dir/"}"
|
|
91
|
+
local count
|
|
92
|
+
count="$(grep -oE "$TBD_RE" "$f" 2>/dev/null | wc -l | tr -d ' ' || true)"
|
|
93
|
+
if (( count > cap )); then
|
|
94
|
+
record "$rel_track" "$kind" "$rel_file" "0" \
|
|
95
|
+
"$count TBD sentinel(s) $detail_suffix"
|
|
96
|
+
fi
|
|
97
|
+
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
98
|
+
}
|
|
99
|
+
|
|
77
100
|
scan_one_track() {
|
|
78
101
|
local track_dir="$1"
|
|
79
102
|
local rel_track="${track_dir#"$REPO_ROOT/"}"
|
|
@@ -138,31 +161,28 @@ scan_one_track() {
|
|
|
138
161
|
done < "$f"
|
|
139
162
|
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
140
163
|
|
|
141
|
-
# 4. TBD budget per-doc.
|
|
164
|
+
# 4. TBD budget per-doc. Caps come from metadata.json:hygiene_budget when
|
|
165
|
+
# present (draft_tbd_cap / ready_for_review_tbd_cap; -1 = unlimited),
|
|
166
|
+
# falling back to the historical defaults.
|
|
142
167
|
case "$meta_status" in
|
|
143
|
-
|
|
168
|
+
archived) ;;
|
|
169
|
+
draft)
|
|
170
|
+
local draft_cap
|
|
171
|
+
draft_cap="$(read_json_num "$meta" "draft_tbd_cap")"
|
|
172
|
+
[[ "$draft_cap" =~ ^-?[0-9]+$ ]] || draft_cap=-1
|
|
173
|
+
check_tbd_budget "$track_dir" "$rel_track" "$draft_cap" "tbd-over-cap" \
|
|
174
|
+
"(cap $draft_cap at status=$meta_status)"
|
|
175
|
+
;;
|
|
144
176
|
ready-for-review|in_progress)
|
|
145
|
-
local tbd_cap
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (( count > tbd_cap )); then
|
|
151
|
-
record "$rel_track" "tbd-over-cap" "$rel_file" "0" \
|
|
152
|
-
"$count TBD sentinel(s) (cap $tbd_cap at status=$meta_status)"
|
|
153
|
-
fi
|
|
154
|
-
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
177
|
+
local tbd_cap
|
|
178
|
+
tbd_cap="$(read_json_num "$meta" "ready_for_review_tbd_cap")"
|
|
179
|
+
[[ "$tbd_cap" =~ ^-?[0-9]+$ ]] || tbd_cap=3
|
|
180
|
+
check_tbd_budget "$track_dir" "$rel_track" "$tbd_cap" "tbd-over-cap" \
|
|
181
|
+
"(cap $tbd_cap at status=$meta_status)"
|
|
155
182
|
;;
|
|
156
183
|
completed)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
local count
|
|
160
|
-
count="$(grep -oE "$TBD_RE" "$f" 2>/dev/null | wc -l | tr -d ' ')"
|
|
161
|
-
if (( count > 0 )); then
|
|
162
|
-
record "$rel_track" "tbd-in-completed" "$rel_file" "0" \
|
|
163
|
-
"$count TBD sentinel(s) at status=completed"
|
|
164
|
-
fi
|
|
165
|
-
done < <(find "$track_dir" -maxdepth 1 -type f -name '*.md')
|
|
184
|
+
check_tbd_budget "$track_dir" "$rel_track" 0 "tbd-in-completed" \
|
|
185
|
+
"at status=completed"
|
|
166
186
|
;;
|
|
167
187
|
esac
|
|
168
188
|
|
|
@@ -34,14 +34,14 @@ Output (per file): {path, lang, category, is_test, is_generated, loc, module}
|
|
|
34
34
|
|
|
35
35
|
Language is inferred from extension and shebang. A file is classified as a
|
|
36
36
|
test when its path or basename matches common test patterns. It is flagged
|
|
37
|
-
generated when the first 5 lines contain "Code generated"
|
|
38
|
-
Module is the first path segment under root.
|
|
37
|
+
generated when the first 5 lines contain "Code generated", "DO NOT EDIT",
|
|
38
|
+
"@generated", or "autogenerated". Module is the first path segment under root.
|
|
39
39
|
EOF
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
while [[ $# -gt 0 ]]; do
|
|
43
43
|
case "$1" in
|
|
44
|
-
--root) ROOT="$2"; shift 2;;
|
|
44
|
+
--root) ROOT="${2:?--root requires a value}"; shift 2;;
|
|
45
45
|
--json) FORMAT="json"; shift;;
|
|
46
46
|
--jsonl) FORMAT="jsonl"; shift;;
|
|
47
47
|
--help|-h) usage; exit 0;;
|
|
@@ -40,7 +40,7 @@ EOF
|
|
|
40
40
|
|
|
41
41
|
while [[ $# -gt 0 ]]; do
|
|
42
42
|
case "$1" in
|
|
43
|
-
--repo) REPO="$2"; shift 2;;
|
|
43
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
44
44
|
--help|-h) usage; exit 0;;
|
|
45
45
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
46
46
|
esac
|
|
@@ -51,32 +51,33 @@ if [[ ! -d "$REPO" ]]; then
|
|
|
51
51
|
exit 1
|
|
52
52
|
fi
|
|
53
53
|
|
|
54
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
55
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
56
|
-
|
|
57
54
|
unavailable() { echo '{"cycles":[],"source":"unavailable"}'; exit 2; }
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
61
|
-
|
|
62
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
63
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
56
|
+
graph_bootstrap "$REPO" || unavailable
|
|
64
57
|
|
|
65
58
|
# 2- and 3-node CALLS cycles. Cypher lives in _graph_queries.sh (label-agnostic;
|
|
66
59
|
# the Phase 0 fix — code units are mostly :Method, and CALLS only connects
|
|
67
60
|
# callables). LIMIT 100 caps each, so results are a sample, not exhaustive.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
#
|
|
62
|
+
# A failed query is not an empty result. Substituting `{}` and carrying on
|
|
63
|
+
# reported `source:"memory-graph"` with zero cycles — a clean bill of health
|
|
64
|
+
# derived from a query that never ran, which is exactly the true-negative
|
|
65
|
+
# confusion Guardrail 4 exists to prevent, and the one failure mode a caller
|
|
66
|
+
# cannot detect. Both queries must land for the sample to mean anything, so
|
|
67
|
+
# either failure routes to `unavailable`. gq_run only ever echoes validated
|
|
68
|
+
# JSON, so nothing downstream needs a second shape guard.
|
|
69
|
+
R2="$(gq_run "$PROJECT" "$(gq_q_cycles2)")" || unavailable
|
|
70
|
+
R3="$(gq_run "$PROJECT" "$(gq_q_cycles3)")" || unavailable
|
|
74
71
|
|
|
72
|
+
# Self-loops and duplicate orderings are filtered here rather than in Cypher:
|
|
73
|
+
# the engine rejects `a.x < b.x`, which is what the query used to rely on.
|
|
74
|
+
# A 2-cycle comes back twice (A,B and B,A), hence the doubled LIMIT upstream.
|
|
75
75
|
jq -n --argjson r2 "$R2" --argjson r3 "$R3" '
|
|
76
|
-
( ((($r2.rows) // []) | length) >=
|
|
76
|
+
( ((($r2.rows) // []) | length) >= 200
|
|
77
77
|
or ((($r3.rows) // []) | length) >= 100 ) as $trunc
|
|
78
|
+
| ( ($r2.rows // []) | map(select(.[0] != .[1])) | unique_by(sort) ) as $two
|
|
78
79
|
| {
|
|
79
|
-
cycles: (
|
|
80
|
+
cycles: ($two + ($r3.rows // [])),
|
|
80
81
|
truncated: $trunc,
|
|
81
82
|
source: "memory-graph"
|
|
82
83
|
}'
|
|
@@ -37,8 +37,13 @@ LOCK_FILE="${METRICS_DIR}/metrics.lock"
|
|
|
37
37
|
|
|
38
38
|
payload="${1:-}"
|
|
39
39
|
|
|
40
|
-
#
|
|
41
|
-
|
|
40
|
+
# Trim trailing whitespace/newlines so the closing-brace strip below matches
|
|
41
|
+
# even when the caller passes a payload with a stray trailing newline.
|
|
42
|
+
payload="${payload%"${payload##*[![:space:]]}"}"
|
|
43
|
+
|
|
44
|
+
# Validate that a payload was provided and looks like a JSON object — a
|
|
45
|
+
# payload not ending in '}' would corrupt the NDJSON file, so drop it.
|
|
46
|
+
if [[ -z "${payload}" || "${payload}" != *\} ]]; then
|
|
42
47
|
exit 0
|
|
43
48
|
fi
|
|
44
49
|
|
|
@@ -83,6 +83,9 @@ fix_file() {
|
|
|
83
83
|
return 1 # already clean — no change on disk
|
|
84
84
|
fi
|
|
85
85
|
|
|
86
|
+
# mktemp creates 0600 and `mv` swaps the inode — carry the destination's mode
|
|
87
|
+
# across, or an atomic rewrite silently strips the file's permissions.
|
|
88
|
+
apply_dest_mode "$_tmp" "$file"
|
|
86
89
|
mv -f "$_tmp" "$file"
|
|
87
90
|
return 0
|
|
88
91
|
}
|
|
@@ -46,8 +46,8 @@ EOF
|
|
|
46
46
|
|
|
47
47
|
while [[ $# -gt 0 ]]; do
|
|
48
48
|
case "$1" in
|
|
49
|
-
--state) STATE_FILE="$2"; shift 2;;
|
|
50
|
-
--root) ROOT="$2"; shift 2;;
|
|
49
|
+
--state) STATE_FILE="${2:?--state requires a value}"; shift 2;;
|
|
50
|
+
--root) ROOT="${2:?--root requires a value}"; shift 2;;
|
|
51
51
|
--help|-h) usage; exit 0;;
|
|
52
52
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
53
53
|
esac
|
|
@@ -66,12 +66,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
66
66
|
--yaml) FORMAT="yaml"; shift;;
|
|
67
67
|
--json) FORMAT="json"; shift;;
|
|
68
68
|
--project-metadata) WRITE_PROJECT_METADATA=1; shift;;
|
|
69
|
-
--project) PROJECT="$2"; shift 2;;
|
|
70
|
-
--module) MODULE="$2"; shift 2;;
|
|
71
|
-
--track-id) TRACK_ID="$2"; shift 2;;
|
|
72
|
-
--generated-by) GENERATED_BY="$2"; shift 2;;
|
|
73
|
-
--base) BASE_BRANCH="$2"; shift 2;;
|
|
74
|
-
--output-dir) OUTPUT_DIR="$2"; shift 2;;
|
|
69
|
+
--project) PROJECT="${2:?--project requires a value}"; shift 2;;
|
|
70
|
+
--module) MODULE="${2:?--module requires a value}"; shift 2;;
|
|
71
|
+
--track-id) TRACK_ID="${2:?--track-id requires a value}"; shift 2;;
|
|
72
|
+
--generated-by) GENERATED_BY="${2:?--generated-by requires a value}"; shift 2;;
|
|
73
|
+
--base) BASE_BRANCH="${2:?--base requires a value}"; shift 2;;
|
|
74
|
+
--output-dir) OUTPUT_DIR="${2:?--output-dir requires a value}"; shift 2;;
|
|
75
75
|
--help|-h) usage; exit 0;;
|
|
76
76
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
77
77
|
esac
|
|
@@ -127,6 +127,8 @@ if (( WRITE_PROJECT_METADATA )); then
|
|
|
127
127
|
fi
|
|
128
128
|
CMD="${GENERATED_BY:-draft:init}"
|
|
129
129
|
_tmp="$(mktemp "${META_FILE}.XXXXXX")"
|
|
130
|
+
# errexit on a failed write would otherwise leave metadata.json.XXXXXX behind.
|
|
131
|
+
trap 'rm -f "$_tmp"' EXIT
|
|
130
132
|
cat > "$_tmp" <<EOF
|
|
131
133
|
{
|
|
132
134
|
"\$schema": "Draft Project Metadata Schema",
|
|
@@ -150,7 +152,11 @@ if (( WRITE_PROJECT_METADATA )); then
|
|
|
150
152
|
"synced_to_commit": "$FULL_SHA"
|
|
151
153
|
}
|
|
152
154
|
EOF
|
|
155
|
+
# mktemp creates 0600 and `mv` swaps the inode — carry the destination's mode
|
|
156
|
+
# across, or an atomic rewrite silently strips the file's permissions.
|
|
157
|
+
apply_dest_mode "$_tmp" "$META_FILE"
|
|
153
158
|
mv -f "$_tmp" "$META_FILE"
|
|
159
|
+
trap - EXIT
|
|
154
160
|
echo "Written: $META_FILE (synced_to_commit=$FULL_SHA)"
|
|
155
161
|
exit 0
|
|
156
162
|
fi
|
|
@@ -42,7 +42,7 @@ EOF
|
|
|
42
42
|
|
|
43
43
|
while [[ $# -gt 0 ]]; do
|
|
44
44
|
case "$1" in
|
|
45
|
-
--repo) REPO="$2"; shift 2;;
|
|
45
|
+
--repo) REPO="${2:?--repo 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
|
|
@@ -53,20 +53,14 @@ if [[ ! -d "$REPO" ]]; then
|
|
|
53
53
|
exit 1
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
57
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
58
|
-
|
|
59
56
|
unavailable() { echo '{"source":"unavailable"}'; exit 2; }
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
63
|
-
|
|
64
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
65
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
58
|
+
graph_bootstrap "$REPO" || unavailable
|
|
66
59
|
|
|
67
|
-
ARCH_JSON="$(memory_cli get_architecture "
|
|
60
|
+
ARCH_JSON="$(memory_cli get_architecture "$(jq -n --arg p "$PROJECT" '{project:$p, aspects:["all"]}')" || true)"
|
|
68
61
|
[[ -n "$ARCH_JSON" ]] || unavailable
|
|
69
62
|
|
|
70
63
|
# Validate it parses and looks like an architecture object before emitting.
|
|
64
|
+
# Tag with source like every other graph-*.sh wrapper (universal contract).
|
|
71
65
|
echo "$ARCH_JSON" | jq -e '.total_nodes != null' >/dev/null 2>&1 || unavailable
|
|
72
|
-
echo "$ARCH_JSON" | jq '.'
|
|
66
|
+
echo "$ARCH_JSON" | jq '. + {source:"memory-graph"}'
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
# scripts/tools/graph-callers.sh --repo DIR --symbol NAME
|
|
10
10
|
# [--transitive[=N]] [--prod-only] [--qualified]
|
|
11
11
|
#
|
|
12
|
-
# Output: JSON {symbol, callers:[{name,file[,hop]}], status, source}.
|
|
12
|
+
# Output: JSON {symbol, callers:[{name,file,qualified[,hop]}], status, source}.
|
|
13
|
+
# `file` is always a path (empty when the engine does not carry one — the
|
|
14
|
+
# trace_path expander behind --transitive returns no file_path); the symbol's
|
|
15
|
+
# qualified name is in `qualified`, never smuggled into `file`.
|
|
13
16
|
# source = "memory-graph" | "unavailable"
|
|
14
17
|
# status = "ok" | "no-edges" | "no-match" | "unavailable"
|
|
15
18
|
# (fail-loud: distinguishes node-not-found from node-has-no-callers
|
|
@@ -54,8 +57,8 @@ EOF
|
|
|
54
57
|
|
|
55
58
|
while [[ $# -gt 0 ]]; do
|
|
56
59
|
case "$1" in
|
|
57
|
-
--repo) REPO="$2"; shift 2;;
|
|
58
|
-
--symbol) SYMBOL="$2"; shift 2;;
|
|
60
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
61
|
+
--symbol) SYMBOL="${2:?--symbol requires a value}"; shift 2;;
|
|
59
62
|
--transitive) TRANSITIVE=1; shift;;
|
|
60
63
|
--transitive=*) TRANSITIVE=1; DEPTH="${1#*=}"; shift;;
|
|
61
64
|
--prod-only) PROD_ONLY=1; shift;;
|
|
@@ -69,20 +72,13 @@ done
|
|
|
69
72
|
[[ -n "$SYMBOL" ]] || { echo "ERROR: --symbol is required" >&2; usage >&2; exit 1; }
|
|
70
73
|
[[ "$DEPTH" =~ ^[0-9]+$ ]] || { echo "ERROR: --transitive depth must be a non-negative integer" >&2; exit 1; }
|
|
71
74
|
|
|
72
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
73
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
74
|
-
|
|
75
75
|
unavailable() {
|
|
76
76
|
jq -n --arg s "$SYMBOL" '{symbol:$s, callers:[], status:"unavailable", source:"unavailable"}' 2>/dev/null \
|
|
77
77
|
|| echo '{"callers":[],"status":"unavailable","source":"unavailable"}'
|
|
78
78
|
exit 2
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
83
|
-
|
|
84
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
85
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
81
|
+
graph_bootstrap "$REPO" || unavailable
|
|
86
82
|
|
|
87
83
|
SYM_ESC="$(gq_escape "$SYMBOL")"
|
|
88
84
|
|
|
@@ -95,12 +91,11 @@ if [[ "$TRANSITIVE" -eq 1 ]]; then
|
|
|
95
91
|
echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable
|
|
96
92
|
N="$(echo "$RES" | jq -r '(.callers // []) | length' 2>/dev/null || echo 0)"
|
|
97
93
|
if [[ "$N" -gt 0 ]]; then STATUS="ok"; else
|
|
98
|
-
|
|
99
|
-
if [[ -n "$EX" && "$(gq_rows_len "$EX")" -gt 0 ]]; then STATUS="no-edges"; else STATUS="no-match"; fi
|
|
94
|
+
STATUS="$(gq_symbol_status "$PROJECT" "$SYM_ESC" '{"rows":[]}')"
|
|
100
95
|
fi
|
|
101
96
|
echo "$RES" | jq --arg s "$SYMBOL" --arg st "$STATUS" '
|
|
102
97
|
{symbol:$s,
|
|
103
|
-
callers: [ (.callers // [])[] | {name:.name, file:(.qualified_name // ""), hop:(.hop // 1)} ],
|
|
98
|
+
callers: [ (.callers // [])[] | {name:.name, file:(.file_path // ""), qualified:(.qualified_name // ""), hop:(.hop // 1)} ],
|
|
104
99
|
status:$st, source:"memory-graph"}'
|
|
105
100
|
exit 0
|
|
106
101
|
fi
|
|
@@ -121,5 +116,5 @@ STATUS="$(gq_symbol_status "$PROJECT" "$SYM_ESC" "$RES")"
|
|
|
121
116
|
|
|
122
117
|
echo "$RES" | jq --arg s "$SYMBOL" --arg st "$STATUS" '
|
|
123
118
|
{symbol:$s,
|
|
124
|
-
callers: [ (.rows // [])[] | {name:.[0], file
|
|
119
|
+
callers: [ (.rows // [])[] | {name:.[0], file:(.[1] // ""), qualified:""} ],
|
|
125
120
|
status:$st, source:"memory-graph"}'
|
|
@@ -43,8 +43,8 @@ EOF
|
|
|
43
43
|
|
|
44
44
|
while [[ $# -gt 0 ]]; do
|
|
45
45
|
case "$1" in
|
|
46
|
-
--repo) REPO="$2"; shift 2;;
|
|
47
|
-
--file) FILE="$2"; shift 2;;
|
|
46
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
47
|
+
--file) FILE="${2:?--file requires a value}"; shift 2;;
|
|
48
48
|
--help|-h) usage; exit 0;;
|
|
49
49
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
50
50
|
esac
|
|
@@ -52,16 +52,9 @@ done
|
|
|
52
52
|
|
|
53
53
|
[[ -d "$REPO" ]] || { echo "ERROR: --repo '$REPO' is not a directory" >&2; exit 1; }
|
|
54
54
|
|
|
55
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
56
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
57
|
-
|
|
58
55
|
unavailable() { echo '{"imports":[],"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_imports)" || true)"
|
|
67
60
|
[[ -n "$RES" ]] || unavailable
|
|
@@ -42,9 +42,9 @@ EOF
|
|
|
42
42
|
|
|
43
43
|
while [[ $# -gt 0 ]]; do
|
|
44
44
|
case "$1" in
|
|
45
|
-
--repo) REPO="$2"; shift 2;;
|
|
46
|
-
--symbol) SYMBOL="$2"; shift 2;;
|
|
47
|
-
--type) TYPE="$2"; shift 2;;
|
|
45
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
46
|
+
--symbol) SYMBOL="${2:?--symbol requires a value}"; shift 2;;
|
|
47
|
+
--type) TYPE="${2:?--type requires a value}"; shift 2;;
|
|
48
48
|
--help|-h) usage; exit 0;;
|
|
49
49
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
50
50
|
esac
|
|
@@ -56,9 +56,6 @@ if [[ -n "$SYMBOL" && -n "$TYPE" ]]; then
|
|
|
56
56
|
fi
|
|
57
57
|
[[ -n "$SYMBOL" || -n "$TYPE" ]] || { echo "ERROR: provide --symbol or --type" >&2; usage >&2; exit 1; }
|
|
58
58
|
|
|
59
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
60
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
61
|
-
|
|
62
59
|
unavailable() {
|
|
63
60
|
if [[ -n "$TYPE" ]]; then
|
|
64
61
|
jq -n --arg t "$TYPE" '{type:$t, raisers:[], status:"unavailable", source:"unavailable"}' 2>/dev/null \
|
|
@@ -70,11 +67,7 @@ unavailable() {
|
|
|
70
67
|
exit 2
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
command -v jq >/dev/null 2>&1 || unavailable
|
|
75
|
-
|
|
76
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
77
|
-
[[ -n "$PROJECT" ]] || unavailable
|
|
70
|
+
graph_bootstrap "$REPO" || unavailable
|
|
78
71
|
|
|
79
72
|
if [[ -n "$TYPE" ]]; then
|
|
80
73
|
SYM_ESC="$(gq_escape "$TYPE")"
|
|
@@ -43,9 +43,9 @@ EOF
|
|
|
43
43
|
|
|
44
44
|
while [[ $# -gt 0 ]]; do
|
|
45
45
|
case "$1" in
|
|
46
|
-
--repo) REPO="$2"; shift 2;;
|
|
47
|
-
--symbol) SYMBOL="$2"; shift 2;;
|
|
48
|
-
--derived) DERIVED="$2"; shift 2;;
|
|
46
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
47
|
+
--symbol) SYMBOL="${2:?--symbol requires a value}"; shift 2;;
|
|
48
|
+
--derived) DERIVED="${2:?--derived 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
|
|
@@ -56,16 +56,9 @@ if [[ -n "$SYMBOL" && -n "$DERIVED" ]]; then
|
|
|
56
56
|
echo "ERROR: use either --symbol or --derived, not both" >&2; exit 1
|
|
57
57
|
fi
|
|
58
58
|
|
|
59
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
60
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
61
|
-
|
|
62
59
|
unavailable() { echo '{"edges":[],"status":"unavailable","source":"unavailable"}'; exit 2; }
|
|
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
|
if [[ -n "$SYMBOL" ]]; then
|
|
71
64
|
SYM_ESC="$(gq_escape "$SYMBOL")"; Q="$(gq_q_inherits_sym "$SYM_ESC")"; PROBE="$SYM_ESC"
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
# Usage:
|
|
10
10
|
# scripts/tools/graph-impact.sh --repo DIR (--file PATH | --symbol NAME) [--depth N]
|
|
11
11
|
#
|
|
12
|
-
# Output: JSON {target, kind, impacted:[{name,file,hop}], source}.
|
|
12
|
+
# Output: JSON {target, kind, impacted:[{name,file,qualified,hop}], source}.
|
|
13
|
+
# `file` is always a path (empty when the engine carries none); the qualified
|
|
14
|
+
# name has its own field rather than being emitted as if it were a path.
|
|
13
15
|
# source = "memory-graph" | "unavailable"
|
|
14
16
|
#
|
|
15
17
|
# Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
|
|
@@ -43,10 +45,10 @@ EOF
|
|
|
43
45
|
|
|
44
46
|
while [[ $# -gt 0 ]]; do
|
|
45
47
|
case "$1" in
|
|
46
|
-
--repo) REPO="$2"; shift 2;;
|
|
47
|
-
--file) FILE="$2"; shift 2;;
|
|
48
|
-
--symbol) SYMBOL="$2"; shift 2;;
|
|
49
|
-
--depth) DEPTH="$2"; shift 2;;
|
|
48
|
+
--repo) REPO="${2:?--repo requires a value}"; shift 2;;
|
|
49
|
+
--file) FILE="${2:?--file requires a value}"; shift 2;;
|
|
50
|
+
--symbol) SYMBOL="${2:?--symbol requires a value}"; shift 2;;
|
|
51
|
+
--depth) DEPTH="${2:?--depth requires a value}"; shift 2;;
|
|
50
52
|
--help|-h) usage; exit 0;;
|
|
51
53
|
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
52
54
|
esac
|
|
@@ -56,9 +58,6 @@ done
|
|
|
56
58
|
[[ -n "$FILE" || -n "$SYMBOL" ]] || { echo "ERROR: provide --file or --symbol" >&2; usage >&2; exit 1; }
|
|
57
59
|
[[ "$DEPTH" =~ ^[0-9]+$ ]] || { echo "ERROR: --depth must be a non-negative integer" >&2; exit 1; }
|
|
58
60
|
|
|
59
|
-
REPO_ABS="$(cd "$REPO" && pwd)"
|
|
60
|
-
SELF_REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
61
|
-
|
|
62
61
|
unavailable() {
|
|
63
62
|
local t="$1" k="$2"
|
|
64
63
|
jq -n --arg t "$t" --arg k "$k" '{target:$t, kind:$k, impacted:[], source:"unavailable"}' 2>/dev/null \
|
|
@@ -68,24 +67,27 @@ unavailable() {
|
|
|
68
67
|
|
|
69
68
|
if [[ -n "$SYMBOL" ]]; then TARGET="$SYMBOL"; KIND="symbol"; else TARGET="$FILE"; KIND="file"; fi
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
command -v jq >/dev/null 2>&1 || unavailable "$TARGET" "$KIND"
|
|
73
|
-
|
|
74
|
-
PROJECT="$(memory_ensure_index "$REPO_ABS" || true)"
|
|
75
|
-
[[ -n "$PROJECT" ]] || unavailable "$TARGET" "$KIND"
|
|
70
|
+
graph_bootstrap "$REPO" || unavailable "$TARGET" "$KIND"
|
|
76
71
|
|
|
77
72
|
if [[ -n "$SYMBOL" ]]; then
|
|
78
73
|
# direction:"both" is the reliable form (the "callers" value returns empty in this engine);
|
|
79
|
-
# we read the .callers array from it.
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
# we read the .callers array from it. Payload built with jq so a quote in
|
|
75
|
+
# --symbol can never corrupt the JSON; an engine failure is unavailable,
|
|
76
|
+
# never a fabricated empty-impact success.
|
|
77
|
+
PAYLOAD="$(jq -n --arg p "$PROJECT" --arg f "$SYMBOL" --argjson d "$DEPTH" \
|
|
78
|
+
'{project:$p, function_name:$f, depth:$d, direction:"both"}')"
|
|
79
|
+
RES="$(memory_cli trace_path "$PAYLOAD" 2>/dev/null || true)"
|
|
80
|
+
echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable "$TARGET" "$KIND"
|
|
81
|
+
echo "$RES" | jq --arg t "$TARGET" '
|
|
82
82
|
{target:$t, kind:"symbol",
|
|
83
|
-
impacted: [ (.callers // [])[] | {name:.name, file:(.qualified_name // ""), hop:(.hop // 1)} ],
|
|
83
|
+
impacted: [ (.callers // [])[] | {name:.name, file:(.file_path // ""), qualified:(.qualified_name // ""), hop:(.hop // 1)} ],
|
|
84
84
|
source:"memory-graph"}'
|
|
85
85
|
else
|
|
86
86
|
# File impact: detect_changes maps the working-tree diff to impacted symbols.
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
PAYLOAD="$(jq -n --arg p "$PROJECT" '{project:$p}')"
|
|
88
|
+
RES="$(memory_cli detect_changes "$PAYLOAD" 2>/dev/null || true)"
|
|
89
|
+
echo "$RES" | jq -e . >/dev/null 2>&1 || unavailable "$TARGET" "$KIND"
|
|
90
|
+
echo "$RES" | jq --arg t "$TARGET" '
|
|
89
91
|
{target:$t, kind:"file",
|
|
90
92
|
impacted: [ (.impacted_symbols // [])[]
|
|
91
93
|
| select((.file // "") | endswith($t) or (. == $t))
|
|
@@ -52,7 +52,7 @@ EOF
|
|
|
52
52
|
|
|
53
53
|
while [[ $# -gt 0 ]]; do
|
|
54
54
|
case "$1" in
|
|
55
|
-
--scope) SCOPE="$2"; shift 2;;
|
|
55
|
+
--scope) SCOPE="${2:?--scope requires a value}"; shift 2;;
|
|
56
56
|
--module-only) MODULE_ONLY=1; shift;;
|
|
57
57
|
--no-fetch) NO_FETCH=1; shift;;
|
|
58
58
|
--json) EMIT_JSON=1; shift;;
|
|
@@ -93,7 +93,7 @@ ensure_engine() {
|
|
|
93
93
|
engine_unavailable() {
|
|
94
94
|
if [[ "$EMIT_JSON" -eq 1 ]]; then
|
|
95
95
|
printf '{"status":"unavailable","root":"%s","scope":"%s","is_root":%s}\n' \
|
|
96
|
-
"$ROOT_ABS" "$SCOPE_ABS" "$IS_ROOT"
|
|
96
|
+
"$(json_escape "$ROOT_ABS")" "$(json_escape "$SCOPE_ABS")" "$IS_ROOT"
|
|
97
97
|
else
|
|
98
98
|
echo "WARNING: knowledge-graph engine (codebase-memory-mcp) is unavailable — no graph built." >&2
|
|
99
99
|
echo " The engine is Draft's default capability tier. Install it with:" >&2
|
|
@@ -115,7 +115,7 @@ build_snapshot() {
|
|
|
115
115
|
# Path from <module>/draft/graph back to <root>/draft/graph (module is under root).
|
|
116
116
|
root_link_relpath() {
|
|
117
117
|
local sub="${SCOPE_ABS#"$ROOT_ABS"/}"
|
|
118
|
-
local ups
|
|
118
|
+
local ups seg
|
|
119
119
|
IFS='/' read -ra seg <<< "$sub"
|
|
120
120
|
ups=$(( ${#seg[@]} + 2 ))
|
|
121
121
|
local i out=""
|
|
@@ -135,13 +135,16 @@ write_root_link() {
|
|
|
135
135
|
fi
|
|
136
136
|
root_commit="$(git -C "$ROOT_ABS" rev-parse --verify --quiet HEAD 2>/dev/null || echo none)"
|
|
137
137
|
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
138
|
+
# Every interpolated value goes through json_escape: a repo path or engine
|
|
139
|
+
# project name carrying a quote or backslash would otherwise emit a
|
|
140
|
+
# root-link.json that no consumer can parse.
|
|
138
141
|
cat > "$mod_graph/root-link.json" <<EOF
|
|
139
142
|
{
|
|
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",
|
|
143
|
+
"root_graph": "$(json_escape "$rel")",
|
|
144
|
+
"root_abs": "$(json_escape "$ROOT_ABS/draft/graph")",
|
|
145
|
+
"root_project": "$(json_escape "${root_project:-unknown}")",
|
|
146
|
+
"root_commit": "$(json_escape "$root_commit")",
|
|
147
|
+
"status": "$(json_escape "$status")",
|
|
145
148
|
"linked_at": "$ts",
|
|
146
149
|
"linked_by": "graph-init.sh",
|
|
147
150
|
"note": "Root is the authoritative whole-repo graph. Follow root_graph for cross-module understanding."
|
|
@@ -173,7 +176,8 @@ fi
|
|
|
173
176
|
|
|
174
177
|
if [[ "$EMIT_JSON" -eq 1 ]]; then
|
|
175
178
|
printf '{"status":"ok","root":"%s","scope":"%s","is_root":%s,"root_built":%s,"module_built":%s,"link_status":"%s"}\n' \
|
|
176
|
-
"$
|
|
179
|
+
"$(json_escape "$ROOT_ABS")" "$(json_escape "$SCOPE_ABS")" \
|
|
180
|
+
"$IS_ROOT" "$ROOT_BUILT" "$MODULE_BUILT" "$(json_escape "$LINK_STATUS")"
|
|
177
181
|
else
|
|
178
182
|
echo "--- graph-init ---"
|
|
179
183
|
echo "Root: $ROOT_ABS$([[ $IS_ROOT -eq 1 ]] && echo ' (this scope is the root)')"
|
|
@@ -168,9 +168,12 @@ if find_memory_bin "$REPO_ABS" "$SELF_REPO"; then
|
|
|
168
168
|
ENGINE_FOUND=true
|
|
169
169
|
VER="$("$ENGINE" --version 2>/dev/null | head -1 || echo '?')"
|
|
170
170
|
ok "Engine: $ENGINE ($VER)"
|
|
171
|
+
# Numeric-only: the value is emitted bare into the --json report, so a
|
|
172
|
+
# non-numeric field (engine output format drift) would produce invalid JSON.
|
|
171
173
|
LIMIT="$("$ENGINE" config list 2>/dev/null | awk '/auto_index_limit/{print $3}' || true)"
|
|
174
|
+
[[ "$LIMIT" =~ ^[0-9]+$ ]] || LIMIT=""
|
|
172
175
|
[[ -n "$LIMIT" ]] && info "auto_index_limit: $LIMIT (governs AUTO-index only; explicit index_repository should bypass)" || true
|
|
173
|
-
if [[ "$GIT_OK" -eq 1 &&
|
|
176
|
+
if [[ "$GIT_OK" -eq 1 && "${LIMIT:-}" =~ ^[0-9]+$ && "$TRACKED" -gt "$LIMIT" ]]; then
|
|
174
177
|
warn "Tracked files ($TRACKED) > auto_index_limit ($LIMIT) — confirm the explicit index isn't truncated near $LIMIT."
|
|
175
178
|
fi
|
|
176
179
|
else
|