@nklisch/pi-agile-workflow 0.15.3
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 +10 -0
- package/.codex-plugin/plugin.json +28 -0
- package/CHANGELOG.md +591 -0
- package/README.md +107 -0
- package/docs/ARCHITECTURE.md +716 -0
- package/docs/MIGRATION.md +419 -0
- package/docs/PRINCIPLES.md +464 -0
- package/docs/ROADMAP.md +95 -0
- package/docs/SPEC.md +855 -0
- package/docs/VISION.md +106 -0
- package/extensions/agile-workflow.test.ts +419 -0
- package/extensions/agile-workflow.ts +467 -0
- package/hooks/hooks.json +52 -0
- package/hooks/scripts/prompt-context.py +628 -0
- package/hooks/scripts/substrate-maintainer.py +359 -0
- package/hooks/scripts/test_prompt_context.py +757 -0
- package/package.json +30 -0
- package/scripts/install-work-view.sh +165 -0
- package/scripts/tests/agent-metadata.test.sh +75 -0
- package/scripts/tests/bump-version.test.sh +552 -0
- package/scripts/tests/channel-parity.test.sh +117 -0
- package/scripts/tests/convert-content-integrity.test.sh +246 -0
- package/scripts/tests/convert-install-routing.test.sh +159 -0
- package/scripts/tests/install-work-view.test.sh +411 -0
- package/scripts/tests/pi-package-metadata.test.sh +119 -0
- package/scripts/tests/work-board-shim.test.sh +239 -0
- package/scripts/tests/work-view-dist-version.test.sh +105 -0
- package/scripts/work-board.sh +64 -0
- package/scripts/work-view.sh +423 -0
- package/skills/autopilot/SKILL.md +330 -0
- package/skills/autopilot/agents/openai.yaml +6 -0
- package/skills/board/SKILL.md +62 -0
- package/skills/board/agents/openai.yaml +6 -0
- package/skills/bold-refactor/SKILL.md +244 -0
- package/skills/bold-refactor/agents/openai.yaml +6 -0
- package/skills/bug-scan/SKILL.md +378 -0
- package/skills/bug-scan/agents/openai.yaml +6 -0
- package/skills/bug-scan/references/async-promises.md +119 -0
- package/skills/bug-scan/references/concurrency-races.md +148 -0
- package/skills/bug-scan/references/data-layer.md +167 -0
- package/skills/bug-scan/references/error-handling.md +197 -0
- package/skills/bug-scan/references/gate-item-template.md +94 -0
- package/skills/bug-scan/references/language-footguns.md +219 -0
- package/skills/bug-scan/references/parked-item-template.md +103 -0
- package/skills/bug-scan/references/report-template.md +115 -0
- package/skills/bug-scan/references/resource-leaks.md +199 -0
- package/skills/bug-scan/references/state-closures.md +148 -0
- package/skills/bug-scan/references/time-numbers.md +158 -0
- package/skills/convert/SKILL.md +1463 -0
- package/skills/convert/agents/openai.yaml +6 -0
- package/skills/convert/references/legacy-overlap-migration.md +179 -0
- package/skills/deep-code-scan/SKILL.md +378 -0
- package/skills/deep-code-scan/agents/openai.yaml +6 -0
- package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
- package/skills/deep-code-scan/references/consolidation.md +91 -0
- package/skills/deep-code-scan/references/decomposition.md +127 -0
- package/skills/deep-code-scan/references/item-templates.md +263 -0
- package/skills/deep-code-scan/references/lane-catalog.md +129 -0
- package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
- package/skills/deep-code-scan/references/scanner-brief.md +152 -0
- package/skills/e2e-test-design/SKILL.md +517 -0
- package/skills/e2e-test-design/references/anti-tautology.md +261 -0
- package/skills/e2e-test-design/references/service-mocks.md +234 -0
- package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
- package/skills/epic-design/SKILL.md +476 -0
- package/skills/epicize/SKILL.md +210 -0
- package/skills/epicize/agents/openai.yaml +6 -0
- package/skills/feature-design/SKILL.md +472 -0
- package/skills/fix/SKILL.md +173 -0
- package/skills/gate-cruft/SKILL.md +258 -0
- package/skills/gate-docs/SKILL.md +278 -0
- package/skills/gate-patterns/SKILL.md +389 -0
- package/skills/gate-refactor/SKILL.md +408 -0
- package/skills/gate-security/SKILL.md +275 -0
- package/skills/gate-tests/SKILL.md +367 -0
- package/skills/groom/SKILL.md +162 -0
- package/skills/groom/agents/openai.yaml +6 -0
- package/skills/ideate/SKILL.md +185 -0
- package/skills/ideate/agents/openai.yaml +6 -0
- package/skills/implement/SKILL.md +288 -0
- package/skills/implement-orchestrator/SKILL.md +604 -0
- package/skills/park/SKILL.md +111 -0
- package/skills/perf-design/SKILL.md +427 -0
- package/skills/perf-scout/SKILL.md +404 -0
- package/skills/perf-scout/agents/openai.yaml +6 -0
- package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
- package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
- package/skills/perf-scout/references/caching-and-memoization.md +123 -0
- package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
- package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
- package/skills/perf-scout/references/distributed-systems.md +150 -0
- package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
- package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
- package/skills/perf-scout/references/idea-ranking.md +75 -0
- package/skills/perf-scout/references/io-and-batching.md +135 -0
- package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
- package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
- package/skills/perf-scout/references/parked-item-template.md +117 -0
- package/skills/perf-scout/references/peer-review-pass.md +150 -0
- package/skills/perf-scout/references/report-template.md +148 -0
- package/skills/principles/SKILL.md +678 -0
- package/skills/principles/references/models.md +191 -0
- package/skills/principles/references/subagents.md +158 -0
- package/skills/prose-author/SKILL.md +185 -0
- package/skills/refactor-conventions-creator/SKILL.md +359 -0
- package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
- package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
- package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
- package/skills/refactor-design/SKILL.md +384 -0
- package/skills/release-deploy/SKILL.md +667 -0
- package/skills/release-deploy/agents/openai.yaml +6 -0
- package/skills/research/SKILL.md +166 -0
- package/skills/research/agents/openai.yaml +6 -0
- package/skills/review/SKILL.md +267 -0
- package/skills/review/references/deep-review.md +98 -0
- package/skills/review/references/review-lenses.md +68 -0
- package/skills/review/references/substrate-side-effects.md +161 -0
- package/skills/review/references/target-resolution.md +52 -0
- package/skills/scope/SKILL.md +486 -0
- package/work-view/Cargo.lock +486 -0
- package/work-view/Cargo.toml +9 -0
- package/work-view/crates/cli/.work-view-version +1 -0
- package/work-view/crates/cli/Cargo.toml +18 -0
- package/work-view/crates/cli/src/actionable.rs +500 -0
- package/work-view/crates/cli/src/args.rs +792 -0
- package/work-view/crates/cli/src/board/assets/board.css +855 -0
- package/work-view/crates/cli/src/board/assets/board.js +259 -0
- package/work-view/crates/cli/src/board/assets/card.js +139 -0
- package/work-view/crates/cli/src/board/assets/components.css +290 -0
- package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
- package/work-view/crates/cli/src/board/assets/detail.js +256 -0
- package/work-view/crates/cli/src/board/assets/filters.js +389 -0
- package/work-view/crates/cli/src/board/assets/index.html +77 -0
- package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
- package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
- package/work-view/crates/cli/src/board/assets/motion.css +131 -0
- package/work-view/crates/cli/src/board/assets/state.js +245 -0
- package/work-view/crates/cli/src/board/assets/table.js +304 -0
- package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
- package/work-view/crates/cli/src/board/assets/views.js +41 -0
- package/work-view/crates/cli/src/board/assets.rs +162 -0
- package/work-view/crates/cli/src/board/feed.rs +204 -0
- package/work-view/crates/cli/src/board/mod.rs +246 -0
- package/work-view/crates/cli/src/board/open.rs +144 -0
- package/work-view/crates/cli/src/board/server.rs +407 -0
- package/work-view/crates/cli/src/main.rs +195 -0
- package/work-view/crates/cli/src/render.rs +354 -0
- package/work-view/crates/cli/src/scope.rs +157 -0
- package/work-view/crates/cli/src/stale.rs +581 -0
- package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
- package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
- package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
- package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
- package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
- package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
- package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
- package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
- package/work-view/crates/cli/tests/integration.rs +2833 -0
- package/work-view/crates/core/Cargo.toml +13 -0
- package/work-view/crates/core/src/error.rs +61 -0
- package/work-view/crates/core/src/filter.rs +804 -0
- package/work-view/crates/core/src/graph.rs +324 -0
- package/work-view/crates/core/src/index.rs +590 -0
- package/work-view/crates/core/src/lib.rs +54 -0
- package/work-view/crates/core/src/model.rs +202 -0
- package/work-view/crates/core/src/parse.rs +494 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
- package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
- package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
- package/work-view/crates/core/tests/integration.rs +541 -0
- package/work-view/dist/.gitattributes +1 -0
- package/work-view/dist/README.md +42 -0
- package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
- package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
- package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
- package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-deploy
|
|
3
|
+
description: >
|
|
4
|
+
Cut a release in the agile-workflow substrate. Interactively binds items to a version, advances the
|
|
5
|
+
release stage from planned to quality-gate, runs all configured gates in CONVENTIONS.md order
|
|
6
|
+
(default: security, tests, cruft, docs, patterns), waits until all bound items + gate-produced items
|
|
7
|
+
reach stage:done, ships per release mapping (tag-based / branch-held / release-branch / none),
|
|
8
|
+
collapses bound items into one release summary and disposes their bodies per the terminal-tier
|
|
9
|
+
retention convention (delete-refs prunes to git history; retain-bodies keeps bodies on disk),
|
|
10
|
+
advances release to released. Idempotent — safe to re-run after fixing gate findings.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Release-Deploy
|
|
14
|
+
|
|
15
|
+
You orchestrate a release. The work is in three movements: **bind** items to a
|
|
16
|
+
version — both active done items and all unbound archived stubs late-bound here (each stub's
|
|
17
|
+
`archived_atop` records the baseline it was done atop, kept as provenance) — **gate** the bundle
|
|
18
|
+
(each gate produces items, not pass/fail; archived stubs are re-gated by hydrating their historical
|
|
19
|
+
full bodies from `git_ref` when needed), **ship**
|
|
20
|
+
when readiness criteria are met.
|
|
21
|
+
|
|
22
|
+
The release file at `.work/active/<release-id>.md` is the orchestration state.
|
|
23
|
+
Its stage advances `planned → quality-gate → released` as the release proceeds.
|
|
24
|
+
|
|
25
|
+
## Arguments
|
|
26
|
+
|
|
27
|
+
- `release-deploy <version>` — start (or resume) a release with the given version
|
|
28
|
+
- `release-deploy` — discover the active release (one at `stage: planned` or
|
|
29
|
+
`quality-gate`); resume it. If multiple are active, ask the user.
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
### Phase 1: Read CONVENTIONS
|
|
34
|
+
|
|
35
|
+
Read `.work/CONVENTIONS.md`:
|
|
36
|
+
- Release mapping: `branch-held | tag-based | release-branch | none`
|
|
37
|
+
- Gate config: `gates_for_release: [...]` (default if absent: `[security, tests, cruft, docs, patterns]`)
|
|
38
|
+
- Terminal-tier retention: `delete-refs | retain-bodies` (default if absent: `delete-refs`) —
|
|
39
|
+
governs Phase 7's body disposition
|
|
40
|
+
|
|
41
|
+
If the mapping is `none`, continue with a gate/archive-only release flow:
|
|
42
|
+
release-deploy binds items, runs gates, waits for every bound item to reach
|
|
43
|
+
`done`, drafts the changelog, and collapses the bundle into one summary at
|
|
44
|
+
`.work/releases/<version>/release-<version>.md` (bound item bodies pruned or retained
|
|
45
|
+
per terminal-tier retention; git holds history). It does **not** tag, branch, merge, push, or bump
|
|
46
|
+
versions. Publishing/version bumping is external to release-deploy and must be
|
|
47
|
+
handled by the project-specific release mechanism.
|
|
48
|
+
|
|
49
|
+
### Phase 2: Locate or create the release file
|
|
50
|
+
|
|
51
|
+
If `release-deploy <version>` was invoked and the file doesn't exist, create:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
---
|
|
55
|
+
id: release-<version>
|
|
56
|
+
kind: release
|
|
57
|
+
stage: planned
|
|
58
|
+
tags: []
|
|
59
|
+
parent: null
|
|
60
|
+
depends_on: []
|
|
61
|
+
release_binding: <version>
|
|
62
|
+
gate_origin: null
|
|
63
|
+
created: YYYY-MM-DD
|
|
64
|
+
updated: YYYY-MM-DD
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
# Release <version>
|
|
68
|
+
|
|
69
|
+
## Bound items
|
|
70
|
+
<populated in Phase 3>
|
|
71
|
+
|
|
72
|
+
## Gate runs
|
|
73
|
+
<populated in Phase 5>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Write to `.work/active/release-<version>.md` (release files live in active until
|
|
77
|
+
shipped, then move to `.work/releases/<version>/`).
|
|
78
|
+
|
|
79
|
+
### Phase 3: Bind items (if at stage: planned)
|
|
80
|
+
|
|
81
|
+
A release binds two kinds of work: **active done items** (bound the way they always were, by setting
|
|
82
|
+
`release_binding`) and **archived stubs late-bound here** (all unbound archived stubs, pulled in even
|
|
83
|
+
though archiving was decoupled from any release; each stub's `archived_atop` records which baseline it
|
|
84
|
+
was done atop, kept as provenance).
|
|
85
|
+
|
|
86
|
+
If the release is at `stage: planned`:
|
|
87
|
+
|
|
88
|
+
1. **Active done candidates (straggler sweep).** Projects typically bind items as they pass
|
|
89
|
+
review — the item carries `release_binding` before flipping `done` — so this gather only
|
|
90
|
+
sweeps done items the en-route binding missed. Show items at `stage: done` without a
|
|
91
|
+
`release_binding`. Note `--release ""` is NOT an unbound filter (work-view skips an empty
|
|
92
|
+
value entirely); filter the frontmatter directly. When the `agentic-research` plugin is
|
|
93
|
+
installed, also exclude `tags: [research]` items here — before the user-facing
|
|
94
|
+
confirmation — via work-view's real tag parse (never a hand-rolled tags regex):
|
|
95
|
+
```bash
|
|
96
|
+
agentic_research_installed=false
|
|
97
|
+
if [ -d ".research" ] ||
|
|
98
|
+
[ -f "plugins/agentic-research/skills/research-orchestrator/SKILL.md" ] ||
|
|
99
|
+
[ -f ".agents/skills/research-orchestrator/SKILL.md" ] ||
|
|
100
|
+
[ -f ".claude/skills/research-orchestrator/SKILL.md" ]; then
|
|
101
|
+
agentic_research_installed=true
|
|
102
|
+
fi
|
|
103
|
+
research_paths=""
|
|
104
|
+
if [ "$agentic_research_installed" = true ]; then
|
|
105
|
+
research_paths=$(.work/bin/work-view --tag research --paths | sort)
|
|
106
|
+
fi
|
|
107
|
+
.work/bin/work-view --stage done --paths | while IFS= read -r item; do
|
|
108
|
+
[ -n "$research_paths" ] && printf '%s\n' "$research_paths" | grep -qxF "$item" && continue
|
|
109
|
+
binding=$(grep -m1 '^release_binding:' "$item" | awk '{print $2}')
|
|
110
|
+
{ [ "$binding" = "null" ] || [ -z "$binding" ]; } && echo "$item"
|
|
111
|
+
done
|
|
112
|
+
```
|
|
113
|
+
(Research engagements are inputs that ground other work, not release members; they never
|
|
114
|
+
bind. Without `agentic-research`, `[research]` is an inert project tag — items with it
|
|
115
|
+
bind normally, and the probe above leaves the gather unfiltered.)
|
|
116
|
+
|
|
117
|
+
2. **Archived-stub candidates (`archived_atop` late-binding).** Gather **all unbound archived
|
|
118
|
+
stubs** (`release_binding: null`) regardless of their `archived_atop` value. Each unbound stub is
|
|
119
|
+
work done atop some prior baseline that no release has yet claimed; this version claims whatever
|
|
120
|
+
remains. `archived_atop` is recorded provenance (it surfaces in the Phase 7 summary column), **not**
|
|
121
|
+
the gather filter. Gathering by strict `archived_atop == prior` equality strands a stub forever
|
|
122
|
+
whenever a release is skipped: if a stub atop `N` is deselected (or its baseline differs) at `N+1`,
|
|
123
|
+
then at `N+2` the prior tag has advanced and that stub — still `archived_atop: N`,
|
|
124
|
+
`release_binding: null` — would never be gathered again. Claiming all unbound stubs prevents the
|
|
125
|
+
leak.
|
|
126
|
+
```bash
|
|
127
|
+
# The prior shipped tag is still useful as recorded provenance / display context.
|
|
128
|
+
prior=$(git describe --tags --abbrev=0 2>/dev/null \
|
|
129
|
+
|| ls -d .work/releases/*/ 2>/dev/null | sort -V | tail -1 | xargs -r basename)
|
|
130
|
+
prior=${prior:-pre-release}
|
|
131
|
+
# Gather ALL unbound archived stubs (recurse: ROADMAP-phase epics archive to
|
|
132
|
+
# .work/archive/epics/, not just the flat .work/archive/ root).
|
|
133
|
+
# When agentic-research is installed: [research] items are research inputs, not
|
|
134
|
+
# release members — exclude them. Use work-view's real tag parse for the exclusion
|
|
135
|
+
# set, never a hand-rolled tags regex (a regex misreads block-style tag lists and
|
|
136
|
+
# false-positives on tags like research-ops). Without agentic-research, [research]
|
|
137
|
+
# is an inert project tag and must not be filtered.
|
|
138
|
+
agentic_research_installed=false
|
|
139
|
+
if [ -d ".research" ] ||
|
|
140
|
+
[ -f "plugins/agentic-research/skills/research-orchestrator/SKILL.md" ] ||
|
|
141
|
+
[ -f ".agents/skills/research-orchestrator/SKILL.md" ] ||
|
|
142
|
+
[ -f ".claude/skills/research-orchestrator/SKILL.md" ]; then
|
|
143
|
+
agentic_research_installed=true
|
|
144
|
+
fi
|
|
145
|
+
research_paths=""
|
|
146
|
+
if [ "$agentic_research_installed" = true ]; then
|
|
147
|
+
research_paths=$(.work/bin/work-view --scope archive --tag research --paths | sort)
|
|
148
|
+
fi
|
|
149
|
+
find .work/archive -name '*.md' -type f | while read -r p; do
|
|
150
|
+
[ -n "$research_paths" ] && printf '%s\n' "$research_paths" | grep -qxF "$p" && continue
|
|
151
|
+
binding=$(grep -m1 '^release_binding:' "$p" | awk '{print $2}')
|
|
152
|
+
[[ "$binding" == "null" ]] && echo "$p"
|
|
153
|
+
done
|
|
154
|
+
```
|
|
155
|
+
(Skip stubs already carrying a `release_binding` — they belong to an earlier release. Show each
|
|
156
|
+
stub's `archived_atop` so the user sees the baseline it was done atop when confirming. **When the
|
|
157
|
+
`agentic-research` plugin is installed, skip `tags: [research]` stubs** — a research engagement
|
|
158
|
+
is an input that grounds other work, not a shippable bundle member, so it never binds to a
|
|
159
|
+
release; without `agentic-research`, `[research]` is an inert project tag and stubs bind
|
|
160
|
+
normally. See the `[research]` tag semantics.)
|
|
161
|
+
|
|
162
|
+
3. Use structured question tool to confirm the full set (active done items + gathered archived stubs).
|
|
163
|
+
Default: all active done items without binding plus all unbound archived stubs go in. Confirming
|
|
164
|
+
the archived set explicitly is required, since late-binding pulls in work the user never bound by
|
|
165
|
+
hand; the user may deselect any stub (it stays unbound for a later release).
|
|
166
|
+
|
|
167
|
+
4. For each chosen item — active done item OR archived stub — edit its frontmatter:
|
|
168
|
+
`release_binding: <version>`. The PostToolUse hook bumps `updated:`. Do **not** touch a stub's
|
|
169
|
+
`archived_atop` (it is immutable) or `git_ref`.
|
|
170
|
+
|
|
171
|
+
5. Update the release file's body with the bound items list (note which were late-bound archived
|
|
172
|
+
stubs).
|
|
173
|
+
|
|
174
|
+
6. Advance the release file: `stage: planned → quality-gate`.
|
|
175
|
+
|
|
176
|
+
7. Commit:
|
|
177
|
+
```bash
|
|
178
|
+
git add .work/active/release-<version>.md <bound-item-files> <bound-archive-stubs>
|
|
179
|
+
git commit -m "release-deploy: bind <N> items to <version>"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Phase 3.5: Binding-consistency guard
|
|
183
|
+
|
|
184
|
+
Read `binding_guard:` from `.work/CONVENTIONS.md` (same read as `gates_for_release` in Phase 1).
|
|
185
|
+
Values: `warn` (default when the key is absent) | `halt` | `off`. The guard reports two classes of
|
|
186
|
+
finding (defined below): **CONFLICT** (always acted on) and **INCOMPLETE** (severity governed by the
|
|
187
|
+
`epic_cohesion` convention).
|
|
188
|
+
|
|
189
|
+
- `off` — skip all three checks; log one line: "binding-consistency guard is off (CONVENTIONS
|
|
190
|
+
binding_guard: off); skipping." Continue to Phase 4. The guard short-circuits here **before** any
|
|
191
|
+
gathering or walking — the CONVENTIONS read is the first thing the bash block does.
|
|
192
|
+
- `warn` — run all three checks; if findings are present, print the full report into the
|
|
193
|
+
conversation AND record it in the release file body under a `### Binding-consistency warnings`
|
|
194
|
+
subsection (so the record is durable), then **continue** the release flow. The record is
|
|
195
|
+
**replace-or-skip**: a re-invoke rewrites the existing subsection with the current report rather
|
|
196
|
+
than appending a second block (current state, not history — release-deploy is idempotent).
|
|
197
|
+
- `halt` — run all three checks; if any **acted-on** finding is present (every CONFLICT, plus
|
|
198
|
+
INCOMPLETEs only under `epic_cohesion: total`), report and stop. The user resolves and re-runs
|
|
199
|
+
`/agile-workflow:release-deploy <version>`. Informational INCOMPLETEs (under `phased`) never halt.
|
|
200
|
+
|
|
201
|
+
**Two report classes.** The guard's hard invariant is *no cross-version drift*; whether an epic
|
|
202
|
+
must ship *whole* is a project convention (`epic_cohesion`), so the two are split:
|
|
203
|
+
|
|
204
|
+
- **CONFLICT** — a child bound to a *different* version than its bound parent, or a done parent left
|
|
205
|
+
unbound while its children are bound. Always a genuine coherence violation; always acted on per
|
|
206
|
+
`binding_guard` mode.
|
|
207
|
+
- **INCOMPLETE** — an unbound child of a bound parent. This is legitimate phased delivery for some
|
|
208
|
+
projects and drift for others, so its severity is governed by **`epic_cohesion`** (read from
|
|
209
|
+
`.work/CONVENTIONS.md`): `phased` (default) treats INCOMPLETE entries as informational — listed in
|
|
210
|
+
the warn report, never counting toward a halt; `total` treats them as mismatches acted on per
|
|
211
|
+
`binding_guard` mode, exactly like CONFLICTs.
|
|
212
|
+
|
|
213
|
+
Projects that hold the stronger "epics ship whole" convention set `epic_cohesion: total` (and
|
|
214
|
+
typically `binding_guard: halt`); the defaults (`phased` + `warn`) surface drift without imposing
|
|
215
|
+
total cohesion — so `halt` stays usable for phased / multi-release epics.
|
|
216
|
+
|
|
217
|
+
Before any gate runs, walk every item bound to this release plus every done-but-unbound parent
|
|
218
|
+
across active/archive, and verify three invariants. Any acted-on finding halts (or warns, per
|
|
219
|
+
`binding_guard`) — do NOT rebind anything implicitly. The user must resolve any inconsistency
|
|
220
|
+
flagged at `halt` level and re-run `/agile-workflow:release-deploy <version>`.
|
|
221
|
+
|
|
222
|
+
Throughout this guard a **parent** is an item of kind epic OR feature: Check 1 walks children of
|
|
223
|
+
bound epics/features, and Checks 2/3 walk done-unbound epics/features directly, so a bound feature
|
|
224
|
+
whose stories drift cross-version is caught just as a bound epic's features are.
|
|
225
|
+
|
|
226
|
+
This guard catches drift where a child's review-pass bound it to the release but its parent (epic or
|
|
227
|
+
feature) was forgotten, or where a done parent's children were bound without the parent itself being
|
|
228
|
+
bound.
|
|
229
|
+
|
|
230
|
+
Note: the plugin's archive tier may hold **bodyless stubs** (when `terminal-tier retention:
|
|
231
|
+
delete-refs` is in effect). Stubs retain their frontmatter (`id`, `parent`, `stage`,
|
|
232
|
+
`release_binding`), so the parent-walk works identically for stubs and live items — read the
|
|
233
|
+
frontmatter fields to resolve parentage and binding regardless of whether a body is present.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
version=<version> # substitute the target release version here (single substitution point)
|
|
237
|
+
|
|
238
|
+
# Read the gate config FIRST so `off` short-circuits before any gathering or walking.
|
|
239
|
+
# (Reading after the loop would run every check and then discard the result, contradicting
|
|
240
|
+
# "off — skip all three checks".)
|
|
241
|
+
binding_guard=$(grep -m1 '^binding_guard:' .work/CONVENTIONS.md | awk '{print $2}')
|
|
242
|
+
binding_guard="${binding_guard:-warn}" # default: warn
|
|
243
|
+
epic_cohesion=$(grep -m1 '^epic_cohesion:' .work/CONVENTIONS.md | awk '{print $2}')
|
|
244
|
+
epic_cohesion="${epic_cohesion:-phased}" # default: phased (an epic may ship across releases)
|
|
245
|
+
|
|
246
|
+
if [ "$binding_guard" = "off" ]; then
|
|
247
|
+
echo "binding-consistency guard is off (CONVENTIONS binding_guard: off); skipping."
|
|
248
|
+
# Continue to Phase 4 — do NOT gather or walk anything.
|
|
249
|
+
return 0 2>/dev/null || exit 0
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# Gather all items bound to this release (auto-widens to active + archive tiers).
|
|
253
|
+
# Exclude the release orchestration item itself.
|
|
254
|
+
bound_items=$(.work/bin/work-view --release "$version" --paths \
|
|
255
|
+
| xargs grep -lm1 'kind: \(epic\|feature\|story\)' 2>/dev/null)
|
|
256
|
+
|
|
257
|
+
# ONLY when the agentic-research plugin is installed, additionally apply Phase 3's [research]
|
|
258
|
+
# exclusion here too (research engagements never bind; defensively drop any that slipped into
|
|
259
|
+
# the bind set, e.g. bound before the plugin was adopted). Without the plugin, [research] is
|
|
260
|
+
# an inert project tag — its items bind normally and MUST be walked like any other item.
|
|
261
|
+
agentic_research_installed=false
|
|
262
|
+
if [ -d ".research" ] ||
|
|
263
|
+
[ -f "plugins/agentic-research/skills/research-orchestrator/SKILL.md" ] ||
|
|
264
|
+
[ -f ".agents/skills/research-orchestrator/SKILL.md" ] ||
|
|
265
|
+
[ -f ".claude/skills/research-orchestrator/SKILL.md" ]; then
|
|
266
|
+
agentic_research_installed=true
|
|
267
|
+
fi
|
|
268
|
+
if [ "$agentic_research_installed" = true ]; then
|
|
269
|
+
research_paths=$(.work/bin/work-view --scope all --tag research --paths | sort)
|
|
270
|
+
bound_items=$(printf '%s\n' $bound_items | while IFS= read -r p; do
|
|
271
|
+
[ -n "$p" ] || continue
|
|
272
|
+
printf '%s\n' "$research_paths" | grep -qxF "$p" || echo "$p"
|
|
273
|
+
done)
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
# Two report classes, split by severity (the guard's hard invariant is no-cross-version-drift;
|
|
277
|
+
# whether an epic must ship *whole* is a project convention, carried by epic_cohesion):
|
|
278
|
+
# conflicts[] — CONFLICT: a child bound to a DIFFERENT version than its bound parent, or a
|
|
279
|
+
# done parent unbound while its children are bound. Always a genuine coherence
|
|
280
|
+
# violation; always acted on per binding_guard mode.
|
|
281
|
+
# incompletes[] — INCOMPLETE: an unbound child of a bound parent. Legitimate phased delivery
|
|
282
|
+
# under epic_cohesion: phased (informational only); a mismatch under
|
|
283
|
+
# epic_cohesion: total (acted on per binding_guard mode, like a CONFLICT).
|
|
284
|
+
conflicts=()
|
|
285
|
+
incompletes=()
|
|
286
|
+
|
|
287
|
+
# Note on search scope: the parent/child walks grep `.work/active .work/archive` only and
|
|
288
|
+
# deliberately exclude `.work/releases`. Collapsed release items are not valid parents under
|
|
289
|
+
# either retention model (delete-refs prunes to a single summary doc; retain-bodies keeps bodies
|
|
290
|
+
# but the items are terminal), so a `parent:` pointing into releases/ is an orphan by construction
|
|
291
|
+
# and would never resolve a live binding here.
|
|
292
|
+
|
|
293
|
+
for item_path in $bound_items; do
|
|
294
|
+
kind=$(grep -m1 '^kind:' "$item_path" | awk '{print $2}')
|
|
295
|
+
item_id=$(grep -m1 '^id:' "$item_path" | awk '{print $2}')
|
|
296
|
+
item_binding=$(grep -m1 '^release_binding:' "$item_path" | awk '{print $2}')
|
|
297
|
+
item_stage=$(grep -m1 '^stage:' "$item_path" | awk '{print $2}')
|
|
298
|
+
parent_id=$(grep -m1 '^parent:' "$item_path" | awk '{print $2}')
|
|
299
|
+
|
|
300
|
+
# Check 1: children of a bound parent (epic OR feature) are consistent with it.
|
|
301
|
+
# Walk this item's children (items whose parent: field matches this item's id).
|
|
302
|
+
if [ "$kind" = "epic" ] || [ "$kind" = "feature" ]; then
|
|
303
|
+
# Find all children of this parent across active and archive tiers (see scope note above).
|
|
304
|
+
while IFS= read -r child_path; do
|
|
305
|
+
child_binding=$(grep -m1 '^release_binding:' "$child_path" | awk '{print $2}')
|
|
306
|
+
child_id=$(grep -m1 '^id:' "$child_path" | awk '{print $2}')
|
|
307
|
+
if [ "$child_binding" != "$version" ] && [ "$child_binding" != "null" ] && [ "$child_binding" != "" ]; then
|
|
308
|
+
# CONFLICT: child bound to a different version than its bound parent.
|
|
309
|
+
conflicts+=("CONFLICT — child $child_id is bound to $child_binding, but its parent $kind $item_id is bound to $version")
|
|
310
|
+
elif [ "$child_binding" = "null" ] || [ "$child_binding" = "" ]; then
|
|
311
|
+
# INCOMPLETE: unbound child of a bound parent. Severity depends on epic_cohesion.
|
|
312
|
+
incompletes+=("INCOMPLETE — child $child_id (unbound) has parent $kind $item_id bound to $version; under epic_cohesion: total all children of a bound parent must share the same release binding")
|
|
313
|
+
fi
|
|
314
|
+
done < <(grep -rl "^parent: ${item_id}$" .work/active .work/archive 2>/dev/null)
|
|
315
|
+
fi
|
|
316
|
+
done
|
|
317
|
+
|
|
318
|
+
# Checks 2 & 3: a done parent (epic OR feature) left unbound while any child is bound is itself a
|
|
319
|
+
# CONFLICT (orphan risk). These parents are NOT in `bound_items` by definition, so walk all active
|
|
320
|
+
# and archived epics/features directly. The two invariants share one predicate — done + unbound +
|
|
321
|
+
# a bound child — so emit a single CONFLICT per parent (no double-report).
|
|
322
|
+
while IFS= read -r parent_path; do
|
|
323
|
+
kind=$(grep -m1 '^kind:' "$parent_path" | awk '{print $2}')
|
|
324
|
+
item_id=$(grep -m1 '^id:' "$parent_path" | awk '{print $2}')
|
|
325
|
+
item_binding=$(grep -m1 '^release_binding:' "$parent_path" | awk '{print $2}')
|
|
326
|
+
item_stage=$(grep -m1 '^stage:' "$parent_path" | awk '{print $2}')
|
|
327
|
+
|
|
328
|
+
[ "$item_stage" = "done" ] || continue
|
|
329
|
+
{ [ "$item_binding" = "null" ] || [ -z "$item_binding" ]; } || continue
|
|
330
|
+
|
|
331
|
+
# Look for any child that is bound to this release (see scope note above).
|
|
332
|
+
if grep -rl "^parent: ${item_id}$" .work/active .work/archive 2>/dev/null \
|
|
333
|
+
| xargs grep -lm1 "^release_binding: ${version}$" 2>/dev/null | grep -q .; then
|
|
334
|
+
conflicts+=("CONFLICT — $kind $item_id is at stage: done and unbound while its children are bound to $version (orphan risk); bind the $kind to $version before proceeding")
|
|
335
|
+
fi
|
|
336
|
+
done < <(grep -rl '^kind: \(epic\|feature\)$' .work/active .work/archive 2>/dev/null)
|
|
337
|
+
|
|
338
|
+
# Severity assembly. CONFLICTs always count toward halt/warn. INCOMPLETEs count only under
|
|
339
|
+
# epic_cohesion: total; under phased they are informational and never trigger a halt.
|
|
340
|
+
acted=("${conflicts[@]}")
|
|
341
|
+
informational=()
|
|
342
|
+
if [ "$epic_cohesion" = "total" ]; then
|
|
343
|
+
acted+=("${incompletes[@]}")
|
|
344
|
+
else
|
|
345
|
+
informational=("${incompletes[@]}")
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
if [ "${#acted[@]}" -gt 0 ] || [ "${#informational[@]}" -gt 0 ]; then
|
|
349
|
+
report="BINDING CONSISTENCY — release $version (epic_cohesion: $epic_cohesion):"
|
|
350
|
+
for m in "${acted[@]}"; do
|
|
351
|
+
report+=$'\n'" • $m"
|
|
352
|
+
done
|
|
353
|
+
for m in "${informational[@]}"; do
|
|
354
|
+
report+=$'\n'" • $m [informational under epic_cohesion: phased]"
|
|
355
|
+
done
|
|
356
|
+
report+=$'\n'
|
|
357
|
+
report+="Resolve each CONFLICT manually (edit release_binding or parent frontmatter as appropriate),"
|
|
358
|
+
report+=$'\n'"then re-run: /agile-workflow:release-deploy $version"
|
|
359
|
+
|
|
360
|
+
if [ "$binding_guard" = "halt" ] && [ "${#acted[@]}" -gt 0 ]; then
|
|
361
|
+
# halt only on acted-on entries (CONFLICTs always; INCOMPLETEs only under total).
|
|
362
|
+
echo "BINDING CONSISTENCY FAILURES (halt) — $report"
|
|
363
|
+
exit 1
|
|
364
|
+
else
|
|
365
|
+
# warn (or halt with only informational INCOMPLETEs): surface in conversation + record durably.
|
|
366
|
+
echo "BINDING CONSISTENCY WARNINGS (warn) — $report"
|
|
367
|
+
# Replace-or-skip the durable record so re-invokes don't accumulate duplicate subsections
|
|
368
|
+
# (release-deploy is idempotent — current state, not history).
|
|
369
|
+
release_file=.work/active/release-"$version".md
|
|
370
|
+
subsection="### Binding-consistency warnings"
|
|
371
|
+
new_block=$(printf '%s\n\n%s\n' "$subsection" "$report")
|
|
372
|
+
if grep -qF "$subsection" "$release_file"; then
|
|
373
|
+
# Rewrite the existing subsection in place: drop the old block (from its heading up to the
|
|
374
|
+
# next "### "/"## " heading or EOF) and splice the current report at the same spot.
|
|
375
|
+
awk -v block="$new_block" '
|
|
376
|
+
$0 == "### Binding-consistency warnings" { skip=1; print block; next }
|
|
377
|
+
skip && /^#{2,3} / && $0 != "### Binding-consistency warnings" { skip=0 }
|
|
378
|
+
!skip { print }
|
|
379
|
+
' "$release_file" > "$release_file.tmp" && mv "$release_file.tmp" "$release_file"
|
|
380
|
+
else
|
|
381
|
+
printf '\n%s\n' "$new_block" >> "$release_file"
|
|
382
|
+
fi
|
|
383
|
+
fi
|
|
384
|
+
fi
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Act on the result per `binding_guard`:
|
|
388
|
+
|
|
389
|
+
- **`off`**: the bash block short-circuits at its first step (the CONVENTIONS read) before gathering
|
|
390
|
+
or walking anything; log one line and continue to Phase 4.
|
|
391
|
+
- **`halt`**: if any acted-on finding is present (every CONFLICT, plus INCOMPLETEs only under
|
|
392
|
+
`epic_cohesion: total`), stop the release flow. Do not proceed to gates. Do not rebind anything.
|
|
393
|
+
The user is the only actor who can resolve a binding inconsistency. Informational INCOMPLETEs
|
|
394
|
+
(under `epic_cohesion: phased`) are recorded but do not halt.
|
|
395
|
+
- **`warn`** (default): if any finding is present, print the report into the conversation AND record
|
|
396
|
+
it in the release body (replace-or-skip the `### Binding-consistency warnings` subsection rather
|
|
397
|
+
than appending a duplicate), then continue to Phase 4. Do not rebind anything.
|
|
398
|
+
|
|
399
|
+
### Phase 4: Gate execution
|
|
400
|
+
|
|
401
|
+
**Gates run over every bound non-release item, including late-bound archived stubs.** Archived stubs
|
|
402
|
+
are `done`, but release binding may happen long after the item left active work. The gate phase MUST
|
|
403
|
+
re-examine those stubs for the release bundle. A gate that needs an archived item's body must
|
|
404
|
+
recover the historical full body from the stub's `git_ref` (trying the archive path and former
|
|
405
|
+
active/backlog paths for the item id) rather than treating the pruned stub body as missing evidence.
|
|
406
|
+
|
|
407
|
+
Each gate enforces this itself: gates build their bundle from
|
|
408
|
+
`work-view --release <version> --paths` (which auto-widens to all tiers), ignore only the release
|
|
409
|
+
orchestration item (`kind: release`), and include active done items plus archived stubs. release-deploy
|
|
410
|
+
does not pass a pre-filtered set to the gates.
|
|
411
|
+
|
|
412
|
+
If the release is at `stage: quality-gate`:
|
|
413
|
+
|
|
414
|
+
For each gate in `gates_for_release` order, invoke it via the `Skill` tool (or
|
|
415
|
+
spawn a sub-agent that invokes it):
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
Skill(skill="agile-workflow:gate-<name>", args="<version>")
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Each gate is idempotent — re-running doesn't duplicate items. If a gate has
|
|
422
|
+
already run and the items it produced are at `stage: done`, the gate confirms
|
|
423
|
+
"no new findings" and moves on.
|
|
424
|
+
|
|
425
|
+
After each gate, append to the release body:
|
|
426
|
+
|
|
427
|
+
```markdown
|
|
428
|
+
### Gate runs
|
|
429
|
+
- **gate-security** (YYYY-MM-DD) — N findings (3 critical, 2 high, 5 medium)
|
|
430
|
+
- **gate-tests** (YYYY-MM-DD) — M coverage gaps (4 critical, 6 medium)
|
|
431
|
+
- ...
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Phase 5: Wait for readiness
|
|
435
|
+
|
|
436
|
+
Readiness condition: **every item with `release_binding: <version>` is at
|
|
437
|
+
`stage: done`.** This spans active done items and archived stubs alike (both carry the
|
|
438
|
+
binding); `work-view --release <version>` finds both tiers.
|
|
439
|
+
|
|
440
|
+
Archived stubs are `stage: done` by construction and their bodies are pruned — readiness checks only
|
|
441
|
+
their `stage`, never body presence. A missing stub body NEVER counts as pending and never blocks a
|
|
442
|
+
release.
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# Items still active
|
|
446
|
+
.work/bin/work-view --release <version> --paths | while read p; do
|
|
447
|
+
s=$(grep -m1 '^stage:' "$p" | awk '{print $2}')
|
|
448
|
+
[[ "$s" != "done" ]] && echo "PENDING: $p ($s)"
|
|
449
|
+
done
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
If pending items remain, halt with the list and:
|
|
453
|
+
|
|
454
|
+
> "Release `<version>` is not ready. The following items are still in flight:
|
|
455
|
+
> [list]. Drive these to done (via `/agile-workflow:implement`,
|
|
456
|
+
> `/agile-workflow:review`, etc.), then re-run `/agile-workflow:release-deploy
|
|
457
|
+
> <version>` to resume."
|
|
458
|
+
|
|
459
|
+
`release-deploy` is idempotent — the user runs it again later and you pick up
|
|
460
|
+
where you left off.
|
|
461
|
+
|
|
462
|
+
### Phase 5.5: Draft the changelog entry
|
|
463
|
+
|
|
464
|
+
Before shipping, draft a CHANGELOG.md entry from the bound items + their commits:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
# Commits since the last tag — all commits when no tag exists (an empty $prior drops the
|
|
468
|
+
# range; the old `$(git describe ...)..HEAD || git log` form never fired its fallback,
|
|
469
|
+
# because `..HEAD` with an empty left side is a valid empty range that exits 0). The
|
|
470
|
+
# `|| true` keeps the no-tag assignment from aborting under `set -e`.
|
|
471
|
+
prior=$(git describe --tags --abbrev=0 2>/dev/null || true)
|
|
472
|
+
git log --oneline ${prior:+${prior}..}HEAD
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Group changes:
|
|
476
|
+
- **Features** — new capabilities (look for items with no specialized tag)
|
|
477
|
+
- **Fixes** — bug fixes (items with `tags: [bug]` or from `/agile-workflow:fix`)
|
|
478
|
+
- **Refactor** — items with `tags: [refactor]`
|
|
479
|
+
- **Performance** — items with `tags: [perf]`
|
|
480
|
+
- **Security** — items with `gate_origin: security`
|
|
481
|
+
- **Documentation** — items with `gate_origin: docs` or `tags: [documentation]`
|
|
482
|
+
- **Internal** — anything else worth noting; otherwise omit
|
|
483
|
+
|
|
484
|
+
Use concise bullets — one per logical change, not per commit. Omit noise:
|
|
485
|
+
version-bump commits, merge commits, typo/formatting-only commits.
|
|
486
|
+
|
|
487
|
+
Format the entry header as `## v<version>` and prepend to CHANGELOG.md (preserve
|
|
488
|
+
existing content). If CHANGELOG.md doesn't exist, create it with this entry.
|
|
489
|
+
|
|
490
|
+
Show the user the drafted changelog and ask via structured question tool: "Does this look
|
|
491
|
+
correct? Reply yes to proceed with shipping, or provide edits." Apply edits and
|
|
492
|
+
re-confirm before shipping.
|
|
493
|
+
|
|
494
|
+
### Phase 6: Ship per release mapping
|
|
495
|
+
|
|
496
|
+
When all bound items are `done` AND the changelog entry is confirmed:
|
|
497
|
+
|
|
498
|
+
#### Mapping: `tag-based`
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
# 1. Find the version source (package.json / Cargo.toml / pyproject.toml / version.txt / etc.)
|
|
502
|
+
# 2. Update version
|
|
503
|
+
# 3. Find the project's release script (scripts/release*, package.json scripts,
|
|
504
|
+
# Makefile target, etc.)
|
|
505
|
+
# 4. Run the project's release script (which typically tags + pushes + triggers CI)
|
|
506
|
+
# Or, manually: git tag <version> && git push origin <version>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### Mapping: `branch-held`
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
# Items implement on feature branches; release-deploy merges bound PRs
|
|
513
|
+
gh pr list --search "<id>" --json number,headRefName # for each bound item
|
|
514
|
+
gh pr merge <pr-number> --squash --delete-branch
|
|
515
|
+
# Then trigger the project's release CI as usual
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### Mapping: `release-branch`
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# Long-running 'next' branch holds work; cut by merging next -> main -> tag
|
|
522
|
+
git checkout main && git merge next --ff-only
|
|
523
|
+
git tag <version> && git push origin main <version>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Mapping: `none`
|
|
527
|
+
|
|
528
|
+
No tag, branch, merge, push, or version bump happens inside release-deploy.
|
|
529
|
+
Proceed directly to archiving after readiness and changelog confirmation.
|
|
530
|
+
Record the external publishing mechanism in the release summary.
|
|
531
|
+
|
|
532
|
+
If the mapping requires user action (CI credentials, manual confirmation), pause
|
|
533
|
+
and prompt.
|
|
534
|
+
|
|
535
|
+
### Phase 7: Collapse into one summary; dispose bodies per retention
|
|
536
|
+
|
|
537
|
+
Both retention modes produce the same single summary doc; they differ only in step 4's body
|
|
538
|
+
disposition (the Phase 1 `terminal-tier retention` read; SPEC §Terminal-tier retention).
|
|
539
|
+
|
|
540
|
+
Under `delete-refs` (default): bound items do **not** move to `.work/releases/<version>/` as
|
|
541
|
+
bodies. Collapse them into a single summary doc and `git rm` their bodies — git history retains
|
|
542
|
+
the full content. Terminal prose never persists on disk (it carries zero design authority; see
|
|
543
|
+
the "Zero Design Authority" convention).
|
|
544
|
+
|
|
545
|
+
Under `retain-bodies` (the legacy opt-out): the summary doc and shipped-items table are still
|
|
546
|
+
produced, but full bodies stay on disk — kept under `.work/archive/` and
|
|
547
|
+
`.work/releases/<version>/` — and nothing is pruned.
|
|
548
|
+
|
|
549
|
+
1. Collect every item with `release_binding: <version>` — active done items AND archived stubs
|
|
550
|
+
(`work-view --release <version>` spans both tiers):
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
.work/bin/work-view --release <version> --paths
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
2. Resolve each item's git ref (the commit where its full body can be recovered, at the item's
|
|
557
|
+
former active path) and its `archived_atop`:
|
|
558
|
+
- archived item → reuse its `git_ref:` and `archived_atop:` frontmatter fields (under
|
|
559
|
+
`retain-bodies` the body is also still on disk; if no `git_ref` was stamped, record `—`).
|
|
560
|
+
- active done item → `git_ref=$(git rev-parse --short HEAD)` (the body is present at HEAD,
|
|
561
|
+
before Phase 7's disposition commit). It has no `archived_atop` (it was bound directly,
|
|
562
|
+
never archived); record `—` in that column.
|
|
563
|
+
|
|
564
|
+
3. Turn the release file into the single summary doc — move it and append a shipped-items table:
|
|
565
|
+
|
|
566
|
+
```bash
|
|
567
|
+
mkdir -p .work/releases/<version>
|
|
568
|
+
git mv .work/active/release-<version>.md .work/releases/<version>/release-<version>.md
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
Append to its body:
|
|
572
|
+
|
|
573
|
+
```markdown
|
|
574
|
+
## Shipped items
|
|
575
|
+
|
|
576
|
+
Bodies live in git history (delete-refs) or on disk (retain-bodies) — `git show
|
|
577
|
+
<git ref>:<former active path>` recovers any pruned body.
|
|
578
|
+
|
|
579
|
+
| id | title | kind | archived_atop | git ref |
|
|
580
|
+
|----|-------|------|---------------|---------|
|
|
581
|
+
| <id> | <title> | <kind> | <release \| pre-release \| —> | <git_ref> |
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
The `archived_atop` column records the baseline each late-bound archived stub was done atop
|
|
585
|
+
(`—` for active items bound directly). This is the durable trace of the late-binding query.
|
|
586
|
+
|
|
587
|
+
4. Dispose of each bound item body per retention (never the release summary):
|
|
588
|
+
|
|
589
|
+
Under `delete-refs` (default) — prune:
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
git rm .work/active/<kind>s/<id>.md # active done items
|
|
593
|
+
git rm .work/archive/<id>.md # archived stubs
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
The release folder ends holding exactly one file: `release-<version>.md`.
|
|
597
|
+
|
|
598
|
+
Under `retain-bodies` — keep full bodies on disk; nothing is pruned:
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
git mv .work/active/<kind>s/<id>.md .work/releases/<version>/ # active done items
|
|
602
|
+
# late-bound archived items keep their full bodies in place under .work/archive/
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
The release folder ends holding the summary plus the directly-bound items' bodies; the
|
|
606
|
+
shipped-items table is still appended (the `archived_atop` provenance applies under both
|
|
607
|
+
modes; the git-ref column records what step 2 resolved).
|
|
608
|
+
|
|
609
|
+
### Phase 8: Advance to released
|
|
610
|
+
|
|
611
|
+
Edit the release summary's frontmatter: `stage: quality-gate → released`.
|
|
612
|
+
|
|
613
|
+
Ensure its body records (alongside the Phase 7 shipped-items table):
|
|
614
|
+
- Date shipped
|
|
615
|
+
- Mapping used
|
|
616
|
+
- Total items shipped
|
|
617
|
+
- Gate finding totals
|
|
618
|
+
- The external publishing mechanism (for `none` mapping)
|
|
619
|
+
|
|
620
|
+
### Phase 9: Commit
|
|
621
|
+
|
|
622
|
+
Phase 7's body disposition (`git rm`s or `git mv`s) is already staged; stage the summary and the
|
|
623
|
+
changelog and commit:
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
git add .work/releases/<version>/ CHANGELOG.md <version-file>
|
|
627
|
+
git commit -m "release-deploy: <version> shipped (<N> items)"
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
The commit captures both the one summary doc and the body disposition. For tag-based mappings,
|
|
631
|
+
the project's release script handles its own commits. Don't double-commit.
|
|
632
|
+
|
|
633
|
+
## Output
|
|
634
|
+
|
|
635
|
+
In conversation:
|
|
636
|
+
- **Release**: `<version>` shipped
|
|
637
|
+
- **Items**: count, listed
|
|
638
|
+
- **Gates run**: list with finding counts per gate
|
|
639
|
+
- **Mapping**: tag-based / branch-held / release-branch / none
|
|
640
|
+
- **Next**: bound items collapsed into `.work/releases/<version>/release-<version>.md`; full
|
|
641
|
+
bodies in git history (`delete-refs`) or kept on disk (`retain-bodies`)
|
|
642
|
+
|
|
643
|
+
If the run halted at readiness (pending items), output the pending list and the
|
|
644
|
+
re-run instruction.
|
|
645
|
+
|
|
646
|
+
## Guardrails
|
|
647
|
+
|
|
648
|
+
- `release-deploy` is idempotent. Safe to re-run at any stage. The release file's
|
|
649
|
+
frontmatter is the state.
|
|
650
|
+
- Gates produce items, not blocking errors. The release ships when all items
|
|
651
|
+
(bound + gate-produced) are `done`.
|
|
652
|
+
- Don't bypass gates. If `gates_for_release` lists 5 gates, run all 5. If a gate
|
|
653
|
+
fails to produce items, that's a finding (or "no new findings"), not a reason
|
|
654
|
+
to skip.
|
|
655
|
+
- Re-gate late-bound archived stubs. Release gates run over every bound non-release item, including
|
|
656
|
+
stubs that were archived before this release claimed them. A gate that needs body text hydrates it
|
|
657
|
+
from the stub's `git_ref`; a missing on-disk stub body must never block a release.
|
|
658
|
+
- Don't ship if any bound item is not `done`. Halt and surface the pending list. (Archived stubs are
|
|
659
|
+
`done` by construction; readiness checks their `stage`, not body presence.)
|
|
660
|
+
- Tag-based and release-branch mappings rely on the project's existing release
|
|
661
|
+
script. branch-held requires `gh` CLI for PR merges. none performs no
|
|
662
|
+
publishing action inside release-deploy.
|
|
663
|
+
- The release file moves to `.work/releases/<version>/` (becoming the single summary doc) ONLY when the
|
|
664
|
+
release is actually shipped. Until then it stays in `.work/active/`. Bound item bodies move there
|
|
665
|
+
only under `retain-bodies`; under `delete-refs` (default) they are pruned and live in git history.
|
|
666
|
+
- Stage transitions: `planned → quality-gate` happens at bind. `quality-gate →
|
|
667
|
+
released` happens at ship. Don't pre-populate.
|