Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions specs/SDD-004-session-start-config/proposal.md
Original file line number Diff line number Diff line change
@@ -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 `<repo>/specs/SDD-004-session-start-config/proposal.md`.

## Why

<!-- from 10_projects/dotfiles/11-tasks.md: Extract session-start-config.json for claude-session-start.{sh,ps1} (highest-frequency cross-OS pair, 937 LOC combined). -->

[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`
55 changes: 55 additions & 0 deletions specs/SDD-004-session-start-config/tasks.md
Original file line number Diff line number Diff line change
@@ -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 <behavior 1>
- [ ] Implement <module/function> to make it pass
- [ ] Refactor for clarity (extract, rename, dedupe)
- [ ] Write failing test for <behavior 2>
- [ ] 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 `<repo>/specs/SDD-004-session-start-config/features.json`):

```json
[
{
"id": "SDD-004-session-start-config-f1",
"behavior": "<one-line copy of an acceptance criterion>",
"verification": "<single shell command; exit 0 means pass>",
"state": "pending",
"evidence": ""
}
]
```
42 changes: 42 additions & 0 deletions specs/SDD-004-session-start-config/verification.md
Original file line number Diff line number Diff line change
@@ -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 `<hash>` / test `<name>`
- [ ] Criterion 2 -> commit `<hash>` / test `<name>`
- [ ] Criterion 3 -> commit `<hash>` / test `<name>`

## Test status

- Test suite: `<command> -> <output / coverage %>`
- 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`? <yes / no - one line of what>
- [ ] ADR-worthy decision for `10_projects/dotfiles/30-architecture/adr-XXX.md`? <yes / no - one line of what>
- [ ] New pattern candidate for `00_meta/patterns/`? Only if this recurs in >1 project. <yes / no - one line>

## 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)
Loading