@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.
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +1 -0
- package/bin/wr-retrospective-migrate-briefing +51 -0
- package/package.json +1 -1
- package/scripts/migrate-briefing.sh +190 -0
- package/skills/migrate-briefing/REFERENCE.md +135 -0
- package/skills/migrate-briefing/SKILL.md +133 -0
- package/skills/migrate-briefing/test/migrate-briefing-contract.bats +105 -0
- package/skills/migrate-briefing/test/migrate-briefing-fixture.bats +242 -0
|
@@ -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
|
@@ -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
|
+
}
|