T-781K-add-obsidian-bases-setup-flag
Status: closed/done · Impact: medium · Complexity: small
Planning artifacts live as markdown under docs/planning/<plural>/, but there’s no built-in way to
browse them in Obsidian as queryable tables. Obsidian’s Bases feature provides per-folder YAML query
files (.base) that render frontmatter as tables. Adding --obsidian to /sdlc:setup lets users
opt into Bases generation in one command, surfacing tasks/epics/milestones/backlog with curated
columns without hand-authoring each .base file.
/sdlc:setupis driven byplugin/scripts/setup_planning.py, which iteratesplugin/entities/<type>/schema.jsonand createsdocs/planning/<plural>/directories. It does not write.basefiles.docs/Untitled.baseanddocs/Untitled 1.baseare minimal stubs (views:\n - type: table\n name: Table) from manual experimentation — not generated, not in the canonical entity folders.- Each entity schema defines the frontmatter shape we’d want to render as columns:
task.schema.jsonhasstatus/impact/complexity/last_reviewed/readiness_verified_at;epicandmilestoneaddtarget_date/version;backloghasstatus/tags/result. - Users who want Obsidian table views over their planning artifacts must hand-write each
.basefile and keep it in sync with schema changes by hand.
Proposed
Section titled “Proposed”/sdlc:setup --obsidiangenerates one<plural>.basefile in eachdocs/planning/<plural>/directory (e.g.tasks.baseindocs/planning/tasks/,backlog.baseindocs/planning/backlog/—backlogis already plural).- Each generated
.basecontains a singletableview scoped to its own folder (viafile.inFolder("docs/planning/<plural>")), with a column order curated per entity (status first, then triage/scheduling fields, then file name), grouped bystatusASC so active stages cluster aboveclosed/*. - Re-running
--obsidianis idempotent: existing.basefiles are left untouched unless--forceis also passed. - Per-entity column choices live alongside each schema as
plugin/entities/<type>/base.yaml, keeping the entity directory the single source of truth (sibling toschema.jsonandtemplate.md). /sdlc:setupwithout--obsidianbehaves exactly as today — no.basefiles written.
Approach
Section titled “Approach”- Add a
base.yamltemplate to eachplugin/entities/<type>/directory — committed as the canonical column set, group/sort, and filter for that entity. YAML so it’s directly emittable to<plural>.base(Bases files are pure YAML). Initial column sets:- task (
tasks.base): columnsstatus,impact,complexity,last_reviewed,readiness_verified_at,file.name.groupBy: { property: note.status, direction: ASC }. Filterfile.inFolder("docs/planning/tasks"). - epic (
epics.base): columnsstatus,target_date,last_reviewed,id,file.name. Group bystatusASC. - milestone (
milestones.base): columnsstatus,version,target_date,last_reviewed,file.name. Sort byversionDESC, thenstatus. - backlog (
backlog.base): columnsstatus,tags,last_reviewed,result,file.name. Group bystatusASC.
- task (
- Extend
plugin/scripts/setup_planning.py: add--obsidianand--forceflags. New helper functionwrite_bases(entities_dir, planning_root, force, dry_run)that, for each entity, readsplugin/entities/<type>/base.yaml, resolves destinationdocs/planning/<plural>/<plural>.base, and writes (or skips with a “exists” report) per the idempotency rule. Reuse the existingpluralize()helper. Keep emission deterministic — copy the template bytes verbatim rather than parsing-and-re-emitting (avoids YAML key reorder churn). - Update
plugin/skills/setup/SKILL.mdusage block to document--obsidianand--force, with a one-sentence pointer to Obsidian Bases (https://help.obsidian.md/bases). Keep the existing dry-run / audit behavior unchanged. - Manually open one generated
.basefile (e.g.docs/planning/tasks/tasks.base) in Obsidian to confirm it loads as a table without YAML errors. Document the verification step in the PR.
Files to touch
Section titled “Files to touch”plugin/entities/task/base.yaml(new) — task Bases template; status-first columns, group by status ASC, filter todocs/planning/tasks.plugin/entities/epic/base.yaml(new) — epic Bases template.plugin/entities/milestone/base.yaml(new) — milestone Bases template; sort by version DESC.plugin/entities/backlog/base.yaml(new) — backlog Bases template.plugin/scripts/setup_planning.py— add--obsidianand--forceflags; addwrite_bases()helper; wire it intomain()so it runs after directory creation when--obsidianis set.plugin/skills/setup/SKILL.md— document the new flags in the Usage block and mention Bases briefly in Notes.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Running
/sdlc:setup --obsidianin a fresh project createstasks.base,epics.base,milestones.base, andbacklog.basein their respectivedocs/planning/<plural>/directories. - AC-2: Each generated
.basefile is valid YAML, contains a singletableview, and the columns referenced underviews[0].orderall map to either a real frontmatter field declared in that entity’sschema.jsonor a documentedfile.*/formula.*reference. - AC-3: Re-running
/sdlc:setup --obsidianwithout--forceleaves an existing.basefile untouched (mtime unchanged); re-running with--forceoverwrites it from the template. - AC-4: Opening one generated
.basefile in Obsidian renders the corresponding planning artifacts as a table without “invalid base” errors (manual verification, noted in PR). - AC-5:
/sdlc:setupwithout--obsidianwrites zero.basefiles — directory-creation behavior is byte-for-byte unchanged. - AC-6:
/sdlc:setup --obsidian --dry-runreports what.basefiles would be written without touching the filesystem.
Out of scope
Section titled “Out of scope”- View types beyond
table(card, gallery, kanban).tableonly for v1. - Cross-entity views (e.g. one base showing all open work across tasks + epics + milestones).
- Formulas (e.g.
days_since_last_reviewed). Can be added once the basic table views are validated in the wild. - An inverse flag to delete previously-generated
.basefiles. - Auto-regenerating
.basefiles when an entity schema changes. The user re-runs with--force.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”<Optional. How this task got onto the list: linked incident, design doc, upstream conversation, prior PR review comment. Helps future-you remember why this was worth doing. Delete this section if there’s nothing to add.>
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-20. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: agent-manual — ran
setup_planning.py --obsidianin a fresh tmp dir;find docs -name "*.base"produceddocs/planning/{tasks/tasks,epics/epics,milestones/milestones,backlog/backlog}.base. - AC-2: auto — Python
yaml.safe_loadparsed eachplugin/entities/<type>/base.yaml, assertedviews[0].type == 'table'and thatorderwas a non-empty list; printed the order for inspection. - AC-3: agent-manual — captured mtime of
tasks.base(1779240251), re-ran without--force(mtime unchanged), re-ran with--force(mtime 1779240258). - AC-4: auto —
plugin/validators/validate_base.py(headless Obsidian Bases parser) validates each shippedplugin/entities/<type>/base.yaml;.claude/skills/project-check/check_entity_bases.pyruns the validator against every template as part of/project-check. Originallydeferred-userbecause Obsidian itself surfaces “invalid base” errors only at render time; reclassified toautoby T-6ZNY-headless-bases-syntax-parser-for-eval. - AC-5: agent-manual — ran
setup_planning.py(no flags) in a separate fresh tmp dir;find docs -name "*.base"produced no output. - AC-6: agent-manual —
setup_planning.py --obsidian --dry-runreported the fourwould write:lines;find docs -type fafter dry-run was empty.
What worked
Section titled “What worked”- The “copy template bytes verbatim” rule from the task spec eliminated all YAML-emit churn; mtime-based idempotency just works.
- Pre-shipping
base.yamlper entity (sibling toschema.json) keeps Bases column choices co-located with the schema they reference — easy to keep in sync. - The pre-existing pluralize() drift on
backlogwas caught immediately by the first dry-run and isolated to a one-line conditional inwrite_baseswithout touchingpluralize().
Friction and automation gaps
Section titled “Friction and automation gaps”just setup-worktreefailed (nojustfilein this repo) — task-work’s Step 4 init was a no-op fallback. The existing backlog task2026-05-19-task-work-graceful-no-justfile-fallback(and the broadertask-work-uses-per-project-quality-checks) already cover this gap. → T-FC8W-task-work-graceful-no-justfile-fallback- Pre-existing
backlogvsbacklogsdirectory inconsistency surfaced again:pluralize("backlog")producesbacklogsbut real backlog content lives indocs/planning/backlog/. Worked around inwrite_baseswith a hardcoded carve-out; root fix would require either (a) makingpluralize()recognizebacklogas already plural (changes AC-5’s invariant), or (b) renaming the entity dir frombacklog/tobacklogitem/(heavier). Worth its own follow-up. → T-5BAR-pluralize-recognizes-backlog-as-plural - AC-2 was verifiable by a Python one-liner but not by any committed test — a
plugin/entities/*/base.yamlshape check would prevent silent drift if the templates evolve. Worth a small lint/validator follow-up. → T-DIMU-lint-entity-base-yaml-templates - AC-4 was structurally a deferred-user check because Obsidian isn’t headless; an eval pattern that
loads a
.basethrough a minimal Bases-syntax parser closes the loop. Resolved by T-6ZNY-headless-bases-syntax-parser-for-eval (validator atplugin/validators/validate_base.py, project-check wiring at.claude/skills/project-check/check_entity_bases.py). → T-6ZNY-headless-bases-syntax-parser-for-eval
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-FC8W-task-work-graceful-no-justfile-fallback — linked to existing task; task-work should detect missing justfile and degrade gracefully.
- T-5BAR-pluralize-recognizes-backlog-as-plural — created; centralize the backlog vs backlogs
carve-out in
pluralize(). - T-DIMU-lint-entity-base-yaml-templates — created; project-check lint that validates each
plugin/entities/<type>/base.yamlagainst its sibling schema. - T-6ZNY-headless-bases-syntax-parser-for-eval — created; headless Bases-syntax parser so AC-4 stops being deferred-user.