devflow-kit 2.4.0 → 2.5.0

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.
Files changed (166) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +86 -18
  3. package/dist/agents/git.md +824 -0
  4. package/dist/cli/commands/agents.js +6 -1
  5. package/dist/cli/commands/attribution-prompts.js +1 -1
  6. package/dist/cli/commands/compliance-prompts.js +1 -1
  7. package/dist/cli/commands/compliance.js +23 -1
  8. package/dist/cli/commands/init-seed.js +24 -26
  9. package/dist/cli/commands/init.js +502 -71
  10. package/dist/cli/commands/install-report.js +205 -0
  11. package/dist/cli/commands/knowledge/index.js +2 -2
  12. package/dist/cli/commands/knowledge/toggle.js +27 -37
  13. package/dist/cli/commands/learning.js +37 -30
  14. package/dist/cli/commands/memory.js +79 -69
  15. package/dist/cli/commands/prompt-io.js +4 -4
  16. package/dist/cli/commands/security.js +76 -16
  17. package/dist/cli/commands/skills.js +53 -7
  18. package/dist/cli/commands/tracker-prompts.js +145 -0
  19. package/dist/cli/commands/tracker.js +405 -0
  20. package/dist/cli/commands/uninstall.js +211 -65
  21. package/dist/cli.js +2 -0
  22. package/dist/commands/bug-analysis.md +22 -4
  23. package/dist/commands/code-review.md +44 -15
  24. package/dist/commands/debug.md +20 -6
  25. package/dist/commands/dynamic-build.md +289 -67
  26. package/dist/commands/dynamic-plan.md +60 -21
  27. package/dist/commands/dynamic-profile.md +1 -1
  28. package/dist/commands/dynamic-tickets.md +58 -8
  29. package/dist/commands/explore.md +2 -2
  30. package/dist/commands/implement.md +241 -53
  31. package/dist/commands/plan.md +88 -17
  32. package/dist/commands/release.md +64 -17
  33. package/dist/commands/resolve.md +138 -58
  34. package/dist/commands/self-review.md +2 -2
  35. package/dist/core/agent-models.js +55 -12
  36. package/dist/core/assets.js +58 -2
  37. package/dist/core/evidence-policy.js +147 -0
  38. package/dist/core/feature-config.js +130 -64
  39. package/dist/core/feature-switch.js +112 -0
  40. package/dist/core/flags.js +4 -4
  41. package/dist/core/manifest.js +33 -7
  42. package/dist/core/mds-variants.js +861 -0
  43. package/dist/core/model-discovery.js +12 -1
  44. package/dist/core/plugins.js +357 -9
  45. package/dist/core/project-paths.js +1 -1
  46. package/dist/core/proxy-log.js +8 -6
  47. package/dist/core/proxy-state.js +11 -8
  48. package/dist/core/reference-sweep.js +136 -0
  49. package/dist/core/tracker.js +407 -0
  50. package/dist/skills/git/references/decision-markers.md +19 -0
  51. package/dist/skills/git/references/learn-conventions.md +56 -0
  52. package/dist/skills/git/references/pr/check-ci-status.md +14 -0
  53. package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
  54. package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
  55. package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
  56. package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
  57. package/dist/skills/git/references/pr/post-review-summary.md +42 -0
  58. package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
  59. package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
  60. package/dist/skills/git/references/pr/validate-branch.md +18 -0
  61. package/dist/skills/git/references/publication-gate.md +13 -0
  62. package/dist/skills/git/references/tracker/_mcp.md +153 -0
  63. package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
  64. package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
  65. package/dist/skills/git/references/tracker/github/create-release.md +11 -0
  66. package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
  67. package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
  68. package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
  69. package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
  70. package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
  71. package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
  72. package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
  73. package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
  74. package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
  75. package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
  76. package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
  77. package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
  78. package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
  79. package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
  80. package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
  81. package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
  82. package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
  83. package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
  84. package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
  85. package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
  86. package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
  87. package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
  88. package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
  89. package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
  90. package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
  91. package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
  92. package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
  93. package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
  94. package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
  95. package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
  96. package/dist/skills/git/references/trust-rule.md +7 -0
  97. package/dist/targets/claude-code/installer.js +1213 -31
  98. package/dist/targets/claude-code/legacy.js +5 -0
  99. package/dist/targets/claude-code/post-install.js +196 -74
  100. package/dist/targets/claude-code/tracker-install.js +161 -0
  101. package/package.json +4 -3
  102. package/src/assets/agents/code.md +42 -4
  103. package/src/assets/agents/design.md +1 -1
  104. package/src/assets/agents/git.mds +827 -0
  105. package/src/assets/agents/knowledge.md +1 -1
  106. package/src/assets/agents/learning.md +11 -0
  107. package/src/assets/agents/synthesize.md +1 -1
  108. package/src/assets/agents/test.md +16 -5
  109. package/src/assets/agents/tracker.md +467 -0
  110. package/src/assets/agents/validate.md +7 -5
  111. package/src/assets/commands/_partials/_engine.mds +11 -9
  112. package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
  113. package/src/assets/commands/_partials/_knowledge.mds +2 -2
  114. package/src/assets/commands/_partials/_plan_contract.mds +22 -7
  115. package/src/assets/commands/_partials/_preamble.mds +1 -1
  116. package/src/assets/commands/_partials/_publication.mds +3 -1
  117. package/src/assets/commands/_partials/_ticket_template.mds +3 -2
  118. package/src/assets/commands/_partials/_tracker.mds +18 -0
  119. package/src/assets/commands/_partials/_wave.mds +16 -10
  120. package/src/assets/commands/bug-analysis.mds +15 -5
  121. package/src/assets/commands/code-review.mds +34 -14
  122. package/src/assets/commands/debug.mds +11 -4
  123. package/src/assets/commands/dynamic-build.mds +227 -41
  124. package/src/assets/commands/dynamic-plan.mds +35 -13
  125. package/src/assets/commands/dynamic-tickets.mds +47 -5
  126. package/src/assets/commands/implement.mds +206 -52
  127. package/src/assets/commands/plan.mds +70 -17
  128. package/src/assets/commands/release.md +64 -17
  129. package/src/assets/commands/resolve.mds +126 -56
  130. package/src/assets/mds/git/_pr.mds +331 -0
  131. package/src/assets/mds/git/_references.mds +135 -0
  132. package/src/assets/mds/tracker/_common.mds +156 -0
  133. package/src/assets/mds/tracker/_github.mds +472 -0
  134. package/src/assets/mds/tracker/_jira.mds +407 -0
  135. package/src/assets/mds/tracker/_linear.mds +449 -0
  136. package/src/assets/mds/tracker/_mcp.mds +299 -0
  137. package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
  138. package/src/assets/scripts/hooks/background-memory-update +14 -9
  139. package/src/assets/scripts/hooks/capture-prompt +6 -2
  140. package/src/assets/scripts/hooks/capture-question +6 -2
  141. package/src/assets/scripts/hooks/capture-turn +6 -2
  142. package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
  143. package/src/assets/scripts/hooks/ensure-root-gitignore +161 -60
  144. package/src/assets/scripts/hooks/hook-log-init +3 -1
  145. package/src/assets/scripts/hooks/json-helper.cjs +223 -5
  146. package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -1
  147. package/src/assets/scripts/hooks/memory-worker +15 -8
  148. package/src/assets/scripts/hooks/pre-compact-memory +12 -8
  149. package/src/assets/scripts/hooks/preamble +1 -4
  150. package/src/assets/scripts/hooks/queue-append +68 -24
  151. package/src/assets/scripts/hooks/session-start-context +355 -8
  152. package/src/assets/scripts/hooks/session-start-memory +12 -8
  153. package/src/assets/scripts/pr-evidence.cjs +1961 -0
  154. package/src/assets/scripts/redact-secrets.cjs +490 -62
  155. package/src/assets/scripts/release-trace.cjs +1143 -0
  156. package/src/assets/scripts/resolve-evidence-policy.cjs +1065 -0
  157. package/src/assets/scripts/verify-evidence.cjs +1822 -0
  158. package/src/assets/skills/compliance/SKILL.md +2 -0
  159. package/src/assets/skills/docs-framework/SKILL.md +5 -3
  160. package/src/assets/skills/git/SKILL.md +8 -78
  161. package/src/assets/skills/git/references/github-api.md +179 -141
  162. package/src/assets/skills/git/references/patterns.md +11 -6
  163. package/src/assets/skills/review-methodology/SKILL.md +1 -1
  164. package/src/assets/skills/review-methodology/references/patterns.md +6 -61
  165. package/src/assets/skills/review-methodology/references/violations.md +14 -22
  166. package/src/assets/agents/git.md +0 -938
