@drafthq/draft 2.8.3 → 3.0.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/README.md +3 -3
- package/bin/README.md +13 -0
- package/core/methodology.md +17 -18
- package/core/shared/condensation.md +1 -1
- package/core/shared/draft-context-loading.md +4 -2
- package/core/shared/graph-query.md +4 -3
- package/core/templates/ai-context.md +1 -0
- package/core/templates/ai-profile.md +1 -0
- package/core/templates/architecture.md +1 -0
- package/core/templates/dependency-graph.md +2 -2
- package/core/templates/discovery.md +1 -0
- package/core/templates/guardrails.md +1 -0
- package/core/templates/hld.md +1 -0
- package/core/templates/lld.md +1 -0
- package/core/templates/plan.md +1 -0
- package/core/templates/product.md +1 -0
- package/core/templates/rca.md +1 -0
- package/core/templates/root-architecture.md +3 -3
- package/core/templates/root-product.md +2 -2
- package/core/templates/root-tech-stack.md +2 -2
- package/core/templates/service-index.md +3 -3
- package/core/templates/spec.md +1 -0
- package/core/templates/tech-matrix.md +2 -2
- package/core/templates/tech-stack.md +1 -0
- package/core/templates/workflow.md +1 -0
- package/integrations/agents/AGENTS.md +134 -918
- package/integrations/copilot/.github/copilot-instructions.md +134 -918
- package/package.json +1 -1
- package/scripts/lib.sh +4 -1
- package/scripts/tools/graph-init.sh +187 -0
- package/scripts/tools/graph-snapshot.sh +6 -1
- package/scripts/tools/okf-bundle.sh +141 -0
- package/scripts/tools/okf-check.sh +137 -0
- package/scripts/tools/okf-emit.sh +161 -0
- package/scripts/tools/skill-caps.conf +0 -1
- package/skills/GRAPH.md +7 -10
- package/skills/bughunt/SKILL.md +13 -0
- package/skills/discover/SKILL.md +2 -4
- package/skills/draft/SKILL.md +2 -2
- package/skills/draft/intent-mapping.md +3 -2
- package/skills/graph/SKILL.md +3 -3
- package/skills/init/SKILL.md +58 -19
- package/skills/init/references/architecture-spec.md +5 -5
- package/skills/index/SKILL.md +0 -848
package/package.json
CHANGED
package/scripts/lib.sh
CHANGED
|
@@ -24,7 +24,6 @@ TOOLS_DIR="$ROOT_DIR/scripts/tools"
|
|
|
24
24
|
SKILL_ORDER=(
|
|
25
25
|
draft
|
|
26
26
|
init
|
|
27
|
-
index
|
|
28
27
|
graph
|
|
29
28
|
new-track
|
|
30
29
|
decompose
|
|
@@ -158,6 +157,10 @@ TOOLS=(
|
|
|
158
157
|
"manage-symlinks.sh"
|
|
159
158
|
"mermaid-from-graph.sh"
|
|
160
159
|
"graph-snapshot.sh"
|
|
160
|
+
"graph-init.sh"
|
|
161
|
+
"okf-emit.sh"
|
|
162
|
+
"okf-bundle.sh"
|
|
163
|
+
"okf-check.sh"
|
|
161
164
|
"graph-impact.sh"
|
|
162
165
|
"graph-callers.sh"
|
|
163
166
|
"validate-frontmatter.sh"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# graph-init.sh — scope-aware, root-first knowledge-graph builder for /draft:init.
|
|
3
|
+
#
|
|
4
|
+
# Ensures the whole-repo "code graph knowledge memory" exists at the repository
|
|
5
|
+
# ROOT (the spine — the single structural source of truth), then builds a
|
|
6
|
+
# scope-local snapshot and links a sub-module's graph up to the root.
|
|
7
|
+
#
|
|
8
|
+
# Model:
|
|
9
|
+
# - ROOT resolution: nearest ancestor ABOVE scope containing draft/ (bounded by
|
|
10
|
+
# the git toplevel) → git toplevel → scope itself (no git / module-local).
|
|
11
|
+
# - The engine is the default capability tier. If the codebase-memory-mcp binary
|
|
12
|
+
# is missing it is fetched (blocking) unless --no-fetch or DRAFT_MEMORY_DISABLE.
|
|
13
|
+
# - Root init (scope == root): build the whole-repo snapshot at <root>/draft/graph/.
|
|
14
|
+
# - Module init (scope != root): unless --module-only, (re)build the root snapshot
|
|
15
|
+
# first (the spine — index time is accepted, incremental once warm), then build
|
|
16
|
+
# <scope>/draft/graph/ and write root-link.json pointing up to the root snapshot.
|
|
17
|
+
#
|
|
18
|
+
# The committed snapshot (draft/graph/) is the git-tracked memory; the engine's
|
|
19
|
+
# ~/.cache index is a disposable accelerator and is never committed.
|
|
20
|
+
#
|
|
21
|
+
# Usage: scripts/tools/graph-init.sh [--scope DIR] [--module-only] [--no-fetch] [--json]
|
|
22
|
+
# Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
|
|
25
|
+
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
+
# shellcheck source=_lib.sh
|
|
27
|
+
source "$TOOLS_DIR/_lib.sh"
|
|
28
|
+
|
|
29
|
+
SCOPE="."
|
|
30
|
+
MODULE_ONLY=0
|
|
31
|
+
NO_FETCH=0
|
|
32
|
+
EMIT_JSON=0
|
|
33
|
+
|
|
34
|
+
usage() {
|
|
35
|
+
cat <<'EOF'
|
|
36
|
+
graph-init.sh — scope-aware, root-first knowledge-graph builder.
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
scripts/tools/graph-init.sh [--scope DIR] [--module-only] [--no-fetch] [--json]
|
|
40
|
+
|
|
41
|
+
Flags:
|
|
42
|
+
--scope DIR Directory init was invoked in (default: cwd).
|
|
43
|
+
--module-only Do not touch the root; build only the module snapshot and mark
|
|
44
|
+
its root link "pending".
|
|
45
|
+
--no-fetch Never download the engine; degrade if it is absent (CI/tests).
|
|
46
|
+
--json Emit a machine-readable summary instead of a human report.
|
|
47
|
+
--help Show this help.
|
|
48
|
+
|
|
49
|
+
Exit 0 on success, 2 when the graph engine is unavailable (no snapshot built).
|
|
50
|
+
EOF
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
while [[ $# -gt 0 ]]; do
|
|
54
|
+
case "$1" in
|
|
55
|
+
--scope) SCOPE="$2"; shift 2;;
|
|
56
|
+
--module-only) MODULE_ONLY=1; shift;;
|
|
57
|
+
--no-fetch) NO_FETCH=1; shift;;
|
|
58
|
+
--json) EMIT_JSON=1; shift;;
|
|
59
|
+
--help|-h) usage; exit 0;;
|
|
60
|
+
*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
[[ -d "$SCOPE" ]] || { echo "ERROR: --scope '$SCOPE' is not a directory" >&2; exit 1; }
|
|
65
|
+
SCOPE_ABS="$(cd "$SCOPE" && pwd)"
|
|
66
|
+
SELF_REPO="$(cd "$TOOLS_DIR/../.." && pwd)"
|
|
67
|
+
|
|
68
|
+
# --- Resolve ROOT (bounded by the git toplevel; never escapes the repo) ---
|
|
69
|
+
GIT_TOP="$(git -C "$SCOPE_ABS" rev-parse --show-toplevel 2>/dev/null || true)"
|
|
70
|
+
resolve_root() {
|
|
71
|
+
if [[ -z "$GIT_TOP" ]]; then printf '%s' "$SCOPE_ABS"; return; fi # no git → module-local
|
|
72
|
+
local d="$SCOPE_ABS"
|
|
73
|
+
while [[ "$d" != "$GIT_TOP" && "$d" != "/" ]]; do
|
|
74
|
+
d="$(dirname "$d")"
|
|
75
|
+
if [[ "$d" != "$SCOPE_ABS" && -d "$d/draft" ]]; then printf '%s' "$d"; return; fi
|
|
76
|
+
done
|
|
77
|
+
printf '%s' "$GIT_TOP"
|
|
78
|
+
}
|
|
79
|
+
ROOT_ABS="$(resolve_root)"
|
|
80
|
+
IS_ROOT=0
|
|
81
|
+
[[ "$SCOPE_ABS" == "$ROOT_ABS" ]] && IS_ROOT=1
|
|
82
|
+
|
|
83
|
+
# --- Ensure the engine (the default tier); fetch when missing unless told not to ---
|
|
84
|
+
ensure_engine() {
|
|
85
|
+
[[ -z "${DRAFT_MEMORY_DISABLE:-}" ]] || return 1
|
|
86
|
+
find_memory_bin "$SCOPE_ABS" "$SELF_REPO" && return 0
|
|
87
|
+
[[ "$NO_FETCH" -eq 0 ]] || return 1
|
|
88
|
+
echo "Graph engine not found — fetching it (one-time download; this may take a while)..." >&2
|
|
89
|
+
"$SELF_REPO/scripts/fetch-memory-engine.sh" >&2 2>&1 || true
|
|
90
|
+
find_memory_bin "$SCOPE_ABS" "$SELF_REPO"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
engine_unavailable() {
|
|
94
|
+
if [[ "$EMIT_JSON" -eq 1 ]]; then
|
|
95
|
+
printf '{"status":"unavailable","root":"%s","scope":"%s","is_root":%s}\n' \
|
|
96
|
+
"$ROOT_ABS" "$SCOPE_ABS" "$IS_ROOT"
|
|
97
|
+
else
|
|
98
|
+
echo "WARNING: knowledge-graph engine (codebase-memory-mcp) is unavailable — no graph built." >&2
|
|
99
|
+
echo " The engine is Draft's default capability tier. Install it with:" >&2
|
|
100
|
+
echo " scripts/fetch-memory-engine.sh" >&2
|
|
101
|
+
echo " or put codebase-memory-mcp on PATH. Set DRAFT_MEMORY_DISABLE=1 to silence this." >&2
|
|
102
|
+
echo " Committed draft/graph/ snapshots (if present) still provide structural context." >&2
|
|
103
|
+
fi
|
|
104
|
+
exit 2
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Build a committed snapshot for a repo dir; returns graph-snapshot's exit code.
|
|
108
|
+
# Snapshot progress goes to stderr so stdout stays clean for --json consumers.
|
|
109
|
+
build_snapshot() {
|
|
110
|
+
local rc=0
|
|
111
|
+
"$TOOLS_DIR/graph-snapshot.sh" --repo "$1" 1>&2 || rc=$?
|
|
112
|
+
return "$rc"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Path from <module>/draft/graph back to <root>/draft/graph (module is under root).
|
|
116
|
+
root_link_relpath() {
|
|
117
|
+
local sub="${SCOPE_ABS#"$ROOT_ABS"/}"
|
|
118
|
+
local ups=2 seg
|
|
119
|
+
IFS='/' read -ra seg <<< "$sub"
|
|
120
|
+
ups=$(( ${#seg[@]} + 2 ))
|
|
121
|
+
local i out=""
|
|
122
|
+
for ((i = 0; i < ups; i++)); do out+="../"; done
|
|
123
|
+
printf '%sdraft/graph' "$out"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
write_root_link() {
|
|
127
|
+
local status="$1"
|
|
128
|
+
local mod_graph="$SCOPE_ABS/draft/graph"
|
|
129
|
+
mkdir -p "$mod_graph"
|
|
130
|
+
local rel root_project="unknown" root_commit ts schema="$ROOT_ABS/draft/graph/schema.yaml"
|
|
131
|
+
rel="$(root_link_relpath)"
|
|
132
|
+
if [[ -f "$schema" ]]; then
|
|
133
|
+
root_project="$(grep -m1 '^project:' "$schema" 2>/dev/null | sed 's/^project:[[:space:]]*//; s/^"//; s/"$//' || true)"
|
|
134
|
+
[[ -n "$root_project" ]] || root_project="unknown"
|
|
135
|
+
fi
|
|
136
|
+
root_commit="$(git -C "$ROOT_ABS" rev-parse --verify --quiet HEAD 2>/dev/null || echo none)"
|
|
137
|
+
ts="$(date -Iseconds 2>/dev/null || date)"
|
|
138
|
+
cat > "$mod_graph/root-link.json" <<EOF
|
|
139
|
+
{
|
|
140
|
+
"root_graph": "$rel",
|
|
141
|
+
"root_abs": "$ROOT_ABS/draft/graph",
|
|
142
|
+
"root_project": "${root_project:-unknown}",
|
|
143
|
+
"root_commit": "$root_commit",
|
|
144
|
+
"status": "$status",
|
|
145
|
+
"linked_at": "$ts",
|
|
146
|
+
"linked_by": "graph-init.sh",
|
|
147
|
+
"note": "Root is the authoritative whole-repo graph. Follow root_graph for cross-module understanding."
|
|
148
|
+
}
|
|
149
|
+
EOF
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
ensure_engine || engine_unavailable
|
|
153
|
+
|
|
154
|
+
ROOT_BUILT=0
|
|
155
|
+
MODULE_BUILT=0
|
|
156
|
+
LINK_STATUS="none"
|
|
157
|
+
|
|
158
|
+
if [[ "$IS_ROOT" -eq 1 ]]; then
|
|
159
|
+
build_snapshot "$ROOT_ABS" && ROOT_BUILT=1
|
|
160
|
+
else
|
|
161
|
+
if [[ "$MODULE_ONLY" -eq 0 ]]; then
|
|
162
|
+
echo "Sub-module of $ROOT_ABS — ensuring the root code-graph spine first..." >&2
|
|
163
|
+
build_snapshot "$ROOT_ABS" && ROOT_BUILT=1
|
|
164
|
+
fi
|
|
165
|
+
build_snapshot "$SCOPE_ABS" && MODULE_BUILT=1
|
|
166
|
+
if [[ -f "$ROOT_ABS/draft/graph/schema.yaml" ]]; then
|
|
167
|
+
LINK_STATUS="linked"
|
|
168
|
+
else
|
|
169
|
+
LINK_STATUS="pending"
|
|
170
|
+
fi
|
|
171
|
+
write_root_link "$LINK_STATUS"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
if [[ "$EMIT_JSON" -eq 1 ]]; then
|
|
175
|
+
printf '{"status":"ok","root":"%s","scope":"%s","is_root":%s,"root_built":%s,"module_built":%s,"link_status":"%s"}\n' \
|
|
176
|
+
"$ROOT_ABS" "$SCOPE_ABS" "$IS_ROOT" "$ROOT_BUILT" "$MODULE_BUILT" "$LINK_STATUS"
|
|
177
|
+
else
|
|
178
|
+
echo "--- graph-init ---"
|
|
179
|
+
echo "Root: $ROOT_ABS$([[ $IS_ROOT -eq 1 ]] && echo ' (this scope is the root)')"
|
|
180
|
+
[[ "$IS_ROOT" -eq 1 ]] && echo "Built whole-repo spine: $ROOT_ABS/draft/graph/"
|
|
181
|
+
if [[ "$IS_ROOT" -eq 0 ]]; then
|
|
182
|
+
[[ "$ROOT_BUILT" -eq 1 ]] && echo "Root spine: refreshed $ROOT_ABS/draft/graph/"
|
|
183
|
+
echo "Module: $SCOPE_ABS/draft/graph/"
|
|
184
|
+
echo "Root link: $LINK_STATUS ($SCOPE_ABS/draft/graph/root-link.json)"
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
exit 0
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# hotspots.jsonl fan-in-ranked symbols, one JSON object per line
|
|
14
14
|
# module-deps.mermaid co-change coupling diagram
|
|
15
15
|
# proto-map.mermaid detected-route diagram
|
|
16
|
+
# okf/ Open Knowledge Format bundle (portable markdown mirror)
|
|
16
17
|
#
|
|
17
18
|
# Usage: scripts/tools/graph-snapshot.sh [--repo DIR] [--out DIR]
|
|
18
19
|
# Exit codes: 0 OK, 1 invocation error, 2 graph engine unavailable.
|
|
@@ -76,7 +77,10 @@ echo "$ARCH" | jq '.' > "$OUT/architecture.json"
|
|
|
76
77
|
"$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram module-deps > "$OUT/module-deps.mermaid" 2>/dev/null || true
|
|
77
78
|
"$TOOLS_DIR/mermaid-from-graph.sh" --repo "$REPO_ABS" --diagram proto-map > "$OUT/proto-map.mermaid" 2>/dev/null || true
|
|
78
79
|
|
|
79
|
-
# 4.
|
|
80
|
+
# 4. OKF bundle (best-effort) — portable Open Knowledge Format mirror of the graph.
|
|
81
|
+
"$TOOLS_DIR/okf-emit.sh" --repo "$REPO_ABS" --snapshot "$OUT" --out "$OUT/okf" >/dev/null 2>&1 || true
|
|
82
|
+
|
|
83
|
+
# 5. schema.yaml — metadata + gate for skill graph use
|
|
80
84
|
NODES="$(echo "$ARCH" | jq -r '.total_nodes // 0')"
|
|
81
85
|
EDGES="$(echo "$ARCH" | jq -r '.total_edges // 0')"
|
|
82
86
|
HOTN="$(wc -l < "$OUT/hotspots.jsonl" | tr -d ' ')"
|
|
@@ -96,6 +100,7 @@ artifacts:
|
|
|
96
100
|
- hotspots.jsonl
|
|
97
101
|
- module-deps.mermaid
|
|
98
102
|
- proto-map.mermaid
|
|
103
|
+
- okf/
|
|
99
104
|
EOF
|
|
100
105
|
|
|
101
106
|
echo "Snapshot written to $OUT (nodes=$NODES edges=$EDGES hotspots=$HOTN)"
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okf-bundle.sh — make a draft/ context directory an Open Knowledge Format bundle.
|
|
3
|
+
#
|
|
4
|
+
# OKF (Google Cloud, open spec) treats a directory of markdown files with YAML
|
|
5
|
+
# frontmatter as a knowledge bundle: one file per concept, the file path is the
|
|
6
|
+
# concept's identity, concepts cross-link with markdown links, and index.md is
|
|
7
|
+
# the navigable root. Draft's project-doc files already carry the required
|
|
8
|
+
# `type` frontmatter (architecture.md, .ai-context.md, product.md, ...); this
|
|
9
|
+
# tool writes the bundle's root index.md so the whole draft/ tree is a portable,
|
|
10
|
+
# vendor-neutral OKF bundle.
|
|
11
|
+
# https://cloud.google.com/blog/products/data-analytics/how-the-open-knowledge-format-can-improve-data-sharing
|
|
12
|
+
#
|
|
13
|
+
# Writes <dir>/index.md (the OKF bundle-root index, §6/§11) linking every concept
|
|
14
|
+
# file present, the tracks, and the graph sub-bundle (graph/okf/). Validate the
|
|
15
|
+
# result with okf-check.sh (the OKF v0.1 conformance checker).
|
|
16
|
+
#
|
|
17
|
+
# Usage: scripts/tools/okf-bundle.sh [--dir DIR]
|
|
18
|
+
# Exit codes: 0 OK, 1 invocation error, 2 dir missing.
|
|
19
|
+
set -euo pipefail
|
|
20
|
+
|
|
21
|
+
DIR="draft"
|
|
22
|
+
|
|
23
|
+
usage() {
|
|
24
|
+
cat <<'EOF'
|
|
25
|
+
okf-bundle.sh — write the OKF bundle-root index.md for a draft/ context bundle.
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
scripts/tools/okf-bundle.sh [--dir DIR]
|
|
29
|
+
|
|
30
|
+
Flags:
|
|
31
|
+
--dir DIR Bundle root (default: draft).
|
|
32
|
+
--help Show this help.
|
|
33
|
+
|
|
34
|
+
Writes <dir>/index.md cross-linking every concept present, the tracks, and the
|
|
35
|
+
graph sub-bundle. Validate conformance with `okf-check.sh --dir <dir>`.
|
|
36
|
+
Exit 0 OK, 1 invocation error, 2 when <dir> is absent.
|
|
37
|
+
EOF
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
while [[ $# -gt 0 ]]; do
|
|
41
|
+
case "$1" in
|
|
42
|
+
--dir) DIR="$2"; shift 2;;
|
|
43
|
+
--help|-h) usage; exit 0;;
|
|
44
|
+
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
45
|
+
*) echo "Unexpected arg: $1" >&2; usage >&2; exit 1;;
|
|
46
|
+
esac
|
|
47
|
+
done
|
|
48
|
+
|
|
49
|
+
[[ -d "$DIR" ]] || { echo "ERROR: --dir '$DIR' is not a directory" >&2; exit 2; }
|
|
50
|
+
|
|
51
|
+
# Canonical Draft concepts, ordered: "filename|label|expected-type".
|
|
52
|
+
# Each present file is linked from index.md and checked for `type:` under --check.
|
|
53
|
+
CONCEPTS=(
|
|
54
|
+
".ai-profile.md|AI Profile|Profile"
|
|
55
|
+
".ai-context.md|AI Context Map|ContextMap"
|
|
56
|
+
"architecture.md|Architecture|Architecture"
|
|
57
|
+
"product.md|Product|Product"
|
|
58
|
+
"tech-stack.md|Tech Stack|TechStack"
|
|
59
|
+
"workflow.md|Workflow|Workflow"
|
|
60
|
+
"guardrails.md|Guardrails|Guardrails"
|
|
61
|
+
"service-index.md|Service Index|ServiceIndex"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# read_fm_field FILE FIELD — print a top-level frontmatter scalar (quotes stripped).
|
|
65
|
+
read_fm_field() {
|
|
66
|
+
awk -v field="$2" '
|
|
67
|
+
NR==1 { if ($0 != "---") exit; next }
|
|
68
|
+
/^---[[:space:]]*$/ { exit }
|
|
69
|
+
{
|
|
70
|
+
if (index($0, field ":") == 1) {
|
|
71
|
+
sub("^" field ":[[:space:]]*", "")
|
|
72
|
+
gsub(/^"|"$/, "")
|
|
73
|
+
print
|
|
74
|
+
exit
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
' "$1"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# --- write the bundle root index.md ---
|
|
81
|
+
PROJECT=""
|
|
82
|
+
for probe in architecture.md .ai-context.md product.md .ai-profile.md; do
|
|
83
|
+
if [[ -f "$DIR/$probe" ]]; then
|
|
84
|
+
PROJECT="$(read_fm_field "$DIR/$probe" project)"
|
|
85
|
+
[[ -n "$PROJECT" ]] && break
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
[[ -n "$PROJECT" && "$PROJECT" != "{PROJECT_NAME}" ]] || PROJECT="$(basename "$(cd "$DIR/.." && pwd)")"
|
|
89
|
+
|
|
90
|
+
INDEX="$DIR/index.md"
|
|
91
|
+
{
|
|
92
|
+
# Bundle-root index.md: frontmatter is permitted only to declare okf_version (§11).
|
|
93
|
+
printf -- '---\n'
|
|
94
|
+
printf 'okf_version: "0.1"\n'
|
|
95
|
+
printf -- '---\n\n'
|
|
96
|
+
printf '# %s — Draft Context Bundle\n\n' "$PROJECT"
|
|
97
|
+
printf 'Open Knowledge Format (OKF) bundle root. Each concept below is a markdown file with a `type` frontmatter field; the links form the navigable knowledge graph.\n'
|
|
98
|
+
|
|
99
|
+
# Context concepts (only if at least one is present)
|
|
100
|
+
context=""
|
|
101
|
+
for entry in "${CONCEPTS[@]}"; do
|
|
102
|
+
IFS='|' read -r fname label expected <<< "$entry"
|
|
103
|
+
[[ -f "$DIR/$fname" ]] || continue
|
|
104
|
+
t="$(read_fm_field "$DIR/$fname" type)"
|
|
105
|
+
[[ -n "$t" ]] || t="$expected"
|
|
106
|
+
d="$(read_fm_field "$DIR/$fname" description)"
|
|
107
|
+
desc="${d:-$t concept}"
|
|
108
|
+
context+="$(printf -- '* [%s](%s) - %s' "$label" "$fname" "$desc")"$'\n'
|
|
109
|
+
done
|
|
110
|
+
if [[ -n "$context" ]]; then
|
|
111
|
+
printf '\n# Context\n\n%s' "$context"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Tracks
|
|
115
|
+
if [[ -f "$DIR/tracks.md" || -d "$DIR/tracks" ]]; then
|
|
116
|
+
printf '\n# Tracks\n\n'
|
|
117
|
+
[[ -f "$DIR/tracks.md" ]] && printf -- '* [Track Index](tracks.md) - active, completed, and archived tracks\n'
|
|
118
|
+
if [[ -d "$DIR/tracks" ]]; then
|
|
119
|
+
for td in "$DIR"/tracks/*/; do
|
|
120
|
+
[[ -d "$td" ]] || continue
|
|
121
|
+
id="$(basename "$td")"
|
|
122
|
+
[[ -f "$td/spec.md" ]] || continue
|
|
123
|
+
title="$id"
|
|
124
|
+
if command -v jq >/dev/null 2>&1 && [[ -f "$td/metadata.json" ]]; then
|
|
125
|
+
mt="$(jq -r '.title // empty' "$td/metadata.json" 2>/dev/null || true)"
|
|
126
|
+
[[ -n "$mt" ]] && title="$mt"
|
|
127
|
+
fi
|
|
128
|
+
printf -- '* [%s](tracks/%s/spec.md) - track %s\n' "$title" "$id" "$id"
|
|
129
|
+
done
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# Knowledge graph sub-bundle
|
|
134
|
+
if [[ -f "$DIR/graph/okf/index.md" ]]; then
|
|
135
|
+
printf '\n# Knowledge graph\n\n'
|
|
136
|
+
printf -- '* [Graph bundle](graph/okf/index.md) - structural knowledge graph (modules, dependencies, hotspots)\n'
|
|
137
|
+
fi
|
|
138
|
+
} > "$INDEX"
|
|
139
|
+
|
|
140
|
+
echo "OKF bundle root written to $INDEX"
|
|
141
|
+
exit 0
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# okf-check.sh — validate a directory against the Open Knowledge Format v0.1 spec.
|
|
3
|
+
#
|
|
4
|
+
# Implements the §9 conformance criteria of OKF v0.1
|
|
5
|
+
# (https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md):
|
|
6
|
+
#
|
|
7
|
+
# §9.1 Every non-reserved .md file has a parseable YAML frontmatter block.
|
|
8
|
+
# §9.2 Every such frontmatter block has a non-empty `type` field.
|
|
9
|
+
# §9.3 Reserved files follow their structure when present:
|
|
10
|
+
# index.md (§6) — contains NO frontmatter, EXCEPT the bundle-root
|
|
11
|
+
# index.md MAY carry frontmatter holding only
|
|
12
|
+
# `okf_version` (§11).
|
|
13
|
+
# log.md (§7) — `## ` date headings MUST be ISO 8601 (YYYY-MM-DD).
|
|
14
|
+
#
|
|
15
|
+
# Consumers are required to be permissive, so this checker only enforces the
|
|
16
|
+
# three hard rules above; everything else in the spec is soft guidance.
|
|
17
|
+
#
|
|
18
|
+
# Usage: scripts/tools/okf-check.sh [--dir DIR] [--quiet]
|
|
19
|
+
# Exit codes: 0 conformant, 1 violations found, 2 dir missing.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
DIR="draft"
|
|
23
|
+
QUIET=0
|
|
24
|
+
|
|
25
|
+
usage() {
|
|
26
|
+
cat <<'EOF'
|
|
27
|
+
okf-check.sh — validate a directory against Open Knowledge Format v0.1 (§9).
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
scripts/tools/okf-check.sh [--dir DIR] [--quiet]
|
|
31
|
+
|
|
32
|
+
Flags:
|
|
33
|
+
--dir DIR Bundle root to validate (default: draft).
|
|
34
|
+
--quiet Print only the summary line, not per-file violations.
|
|
35
|
+
--help Show this help.
|
|
36
|
+
|
|
37
|
+
Exit 0 when conformant, 1 when violations are found, 2 when DIR is absent.
|
|
38
|
+
EOF
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
while [[ $# -gt 0 ]]; do
|
|
42
|
+
case "$1" in
|
|
43
|
+
--dir) DIR="$2"; shift 2;;
|
|
44
|
+
--quiet) QUIET=1; shift;;
|
|
45
|
+
--help|-h) usage; exit 0;;
|
|
46
|
+
-*) echo "Unknown flag: $1" >&2; usage >&2; exit 1;;
|
|
47
|
+
*) echo "Unexpected arg: $1" >&2; usage >&2; exit 1;;
|
|
48
|
+
esac
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
[[ -d "$DIR" ]] || { echo "ERROR: --dir '$DIR' is not a directory" >&2; exit 2; }
|
|
52
|
+
DIR="${DIR%/}"
|
|
53
|
+
|
|
54
|
+
# fm_scan FILE -> "STATUS|TYPE|KEYS" (pipe-delimited; '|' is not IFS-whitespace,
|
|
55
|
+
# so empty TYPE/KEYS fields survive `read` instead of collapsing).
|
|
56
|
+
# STATUS: nofm (no frontmatter) | ok (closed block) | unterminated
|
|
57
|
+
# TYPE: value of the top-level `type:` key, if any
|
|
58
|
+
# KEYS: comma-separated top-level frontmatter keys
|
|
59
|
+
fm_scan() {
|
|
60
|
+
awk '
|
|
61
|
+
NR==1 { if ($0 != "---") { print "nofm||"; exit } ; inblock=1; next }
|
|
62
|
+
inblock && /^---[[:space:]]*$/ { print "ok|" type "|" keys; closed=1; exit }
|
|
63
|
+
inblock {
|
|
64
|
+
if (match($0, /^[A-Za-z_][A-Za-z0-9_]*:/)) {
|
|
65
|
+
k = substr($0, 1, RLENGTH-1)
|
|
66
|
+
keys = keys (keys=="" ? "" : ",") k
|
|
67
|
+
if (k == "type") {
|
|
68
|
+
v = substr($0, RLENGTH+1)
|
|
69
|
+
gsub(/^[ \t]+|[ \t]+$/, "", v)
|
|
70
|
+
gsub(/^"|"$/, "", v)
|
|
71
|
+
type = v
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
next
|
|
75
|
+
}
|
|
76
|
+
END { if (inblock && !closed) print "unterminated|" type "|" keys }
|
|
77
|
+
' "$1"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
violations=0
|
|
81
|
+
concepts=0
|
|
82
|
+
reserved=0
|
|
83
|
+
|
|
84
|
+
report() { # relpath message
|
|
85
|
+
violations=$((violations + 1))
|
|
86
|
+
[[ "$QUIET" == "1" ]] || echo "FAIL $1: $2" >&2
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
while IFS= read -r -d '' file; do
|
|
90
|
+
rel="${file#"$DIR"/}"
|
|
91
|
+
base="$(basename "$file")"
|
|
92
|
+
|
|
93
|
+
case "$base" in
|
|
94
|
+
index.md)
|
|
95
|
+
reserved=$((reserved + 1))
|
|
96
|
+
IFS='|' read -r status _ keys < <(fm_scan "$file")
|
|
97
|
+
if [[ "$status" != "nofm" ]]; then
|
|
98
|
+
if [[ "$rel" == "index.md" ]]; then
|
|
99
|
+
# Bundle-root index.md: frontmatter allowed, but only okf_version (§11).
|
|
100
|
+
IFS=',' read -ra ks <<< "$keys"
|
|
101
|
+
for k in "${ks[@]}"; do
|
|
102
|
+
[[ -z "$k" || "$k" == "okf_version" ]] && continue
|
|
103
|
+
report "$rel" "root index.md frontmatter may only hold 'okf_version' (§11); found '$k'"
|
|
104
|
+
done
|
|
105
|
+
else
|
|
106
|
+
report "$rel" "index.md must not contain frontmatter (§6)"
|
|
107
|
+
fi
|
|
108
|
+
fi
|
|
109
|
+
;;
|
|
110
|
+
log.md)
|
|
111
|
+
reserved=$((reserved + 1))
|
|
112
|
+
while IFS= read -r h; do
|
|
113
|
+
if ! [[ "$h" =~ ^##[[:space:]]+[0-9]{4}-[0-9]{2}-[0-9]{2} ]]; then
|
|
114
|
+
report "$rel" "log.md date heading not ISO 8601 (§7): '$h'"
|
|
115
|
+
fi
|
|
116
|
+
done < <(grep -E '^## ' "$file" 2>/dev/null || true)
|
|
117
|
+
;;
|
|
118
|
+
*)
|
|
119
|
+
concepts=$((concepts + 1))
|
|
120
|
+
IFS='|' read -r status type _ < <(fm_scan "$file")
|
|
121
|
+
case "$status" in
|
|
122
|
+
nofm) report "$rel" "missing YAML frontmatter block (§9.1)";;
|
|
123
|
+
unterminated) report "$rel" "unterminated frontmatter block — no closing '---' (§9.1)";;
|
|
124
|
+
ok)
|
|
125
|
+
[[ -n "$type" ]] || report "$rel" "frontmatter missing required non-empty 'type' (§9.2)"
|
|
126
|
+
;;
|
|
127
|
+
esac
|
|
128
|
+
;;
|
|
129
|
+
esac
|
|
130
|
+
done < <(find "$DIR" -type f -name '*.md' -print0 | sort -z)
|
|
131
|
+
|
|
132
|
+
if [[ "$violations" -eq 0 ]]; then
|
|
133
|
+
echo "OKF v0.1 conformant — $concepts concept file(s), $reserved reserved file(s), 0 violations. ($DIR)"
|
|
134
|
+
exit 0
|
|
135
|
+
fi
|
|
136
|
+
echo "OKF v0.1 NON-CONFORMANT — $violations violation(s) across $concepts concept + $reserved reserved file(s). ($DIR)" >&2
|
|
137
|
+
exit 1
|