diff --git a/.claude/skills/dld-snapshot/SKILL.md b/.claude/skills/dld-snapshot/SKILL.md index 4db2c59..ecdc93c 100644 --- a/.claude/skills/dld-snapshot/SKILL.md +++ b/.claude/skills/dld-snapshot/SKILL.md @@ -23,6 +23,7 @@ Shared scripts (used indirectly via skill scripts): Skill-specific scripts: ``` .claude/skills/dld-snapshot/scripts/collect-active-decisions.sh +.claude/skills/dld-snapshot/scripts/collect-proposed-decisions.sh .claude/skills/dld-snapshot/scripts/update-snapshot-state.sh ``` @@ -30,9 +31,9 @@ Skill-specific scripts: Check that `dld.config.yaml` exists at the repo root. If not, tell the user to run `/dld-init` first and stop. -There must be at least one `accepted` decision. If all decisions are `proposed`, tell the user there's nothing to snapshot yet and suggest `/dld-implement`. +There must be at least one `accepted` or `proposed` decision. If all decisions are `superseded` or `deprecated`, tell the user there's nothing to snapshot yet and suggest `/dld-decide`. -## Step 1: Collect active decisions +## Step 1: Collect decisions ```bash bash .claude/skills/dld-snapshot/scripts/collect-active-decisions.sh @@ -40,6 +41,14 @@ bash .claude/skills/dld-snapshot/scripts/collect-active-decisions.sh This outputs the full content of all `accepted` decisions, separated by `===DLD_DECISION_BOUNDARY===` markers. Parse the output to extract each decision's frontmatter and body. Use the project mode from `dld.config.yaml` (already checked in prerequisites) to determine the organization strategy. +Also collect proposed decisions: + +```bash +bash .claude/skills/dld-snapshot/scripts/collect-proposed-decisions.sh +``` + +This outputs the full content of all `proposed` decisions, separated by the same boundary markers. Keep proposed decisions separate from accepted decisions — they are used in later steps to generate clearly marked sections. + ## Step 2: Generate SNAPSHOT.md Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. @@ -53,6 +62,7 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. > Use `/dld-snapshot` to regenerate. Source of truth: individual decision records. > > **Active decisions:** N (DL-001 through DL-XXX, excluding superseded/deprecated) +> **Proposed decisions:** M --- @@ -71,6 +81,23 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. ### DL-NNN: ... + +--- + +## Proposed Decisions + +> These decisions are proposed but not yet accepted. They represent planned +> direction, not current state. Use `/dld-implement` to accept and implement them. + +### <Namespace or Tag Group> (proposed) + +#### DL-NNN: <Title> + +<The Decision section from the record.> + +**Rationale:** <1-3 sentence summary.> + +--- ``` ### Organization rules @@ -81,8 +108,9 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. ### Content rules -- Only include `accepted` decisions -- Skip `superseded`, `deprecated`, and `proposed` decisions entirely +**Accepted decisions (main body):** +- Include all `accepted` decisions in the main sections +- Skip `superseded` and `deprecated` decisions entirely - When a decision supersedes another (check the `supersedes` field in the YAML frontmatter), include a `*Supersedes: DL-XXX*` note - The **Decision** section content should be copied directly or lightly condensed — this is the authoritative statement - The **Rationale** should be condensed to 1-3 sentences — enough to understand *why*, not every detail @@ -90,6 +118,13 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. - If a decision has no Rationale section, omit the Rationale line - If a decision has no references, omit the Code line +**Proposed decisions (bottom section):** +- Include all `proposed` decisions in a separate "Proposed Decisions" section at the bottom +- Use the same organization rules (by namespace or tag) within the proposed section +- Use H3 (`###`) for group headings and H4 (`####`) for individual decisions within the proposed section, to visually distinguish from accepted decisions +- Omit the Code line — proposed decisions typically don't have code references yet +- If there are no proposed decisions, omit the "Proposed Decisions" section entirely + ## Step 3: Generate OVERVIEW.md Write `decisions/OVERVIEW.md` — the high-level narrative synthesis. @@ -149,6 +184,17 @@ graph LR <Bullet-point summary of the most impactful cross-cutting decisions that don't belong to a single area. E.g., "PostgreSQL for primary data store (DL-001)", "All API input validated with Zod schemas (DL-008)".> + +## Planned Direction + +> The following summarizes proposed decisions that have not yet been accepted. +> This section describes intended future direction, not current system state. + +<Narrative prose summarizing proposed decisions. Use future tense ("will", "plans to") +rather than present tense. Group by namespace or domain theme, same as the main sections. +Reference decision IDs parenthetically, e.g., (DL-015, proposed). +Keep this section proportional — a few proposed decisions need only a short paragraph. +If there are no proposed decisions, omit this section entirely.> ``` ### Narrative guidelines @@ -164,6 +210,7 @@ belong to a single area. E.g., "PostgreSQL for primary data store (DL-001)", - Don't force diagrams if the decisions are simple or unrelated - **Keep it proportional.** A project with 5 decisions needs a short overview. A project with 50 needs more structure. Scale the document to the content. - **Group by namespace (namespaced projects) or by domain theme (flat projects).** For flat projects, identify natural domain groupings from tags and decision content. +- **Separate proposed from accepted.** Proposed decisions go in a "Planned Direction" section at the bottom, written in future tense. Never mix proposed decisions into the main narrative — the main sections describe what the system *does*, the planned section describes what it *will do*. If there are no proposed decisions, omit the section entirely. ### Diagram guidelines @@ -225,11 +272,12 @@ bash .claude/skills/dld-snapshot/scripts/update-snapshot-state.sh ## Step 6: Suggest next steps > Snapshot generated: -> - `decisions/SNAPSHOT.md` — detailed reference (N active decisions) +> - `decisions/SNAPSHOT.md` — detailed reference (N active decisions, M proposed) > - `decisions/OVERVIEW.md` — narrative synthesis with diagrams > - `decisions/<ARTIFACT>.md` — <one line per custom artifact generated, if any> > > Next steps: > - Review the generated documents for accuracy +> - `/dld-implement` — accept and implement proposed decisions > - `/dld-decide` — record new decisions > - `/dld-audit` — check for drift between decisions and code diff --git a/.claude/skills/dld-snapshot/scripts/collect-proposed-decisions.sh b/.claude/skills/dld-snapshot/scripts/collect-proposed-decisions.sh new file mode 100755 index 0000000..9958c62 --- /dev/null +++ b/.claude/skills/dld-snapshot/scripts/collect-proposed-decisions.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Collect all proposed decisions and output their content. +# Output: each decision's full file content, separated by a marker line. +# The marker format is: ===DLD_DECISION_BOUNDARY=== +# This allows the agent to split and process decisions individually. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../dld-common/scripts/common.sh" + +RECORDS_DIR="$(get_records_dir)" + +if [[ ! -d "$RECORDS_DIR" ]]; then + exit 0 +fi + +# Find all decision files, sorted by numeric ID ascending +FILES=$(find "$RECORDS_DIR" -name 'DL-*.md' -type f \ + | awk -F/ '{file=$0; basename=$NF; gsub(/^DL-/,"",basename); gsub(/\.md$/,"",basename); print basename "\t" file}' \ + | sort -n \ + | cut -f2) + +if [[ -z "$FILES" ]]; then + exit 0 +fi + +FIRST=true +while IFS= read -r file; do + STATUS=$(sed -n '/^---$/,/^---$/p' "$file" \ + | grep "^status:" \ + | head -1 \ + | sed 's/^status:[[:space:]]*//') + + if [[ "$STATUS" == "proposed" ]]; then + if $FIRST; then + FIRST=false + else + echo "===DLD_DECISION_BOUNDARY===" + fi + cat "$file" + fi +done <<< "$FILES" diff --git a/skills/dld-snapshot/SKILL.md b/skills/dld-snapshot/SKILL.md index 398d81e..3c3fa57 100644 --- a/skills/dld-snapshot/SKILL.md +++ b/skills/dld-snapshot/SKILL.md @@ -23,6 +23,7 @@ Shared scripts (used indirectly via skill scripts): Skill-specific scripts: ``` scripts/collect-active-decisions.sh +scripts/collect-proposed-decisions.sh scripts/update-snapshot-state.sh ``` @@ -30,9 +31,9 @@ scripts/update-snapshot-state.sh Check that `dld.config.yaml` exists at the repo root. If not, tell the user to run `/dld-init` first and stop. -There must be at least one `accepted` decision. If all decisions are `proposed`, tell the user there's nothing to snapshot yet and suggest `/dld-implement`. +There must be at least one `accepted` or `proposed` decision. If all decisions are `superseded` or `deprecated`, tell the user there's nothing to snapshot yet and suggest `/dld-decide`. -## Step 1: Collect active decisions +## Step 1: Collect decisions ```bash bash scripts/collect-active-decisions.sh @@ -40,6 +41,14 @@ bash scripts/collect-active-decisions.sh This outputs the full content of all `accepted` decisions, separated by `===DLD_DECISION_BOUNDARY===` markers. Parse the output to extract each decision's frontmatter and body. Use the project mode from `dld.config.yaml` (already checked in prerequisites) to determine the organization strategy. +Also collect proposed decisions: + +```bash +bash scripts/collect-proposed-decisions.sh +``` + +This outputs the full content of all `proposed` decisions, separated by the same boundary markers. Keep proposed decisions separate from accepted decisions — they are used in later steps to generate clearly marked sections. + ## Step 2: Generate SNAPSHOT.md Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. @@ -53,6 +62,7 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. > Use `/dld-snapshot` to regenerate. Source of truth: individual decision records. > > **Active decisions:** N (DL-001 through DL-XXX, excluding superseded/deprecated) +> **Proposed decisions:** M --- @@ -71,6 +81,23 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. ### DL-NNN: <Title> ... + +--- + +## Proposed Decisions + +> These decisions are proposed but not yet accepted. They represent planned +> direction, not current state. Use `/dld-implement` to accept and implement them. + +### <Namespace or Tag Group> (proposed) + +#### DL-NNN: <Title> + +<The Decision section from the record.> + +**Rationale:** <1-3 sentence summary.> + +--- ``` ### Organization rules @@ -81,8 +108,9 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. ### Content rules -- Only include `accepted` decisions -- Skip `superseded`, `deprecated`, and `proposed` decisions entirely +**Accepted decisions (main body):** +- Include all `accepted` decisions in the main sections +- Skip `superseded` and `deprecated` decisions entirely - When a decision supersedes another (check the `supersedes` field in the YAML frontmatter), include a `*Supersedes: DL-XXX*` note - The **Decision** section content should be copied directly or lightly condensed — this is the authoritative statement - The **Rationale** should be condensed to 1-3 sentences — enough to understand *why*, not every detail @@ -90,6 +118,13 @@ Write `decisions/SNAPSHOT.md` — the detailed per-decision reference. - If a decision has no Rationale section, omit the Rationale line - If a decision has no references, omit the Code line +**Proposed decisions (bottom section):** +- Include all `proposed` decisions in a separate "Proposed Decisions" section at the bottom +- Use the same organization rules (by namespace or tag) within the proposed section +- Use H3 (`###`) for group headings and H4 (`####`) for individual decisions within the proposed section, to visually distinguish from accepted decisions +- Omit the Code line — proposed decisions typically don't have code references yet +- If there are no proposed decisions, omit the "Proposed Decisions" section entirely + ## Step 3: Generate OVERVIEW.md Write `decisions/OVERVIEW.md` — the high-level narrative synthesis. @@ -149,6 +184,17 @@ graph LR <Bullet-point summary of the most impactful cross-cutting decisions that don't belong to a single area. E.g., "PostgreSQL for primary data store (DL-001)", "All API input validated with Zod schemas (DL-008)".> + +## Planned Direction + +> The following summarizes proposed decisions that have not yet been accepted. +> This section describes intended future direction, not current system state. + +<Narrative prose summarizing proposed decisions. Use future tense ("will", "plans to") +rather than present tense. Group by namespace or domain theme, same as the main sections. +Reference decision IDs parenthetically, e.g., (DL-015, proposed). +Keep this section proportional — a few proposed decisions need only a short paragraph. +If there are no proposed decisions, omit this section entirely.> ``` ### Narrative guidelines @@ -164,6 +210,7 @@ belong to a single area. E.g., "PostgreSQL for primary data store (DL-001)", - Don't force diagrams if the decisions are simple or unrelated - **Keep it proportional.** A project with 5 decisions needs a short overview. A project with 50 needs more structure. Scale the document to the content. - **Group by namespace (namespaced projects) or by domain theme (flat projects).** For flat projects, identify natural domain groupings from tags and decision content. +- **Separate proposed from accepted.** Proposed decisions go in a "Planned Direction" section at the bottom, written in future tense. Never mix proposed decisions into the main narrative — the main sections describe what the system *does*, the planned section describes what it *will do*. If there are no proposed decisions, omit the section entirely. ### Diagram guidelines @@ -225,11 +272,12 @@ bash scripts/update-snapshot-state.sh ## Step 6: Suggest next steps > Snapshot generated: -> - `decisions/SNAPSHOT.md` — detailed reference (N active decisions) +> - `decisions/SNAPSHOT.md` — detailed reference (N active decisions, M proposed) > - `decisions/OVERVIEW.md` — narrative synthesis with diagrams > - `decisions/<ARTIFACT>.md` — <one line per custom artifact generated, if any> > > Next steps: > - Review the generated documents for accuracy +> - `/dld-implement` — accept and implement proposed decisions > - `/dld-decide` — record new decisions > - `/dld-audit` — check for drift between decisions and code diff --git a/skills/dld-snapshot/scripts/collect-proposed-decisions.sh b/skills/dld-snapshot/scripts/collect-proposed-decisions.sh new file mode 100755 index 0000000..9958c62 --- /dev/null +++ b/skills/dld-snapshot/scripts/collect-proposed-decisions.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Collect all proposed decisions and output their content. +# Output: each decision's full file content, separated by a marker line. +# The marker format is: ===DLD_DECISION_BOUNDARY=== +# This allows the agent to split and process decisions individually. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../../dld-common/scripts/common.sh" + +RECORDS_DIR="$(get_records_dir)" + +if [[ ! -d "$RECORDS_DIR" ]]; then + exit 0 +fi + +# Find all decision files, sorted by numeric ID ascending +FILES=$(find "$RECORDS_DIR" -name 'DL-*.md' -type f \ + | awk -F/ '{file=$0; basename=$NF; gsub(/^DL-/,"",basename); gsub(/\.md$/,"",basename); print basename "\t" file}' \ + | sort -n \ + | cut -f2) + +if [[ -z "$FILES" ]]; then + exit 0 +fi + +FIRST=true +while IFS= read -r file; do + STATUS=$(sed -n '/^---$/,/^---$/p' "$file" \ + | grep "^status:" \ + | head -1 \ + | sed 's/^status:[[:space:]]*//') + + if [[ "$STATUS" == "proposed" ]]; then + if $FIRST; then + FIRST=false + else + echo "===DLD_DECISION_BOUNDARY===" + fi + cat "$file" + fi +done <<< "$FILES" diff --git a/tests/test_collect_proposed_decisions.bats b/tests/test_collect_proposed_decisions.bats new file mode 100644 index 0000000..7642ce9 --- /dev/null +++ b/tests/test_collect_proposed_decisions.bats @@ -0,0 +1,93 @@ +#!/usr/bin/env bats +# Tests for dld-snapshot/scripts/collect-proposed-decisions.sh + +load 'test_helper/common' + +SCRIPT="" + +setup() { + setup_flat_project + SCRIPT="$SKILLS_DIR/dld-snapshot/scripts/collect-proposed-decisions.sh" +} + +teardown() { + teardown_project +} + +@test "collect-proposed-decisions outputs nothing with no decisions" { + run bash "$SCRIPT" + assert_success + assert_output "" +} + +@test "collect-proposed-decisions returns only proposed decisions" { + create_decision "DL-001" "accepted" + create_decision "DL-002" "proposed" + create_decision "DL-003" "superseded" + create_decision "DL-004" "proposed" + + run bash "$SCRIPT" + assert_success + assert_output --partial "id: DL-002" + assert_output --partial "id: DL-004" + refute_output --partial "id: DL-001" + refute_output --partial "id: DL-003" +} + +@test "collect-proposed-decisions separates with boundary markers" { + create_decision "DL-001" "proposed" + create_decision "DL-002" "proposed" + + run bash "$SCRIPT" + assert_success + assert_output --partial "===DLD_DECISION_BOUNDARY===" +} + +@test "collect-proposed-decisions has no boundary before first decision" { + create_decision "DL-001" "proposed" + create_decision "DL-002" "proposed" + + output="$(bash "$SCRIPT")" + first_line="$(echo "$output" | head -1)" + assert_equal "$first_line" "---" +} + +@test "collect-proposed-decisions outputs in ascending ID order" { + create_decision "DL-003" "proposed" + create_decision "DL-001" "proposed" + + output="$(bash "$SCRIPT")" + pos_001="$(echo "$output" | grep -n "id: DL-001" | head -1 | cut -d: -f1)" + pos_003="$(echo "$output" | grep -n "id: DL-003" | head -1 | cut -d: -f1)" + [[ "$pos_001" -lt "$pos_003" ]] +} + +@test "collect-proposed-decisions exits cleanly when records dir missing" { + rmdir decisions/records + run bash "$SCRIPT" + assert_success + assert_output "" +} + +@test "collect-proposed-decisions works with namespaced decisions" { + setup_namespaced_project + create_decision "DL-001" "proposed" "billing" + create_decision "DL-002" "accepted" "auth" + create_decision "DL-003" "proposed" "auth" + + run bash "$SCRIPT" + assert_success + assert_output --partial "id: DL-001" + assert_output --partial "id: DL-003" + refute_output --partial "id: DL-002" +} + +@test "collect-proposed-decisions includes full file content" { + create_decision "DL-001" "proposed" + + run bash "$SCRIPT" + assert_success + assert_output --partial "## Context" + assert_output --partial "## Decision" + assert_output --partial "Test decision content for DL-001" +}