@drafthq/draft 3.5.1 → 3.5.3

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.
@@ -12,7 +12,7 @@
12
12
  "name": "draft",
13
13
  "source": "./",
14
14
  "description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
15
- "version": "3.5.1",
15
+ "version": "3.5.3",
16
16
  "author": {
17
17
  "name": "mayurpise"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "draft",
3
3
  "description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
4
- "version": "3.5.1",
4
+ "version": "3.5.3",
5
5
  "author": {
6
6
  "name": "mayurpise"
7
7
  },
@@ -2,7 +2,7 @@
2
2
  "name": "draft",
3
3
  "displayName": "Draft",
4
4
  "description": "Context-Driven Development: draft specs and plans before implementation. Structured workflows for features and fixes.",
5
- "version": "3.5.1",
5
+ "version": "3.5.3",
6
6
  "skills": "./skills/",
7
7
  "agents": "./core/agents/",
8
8
  "author": {
@@ -8,7 +8,7 @@ draft_init_mode: okf
8
8
 
9
9
  # {PROJECT_NAME} — AI Context Index
10
10
 
11
- > Index root for the OKF taxonomy bundle (`wiki/`). Read **Synopsis** for broad
11
+ > Index root for the project wiki (`wiki/`). Read **Synopsis** for broad
12
12
  > tasks (they usually terminate here). For focused tasks, route through the
13
13
  > **Concept Map** to ≤N concept pages — each lists `x-grounded-paths`. This is
14
14
  > both the cheap broad-context path AND the progressive-disclosure entry point.
@@ -1,21 +1,17 @@
1
1
  ---
2
- type: Subsystem
3
- title: "{PROJECT_NAME} — Knowledge Bundle"
4
- description: >
5
- Root index of the OKF taxonomy bundle. Start here, then route into
6
- overview/, systems/, features/, reference/, or entrypoints/ via the
7
- Concept Map. Open a concept only when its description matches the task.
8
- resource: .
9
- tags: [index]
10
- timestamp: "{ISO_TIMESTAMP}"
11
2
  okf_version: "0.1"
12
- okf_types_version: "0.1"
13
3
  ---
14
4
 
15
- # {PROJECT_NAME} Knowledge Bundle
5
+ <!-- OKF §6/§11: an index file carries NO concept frontmatter; the root index.md
6
+ may declare ONLY `okf_version`. The frozen concept-`type` vocabulary version
7
+ is tracked here in the body (not in frontmatter) so bumping it stays visible
8
+ without violating the index-frontmatter rule.
9
+ okf-types-version: 0.1 -->
16
10
 
17
- > OKF v0.1 bundle. One concept per file; cross-links form the graph. The
18
- > live call graph (`codebase-memory-mcp`) is the grounding source; this bundle
11
+ # {PROJECT_NAME} Wiki
12
+
13
+ > Project wiki. One concept per file; cross-links form the graph. The
14
+ > live call graph (`codebase-memory-mcp`) is the grounding source; this wiki
19
15
  > is the navigable serialization. `../ai-context.md` is the consumption entry point.
20
16
 
21
17
  ## Sections
@@ -1,25 +1,16 @@
1
- ---
2
- type: Subsystem
3
- title: "{SECTION_TITLE}"
4
- description: >
5
- Section index. Lists every concept in this section with its one-line
6
- routing description so an agent can pick the right page without opening
7
- each one. {SECTION_PURPOSE}
8
- resource: .
9
- tags: [index]
10
- timestamp: "{ISO_TIMESTAMP}"
11
- ---
1
+ <!-- OKF §6: an index file contains NO frontmatter. This page is a reserved
2
+ navigation index, not a concept — its Concepts table is tool-generated. -->
12
3
 
13
4
  # {SECTION_TITLE}
14
5
 
15
- > Section of the OKF bundle. Back to [bundle root](../index.md).
6
+ > Section of the project wiki. Back to [wiki root](../index.md).
16
7
 
17
8
  ## Concepts
18
9
 
19
- <!-- One row per concept page in this section. `description` is the routing key
20
- copied from each page's frontmatter. Regenerated on every init/refresh. -->
21
-
22
- | Concept | Type | Routing description |
23
- |---------|------|---------------------|
24
- | [{concept-a}]({concept-a}.md) | Module | {one-line routing desc} |
25
- | [{concept-b}]({concept-b}.md) | Feature | {one-line routing desc} |
10
+ <!-- TOOL-OWNED: regenerated deterministically by okf-render-views.sh
11
+ (--section-indexes) from the concept pages in this directory. One row per
12
+ page; `description` is the routing key copied from each page's frontmatter.
13
+ Do not hand-edit between the markers links here are guaranteed to resolve
14
+ because they are built only from files that actually exist. -->
15
+ <!-- CONCEPT-MAP:START -->
16
+ <!-- CONCEPT-MAP:END -->
@@ -341,7 +341,7 @@ The tier-gated default rests on **maintainability/readability** (one navigable c
341
341
  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).
342
342
  2. Every `required` entry in `concept-plan.json` has a non-stub page.
343
343
  3. `okf-validate-all.sh … --plan … --strict` exits 0 (structure + per-type quality + coverage all pass).
344
- 4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — no required package with `fan_in floor` is **MISSING**.
344
+ 4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — **every** package/module the graph found is required by default (the fan-in floor only types Subsystem-vs-Module, it does not exempt), so none may be **MISSING**. Section `index.md` tables are regenerated by `okf-render-views.sh --section-indexes`, never hand-authored, so their links cannot dangle.
345
345
  5. On any failure: **do not** atomic-rename; surface `.state/validation-report.json`.
346
346
 
347
347
  > **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.
@@ -3353,7 +3353,8 @@ Templates for each bundle page live in `core/templates/okf/` (`index.md`,
3353
3353
  ## Frozen `type` vocabulary
3354
3354
 
3355
3355
  Every concept carries a `type` from this frozen set (changing it churns every
3356
- file; versioned via `index.md` frontmatter `okf_types_version`):
3356
+ file; versioned via the `okf-types-version` comment in the wiki root `index.md`
3357
+ body — OKF §6/§11 permit only `okf_version` in an index file's frontmatter):
3357
3358
 
3358
3359
  | type | Maps to | Home |
3359
3360
  |------|---------|------|
@@ -3379,6 +3380,16 @@ the task at hand?"), never a summary. Draft extensions are namespaced `x-` and
3379
3380
  ignored by generic OKF consumers: `x-grounded-paths`, `x-hotspot-score`,
3380
3381
  `x-callers`.
3381
3382
 
3383
+ **Meta pages are not concepts.** Index files (`wiki/index.md` and every
3384
+ `<section>/index.md`) carry **no concept frontmatter**: per OKF §6 an index file
3385
+ has no frontmatter, and per §11 the root `index.md` may declare **only**
3386
+ `okf_version`. The tool-generated `systems/coverage.md` is a non-reserved file, so
3387
+ OKF §9.1/§9.2 require it to carry a typed frontmatter block; it uses a descriptive
3388
+ `type: Report` and is exempt from the frozen vocabulary via `is_meta_page`
3389
+ (basename + the `<!-- okf:coverage-generated -->` marker). `okf-validate.sh`
3390
+ short-circuits all meta pages before the concept checks, so they are never
3391
+ vocab-checked or counted as concepts.
3392
+
3382
3393
  ## Concept granularity (resolves open decision 1)
3383
3394
 
3384
3395
  Derive concepts from the graph, not by hand:
@@ -3398,17 +3409,21 @@ Derive concepts from the graph, not by hand:
3398
3409
  ```
3399
3410
  1. Survey → existing draft init 5-phase + graph snapshot (graph-snapshot.sh)
3400
3411
  2. Plan → DETERMINISTIC. okf-plan-concepts.sh derives the expected-concept
3401
- set from the graph (every package with fan_in floor → required
3402
- Subsystem/Module; entrypointsrequired; below-floor / allow-defer
3403
- deferred with a reason) and writes draft.tmp/.state/concept-plan.json.
3412
+ set from the graph. EVERY package the graph knows about is
3413
+ required (fan_in ≥ floor Subsystem; below floor Module — the
3414
+ floor only types/orders, it never exempts); entrypoints required;
3415
+ only --allow-defer matches are deferred (with a reason). Writes
3416
+ draft.tmp/.state/concept-plan.json.
3404
3417
  okf-plan-concepts.sh --repo . [--scope PATH] \
3405
3418
  [--manifest FILE] [--min-fan-in 2] [--allow-defer GLOB]... \
3406
3419
  --out draft.tmp/.state/concept-plan.json
3407
3420
  This replaces the old in-context concept enumeration — the boundary
3408
3421
  of the work is now a tool output, not an LLM judgment, so modules
3409
- cannot be silently dropped. LOG the counts (expected/required/
3410
- deferred) BEFORE writing any page. `generated_order` is topo-ish
3411
- (required + high-fan-in first) so forward cross-links resolve.
3422
+ and sub-modules cannot be silently dropped. (Legacy fan-in
3423
+ exemption is opt-in via --defer-below-floor.) LOG the counts
3424
+ (expected/required/deferred) BEFORE writing any page.
3425
+ `generated_order` is topo-ish (required + high-fan-in first) so
3426
+ forward cross-links resolve.
3412
3427
  3. Generate → iterate concept-plan.generated_order; write ONE page per REQUIRED
3413
3428
  entry, grounding each from the graph:
3414
3429
  x-callers ← graph-callers.sh --symbol <c>
@@ -3428,9 +3443,11 @@ Derive concepts from the graph, not by hand:
3428
3443
  --plan draft.tmp/.state/concept-plan.json \
3429
3444
  --path-index draft.tmp/.state/path-to-concept.json \
3430
3445
  --strict --report draft.tmp/.state/validation-report.json
3431
- It runs, in order: okf-validate.sh (structure + reverse index),
3432
- okf-validate-quality.sh (per-type anti-stub / depth / mermaid lint),
3433
- okf-coverage-check.sh (every required plan entry real page).
3446
+ It runs, in order: okf-validate.sh (structure + reverse index +
3447
+ empty/untyped-page + leftover-template-token + dangling-link checks),
3448
+ okf-validate-quality.sh (per-type anti-stub / depth / per-section
3449
+ content / mermaid lint), okf-coverage-check.sh (every required plan
3450
+ entry → real page).
3434
3451
  ANY layer failing ⇒ exit non-zero ⇒ DO NOT atomic-rename.
3435
3452
  coverage.md (systems/coverage.md) is regenerated by the coverage
3436
3453
  layer; it is tool-owned (marker <!-- okf:coverage-generated -->) —
@@ -3469,6 +3486,7 @@ regenerated on every init/refresh so they never drift from the bundle:
3469
3486
  ```bash
3470
3487
  okf-render-views.sh draft/wiki \
3471
3488
  --arch-out draft/architecture.md \
3489
+ --section-indexes \
3472
3490
  --concept-map-into draft/wiki/index.md \
3473
3491
  --concept-map-into draft/.ai-context.md \
3474
3492
  --web draft/wiki/web/index.html
@@ -3476,6 +3494,11 @@ okf-render-views.sh draft/wiki \
3476
3494
 
3477
3495
  - `--arch-out` renders the linear `architecture.md` (banner + TOC + every concept
3478
3496
  page in canonical section order, frontmatter stripped, Mermaid preserved).
3497
+ - `--section-indexes` rebuilds each `<section>/index.md` concept table (between its
3498
+ `CONCEPT-MAP` markers) from the pages that actually exist in that directory. This
3499
+ is mandatory: section indexes are NOT hand-authored — building them from real
3500
+ files is what makes their links impossible to dangle. Never write a section
3501
+ index "Concepts" table by hand.
3479
3502
  - `--concept-map-into` rebuilds the routing table between the
3480
3503
  `<!-- CONCEPT-MAP:START -->` / `:END` markers from each concept's `title` +
3481
3504
  `type` + `description` (section `index.md` pages excluded).
@@ -22641,23 +22664,19 @@ List down alerting thresholds on those metrics:
22641
22664
  <core-file path="core/templates/okf/index.md">
22642
22665
 
22643
22666
  ---
22644
- type: Subsystem
22645
- title: "{PROJECT_NAME} — Knowledge Bundle"
22646
- description: >
22647
- Root index of the OKF taxonomy bundle. Start here, then route into
22648
- overview/, systems/, features/, reference/, or entrypoints/ via the
22649
- Concept Map. Open a concept only when its description matches the task.
22650
- resource: .
22651
- tags: [index]
22652
- timestamp: "{ISO_TIMESTAMP}"
22653
22667
  okf_version: "0.1"
22654
- okf_types_version: "0.1"
22655
22668
  ---
22656
22669
 
22657
- # {PROJECT_NAME} Knowledge Bundle
22670
+ <!-- OKF §6/§11: an index file carries NO concept frontmatter; the root index.md
22671
+ may declare ONLY `okf_version`. The frozen concept-`type` vocabulary version
22672
+ is tracked here in the body (not in frontmatter) so bumping it stays visible
22673
+ without violating the index-frontmatter rule.
22674
+ okf-types-version: 0.1 -->
22675
+
22676
+ # {PROJECT_NAME} — Wiki
22658
22677
 
22659
- > OKF v0.1 bundle. One concept per file; cross-links form the graph. The
22660
- > live call graph (`codebase-memory-mcp`) is the grounding source; this bundle
22678
+ > Project wiki. One concept per file; cross-links form the graph. The
22679
+ > live call graph (`codebase-memory-mcp`) is the grounding source; this wiki
22661
22680
  > is the navigable serialization. `../ai-context.md` is the consumption entry point.
22662
22681
 
22663
22682
  ## Sections
@@ -22761,31 +22780,22 @@ Entrypoint) require ≥1 valid Mermaid block and ≥2 x-grounded-paths.
22761
22780
 
22762
22781
  <core-file path="core/templates/okf/section-index.md">
22763
22782
 
22764
- ---
22765
- type: Subsystem
22766
- title: "{SECTION_TITLE}"
22767
- description: >
22768
- Section index. Lists every concept in this section with its one-line
22769
- routing description so an agent can pick the right page without opening
22770
- each one. {SECTION_PURPOSE}
22771
- resource: .
22772
- tags: [index]
22773
- timestamp: "{ISO_TIMESTAMP}"
22774
- ---
22783
+ <!-- OKF §6: an index file contains NO frontmatter. This page is a reserved
22784
+ navigation index, not a concept — its Concepts table is tool-generated. -->
22775
22785
 
22776
22786
  # {SECTION_TITLE}
22777
22787
 
22778
- > Section of the OKF bundle. Back to [bundle root](../index.md).
22788
+ > Section of the project wiki. Back to [wiki root](../index.md).
22779
22789
 
22780
22790
  ## Concepts
22781
22791
 
22782
- <!-- One row per concept page in this section. `description` is the routing key
22783
- copied from each page's frontmatter. Regenerated on every init/refresh. -->
22784
-
22785
- | Concept | Type | Routing description |
22786
- |---------|------|---------------------|
22787
- | [{concept-a}]({concept-a}.md) | Module | {one-line routing desc} |
22788
- | [{concept-b}]({concept-b}.md) | Feature | {one-line routing desc} |
22792
+ <!-- TOOL-OWNED: regenerated deterministically by okf-render-views.sh
22793
+ (--section-indexes) from the concept pages in this directory. One row per
22794
+ page; `description` is the routing key copied from each page's frontmatter.
22795
+ Do not hand-edit between the markers links here are guaranteed to resolve
22796
+ because they are built only from files that actually exist. -->
22797
+ <!-- CONCEPT-MAP:START -->
22798
+ <!-- CONCEPT-MAP:END -->
22789
22799
 
22790
22800
  </core-file>
22791
22801
 
@@ -22805,7 +22815,7 @@ draft_init_mode: okf
22805
22815
 
22806
22816
  # {PROJECT_NAME} — AI Context Index
22807
22817
 
22808
- > Index root for the OKF taxonomy bundle (`wiki/`). Read **Synopsis** for broad
22818
+ > Index root for the project wiki (`wiki/`). Read **Synopsis** for broad
22809
22819
  > tasks (they usually terminate here). For focused tasks, route through the
22810
22820
  > **Concept Map** to ≤N concept pages — each lists `x-grounded-paths`. This is
22811
22821
  > both the cheap broad-context path AND the progressive-disclosure entry point.
@@ -341,7 +341,7 @@ The tier-gated default rests on **maintainability/readability** (one navigable c
341
341
  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).
342
342
  2. Every `required` entry in `concept-plan.json` has a non-stub page.
343
343
  3. `okf-validate-all.sh … --plan … --strict` exits 0 (structure + per-type quality + coverage all pass).
344
- 4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — no required package with `fan_in floor` is **MISSING**.
344
+ 4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — **every** package/module the graph found is required by default (the fan-in floor only types Subsystem-vs-Module, it does not exempt), so none may be **MISSING**. Section `index.md` tables are regenerated by `okf-render-views.sh --section-indexes`, never hand-authored, so their links cannot dangle.
345
345
  5. On any failure: **do not** atomic-rename; surface `.state/validation-report.json`.
346
346
 
347
347
  > **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.
@@ -3353,7 +3353,8 @@ Templates for each bundle page live in `core/templates/okf/` (`index.md`,
3353
3353
  ## Frozen `type` vocabulary
3354
3354
 
3355
3355
  Every concept carries a `type` from this frozen set (changing it churns every
3356
- file; versioned via `index.md` frontmatter `okf_types_version`):
3356
+ file; versioned via the `okf-types-version` comment in the wiki root `index.md`
3357
+ body — OKF §6/§11 permit only `okf_version` in an index file's frontmatter):
3357
3358
 
3358
3359
  | type | Maps to | Home |
3359
3360
  |------|---------|------|
@@ -3379,6 +3380,16 @@ the task at hand?"), never a summary. Draft extensions are namespaced `x-` and
3379
3380
  ignored by generic OKF consumers: `x-grounded-paths`, `x-hotspot-score`,
3380
3381
  `x-callers`.
3381
3382
 
3383
+ **Meta pages are not concepts.** Index files (`wiki/index.md` and every
3384
+ `<section>/index.md`) carry **no concept frontmatter**: per OKF §6 an index file
3385
+ has no frontmatter, and per §11 the root `index.md` may declare **only**
3386
+ `okf_version`. The tool-generated `systems/coverage.md` is a non-reserved file, so
3387
+ OKF §9.1/§9.2 require it to carry a typed frontmatter block; it uses a descriptive
3388
+ `type: Report` and is exempt from the frozen vocabulary via `is_meta_page`
3389
+ (basename + the `<!-- okf:coverage-generated -->` marker). `okf-validate.sh`
3390
+ short-circuits all meta pages before the concept checks, so they are never
3391
+ vocab-checked or counted as concepts.
3392
+
3382
3393
  ## Concept granularity (resolves open decision 1)
3383
3394
 
3384
3395
  Derive concepts from the graph, not by hand:
@@ -3398,17 +3409,21 @@ Derive concepts from the graph, not by hand:
3398
3409
  ```
3399
3410
  1. Survey → existing draft init 5-phase + graph snapshot (graph-snapshot.sh)
3400
3411
  2. Plan → DETERMINISTIC. okf-plan-concepts.sh derives the expected-concept
3401
- set from the graph (every package with fan_in floor → required
3402
- Subsystem/Module; entrypointsrequired; below-floor / allow-defer
3403
- deferred with a reason) and writes draft.tmp/.state/concept-plan.json.
3412
+ set from the graph. EVERY package the graph knows about is
3413
+ required (fan_in ≥ floor Subsystem; below floor Module — the
3414
+ floor only types/orders, it never exempts); entrypoints required;
3415
+ only --allow-defer matches are deferred (with a reason). Writes
3416
+ draft.tmp/.state/concept-plan.json.
3404
3417
  okf-plan-concepts.sh --repo . [--scope PATH] \
3405
3418
  [--manifest FILE] [--min-fan-in 2] [--allow-defer GLOB]... \
3406
3419
  --out draft.tmp/.state/concept-plan.json
3407
3420
  This replaces the old in-context concept enumeration — the boundary
3408
3421
  of the work is now a tool output, not an LLM judgment, so modules
3409
- cannot be silently dropped. LOG the counts (expected/required/
3410
- deferred) BEFORE writing any page. `generated_order` is topo-ish
3411
- (required + high-fan-in first) so forward cross-links resolve.
3422
+ and sub-modules cannot be silently dropped. (Legacy fan-in
3423
+ exemption is opt-in via --defer-below-floor.) LOG the counts
3424
+ (expected/required/deferred) BEFORE writing any page.
3425
+ `generated_order` is topo-ish (required + high-fan-in first) so
3426
+ forward cross-links resolve.
3412
3427
  3. Generate → iterate concept-plan.generated_order; write ONE page per REQUIRED
3413
3428
  entry, grounding each from the graph:
3414
3429
  x-callers ← graph-callers.sh --symbol <c>
@@ -3428,9 +3443,11 @@ Derive concepts from the graph, not by hand:
3428
3443
  --plan draft.tmp/.state/concept-plan.json \
3429
3444
  --path-index draft.tmp/.state/path-to-concept.json \
3430
3445
  --strict --report draft.tmp/.state/validation-report.json
3431
- It runs, in order: okf-validate.sh (structure + reverse index),
3432
- okf-validate-quality.sh (per-type anti-stub / depth / mermaid lint),
3433
- okf-coverage-check.sh (every required plan entry real page).
3446
+ It runs, in order: okf-validate.sh (structure + reverse index +
3447
+ empty/untyped-page + leftover-template-token + dangling-link checks),
3448
+ okf-validate-quality.sh (per-type anti-stub / depth / per-section
3449
+ content / mermaid lint), okf-coverage-check.sh (every required plan
3450
+ entry → real page).
3434
3451
  ANY layer failing ⇒ exit non-zero ⇒ DO NOT atomic-rename.
3435
3452
  coverage.md (systems/coverage.md) is regenerated by the coverage
3436
3453
  layer; it is tool-owned (marker <!-- okf:coverage-generated -->) —
@@ -3469,6 +3486,7 @@ regenerated on every init/refresh so they never drift from the bundle:
3469
3486
  ```bash
3470
3487
  okf-render-views.sh draft/wiki \
3471
3488
  --arch-out draft/architecture.md \
3489
+ --section-indexes \
3472
3490
  --concept-map-into draft/wiki/index.md \
3473
3491
  --concept-map-into draft/.ai-context.md \
3474
3492
  --web draft/wiki/web/index.html
@@ -3476,6 +3494,11 @@ okf-render-views.sh draft/wiki \
3476
3494
 
3477
3495
  - `--arch-out` renders the linear `architecture.md` (banner + TOC + every concept
3478
3496
  page in canonical section order, frontmatter stripped, Mermaid preserved).
3497
+ - `--section-indexes` rebuilds each `<section>/index.md` concept table (between its
3498
+ `CONCEPT-MAP` markers) from the pages that actually exist in that directory. This
3499
+ is mandatory: section indexes are NOT hand-authored — building them from real
3500
+ files is what makes their links impossible to dangle. Never write a section
3501
+ index "Concepts" table by hand.
3479
3502
  - `--concept-map-into` rebuilds the routing table between the
3480
3503
  `<!-- CONCEPT-MAP:START -->` / `:END` markers from each concept's `title` +
3481
3504
  `type` + `description` (section `index.md` pages excluded).
@@ -22641,23 +22664,19 @@ List down alerting thresholds on those metrics:
22641
22664
  <core-file path="core/templates/okf/index.md">
22642
22665
 
22643
22666
  ---
22644
- type: Subsystem
22645
- title: "{PROJECT_NAME} — Knowledge Bundle"
22646
- description: >
22647
- Root index of the OKF taxonomy bundle. Start here, then route into
22648
- overview/, systems/, features/, reference/, or entrypoints/ via the
22649
- Concept Map. Open a concept only when its description matches the task.
22650
- resource: .
22651
- tags: [index]
22652
- timestamp: "{ISO_TIMESTAMP}"
22653
22667
  okf_version: "0.1"
22654
- okf_types_version: "0.1"
22655
22668
  ---
22656
22669
 
22657
- # {PROJECT_NAME} Knowledge Bundle
22670
+ <!-- OKF §6/§11: an index file carries NO concept frontmatter; the root index.md
22671
+ may declare ONLY `okf_version`. The frozen concept-`type` vocabulary version
22672
+ is tracked here in the body (not in frontmatter) so bumping it stays visible
22673
+ without violating the index-frontmatter rule.
22674
+ okf-types-version: 0.1 -->
22675
+
22676
+ # {PROJECT_NAME} — Wiki
22658
22677
 
22659
- > OKF v0.1 bundle. One concept per file; cross-links form the graph. The
22660
- > live call graph (`codebase-memory-mcp`) is the grounding source; this bundle
22678
+ > Project wiki. One concept per file; cross-links form the graph. The
22679
+ > live call graph (`codebase-memory-mcp`) is the grounding source; this wiki
22661
22680
  > is the navigable serialization. `../ai-context.md` is the consumption entry point.
22662
22681
 
22663
22682
  ## Sections
@@ -22761,31 +22780,22 @@ Entrypoint) require ≥1 valid Mermaid block and ≥2 x-grounded-paths.
22761
22780
 
22762
22781
  <core-file path="core/templates/okf/section-index.md">
22763
22782
 
22764
- ---
22765
- type: Subsystem
22766
- title: "{SECTION_TITLE}"
22767
- description: >
22768
- Section index. Lists every concept in this section with its one-line
22769
- routing description so an agent can pick the right page without opening
22770
- each one. {SECTION_PURPOSE}
22771
- resource: .
22772
- tags: [index]
22773
- timestamp: "{ISO_TIMESTAMP}"
22774
- ---
22783
+ <!-- OKF §6: an index file contains NO frontmatter. This page is a reserved
22784
+ navigation index, not a concept — its Concepts table is tool-generated. -->
22775
22785
 
22776
22786
  # {SECTION_TITLE}
22777
22787
 
22778
- > Section of the OKF bundle. Back to [bundle root](../index.md).
22788
+ > Section of the project wiki. Back to [wiki root](../index.md).
22779
22789
 
22780
22790
  ## Concepts
22781
22791
 
22782
- <!-- One row per concept page in this section. `description` is the routing key
22783
- copied from each page's frontmatter. Regenerated on every init/refresh. -->
22784
-
22785
- | Concept | Type | Routing description |
22786
- |---------|------|---------------------|
22787
- | [{concept-a}]({concept-a}.md) | Module | {one-line routing desc} |
22788
- | [{concept-b}]({concept-b}.md) | Feature | {one-line routing desc} |
22792
+ <!-- TOOL-OWNED: regenerated deterministically by okf-render-views.sh
22793
+ (--section-indexes) from the concept pages in this directory. One row per
22794
+ page; `description` is the routing key copied from each page's frontmatter.
22795
+ Do not hand-edit between the markers links here are guaranteed to resolve
22796
+ because they are built only from files that actually exist. -->
22797
+ <!-- CONCEPT-MAP:START -->
22798
+ <!-- CONCEPT-MAP:END -->
22789
22799
 
22790
22800
  </core-file>
22791
22801
 
@@ -22805,7 +22815,7 @@ draft_init_mode: okf
22805
22815
 
22806
22816
  # {PROJECT_NAME} — AI Context Index
22807
22817
 
22808
- > Index root for the OKF taxonomy bundle (`wiki/`). Read **Synopsis** for broad
22818
+ > Index root for the project wiki (`wiki/`). Read **Synopsis** for broad
22809
22819
  > tasks (they usually terminate here). For focused tasks, route through the
22810
22820
  > **Concept Map** to ≤N concept pages — each lists `x-grounded-paths`. This is
22811
22821
  > both the cheap broad-context path AND the progressive-disclosure entry point.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drafthq/draft",
3
- "version": "3.5.1",
3
+ "version": "3.5.3",
4
4
  "description": "Context-Driven Development for AI coding agents — install Draft into Claude Code, Cursor, Codex, or opencode.",
5
5
  "bin": {
6
6
  "draft": "cli/bin/draft.js"
@@ -122,6 +122,17 @@ write_coverage_page() {
122
122
  mkdir -p "$BUNDLE/systems"
123
123
  local tmp; tmp="$(mktemp)"
124
124
  {
125
+ # OKF §9.1/§9.2: every non-reserved .md needs parseable frontmatter with a
126
+ # non-empty `type`. coverage.md is tool-generated and not a code concept, so
127
+ # it uses a descriptive (non-frozen) type; okf-validate.sh exempts it from the
128
+ # frozen-vocab check via is_meta_page (basename + the marker below).
129
+ echo "---"
130
+ echo "type: Report"
131
+ echo "title: Component Coverage"
132
+ echo "description: Coverage of required components by wiki pages — which are documented, stubbed, or missing."
133
+ echo "resource: ."
134
+ echo "---"
135
+ echo ""
125
136
  echo "<!-- okf:coverage-generated -->"
126
137
  echo "# Component Coverage"
127
138
  echo ""
@@ -130,14 +141,21 @@ write_coverage_page() {
130
141
  echo ""
131
142
  echo "| Component | Wiki page | Status | Fan-in |"
132
143
  echo "|-----------|-----------|--------|--------|"
133
- local cid status fanin
144
+ local cid status fanin link
134
145
  while IFS=$'\t' read -r cid required reason ftype fanin; do
135
146
  [[ -z "$cid" ]] && continue
136
147
  if [[ "$required" == "true" ]]; then
137
148
  if [[ -f "$BUNDLE/$cid" ]]; then
138
149
  bl="$(body_lines "$BUNDLE/$cid")"
150
+ # coverage.md lives in systems/; link relative to it. Concepts in
151
+ # other sections (entrypoints/, reference/, …) need a ../ prefix,
152
+ # otherwise the link dangles and fails structure validation.
153
+ case "$cid" in
154
+ systems/*) link="${cid#systems/}";;
155
+ *) link="../$cid";;
156
+ esac
139
157
  if [[ "$bl" -ge "$MIN_STUB_LINES" ]]; then
140
- echo "| \`${cid%.md}\` | [page](${cid#systems/}) | Full | ${fanin} |"
158
+ echo "| \`${cid%.md}\` | [page](${link}) | Full | ${fanin} |"
141
159
  else
142
160
  echo "| \`${cid%.md}\` | ${cid} | **STUB (${bl} lines)** | ${fanin} |"
143
161
  fi
@@ -38,6 +38,7 @@ MANIFEST=""
38
38
  MIN_FAN_IN=2
39
39
  OUT=""
40
40
  JSON=0
41
+ DEFER_BELOW_FLOOR=0
41
42
  ALLOW_DEFER=()
42
43
 
43
44
  usage() {
@@ -54,8 +55,12 @@ Flags:
54
55
  --manifest FILE Component list (one component per line; '#' comments; blanks
55
56
  ignored). When present it is authoritative — every entry is
56
57
  required and the graph is not consulted.
57
- --min-fan-in N Package fan-in floor for "required" (default: 2). Packages
58
- below the floor are deferred with a reason.
58
+ --min-fan-in N Fan-in threshold that types a package as a Subsystem (>=N)
59
+ vs a Module (<N) and orders it first (default: 2). By default
60
+ EVERY graph package is required regardless of fan-in — the
61
+ floor no longer exempts anything.
62
+ --defer-below-floor Restore the old behavior: packages with fan_in < --min-fan-in
63
+ are deferred (not required) instead of required-as-Module.
59
64
  --allow-defer GLOB Defer (don't require) components whose name matches GLOB.
60
65
  Repeatable. Deferred entries still appear in the plan.
61
66
  --out FILE Write the plan JSON here (default: stdout).
@@ -72,6 +77,7 @@ while [[ $# -gt 0 ]]; do
72
77
  --scope) SCOPE="$2"; shift 2;;
73
78
  --manifest) MANIFEST="$2"; shift 2;;
74
79
  --min-fan-in) MIN_FAN_IN="$2"; shift 2;;
80
+ --defer-below-floor) DEFER_BELOW_FLOOR=1; shift;;
75
81
  --allow-defer) ALLOW_DEFER+=("$2"); shift 2;;
76
82
  --out) OUT="$2"; shift 2;;
77
83
  --json) JSON=1; shift;;
@@ -150,8 +156,13 @@ plan_from_graph() {
150
156
  required=false; reason="allow-defer match"; type=Module
151
157
  elif (( fan_in >= MIN_FAN_IN )); then
152
158
  required=true; reason=""; type=Subsystem
153
- else
159
+ elif [[ $DEFER_BELOW_FLOOR -eq 1 ]]; then
160
+ # Opt-in legacy behavior: low-fan-in packages are exempted.
154
161
  required=false; reason="fan_in $fan_in < floor $MIN_FAN_IN"; type=Module
162
+ else
163
+ # Default: every package the graph knows about is documented. Fan-in
164
+ # below the floor only demotes Subsystem→Module; it never exempts.
165
+ required=true; reason=""; type=Module
155
166
  fi
156
167
  add_concept "$name" systems "$type" "$name" "$fan_in" "$required" "$reason"
157
168
  done < <(echo "$arch" | jq -r '.packages[]? | [.name, (.fan_in // 0)] | @tsv')
@@ -10,6 +10,9 @@
10
10
  # 2. Concept Map — a routing table injected between the
11
11
  # <!-- CONCEPT-MAP:START --> / <!-- CONCEPT-MAP:END --> markers in
12
12
  # wiki/index.md (and optionally another index-root file).
13
+ # 3. Section indexes — (--section-indexes) each <section>/index.md concept
14
+ # table rebuilt from the pages that actually exist in that directory, so its
15
+ # links can never dangle (no more hand-authored, link-rotting indexes).
13
16
  #
14
17
  # Usage:
15
18
  # okf-render-views.sh <BUNDLE_DIR> --arch-out <FILE> [--concept-map-into <FILE>]
@@ -25,6 +28,7 @@ BUNDLE=""
25
28
  ARCH_OUT=""
26
29
  WEB_OUT=""
27
30
  CMAP_INTO=()
31
+ SECTION_INDEXES=0
28
32
  COVERAGE_REPORT=""
29
33
  VALIDATED_AT=""
30
34
 
@@ -39,6 +43,10 @@ Flags:
39
43
  --arch-out FILE Write the rendered linear architecture.md here.
40
44
  --concept-map-into FILE Inject the Concept Map between the CONCEPT-MAP markers
41
45
  in FILE (repeatable: e.g. wiki/index.md and ai-context.md).
46
+ --section-indexes Regenerate each section's <section>/index.md concept
47
+ table (between its CONCEPT-MAP markers) from the pages
48
+ that actually exist in that directory. Eliminates
49
+ hand-authored, link-rotting section indexes.
42
50
  --web FILE Write a self-contained, offline HTML viewer (single file:
43
51
  all pages inlined, built-in markdown renderer, sidebar +
44
52
  search). Double-click to open — no server, no internet.
@@ -56,6 +64,7 @@ while [[ $# -gt 0 ]]; do
56
64
  case "$1" in
57
65
  --arch-out) ARCH_OUT="$2"; shift 2;;
58
66
  --concept-map-into) CMAP_INTO+=("$2"); shift 2;;
67
+ --section-indexes) SECTION_INDEXES=1; shift;;
59
68
  --web) WEB_OUT="$2"; shift 2;;
60
69
  --coverage-report) COVERAGE_REPORT="$2"; shift 2;;
61
70
  --validated-at) VALIDATED_AT="$2"; shift 2;;
@@ -100,6 +109,17 @@ strip_frontmatter() {
100
109
  ' "$1"
101
110
  }
102
111
 
112
+ # Human-facing title for a page: frontmatter `title`, else the first `# H1` in the
113
+ # body, else the bundle-relative path. Index files are frontmatter-less under OKF
114
+ # §6, so the H1 fallback keeps the rendered TOC / nav readable.
115
+ page_title() {
116
+ local t; t="$(get_yaml_field "$1" title)"
117
+ if [[ -z "$t" ]]; then
118
+ t="$(strip_frontmatter "$1" | grep -m1 -E '^#[[:space:]]+' | sed -E 's/^#[[:space:]]+//')"
119
+ fi
120
+ printf '%s' "$t"
121
+ }
122
+
103
123
  # Coverage-honesty banner, sourced from okf-coverage-check.sh's JSON report.
104
124
  # Silent when no report is supplied (keeps the view backward-compatible).
105
125
  emit_coverage_banner() {
@@ -134,7 +154,7 @@ render_architecture() {
134
154
  echo ""
135
155
  echo "# Architecture (Rendered View)"
136
156
  echo ""
137
- echo "> **Generated** from the \`wiki/\` OKF bundle — do not edit by hand."
157
+ echo "> **Generated** from the \`wiki/\` bundle — do not edit by hand."
138
158
  echo "> The bundle is the source of truth; this is the single-document linear"
139
159
  echo "> view for onboarding. Regenerate with \`okf-render-views.sh\`."
140
160
  echo ""
@@ -150,7 +170,7 @@ render_architecture() {
150
170
  echo "- **${sec}/**"
151
171
  last_sec="$sec"
152
172
  fi
153
- title="$(get_yaml_field "$BUNDLE/$rel" title)"
173
+ title="$(page_title "$BUNDLE/$rel")"
154
174
  [[ -n "$title" ]] || title="$rel"
155
175
  local anchor; anchor="$(printf '%s' "$title" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-')"
156
176
  anchor="${anchor#-}"; anchor="${anchor%-}"
@@ -174,10 +194,14 @@ render_architecture() {
174
194
  build_concept_map() {
175
195
  echo "| Concept | Type | Open it when… |"
176
196
  echo "|---------|------|---------------|"
177
- local rel type title desc
197
+ local rel base type title desc
178
198
  while IFS= read -r -d '' page; do
179
199
  rel="${page#"$BUNDLE/"}"
180
- [[ "$(basename "$rel")" == "index.md" ]] && continue
200
+ base="$(basename "$rel")"
201
+ # Meta pages are not routable concepts (reserved index/log + the typed,
202
+ # tool-generated coverage page).
203
+ [[ "$base" == "index.md" || "$base" == "log.md" || "$base" == "coverage.md" ]] && continue
204
+ grep -q '<!-- okf:coverage-generated -->' "$page" 2>/dev/null && continue
181
205
  type="$(get_yaml_field "$page" type)"
182
206
  [[ -n "$type" ]] || continue
183
207
  title="$(get_yaml_field "$page" title)"
@@ -192,6 +216,50 @@ build_concept_map() {
192
216
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
193
217
  }
194
218
 
219
+ # First non-empty line of a page's `description` frontmatter (handles folded `>`).
220
+ page_desc() {
221
+ awk '
222
+ NR==1&&/^---$/{fm=1;next} fm&&/^---$/{exit}
223
+ fm && /^description:/ { collect=1; sub(/^description:[[:space:]]*>?[[:space:]]*/,""); if($0!=""){print; exit} next }
224
+ fm && collect { sub(/^[[:space:]]+/,""); if($0!=""){print; exit} }
225
+ ' "$1"
226
+ }
227
+
228
+ # Build the per-section concept table (stdout) for a single section directory.
229
+ # Links are bundle-section-relative (just the filename) so they resolve from the
230
+ # section's own index.md. Only pages that actually exist are listed — so the
231
+ # table can never point at a missing file.
232
+ build_section_map() {
233
+ local dir="$1" f rel base type title desc
234
+ echo "| Concept | Type | Routing description |"
235
+ echo "|---------|------|---------------------|"
236
+ while IFS= read -r f; do
237
+ base="$(basename "$f")"
238
+ [[ "$base" == "index.md" ]] && continue
239
+ [[ "$base" == "coverage.md" ]] && continue
240
+ grep -q '<!-- okf:coverage-generated -->' "$f" 2>/dev/null && continue
241
+ type="$(get_yaml_field "$f" type)"
242
+ [[ -n "$type" ]] || continue
243
+ title="$(get_yaml_field "$f" title)"; [[ -n "$title" ]] || title="$base"
244
+ desc="$(page_desc "$f")"
245
+ echo "| [${title}](${base}) | ${type} | ${desc} |"
246
+ done < <(find "$dir" -maxdepth 1 -type f -name '*.md' | sort)
247
+ }
248
+
249
+ # Regenerate every <section>/index.md concept table from real pages.
250
+ render_section_indexes() {
251
+ local sec dir idx
252
+ for sec in "${SECTIONS[@]}"; do
253
+ dir="$BUNDLE/$sec"
254
+ idx="$dir/index.md"
255
+ [[ -d "$dir" && -f "$idx" ]] || continue
256
+ local map_tmp; map_tmp="$(mktemp)"
257
+ build_section_map "$dir" >"$map_tmp"
258
+ inject_concept_map "$idx" "$map_tmp"
259
+ rm -f "$map_tmp"
260
+ done
261
+ }
262
+
195
263
  # Inject the Concept Map between markers in a target file (path may be relative
196
264
  # to BUNDLE: links in the map are bundle-relative, so the target should resolve
197
265
  # them — wiki/index.md works directly; an index root above wiki/ should prefix).
@@ -277,7 +345,7 @@ HTML_HEAD
277
345
  while IFS= read -r -d '' page; do
278
346
  local rel title type
279
347
  rel="${page#"$BUNDLE/"}"
280
- title="$(get_yaml_field "$page" title)"; [[ -n "$title" ]] || title="$rel"
348
+ title="$(page_title "$page")"; [[ -n "$title" ]] || title="$rel"
281
349
  type="$(get_yaml_field "$page" type)"
282
350
  printf '%s: {"title": %s, "type": %s, "md": %s},\n' \
283
351
  "$(jq -Rn --arg v "$rel" '$v')" \
@@ -397,7 +465,10 @@ if [[ ${#CMAP_INTO[@]} -gt 0 ]]; then
397
465
  rm -f "$MAP_TMP"
398
466
  fi
399
467
 
468
+ [[ $SECTION_INDEXES -eq 1 ]] && render_section_indexes
469
+
400
470
  [[ -n "$WEB_OUT" ]] && render_web "$WEB_OUT"
401
471
 
402
- [[ -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; }
472
+ [[ -n "$ARCH_OUT" || -n "$WEB_OUT" || ${#CMAP_INTO[@]} -gt 0 || $SECTION_INDEXES -eq 1 ]] \
473
+ || { echo "ERROR: nothing to do (pass --arch-out, --web, --section-indexes, and/or --concept-map-into)" >&2; exit 1; }
403
474
  exit 0
@@ -11,7 +11,8 @@
11
11
  # Pages in scope: any *.md whose frontmatter declares a frozen `type:`. Section
12
12
  # index.md pages, log.md, and the generated coverage page are excluded.
13
13
  #
14
- # Checks (per type): required H2 sections, min body lines, >=1 mermaid block
14
+ # Checks (per type): required H2 sections AND real content beneath each (so a
15
+ # present-but-empty heading fails as "shallow"), min body lines, >=1 mermaid block
15
16
  # (diagram types only), min x-grounded-paths, anti-stub patterns, unreplaced
16
17
  # template tokens, duplicate "What it is" paragraphs, and a syntax-only mermaid
17
18
  # lint (no Node, no headless browser).
@@ -88,6 +89,19 @@ grounded_count() {
88
89
 
89
90
  has_section() { grep -qE "^##[[:space:]]+$1([[:space:]]|$)" "$2"; }
90
91
 
92
+ # Count non-blank content lines under a given H2 section (heading itself
93
+ # excluded), stopping at the next H2. Used to fail a section that is present as a
94
+ # heading but carries no real content — the classic "shallow" page.
95
+ section_content_lines() {
96
+ local sec="$1" file="$2"
97
+ awk -v sec="$sec" '
98
+ $0 ~ "^##[[:space:]]+" sec "([[:space:]]|$)" { grab=1; next }
99
+ grab && /^##[[:space:]]/ { exit }
100
+ grab && /[^[:space:]]/ { n++ }
101
+ END { print n+0 }
102
+ ' "$file"
103
+ }
104
+
91
105
  mermaid_block_count() { grep -cE '^[[:space:]]*```mermaid' "$1" || true; }
92
106
 
93
107
  # Syntax-only mermaid lint: catch the breakers that silently fail previewers
@@ -139,19 +153,22 @@ whatitis_hash() {
139
153
  ANTI_STUB='see architecture\.md|deferred to ref-docs|\bTBD\b|TODO:[[:space:]]*document|stub page|placeholder page'
140
154
  TOKEN_RE='\{[A-Z_]+\}'
141
155
 
142
- # Per-type policy. echoes: sections|min_lines|need_mermaid|min_grounded
156
+ # Per-type policy. echoes: sections|min_lines|need_mermaid|min_grounded|min_section_lines
157
+ # min_section_lines: minimum non-blank content lines required UNDER each named
158
+ # section (0 disables the per-section depth check). This is what turns a
159
+ # present-but-empty heading — the hallmark of a shallow page — into a failure.
143
160
  type_policy() {
144
161
  case "$1" in
145
162
  Subsystem|Module|Feature|Entrypoint)
146
- echo "What it is;How it works;Used by;Blast radius;See also|25|1|2";;
163
+ echo "What it is;How it works;Used by;Blast radius;See also|35|1|2|2";;
147
164
  API|DataModel)
148
- echo "What it is;How it works;See also|18|0|1";;
165
+ echo "What it is;How it works;See also|22|0|1|2";;
149
166
  Dependency)
150
- echo "What it is;Used by|10|0|0";;
167
+ echo "What it is;Used by|12|0|0|1";;
151
168
  ADR|Runbook)
152
- echo "|8|0|0";;
169
+ echo "|8|0|0|0";;
153
170
  *)
154
- echo "|8|0|0";;
171
+ echo "|8|0|0|0";;
155
172
  esac
156
173
  }
157
174
 
@@ -172,13 +189,26 @@ while IFS= read -r -d '' page; do
172
189
  [[ -z "$type_val" ]] && continue # not a concept page
173
190
  CHECKED=$((CHECKED + 1))
174
191
 
175
- IFS='|' read -r sections min_lines need_mermaid min_grounded <<< "$(type_policy "$type_val")"
192
+ IFS='|' read -r sections min_lines need_mermaid min_grounded min_section_lines <<< "$(type_policy "$type_val")"
176
193
 
177
- # Q-SEC: required sections.
194
+ # Q-SEC: required sections must be present. Q-SECLEN: the NARRATIVE sections
195
+ # ("What it is" / "How it works") must carry real prose beneath the heading —
196
+ # a heading with nothing under it is the classic shallow stub. List sections
197
+ # ("Used by", "See also", "Blast radius") are legitimately terse, so the
198
+ # depth bar only applies to the narrative ones.
178
199
  if [[ -n "$sections" ]]; then
179
200
  IFS=';' read -ra secs <<< "$sections"
180
201
  for s in "${secs[@]}"; do
181
- has_section "$s" "$page" || fail "$rel" "Q-SEC" "missing required section '## $s'"
202
+ if ! has_section "$s" "$page"; then
203
+ fail "$rel" "Q-SEC" "missing required section '## $s'"
204
+ elif [[ "${min_section_lines:-0}" -gt 0 ]]; then
205
+ case "$s" in
206
+ "What it is"|"How it works")
207
+ scl="$(section_content_lines "$s" "$page")"
208
+ [[ "$scl" -ge "$min_section_lines" ]] \
209
+ || fail "$rel" "Q-SECLEN" "section '## $s' has $scl content lines < $min_section_lines (shallow)";;
210
+ esac
211
+ fi
182
212
  done
183
213
  fi
184
214
 
@@ -11,6 +11,11 @@
11
11
  # 2. Every concept page (any *.md whose frontmatter declares `type:`) carries
12
12
  # all required OKF frontmatter keys: type, title, description, resource.
13
13
  # 3. Every declared `type` is in the frozen code-repo vocabulary (§4 of HLD).
14
+ # 3a. Every non-meta *.md is a real concept: an empty or frontmatter-less page
15
+ # (a blank placeholder) fails — it would otherwise be invisible to the
16
+ # quality + coverage layers, which both key on `type`.
17
+ # 3b. No page carries an unreplaced {ALL_CAPS} template token (catches leftover
18
+ # placeholders in hand-seeded index pages, which the quality layer skips).
14
19
  # 4. Every relative markdown cross-link ( ](path.md) ) resolves to a file that
15
20
  # exists inside the bundle. External (http/https/mailto) and pure-anchor
16
21
  # (#frag) links are ignored.
@@ -105,14 +110,53 @@ if [[ ! -f "$BUNDLE/index.md" ]]; then
105
110
  add_error "missing bundle root: $BUNDLE/index.md"
106
111
  fi
107
112
 
113
+ # A page is "meta" (not a concept) if it is a section/root index, the change log,
114
+ # or the tool-generated coverage page. Everything else MUST be a real concept —
115
+ # an empty or frontmatter-less .md placeholder is a completeness failure, not an
116
+ # invisible non-concept.
117
+ is_meta_page() {
118
+ local base="$1" page="$2"
119
+ case "$base" in
120
+ index.md|log.md|coverage.md) return 0;;
121
+ esac
122
+ grep -q '<!-- okf:coverage-generated -->' "$page" 2>/dev/null && return 0
123
+ return 1
124
+ }
125
+
126
+ # Non-blank body line count (everything after the frontmatter block).
127
+ nonblank_body_lines() {
128
+ awk 'NR==1&&/^---$/{fm=1;next} fm&&/^---$/{fm=0;next} !fm{print}' "$1" \
129
+ | grep -cE '[^[:space:]]' || true
130
+ }
131
+
108
132
  # --- 2/3. Per-page frontmatter + type vocabulary ---
109
133
  while IFS= read -r -d '' page; do
110
134
  PAGE_COUNT=$((PAGE_COUNT + 1))
111
135
  rel="${page#"$BUNDLE/"}"
136
+ base="$(basename "$rel")"
137
+
138
+ # Meta pages are NOT concepts: reserved index.md/log.md (OKF §6/§7) carry no
139
+ # concept frontmatter, and the tool-generated coverage page uses a descriptive
140
+ # (non-frozen) type. Exempt them from the frozen-vocab + required-field + body
141
+ # checks, and don't count them as concepts. The template-token (§3b) and
142
+ # cross-link (§4) scans below still cover them.
143
+ if is_meta_page "$base" "$page"; then
144
+ continue
145
+ fi
112
146
 
113
- # A page is a "concept" only if its frontmatter declares a type.
114
147
  type_val="$(get_yaml_field "$page" "type")"
115
- [[ -z "$type_val" ]] && continue
148
+
149
+ # Empty / placeholder pages: a non-meta page with no type (or no body) slips
150
+ # past every downstream check (quality + coverage both key on `type`). Catch
151
+ # it here so a blank or stub file can never ship.
152
+ if [[ -z "$type_val" ]]; then
153
+ if [[ "$(nonblank_body_lines "$page")" -eq 0 ]]; then
154
+ add_error "$rel: empty page (no frontmatter type, no body) — every wiki page must be a real concept"
155
+ else
156
+ add_error "$rel: untyped page (missing frontmatter 'type:') — not a valid concept page"
157
+ fi
158
+ continue
159
+ fi
116
160
  CONCEPT_COUNT=$((CONCEPT_COUNT + 1))
117
161
 
118
162
  for key in title description resource; do
@@ -124,6 +168,24 @@ while IFS= read -r -d '' page; do
124
168
  if ! is_known_type "$type_val"; then
125
169
  add_error "$rel: unknown concept type '$type_val' (frozen vocab: $OKF_TYPES)"
126
170
  fi
171
+
172
+ # A typed concept page with no body is still a stub — fail it even though the
173
+ # depth/anti-stub bars live in the quality layer (this layer must stand alone).
174
+ if [[ "$(nonblank_body_lines "$page")" -eq 0 ]]; then
175
+ add_error "$rel: concept page has empty body"
176
+ fi
177
+ done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
178
+
179
+ # --- 3b. Unreplaced template tokens (any page, including index pages) ---
180
+ # Quality checks skip index.md pages, so a leftover {SECTION_TITLE}/{PROJECT_NAME}
181
+ # placeholder in a hand-seeded index would otherwise survive. Match {ALL_CAPS}
182
+ # tokens (safe: real prose almost never contains them).
183
+ while IFS= read -r -d '' page; do
184
+ prel="${page#"$BUNDLE/"}"
185
+ if grep -qE '\{[A-Z][A-Z0-9_]+\}' "$page" 2>/dev/null; then
186
+ tok="$(grep -oE '\{[A-Z][A-Z0-9_]+\}' "$page" | head -1)"
187
+ add_error "$prel: unreplaced template token '$tok'"
188
+ fi
127
189
  done < <(find "$BUNDLE" -type f -name '*.md' -print0 | sort -z)
128
190
 
129
191
  # --- 4. Cross-link resolution ---
@@ -221,7 +221,7 @@ The tier-gated default rests on **maintainability/readability** (one navigable c
221
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
222
  2. Every `required` entry in `concept-plan.json` has a non-stub page.
223
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**.
224
+ 4. `systems/coverage.md` was generated by tooling (verify the `<!-- okf:coverage-generated -->` marker) — **every** package/module the graph found is required by default (the fan-in floor only types Subsystem-vs-Module, it does not exempt), so none may be **MISSING**. Section `index.md` tables are regenerated by `okf-render-views.sh --section-indexes`, never hand-authored, so their links cannot dangle.
225
225
  5. On any failure: **do not** atomic-rename; surface `.state/validation-report.json`.
226
226
 
227
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.
@@ -77,7 +77,8 @@ Templates for each bundle page live in `core/templates/okf/` (`index.md`,
77
77
  ## Frozen `type` vocabulary
78
78
 
79
79
  Every concept carries a `type` from this frozen set (changing it churns every
80
- file; versioned via `index.md` frontmatter `okf_types_version`):
80
+ file; versioned via the `okf-types-version` comment in the wiki root `index.md`
81
+ body — OKF §6/§11 permit only `okf_version` in an index file's frontmatter):
81
82
 
82
83
  | type | Maps to | Home |
83
84
  |------|---------|------|
@@ -103,6 +104,16 @@ the task at hand?"), never a summary. Draft extensions are namespaced `x-` and
103
104
  ignored by generic OKF consumers: `x-grounded-paths`, `x-hotspot-score`,
104
105
  `x-callers`.
105
106
 
107
+ **Meta pages are not concepts.** Index files (`wiki/index.md` and every
108
+ `<section>/index.md`) carry **no concept frontmatter**: per OKF §6 an index file
109
+ has no frontmatter, and per §11 the root `index.md` may declare **only**
110
+ `okf_version`. The tool-generated `systems/coverage.md` is a non-reserved file, so
111
+ OKF §9.1/§9.2 require it to carry a typed frontmatter block; it uses a descriptive
112
+ `type: Report` and is exempt from the frozen vocabulary via `is_meta_page`
113
+ (basename + the `<!-- okf:coverage-generated -->` marker). `okf-validate.sh`
114
+ short-circuits all meta pages before the concept checks, so they are never
115
+ vocab-checked or counted as concepts.
116
+
106
117
  ## Concept granularity (resolves open decision 1)
107
118
 
108
119
  Derive concepts from the graph, not by hand:
@@ -122,17 +133,21 @@ Derive concepts from the graph, not by hand:
122
133
  ```
123
134
  1. Survey → existing /draft:init 5-phase + graph snapshot (graph-snapshot.sh)
124
135
  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; entrypointsrequired; below-floor / allow-defer
127
- deferred with a reason) and writes draft.tmp/.state/concept-plan.json.
136
+ set from the graph. EVERY package the graph knows about is
137
+ required (fan_in ≥ floor Subsystem; below floor Module — the
138
+ floor only types/orders, it never exempts); entrypoints required;
139
+ only --allow-defer matches are deferred (with a reason). Writes
140
+ draft.tmp/.state/concept-plan.json.
128
141
  okf-plan-concepts.sh --repo . [--scope PATH] \
129
142
  [--manifest FILE] [--min-fan-in 2] [--allow-defer GLOB]... \
130
143
  --out draft.tmp/.state/concept-plan.json
131
144
  This replaces the old in-context concept enumeration — the boundary
132
145
  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.
146
+ and sub-modules cannot be silently dropped. (Legacy fan-in
147
+ exemption is opt-in via --defer-below-floor.) LOG the counts
148
+ (expected/required/deferred) BEFORE writing any page.
149
+ `generated_order` is topo-ish (required + high-fan-in first) so
150
+ forward cross-links resolve.
136
151
  3. Generate → iterate concept-plan.generated_order; write ONE page per REQUIRED
137
152
  entry, grounding each from the graph:
138
153
  x-callers ← graph-callers.sh --symbol <c>
@@ -152,9 +167,11 @@ Derive concepts from the graph, not by hand:
152
167
  --plan draft.tmp/.state/concept-plan.json \
153
168
  --path-index draft.tmp/.state/path-to-concept.json \
154
169
  --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).
170
+ It runs, in order: okf-validate.sh (structure + reverse index +
171
+ empty/untyped-page + leftover-template-token + dangling-link checks),
172
+ okf-validate-quality.sh (per-type anti-stub / depth / per-section
173
+ content / mermaid lint), okf-coverage-check.sh (every required plan
174
+ entry → real page).
158
175
  ANY layer failing ⇒ exit non-zero ⇒ DO NOT atomic-rename.
159
176
  coverage.md (systems/coverage.md) is regenerated by the coverage
160
177
  layer; it is tool-owned (marker <!-- okf:coverage-generated -->) —
@@ -193,6 +210,7 @@ regenerated on every init/refresh so they never drift from the bundle:
193
210
  ```bash
194
211
  okf-render-views.sh draft/wiki \
195
212
  --arch-out draft/architecture.md \
213
+ --section-indexes \
196
214
  --concept-map-into draft/wiki/index.md \
197
215
  --concept-map-into draft/.ai-context.md \
198
216
  --web draft/wiki/web/index.html
@@ -200,6 +218,11 @@ okf-render-views.sh draft/wiki \
200
218
 
201
219
  - `--arch-out` renders the linear `architecture.md` (banner + TOC + every concept
202
220
  page in canonical section order, frontmatter stripped, Mermaid preserved).
221
+ - `--section-indexes` rebuilds each `<section>/index.md` concept table (between its
222
+ `CONCEPT-MAP` markers) from the pages that actually exist in that directory. This
223
+ is mandatory: section indexes are NOT hand-authored — building them from real
224
+ files is what makes their links impossible to dangle. Never write a section
225
+ index "Concepts" table by hand.
203
226
  - `--concept-map-into` rebuilds the routing table between the
204
227
  `<!-- CONCEPT-MAP:START -->` / `:END` markers from each concept's `title` +
205
228
  `type` + `description` (section `index.md` pages excluded).