@@ -0,0 +1,472 @@
1
+ ---
2
+ output-dir: dist/skills/git/references
3
+ ---
4
+ @import "./_common.mds" as common
5
+
6
+ GitHub tracker mechanics for the `devflow:git` skill.
7
+
8
+ One section per tracker operation. The build emits each section as its own file
9
+ under `tracker/github/` inside the skill's `references/` directory; the op roster
10
+ and the sub-directory come from `VARIANT_MODULES` in `src/core/mds-variants.ts`,
11
+ and the two must agree in both directions or the build fails. Everything above
12
+ the first section marker is module-level prose and is emitted nowhere.
13
+
14
+ The op roster is the SAME exported list the other provider modules read, so
15
+ file-set parity across providers is a compile-time property rather than an
16
+ assertion: a provider cannot gain or lose an operation without every provider
17
+ moving with it. Define-set parity — one `@define` per op, named identically
18
+ across modules — is what the `cross-provider define-set parity` scan asserts
19
+ over all three, because a define name is visible to no type.
20
+
21
+ Each section states what the Git agent loads it for. An operation's contract —
22
+ its `**Input:**`, its `**Output:**` template and its `**Degradation (D4):**`
23
+ clause — is never restated here: that is the agent's, and a second copy outside
24
+ the single-authority corpus is the divergence this split exists to prevent.
25
+
26
+ Headings below each section's own anchor are `###` by grammar, not by taste: a
27
+ column-0 `## ` line outside a fence terminates the section for every guard that
28
+ reads it through `extractOpSectionFromCorpus`, and everything under it becomes
29
+ invisible while the bytes stay on disk (PF-063).
30
+
31
+ `_common.mds` is an ALIAS import (`as common`), as in the other two provider
32
+ modules. A SELECTIVE import deep-copies each named function into every `@define`
33
+ here: with four names selected this module measured ~320 ms against ~10 ms
34
+ aliased. An alias changes lookup, not expansion — the emitted bytes are
35
+ identical — and `tests/build-mds-compile-time.test.ts` holds the budget.
36
+
37
+ @define setup_task():
38
+ ## Operation: setup-task
39
+
40
+ Load when the resolved tracker provider is `github` and the operation is `setup-task`.
41
+
42
+ **Mechanics held here:** the `**Process:**` steps that talk to GitHub — issue lookup, branch-token rendering, and the conventions probe.
43
+
44
+ ### Process
45
+
46
+ 1. **`ISSUE_INPUT` pre-flight**, when provided: it must satisfy `^#?[1-9][0-9]\{0,8\}$`, anchored at both ends; strip one leading `#` — the digits are the issue number steps 1c and 3 use. Anything else ⇒ `TRACEABILITY: DEGRADED (issue reference "\{ref\}" does not match github reference grammar)`, and the task proceeds with no issue.
47
+ {common.conventions_step()}
48
+ 1c. Issue-first, only when `ISSUE_REQUIRED` is `true`: before branch derivation, ensure a GitHub issue exists for this task:
49
+ - Preconditions: remote reachable AND `gh` authenticated. If either fails → emit `TRACEABILITY: DEGRADED (\{reason\})` and continue to step 2 (convention still applies; no issue number is set).
50
+ - If `ISSUE_INPUT` was provided, step 1 alone decides the number.
51
+ - Otherwise: invoke `ensure-traceable-issue` with `TASK_DESCRIPTION` (and `PLAN_ARTIFACT_PATH` if provided) to create or find an issue. Capture the returned issue number.
52
+ - Issue number drives the branch name in step 3: `\{type\}/\{number\}-\{slug\}`.
53
+ {common.branch_detection_step()}
54
+ 3. **Derive branch name** (using detected convention):
55
+ - If issue number is known (from step 1 or 1c): fetch issue via GitHub API, then derive branch name as `\{type\}/\{number\}-\{slug\}` where:
56
+ - `type` is inferred from issue labels: `bug` → `fix`, `documentation` or `docs` → `docs`, `refactor` → `refactor`, `chore` or `maintenance` → `chore`, default → `feature`
57
+ - `slug` is the issue title: lowercased, non-alphanumeric replaced with hyphens, consecutive hyphens collapsed, trimmed, max 40 characters
58
+ - Before placing fetched content in the output, neutralise any `</untrusted-issue-body>` in it (Principle 8 marker neutralisation).
59
+ - If `TASK_DESCRIPTION` provided (no issue): infer type from description keywords (e.g., "fix login bug" → `fix`, "refactor auth" → `refactor`, "add JWT" → `feature`, "update docs" → `docs`, "chore: cleanup" → `chore`), then slugify description as `\{type\}/\{slug\}` (max 40 chars)
60
+ - If neither: fallback to `task-\{YYYY-MM-DD_HHMM\}`
61
+
62
+ {common.handoff_values("`{n}` (bare, never `#{n}`)", "Closes #{n}")}
63
+ @end
64
+
65
+ @define fetch_issue():
66
+ ## Operation: fetch-issue
67
+
68
+ Load when the resolved tracker provider is `github` and the operation is `fetch-issue`.
69
+
70
+ **Mechanics held here:** the `**Process:**` body — single-issue lookup and the field projection it requests.
71
+
72
+ ### Process
73
+
74
+ 1b. **Ref pre-flight.** The numeric path is taken only when `ISSUE_INPUT` satisfies `^#?[1-9][0-9]\{0,8\}$` — this provider's anchored reference grammar, stated with its strip-one-leading-`#` normalisation and the shell-comment reason it exists for in this operation's sibling `backlink-shipped-issues` reference. Interpolate only the digits that survive the strip. Anything the grammar rejects is a SEARCH TERM and takes the text path, so it never reaches a command.
75
+ 2. Fetch full issue data (title, body, labels, assignees, milestone, comments)
76
+ 3. Extract acceptance criteria and dependencies from body; neutralise any `</untrusted-issue-body>` in the body before wrapping (Principle 8 marker neutralisation).
77
+
78
+ {common.handoff_values("`{n}` (bare, never `#{n}`)", "Closes #{n}")}
79
+
80
+ ### Fetch Issue with All Details
81
+
82
+ ```bash
83
+ gh issue view "$ISSUE_NUMBER" \
84
+ --json number,title,body,state,labels,assignees,milestone,author,createdAt,comments
85
+ ```
86
+
87
+ ### Extract Issue Data
88
+
89
+ ```bash
90
+ BODY=$(gh issue view "$ISSUE" --json body -q '.body')
91
+
92
+ # Extract acceptance criteria
93
+ CRITERIA=$(printf '%s\n' "$BODY" | tr -d '\r' | awk 'tolower($0) ~ /^##[[:blank:]]*acceptance criteria:?[[:blank:]]*$/ {f=1; next} /^##[[:blank:]]/ {f=0} f' | grep -E '^[[:space:]]*([-*]|[0-9]+[.)])[[:space:]]+[^[:space:]]' || true)
94
+
95
+ # Extract dependencies
96
+ DEPENDS_ON=$(echo "$BODY" | grep -oE '(depends on|blocked by) #[0-9]+' | grep -oE '#[0-9]+' || true)
97
+ ```
98
+ @end
99
+
100
+ @define fetch_issues_batch():
101
+ ## Operation: fetch-issues-batch
102
+
103
+ Load when the resolved tracker provider is `github` and the operation is `fetch-issues-batch`.
104
+
105
+ **Mechanics held here:** the `**Process:**` body — the single bounded batch query and the reporting of references it could not resolve.
106
+
107
+ ### Process
108
+
109
+ 2. Fetch all issues in a **single** GraphQL query using per-issue aliases (dynamically constructed for the resolved list); resolve owner/repo from the git remote context:
110
+ ```
111
+ gh api graphql -f query='query \{ repository(owner:"OWNER", name:"REPO") \{
112
+ i1: issue(number:N1) \{ number title state body labels(first:10)\{nodes\{name\}\} assignees(first:5)\{nodes\{login\}\} milestone\{title\} \}
113
+ i2: issue(number:N2) \{ number title state body labels(first:10)\{nodes\{name\}\} assignees(first:5)\{nodes\{login\}\} milestone\{title\} \}
114
+ ...
115
+ \}\}'
116
+ ```
117
+ {common.state_batch_line("`state` (`OPEN` or `CLOSED`)", "`state`", "{ISSUE_REF}")}
118
+ @end
119
+
120
+ @define manage_debt():
121
+ ## Operation: manage-debt
122
+
123
+ Load when the resolved tracker provider is `github` and the operation is `manage-debt`.
124
+
125
+ **Mechanics held here:** the `**Process:**` body — locating the rolling tech-debt item, creating it when absent, and updating its description.
126
+
127
+ ### Process
128
+
129
+ 1. Find or create "Tech Debt Backlog" issue with `tech-debt` label
130
+ 2. Check issue body size; archive if > 60000 chars (per devflow:git)
131
+ 3. Extract items to add:
132
+ - `## Fix Separately` entries from `\{REVIEW_DIR\}/resolution-summary.md` (FIX_SEPARATE from Triage agent)
133
+ - `## Deferred to Tech Debt` entries from `\{REVIEW_DIR\}/resolution-summary.md` (TECH_DEBT from Triage agent)
134
+ - Pre-existing issues (Category 3) from review reports
135
+ 4. Deduplicate against existing items using semantic matching
136
+ 5. Remove items that have been fixed (verify in codebase)
137
+ 6. Compose updated issue body to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) and post via `gh issue edit \{number\} --body-file "$DEVFLOW_BODY"`
138
+ 7. Return the backlog issue number for Tracked field backfill in resolution-summary.md
139
+
140
+ ### Tech Debt Issue Management
141
+
142
+ Every body below reaches GitHub through `$DEVFLOW_BODY`, the file the D11 scrub chain
143
+ produced — manage-debt is a body-posting op, so the scrub is unconditional. Each post
144
+ therefore writes ITS OWN content to `$DEVFLOW_BODY_RAW` first: `$DEVFLOW_BODY` is the
145
+ scrubber's output, not a shared mailbox, and posting it without composing into
146
+ `$DEVFLOW_BODY_RAW` in the same step publishes whatever the last scrub happened to leave.
147
+
148
+ ```bash
149
+ MAX_SIZE=60000
150
+
151
+ post_scrubbed() {
152
+ # Compose → scrub → post, chained with && from the FIRST link: the compose is
153
+ # inside the chain, so a failed write stops the post instead of letting the
154
+ # scrubber scrub — and the chain publish — whatever the RAW file last held.
155
+ # Never a pipeline: a pipeline's exit status hides a scrubber crash (fail-open).
156
+ printf '%s\n' "$1" > "$DEVFLOW_BODY_RAW" \
157
+ && node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/redact-secrets.cjs" \
158
+ "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" \
159
+ && gh issue comment "$2" --body-file "$DEVFLOW_BODY"
160
+ }
161
+
162
+ add_tech_debt_item() {
163
+ local new_item="$1"
164
+ local current_body
165
+ # Items append to the BODY (Process step 6) — a comment would leave the body
166
+ # invariant, so the probe below could never fire and the archive successor would
167
+ # be unreachable. A failed read must stop: an empty body REPLACES the backlog.
168
+ current_body=$(gh issue view "$TECH_DEBT_ISSUE" --json body -q '.body') || return 1
169
+ local body_length=${#current_body}
170
+
171
+ if [ "$body_length" -gt "$MAX_SIZE" ]; then
172
+ echo "Tech debt issue approaching size limit, archiving..."
173
+ archive_tech_debt_issue
174
+ # The successor is a different issue with a different body; if the archive
175
+ # degraded, TECH_DEBT_ISSUE still names the predecessor and this returns
176
+ # what the first read did.
177
+ current_body=$(gh issue view "$TECH_DEBT_ISSUE" --json body -q '.body') || return 1
178
+ fi
179
+
180
+ # Same chain, same reason, as post_scrubbed — only the sink differs: `gh issue
181
+ # edit` replaces the whole body, so what is composed is the body just read plus
182
+ # the new item, under its trailing `## Items` heading.
183
+ printf '%s\n%s\n' "$current_body" "$new_item" > "$DEVFLOW_BODY_RAW" \
184
+ && node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/redact-secrets.cjs" \
185
+ "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" \
186
+ && gh issue edit "$TECH_DEBT_ISSUE" --body-file "$DEVFLOW_BODY"
187
+ }
188
+
189
+ archive_tech_debt_issue() {
190
+ local old_issue=$TECH_DEBT_ISSUE
191
+ local new_url
192
+ local new_number
193
+
194
+ # The successor's body is a posted body: compose, scrub, and create only on a
195
+ # clean scrubber exit. `gh issue create` prints the new issue's URL, so the
196
+ # number is its last path segment — parsed command output, checked to be a digit
197
+ # run before it becomes the issue every later post targets. One `&&` chain end to
198
+ # end, compose included — the archive comment names the real successor, and the
199
+ # close happens only after it lands. A failure anywhere reports and stops without
200
+ # returning non-zero: TECH_DEBT_ISSUE still names the still-open predecessor, so
201
+ # the caller's item lands there rather than being dropped.
202
+ printf '%s\n' "Continued from #${old_issue}
203
+
204
+ ## Items
205
+ " > "$DEVFLOW_BODY_RAW" \
206
+ && node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/redact-secrets.cjs" \
207
+ "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" \
208
+ && new_url=$(gh issue create \
209
+ --title "Tech Debt Backlog" \
210
+ --label "tech-debt" \
211
+ --body-file "$DEVFLOW_BODY") \
212
+ && new_number="${new_url##*/}" \
213
+ && [[ "$new_number" =~ ^[0-9]+$ ]] \
214
+ && TECH_DEBT_ISSUE="$new_number" \
215
+ && post_scrubbed "## Archived
216
+ This issue reached the size limit.
217
+ **Continued in:** #${TECH_DEBT_ISSUE}" "$old_issue" \
218
+ && gh issue close "$old_issue" \
219
+ || echo "TRACEABILITY: DEGRADED (tech-debt archive failed for #${old_issue})"
220
+ }
221
+ ```
222
+ @end
223
+
224
+ @define create_release():
225
+ ## Operation: create-release
226
+
227
+ Load when the resolved tracker provider is `github` and the operation is `create-release`.
228
+
229
+ **Mechanics held here:** the closed-issues step only. Tag creation, release creation and notes composition stay with the operation.
230
+
231
+ ### Process
232
+
233
+ Inside step 5 (compose release notes):
234
+
235
+ - If `SHIPPED_ISSUES` provided: append a `## Closed Issues` section with issue references — **first ≤50 issues** (the same bound `backlink-shipped-issues` applies); if truncated, add a final `…and \{n\} more issues` line (D4 degrade if enrichment fails)
236
+ @end
237
+
238
+ @define gather_release_evidence():
239
+ ## Operation: gather-release-evidence
240
+
241
+ Load when the resolved tracker provider is `github` and the operation is `gather-release-evidence`.
242
+
243
+ **Mechanics held here:** the last release tag, the closing-keyword rule, this provider's history grammar, resolving which issues the range's merged PRs close — one listing, with its bounded fallback — and the trace map.
244
+
245
+ ### Process
246
+
247
+ {common.last_release_tag_step()}
248
+ {common.closing_keyword_rule()}
249
+ 3b. **This provider's history grammar** is `^#[1-9][0-9]\{0,8\}$`. A bare number is not a reference here either: a keyword-anchored candidate must carry the `#`, and step 4 renders every number it reads from a merged PR as `#\{n\}` before step 5's gate.
250
+ 4. If `gh` is authenticated and remote is reachable, resolve which issues the range's merged PRs close — **one listing, never one call per commit** — and merge the result with the commit-message set:
251
+ - **Once, before the listing:** this repo's identity, `gh api 'repos/\{owner\}/\{repo\}' --jq '.owner.login + "/" + .name'`, and the tag's UTC date, `TAG_DATE=$(TZ=UTC git log -1 --date=format-local:%Y-%m-%d --format=%cd \{last_tag\})`, shape-gated `^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}$` — a local date can drop a PR merged just after the tag. If `TAG_DATE` fails this gate, treat it as **the listing fails** below: emit `TRACEABILITY: DEGRADED (\{reason\})` and go straight to that bullet's ≤25-PR fallback.
252
+ - **The listing:** `gh pr list --state merged --search "merged:>=$TAG_DATE" --limit 200 --json number,mergeCommit,closingIssuesReferences`; with no tag, omit `--search`.
253
+ - **Map locally, with no further call:** keep a listed PR when its `mergeCommit.oid` is in `git rev-list \{last_tag\}..HEAD`, or when a range commit's subject ends `(#N)` naming it. Keep only references whose `repository.owner.login`/`name` equal this repo's identity — any other ⇒ `TRACEABILITY: DEGRADED (foreign issue reference \{ref\})`. Render each kept number `#\{n\}`.
254
+ - **Coverage:** a range subject carries a PR marker (`(#N)` or `Merge pull request #N`) but no listed PR maps into the range ⇒ `TRACEABILITY: DEGRADED (merged-PR listing did not cover the range)`. A listing of exactly 200 ⇒ status `INDETERMINATE (merged-PR listing hit its 200 cap)`, returning what was collected.
255
+ - **The listing fails** (an older `gh` reports `Unknown JSON field`) ⇒ `TRACEABILITY: DEGRADED (\{reason\})`, then fall back to `gh pr view N --json closingIssuesReferences` over the PR numbers in `(#N)` / `Merge pull request #N` subjects — after a listing that succeeds, also over each range `(#N)` naming no listed PR — each N gated `^[1-9][0-9]\{0,8\}$`, filtered and rendered as above, bounded at ≤25 PRs; report the remainder as `THROTTLED (\{n\} not processed)` and never report the enrichment as complete while PRs went unresolved.
256
+ - On any 4xx → DEGRADED for that item, continue. On 5xx → 1 retry; still 5xx → DEGRADED for that item, continue. On the secondary rate limit of `### Provider signals (GitHub)` in this operation's `backlink-shipped-issues` reference → stop GitHub enrichment immediately, report remaining as `THROTTLED`.
257
+ {common.trace_map_step("--grammar github --traced-file \"$T\"", "In one shell: `trap 'rm -- \"$T\"' EXIT; T=\"$(mktemp)\"`, then one 40-hex SHA per line into `$T` — each range commit step 4 tied to a PR with ≥1 kept reference (its `mergeCommit.oid`, or a subject naming it). ")}
258
+ @end
259
+
260
+ @define backlink_shipped_issues():
261
+ ## Operation: backlink-shipped-issues
262
+
263
+ Load when the resolved tracker provider is `github` and the operation is `backlink-shipped-issues`.
264
+
265
+ **Mechanics held here:** the `**Process:**` body — the hoisted current-user lookup, the back-link post, and the inter-item throttle — and, because this is the tracker operation that owns the fan-out, GitHub's rate-limit and posting signals for the always-loaded D4 and D11 contracts.
266
+
267
+ ### Provider signals (GitHub)
268
+
269
+ The D4 degradation contract and the D11 comment-sink scrub state the rules; what they leave to the provider is the SIGNAL. These are GitHub's, for the tracker fan-out this operation owns.
270
+
271
+ - **Secondary rate limit:** a 403 or 429 response with a rate-limit body, or an `X-RateLimit-Remaining` header < 10. Continuing to issue requests into one extends GitHub's penalty window, which is why D4 says STOP rather than wait.
272
+ - **Backpressure rung:** `X-RateLimit-Remaining` < 50 — the point at which D4's inter-operation delay rises from 1s to 3s for the remainder of the batch.
273
+ - **Unavailability:** `gh` absent or unauthenticated, or no remote — D4's "no remote" condition on this provider.
274
+
275
+ **Scrub-then-post chain** — D11's `&&` discipline instantiated for GitHub. A pipeline's exit status would swallow a scrubber crash, so the chain is `&&` and never `|`; and D11's removal rule is armed before the `mktemp` that opens the chain, so the raw body outlives no path:
276
+
277
+ ```bash
278
+ trap 'GATE=$?; rm -- "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" 2>/dev/null; exit "$GATE"' EXIT INT TERM
279
+ node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/redact-secrets.cjs" "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" \
280
+ && gh issue comment {number} --body-file "$DEVFLOW_BODY"
281
+ ```
282
+
283
+ ### Process
284
+
285
+ **Ref pre-flight (the always-loaded entry gate, instantiated for this provider).** Every entry of `SHIPPED_ISSUES` must satisfy `^#?[1-9][0-9]\{0,8\}$`, anchored at both ends of the STRING (a newline fails it) — this provider's reference grammar is what the entry gate's shape requirement means here, and the anchored form is what keeps a reference out of the commands below. **Then normalise once, before the loop, never inside it:** strip **exactly one** leading `#` from every admitted entry (`#42` ≡ `42`) and interpolate only the stripped digits. The grammar admits both spellings because both are how a reference is written here, but a `#` at word start opens a shell comment — an un-stripped `#42` would truncate `gh issue view`, `gh issue comment` and every other command below at the reference, so the stripped form is the only one that reaches a command. **Drop** every entry that fails and report it as `TRACEABILITY: DEGRADED (issue reference "\{ref\}" does not match github reference grammar)`. If every entry is dropped, emit `TRACEABILITY: DEGRADED (no parseable refs for provider \{p\})`, post nothing, and **never report the status as `COMPLETE`**.
286
+
287
+ **Setup (once, before the loop):** Fetch viewer login: `gh api user --jq '.login'` → store as VIEWER_LOGIN
288
+
289
+ Then, per issue, within the operation's ≤50 bound:
290
+
291
+ 1. Fetch existing comments authored by the viewer: `gh issue view \{number\} --json comments --jq '[.comments[] | select(.author.login == "'"$VIEWER_LOGIN"'")] | .[].body'`
292
+ 2. Check if `<!-- devflow:shipped v\{BARE_VERSION\} -->` already present in viewer-authored comments. If yes: skip.
293
+ 3. Write the two-line body to `$DEVFLOW_BODY_RAW` — a real newline, not a `\n` escape (bash does not
294
+ expand `\n` inside double quotes, so an inline `--body` would post a single literal line):
295
+ ```
296
+ <!-- devflow:shipped v\{BARE_VERSION\} -->
297
+ This was shipped in v\{BARE_VERSION\}.
298
+ ```
299
+ Apply the Comment-sink scrub (D11) and post via `gh issue comment \{number\} --body-file "$DEVFLOW_BODY"`.
300
+ 4. Wait 1s between issues.
301
+ @end
302
+
303
+ @define associate_release():
304
+ ## Operation: associate-release
305
+
306
+ Load when the resolved tracker provider is `github` and the operation is `associate-release`.
307
+
308
+ **Mechanics held here:** the release milestone — created first, else found by a bounded walk — then one batched read and one batched assignment that never replaces a milestone.
309
+
310
+ ### Process
311
+
312
+ **Ref pre-flight (the always-loaded entry gate, instantiated for this provider).** Every entry of `SHIPPED_ISSUES` must satisfy `^#?[1-9][0-9]\{0,8\}$`, anchored at both ends of the STRING; strip exactly one leading `#` once, and interpolate only the digits. Drop each failure as `TRACEABILITY: DEGRADED (issue reference "\{ref\}" does not match github reference grammar)`. Every entry dropped ⇒ `TRACEABILITY: DEGRADED (no parseable refs for provider \{p\})`, no call, and **never report the status as `COMPLETE`**.
313
+
314
+ Steps 1, 3 and 4 each run in ONE shell that opens with `trap 'rm -- "$F"' EXIT; F="$(mktemp)"`.
315
+
316
+ 1. **Create first**, once: `gh api --method POST "repos/\{owner\}/\{repo\}/milestones" -f title="v\{BARE_VERSION\}" > "$F"; echo "exit=$?"`, then read `$F` raw — never `--jq` over an error body. `exit=0` ⇒ `created`; keep its `number` and `node_id`.
317
+ 2. **HTTP 422 whose `errors[].code` includes `already_exists`** ⇒ `existing`: `gh api --method GET "repos/\{owner\}/\{repo\}/milestones" -f state=all -f per_page=100 -f page=N`, N = 1…10, stopping at the first page under 100 items; keep the one exact `title` match. None found ⇒ `TRACEABILITY: DEGRADED (release marker unavailable)`; its `state` `closed` ⇒ `TRACEABILITY: DEGRADED (release marker closed)`. Any other failure of step 1 or 2 ⇒ `TRACEABILITY: DEGRADED (release marker unavailable)`. Each of these makes no item call.
318
+ 3. **Read**, one query: write `query($owner:String!, $name:String!)\{ repository(owner:$owner, name:$name)\{ … \} \}` to `$F`, one alias per item, `iN: issue(number:N)\{ id milestone\{ number \} \}`, and run `gh api graphql -F owner='\{owner\}' -F name='\{repo\}' -F query=@"$F"`. Read every alias even when `gh` exits 1: a null or absent alias ⇒ that item DEGRADED; this milestone's number ⇒ Already set; another ⇒ Kept other release, left untouched.
319
+ 4. **Assign**, one mutation over the items with no milestone: gate every node ID, the milestone's too, against `^[A-Za-z0-9_=-]\{1,100\}$`; write one `mutation` to `$F` with `mN: updateIssue(input:\{id:"<id>", milestoneId:"<node_id>"\})\{ issue\{ number \} \}` per item, and run `gh api graphql -F query=@"$F"`. Parse every alias even when `gh` exits 1: `data.mN` non-null ⇒ Added, null ⇒ that item DEGRADED.
320
+
321
+ **Residual race, not closed:** a milestone set on an item between steps 3 and 4 is overwritten — GitHub has no conditional update. On backpressure, follow `### Provider signals (GitHub)` in this operation's `backlink-shipped-issues` reference.
322
+ @end
323
+
324
+ @define ensure_traceable_issue():
325
+ ## Operation: ensure-traceable-issue
326
+
327
+ Load when the resolved tracker provider is `github` and the operation is `ensure-traceable-issue`.
328
+
329
+ **Mechanics held here:** the `**Process:**` body — issue creation, and posting the design artifact as a collapsed comment; and the D3 issue template below, whose section headings are GitHub's Markdown, not every tracker's.
330
+
331
+ {common.traceable_issue_rules()}
332
+
333
+ ### Process
334
+
335
+ 1. If `ISSUE_INPUT` is provided (numeric = existing issue; text = search for it):
336
+ - Compose structured comment to `$DEVFLOW_BODY_RAW` (NEVER rewrite the issue body); apply the Comment-sink scrub (D11) and post via `gh issue comment \{number\} --body-file "$DEVFLOW_BODY"`. Comment template:
337
+ ```markdown
338
+ ## Devflow Traceability Update
339
+ **Initial Request**: \{TASK_DESCRIPTION or "(see issue body)"\}
340
+ **Status**: Linked to branch for implementation
341
+ ```
342
+ - If `PLAN_ARTIFACT_PATH` provided: read the design artifact, cap the body at 60000 characters (if larger, truncate and end with `…truncated — full report in the local plan artifact \{PLAN_ARTIFACT_PATH\} (not committed; ask the author)`), compose to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) and post as a collapsed `<details>` comment via `gh issue comment \{number\} --body-file "$DEVFLOW_BODY"`, then reference the comment URL from the `## Implementation Plan` section in a follow-up comment.
343
+ - Return the issue number.
344
+ 2. If no `ISSUE_INPUT`: create a new issue using the D3 template:
345
+ - Title: derived from `TASK_DESCRIPTION` (same slug logic as setup-task); bind to a shell variable: `DEVFLOW_ISSUE_TITLE="..."`.
346
+ - Compose the issue body to `$DEVFLOW_BODY_RAW` using the D3 template in the `### Traceability Issue Template (D3)` section below. `TASK_DESCRIPTION`, `INITIAL_REQUEST`, and `REQUIREMENTS` are caller-supplied and untrusted — never interpolate them into the command string. Apply the Comment-sink scrub (D11) — non-zero exit → DEGRADED, do not create issue.
347
+ - If `LABELS` provided: bind to a shell variable `DEVFLOW_LABELS`; create with `gh issue create --title "$DEVFLOW_ISSUE_TITLE" --body-file "$DEVFLOW_BODY" --label "$DEVFLOW_LABELS"`. Label values are third-party input — never interpolate them into the command string.
348
+ - If `LABELS` not provided: create with `gh issue create --title "$DEVFLOW_ISSUE_TITLE" --body-file "$DEVFLOW_BODY"`.
349
+ - If `PLAN_ARTIFACT_PATH` provided: read the design artifact, cap the body at 60000 characters (if larger, truncate and end with `…truncated — full report in the local plan artifact \{PLAN_ARTIFACT_PATH\} (not committed; ask the author)`), compose to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) and post as a collapsed `<details>` comment via `gh issue comment \{number\} --body-file "$DEVFLOW_BODY"`; then reference the comment URL in a follow-up comment to the issue.
350
+ 3. Return the issue number.
351
+
352
+ ### Create Issue with Labels and Assignees
353
+
354
+ ```bash
355
+ { cat > "$DEVFLOW_BODY_RAW" <<'EOF'
356
+ ## Description
357
+ Login fails when using SSO authentication.
358
+
359
+ ## Steps to Reproduce
360
+ 1. Click "Login with SSO"
361
+ 2. Enter credentials
362
+ 3. Observe error
363
+
364
+ ## Expected Behavior
365
+ User should be logged in successfully.
366
+ EOF
367
+ } && node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/redact-secrets.cjs" \
368
+ "$DEVFLOW_BODY_RAW" "$DEVFLOW_BODY" \
369
+ && gh issue create \
370
+ --title "Bug: Login fails for SSO users" \
371
+ --label "bug,priority-high" \
372
+ --assignee "username" \
373
+ --body-file "$DEVFLOW_BODY"
374
+ ```
375
+
376
+ ### Traceability Issue Template (D3)
377
+
378
+ When creating or enriching a GitHub issue via the `ensure-traceable-issue` operation, use the following canonical D3 template:
379
+
380
+ ```markdown
381
+ ## Initial Request
382
+ {The verbatim or paraphrased user request / scope statement that drove this task}
383
+
384
+ ## Product Requirements
385
+ {Discovered requirements summary — user needs, acceptance criteria, constraints}
386
+
387
+ ## Implementation Plan
388
+ [Design artifact posted as a collapsed comment — see linked comment below]
389
+ ```
390
+
391
+ **Rules:**
392
+ - Pre-existing issues: post a structured comment using D3 sections — NEVER rewrite the issue body.
393
+ - New issues: create with D3 body; then post the design artifact as a `<details>` collapsed comment; link that comment URL in the `## Implementation Plan` section.
394
+ @end
395
+
396
+ @define post_wave_report():
397
+ ## Operation: post-wave-report
398
+
399
+ Load when the resolved tracker provider is `github` and the operation is `post-wave-report`.
400
+
401
+ **Mechanics held here:** the `**Process:**` body — locating the wave's tracking item and posting or updating the report.
402
+
403
+ {common.wave_report_inputs()}
404
+
405
+ ### Process
406
+
407
+ 1. Check for existing marker (author-filtered — a third party posting the marker must not suppress the post):
408
+ - Fetch viewer login: `gh api user --jq '.login'` → store as VIEWER_LOGIN
409
+ - `gh issue view \{TRACKING_ISSUE\} --json comments --jq '[.comments[] | select(.author.login == "'"$VIEWER_LOGIN"'")] | .[].body'`
410
+ - Search for `<!-- devflow:wave-report wave:\{WAVE_ID\} -->` in viewer-authored comment bodies only
411
+ - If found: skip — report `Skipped: wave report for \{WAVE_ID\} already posted`
412
+ 3. Compose the comment body:
413
+ ```markdown
414
+ <!-- devflow:wave-report wave:\{WAVE_ID\} -->
415
+ \{contents of WAVE_REPORT_PATH\}
416
+ ```
417
+ Cap the composed body at 60000 characters; if larger, truncate and end with
418
+ `…truncated — full report in the local wave artifact \{WAVE_REPORT_PATH\} (not committed; ask the author)`.
419
+ 4. Write composed body to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) and post via `gh issue comment \{TRACKING_ISSUE\} --body-file "$DEVFLOW_BODY"`.
420
+ @end
421
+
422
+ @define ensure_pr_ready():
423
+ ## Operation: ensure-pr-ready
424
+
425
+ Load when the resolved tracker provider is `github` and the operation is `ensure-pr-ready`.
426
+
427
+ **Mechanics held here:** step 4b's TRACKER half only — the issue-number resolution and its `Closes #\{n\}` line; every other step is in `references/pr/ensure-pr-ready.md`.
428
+
429
+ ### Process
430
+
431
+ 4b. (ALWAYS-ON) Ensure PR body contains a `## Related Issues` section with `Closes #\{n\}` link when a verified issue number is known. Resolution order:
432
+ a. Prefer the issue number returned by `setup-task` / `ensure-traceable-issue` for this branch.
433
+ b. If unavailable, fall back to the branch name pattern `\{type\}/\{number\}-\{slug\}`: extract the numeric segment and verify with `gh issue view \{n\} --json number,state`. If the call fails or `.state` is not `"open"`, skip silently — never add a `Closes` link for an unverified number. Branches like `chore/2026-cleanup` or `fix/2fa-login` may produce false matches; the existence check is the guard.
434
+
435
+ Publish the section through step 4b's PR-host half.
436
+
437
+ If no verified issue number is discoverable, skip silently.
438
+ On any 4xx/5xx from `gh pr edit` when updating the body: emit `TRACEABILITY: DEGRADED (\{reason\})` and continue — a failed Related Issues update never blocks the PR.
439
+ @end
440
+
441
+ <!-- op: setup-task -->
442
+ {setup_task()}
443
+
444
+ <!-- op: fetch-issue -->
445
+ {fetch_issue()}
446
+
447
+ <!-- op: fetch-issues-batch -->
448
+ {fetch_issues_batch()}
449
+
450
+ <!-- op: manage-debt -->
451
+ {manage_debt()}
452
+
453
+ <!-- op: create-release -->
454
+ {create_release()}
455
+
456
+ <!-- op: gather-release-evidence -->
457
+ {gather_release_evidence()}
458
+
459
+ <!-- op: backlink-shipped-issues -->
460
+ {backlink_shipped_issues()}
461
+
462
+ <!-- op: associate-release -->
463
+ {associate_release()}
464
+
465
+ <!-- op: ensure-traceable-issue -->
466
+ {ensure_traceable_issue()}
467
+
468
+ <!-- op: post-wave-report -->
469
+ {post_wave_report()}
470
+
471
+ <!-- op: ensure-pr-ready -->
472
+ {ensure_pr_ready()}