diff --git a/specs/SDD-004-session-start-config/proposal.md b/specs/SDD-004-session-start-config/proposal.md new file mode 100644 index 0000000..381cf2e --- /dev/null +++ b/specs/SDD-004-session-start-config/proposal.md @@ -0,0 +1,70 @@ +--- +id: "SDD-004-session-start-config" +type: spec +status: draft +created: "2026-05-21" +tags: [spec, proposal, sdd-004, cross-os, session-start, ssot, audit-002-followup] +template_version: "1.0" +--- + +# SDD-004: Session start config + +> **Naming**: file lives at `/specs/SDD-004-session-start-config/proposal.md`. + +## Why + + + +[AGENT-DRAFT — review before archive] + +`claude-session-start.{sh,ps1}` is 937 LOC combined (497 sh + 440 ps1) and is the highest-change-frequency cross-OS pair in the repo — it gains a new injector almost every sprint (BUG-003, SDD-001, SDD-021, the claude-mem heal cascade, etc.). Every change today is a two-place edit, so probe-order or injection-layout drift between Linux and Windows is invisible until a real Claude session prints a malformed SessionStart bundle on the wrong OS. If we don't ship this, the drift surface keeps growing and every new injector is a coin-flip on whether Windows parity will be remembered. AUDIT-002 ranks this pair as the #1 SSOT-extractable candidate; `doctor.{sh,ps1}` + `env-contract.json` is the reference pattern already proven in-repo. + +## What + +[AGENT-DRAFT — review before archive] + +A new `session-start-config.json` at repo root becomes the SSOT for: (1) the ordered list of injectors that run on SessionStart, (2) each injector's probe command + content source, and (3) on-failure behavior (skip vs warn). `claude-session-start.{sh,ps1}` are refactored into thin readers (~787 LOC combined, -150 LOC target) that parse the JSON via `jq` (Linux) / `ConvertFrom-Json` (Windows) and dispatch. Adding a new injector becomes a one-place JSON edit instead of a two-file shell edit, and `git diff` on the config will be the canonical record of what changed for any future SessionStart enhancement. + +## Out of scope + +[AGENT-DRAFT — review before archive] + +- Refactoring other AUDIT-002 SSOT-extractable candidates (`knowledge-crystallize.{sh,ps1}`, `dotfiles-sync.{sh,ps1}`) — deferred per AUDIT-002 §"SSOT-isation priority list" (rank 2-3). +- Touching the `load-secrets.{sh,ps1}` 0.24-ratio anomaly — separate BUG-006 cross-OS-completeness audit, not an SSOT-isation target. +- Changing the SessionStart hook output contract itself. Injection layout, ordering, and content MUST stay byte-identical to pre-refactor for all existing injectors; only the *source of truth for what gets injected* moves. + +## Risks / open questions + +[AGENT-DRAFT — review before archive] + +- **R1 (BLOCKER — must resolve before code):** Byte-equivalence regressions. SessionStart fires on EVERY Claude session; any output difference breaks all subsequent sessions silently. The first task MUST be capturing golden output (one per OS) BEFORE any refactor, and the implementation MUST assert byte-identity post-refactor. This is a non-negotiable invariant. +- **R2:** PowerShell ASCII-only rule (pattern-powershell-ascii-only — hit twice already: Mar 2026 + PR #36 May 2026). The JSON file itself MUST be ASCII-only; any em-dash / smart-quote sneaking into a probe message or comment via JSON triggers PSScriptAnalyzer non-ASCII fail when the ps1 reads it. +- **R3 (open question — resolve before tasks.md freeze):** Schema shape. Two candidates: (a) flat list of injector objects `[{id, probe, content_source, on_failure}, ...]`, or (b) nested by category (`{vault: {...}, claude_mem: {...}, sdd_reminder: {...}}`). AUDIT-002 explicitly warns "keep schemas minimal" (single `jq` / `ConvertFrom-Json` call). Recommendation: flat list (a) for parser simplicity, but needs explicit decision. + +## Acceptance criteria + +[AGENT-DRAFT — review before archive] + +- [ ] `session-start-config.json` exists at repo root, is valid JSON, and is schema-locked by a bats test that parses it via `jq` and asserts every injector entry has the agreed fields. +- [ ] `claude-session-start.sh` and `claude-session-start.ps1` both read the config and emit SessionStart output that is **byte-identical** to a pre-refactor golden file (one golden captured per OS before any code change). +- [ ] Total LOC of `claude-session-start.{sh,ps1}` drops ≥100 LOC vs pre-refactor (audit target: ~150 LOC saved). Counted via `wc -l` on the two files combined. +- [ ] Adding a fixture probe to `session-start-config.json` (test-only entry) produces the expected new injection on both OSes without touching either `.sh` or `.ps1` source — the "new injector = JSON-edit only" promise is locked by a bats test. + +## Completeness review + +[AGENT-DRAFT — review before archive] + +Standard items considered: +- Rate limit / cost guard — N/A (local hook, no external API). +- Idempotency — already guaranteed; probes are read-only. +- Regression test — covered by R1 byte-equivalence assertion + 4th acceptance criterion. + +Adding (not in template, but load-bearing here): +- **Migration plan:** for the existing 7 injectors documented in AUDIT-002, each one needs a 1-line entry in the JSON + a corresponding golden assertion. PR cannot land until all 7 are migrated. +- **Rollback plan:** config + both readers ship in the same PR. `git revert` on the single merge commit atomically restores pre-refactor `.sh`/`.ps1` AND removes the JSON. No partial-state failure. + +## References + +- Vault: `10_projects/dotfiles/11-tasks.md` (backlog entry #7, P1) +- Related ADR: `10_projects/dotfiles/30-architecture/audit-002-cross-os-duplication.md` (surfaces SDD-004 as top candidate) +- Related pattern: `10_projects/dotfiles/30-architecture/audit-002-cross-os-duplication.md` reference `doctor.{sh,ps1}` + `env-contract.json` diff --git a/specs/SDD-004-session-start-config/tasks.md b/specs/SDD-004-session-start-config/tasks.md new file mode 100644 index 0000000..a45e597 --- /dev/null +++ b/specs/SDD-004-session-start-config/tasks.md @@ -0,0 +1,55 @@ +--- +tags: [spec, tasks, sdd-004] +created: "2026-05-21" +--- + +# Tasks - SDD-004-session-start-config + +> TDD order. One task = one focused commit. Tick as you go. Reorder freely while spec is in `draft` state; freeze once you start `implementing`. + +## Setup + +- [ ] Branch created from main: `feat/SDD-004-session-start-config` +- [ ] `proposal.md` is complete and acceptance criteria are testable +- [ ] No open questions left in `proposal.md` "Risks / open questions" + +## Implementation + +> Replace these with the actual steps for this feature. Keep them small (one commit each) and in TDD order. + +- [ ] Write failing test for +- [ ] Implement to make it pass +- [ ] Refactor for clarity (extract, rename, dedupe) +- [ ] Write failing test for +- [ ] Implement to make it pass +- [ ] ... + +## Closing + +- [ ] Every acceptance criterion from `proposal.md` is covered by at least one test +- [ ] Every acceptance criterion has a matching entry in `features.json` (see below) with a non-vacuous verification command +- [ ] Type checks pass +- [ ] Lint passes +- [ ] No unrelated changes in the diff (no scope creep) +- [ ] `verification.md` filled in +- [ ] PR opened referencing this spec folder + +## Machine-readable features + +This spec emits a sibling `features.json` (alongside this file) following [[pattern-feature-list-as-primitive]]. The JSON is the harness-facing contract: each acceptance criterion maps to ≥1 feature with `id`, `behavior`, `verification` (executable command), `state` (lifecycle), and `evidence` (harness-captured output). + +**Pass-state gating:** the agent CANNOT write `"state": "passing"` — only the harness, after running `verification` and capturing exit code 0, may set that terminal state. Reviewers must reject PRs where features.json contains `passing` entries with empty `evidence`. + +Minimal `features.json` skeleton (drop into `/specs/SDD-004-session-start-config/features.json`): + +```json +[ + { + "id": "SDD-004-session-start-config-f1", + "behavior": "", + "verification": "", + "state": "pending", + "evidence": "" + } +] +``` diff --git a/specs/SDD-004-session-start-config/verification.md b/specs/SDD-004-session-start-config/verification.md new file mode 100644 index 0000000..32bd2d7 --- /dev/null +++ b/specs/SDD-004-session-start-config/verification.md @@ -0,0 +1,42 @@ +--- +tags: [spec, verification, sdd-004] +created: "2026-05-21" +--- + +# Verification - SDD-004-session-start-config + +## Evidence + +Map every acceptance criterion from `proposal.md` to concrete proof (commit hash, test name, or observed behavior). + +- [ ] Criterion 1 -> commit `` / test `` +- [ ] Criterion 2 -> commit `` / test `` +- [ ] Criterion 3 -> commit `` / test `` + +## Test status + +- Test suite: ` -> ` +- Manual smoke test: what was exercised, what was observed +- No regressions in existing test suite: yes / no (if no, document) + +## Decisions made during implementation + +Brief log of non-obvious trade-offs or course corrections taken during the work. Routine choices belong in commit messages, not here. + +- +- + +## Promotion candidates + +Before archiving, flag what (if anything) should be promoted to the vault. If all three are "no", archive in repo is the only persistence. + +- [ ] Lesson for `10_projects/dotfiles/90-lessons.md`? +- [ ] ADR-worthy decision for `10_projects/dotfiles/30-architecture/adr-XXX.md`? +- [ ] New pattern candidate for `00_meta/patterns/`? Only if this recurs in >1 project. + +## Archive checklist + +- [ ] `proposal.md` frontmatter set to `status: archived` +- [ ] Folder moved: `specs/SDD-004-session-start-config/` -> `specs/archive/SDD-004-session-start-config/` +- [ ] Backlog entry in vault `11-tasks.md` ticked with PR link +- [ ] Promotions above executed (if any)