@drafthq/draft 3.4.0 → 3.5.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 +1 -1
- package/core/templates/okf/concept.md +9 -0
- package/integrations/agents/AGENTS.md +86 -20
- package/integrations/copilot/.github/copilot-instructions.md +86 -20
- package/package.json +1 -1
- package/scripts/lib.sh +5 -0
- package/scripts/tools/okf-coverage-check.sh +192 -0
- package/scripts/tools/okf-plan-concepts.sh +296 -0
- package/scripts/tools/okf-render-views.sh +30 -0
- package/scripts/tools/okf-validate-all.sh +117 -0
- package/scripts/tools/okf-validate-quality.sh +272 -0
- package/scripts/tools/okf-validate.sh +35 -1
- package/skills/init/SKILL.md +10 -0
- package/skills/init/references/okf-emitter.md +67 -20
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okf-validate-quality.sh — deterministic per-page SEMANTIC checks for an OKF bundle.
|
|
3
|
+
#
|
|
4
|
+
# okf-validate.sh proves a page is structurally sound (frontmatter, type, links).
|
|
5
|
+
# This proves a page is actually WRITTEN — not a stub, redirect, or template
|
|
6
|
+
# leftover — so that "every module has a page" (okf-coverage-check.sh) cannot be
|
|
7
|
+
# satisfied with placeholder content. Thresholds are per concept TYPE: a
|
|
8
|
+
# Subsystem must carry a diagram and real depth; an ADR or Dependency legitimately
|
|
9
|
+
# does not, so applying one global bar would false-fail them.
|
|
10
|
+
#
|
|
11
|
+
# Pages in scope: any *.md whose frontmatter declares a frozen `type:`. Section
|
|
12
|
+
# index.md pages, log.md, and the generated coverage page are excluded.
|
|
13
|
+
#
|
|
14
|
+
# Checks (per type): required H2 sections, min body lines, >=1 mermaid block
|
|
15
|
+
# (diagram types only), min x-grounded-paths, anti-stub patterns, unreplaced
|
|
16
|
+
# template tokens, duplicate "What it is" paragraphs, and a syntax-only mermaid
|
|
17
|
+
# lint (no Node, no headless browser).
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# okf-validate-quality.sh <BUNDLE_DIR> [--strict] [--json]
|
|
21
|
+
#
|
|
22
|
+
# Exit codes: 0 pass, 1 fail, 2 bundle not found.
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
# shellcheck source=scripts/tools/_lib.sh
|
|
27
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
28
|
+
|
|
29
|
+
BUNDLE=""
|
|
30
|
+
STRICT=0
|
|
31
|
+
JSON=0
|
|
32
|
+
|
|
33
|
+
usage() {
|
|
34
|
+
cat <<'EOF'
|
|
35
|
+
okf-validate-quality.sh — per-page semantic / anti-stub checks for an OKF bundle.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
okf-validate-quality.sh <BUNDLE_DIR> [--strict] [--json]
|
|
39
|
+
|
|
40
|
+
Flags:
|
|
41
|
+
--strict Treat warnings (e.g. duplicate paragraphs) as failures.
|
|
42
|
+
--json Emit a JSON summary instead of human diagnostics.
|
|
43
|
+
--help Show this help.
|
|
44
|
+
|
|
45
|
+
Exit: 0 pass, 1 fail, 2 bundle not found.
|
|
46
|
+
EOF
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
while [[ $# -gt 0 ]]; do
|
|
50
|
+
case "$1" in
|
|
51
|
+
--strict) STRICT=1; shift;;
|
|
52
|
+
--json) JSON=1; shift;;
|
|
53
|
+
--help|-h) usage; exit 0;;
|
|
54
|
+
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
55
|
+
*) if [[ -z "$BUNDLE" ]]; then BUNDLE="$1"; else echo "Unexpected arg: $1" >&2; exit 1; fi; shift;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
[[ -n "$BUNDLE" ]] || { usage >&2; exit 1; }
|
|
60
|
+
[[ -d "$BUNDLE" ]] || { echo "ERROR: bundle directory not found: $BUNDLE" >&2; exit 2; }
|
|
61
|
+
BUNDLE="${BUNDLE%/}"
|
|
62
|
+
|
|
63
|
+
FAILURES=() # page\tcheck\tdetail
|
|
64
|
+
WARNINGS=()
|
|
65
|
+
CHECKED=0
|
|
66
|
+
|
|
67
|
+
fail() { FAILURES+=("$1"$'\t'"$2"$'\t'"$3"); }
|
|
68
|
+
warn() { WARNINGS+=("$1"$'\t'"$2"$'\t'"$3"); }
|
|
69
|
+
|
|
70
|
+
# Body (everything after the frontmatter block).
|
|
71
|
+
body_of() {
|
|
72
|
+
awk 'NR==1&&/^---$/{fm=1;next} fm&&/^---$/{fm=0;next} !fm{print}' "$1"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Count body lines after the frontmatter close (Q-LEN = "lines after frontmatter").
|
|
76
|
+
body_lines() { body_of "$1" | wc -l | tr -d ' '; }
|
|
77
|
+
|
|
78
|
+
# x-grounded-paths array length (entries inside [ ... ]).
|
|
79
|
+
grounded_count() {
|
|
80
|
+
local arr
|
|
81
|
+
arr="$(grep -m1 -E '^x-grounded-paths:' "$1" 2>/dev/null || true)"
|
|
82
|
+
[[ -z "$arr" ]] && { echo 0; return; }
|
|
83
|
+
arr="${arr#*[}"; arr="${arr%]*}"
|
|
84
|
+
arr="$(printf '%s' "$arr" | tr -d ' ')"
|
|
85
|
+
[[ -z "$arr" ]] && { echo 0; return; }
|
|
86
|
+
awk -F',' '{print NF}' <<< "$arr"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
has_section() { grep -qE "^##[[:space:]]+$1([[:space:]]|$)" "$2"; }
|
|
90
|
+
|
|
91
|
+
mermaid_block_count() { grep -cE '^[[:space:]]*```mermaid' "$1" || true; }
|
|
92
|
+
|
|
93
|
+
# Syntax-only mermaid lint: catch the breakers that silently fail previewers
|
|
94
|
+
# without spawning Node. Returns 0 clean, 1 with a reason on stdout.
|
|
95
|
+
mermaid_lint() {
|
|
96
|
+
local file="$1"
|
|
97
|
+
# Extract content of every ```mermaid ... ``` block.
|
|
98
|
+
local blocks; blocks="$(awk '
|
|
99
|
+
/^[[:space:]]*```mermaid/ {inb=1; next}
|
|
100
|
+
inb && /^[[:space:]]*```/ {inb=0; next}
|
|
101
|
+
inb {print}
|
|
102
|
+
' "$file")"
|
|
103
|
+
[[ -z "$blocks" ]] && return 0
|
|
104
|
+
# Unicode arrows.
|
|
105
|
+
if printf '%s' "$blocks" | grep -qE '→|⟶|⇒|←'; then
|
|
106
|
+
echo "unicode arrow in mermaid (use --> not →)"; return 1
|
|
107
|
+
fi
|
|
108
|
+
# '&' node chaining (common breaker).
|
|
109
|
+
if printf '%s' "$blocks" | grep -qE '[A-Za-z0-9_]\s*&\s*[A-Za-z0-9_]'; then
|
|
110
|
+
echo "'&' node chaining in mermaid"; return 1
|
|
111
|
+
fi
|
|
112
|
+
# Reserved bareword node ids.
|
|
113
|
+
if printf '%s' "$blocks" | grep -qE '(^|[[:space:]])(end|class|click|graph|subgraph)[[:space:]]*(--|==|-\.)'; then
|
|
114
|
+
echo "reserved word used as node id in mermaid"; return 1
|
|
115
|
+
fi
|
|
116
|
+
# Unbalanced subgraph/end.
|
|
117
|
+
local sg en
|
|
118
|
+
sg="$(printf '%s' "$blocks" | grep -cE '^[[:space:]]*subgraph' || true)"
|
|
119
|
+
en="$(printf '%s' "$blocks" | grep -cE '^[[:space:]]*end[[:space:]]*$' || true)"
|
|
120
|
+
if [[ "$sg" -gt 0 && "$sg" != "$en" ]]; then
|
|
121
|
+
echo "unbalanced subgraph/end ($sg subgraph, $en end)"; return 1
|
|
122
|
+
fi
|
|
123
|
+
return 0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Normalized hash of the first paragraph under "## What it is" (dup detection).
|
|
127
|
+
whatitis_hash() {
|
|
128
|
+
local p
|
|
129
|
+
p="$(awk '
|
|
130
|
+
/^##[[:space:]]+What it is/ {grab=1; next}
|
|
131
|
+
grab && /^##[[:space:]]/ {exit}
|
|
132
|
+
grab && /^[[:space:]]*$/ { if (seen) exit; else next }
|
|
133
|
+
grab {seen=1; print}
|
|
134
|
+
' "$1")"
|
|
135
|
+
[[ -z "$p" ]] && return 0
|
|
136
|
+
printf '%s' "$p" | tr '[:upper:]' '[:lower:]' | tr -s ' \t' ' ' | cksum | awk '{print $1}'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ANTI_STUB='see architecture\.md|deferred to ref-docs|\bTBD\b|TODO:[[:space:]]*document|stub page|placeholder page'
|
|
140
|
+
TOKEN_RE='\{[A-Z_]+\}'
|
|
141
|
+
|
|
142
|
+
# Per-type policy. echoes: sections|min_lines|need_mermaid|min_grounded
|
|
143
|
+
type_policy() {
|
|
144
|
+
case "$1" in
|
|
145
|
+
Subsystem|Module|Feature|Entrypoint)
|
|
146
|
+
echo "What it is;How it works;Used by;Blast radius;See also|25|1|2";;
|
|
147
|
+
API|DataModel)
|
|
148
|
+
echo "What it is;How it works;See also|18|0|1";;
|
|
149
|
+
Dependency)
|
|
150
|
+
echo "What it is;Used by|10|0|0";;
|
|
151
|
+
ADR|Runbook)
|
|
152
|
+
echo "|8|0|0";;
|
|
153
|
+
*)
|
|
154
|
+
echo "|8|0|0";;
|
|
155
|
+
esac
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Seen "What it is" hashes (hash<TAB>rel), as a temp file for bash 3.2 portability.
|
|
159
|
+
WHATIS_SEEN="$(mktemp)"
|
|
160
|
+
trap 'rm -f "$WHATIS_SEEN"' EXIT
|
|
161
|
+
|
|
162
|
+
while IFS= read -r -d '' page; do
|
|
163
|
+
rel="${page#"$BUNDLE/"}"
|
|
164
|
+
base="$(basename "$rel")"
|
|
165
|
+
# Exclusions: section/root index, log, generated coverage page.
|
|
166
|
+
[[ "$base" == "index.md" ]] && continue
|
|
167
|
+
[[ "$base" == "log.md" ]] && continue
|
|
168
|
+
[[ "$base" == "coverage.md" ]] && continue
|
|
169
|
+
grep -q '<!-- okf:coverage-generated -->' "$page" 2>/dev/null && continue
|
|
170
|
+
|
|
171
|
+
type_val="$(get_yaml_field "$page" type)"
|
|
172
|
+
[[ -z "$type_val" ]] && continue # not a concept page
|
|
173
|
+
CHECKED=$((CHECKED + 1))
|
|
174
|
+
|
|
175
|
+
IFS='|' read -r sections min_lines need_mermaid min_grounded <<< "$(type_policy "$type_val")"
|
|
176
|
+
|
|
177
|
+
# Q-SEC: required sections.
|
|
178
|
+
if [[ -n "$sections" ]]; then
|
|
179
|
+
IFS=';' read -ra secs <<< "$sections"
|
|
180
|
+
for s in "${secs[@]}"; do
|
|
181
|
+
has_section "$s" "$page" || fail "$rel" "Q-SEC" "missing required section '## $s'"
|
|
182
|
+
done
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
# Q-LEN: body length.
|
|
186
|
+
bl="$(body_lines "$page")"
|
|
187
|
+
[[ "$bl" -ge "$min_lines" ]] || fail "$rel" "Q-LEN" "body $bl lines < $min_lines for type $type_val"
|
|
188
|
+
|
|
189
|
+
# Q-DIAG: mermaid presence for diagram types.
|
|
190
|
+
if [[ "$need_mermaid" == "1" ]]; then
|
|
191
|
+
mc="$(mermaid_block_count "$page")"
|
|
192
|
+
[[ "$mc" -ge 1 ]] || fail "$rel" "Q-DIAG" "no mermaid block (required for $type_val)"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Q-MERMAID: syntax lint on any present blocks.
|
|
196
|
+
if [[ "$(mermaid_block_count "$page")" -ge 1 ]]; then
|
|
197
|
+
if reason="$(mermaid_lint "$page")"; [[ -n "$reason" ]]; then
|
|
198
|
+
fail "$rel" "Q-MERMAID" "$reason"
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
# Q-GROUND: grounded paths count.
|
|
203
|
+
if [[ "$min_grounded" -gt 0 ]]; then
|
|
204
|
+
gc="$(grounded_count "$page")"
|
|
205
|
+
[[ "$gc" -ge "$min_grounded" ]] || fail "$rel" "Q-GROUND" "x-grounded-paths $gc < $min_grounded"
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
# Q-STUB: anti-stub patterns in body.
|
|
209
|
+
if body_of "$page" | grep -qiE "$ANTI_STUB"; then
|
|
210
|
+
fail "$rel" "Q-STUB" "matches anti-stub pattern"
|
|
211
|
+
fi
|
|
212
|
+
|
|
213
|
+
# Q-TEMPLATE: unreplaced {TOKEN} placeholders.
|
|
214
|
+
if body_of "$page" | grep -qE "$TOKEN_RE"; then
|
|
215
|
+
fail "$rel" "Q-TEMPLATE" "unreplaced template token {PLACEHOLDER}"
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# Q-DUP: duplicate "What it is" opening paragraph (warning unless --strict).
|
|
219
|
+
h="$(whatitis_hash "$page")"
|
|
220
|
+
if [[ -n "$h" ]]; then
|
|
221
|
+
prev="$(awk -F'\t' -v h="$h" '$1==h{print $2; exit}' "$WHATIS_SEEN")"
|
|
222
|
+
if [[ -n "$prev" ]]; then
|
|
223
|
+
warn "$rel" "Q-DUP" "duplicate 'What it is' paragraph (matches $prev)"
|
|
224
|
+
else
|
|
225
|
+
printf '%s\t%s\n' "$h" "$rel" >> "$WHATIS_SEEN"
|
|
226
|
+
fi
|
|
227
|
+
fi
|
|
228
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
|
|
229
|
+
|
|
230
|
+
# In --strict, warnings become failures.
|
|
231
|
+
if [[ $STRICT -eq 1 && ${#WARNINGS[@]} -gt 0 ]]; then
|
|
232
|
+
for w in "${WARNINGS[@]}"; do FAILURES+=("$w"); done
|
|
233
|
+
WARNINGS=()
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if [[ $JSON -eq 1 ]]; then
|
|
237
|
+
valid=true; [[ ${#FAILURES[@]} -eq 0 ]] || valid=false
|
|
238
|
+
printf '{"valid":%s,"bundle":"%s","concepts_checked":%d,"failures":[' \
|
|
239
|
+
"$valid" "$(json_escape "$BUNDLE")" "$CHECKED"
|
|
240
|
+
for i in "${!FAILURES[@]}"; do
|
|
241
|
+
[[ $i -gt 0 ]] && printf ','
|
|
242
|
+
IFS=$'\t' read -r p c d <<< "${FAILURES[$i]}"
|
|
243
|
+
printf '{"page":"%s","check":"%s","detail":"%s"}' \
|
|
244
|
+
"$(json_escape "$p")" "$(json_escape "$c")" "$(json_escape "$d")"
|
|
245
|
+
done
|
|
246
|
+
printf '],"warnings":['
|
|
247
|
+
for i in "${!WARNINGS[@]}"; do
|
|
248
|
+
[[ $i -gt 0 ]] && printf ','
|
|
249
|
+
IFS=$'\t' read -r p c d <<< "${WARNINGS[$i]}"
|
|
250
|
+
printf '{"page":"%s","check":"%s","detail":"%s"}' \
|
|
251
|
+
"$(json_escape "$p")" "$(json_escape "$c")" "$(json_escape "$d")"
|
|
252
|
+
done
|
|
253
|
+
printf ']}\n'
|
|
254
|
+
else
|
|
255
|
+
if [[ ${#FAILURES[@]} -gt 0 ]]; then
|
|
256
|
+
echo "OKF quality FAIL: $BUNDLE ($CHECKED concepts checked)" >&2
|
|
257
|
+
for f in "${FAILURES[@]}"; do
|
|
258
|
+
IFS=$'\t' read -r p c d <<< "$f"
|
|
259
|
+
echo " - [$c] $p: $d" >&2
|
|
260
|
+
done
|
|
261
|
+
else
|
|
262
|
+
echo "OKF quality pass: $BUNDLE ($CHECKED concepts checked)"
|
|
263
|
+
fi
|
|
264
|
+
for w in "${WARNINGS[@]:-}"; do
|
|
265
|
+
[[ -z "$w" ]] && continue
|
|
266
|
+
IFS=$'\t' read -r p c d <<< "$w"
|
|
267
|
+
echo " warn [$c] $p: $d" >&2
|
|
268
|
+
done
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
[[ ${#FAILURES[@]} -eq 0 ]] || exit 1
|
|
272
|
+
exit 0
|
|
@@ -34,6 +34,8 @@ OKF_TYPES="Subsystem Module Feature Entrypoint API DataModel Dependency ADR Runb
|
|
|
34
34
|
BUNDLE=""
|
|
35
35
|
PATH_INDEX=""
|
|
36
36
|
JSON=0
|
|
37
|
+
REVERSE=0
|
|
38
|
+
STRUCTURE_ONLY=0
|
|
37
39
|
|
|
38
40
|
usage() {
|
|
39
41
|
cat <<'EOF'
|
|
@@ -44,7 +46,12 @@ Usage:
|
|
|
44
46
|
|
|
45
47
|
Flags:
|
|
46
48
|
--path-index FILE Validate a path→concept index (JSON): every concept page it
|
|
47
|
-
names must exist in the bundle.
|
|
49
|
+
names must exist in the bundle (forward check).
|
|
50
|
+
--reverse Also require every concept page (excluding section index.md)
|
|
51
|
+
to appear in at least one value array of the path-index, so
|
|
52
|
+
no page is orphaned from its source grounding. Needs --path-index.
|
|
53
|
+
--structure-only Run only the original structural checks (disables --reverse);
|
|
54
|
+
for backward-compatible Layer-1 callers.
|
|
48
55
|
--json Emit a JSON summary instead of human diagnostics.
|
|
49
56
|
--help Show this help.
|
|
50
57
|
|
|
@@ -55,6 +62,8 @@ EOF
|
|
|
55
62
|
while [[ $# -gt 0 ]]; do
|
|
56
63
|
case "$1" in
|
|
57
64
|
--path-index) PATH_INDEX="$2"; shift 2;;
|
|
65
|
+
--reverse) REVERSE=1; shift;;
|
|
66
|
+
--structure-only) STRUCTURE_ONLY=1; shift;;
|
|
58
67
|
--json) JSON=1; shift;;
|
|
59
68
|
--help|-h) usage; exit 0;;
|
|
60
69
|
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
@@ -176,6 +185,31 @@ if [[ -n "$PATH_INDEX" ]]; then
|
|
|
176
185
|
fi
|
|
177
186
|
fi
|
|
178
187
|
|
|
188
|
+
# --- 6. Reverse index: every concept page is grounded by the index (optional) ---
|
|
189
|
+
# Forward proves the index doesn't name ghosts; reverse proves no page is an
|
|
190
|
+
# orphan with no source mapping (a symptom of hand-written / off-plan pages).
|
|
191
|
+
if [[ $REVERSE -eq 1 && $STRUCTURE_ONLY -eq 0 && -n "$PATH_INDEX" && -f "$PATH_INDEX" ]]; then
|
|
192
|
+
# All pages the index maps to (its array values).
|
|
193
|
+
INDEXED_FILE="$(mktemp)"
|
|
194
|
+
grep -oE '\[[^]]*\]' "$PATH_INDEX" 2>/dev/null \
|
|
195
|
+
| grep -oE '"[^"]+\.md"' | tr -d '"' | sort -u > "$INDEXED_FILE" || true
|
|
196
|
+
while IFS= read -r -d '' page; do
|
|
197
|
+
rel="${page#"$BUNDLE/"}"
|
|
198
|
+
base="$(basename "$rel")"
|
|
199
|
+
# Section/root indexes and generated meta pages are not concept pages.
|
|
200
|
+
[[ "$base" == "index.md" ]] && continue
|
|
201
|
+
[[ "$base" == "log.md" ]] && continue
|
|
202
|
+
[[ "$base" == "coverage.md" ]] && continue
|
|
203
|
+
grep -q '<!-- okf:coverage-generated -->' "$page" 2>/dev/null && continue
|
|
204
|
+
# Only pages that declare a type are concepts.
|
|
205
|
+
[[ -z "$(get_yaml_field "$page" "type")" ]] && continue
|
|
206
|
+
if ! grep -qxF "$rel" "$INDEXED_FILE"; then
|
|
207
|
+
add_error "concept page not grounded by path-index (orphan): $rel"
|
|
208
|
+
fi
|
|
209
|
+
done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
|
|
210
|
+
rm -f "$INDEXED_FILE"
|
|
211
|
+
fi
|
|
212
|
+
|
|
179
213
|
# --- Report ---
|
|
180
214
|
if [[ $JSON -eq 1 ]]; then
|
|
181
215
|
valid=true
|
package/skills/init/SKILL.md
CHANGED
|
@@ -216,6 +216,16 @@ DRAFT_INIT_MODE="${DRAFT_INIT_MODE:-auto}"
|
|
|
216
216
|
|
|
217
217
|
The tier-gated default rests on **maintainability/readability** (one navigable concept per file, cleaner PRs, a generated `architecture.md` preserved for linear onboarding) — not on the A/B benchmark, which was accuracy-parity (`docs/audit/okf-benchmark.md`). `monolith` is **retained, not retired**: it is the tier-1/2 default, the A/B baseline, and the fallback. If `DRAFT_INIT_MODE` is unset, do not commit to a mode until **Step 1.4.5** has computed the tier.
|
|
218
218
|
|
|
219
|
+
**OKF Completeness Verification (blocking — tier 3+ `okf` mode).** Completeness is enforced by tooling, not honor system, so the wiki is generated completely for every module/sub-module/component on every run. Before promoting `draft.tmp/` → `draft/`, ALL must hold (see `references/okf-emitter.md` for the pipeline):
|
|
220
|
+
|
|
221
|
+
1. `okf-plan-concepts.sh` ran and the expected/required/deferred counts were logged **before** any page was written (the concept boundary is a tool output, not an in-context guess).
|
|
222
|
+
2. Every `required` entry in `concept-plan.json` has a non-stub page.
|
|
223
|
+
3. `okf-validate-all.sh … --plan … --strict` exits 0 (structure + per-type quality + coverage all pass).
|
|
224
|
+
4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — no required package with `fan_in ≥ floor` is **MISSING**.
|
|
225
|
+
5. On any failure: **do not** atomic-rename; surface `.state/validation-report.json`.
|
|
226
|
+
|
|
227
|
+
> **Red flag:** writing concept pages without first running `okf-plan-concepts.sh`, or finishing generation while any `required` plan entry is unwritten, is a **completeness failure** — not a stylistic one.
|
|
228
|
+
|
|
219
229
|
### Route Explicit Modes Before Initialization
|
|
220
230
|
|
|
221
231
|
If the user explicitly invoked a specialist mode, route directly:
|
|
@@ -25,7 +25,10 @@ esac
|
|
|
25
25
|
Everything else in `/draft:init` (5-phase analysis, graph snapshot, `.state/`
|
|
26
26
|
hashing, scope detection, atomic staging) is **reused unchanged**. This mode adds
|
|
27
27
|
a decomposition + serialization stage. It introduces **no new LLM analysis
|
|
28
|
-
engine
|
|
28
|
+
engine**; its deterministic helpers are `okf-plan-concepts.sh` (expected-concept
|
|
29
|
+
set), `okf-validate.sh` (structure), `okf-validate-quality.sh` (per-type
|
|
30
|
+
anti-stub), `okf-coverage-check.sh` (completeness), and `okf-validate-all.sh`
|
|
31
|
+
(the single promotion gate that runs all three), plus `okf-render-views.sh`.
|
|
29
32
|
|
|
30
33
|
## Target layout
|
|
31
34
|
|
|
@@ -118,25 +121,64 @@ Derive concepts from the graph, not by hand:
|
|
|
118
121
|
|
|
119
122
|
```
|
|
120
123
|
1. Survey → existing /draft:init 5-phase + graph snapshot (graph-snapshot.sh)
|
|
121
|
-
2. Plan →
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
2. Plan → DETERMINISTIC. okf-plan-concepts.sh derives the expected-concept
|
|
125
|
+
set from the graph (every package with fan_in ≥ floor → required
|
|
126
|
+
Subsystem/Module; entrypoints → required; below-floor / allow-defer
|
|
127
|
+
→ deferred with a reason) and writes draft.tmp/.state/concept-plan.json.
|
|
128
|
+
okf-plan-concepts.sh --repo . [--scope PATH] \
|
|
129
|
+
[--manifest FILE] [--min-fan-in 2] [--allow-defer GLOB]... \
|
|
130
|
+
--out draft.tmp/.state/concept-plan.json
|
|
131
|
+
This replaces the old in-context concept enumeration — the boundary
|
|
132
|
+
of the work is now a tool output, not an LLM judgment, so modules
|
|
133
|
+
cannot be silently dropped. LOG the counts (expected/required/
|
|
134
|
+
deferred) BEFORE writing any page. `generated_order` is topo-ish
|
|
135
|
+
(required + high-fan-in first) so forward cross-links resolve.
|
|
136
|
+
3. Generate → iterate concept-plan.generated_order; write ONE page per REQUIRED
|
|
137
|
+
entry, grounding each from the graph:
|
|
126
138
|
x-callers ← graph-callers.sh --symbol <c>
|
|
127
139
|
x-grounded-paths ← graph-impact.sh --symbol <c> (blast radius)
|
|
128
140
|
x-hotspot-score ← hotspot-rank.sh
|
|
129
141
|
overview diagrams ← mermaid-from-graph.sh
|
|
130
142
|
Record each source path → page in .state/path-to-concept.json.
|
|
143
|
+
Loop post-condition: every required concept_id has an output file.
|
|
144
|
+
⚠ Writing pages via shell heredoc without reading x-grounded-paths
|
|
145
|
+
sources, or finishing while any required entry is unwritten, is a
|
|
146
|
+
completeness failure — not a stylistic one.
|
|
131
147
|
4. Render views → ai-context.md (synopsis + Concept Map), architecture.md
|
|
132
|
-
(concatenated view), wiki/log.md (see M4).
|
|
133
|
-
5. Validate →
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
(concatenated view + coverage banner), wiki/log.md (see M4).
|
|
149
|
+
5. Validate → the promotion gate. Run all layers via the orchestrator:
|
|
150
|
+
5a. okf-validate-all.sh draft.tmp/wiki \
|
|
151
|
+
--repo . \
|
|
152
|
+
--plan draft.tmp/.state/concept-plan.json \
|
|
153
|
+
--path-index draft.tmp/.state/path-to-concept.json \
|
|
154
|
+
--strict --report draft.tmp/.state/validation-report.json
|
|
155
|
+
It runs, in order: okf-validate.sh (structure + reverse index),
|
|
156
|
+
okf-validate-quality.sh (per-type anti-stub / depth / mermaid lint),
|
|
157
|
+
okf-coverage-check.sh (every required plan entry → real page).
|
|
158
|
+
ANY layer failing ⇒ exit non-zero ⇒ DO NOT atomic-rename.
|
|
159
|
+
coverage.md (systems/coverage.md) is regenerated by the coverage
|
|
160
|
+
layer; it is tool-owned (marker <!-- okf:coverage-generated -->) —
|
|
161
|
+
never hand-author it except deferral reasons in the manifest.
|
|
162
|
+
6. Emit → mv draft.tmp/ draft/ ONLY IF step 5 exit 0 ; update .state/.
|
|
163
|
+
On failure keep draft.tmp/ and surface validation-report.json.
|
|
138
164
|
```
|
|
139
165
|
|
|
166
|
+
### Validation report schema (`.state/validation-report.json`)
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "valid": false, "bundle": "draft.tmp/wiki",
|
|
170
|
+
"layers": { "structure": "pass", "quality": "pass", "coverage": "fail" } }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Component manifest (optional — `--manifest FILE`)
|
|
174
|
+
|
|
175
|
+
When the graph engine is unavailable (or a repo wants an authoritative list), pass
|
|
176
|
+
a plain-text manifest: one component name per line, `#` comments and blanks ignored.
|
|
177
|
+
Every listed component becomes a REQUIRED concept; `--allow-defer GLOB` still moves
|
|
178
|
+
matches to deferred. Without a manifest the plan comes from the graph, and only if
|
|
179
|
+
both are unavailable does it fall back to a heuristic top-level-dir scan (which it
|
|
180
|
+
marks `degraded: true`).
|
|
181
|
+
|
|
140
182
|
Page bodies are LLM-narrated for readability **but** the graph-derived
|
|
141
183
|
frontmatter and the `Blast radius`/`Used by` sections are deterministic. To keep
|
|
142
184
|
incremental carry-forward byte-identical (open decision 2), cache the narrated
|
|
@@ -190,17 +232,22 @@ section `index.md` tables are the injection slots for the routing tables.
|
|
|
190
232
|
`/draft:init refresh` under `okf` mode:
|
|
191
233
|
|
|
192
234
|
```
|
|
193
|
-
1.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
6.
|
|
235
|
+
1. Re-derive the plan: okf-plan-concepts.sh (modules added since last run become
|
|
236
|
+
REQUIRED — a new package can't slip through a refresh either)
|
|
237
|
+
2. Diff hashes.json vs working tree → changed source paths
|
|
238
|
+
3. path-to-concept.json → affected concept pages
|
|
239
|
+
4. Regenerate ONLY affected concepts; carry the rest verbatim (cached narration)
|
|
240
|
+
5. Re-render ai-context.md / architecture.md / log.md (cheap; always regenerated)
|
|
241
|
+
6. Re-validate (full gate): okf-validate-all.sh on the bundle with --plan and
|
|
242
|
+
--path-index. Refresh re-runs structure + quality + coverage — a changed
|
|
243
|
+
concept must still clear the quality bar, and a newly-required module must
|
|
244
|
+
still be present.
|
|
245
|
+
7. Append log.md; update hashes.json + path-to-concept.json
|
|
200
246
|
```
|
|
201
247
|
|
|
202
248
|
A 1-file change regenerates only the concept(s) that file grounds. Unchanged
|
|
203
|
-
concepts are byte-identical across runs.
|
|
249
|
+
concepts are byte-identical across runs. The full gate still runs, so refresh
|
|
250
|
+
cannot promote a bundle that a newly-added module left incomplete.
|
|
204
251
|
|
|
205
252
|
## Backward compatibility (§9)
|
|
206
253
|
|