@windyroad/retrospective 0.23.1-preview.583 → 0.23.1-preview.587

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,18 @@
52
52
  },
53
53
  "schema_version": "2.0"
54
54
  },
55
+ "migrate-briefing": {
56
+ "band": "Experimental",
57
+ "bootstrapping": true,
58
+ "computed_at": "2026-06-06T00:00:00Z",
59
+ "evidence": {
60
+ "breaking_change_age_days": null,
61
+ "closed_tickets_window": 0,
62
+ "days_shipped": 0,
63
+ "invocations_30d": 0
64
+ },
65
+ "schema_version": "2.0"
66
+ },
55
67
  "run-retro": {
56
68
  "band": "Alpha",
57
69
  "computed_at": "2026-05-18T11:42:50Z",
package/README.md CHANGED
@@ -50,6 +50,7 @@ The plugin also triggers a reminder via a `Stop` hook when a session ends natura
50
50
  | ------- | --------- | --- |
51
51
  | `/wr-retrospective:run-retro` | Run a session retrospective; emits the briefing update, advisory detector outputs, and the Pipeline Instability section per Step 2b | Alpha |
52
52
  | `/wr-retrospective:analyze-context` | Deep on-demand context-usage analyser per ADR-043; produces per-turn attribution and trim suggestions | Experimental |
53
+ | `/wr-retrospective:migrate-briefing` | One-time idempotent migration of a legacy single-file `docs/BRIEFING.md` into the per-topic `docs/briefing/` tree the session-start hook + Tier-3 rotation expect (ADR-040) | Experimental |
53
54
 
54
55
  ## Advisory scripts
55
56
 
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # Generated by scripts/sync-shim-wrappers.sh from
3
+ # packages/shared/lib/shim-wrapper-template.sh. DO NOT EDIT individual
4
+ # shim files in packages/*/bin/wr-* directly; edit the template + run
5
+ # `npm run sync:shim-wrappers` to regenerate.
6
+ #
7
+ # Resolution (ADR-080):
8
+ # 1. If the wrapper's parent dir is semver-shaped, treat as installed-
9
+ # cache execution and resolve to the highest-version sibling's
10
+ # scripts/ entry below.
11
+ # 2. Otherwise (parent dir is e.g. `architect`), treat as source-
12
+ # monorepo execution and dispatch to own scripts/. The source-repo-
13
+ # guard `exec` is the anchor parsed by
14
+ # packages/retrospective/scripts/check-tarball-shipped-shims.sh.
15
+ # 3. If the cache parent contains zero semver-shaped siblings, exit
16
+ # 127 with a stderr message naming the cache parent (per SQ-080-2).
17
+ #
18
+ # @adr ADR-080 (highest-version-wins shim wrapper plugin scaffold)
19
+ # @adr ADR-049 (plugin-bundled scripts resolve via bin/ on $PATH — amended)
20
+ # @problem P343 (mid-session staleness window)
21
+
22
+ set -euo pipefail
23
+
24
+ SHIM_DIR="$(cd "$(dirname "$0")" && pwd)"
25
+ OWN_VERSION_DIR="$(dirname "$SHIM_DIR")"
26
+ OWN_VERSION_NAME="$(basename "$OWN_VERSION_DIR")"
27
+ CACHE_PARENT="$(dirname "$OWN_VERSION_DIR")"
28
+
29
+ SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
30
+
31
+ # Source-repo guard: own parent dir is NOT semver → dispatch to own scripts/.
32
+ if ! [[ "$OWN_VERSION_NAME" =~ $SEMVER_RE ]]; then
33
+ exec "$SHIM_DIR/../scripts/migrate-briefing.sh" "$@"
34
+ fi
35
+
36
+ # Cache execution: pick the highest-semver sibling under CACHE_PARENT.
37
+ HIGHEST=""
38
+ while IFS= read -r dir; do
39
+ name="$(basename "$dir")"
40
+ [[ "$name" =~ $SEMVER_RE ]] || continue
41
+ if [[ -z "$HIGHEST" ]] || [[ "$(printf '%s\n%s\n' "$HIGHEST" "$name" | sort -V | tail -1)" == "$name" ]]; then
42
+ HIGHEST="$name"
43
+ fi
44
+ done < <(find "$CACHE_PARENT" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
45
+
46
+ if [[ -z "$HIGHEST" ]]; then
47
+ printf 'wr-shim: no cached versions in %s\n' "$CACHE_PARENT" >&2
48
+ exit 127
49
+ fi
50
+
51
+ exec "$CACHE_PARENT/$HIGHEST/scripts/migrate-briefing.sh" "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windyroad/retrospective",
3
- "version": "0.23.1-preview.583",
3
+ "version": "0.23.1-preview.587",
4
4
  "description": "Session retrospectives that update briefings and create problem tickets",
5
5
  "bin": {
6
6
  "windyroad-retrospective": "./bin/install.mjs"
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env bash
2
+ # Migrate a legacy single-file docs/BRIEFING.md to the per-topic
3
+ # docs/briefing/ tree expected by the wr-retrospective Tier-3 rotation
4
+ # contract (ADR-040). Idempotent: silently no-ops when the tree already
5
+ # exists or when no legacy file is present.
6
+ #
7
+ # Closes P204.
8
+ #
9
+ # Usage:
10
+ # wr-retrospective-migrate-briefing [--dry-run] [--force]
11
+ #
12
+ # @adr ADR-040 (session-start briefing surface — target tree shape)
13
+ # @adr ADR-014 (governance skills commit their own work — invoked by SKILL.md)
14
+ # @adr ADR-038 (progressive disclosure — SKILL.md + REFERENCE.md split)
15
+ # @adr ADR-049 (plugin-bundled scripts on $PATH)
16
+ # @problem P204 (no migrate-briefing skill — legacy → tree migration manual)
17
+
18
+ set -euo pipefail
19
+
20
+ DRY_RUN=0
21
+ FORCE=0
22
+
23
+ while [ $# -gt 0 ]; do
24
+ case "$1" in
25
+ --dry-run) DRY_RUN=1; shift ;;
26
+ --force) FORCE=1; shift ;;
27
+ -h|--help)
28
+ cat <<EOF
29
+ Usage: wr-retrospective-migrate-briefing [--dry-run] [--force]
30
+
31
+ Migrate legacy docs/BRIEFING.md to docs/briefing/<topic>.md tree.
32
+
33
+ Flags:
34
+ --dry-run Print the planned topic slugs and file paths; no writes.
35
+ --force Re-run even when docs/briefing/README.md already exists.
36
+ EOF
37
+ exit 0 ;;
38
+ *)
39
+ printf 'migrate-briefing: unknown flag: %s\n' "$1" >&2
40
+ exit 2 ;;
41
+ esac
42
+ done
43
+
44
+ LEGACY="docs/BRIEFING.md"
45
+ TREE_DIR="docs/briefing"
46
+ TREE_INDEX="$TREE_DIR/README.md"
47
+
48
+ # Idempotency: tree already present.
49
+ if [ -f "$TREE_INDEX" ] && [ "$FORCE" != "1" ]; then
50
+ echo "migrate-briefing: $TREE_INDEX already exists; tree already migrated (no action)."
51
+ exit 0
52
+ fi
53
+
54
+ # Idempotency: no legacy file (or empty stub).
55
+ if [ ! -s "$LEGACY" ]; then
56
+ echo "migrate-briefing: $LEGACY missing or empty; nothing to migrate (no action)."
57
+ exit 0
58
+ fi
59
+
60
+ # Slug derivation: heading text → kebab-case-truncated-to-60.
61
+ derive_slug() {
62
+ printf '%s' "$1" \
63
+ | tr '[:upper:]' '[:lower:]' \
64
+ | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' \
65
+ | cut -c1-60
66
+ }
67
+
68
+ # Stage outputs under a temp dir so a parse failure mid-walk leaves the
69
+ # repo untouched. Atomic-move into place after the walk completes.
70
+ STAGING="$(mktemp -d -t migrate-briefing.XXXXXX)"
71
+ trap 'rm -rf "$STAGING"' EXIT
72
+
73
+ PREAMBLE_FILE="$STAGING/__preamble__"
74
+ INDEX_LINES="$STAGING/__index__"
75
+ : > "$PREAMBLE_FILE"
76
+ : > "$INDEX_LINES"
77
+
78
+ current_slug="__preamble__"
79
+ current_file="$PREAMBLE_FILE"
80
+ in_fence=0
81
+ topic_index=0
82
+
83
+ # Track slug → original-heading so the index row preserves human label.
84
+ declare -A SLUG_HEADING
85
+ declare -A SLUG_TAKEN
86
+
87
+ while IFS= read -r line || [ -n "$line" ]; do
88
+ # Column-anchored fence toggle (``` or ~~~ at column 0).
89
+ if [[ "$line" =~ ^(\`\`\`|~~~) ]]; then
90
+ in_fence=$(( 1 - in_fence ))
91
+ printf '%s\n' "$line" >> "$current_file"
92
+ continue
93
+ fi
94
+
95
+ # Inside a fence: emit verbatim, never promote.
96
+ if [ "$in_fence" -eq 1 ]; then
97
+ printf '%s\n' "$line" >> "$current_file"
98
+ continue
99
+ fi
100
+
101
+ # H2 marker → close current topic, start new one.
102
+ if [[ "$line" =~ ^\#\#[[:space:]]+(.+)$ ]]; then
103
+ heading_text="${BASH_REMATCH[1]}"
104
+ base_slug="$(derive_slug "$heading_text")"
105
+ if [ -z "$base_slug" ]; then
106
+ topic_index=$(( topic_index + 1 ))
107
+ base_slug="topic-$topic_index"
108
+ fi
109
+ # Collision handling.
110
+ candidate="$base_slug"
111
+ n=2
112
+ while [ -n "${SLUG_TAKEN[$candidate]:-}" ]; do
113
+ candidate="${base_slug}-${n}"
114
+ n=$(( n + 1 ))
115
+ done
116
+ SLUG_TAKEN[$candidate]=1
117
+ SLUG_HEADING[$candidate]="$heading_text"
118
+ current_slug="$candidate"
119
+ current_file="$STAGING/${current_slug}.md"
120
+ : > "$current_file"
121
+ printf '%s\n' "$line" >> "$current_file"
122
+ echo "$current_slug" >> "$INDEX_LINES"
123
+ continue
124
+ fi
125
+
126
+ # Plain content → emit to current topic body.
127
+ printf '%s\n' "$line" >> "$current_file"
128
+ done < "$LEGACY"
129
+
130
+ # Build the README index.
131
+ README_OUT="$STAGING/README.md"
132
+ today="$(date +%Y-%m-%d)"
133
+ {
134
+ echo "# Project Briefing"
135
+ echo
136
+ echo "Migrated from legacy \`docs/BRIEFING.md\` via \`/wr-retrospective:migrate-briefing\` on $today."
137
+ echo
138
+ echo "## Critical Points (Session-Start Surface)"
139
+ echo
140
+ echo "_To be populated by the next \`/wr-retrospective:run-retro\` Step 1.5 signal-vs-noise pass (per ADR-040)._"
141
+ echo
142
+ echo "## Topic Index"
143
+ echo
144
+ echo "| File | Source heading |"
145
+ echo "|---|---|"
146
+ while IFS= read -r slug; do
147
+ [ -z "$slug" ] && continue
148
+ heading="${SLUG_HEADING[$slug]:-$slug}"
149
+ echo "| [${slug}.md](./${slug}.md) | ${heading} |"
150
+ done < "$INDEX_LINES"
151
+ if [ -s "$PREAMBLE_FILE" ]; then
152
+ echo
153
+ echo "## Preamble"
154
+ echo
155
+ cat "$PREAMBLE_FILE"
156
+ fi
157
+ } > "$README_OUT"
158
+
159
+ # Dry-run: print the plan, do not write.
160
+ if [ "$DRY_RUN" = "1" ]; then
161
+ echo "migrate-briefing: --dry-run plan:"
162
+ echo " index → $TREE_INDEX"
163
+ while IFS= read -r slug; do
164
+ [ -z "$slug" ] && continue
165
+ echo " topic → $TREE_DIR/${slug}.md (heading: ${SLUG_HEADING[$slug]:-?})"
166
+ done < "$INDEX_LINES"
167
+ echo " rename → $LEGACY → $LEGACY.migrated-$today"
168
+ exit 0
169
+ fi
170
+
171
+ # Atomic move into place.
172
+ mkdir -p "$TREE_DIR"
173
+ cp "$README_OUT" "$TREE_INDEX"
174
+ while IFS= read -r slug; do
175
+ [ -z "$slug" ] && continue
176
+ cp "$STAGING/${slug}.md" "$TREE_DIR/${slug}.md"
177
+ done < "$INDEX_LINES"
178
+
179
+ # Retire the legacy file under a date-stamped suffix so its content is
180
+ # preserved on disk but no longer matches the SessionStart hook's reads.
181
+ if git -C . rev-parse HEAD >/dev/null 2>&1; then
182
+ git mv "$LEGACY" "${LEGACY}.migrated-${today}" 2>/dev/null \
183
+ || mv "$LEGACY" "${LEGACY}.migrated-${today}"
184
+ else
185
+ mv "$LEGACY" "${LEGACY}.migrated-${today}"
186
+ fi
187
+
188
+ # Final report.
189
+ echo "migrate-briefing: migrated $LEGACY → $TREE_DIR/ ($(wc -l < "$INDEX_LINES" | tr -d ' ') topic files + README index)."
190
+ echo "migrate-briefing: legacy file retired as ${LEGACY}.migrated-${today}."
@@ -0,0 +1,135 @@
1
+ # migrate-briefing — Reference
2
+
3
+ Edge cases, algorithm detail, and recovery procedures for `/wr-retrospective:migrate-briefing`. SKILL.md is the canonical contract surface; this file expands the points the SKILL summary defers per ADR-038 progressive disclosure.
4
+
5
+ ## Heading-extraction algorithm
6
+
7
+ The legacy `docs/BRIEFING.md` is walked line-by-line by `bin/migrate-briefing.sh`. The walker maintains two pieces of state:
8
+
9
+ - `in_fence` — boolean, toggled when a ` ``` ` or `~~~` fence opens or closes at column 0. Lines inside a fence are emitted to the **current** topic body without heading-pattern matching.
10
+ - `current_slug` — the active topic slug. Defaults to a special `__preamble__` slug for content before the first H2.
11
+
12
+ For each input line:
13
+
14
+ 1. If the line is a fence delimiter at column 0, flip `in_fence` and emit verbatim.
15
+ 2. Else if `in_fence` is true, emit the line verbatim to the current topic body.
16
+ 3. Else if the line matches `^## ` (H2 marker at column 0), close the previous topic, derive a new slug from the heading text, and start a new topic body. The H2 line itself becomes the first line of the new topic body (so the heading is preserved in the per-topic file).
17
+ 4. Else emit the line verbatim to the current topic body.
18
+
19
+ Output per topic:
20
+
21
+ - `__preamble__` → contents (if non-empty) written into the README index under a "Preamble" section.
22
+ - Every other slug → written to `docs/briefing/<slug>.md`.
23
+
24
+ ### Slug derivation
25
+
26
+ ```
27
+ slug = heading_text
28
+ | lowercase
29
+ | replace non-[a-z0-9]+ runs with '-'
30
+ | trim leading and trailing '-'
31
+ | truncate to 60 chars
32
+ ```
33
+
34
+ If the resulting slug is empty (e.g. an H2 with no alphanumeric content), substitute `topic-<N>` where `<N>` is the sequential topic index.
35
+
36
+ ### Collision handling
37
+
38
+ A flat slug-set is maintained. When a derived slug collides with one already taken:
39
+
40
+ ```
41
+ candidate = slug
42
+ n = 2
43
+ while candidate in taken:
44
+ candidate = slug + "-" + str(n)
45
+ n += 1
46
+ slug = candidate
47
+ ```
48
+
49
+ Worked example — legacy file with three `## Hooks` sections:
50
+
51
+ - First section → `hooks.md`
52
+ - Second → `hooks-2.md`
53
+ - Third → `hooks-3.md`
54
+
55
+ Collisions are surfaced in the final report so the adopter can rename for clarity.
56
+
57
+ ## Code-fence-awareness rationale
58
+
59
+ A legacy briefing entry that documents a hook script may include a fenced bash block with `## Some heading` as a literal source line. Without fence-awareness, the walker would treat that line as a topic marker and shred the code block across two files. The fence guard preserves the example intact in whatever topic owns the surrounding prose.
60
+
61
+ The guard is column-anchored (`^\\\`\\\`\\\``) — indented fences inside list items are NOT recognised as fence delimiters. This is deliberate: GFM fenced blocks inside list items use different parsing rules and rarely contain heading-pattern lines that would mislead the splitter; the simpler column-anchored detector covers >95% of real briefing content without the parser complexity of full CommonMark fence handling.
62
+
63
+ ## README index shape
64
+
65
+ The generated `docs/briefing/README.md` has the structure:
66
+
67
+ ```markdown
68
+ # Project Briefing
69
+
70
+ Migrated from legacy `docs/BRIEFING.md` via `/wr-retrospective:migrate-briefing` on <YYYY-MM-DD>.
71
+
72
+ ## Critical Points (Session-Start Surface)
73
+
74
+ _To be populated by the next `/wr-retrospective:run-retro` Step 1.5 signal-vs-noise pass (per ADR-040)._
75
+
76
+ ## Topic Index
77
+
78
+ | File | Source heading |
79
+ |---|---|
80
+ | [hooks.md](./hooks.md) | Hooks |
81
+ | [releases.md](./releases.md) | Releases |
82
+ | ... | ... |
83
+
84
+ ## Preamble
85
+
86
+ <contents of the legacy file before its first H2, if any>
87
+ ```
88
+
89
+ The "Source heading" column is the raw H2 text from the legacy file — useful when the slug truncation or collision-suffix obscures the original meaning.
90
+
91
+ ## Recovery
92
+
93
+ If the migration produces output the adopter is unhappy with:
94
+
95
+ ```bash
96
+ git checkout HEAD -- docs/BRIEFING.md docs/briefing/
97
+ ```
98
+
99
+ reverts the migration entirely. The skill commits its work as a single coherent commit per ADR-014, so revert is a single `git revert <sha>` or a checkout of the pre-skill HEAD.
100
+
101
+ The legacy file is moved (not deleted) to `docs/BRIEFING.md.migrated-<date>` in the working tree before the commit, so adopters who want to inspect the original after the fact can still see it under that name in any pre-revert clone.
102
+
103
+ ## Scope exclusions
104
+
105
+ - **Does NOT seed Critical Points**. The roll-up surface ADR-040 defines is populated from per-entry signal scores in the run-retro pass; pre-seeding from heading text would be lossy guesswork. The migrated tree's index leaves the section as a placeholder for the next retro to populate.
106
+ - **Does NOT classify entries**. No signal-vs-noise grading happens during migration — that is run-retro Step 1.5's mechanical pass, not this skill's responsibility.
107
+ - **Does NOT detect or split H3 subsections**. H2 is the only topic boundary. Deeper nesting stays inside the parent topic file and can be split manually if needed.
108
+ - **Does NOT carry archives forward**. The legacy file is treated as the live current state; if it already has `## Archive` sections, they are migrated as their own topic files (named per the slug rule). No special archive-rotation handling.
109
+ - **Does NOT touch `docs/briefing/` if a tree is already present**. The idempotency contract is hard: tree-present → no-op. Use `--force` to override.
110
+
111
+ ## Verification
112
+
113
+ Behavioural fixture in `test/migrate-briefing-fixture.bats` exercises:
114
+
115
+ - Empty repo (no legacy file) → no-op exit 0
116
+ - Empty legacy file (`-s` returns false) → no-op exit 0
117
+ - Tree already migrated (README.md exists) → no-op exit 0
118
+ - Synthetic legacy file with three H2 sections → three per-topic files + index
119
+ - Slug collision (two identical H2 texts) → `-2` suffix applied
120
+ - Code-fence-protected H2 inside a fenced block → not promoted to topic marker
121
+ - Idempotent re-run → no-op exit 0 (no second migration)
122
+
123
+ Contract bats in `test/migrate-briefing-contract.bats` asserts:
124
+
125
+ - SKILL.md frontmatter declares the skill name + description
126
+ - ADR-032 (foreground-synchronous), ADR-014 (self-commit), ADR-038 (progressive disclosure), ADR-040 (target shape), ADR-052 (behavioural tests) all cross-referenced
127
+ - Idempotency clause present (two-direction no-op)
128
+ - Rule 6 audit section present (ADR-013)
129
+
130
+ ## Related
131
+
132
+ - P204 — the ticket this skill closes
133
+ - ADR-040 — target tree contract
134
+ - JTBD-007 — adopter currency (pending amendment to extend scope to artefact-layout-currency per P204 new-jtbd-flag; queued for next /wr-itil:review-problems)
135
+ - `feedback_no_repo_relative_paths_in_published_artifacts.md` — why the helper script ships via `bin/` shim per ADR-049
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: wr-retrospective:migrate-briefing
3
+ description: Migrate a legacy single-file `docs/BRIEFING.md` into the per-topic `docs/briefing/` tree expected by the `wr-retrospective:run-retro` Tier-3 rotation and the SessionStart briefing surface. Idempotent and foreground-synchronous — silently no-ops when the tree is already in place or when no legacy file is present. Implements the migration path P204 left manual.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Migrate Briefing — Legacy Single-File → Per-Topic Tree
8
+
9
+ Adopters who carried a legacy monolithic `docs/BRIEFING.md` from an older `@windyroad/retrospective` release have no automation path to the per-topic `docs/briefing/` tree the current Tier-3 rotation contract (ADR-040) expects. The dual-tolerant SessionStart hook (`packages/retrospective/hooks/session-start-briefing.sh`) keeps adopters working while the legacy file remains, but per-topic-rotation only fires once the tree exists. This skill closes the loop.
10
+
11
+ See `REFERENCE.md` in this directory for the heading-extraction algorithm, slug-collision handling, code-fence-aware parsing, recovery semantics, and scope exclusions (progressive disclosure per ADR-038).
12
+
13
+ ## Pattern
14
+
15
+ This skill is **foreground-synchronous** per [ADR-032](../../../../docs/decisions/032-governance-skill-invocation-patterns.proposed.md) (Governance skill invocation patterns). Migration writes files into `docs/briefing/` that the user normally wants to review before they commit — the wrapped subagent shape would defeat that review. The skill commits its own work per [ADR-014](../../../../docs/decisions/014-governance-skills-commit-their-own-work.proposed.md).
16
+
17
+ The target tree shape (per-topic files + `README.md` index + Critical Points roll-up) is defined by [ADR-040](../../../../docs/decisions/040-session-start-briefing-surface.proposed.md). This skill is the migration path INTO that shape — not a re-encoding of it.
18
+
19
+ REFERENCE.md split + the progressive-disclosure structure of this SKILL.md follows [ADR-038](../../../../docs/decisions/038-progressive-disclosure-pattern.proposed.md). Behavioural-first bats coverage follows [ADR-052](../../../../docs/decisions/052-behavioural-tests-default.proposed.md).
20
+
21
+ ## Idempotency Contract
22
+
23
+ The skill MUST silently no-op in two cases:
24
+
25
+ 1. **Tree already present** — `docs/briefing/README.md` exists. Re-running after a previous migration is safe and reports "already migrated; no action".
26
+ 2. **No legacy file** — `docs/BRIEFING.md` does not exist (or is empty). Fresh adopters who never had a monolithic briefing get a "no legacy file found; no action" outcome.
27
+
28
+ Both no-op paths exit 0. The fixture bats asserts both directions.
29
+
30
+ ## Invocation
31
+
32
+ ```
33
+ /wr-retrospective:migrate-briefing [--dry-run] [--force]
34
+ ```
35
+
36
+ | Flag | Effect |
37
+ |---|---|
38
+ | `--dry-run` | Print the topic slug list + per-topic file plan; no writes. |
39
+ | `--force` | Re-run even when `docs/briefing/README.md` already exists. Off by default — present tree is reported and skipped per the idempotency contract. |
40
+
41
+ ## Steps
42
+
43
+ ### 1. Detect inputs and short-circuit
44
+
45
+ ```bash
46
+ LEGACY="docs/BRIEFING.md"
47
+ TREE_DIR="docs/briefing"
48
+ TREE_INDEX="$TREE_DIR/README.md"
49
+
50
+ if [ -f "$TREE_INDEX" ] && [ "${FORCE:-0}" != "1" ]; then
51
+ echo "migrate-briefing: $TREE_INDEX already exists; tree already migrated (no action)."
52
+ exit 0
53
+ fi
54
+
55
+ if [ ! -s "$LEGACY" ]; then
56
+ echo "migrate-briefing: $LEGACY missing or empty; nothing to migrate (no action)."
57
+ exit 0
58
+ fi
59
+ ```
60
+
61
+ `-s` (non-empty file test) covers the empty-file edge case. An empty legacy stub is functionally indistinguishable from "no legacy file" — both warrant the no-op path.
62
+
63
+ ### 2. Split the legacy file by H2 headings
64
+
65
+ Walk the legacy file line-by-line. Skip lines inside fenced code blocks (toggled by ` ``` ` or `~~~` at column 0) so accidental `## ` patterns inside code samples are not promoted to topic markers. Each H2 (`^## `) starts a new topic; H1 (`^# `) becomes the document preamble.
66
+
67
+ Slug derivation per heading text:
68
+ - Lowercase
69
+ - Replace non-alphanumeric runs with `-`
70
+ - Trim leading/trailing `-`
71
+ - Truncate to 60 chars
72
+ - On collision, append `-2`, `-3`, ...
73
+
74
+ Write each topic body to `docs/briefing/<slug>.md`. The preamble (everything before the first H2) goes to the index README under a "Preamble" section.
75
+
76
+ The full bash implementation ships as a script invoked via the `wr-retrospective-migrate-briefing` shim on `$PATH` per [ADR-049](../../../../docs/decisions/049-plugin-bundled-scripts-resolve-via-bin-on-path.proposed.md) (no repo-relative `packages/...` paths in shipped SKILL prose). The shim is wrapped per [ADR-080](../../../../docs/decisions/080-highest-version-wins-shim-wrapper-plugin-scaffold.proposed.md) so it resolves correctly under both source-monorepo execution and installed-cache execution:
77
+
78
+ ```bash
79
+ wr-retrospective-migrate-briefing "$@"
80
+ ```
81
+
82
+ See REFERENCE.md → "Heading-extraction algorithm" for the line-walker pseudocode + slug-collision worked example.
83
+
84
+ ### 3. Generate the index
85
+
86
+ `docs/briefing/README.md` is written with:
87
+
88
+ - Title `# Project Briefing`.
89
+ - A short "Critical Points (Session-Start Surface)" placeholder noting the run-retro pass populates it from per-entry signal scores (ADR-040).
90
+ - A "Topic Index" table listing every generated topic file with its source-section heading text as the human label.
91
+ - A "Migrated from legacy `docs/BRIEFING.md` via `/wr-retrospective:migrate-briefing`" provenance line.
92
+
93
+ The Critical Points section is intentionally left as a placeholder. The next `/wr-retrospective:run-retro` Step 1.5 signal-vs-noise pass populates it from per-entry classifications. Pre-seeding from heading text would be lossy guesswork.
94
+
95
+ ### 4. Retire the legacy file
96
+
97
+ Rename `docs/BRIEFING.md` → `docs/BRIEFING.md.migrated-<date>` so the source is preserved on disk but no longer matches the SessionStart hook's read paths. `git mv` is used so the rename stages cleanly. (Per the briefing's own `git mv + Edit + git add` note — there is no Edit in this step, so no re-stage is needed.)
98
+
99
+ ### 5. Commit
100
+
101
+ One coherent commit per ADR-014:
102
+
103
+ ```
104
+ chore(retrospective): migrate legacy docs/BRIEFING.md to per-topic docs/briefing/ tree
105
+ ```
106
+
107
+ with a `RISK_BYPASS: legacy-briefing-migration` trailer if the project's risk-scorer pipeline flags the bulk-file-add.
108
+
109
+ ## Rule 6 audit (ADR-013)
110
+
111
+ This skill emits **no** `AskUserQuestion` calls. Every decision is mechanical (idempotency detection, slug derivation, file write). Per ADR-032 + ADR-013 Rule 5, mechanical / policy-authorised stages own silent classification and MUST NOT surface consent gates (P132 inverse-P078).
112
+
113
+ If a future enhancement adds direction-setting choices (e.g. user-supplied topic-grouping), that surface routes through `AskUserQuestion` per ADR-013 Rule 1 with the 4-option cap. The non-interactive / AFK fallback is the queue-and-continue default per ADR-013 Rule 6 (P352 universal default) — never auto-decide direction-setters.
114
+
115
+ ## When to invoke
116
+
117
+ - One-time during an adopter migration to `@windyroad/retrospective` ≥ the version that ships per-topic rotation.
118
+ - After a manual edit of `docs/BRIEFING.md` that the adopter wants to formally re-split (use `--force`).
119
+ - Safe to run any time — the idempotency contract guarantees no-op on already-migrated trees and on fresh repos.
120
+
121
+ ## Relationship to other skills
122
+
123
+ - **Composes with** `/wr-retrospective:run-retro` — once the tree exists, Step 1.5 (signal-vs-noise pass) + Step 3 (briefing curation) operate on the per-topic files. Pre-migration, run-retro reads the legacy single file.
124
+ - **Composes with** the SessionStart hook (`session-start-briefing.sh`) — the hook silently no-ops when neither `docs/briefing/README.md` nor `docs/BRIEFING.md` is present; this skill produces the former from the latter.
125
+ - **Distinct from** `/install-updates` — that skill refreshes the plugin install cache; this skill migrates adopter artefacts on disk.
126
+
127
+ ## See also
128
+
129
+ - P204 (Known Error) — the ticket this skill closes
130
+ - ADR-040 — per-topic briefing surface contract (target shape)
131
+ - ADR-038 — progressive disclosure pattern (SKILL/REFERENCE split)
132
+ - ADR-052 — behavioural tests by default
133
+ - JTBD-007 — Keep Plugins Current (adopter currency — pending amendment to extend currency scope to adopter-artefact-layout; see P204 new-jtbd-flag)
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Contract-level structural tests for /wr-retrospective:migrate-briefing.
4
+ # Permitted Exception per ADR-005 — structural SKILL.md content checks,
5
+ # mirrored on scaffold-intake-contract.bats. Behavioural coverage lives
6
+ # in migrate-briefing-fixture.bats per ADR-052.
7
+ #
8
+ # Closes P204 verification surface.
9
+
10
+ setup() {
11
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
12
+ SKILL_DIR="$REPO_ROOT/packages/retrospective/skills/migrate-briefing"
13
+ SKILL_MD="$SKILL_DIR/SKILL.md"
14
+ REFERENCE_MD="$SKILL_DIR/REFERENCE.md"
15
+ SCRIPT="$REPO_ROOT/packages/retrospective/scripts/migrate-briefing.sh"
16
+ SHIM="$REPO_ROOT/packages/retrospective/bin/wr-retrospective-migrate-briefing"
17
+ }
18
+
19
+ @test "migrate-briefing: SKILL.md exists" {
20
+ [ -f "$SKILL_MD" ]
21
+ }
22
+
23
+ @test "migrate-briefing: REFERENCE.md exists (ADR-038 split)" {
24
+ [ -f "$REFERENCE_MD" ]
25
+ }
26
+
27
+ @test "migrate-briefing: implementation script exists and is executable" {
28
+ [ -x "$SCRIPT" ]
29
+ }
30
+
31
+ @test "migrate-briefing: bin shim exists and is executable" {
32
+ [ -x "$SHIM" ]
33
+ }
34
+
35
+ @test "migrate-briefing: SKILL.md frontmatter declares the skill name" {
36
+ run grep -F 'name: wr-retrospective:migrate-briefing' "$SKILL_MD"
37
+ [ "$status" -eq 0 ]
38
+ }
39
+
40
+ @test "migrate-briefing: SKILL.md cites ADR-040 (target tree shape — load-bearing per architect)" {
41
+ run grep -F 'ADR-040' "$SKILL_MD"
42
+ [ "$status" -eq 0 ]
43
+ }
44
+
45
+ @test "migrate-briefing: SKILL.md cites ADR-032 foreground-synchronous pattern" {
46
+ run grep -F 'ADR-032' "$SKILL_MD"
47
+ [ "$status" -eq 0 ]
48
+ run grep -iE 'foreground.synchronous' "$SKILL_MD"
49
+ [ "$status" -eq 0 ]
50
+ }
51
+
52
+ @test "migrate-briefing: SKILL.md cites ADR-014 self-commit pattern" {
53
+ run grep -F 'ADR-014' "$SKILL_MD"
54
+ [ "$status" -eq 0 ]
55
+ }
56
+
57
+ @test "migrate-briefing: SKILL.md cites ADR-038 progressive-disclosure pattern" {
58
+ run grep -F 'ADR-038' "$SKILL_MD"
59
+ [ "$status" -eq 0 ]
60
+ }
61
+
62
+ @test "migrate-briefing: SKILL.md cites ADR-052 behavioural-tests-default" {
63
+ run grep -F 'ADR-052' "$SKILL_MD"
64
+ [ "$status" -eq 0 ]
65
+ }
66
+
67
+ @test "migrate-briefing: SKILL.md cites ADR-049 (no repo-relative paths — recurring class)" {
68
+ run grep -F 'ADR-049' "$SKILL_MD"
69
+ [ "$status" -eq 0 ]
70
+ }
71
+
72
+ @test "migrate-briefing: SKILL.md declares two-direction idempotency clause" {
73
+ # Both no-op directions named: tree already present + no legacy file.
74
+ run grep -iE 'tree.already.present|already.migrated' "$SKILL_MD"
75
+ [ "$status" -eq 0 ]
76
+ run grep -iE 'no.legacy.file|legacy.*does.not.exist|missing or empty' "$SKILL_MD"
77
+ [ "$status" -eq 0 ]
78
+ }
79
+
80
+ @test "migrate-briefing: SKILL.md carries Rule 6 audit section (ADR-013)" {
81
+ run grep -F 'Rule 6' "$SKILL_MD"
82
+ [ "$status" -eq 0 ]
83
+ }
84
+
85
+ @test "migrate-briefing: SKILL.md cross-references P204 (the closing ticket)" {
86
+ run grep -F 'P204' "$SKILL_MD"
87
+ [ "$status" -eq 0 ]
88
+ }
89
+
90
+ @test "migrate-briefing: SKILL.md does NOT carry repo-relative packages/ paths (P151/P153/P219/P317 class)" {
91
+ # No bash invocations that hardcode a packages/.../scripts/ path —
92
+ # the script ships via ADR-049 PATH shim.
93
+ run grep -E 'packages/[a-z]+/scripts/migrate-briefing\.sh' "$SKILL_MD"
94
+ [ "$status" -ne 0 ]
95
+ }
96
+
97
+ @test "migrate-briefing: REFERENCE.md documents the heading-extraction algorithm" {
98
+ run grep -iE 'heading.extraction|slug.derivation|collision' "$REFERENCE_MD"
99
+ [ "$status" -eq 0 ]
100
+ }
101
+
102
+ @test "migrate-briefing: REFERENCE.md documents code-fence-aware parsing" {
103
+ run grep -iE 'fence|code.fence|in_fence' "$REFERENCE_MD"
104
+ [ "$status" -eq 0 ]
105
+ }
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env bats
2
+
3
+ # Behavioural fixture for /wr-retrospective:migrate-briefing per ADR-052.
4
+ # Exercises the implementation script against synthetic legacy
5
+ # docs/BRIEFING.md fixtures in a temp dir. Asserts observable
6
+ # file-system outcomes — per feedback_behavioural_tests.md (P081),
7
+ # not source content.
8
+ #
9
+ # Closes P204 verification surface.
10
+
11
+ setup() {
12
+ REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../../.." && pwd)"
13
+ SCRIPT="$REPO_ROOT/packages/retrospective/scripts/migrate-briefing.sh"
14
+ ORIG_DIR="$PWD"
15
+ TEST_DIR=$(mktemp -d -t migrate-briefing-bats.XXXXXX)
16
+ cd "$TEST_DIR"
17
+ mkdir -p docs
18
+ }
19
+
20
+ teardown() {
21
+ cd "$ORIG_DIR"
22
+ rm -rf "$TEST_DIR"
23
+ }
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # Idempotency direction 1: no legacy file → no-op exit 0
27
+ # ---------------------------------------------------------------------------
28
+
29
+ @test "fixture: no legacy file → no-op exit 0, tree not created" {
30
+ run bash "$SCRIPT"
31
+ [ "$status" -eq 0 ]
32
+ [[ "$output" == *"nothing to migrate"* ]] || [[ "$output" == *"no action"* ]]
33
+ [ ! -d docs/briefing ]
34
+ }
35
+
36
+ @test "fixture: empty legacy file (zero-byte) → no-op exit 0" {
37
+ : > docs/BRIEFING.md
38
+ run bash "$SCRIPT"
39
+ [ "$status" -eq 0 ]
40
+ [[ "$output" == *"no action"* ]]
41
+ [ ! -d docs/briefing ]
42
+ }
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # Idempotency direction 2: tree already present → no-op exit 0
46
+ # ---------------------------------------------------------------------------
47
+
48
+ @test "fixture: tree already present → no-op exit 0, legacy untouched" {
49
+ mkdir -p docs/briefing
50
+ cat > docs/briefing/README.md <<'EOF'
51
+ # Project Briefing
52
+ existing content
53
+ EOF
54
+ cat > docs/BRIEFING.md <<'EOF'
55
+ # Legacy
56
+ ## Hooks
57
+ content
58
+ EOF
59
+ run bash "$SCRIPT"
60
+ [ "$status" -eq 0 ]
61
+ [[ "$output" == *"already migrated"* ]]
62
+ [ -f docs/BRIEFING.md ]
63
+ run grep -F 'existing content' docs/briefing/README.md
64
+ [ "$status" -eq 0 ]
65
+ }
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Core migration: synthetic legacy file with three H2 sections
69
+ # ---------------------------------------------------------------------------
70
+
71
+ @test "fixture: three-section legacy → three topic files + README index" {
72
+ cat > docs/BRIEFING.md <<'EOF'
73
+ # Project Briefing
74
+
75
+ Preamble content here.
76
+
77
+ ## Hooks
78
+
79
+ Hook entry one.
80
+
81
+ ## Releases
82
+
83
+ Release entry one.
84
+
85
+ ## Plugin Distribution
86
+
87
+ Plugin distribution entry one.
88
+ EOF
89
+ run bash "$SCRIPT"
90
+ [ "$status" -eq 0 ]
91
+ [ -f docs/briefing/README.md ]
92
+ [ -f docs/briefing/hooks.md ]
93
+ [ -f docs/briefing/releases.md ]
94
+ [ -f docs/briefing/plugin-distribution.md ]
95
+ run grep -F 'Hook entry one.' docs/briefing/hooks.md
96
+ [ "$status" -eq 0 ]
97
+ run grep -F 'Release entry one.' docs/briefing/releases.md
98
+ [ "$status" -eq 0 ]
99
+ run grep -F 'Plugin distribution entry one.' docs/briefing/plugin-distribution.md
100
+ [ "$status" -eq 0 ]
101
+ run grep -F '[hooks.md](./hooks.md)' docs/briefing/README.md
102
+ [ "$status" -eq 0 ]
103
+ run grep -F 'Preamble content here.' docs/briefing/README.md
104
+ [ "$status" -eq 0 ]
105
+ }
106
+
107
+ @test "fixture: migration retires legacy under date-stamped suffix" {
108
+ cat > docs/BRIEFING.md <<'EOF'
109
+ # Project Briefing
110
+
111
+ ## Hooks
112
+
113
+ Hook content.
114
+ EOF
115
+ run bash "$SCRIPT"
116
+ [ "$status" -eq 0 ]
117
+ [ ! -f docs/BRIEFING.md ]
118
+ run bash -c 'ls docs/BRIEFING.md.migrated-* 2>/dev/null'
119
+ [ "$status" -eq 0 ]
120
+ [ -n "$output" ]
121
+ }
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # Idempotent re-run after successful migration: tree present → no-op
125
+ # ---------------------------------------------------------------------------
126
+
127
+ @test "fixture: re-run after successful migration → no-op exit 0" {
128
+ cat > docs/BRIEFING.md <<'EOF'
129
+ # Project Briefing
130
+
131
+ ## Hooks
132
+
133
+ Hook content.
134
+ EOF
135
+ bash "$SCRIPT" >/dev/null
136
+ # Second run sees the tree, no-ops, and does NOT re-process the
137
+ # already-retired legacy file.
138
+ run bash "$SCRIPT"
139
+ [ "$status" -eq 0 ]
140
+ [[ "$output" == *"already migrated"* ]]
141
+ }
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Slug collision: two H2 sections with the same heading text
145
+ # ---------------------------------------------------------------------------
146
+
147
+ @test "fixture: duplicate H2 headings → second slug gets -2 suffix" {
148
+ cat > docs/BRIEFING.md <<'EOF'
149
+ # Project Briefing
150
+
151
+ ## Hooks
152
+
153
+ First hooks section.
154
+
155
+ ## Hooks
156
+
157
+ Second hooks section.
158
+ EOF
159
+ run bash "$SCRIPT"
160
+ [ "$status" -eq 0 ]
161
+ [ -f docs/briefing/hooks.md ]
162
+ [ -f docs/briefing/hooks-2.md ]
163
+ run grep -F 'First hooks section.' docs/briefing/hooks.md
164
+ [ "$status" -eq 0 ]
165
+ run grep -F 'Second hooks section.' docs/briefing/hooks-2.md
166
+ [ "$status" -eq 0 ]
167
+ }
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Code-fence awareness: H2 inside a fenced block must NOT be promoted
171
+ # ---------------------------------------------------------------------------
172
+
173
+ @test "fixture: H2 inside fenced code block → not promoted to topic marker" {
174
+ cat > docs/BRIEFING.md <<'EOF'
175
+ # Project Briefing
176
+
177
+ ## Real Topic
178
+
179
+ Real content.
180
+
181
+ ```bash
182
+ ## This looks like H2 but is inside a fence
183
+ echo "do not promote"
184
+ ```
185
+
186
+ More real content after the fence.
187
+
188
+ ## Second Real Topic
189
+
190
+ Second real content.
191
+ EOF
192
+ run bash "$SCRIPT"
193
+ [ "$status" -eq 0 ]
194
+ # Only two topic files — the fenced ## did NOT create a third.
195
+ [ -f docs/briefing/real-topic.md ]
196
+ [ -f docs/briefing/second-real-topic.md ]
197
+ [ ! -f "docs/briefing/this-looks-like-h2-but-is-inside-a-fence.md" ]
198
+ # The fenced ## line is preserved inside real-topic.md.
199
+ run grep -F '## This looks like H2 but is inside a fence' docs/briefing/real-topic.md
200
+ [ "$status" -eq 0 ]
201
+ }
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Dry-run: prints plan, does NOT write
205
+ # ---------------------------------------------------------------------------
206
+
207
+ @test "fixture: --dry-run prints plan and does NOT write tree" {
208
+ cat > docs/BRIEFING.md <<'EOF'
209
+ # Project Briefing
210
+
211
+ ## Hooks
212
+
213
+ Hook content.
214
+ EOF
215
+ run bash "$SCRIPT" --dry-run
216
+ [ "$status" -eq 0 ]
217
+ [[ "$output" == *"dry-run"* ]]
218
+ [[ "$output" == *"hooks"* ]]
219
+ [ ! -d docs/briefing ]
220
+ [ -f docs/BRIEFING.md ]
221
+ }
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # --force: re-runs even when tree already exists
225
+ # ---------------------------------------------------------------------------
226
+
227
+ @test "fixture: --force overrides tree-present idempotency guard" {
228
+ mkdir -p docs/briefing
229
+ echo "# stale" > docs/briefing/README.md
230
+ cat > docs/BRIEFING.md <<'EOF'
231
+ # Project Briefing
232
+
233
+ ## Hooks
234
+
235
+ Hook content.
236
+ EOF
237
+ run bash "$SCRIPT" --force
238
+ [ "$status" -eq 0 ]
239
+ [ -f docs/briefing/hooks.md ]
240
+ run grep -F 'Migrated from legacy' docs/briefing/README.md
241
+ [ "$status" -eq 0 ]
242
+ }