strategy-lint
Section titled “strategy-lint”Validates and regenerates the $MART DEBT Strategies Library — the markdown-with-frontmatter strategies in SDC/IP/Strategies/, a conformant OKF bundle.
Location
Section titled “Location”- 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.
Quick start
Section titled “Quick start”~/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/pathThe library path defaults to SDC/IP/Strategies, falling back to SDC/Strategy/Library if that does not exist.
What lint checks
Section titled “What lint checks”The mechanical half of SDC/IP/Strategies/SCHEMA.md §10 — the half a human review reliably misses:
- Parseable YAML frontmatter, every required field present.
- Every value in its declared vocabulary (
unverified — needs Talbotis always allowed). codeunique library-wide and matching itsgroup’s prefix.rankunique library-wide;group-rankunique within eachgroup.- Every bare
wikilinkresolves somewhere in the vault. - Every strategy carries a
**Risk justification**line; any strategy withbenefitset also carries**Benefit justification**. risk-levelparses as an integer — it is the primary sort key and the index groups on it.
What index does
Section titled “What index does”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.
What artifact does
Section titled “What artifact does”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.
Generated files are left read-only
Section titled “Generated files are left read-only”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).
Scope and gotchas
Section titled “Scope and gotchas”- The vocabulary is NOT in this script. It is parsed out of
SCHEMA.md§8’syamlblock, which is its single source of truth. Adding a property value or a newgroupmeans 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"againstTrueand 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-statusand human review are for. codeis never reassigned. If a strategy changesgroup, 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, andindex.mdis a name that will recur once a second OKF bundle exists. Use[Strategies Library](/sdc/ip/strategies/).
Related
Section titled “Related”SDC/IP/Strategies/SCHEMA.md§8 (vocabulary SSOT) and §10 (whatlintenforces)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.