@drafthq/draft 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +28 -0
- package/README.md +2 -2
- package/cli/src/hosts/cursor.js +35 -5
- package/cli/src/installer.js +20 -0
- package/cli/src/lib/cursor-registry.js +122 -0
- package/cli/src/lib/marker.js +93 -0
- package/cli/src/lib/plugin-manifest.js +20 -0
- package/core/methodology.md +1 -1
- package/core/shared/condensation.md +3 -2
- package/core/shared/git-report-metadata.md +3 -2
- package/core/shared/graph-query.md +4 -3
- package/core/shared/tool-resolver.md +71 -4
- package/core/templates/okf/ai-context-index.md +48 -0
- package/core/templates/okf/concept.md +54 -0
- package/core/templates/okf/index.md +40 -0
- package/core/templates/okf/section-index.md +25 -0
- package/core/templates/plan.md +3 -2
- package/integrations/agents/AGENTS.md +792 -102
- package/integrations/copilot/.github/copilot-instructions.md +792 -102
- package/package.json +3 -2
- package/scripts/lib.sh +10 -0
- package/scripts/tools/graph-preflight.sh +259 -0
- package/scripts/tools/okf-render-views.sh +373 -0
- package/scripts/tools/okf-validate.sh +204 -0
- package/scripts/tools/resolve-tools.sh +78 -0
- package/skills/adr/SKILL.md +3 -2
- package/skills/bughunt/SKILL.md +10 -1
- package/skills/coverage/SKILL.md +8 -3
- package/skills/debug/SKILL.md +16 -5
- package/skills/decompose/SKILL.md +29 -12
- package/skills/deep-review/SKILL.md +19 -6
- package/skills/deploy-checklist/SKILL.md +6 -5
- package/skills/graph/SKILL.md +15 -6
- package/skills/impact/SKILL.md +12 -1
- package/skills/implement/SKILL.md +20 -4
- package/skills/init/SKILL.md +36 -10
- package/skills/init/references/architecture-spec.md +17 -6
- package/skills/init/references/okf-emitter.md +223 -0
- package/skills/learn/SKILL.md +15 -4
- package/skills/quick-review/SKILL.md +13 -3
- package/skills/review/SKILL.md +32 -8
- package/skills/standup/SKILL.md +3 -2
- package/skills/status/SKILL.md +3 -2
- package/skills/tech-debt/SKILL.md +20 -6
- package/skills/upload/SKILL.md +3 -2
- package/integrations/copilot/.github/copilot-instructions.md.7iDz8X +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.DoBdtd +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.McGoBW +0 -122
- package/integrations/copilot/.github/copilot-instructions.md.VsPyLB +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.XAVr7D +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.YoFVFa +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.a9DeW0 +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.oxQs3B +0 -91
- package/integrations/copilot/.github/copilot-instructions.md.ww33Ly +0 -91
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okf-render-views.sh — render the demoted views from an OKF taxonomy bundle.
|
|
3
|
+
#
|
|
4
|
+
# The wiki/ bundle is the source of truth. This produces the two derived,
|
|
5
|
+
# human-facing views deterministically (so they never drift from the bundle and
|
|
6
|
+
# carry zero extra maintenance):
|
|
7
|
+
# 1. architecture.md — a single linear concatenation of every concept page,
|
|
8
|
+
# frontmatter stripped, in canonical section order, with a banner + TOC.
|
|
9
|
+
# This is the onboarding "read one doc" view (demoted, not deleted).
|
|
10
|
+
# 2. Concept Map — a routing table injected between the
|
|
11
|
+
# <!-- CONCEPT-MAP:START --> / <!-- CONCEPT-MAP:END --> markers in
|
|
12
|
+
# wiki/index.md (and optionally another index-root file).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# okf-render-views.sh <BUNDLE_DIR> --arch-out <FILE> [--concept-map-into <FILE>]
|
|
16
|
+
#
|
|
17
|
+
# BUNDLE_DIR is the wiki/ directory. Exit 0 ok, 1 error, 2 bundle not found.
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
# shellcheck source=scripts/tools/_lib.sh
|
|
22
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
23
|
+
|
|
24
|
+
BUNDLE=""
|
|
25
|
+
ARCH_OUT=""
|
|
26
|
+
WEB_OUT=""
|
|
27
|
+
CMAP_INTO=()
|
|
28
|
+
|
|
29
|
+
usage() {
|
|
30
|
+
cat <<'EOF'
|
|
31
|
+
okf-render-views.sh — render architecture.md + Concept Map + HTML viewer from an OKF bundle.
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
okf-render-views.sh <BUNDLE_DIR> [--arch-out FILE] [--concept-map-into FILE]... [--web FILE]
|
|
35
|
+
|
|
36
|
+
Flags:
|
|
37
|
+
--arch-out FILE Write the rendered linear architecture.md here.
|
|
38
|
+
--concept-map-into FILE Inject the Concept Map between the CONCEPT-MAP markers
|
|
39
|
+
in FILE (repeatable: e.g. wiki/index.md and ai-context.md).
|
|
40
|
+
--web FILE Write a self-contained, offline HTML viewer (single file:
|
|
41
|
+
all pages inlined, built-in markdown renderer, sidebar +
|
|
42
|
+
search). Double-click to open — no server, no internet.
|
|
43
|
+
--help Show this help.
|
|
44
|
+
|
|
45
|
+
Requires jq (already a Draft prereq) for --web. Exit 0 ok, 1 error, 2 bundle not found.
|
|
46
|
+
EOF
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
while [[ $# -gt 0 ]]; do
|
|
50
|
+
case "$1" in
|
|
51
|
+
--arch-out) ARCH_OUT="$2"; shift 2;;
|
|
52
|
+
--concept-map-into) CMAP_INTO+=("$2"); shift 2;;
|
|
53
|
+
--web) WEB_OUT="$2"; shift 2;;
|
|
54
|
+
--help|-h) usage; exit 0;;
|
|
55
|
+
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
56
|
+
*)
|
|
57
|
+
if [[ -z "$BUNDLE" ]]; then BUNDLE="$1"; else echo "Unexpected arg: $1" >&2; exit 1; fi
|
|
58
|
+
shift
|
|
59
|
+
;;
|
|
60
|
+
esac
|
|
61
|
+
done
|
|
62
|
+
|
|
63
|
+
[[ -n "$BUNDLE" ]] || { usage >&2; exit 1; }
|
|
64
|
+
[[ -d "$BUNDLE" ]] || { echo "ERROR: bundle directory not found: $BUNDLE" >&2; exit 2; }
|
|
65
|
+
BUNDLE="${BUNDLE%/}"
|
|
66
|
+
|
|
67
|
+
# Canonical section order for the linear render. Sections not present are skipped.
|
|
68
|
+
SECTIONS=(overview systems features reference entrypoints)
|
|
69
|
+
|
|
70
|
+
# Emit bundle-relative page paths in canonical order: for each section, its
|
|
71
|
+
# index.md first, then the rest alphabetically. Pages outside these sections
|
|
72
|
+
# (e.g. log.md, the bundle root index.md) are excluded from the linear view.
|
|
73
|
+
ordered_pages() {
|
|
74
|
+
local sec dir f
|
|
75
|
+
for sec in "${SECTIONS[@]}"; do
|
|
76
|
+
dir="$BUNDLE/$sec"
|
|
77
|
+
[[ -d "$dir" ]] || continue
|
|
78
|
+
[[ -f "$dir/index.md" ]] && echo "$sec/index.md"
|
|
79
|
+
while IFS= read -r f; do
|
|
80
|
+
[[ "$(basename "$f")" == "index.md" ]] && continue
|
|
81
|
+
echo "$sec/${f##*/}"
|
|
82
|
+
done < <(find "$dir" -maxdepth 1 -type f -name '*.md' | sort)
|
|
83
|
+
done
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Strip YAML frontmatter from a page (leading --- ... --- block on line 1).
|
|
87
|
+
strip_frontmatter() {
|
|
88
|
+
awk '
|
|
89
|
+
NR==1 && /^---$/ { fm=1; next }
|
|
90
|
+
fm && /^---$/ { fm=0; next }
|
|
91
|
+
!fm { print }
|
|
92
|
+
' "$1"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# --- 1. Render architecture.md ---
|
|
96
|
+
render_architecture() {
|
|
97
|
+
local out="$1"
|
|
98
|
+
local tmp; tmp="$(mktemp)"
|
|
99
|
+
{
|
|
100
|
+
echo "---"
|
|
101
|
+
echo "generated_by: \"draft:init (okf-render-views.sh)\""
|
|
102
|
+
echo "view: rendered"
|
|
103
|
+
echo "source_of_truth: \"wiki/\""
|
|
104
|
+
echo "---"
|
|
105
|
+
echo ""
|
|
106
|
+
echo "# Architecture (Rendered View)"
|
|
107
|
+
echo ""
|
|
108
|
+
echo "> **Generated** from the \`wiki/\` OKF bundle — do not edit by hand."
|
|
109
|
+
echo "> The bundle is the source of truth; this is the single-document linear"
|
|
110
|
+
echo "> view for onboarding. Regenerate with \`okf-render-views.sh\`."
|
|
111
|
+
echo ""
|
|
112
|
+
echo "## Contents"
|
|
113
|
+
echo ""
|
|
114
|
+
# TOC from page titles.
|
|
115
|
+
local rel title sec last_sec=""
|
|
116
|
+
while IFS= read -r rel; do
|
|
117
|
+
[[ -z "$rel" ]] && continue
|
|
118
|
+
sec="${rel%%/*}"
|
|
119
|
+
if [[ "$sec" != "$last_sec" ]]; then
|
|
120
|
+
echo "- **${sec}/**"
|
|
121
|
+
last_sec="$sec"
|
|
122
|
+
fi
|
|
123
|
+
title="$(get_yaml_field "$BUNDLE/$rel" title)"
|
|
124
|
+
[[ -n "$title" ]] || title="$rel"
|
|
125
|
+
local anchor; anchor="$(printf '%s' "$title" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-')"
|
|
126
|
+
anchor="${anchor#-}"; anchor="${anchor%-}"
|
|
127
|
+
echo " - [${title}](#${anchor})"
|
|
128
|
+
done < <(ordered_pages)
|
|
129
|
+
echo ""
|
|
130
|
+
# Body: each page, frontmatter stripped.
|
|
131
|
+
while IFS= read -r rel; do
|
|
132
|
+
[[ -z "$rel" ]] && continue
|
|
133
|
+
echo ""
|
|
134
|
+
echo "---"
|
|
135
|
+
echo ""
|
|
136
|
+
strip_frontmatter "$BUNDLE/$rel"
|
|
137
|
+
done < <(ordered_pages)
|
|
138
|
+
} >"$tmp"
|
|
139
|
+
mv "$tmp" "$out"
|
|
140
|
+
echo "rendered architecture view → $out ($(ordered_pages | grep -c . ) pages)"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# --- 2. Build the Concept Map table (stdout) ---
|
|
144
|
+
build_concept_map() {
|
|
145
|
+
echo "| Concept | Type | Open it when… |"
|
|
146
|
+
echo "|---------|------|---------------|"
|
|
147
|
+
local rel type title desc
|
|
148
|
+
while IFS= read -r -d '' page; do
|
|
149
|
+
rel="${page#"$BUNDLE/"}"
|
|
150
|
+
[[ "$(basename "$rel")" == "index.md" ]] && continue
|
|
151
|
+
type="$(get_yaml_field "$page" type)"
|
|
152
|
+
[[ -n "$type" ]] || continue
|
|
153
|
+
title="$(get_yaml_field "$page" title)"
|
|
154
|
+
[[ -n "$title" ]] || title="$rel"
|
|
155
|
+
# description may be a folded (>) block — take the first non-empty body line.
|
|
156
|
+
desc="$(awk '
|
|
157
|
+
NR==1&&/^---$/{fm=1;next} fm&&/^---$/{exit}
|
|
158
|
+
fm && /^description:/ { collect=1; sub(/^description:[[:space:]]*>?[[:space:]]*/,""); if($0!=""){print; exit} next }
|
|
159
|
+
fm && collect { sub(/^[[:space:]]+/,""); if($0!=""){print; exit} }
|
|
160
|
+
' "$page")"
|
|
161
|
+
echo "| [${title}](${rel}) | ${type} | ${desc} |"
|
|
162
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Inject the Concept Map between markers in a target file (path may be relative
|
|
166
|
+
# to BUNDLE: links in the map are bundle-relative, so the target should resolve
|
|
167
|
+
# them — wiki/index.md works directly; an index root above wiki/ should prefix).
|
|
168
|
+
inject_concept_map() {
|
|
169
|
+
local target="$1" map="$2"
|
|
170
|
+
[[ -f "$target" ]] || { echo "WARN: concept-map target not found: $target" >&2; return 0; }
|
|
171
|
+
if ! grep -q 'CONCEPT-MAP:START' "$target" || ! grep -q 'CONCEPT-MAP:END' "$target"; then
|
|
172
|
+
echo "WARN: $target has no CONCEPT-MAP markers — skipping injection" >&2
|
|
173
|
+
return 0
|
|
174
|
+
fi
|
|
175
|
+
local tmp; tmp="$(mktemp)"
|
|
176
|
+
awk -v mapfile="$map" '
|
|
177
|
+
/<!-- CONCEPT-MAP:START -->/ { print; while ((getline line < mapfile) > 0) print line; close(mapfile); skip=1; next }
|
|
178
|
+
/<!-- CONCEPT-MAP:END -->/ { skip=0 }
|
|
179
|
+
!skip { print }
|
|
180
|
+
' "$target" >"$tmp"
|
|
181
|
+
mv "$tmp" "$target"
|
|
182
|
+
echo "injected Concept Map → $target"
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# --- 3. Render a self-contained offline HTML viewer (single file) ---
|
|
186
|
+
# All pages are inlined as JSON; a small built-in markdown renderer draws them in
|
|
187
|
+
# the browser. No server, no internet, no CDN. jq encodes page content safely
|
|
188
|
+
# (and we neutralize any literal </ so embedded "</script>" can't break parsing).
|
|
189
|
+
render_web() {
|
|
190
|
+
local out="$1"
|
|
191
|
+
command -v jq >/dev/null 2>&1 || { echo "ERROR: --web requires jq" >&2; return 1; }
|
|
192
|
+
local tmp; tmp="$(mktemp)"
|
|
193
|
+
|
|
194
|
+
cat >"$tmp" <<'HTML_HEAD'
|
|
195
|
+
<!doctype html>
|
|
196
|
+
<html lang="en">
|
|
197
|
+
<head>
|
|
198
|
+
<meta charset="utf-8">
|
|
199
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
200
|
+
<title>Knowledge Bundle</title>
|
|
201
|
+
<style>
|
|
202
|
+
:root { --bg:#0f1115; --panel:#161a22; --ink:#d7dce5; --muted:#8a93a6; --accent:#6ea8fe; --border:#262c38; --code:#1b2030; }
|
|
203
|
+
* { box-sizing: border-box; }
|
|
204
|
+
body { margin:0; font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; color:var(--ink); background:var(--bg); }
|
|
205
|
+
#app { display:flex; min-height:100vh; }
|
|
206
|
+
#side { width:300px; flex:0 0 300px; background:var(--panel); border-right:1px solid var(--border); height:100vh; overflow:auto; position:sticky; top:0; padding:14px; }
|
|
207
|
+
#side h1 { font-size:14px; margin:0 0 10px; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; }
|
|
208
|
+
#search { width:100%; padding:8px 10px; margin-bottom:12px; background:var(--code); border:1px solid var(--border); border-radius:6px; color:var(--ink); }
|
|
209
|
+
.sec { font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin:14px 0 4px; }
|
|
210
|
+
.nav a { display:block; padding:4px 8px; color:var(--ink); text-decoration:none; border-radius:5px; font-size:13.5px; }
|
|
211
|
+
.nav a:hover { background:var(--code); }
|
|
212
|
+
.nav a.active { background:var(--accent); color:#0b0e14; }
|
|
213
|
+
.nav a .ty { float:right; font-size:10px; color:var(--muted); }
|
|
214
|
+
.nav a.active .ty { color:#0b0e14; }
|
|
215
|
+
#main { flex:1; max-width:900px; padding:32px 44px; }
|
|
216
|
+
#content h1,#content h2,#content h3 { line-height:1.25; }
|
|
217
|
+
#content h1 { font-size:28px; border-bottom:1px solid var(--border); padding-bottom:8px; }
|
|
218
|
+
#content a { color:var(--accent); }
|
|
219
|
+
#content code { background:var(--code); padding:2px 5px; border-radius:4px; font-size:90%; }
|
|
220
|
+
#content pre { background:var(--code); border:1px solid var(--border); border-radius:8px; padding:12px 14px; overflow:auto; }
|
|
221
|
+
#content pre code { background:none; padding:0; }
|
|
222
|
+
#content pre.mermaid-src { border-left:3px solid var(--accent); }
|
|
223
|
+
#content pre.mermaid-src::before { content:"⬡ Mermaid diagram (source)"; display:block; color:var(--muted); font-size:11px; margin-bottom:6px; }
|
|
224
|
+
#content table { border-collapse:collapse; width:100%; margin:14px 0; font-size:13.5px; }
|
|
225
|
+
#content th,#content td { border:1px solid var(--border); padding:6px 9px; text-align:left; vertical-align:top; }
|
|
226
|
+
#content th { background:var(--code); }
|
|
227
|
+
#content blockquote { border-left:3px solid var(--border); margin:12px 0; padding:2px 14px; color:var(--muted); }
|
|
228
|
+
#content hr { border:none; border-top:1px solid var(--border); margin:22px 0; }
|
|
229
|
+
.crumb { color:var(--muted); font-size:12px; margin-bottom:8px; }
|
|
230
|
+
</style>
|
|
231
|
+
</head>
|
|
232
|
+
<body>
|
|
233
|
+
<div id="app">
|
|
234
|
+
<nav id="side">
|
|
235
|
+
<h1>Knowledge Bundle</h1>
|
|
236
|
+
<input id="search" placeholder="Search…" autocomplete="off">
|
|
237
|
+
<div id="nav" class="nav"></div>
|
|
238
|
+
</nav>
|
|
239
|
+
<main id="main"><div id="content"></div></main>
|
|
240
|
+
</div>
|
|
241
|
+
<script>
|
|
242
|
+
HTML_HEAD
|
|
243
|
+
|
|
244
|
+
# Inline page data: PAGES[rel] = {title, type, md}, plus ORDER (index first).
|
|
245
|
+
{
|
|
246
|
+
echo "const PAGES = {"
|
|
247
|
+
while IFS= read -r -d '' page; do
|
|
248
|
+
local rel title type
|
|
249
|
+
rel="${page#"$BUNDLE/"}"
|
|
250
|
+
title="$(get_yaml_field "$page" title)"; [[ -n "$title" ]] || title="$rel"
|
|
251
|
+
type="$(get_yaml_field "$page" type)"
|
|
252
|
+
printf '%s: {"title": %s, "type": %s, "md": %s},\n' \
|
|
253
|
+
"$(jq -Rn --arg v "$rel" '$v')" \
|
|
254
|
+
"$(jq -Rn --arg v "$title" '$v')" \
|
|
255
|
+
"$(jq -Rn --arg v "$type" '$v')" \
|
|
256
|
+
"$(strip_frontmatter "$page" | jq -Rs . | sed 's#</#<\\/#g')"
|
|
257
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
|
|
258
|
+
echo "};"
|
|
259
|
+
# ORDER: bundle root index.md first, then everything else sorted.
|
|
260
|
+
echo "const ORDER = Object.keys(PAGES).sort(function(a,b){"
|
|
261
|
+
echo " if(a==='index.md') return -1; if(b==='index.md') return 1;"
|
|
262
|
+
echo " return a<b?-1:a>b?1:0; });"
|
|
263
|
+
} >>"$tmp"
|
|
264
|
+
|
|
265
|
+
cat >>"$tmp" <<'HTML_TAIL'
|
|
266
|
+
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
|
|
267
|
+
function resolve(base, href){
|
|
268
|
+
if(/^[a-z]+:\/\//.test(href)||href[0]==='#') return href;
|
|
269
|
+
var dir = base.indexOf('/')<0 ? '' : base.replace(/\/[^/]*$/,'');
|
|
270
|
+
var parts = (dir? dir.split('/'):[]).concat(href.split('/')), out=[];
|
|
271
|
+
for(var i=0;i<parts.length;i++){ var p=parts[i];
|
|
272
|
+
if(p==='..') out.pop(); else if(p!=='.'&&p!=='') out.push(p); }
|
|
273
|
+
return out.join('/');
|
|
274
|
+
}
|
|
275
|
+
function inline(s, base){
|
|
276
|
+
s = s.replace(/`([^`]+)`/g, function(m,c){return '<code>'+esc(c)+'</code>';});
|
|
277
|
+
s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, function(m,t,u){
|
|
278
|
+
if(/^[a-z]+:\/\//.test(u)) return '<a href="'+u+'" target="_blank" rel="noopener">'+t+'</a>';
|
|
279
|
+
var key=resolve(base,u);
|
|
280
|
+
if(PAGES[key]) return '<a href="#'+key+'" data-nav="'+key+'">'+t+'</a>';
|
|
281
|
+
return '<span title="'+esc(u)+'">'+t+'</span>';
|
|
282
|
+
});
|
|
283
|
+
s = s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>');
|
|
284
|
+
s = s.replace(/(^|[^*])\*([^*\n]+)\*/g,'$1<em>$2</em>');
|
|
285
|
+
return s;
|
|
286
|
+
}
|
|
287
|
+
function render(md, base){
|
|
288
|
+
// Pull fenced code blocks out first so their contents aren't block-parsed.
|
|
289
|
+
var blocks=[], src=md.replace(/```(\w*)\n([\s\S]*?)```/g,function(m,lang,body){
|
|
290
|
+
var cls = lang==='mermaid' ? ' class="mermaid-src"' : '';
|
|
291
|
+
blocks.push('<pre'+cls+'><code>'+esc(body.replace(/\n$/,''))+'</code></pre>');
|
|
292
|
+
return 'BLOCK'+(blocks.length-1)+'';
|
|
293
|
+
});
|
|
294
|
+
var lines=src.split('\n'), out='', i=0, list='', tbl=[];
|
|
295
|
+
function closeList(){ if(list){ out+='</'+list+'>'; list=''; } }
|
|
296
|
+
function flushTbl(){
|
|
297
|
+
if(!tbl.length) return;
|
|
298
|
+
var rows=tbl.filter(function(r){return !/^\s*\|?[\s:|-]+\|?\s*$/.test(r);});
|
|
299
|
+
out+='<table>';
|
|
300
|
+
rows.forEach(function(r,ri){
|
|
301
|
+
var cells=r.replace(/^\||\|$/g,'').split('|');
|
|
302
|
+
out+='<tr>'+cells.map(function(c){var t=ri===0?'th':'td';return '<'+t+'>'+inline(c.trim(),base)+'</'+t+'>';}).join('')+'</tr>';
|
|
303
|
+
});
|
|
304
|
+
out+='</table>'; tbl=[];
|
|
305
|
+
}
|
|
306
|
+
for(;i<lines.length;i++){
|
|
307
|
+
var ln=lines[i];
|
|
308
|
+
if(/^\s*\|.*\|\s*$/.test(ln)){ closeList(); tbl.push(ln); continue; } else flushTbl();
|
|
309
|
+
var h=ln.match(/^(#{1,6})\s+(.*)$/);
|
|
310
|
+
if(h){ closeList(); out+='<h'+h[1].length+'>'+inline(esc(h[2]),base)+'</h'+h[1].length+'>'; continue; }
|
|
311
|
+
if(/^\s*---\s*$/.test(ln)){ closeList(); out+='<hr>'; continue; }
|
|
312
|
+
if(/^\s*>\s?/.test(ln)){ closeList(); out+='<blockquote>'+inline(esc(ln.replace(/^\s*>\s?/,'')),base)+'</blockquote>'; continue; }
|
|
313
|
+
var li=ln.match(/^\s*([-*]|\d+\.)\s+(.*)$/);
|
|
314
|
+
if(li){ var want=/^\d/.test(li[1])?'ol':'ul'; if(list!==want){ closeList(); list=want; out+='<'+want+'>'; } out+='<li>'+inline(esc(li[2]),base)+'</li>'; continue; }
|
|
315
|
+
var b=ln.match(/^BLOCK(\d+)$/);
|
|
316
|
+
if(b){ closeList(); out+=blocks[+b[1]]; continue; }
|
|
317
|
+
if(/^\s*$/.test(ln)){ closeList(); continue; }
|
|
318
|
+
closeList(); out+='<p>'+inline(esc(ln),base)+'</p>';
|
|
319
|
+
}
|
|
320
|
+
flushTbl(); closeList();
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
var navEl=document.getElementById('nav'), contentEl=document.getElementById('content');
|
|
324
|
+
function section(k){ return k.indexOf('/')<0 ? '(root)' : k.split('/')[0]; }
|
|
325
|
+
function buildNav(filter){
|
|
326
|
+
navEl.innerHTML=''; var lastSec=null;
|
|
327
|
+
ORDER.forEach(function(k){
|
|
328
|
+
var p=PAGES[k];
|
|
329
|
+
if(filter && (p.title+' '+p.md).toLowerCase().indexOf(filter)<0) return;
|
|
330
|
+
var sec=section(k);
|
|
331
|
+
if(sec!==lastSec){ var s=document.createElement('div'); s.className='sec'; s.textContent=sec; navEl.appendChild(s); lastSec=sec; }
|
|
332
|
+
var a=document.createElement('a'); a.href='#'+k; a.dataset.nav=k;
|
|
333
|
+
a.innerHTML=esc(p.title)+(p.type?'<span class="ty">'+esc(p.type)+'</span>':'');
|
|
334
|
+
navEl.appendChild(a);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function show(k){
|
|
338
|
+
var p=PAGES[k]; if(!p){ k=ORDER[0]; p=PAGES[k]; }
|
|
339
|
+
contentEl.innerHTML='<div class="crumb">'+esc(k)+'</div>'+render(p.md,k);
|
|
340
|
+
document.querySelectorAll('#nav a').forEach(function(a){ a.classList.toggle('active', a.dataset.nav===k); });
|
|
341
|
+
if(location.hash.slice(1)!==k) history.replaceState(null,'','#'+k);
|
|
342
|
+
contentEl.parentElement.scrollTop=0; window.scrollTo(0,0);
|
|
343
|
+
}
|
|
344
|
+
document.addEventListener('click',function(e){ var a=e.target.closest('[data-nav]'); if(a){ e.preventDefault(); show(a.dataset.nav); } });
|
|
345
|
+
document.getElementById('search').addEventListener('input',function(e){ buildNav(e.target.value.toLowerCase().trim()); });
|
|
346
|
+
window.addEventListener('hashchange',function(){ var k=decodeURIComponent(location.hash.slice(1)); if(PAGES[k]) show(k); });
|
|
347
|
+
buildNav('');
|
|
348
|
+
show(decodeURIComponent(location.hash.slice(1)) || ORDER[0]);
|
|
349
|
+
</script>
|
|
350
|
+
</body>
|
|
351
|
+
</html>
|
|
352
|
+
HTML_TAIL
|
|
353
|
+
|
|
354
|
+
mkdir -p "$(dirname "$out")"
|
|
355
|
+
mv "$tmp" "$out"
|
|
356
|
+
echo "rendered offline HTML viewer → $out ($(find "$BUNDLE" -type f -name '*.md' | grep -c .) pages)"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
[[ -n "$ARCH_OUT" ]] && render_architecture "$ARCH_OUT"
|
|
360
|
+
|
|
361
|
+
if [[ ${#CMAP_INTO[@]} -gt 0 ]]; then
|
|
362
|
+
MAP_TMP="$(mktemp)"
|
|
363
|
+
build_concept_map >"$MAP_TMP"
|
|
364
|
+
for tgt in "${CMAP_INTO[@]}"; do
|
|
365
|
+
inject_concept_map "$tgt" "$MAP_TMP"
|
|
366
|
+
done
|
|
367
|
+
rm -f "$MAP_TMP"
|
|
368
|
+
fi
|
|
369
|
+
|
|
370
|
+
[[ -n "$WEB_OUT" ]] && render_web "$WEB_OUT"
|
|
371
|
+
|
|
372
|
+
[[ -n "$ARCH_OUT" || -n "$WEB_OUT" || ${#CMAP_INTO[@]} -gt 0 ]] || { echo "ERROR: nothing to do (pass --arch-out, --web, and/or --concept-map-into)" >&2; exit 1; }
|
|
373
|
+
exit 0
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okf-validate.sh — validate an OKF (Open Knowledge Format) taxonomy bundle.
|
|
3
|
+
#
|
|
4
|
+
# This is the deterministic ground-truth verifier for the `/draft:init` OKF
|
|
5
|
+
# emitter (DRAFT_INIT_MODE=okf). It fails the build on dangling cross-links,
|
|
6
|
+
# missing/invalid frontmatter, and an incomplete path→concept index, so a
|
|
7
|
+
# page-by-page generation pass cannot ship a structurally broken bundle.
|
|
8
|
+
#
|
|
9
|
+
# Checks:
|
|
10
|
+
# 1. BUNDLE_DIR exists and contains a root index.md.
|
|
11
|
+
# 2. Every concept page (any *.md whose frontmatter declares `type:`) carries
|
|
12
|
+
# all required OKF frontmatter keys: type, title, description, resource.
|
|
13
|
+
# 3. Every declared `type` is in the frozen code-repo vocabulary (§4 of HLD).
|
|
14
|
+
# 4. Every relative markdown cross-link ( ](path.md) ) resolves to a file that
|
|
15
|
+
# exists inside the bundle. External (http/https/mailto) and pure-anchor
|
|
16
|
+
# (#frag) links are ignored.
|
|
17
|
+
# 5. (optional) --path-index FILE: every concept page referenced by the
|
|
18
|
+
# path→concept index exists in the bundle (no dangle, no stale rename).
|
|
19
|
+
#
|
|
20
|
+
# Usage:
|
|
21
|
+
# scripts/tools/okf-validate.sh <BUNDLE_DIR> [--path-index FILE] [--json]
|
|
22
|
+
#
|
|
23
|
+
# Exit codes: 0 valid, 1 invalid (diagnostics to stderr), 2 bundle not found.
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
# shellcheck source=scripts/tools/_lib.sh
|
|
28
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
29
|
+
|
|
30
|
+
# Frozen concept `type` vocabulary for code repos. Changing this churns every
|
|
31
|
+
# generated file, so it is versioned in the bundle (index.md: okf_types_version).
|
|
32
|
+
OKF_TYPES="Subsystem Module Feature Entrypoint API DataModel Dependency ADR Runbook"
|
|
33
|
+
|
|
34
|
+
BUNDLE=""
|
|
35
|
+
PATH_INDEX=""
|
|
36
|
+
JSON=0
|
|
37
|
+
|
|
38
|
+
usage() {
|
|
39
|
+
cat <<'EOF'
|
|
40
|
+
okf-validate.sh — validate an OKF taxonomy bundle (the /draft:init OKF emitter output).
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
scripts/tools/okf-validate.sh <BUNDLE_DIR> [--path-index FILE] [--json]
|
|
44
|
+
|
|
45
|
+
Flags:
|
|
46
|
+
--path-index FILE Validate a path→concept index (JSON): every concept page it
|
|
47
|
+
names must exist in the bundle.
|
|
48
|
+
--json Emit a JSON summary instead of human diagnostics.
|
|
49
|
+
--help Show this help.
|
|
50
|
+
|
|
51
|
+
Exit 0 valid, 1 invalid, 2 bundle directory not found.
|
|
52
|
+
EOF
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
while [[ $# -gt 0 ]]; do
|
|
56
|
+
case "$1" in
|
|
57
|
+
--path-index) PATH_INDEX="$2"; shift 2;;
|
|
58
|
+
--json) JSON=1; shift;;
|
|
59
|
+
--help|-h) usage; exit 0;;
|
|
60
|
+
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
61
|
+
*)
|
|
62
|
+
if [[ -z "$BUNDLE" ]]; then BUNDLE="$1"
|
|
63
|
+
else echo "Unexpected arg: $1" >&2; exit 1
|
|
64
|
+
fi
|
|
65
|
+
shift
|
|
66
|
+
;;
|
|
67
|
+
esac
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if [[ -z "$BUNDLE" ]]; then
|
|
71
|
+
usage >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [[ ! -d "$BUNDLE" ]]; then
|
|
76
|
+
echo "ERROR: bundle directory not found: $BUNDLE" >&2
|
|
77
|
+
exit 2
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
BUNDLE="${BUNDLE%/}"
|
|
81
|
+
|
|
82
|
+
ERRORS=()
|
|
83
|
+
PAGE_COUNT=0
|
|
84
|
+
CONCEPT_COUNT=0
|
|
85
|
+
|
|
86
|
+
add_error() { ERRORS+=("$1"); }
|
|
87
|
+
|
|
88
|
+
# Does the frozen vocabulary contain $1?
|
|
89
|
+
is_known_type() {
|
|
90
|
+
local t="$1"
|
|
91
|
+
[[ " $OKF_TYPES " == *" $t "* ]]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# --- 1. Root index ---
|
|
95
|
+
if [[ ! -f "$BUNDLE/index.md" ]]; then
|
|
96
|
+
add_error "missing bundle root: $BUNDLE/index.md"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# --- 2/3. Per-page frontmatter + type vocabulary ---
|
|
100
|
+
while IFS= read -r -d '' page; do
|
|
101
|
+
PAGE_COUNT=$((PAGE_COUNT + 1))
|
|
102
|
+
rel="${page#"$BUNDLE/"}"
|
|
103
|
+
|
|
104
|
+
# A page is a "concept" only if its frontmatter declares a type.
|
|
105
|
+
type_val="$(get_yaml_field "$page" "type")"
|
|
106
|
+
[[ -z "$type_val" ]] && continue
|
|
107
|
+
CONCEPT_COUNT=$((CONCEPT_COUNT + 1))
|
|
108
|
+
|
|
109
|
+
for key in title description resource; do
|
|
110
|
+
if [[ -z "$(get_yaml_field "$page" "$key")" ]]; then
|
|
111
|
+
add_error "$rel: concept missing required frontmatter field '$key'"
|
|
112
|
+
fi
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
if ! is_known_type "$type_val"; then
|
|
116
|
+
add_error "$rel: unknown concept type '$type_val' (frozen vocab: $OKF_TYPES)"
|
|
117
|
+
fi
|
|
118
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
|
|
119
|
+
|
|
120
|
+
# --- 4. Cross-link resolution ---
|
|
121
|
+
# Scan every markdown page for relative links of the form ](target.md[#frag]).
|
|
122
|
+
# Resolve targets relative to the linking file's directory; flag dangles.
|
|
123
|
+
# Use a temp file (outside the bundle) because the scan runs in a pipeline
|
|
124
|
+
# subshell where add_error would not persist.
|
|
125
|
+
DANGLE_FILE="$(mktemp)"
|
|
126
|
+
trap 'rm -f "$DANGLE_FILE"' EXIT
|
|
127
|
+
while IFS= read -r -d '' page; do
|
|
128
|
+
pdir="$(dirname "$page")"
|
|
129
|
+
prel="${page#"$BUNDLE/"}"
|
|
130
|
+
# Extract link targets: text inside ]( ... ) up to a space or closing paren.
|
|
131
|
+
grep -oE '\]\([^) ]+\)' "$page" 2>/dev/null | sed -E 's/^\]\(//; s/\)$//' | while IFS= read -r target; do
|
|
132
|
+
[[ -z "$target" ]] && continue
|
|
133
|
+
# Skip external schemes and pure anchors.
|
|
134
|
+
case "$target" in
|
|
135
|
+
http://*|https://*|mailto:*|\#*) continue;;
|
|
136
|
+
esac
|
|
137
|
+
# Strip any #anchor and ?query.
|
|
138
|
+
target="${target%%#*}"
|
|
139
|
+
target="${target%%\?*}"
|
|
140
|
+
[[ -z "$target" ]] && continue
|
|
141
|
+
# Only resolve intra-bundle markdown/asset links (relative paths).
|
|
142
|
+
case "$target" in
|
|
143
|
+
/*) continue;; # absolute path — out of scope for bundle integrity
|
|
144
|
+
esac
|
|
145
|
+
resolved="$pdir/$target"
|
|
146
|
+
if [[ ! -e "$resolved" ]]; then
|
|
147
|
+
printf '%s\t%s\n' "$prel" "$target"
|
|
148
|
+
fi
|
|
149
|
+
done || true # inner pipeline returns non-zero at EOF; don't trip set -e
|
|
150
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z) >>"$DANGLE_FILE"
|
|
151
|
+
|
|
152
|
+
if [[ -s "$DANGLE_FILE" ]]; then
|
|
153
|
+
while IFS=$'\t' read -r prel target; do
|
|
154
|
+
add_error "$prel: dangling cross-link → '$target'"
|
|
155
|
+
done < "$DANGLE_FILE"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# --- 5. path→concept index completeness (optional) ---
|
|
159
|
+
if [[ -n "$PATH_INDEX" ]]; then
|
|
160
|
+
if [[ ! -f "$PATH_INDEX" ]]; then
|
|
161
|
+
add_error "path-index not found: $PATH_INDEX"
|
|
162
|
+
else
|
|
163
|
+
# The index maps source path → array of concept page(s), bundle-relative:
|
|
164
|
+
# { "src/auth/login.go": ["systems/auth.md"], ... }
|
|
165
|
+
# Validate the array VALUES (the pages) only. Keys are source paths and may
|
|
166
|
+
# themselves end in .md (e.g. grounding to docs/INVARIANTS.md) — those are
|
|
167
|
+
# not bundle pages, so we extract strings *inside* the [ ... ] value arrays
|
|
168
|
+
# and ignore keys entirely. Each page must exist in the bundle.
|
|
169
|
+
while IFS= read -r ref; do
|
|
170
|
+
[[ -z "$ref" ]] && continue
|
|
171
|
+
if [[ ! -f "$BUNDLE/$ref" ]]; then
|
|
172
|
+
add_error "path-index references missing concept page: $ref"
|
|
173
|
+
fi
|
|
174
|
+
done < <(grep -oE '\[[^]]*\]' "$PATH_INDEX" 2>/dev/null \
|
|
175
|
+
| grep -oE '"[^"]+\.md"' | tr -d '"' | sort -u)
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# --- Report ---
|
|
180
|
+
if [[ $JSON -eq 1 ]]; then
|
|
181
|
+
valid=true
|
|
182
|
+
[[ ${#ERRORS[@]} -eq 0 ]] || valid=false
|
|
183
|
+
printf '{"valid":%s,"bundle":"%s","pages":%d,"concepts":%d,"errors":[' \
|
|
184
|
+
"$valid" "$(json_escape "$BUNDLE")" "$PAGE_COUNT" "$CONCEPT_COUNT"
|
|
185
|
+
if [[ ${#ERRORS[@]} -gt 0 ]]; then
|
|
186
|
+
for i in "${!ERRORS[@]}"; do
|
|
187
|
+
[[ $i -gt 0 ]] && printf ','
|
|
188
|
+
printf '"%s"' "$(json_escape "${ERRORS[$i]}")"
|
|
189
|
+
done
|
|
190
|
+
fi
|
|
191
|
+
printf ']}\n'
|
|
192
|
+
else
|
|
193
|
+
if [[ ${#ERRORS[@]} -gt 0 ]]; then
|
|
194
|
+
echo "OKF bundle INVALID: $BUNDLE ($PAGE_COUNT pages, $CONCEPT_COUNT concepts)" >&2
|
|
195
|
+
for e in "${ERRORS[@]}"; do
|
|
196
|
+
echo " - $e" >&2
|
|
197
|
+
done
|
|
198
|
+
else
|
|
199
|
+
echo "OKF bundle valid: $BUNDLE ($PAGE_COUNT pages, $CONCEPT_COUNT concepts)"
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
[[ ${#ERRORS[@]} -eq 0 ]] || exit 1
|
|
204
|
+
exit 0
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resolve-tools.sh — print the absolute path to Draft's bundled scripts/tools dir.
|
|
3
|
+
#
|
|
4
|
+
# Skills run with cwd = the user's project, and ${CLAUDE_PLUGIN_ROOT} is NOT exported
|
|
5
|
+
# into skill-driven Bash, so a bare `scripts/tools/foo.sh` invocation fails. This
|
|
6
|
+
# resolver finds the plugin's helper directory regardless of how Draft was installed.
|
|
7
|
+
# See core/shared/tool-resolver.md for the canonical procedure and the inline preamble
|
|
8
|
+
# skills embed (this script is the single source of truth for the resolution order).
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# DRAFT_TOOLS="$(scripts/tools/resolve-tools.sh)" # prints the dir, exit 0 if found
|
|
12
|
+
# scripts/tools/resolve-tools.sh || echo "tools not found" # exit 1 if none exist
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
case "${1:-}" in
|
|
16
|
+
-h|--help)
|
|
17
|
+
sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//'
|
|
18
|
+
exit 0
|
|
19
|
+
;;
|
|
20
|
+
esac
|
|
21
|
+
|
|
22
|
+
newest() {
|
|
23
|
+
# Echo the lexically-newest existing match of a glob (by version sort), or nothing.
|
|
24
|
+
# shellcheck disable=SC2086
|
|
25
|
+
ls -d $1 2>/dev/null | sort -V | tail -1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
resolve() {
|
|
29
|
+
local d
|
|
30
|
+
|
|
31
|
+
# 1. Explicit override (testing / pinned installs).
|
|
32
|
+
d="${DRAFT_PLUGIN_ROOT:-}/scripts/tools"
|
|
33
|
+
[ -n "${DRAFT_PLUGIN_ROOT:-}" ] && [ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
34
|
+
|
|
35
|
+
# 1b. Dev / dogfooding: cwd IS the draft repo. Guarded by this script's own
|
|
36
|
+
# presence so it can never misfire in a user project (which has no resolve-tools.sh).
|
|
37
|
+
[ -f "$PWD/scripts/tools/resolve-tools.sh" ] && { printf '%s' "$PWD/scripts/tools"; return 0; }
|
|
38
|
+
|
|
39
|
+
# 2. Install marker written by `draft install` (authoritative).
|
|
40
|
+
local marker="$HOME/.cache/draft/plugin-root"
|
|
41
|
+
if [ -f "$marker" ]; then
|
|
42
|
+
d="$(cat "$marker" 2>/dev/null)/scripts/tools"
|
|
43
|
+
[ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# 3. ${CLAUDE_PLUGIN_ROOT} — set in hook/MCP contexts; harmless to probe.
|
|
47
|
+
d="${CLAUDE_PLUGIN_ROOT:-}/scripts/tools"
|
|
48
|
+
[ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
49
|
+
|
|
50
|
+
# 4. Claude Code's own registry (authoritative installPath; needs jq).
|
|
51
|
+
local reg="$HOME/.claude/plugins/installed_plugins.json"
|
|
52
|
+
if command -v jq >/dev/null 2>&1 && [ -f "$reg" ]; then
|
|
53
|
+
local ip
|
|
54
|
+
ip="$(jq -r '.plugins | to_entries[] | select(.key|startswith("draft@")) | .value[0].installPath' \
|
|
55
|
+
"$reg" 2>/dev/null | head -1)"
|
|
56
|
+
[ -n "$ip" ] && [ -d "$ip/scripts/tools" ] && { printf '%s' "$ip/scripts/tools"; return 0; }
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# 5. Newest cache install (glob).
|
|
60
|
+
d="$(newest "$HOME/.claude/plugins/cache/*/draft/*/scripts/tools")"
|
|
61
|
+
[ -n "$d" ] && [ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
62
|
+
|
|
63
|
+
# 6. Marketplace clone.
|
|
64
|
+
d="$(newest "$HOME/.claude/plugins/marketplaces/*draft*/scripts/tools")"
|
|
65
|
+
[ -n "$d" ] && [ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
66
|
+
|
|
67
|
+
# 7. Cursor local install.
|
|
68
|
+
d="$HOME/.cursor/plugins/local/draft/scripts/tools"
|
|
69
|
+
[ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
70
|
+
|
|
71
|
+
# 8. Dev / dogfooding (running inside the draft repo itself).
|
|
72
|
+
d="$PWD/scripts/tools"
|
|
73
|
+
[ -d "$d" ] && { printf '%s' "$d"; return 0; }
|
|
74
|
+
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolve
|
package/skills/adr/SKILL.md
CHANGED
|
@@ -54,8 +54,9 @@ Check for arguments:
|
|
|
54
54
|
If argument is `list`:
|
|
55
55
|
1. Prefer the deterministic `adr-index.sh` wrapper for the listing — it returns a structured JSON `{adrs:[{id,title,date,status,path,related_tracks}]}` derived from each ADR's frontmatter. Resolve via the canonical tool resolver (see [core/shared/tool-resolver.md](../../core/shared/tool-resolver.md)):
|
|
56
56
|
```bash
|
|
57
|
-
DRAFT_TOOLS="$
|
|
58
|
-
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$
|
|
57
|
+
DRAFT_TOOLS="$(cat ~/.cache/draft/plugin-root 2>/dev/null)/scripts/tools"
|
|
58
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/cache/*/draft/*/scripts/tools 2>/dev/null | sort -V | tail -1)"
|
|
59
|
+
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$(ls -d ~/.claude/plugins/marketplaces/*draft*/scripts/tools 2>/dev/null | tail -1)"
|
|
59
60
|
[ -d "$DRAFT_TOOLS" ] || DRAFT_TOOLS="$PWD/scripts/tools"
|
|
60
61
|
if [ -x "$DRAFT_TOOLS/adr-index.sh" ]; then
|
|
61
62
|
bash "$DRAFT_TOOLS/adr-index.sh" --root draft/adrs
|