T-VWH3-ts-index-generator-script
Status: closed/done · Impact: medium · Complexity: medium
AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-06-03 at operator request (the task carries no autonomy field). Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.
Make docs/index.md a true build artifact: a deterministic TypeScript
(bun) generator that the index skill shells out to, replacing the
hand-maintained Reference tables with generated ones. The skill becomes
a thin head over the deterministic tail (per
S-0004-sdlc-cli-llm-head-deterministic-tail /
S-0001-co-locate-first-promote-when-shared).
Deferred from the index PR: tooling is mid-migration from Python to TypeScript, so no generator code was written there. This task is the plan of work; build it in TypeScript/bun once that surface is ready.
Context
Section titled “Context”docs/index.md today is a hand-authored copy of what the generator
should emit. Its shape and rules are already specified in
plugin/skills/index/SKILL.md (the Reference section is one ## table
per entity type; summary cells transclude each entity’s ^summary
block). This task turns that spec into running code.
The 2026-06-02 planning review sweep of docs/index.md added three
structural refinements the generator must honour, folded into the Plan
below: render through a template fed plain data structures, split
each section into two tables (active vs emerging/obsoleted), and
render deferred milestones as a table rather than a prose list.
| Location | Role today |
|---|---|
plugin/skills/index/SKILL.md | The LLM-head index skill: specifies the Reference-table rules and explicitly defers the deterministic generator to this task (“regenerate by hand … this skill is the head for now”) |
docs/index.md | Hand-maintained output the generator replaces — declared a never-hand-edited build artifact, but regenerated by the LLM today |
plugin/lib/model/authoring.ts | Existing Eta render pipeline (entity body templates) — the engine precedent the new template reuses |
plugin/lib/util/yaml.ts | Promoted YAML helper the extraction module uses for frontmatter parsing |
package.json | Ships eta already; no remark/mdast dependency yet |
-
A small markdown-extraction module (TS) built on remark/mdast: parse once to AST; extract a
## sectionbody by heading; test for a^block-id; iterate entity files under a directory (excluding README/index). Frontmatter viaremark-frontmatter, parsed with the existinglib/util/yamlhelper. remark is a new dependency, but it is the AST substrate B-8FL9 proposes for structural validation and a T-0007 formatter candidate — this module seeds that shared use rather than hand-rolling parsers. -
A template/renderer using Eta — already a dependency and the engine behind the entity body pipeline (
plugin/lib/model/authoring.ts). Separate the two responsibilities: the generator gathers entities into data; the.etatemplate renders. The template is the single place table layout lives, so generation stays consistent and the skill’s job is reduced to handing it data. -
A generator that reads
docs/planning/entities, builds those data structures, and writesdocs/index.mdthrough the template:- Narrative: a fixed pointer block to
vision/ PR-0001-sdlc / D-VSLI-distributed-work-runner-architecture (no transclusion of long prose). - Reference tables, sourced from frontmatter +
^summary: Products, Drivers (bykindthen id), Milestones (ascendingversion, unversioned last), Features (kind: feature), Principles (grouped by theprinciple/<category>tag), Standards, ADRs (folder ADRs inline, single-file ADRs via^summary), Technical capabilities (kind: technical). - Summary cells emit
<id>transclusions.
- Narrative: a fixed pointer block to
-
Two tables per entity type, across the board. Each catalogued section (Principles, Standards, ADRs, Capabilities, Drivers, Milestones — not just some) splits its entities by lifecycle status into an Active table and a follow-up Emerging / obsoleted table. Omit a table when its bucket is empty. Conceptual buckets, drawn from the existing per-entity status enums (the implementer finalises the exact boundary per schema — no schema changes needed):
Bucket Status values Active (in effect) open/active,open/accepted,open/published; productsactiveEmerging open/draft,open/proposed,open/plannedObsoleted every closed/*—retired,superseded,deprecated,abandoned,partialThe follow-up table combines Emerging + Obsoleted (with a status column so the two are distinguishable).
-
Deferred milestones render as a table, not the current
·-separated prose list — id · milestone · status columns. This is the Milestones section’s follow-up (emerging/obsoleted) table. -
Wire the
indexskill to call the generator; keep the skill body as the human-readable contract. Update the SKILL.md generation rules to document the two-table-per-section shape. -
Idempotent: re-running against unchanged sources produces no diff.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/skills/index/markdown_extract.ts | new | remark/mdast extraction helpers: frontmatter → object (via remark-frontmatter + plugin/lib/util/yaml.ts), section body by heading, ^block-id test, entity-file iteration |
plugin/skills/index/index_template.eta | new | Eta template owning the index layout: narrative pointer block, two tables per section, deferred-milestones table |
plugin/skills/index/generate_index.ts | new | Generator entry: read docs/planning/ entities → plain data structures → render template → write docs/index.md |
plugin/skills/index/tests/ | new | Fixture test suite: extraction helpers + generator idempotency (per the established plugin/skills/<slug>/tests/ pattern) |
plugin/skills/index/SKILL.md | modify | Wire the skill to shell out to the generator; document the two-table-per-section shape in the generation rules |
docs/index.md | modify | First generated output replaces the hand-maintained Reference tables |
package.json | modify | Add the remark/mdast dependencies (remark-parse, remark-frontmatter, unified) |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
bun run plugin/skills/index/generate_index.tsregeneratesdocs/index.mdfrom entity data passed through the Eta template. (Registering it as thesdlc index generateverb is T-0014’s scope per its 2026-06-03 hand-off note.) - AC-2: Every catalogued section emits an Active table and, when its bucket is non-empty, a follow-up Emerging / obsoleted table.
- AC-3: Deferred milestones render as a table, not a prose list.
- AC-4: Running the generator twice in a row yields no diff on the second run.
- AC-5: The extraction helpers carry a fixture test suite under
plugin/skills/index/tests/, passing viabun test.
Out of scope
Section titled “Out of scope”- Any Python changes (tooling is migrating to TypeScript/bun).
- Restructuring the entity schemas.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-06-03. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
bun run plugin/skills/index/generate_index.tsemitsINDEX-GENERATE-OKand writesdocs/index.md - AC-2: auto — rendered-output tests + parent spot-check (6 follow-up tables present, Active table per section)
- AC-3: agent-manual — Milestones follow-up renders as a Version · ID · Milestone · Status table (9 rows), prose list gone
- AC-4: auto — two consecutive runs byte-identical (
git diff --stat docs/index.mdempty; idempotency test in suite) - AC-5: auto —
bun test plugin/skills/index/tests/→ 33 pass / 0 fail
What worked
Section titled “What worked”- The auto-define → ensure-ready → task-work loop carried a spec gap to verified-ready and through to implementation in one session
- Co-located precedents (authoring.ts for Eta config, skills/*/tests/ pattern) made conventions unambiguous for the implementing sub-agent
- The generator emits its own slug-namespaced
INDEX-GENERATE-OKmarker, consistent with the marker convention
Friction and automation gaps
Section titled “Friction and automation gaps”- Step 7’s baseline diff flagged 8 false-positive new-drift findings: pre-existing failing suites
embed randomized tempdir paths (
/tmp/claude-502/append-pr-url-*/) so the differ can’t match them run-to-run — already tracked by T-TWZD-normalize-baseline-diff-nondeterministic-output; link, don’t respawn → T-TWZD-normalize-baseline-diff-nondeterministic-output - Fresh worktree had no node_modules; the implementing sub-agent had to run
bun installmanually before anything worked — already tracked by T-7PXF-declare-worktree-init-bun-install; link, don’t respawn → T-7PXF-declare-worktree-init-bun-install - Step 7’s gate resolved the baseline dir from the worktree cwd while Step 3a’s capture wrote to the
main repo’s
.sdlc/quality-baselines/, costing a failed invocation until--baseline-dirwas passed explicitly — run_quality_checks.ts could resolve the baseline dir via the git common dir so worktree invocations find the main repo’s cache without the flag → T-44OO-plugin-scripts-self-discover-project-root - Driver and Capability statuses on disk (
validated/verified) fall outside the task’s Active/Emerging/Obsoleted bucket table, so both sections render entirely in follow-up tables — a human should either bless those statuses into the Active bucket (generator change) or normalize the source entities’ statuses; needs a decision, not silently fine → T-0RW4-index-generator-status-bucket-coverage - Task spec said frontmatter is “parsed with the existing lib/util/yaml helper” but that module is
dumper-only; the implementer substituted the
yamlpackage’s parse (repo convention) — spec-authoring drift between intent and the cited helper’s actual surface → T-RKWR-auto-define-cites-real-helper-surface
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-0RW4-index-generator-status-bucket-coverage (https://github.com/sksizer/dev/pull/258) — Upstream-plugin (sdlc-meta); index generator’s bucket map should cover Driver/Capability statuses (validated/verified). spawned
- T-RKWR-auto-define-cites-real-helper-surface (https://github.com/sksizer/dev/pull/259) — Upstream-plugin (sdlc-meta); auto-define / task specs should cite a helper’s real surface (yaml parse vs dump). spawned
- T-44OO-plugin-scripts-self-discover-project-root — baseline-dir-via-git-common-dir; active successor to the closed/superseded T-5X6Y. linked
- T-TWZD-normalize-baseline-diff-nondeterministic-output — randomized-tmpdir baseline-diff false positives. linked
- T-7PXF-declare-worktree-init-bun-install — fresh worktree missing node_modules. linked