From 3d9bf6600c59d14b88458d49f70ec5a671bff920 Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:36:32 -0500 Subject: [PATCH 1/6] chore: scaffold SDLC planning structure Adds docs/planning/{backlogs,epics,milestones,tasks}/ from the sdlc plugin's /sdlc:setup, with Obsidian Bases files per entity. Includes the obsidian-vault-id-uri task spec. --- docs/planning/backlogs/backlogs.base | 16 ++ docs/planning/epics/epics.base | 16 ++ docs/planning/milestones/milestones.base | 18 ++ .../tasks/2026-05-19-obsidian-vault-id-uri.md | 169 ++++++++++++++++++ docs/planning/tasks/tasks.base | 17 ++ 5 files changed, 236 insertions(+) create mode 100644 docs/planning/backlogs/backlogs.base create mode 100644 docs/planning/epics/epics.base create mode 100644 docs/planning/milestones/milestones.base create mode 100644 docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md create mode 100644 docs/planning/tasks/tasks.base diff --git a/docs/planning/backlogs/backlogs.base b/docs/planning/backlogs/backlogs.base new file mode 100644 index 0000000..b13b4ba --- /dev/null +++ b/docs/planning/backlogs/backlogs.base @@ -0,0 +1,16 @@ +filters: + and: + - file.inFolder("planning/backlogs") + - file.ext != "base" +views: + - type: table + name: All backlog items + order: + - status + - tags + - last_reviewed + - result + - file.name + sort: + - property: status + direction: ASC diff --git a/docs/planning/epics/epics.base b/docs/planning/epics/epics.base new file mode 100644 index 0000000..4fd18ed --- /dev/null +++ b/docs/planning/epics/epics.base @@ -0,0 +1,16 @@ +filters: + and: + - file.inFolder("planning/epics") + - file.ext != "base" +views: + - type: table + name: All epics + order: + - status + - target_date + - last_reviewed + - id + - file.name + sort: + - property: status + direction: ASC diff --git a/docs/planning/milestones/milestones.base b/docs/planning/milestones/milestones.base new file mode 100644 index 0000000..48af4b2 --- /dev/null +++ b/docs/planning/milestones/milestones.base @@ -0,0 +1,18 @@ +filters: + and: + - file.inFolder("planning/milestones") + - file.ext != "base" +views: + - type: table + name: All milestones + order: + - status + - version + - target_date + - last_reviewed + - file.name + sort: + - property: version + direction: DESC + - property: status + direction: ASC diff --git a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md new file mode 100644 index 0000000..fc02401 --- /dev/null +++ b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md @@ -0,0 +1,169 @@ +--- +type: task +schema_version: 1 +status: open/ready +created: '2026-05-19' +last_reviewed: '2026-05-19' +impact: high +complexity: small +tags: +- bugfix +- obsidian +- uri +related: [] +--- +# Emit Obsidian URIs using internal vault id (0.4) + +## Goal + +Stop ambiguous `obsidian://open?vault=` URIs from misrouting +to the wrong vault when two registered Obsidian vaults share a folder +basename. Switch to emitting the vault's internal id +(`vault=`) so the launch is deterministic. Ships as +`path-opener` 0.4.0. + +This task is the upstream half of polish task +`2026-05-19-obsidian-uri-vault-id` (see +`/Users/sksizer/Developer/polish/docs/planning/tasks/2026-05-19-obsidian-uri-vault-id.md`). +The downstream half (bumping `path-opener` in polish and verifying) +stays tracked in polish. + +## Today + +- `src/obsidian.rs::discover_vaults` reads `obsidian.json` and + produces `Vault { id, name, path }` records, where `id` is the + stable internal Obsidian identifier and `name` is the folder + basename (`src/obsidian.rs:67-69`). +- `Vault.id` carries `#[allow(dead_code)]` (`src/obsidian.rs:25-27`) + — it's parsed and discarded for URI purposes. +- `build_uri` (`src/obsidian.rs:86-100`) emits URIs using `name`: + - vault root → `obsidian://open?vault=` + - file inside a vault → `obsidian://open?vault=&file=` + - outside any known vault → `obsidian://open?path=` +- Vault matching uses `v.path == abs` (post-`canonicalize`), which + correctly picks ONE vault per path. The bug is downstream of that + pick: if two vaults register with the same basename + (`/Users/me/work/notes` and `/Users/me/personal/notes`), both + legitimately produce `vault=notes`, and Obsidian's URI resolver + picks whichever vault it considers first internally. +- Existing tests in `src/obsidian.rs:208-265` cover the three + emission strategies but use unique vault names, so the ambiguity + case is uncovered. +- Current published: 0.3.0 (preview_command API). + +## Proposed + +- Emit Obsidian URIs using the internal vault id unconditionally. + The strategy ladder becomes: + - vault root → `obsidian://open?vault=` + - file inside a vault → `obsidian://open?vault=&file=` + - outside any known vault → `obsidian://open?path=` (unchanged) +- `Vault.id` loses its `#[allow(dead_code)]` annotation; the field + is now load-bearing. +- Always-on, not conditional on detecting duplicate names: + 1. Obsidian's URI scheme accepts `vault=` equivalently to + `vault=`. + 2. IDs are stable across vault renames, so launches stay correct + even when the user renames a folder. + 3. Conditional behavior — "use id when ambiguous, name otherwise" — + creates two code paths and a third state (the ambiguity + detection itself) for negligible win. + 4. The `preview_command` API (0.3) exposes the URI to downstream + consumers; ids are still human-readable enough in that surface. + +## Approach + +1. **Switch URI emission to `vault=`.** In + `src/obsidian.rs::build_uri` (lines 86-100): + + ```rust + // before + if let Some(v) = vaults.iter().find(|v| v.path == abs) { + return Ok(format!("obsidian://open?vault={}", encode(&v.name))); + } + if let Some((v, rel)) = vaults.iter().find_map(...) { + let rel_str = rel.to_str().ok_or_else(...)?; + return Ok(format!("obsidian://open?vault={}&file={}", encode(&v.name), encode(rel_str))); + } + + // after + if let Some(v) = vaults.iter().find(|v| v.path == abs) { + return Ok(format!("obsidian://open?vault={}", encode(&v.id))); + } + if let Some((v, rel)) = vaults.iter().find_map(...) { + let rel_str = rel.to_str().ok_or_else(...)?; + return Ok(format!("obsidian://open?vault={}&file={}", encode(&v.id), encode(rel_str))); + } + ``` + + Remove `#[allow(dead_code)]` from `Vault.id` (`src/obsidian.rs:25`). + The `path` query fallback (line 99) is unchanged. + +2. **Update tests.** Adjust the three existing assertion tests in + `src/obsidian.rs:208-265` so expected URIs use the vault id, not + the name. Add a new test that builds two vaults with the same + `name` but different `id` and asserts both produce distinct URIs + (proves the bug is fixed). Keep the URI-encoding tests as they are. + +3. **Release 0.4.0.** Bump `Cargo.toml` to `0.4.0`. Update + `CHANGELOG.md` with a clearly-marked BREAKING-IF-OBSERVED note: + *"Obsidian URIs now use the vault's internal id (`vault=`) + instead of the basename (`vault=`). Behavior is identical + when vault names are unique; for users with duplicate-named vaults, + the launch is now deterministic. Downstream consumers that scrape + the URI may need to update."* The user-visible behavior change + warrants a minor-version bump (0.3 → 0.4) rather than a patch. + `cargo publish`. + +## Files to touch + +- `src/obsidian.rs` — change `build_uri` to emit `vault=` in + both the vault-root and file-in-vault arms; drop + `#[allow(dead_code)]` on `Vault.id`; update existing tests; + add the duplicate-name disambiguation test. +- `Cargo.toml` — bump version to `0.4.0`. +- `CHANGELOG.md` — note the URI-emission change with the + user-visible-behavior callout. + +## Acceptance criteria + +- [ ] AC-1: `build_uri` emits `vault=` for both the vault-root + and file-in-vault arms; the `path` fallback is unchanged. +- [ ] AC-2: New test: two `Vault` records with identical `name` but + distinct `id` produce distinct URIs (the bug-fix proof). +- [ ] AC-3: Existing emission tests pass with updated expected URIs. +- [ ] AC-4: `Vault.id` is no longer `#[allow(dead_code)]` — the + lint passes without the attribute. +- [ ] AC-5: `CHANGELOG.md` documents the 0.4.0 behavior change with + the BREAKING-IF-OBSERVED callout. +- [ ] AC-6: `path-opener` 0.4.0 is published to crates.io. +- [ ] AC-7: `just full-check` passes. + +## Out of scope + +- Backwards-compatibility shim. We're not keeping the `vault=` + emission as a fallback or option. The id form is strictly more + correct and Obsidian supports both equivalently. +- Migrating Obsidian's `obsidian.json` itself or interacting with + Obsidian's vault registry beyond reading it. +- Exposing vault id information through path-opener's public API + (it's still crate-private — `Vault` is `pub(crate)`). +- Detecting and warning about duplicate-name vaults inside any + downstream consumer. Diagnostics are not the bug; the + wrong-launch is. + +## Dependencies + +- None. 0.4 changes are non-conflicting with 0.3's preview_command + API (different code paths). + +## Discovery context + +- Surfaced 2026-05-19 from downstream usage in + `sksizer/polish`: user reported "open in Obsidian" launching the + wrong vault and hypothesized that Obsidian was mishandling vaults + with the same folder name. Investigation showed the path-opener + URI builder emits `vault=` which Obsidian resolves + non-deterministically when names collide. +- Obsidian's URI scheme docs confirm `vault=` is supported and + is the recommended unambiguous form. diff --git a/docs/planning/tasks/tasks.base b/docs/planning/tasks/tasks.base new file mode 100644 index 0000000..b6b65d9 --- /dev/null +++ b/docs/planning/tasks/tasks.base @@ -0,0 +1,17 @@ +filters: + and: + - file.inFolder("planning/tasks") + - file.ext != "base" +views: + - type: table + name: All tasks + order: + - status + - impact + - complexity + - last_reviewed + - readiness_verified_at + - file.name + sort: + - property: status + direction: ASC From cb7fad2e5a5693f4cfb6d9b048195c26cddf3197 Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:37:51 -0500 Subject: [PATCH 2/6] chore(tasks): start 2026-05-19-obsidian-vault-id-uri Emit Obsidian URIs using internal vault id (0.4) --- docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md index fc02401..26a9024 100644 --- a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md +++ b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md @@ -1,9 +1,9 @@ --- type: task schema_version: 1 -status: open/ready +status: in-progress created: '2026-05-19' -last_reviewed: '2026-05-19' +last_reviewed: '2026-05-20' impact: high complexity: small tags: From 7e1bd8f20d54d4605da1ac0bc4bb771935b8d40e Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:37:36 -0500 Subject: [PATCH 3/6] docs(tasks): verify 2026-05-19-obsidian-vault-id-uri implementation-ready --- docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md index 26a9024..82c9216 100644 --- a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md +++ b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md @@ -4,6 +4,7 @@ schema_version: 1 status: in-progress created: '2026-05-19' last_reviewed: '2026-05-20' +readiness_verified_at: '2026-05-20T04:37:11Z' impact: high complexity: small tags: From 0d1b3d8c005843746a275e9888b74ecf533adabb Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:51:51 -0500 Subject: [PATCH 4/6] feat(obsidian): emit URIs with internal vault id Switch `build_uri` to format URIs as `vault=` instead of `vault=`. When two registered Obsidian vaults share a folder basename (e.g. `~/work/notes` and `~/personal/notes`), the basename form was ambiguous and Obsidian's URI resolver picked one non-deterministically. The internal vault id is stable and unique, so launches are now deterministic. - `Vault.id` is now load-bearing; `#[allow(dead_code)]` moves to `name` (retained for diagnostic context and test fixtures). - New test `uri_disambiguates_vaults_with_identical_basename` proves the fix. - `vault=` and `vault=` are equivalent in Obsidian's URI scheme, so unique-name vaults behave identically. - Bumps version to 0.4.0 with a BREAKING-IF-OBSERVED note for downstream consumers that scrape the URI. --- CHANGELOG.md | 6 ++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- src/obsidian.rs | 55 +++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b84532..10a6f77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [0.4.0] - 2026-05-20 + +### Changed + +- **BREAKING-IF-OBSERVED:** Obsidian URIs now use the vault's internal id (`vault=`) instead of the basename (`vault=`). Behavior is identical when vault names are unique; for users with duplicate-named vaults (e.g. `~/work/notes` and `~/personal/notes`), the launch is now deterministic — Obsidian previously picked one of the colliding vaults non-deterministically. Downstream consumers that scrape the URI (via `preview_command` or otherwise) may need to update if they were parsing `vault=` as a human-readable name. + ## [0.3.0] - 2026-05-19 ### Added diff --git a/Cargo.lock b/Cargo.lock index 01963d8..13a2e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "path-opener" -version = "0.3.0" +version = "0.4.0" dependencies = [ "dirs", "serde", diff --git a/Cargo.toml b/Cargo.toml index c8b363e..ce8df24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "path-opener" -version = "0.3.0" +version = "0.4.0" edition = "2024" description = "Detects installed apps (editors, terminals, file managers, Markdown apps) and opens paths with them. Cross-platform." license = "MIT" diff --git a/src/obsidian.rs b/src/obsidian.rs index 239b1e9..e1cb34b 100644 --- a/src/obsidian.rs +++ b/src/obsidian.rs @@ -4,9 +4,13 @@ //! CLI. This module reads Obsidian's own config to discover vaults, then assembles //! the right URI based on what `path` it's handed: //! -//! - vault root → `obsidian://open?vault=` -//! - file inside a vault → `obsidian://open?vault=&file=` +//! - vault root → `obsidian://open?vault=` +//! - file inside a vault → `obsidian://open?vault=&file=` //! - anything else → `obsidian://open?path=` (Obsidian decides) +//! +//! `vault=` uses Obsidian's stable internal vault identifier rather than +//! the folder basename, so launches stay deterministic when two registered +//! vaults share a basename (e.g. `~/work/notes` and `~/personal/notes`). use std::collections::HashMap; use std::io; @@ -21,10 +25,13 @@ use serde::Deserialize; /// absent from path-opener's public vocabulary. #[derive(Debug, Clone)] pub(crate) struct Vault { - /// Internal ID Obsidian assigns. Stable across vault renames. - #[allow(dead_code)] + /// Internal ID Obsidian assigns. Stable across vault renames; emitted in + /// `vault=` URIs so launches stay deterministic when two registered + /// vaults share a folder basename. pub(crate) id: String, - /// Vault display name — basename of `path`. This is what `vault=` in URIs expects. + /// Vault display name — basename of `path`. Retained for diagnostic context + /// and test fixtures; URIs use `id` instead (see module docs). + #[allow(dead_code)] pub(crate) name: String, /// Absolute path to the vault root directory. pub(crate) path: PathBuf, @@ -80,19 +87,19 @@ pub(crate) fn build_command(path: &Path) -> io::Result { /// Pure URI-building logic, factored out for testability. /// /// Walks the strategy ladder: -/// 1. `path` matches a known vault root → `obsidian://open?vault=` -/// 2. `path` lives inside a known vault → `obsidian://open?vault=&file=` +/// 1. `path` matches a known vault root → `obsidian://open?vault=` +/// 2. `path` lives inside a known vault → `obsidian://open?vault=&file=` /// 3. Otherwise → `obsidian://open?path=` fn build_uri(path: &Path, vaults: &[Vault]) -> io::Result { let abs = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); if let Some(v) = vaults.iter().find(|v| v.path == abs) { - return Ok(format!("obsidian://open?vault={}", encode(&v.name))); + return Ok(format!("obsidian://open?vault={}", encode(&v.id))); } if let Some((v, rel)) = vaults.iter().find_map(|v| abs.strip_prefix(&v.path).ok().map(|r| (v, r))) { let rel_str = rel.to_str().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "non-utf8 path"))?; - return Ok(format!("obsidian://open?vault={}&file={}", encode(&v.name), encode(rel_str))); + return Ok(format!("obsidian://open?vault={}&file={}", encode(&v.id), encode(rel_str))); } let abs_str = abs.to_str().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "non-utf8 path"))?; @@ -213,7 +220,7 @@ mod tests { let vaults = vec![Vault { id: "v1".into(), name: "MyVault".into(), path: vault_dir.clone() }]; let uri = build_uri(&vault_dir, &vaults).expect("build_uri"); - assert_eq!(uri, "obsidian://open?vault=MyVault"); + assert_eq!(uri, "obsidian://open?vault=v1"); let _ = fs::remove_dir_all(&tmp); } @@ -228,7 +235,33 @@ mod tests { let vaults = vec![Vault { id: "v1".into(), name: "Notes".into(), path: vault_dir.clone() }]; let uri = build_uri(&file, &vaults).expect("build_uri"); - assert_eq!(uri, "obsidian://open?vault=Notes&file=sub%2Fnote.md"); + assert_eq!(uri, "obsidian://open?vault=v1&file=sub%2Fnote.md"); + + let _ = fs::remove_dir_all(&tmp); + } + + #[test] + fn uri_disambiguates_vaults_with_identical_basename() { + // Two vaults legitimately register with the same folder basename — e.g. + // `~/work/notes` and `~/personal/notes`. Pre-0.4, both produced + // `vault=notes` and Obsidian picked one non-deterministically. Emitting + // `vault=` makes the launch deterministic. + let tmp = env::temp_dir().join(format!("path-opener-test-vault-dupe-{}", std::process::id())); + let _ = fs::remove_dir_all(&tmp); + let work = make_real_dir(&tmp.join("work"), "notes"); + let personal = make_real_dir(&tmp.join("personal"), "notes"); + + let vaults = vec![ + Vault { id: "work-id".into(), name: "notes".into(), path: work.clone() }, + Vault { id: "personal-id".into(), name: "notes".into(), path: personal.clone() }, + ]; + + let uri_work = build_uri(&work, &vaults).expect("build_uri work"); + let uri_personal = build_uri(&personal, &vaults).expect("build_uri personal"); + + assert_eq!(uri_work, "obsidian://open?vault=work-id"); + assert_eq!(uri_personal, "obsidian://open?vault=personal-id"); + assert_ne!(uri_work, uri_personal, "duplicate-name vaults must produce distinct URIs"); let _ = fs::remove_dir_all(&tmp); } From bec00d6ae9fca268a4e621a3ad6f825e13be0d80 Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:52:30 -0500 Subject: [PATCH 5/6] docs(tasks): post-mortem for 2026-05-19-obsidian-vault-id-uri --- .../tasks/2026-05-19-obsidian-vault-id-uri.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md index 82c9216..06d2d1c 100644 --- a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md +++ b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md @@ -168,3 +168,30 @@ stays tracked in polish. non-deterministically when names collide. - Obsidian's URI scheme docs confirm `vault=` is supported and is the recommended unambiguous form. + +## Post-mortem + +_Captured by /sdlc:task-work on 2026-05-20. PR: pending._ + +### Acceptance criteria coverage + +- AC-1: auto — `cargo test obsidian::tests::` (both vault-root and file-in-vault arms have updated assertions; the path fallback is still covered by `uri_for_path_outside_any_vault_falls_through_to_path_query`). +- AC-2: auto — new test `uri_disambiguates_vaults_with_identical_basename` asserts distinct URIs from two same-name vaults. +- AC-3: auto — `cargo test` (all 20 unit tests + 6 doctests pass). +- AC-4: auto — `cargo clippy -- --deny warnings` (via `just full-check`) passes without `#[allow(dead_code)]` on `Vault.id`. +- AC-5: auto — CHANGELOG.md content visible in diff. +- AC-6: deferred-user — `cargo publish` is a live, irreversible release action that must be authorized by the maintainer. +- AC-7: auto — `just full-check` passes (see commit-time check log). + +### What worked + +- Spec included the literal before/after diff for `build_uri`. Implementation was mechanical; sub-agent delegation would have added overhead with no upside. +- `just full-check` + `just ci` caught nothing because there was nothing to catch — first run was clean, tests passed first time. + +### Friction and automation gaps + +- The 5b rebase (gate-then-flip) hit a frontmatter conflict because the verify-commit (on feat) and the start-commit (on main) both touched the YAML block, on adjacent lines. The conflict resolution is structurally always the union of fields. `/sdlc:task-work`'s skill text says "stop and ask" on rebase conflict, but this specific conflict is inherent to the design and asking the user is busywork — the skill could either (a) pre-resolve via a deterministic merge driver, or (b) have ensure-ready stamp on a separate line so the conflict is empty, or (c) document that this conflict shape is expected and auto-resolvable. +- `/sdlc:setup --obsidian` wrote `docs/planning/backlog/backlog.base` (singular) into a directory that doesn't otherwise exist, while the entity directory the same script created is `backlogs/` (plural). Required a manual move + filter edit. The plugin's `entities/backlog/base.yaml` template also has `file.inFolder("planning/backlog")` (singular) baked into its filter, which is inconsistent with the plural directory the structure step creates. Upstream plugin bug worth a follow-up. +- Spec said "drop `#[allow(dead_code)]` on `Vault.id`" but didn't address what happens to `Vault.name` once it's no longer read by production code. Had to make a judgment call (kept the field, moved the attribute) — a one-line note in the spec about the symmetric handling would have removed the decision. +- Project's `just setup-worktree` recipe doesn't exist (skill assumes a JS-flavored repo with pnpm + lefthook). For pure-Rust projects this step is a no-op. The skill could detect repo language and skip the init block when it doesn't apply, or split into a "JS init" sub-step that runs conditionally. +- The base session started with a pre-existing `M CHANGELOG.md` on main from an unrelated edit. The worktree branched from the committed state and was unaffected, but the leftover modification is still sitting in the main working tree and will need separate attention. From aa5e1d006ae9b825884712cd5bac2bc62b9c8e5d Mon Sep 17 00:00:00 2001 From: sksizer Date: Tue, 19 May 2026 23:59:02 -0500 Subject: [PATCH 6/6] docs(tasks): link spawned follow-up tasks in post-mortem --- .../tasks/2026-05-19-obsidian-vault-id-uri.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md index 06d2d1c..0e4435d 100644 --- a/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md +++ b/docs/planning/tasks/2026-05-19-obsidian-vault-id-uri.md @@ -195,3 +195,15 @@ _Captured by /sdlc:task-work on 2026-05-20. PR: pending._ - Spec said "drop `#[allow(dead_code)]` on `Vault.id`" but didn't address what happens to `Vault.name` once it's no longer read by production code. Had to make a judgment call (kept the field, moved the attribute) — a one-line note in the spec about the symmetric handling would have removed the decision. - Project's `just setup-worktree` recipe doesn't exist (skill assumes a JS-flavored repo with pnpm + lefthook). For pure-Rust projects this step is a no-op. The skill could detect repo language and skip the init block when it doesn't apply, or split into a "JS init" sub-step that runs conditionally. - The base session started with a pre-existing `M CHANGELOG.md` on main from an unrelated edit. The worktree branched from the committed state and was unaffected, but the leftover modification is still sitting in the main working tree and will need separate attention. + +### Spawned follow-up tasks + +Plugin-side gaps were filed as draft tasks in `sksizer/dev` (the plugin repo), not in this project. Tracked via PR https://github.com/sksizer/dev/pull/42: + +- `2026-05-20-setup-obsidian-backlog-dir-mismatch` — fix `/sdlc:setup --obsidian` writing into singular `backlog/` dir. +- `2026-05-20-task-work-rebase-frontmatter-conflict` — eliminate Step 5b's deterministic rebase conflict. +- `2026-05-20-task-work-worktree-init-language-agnostic` — make Step 4's init block conditional on project type so it no-ops on pure-Rust repos. + +Project-internal items not promoted to tasks: +- Spec gap re: `Vault.name` once `id` becomes load-bearing — handled inline by keeping `name` with `#[allow(dead_code)]` (it's still used by test fixtures and remains useful for diagnostics). +- Leftover `M CHANGELOG.md` on main from a prior session — unrelated to this work, will need a separate cleanup pass.