Skip to content

Validates and regenerates the $MART DEBT Strategies Library — the markdown-with-frontmatter strategies in SDC/IP/Strategies/, a conformant OKF bundle.

  • Script: /home/ta/utils/strategy-lint/ — strategy-lint (wrapper) + strategy_lint.py
  • Windows: \\wsl$\Ubuntu-24.04\home\ta\utils\strategy-lint\
  • Python 3 with pyyaml (already present system-wide). No virtualenv, nothing to install.
Terminal window
~/utils/strategy-lint/strategy-lint lint # validate every record; exit 1 on any error
~/utils/strategy-lint/strategy-lint index # regenerate index.md (root + one per group)
~/utils/strategy-lint/strategy-lint artifact # regenerate library-view.html
~/utils/strategy-lint/strategy-lint lint --lib /some/other/path

The library path defaults to SDC/IP/Strategies, falling back to SDC/Strategy/Library if that does not exist.

The mechanical half of SDC/IP/Strategies/SCHEMA.md §10 — the half a human review reliably misses:

  1. Parseable YAML frontmatter, every required field present.
  2. Every value in its declared vocabulary (unverified — needs Talbot is always allowed).
  3. code unique library-wide and matching its group’s prefix.
  4. rank unique library-wide; group-rank unique within each group.
  5. Every bare wikilink resolves somewhere in the vault.
  6. Every strategy carries a **Risk justification** line; any strategy with benefit set also carries **Benefit justification**.
  7. risk-level parses as an integer — it is the primary sort key and the index groups on it.

Rewrites the root index.md plus one index.md per group folder — risk-level sections, the code/objective/group/jurisdiction/evidence/tool table, the computable-vs-not split, the honest-gaps list. Each group index lists its own suite in teaching order (group-rank). Generated; never hand-edit. Edit a strategy, then re-run index.

Rewrites library-view.html — a single self-contained page with three views: Risk ladder (default), Worth It (a benefit × effort matrix), and By audience. Label toggle (name / code), and domain + evidence filters behind a “See all filters” disclosure, per the F.A.S.T. progressive-disclosure ladder in Core/Processes/Design/.

The data is inlined at build time — an HTML page cannot read vault frontmatter live. Open it from D:\FSS\KB\SDC\IP\Strategies\library-view.html.

Placeholder effort/benefit: while those two fields are unverified — needs Talbot, the artifact derives stand-in values for display only so the Worth It view can be reviewed. They are never written back to a strategy, every affected card is tagged, and a banner says so. Once the real values land the placeholders disappear automatically.

availability and decline-type tags (added 2026-09-22, schema v0.5). A card shows its availability when it is not always, and — where decline-type lists fewer than all four types — a warning tag naming the types it fails in, not the ones it works in. That inversion is deliberate: the schema (§6) makes a decline-type caveat permanent and requires it to travel with the strategy wherever it is read, and “fails in B” is the form a reader acts on. An omitted availability is treated as always, matching the schema’s own default, so pre-v0.5 records render unchanged.

index and artifact chmod 444 everything they write, and chmod 644 it again before rewriting. On WSL that also sets the Windows read-only attribute — verified 2026-09-21: a PowerShell write to a chmod-444 vault file raises UnauthorizedAccessException, so Obsidian cannot save over a generated file. This is the robust version of a “do not hand edit” banner: the banner asks, the file attribute refuses.

Generalizable — any vault file with a generator should get the same treatment. Checked into this util rather than a global hook because only the generator knows which files it owns.

The file is named index.md rather than Library.md because index.md is an OKF reserved filename, which is what makes the folder a conformant OKF bundle (SCHEMA §9).

  • The vocabulary is NOT in this script. It is parsed out of SCHEMA.md §8’s yaml block, which is its single source of truth. Adding a property value or a new group means editing SCHEMA.md and nothing else.
  • Quote any boolean-looking value in that yaml block (yes, no, on, off, y, n, true, false). YAML 1.1 parses them as booleans, so an unquoted entry makes the validator compare the string "yes" against True and reject every strategy in the library. worth-it’s "yes" is the live case.
  • It checks structure, not truth. A clean run says nothing about whether a strategy’s claims are correct or sourced — that is what evidence-status and human review are for.
  • code is never reassigned. If a strategy changes group, its code is grandfathered (Chart-of-Accounts rule, SCHEMA §5). The prefix check will then flag it; that flag is expected and is documented in the strategy, not “fixed” by renumbering.
  • Ranks are sparse (multiples of 10) so an insertion costs one number rather than a renumber. The lint enforces uniqueness, not spacing.
  • Never link the index with a bare [index](/). Vault wikilink resolution is first-occurrence-wins by filesystem walk, and index.md is a name that will recur once a second OKF bundle exists. Use [Strategies Library](/sdc/ip/strategies/).
  • SDC/IP/Strategies/SCHEMA.md §8 (vocabulary SSOT) and §10 (what lint enforces)
  • SDC/IP/JOB_DESCRIPTION.md — the department that owns the library

Gotcha: vault-root detection (fixed 2026-09-24)

Section titled “Gotcha: vault-root detection (fixed 2026-09-24)”

Wikilink checking resolves against every note in the vault, and the vault root is found by walking up from the library. The old sentinel was a CLAUDE.md file. The vault root has none, so the walk climbed to /, and rglob then scanned the whole filesystem including /mnt/c. On WSL that hangs in a 9P call (p9_client_rpc), with no error, indefinitely. The sentinel is now the vault’s own .obsidian/ folder, and the script refuses to run if none is found. Run time dropped from hung (35+ min) to about 3 s. ~/utils/strategy-lint/ is not a git repo, so this fix is recorded here only.