@deftai/directive-content 0.97.0 → 0.98.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.
@@ -0,0 +1,11 @@
1
+ ---
2
+ name: deft-directive-portfolio-priority
3
+ description: >-
4
+ Pre-promotion portfolio prioritization: cluster competing open RFCs/issues
5
+ from the local github-issue cache, emit a propose-not-apply priority brief
6
+ (conflict matrix, shortlist, park), with epistemic citation gates, then hand
7
+ off for operator dispose. Use when the operator asks for portfolio priority,
8
+ a priority brief, competing RFCs, or to cluster open issues.
9
+ ---
10
+
11
+ Read and follow: skills/deft-directive-portfolio-priority/SKILL.md
package/UPGRADING.md CHANGED
@@ -260,26 +260,29 @@ The `deft-directive-sync` skill (and any agent following this upgrade path) MUST
260
260
  - ! Missing global CLI → remediate with `npm i -g @deftai/directive@latest` (not GitHub release-asset archaeology).
261
261
  - ! Git submodule update remains **legacy / back-compat only**; npm + `directive update` / `deft update` is the primary path.
262
262
 
263
- ### One upgrade PR shape (#3127)
263
+ ### One upgrade PR shape (#3127 / #3193)
264
264
 
265
- A normal framework upgrade is **one PR**, not two stacked PRs. The deposited `deft-core-guard` (`no-mixed-core-and-app`, #1430) allowlists the upgrade co-travel unit so deposit + pin + freshness stamp land together:
265
+ A normal framework upgrade is **one PR**, not two stacked PRs. The deposited `deft-core-guard` (`no-mixed-core-and-app`, #1430) allowlists the upgrade co-travel unit so deposit + pin + freshness stamp land together — and **content-constrains** package/lock when they co-travel with `.deft/core/**` (#3193):
266
266
 
267
267
  | Include in the upgrade PR | Why |
268
268
  | --- | --- |
269
269
  | `.deft/core/**` | Framework deposit payload |
270
270
  | Existing installer-managed deposits (`AGENTS.md`, hooks, skill stubs, slash commands, Taskfile include, `xbrief/.deft-version`, …) | Already framework-adjacent |
271
- | `package.json` + lockfile (`package-lock.json` / `pnpm-lock.yaml` / `yarn.lock`) when the change is the `@deftai/directive` pin (and lock follow-through) | npm pin doctor compares to deposit; part of the upgrade unit |
272
- | `.deft/GENERATION.json` | Live freshness stamp from `init` / `update` (#3117) |
271
+ | `package.json` when the **only** dependency-key changes are `@deftai/directive*` under `dependencies` / `devDependencies` / `optionalDependencies` / `peerDependencies` (not scripts/settings that merely contain the substring) | npm pin doctor compares to deposit; pin unit only (#3193) |
272
+ | lockfile (`package-lock.json` / `pnpm-lock.yaml` / `yarn.lock`) when changes are **follow-through** for that pin (Directive identity + necessary transitive/resolution lines), not unrelated direct product dependency identity | Lock follow-through for the pin unit (#3193) |
273
+ | `.deft/GENERATION.json` | Live freshness stamp from `init` / `update` (#3117); remains path-allowlisted |
273
274
 
274
275
  | Keep out of the upgrade PR | Why |
275
276
  | --- | --- |
276
277
  | Application source, tests, product docs | True app/product work — guard still fails if mixed with `.deft/core/**` |
278
+ | Unrelated `package.json` fields (scripts, metadata, non-Directive deps) co-travelling with deposit | Content-aware guard rejects (#3193); path allowlist alone is not enough |
279
+ | Unrelated lock direct product dep bumps riding the pin exemption | Content-aware guard rejects (#3193) |
277
280
  | Consumer kit narrative / host prose not deposited by Directive | Not installer-managed |
278
281
  | Arbitrary playbooks, cast, features | Product scope, not upgrade |
279
282
 
280
- **Do not** split a routine version bump into “deposit-only” then “pin/GENERATION” PRs — that re-creates engine / deposit / pin skew between merges. **Do** keep product feature work on a separate branch/PR from the framework upgrade.
283
+ **Do not** split a routine version bump into “deposit-only” then “pin/GENERATION” PRs — that re-creates engine / deposit / pin skew between merges. **Do** keep product feature work on a separate branch/PR from the framework upgrade. Consumers should **not** hand-roll a forked `deft-core-guard.yml` for normal upgrades — the deposited workflow already enforces pin-only + lock follow-through.
281
284
 
282
- Refs: [#3127](https://github.com/deftai/directive/issues/3127), [#1430](https://github.com/deftai/directive/issues/1430), [#3117](https://github.com/deftai/directive/issues/3117).
285
+ Refs: [#3127](https://github.com/deftai/directive/issues/3127), [#3193](https://github.com/deftai/directive/issues/3193), [#1430](https://github.com/deftai/directive/issues/1430), [#3117](https://github.com/deftai/directive/issues/3117).
283
286
 
284
287
  Machine-readable skill exit line (for agents/operators):
285
288
 
package/commands.md CHANGED
@@ -477,6 +477,8 @@ User-facing surface for the Phase 0 triage workflow and the unified content cach
477
477
 
478
478
  Directive does not guess your mix. Either you name the next units in order (**ordered plan**), or you let the ranked backlog suggest (**queue**). Labels bias the queue; they do not override an active plan.
479
479
 
480
+ **See also (pre-promotion portfolio):** when the need is to cluster competing RFCs/issues into a propose-not-apply priority brief (not single-item queue ranking), use `skills/deft-directive-portfolio-priority/SKILL.md` (#3201 / #3198) — classify is filter-only; no SCM label writes.
481
+
480
482
  | Path | When | Who sets it | Bare "what's next?" means |
481
483
  |---|---|---|---|
482
484
  | **Ordered plan** | You know the next few units (A then B then stop) | `task plan-sequence:set -- --file <json>` | Current sequence entry only; exhaustion fails closed |
@@ -503,7 +505,7 @@ Directive does not guess your mix. Either you name the next units in order (**or
503
505
  - `task triage:reset -- <issue>` -- append a reset record so a candidate can be reconsidered.
504
506
  - `task triage:bulk-accept|bulk-reject|bulk-defer|bulk-needs-ac` -- apply predictable decisions over filtered cached candidates.
505
507
  - `task triage:summary`, `task triage:scope`, `task triage:scope-drift`, `task triage:subscribe`, `task triage:unsubscribe`, `task triage:classify`, `task triage:welcome`, and `task triage:smoketest` -- supporting workflow and onboarding commands.
506
- - `task triage:classify -- --mirror [--apply] [--include-closed] [--author LOGIN|@me] [--repo owner/name] [--batch-size N] [--delay-ms N] [--sample-limit N] [--json]` -- **Bootstrap mass-triage / Tier-1 SCM label mirror (#1423 Wave 1–2 / #3125, #3129).** Runs the existing classify engine over the github-issue cache and mirrors outcomes as labels (`triaged` idempotency marker + optional `plan.policy.triageLabelMirror.actionLabels`). **Default state filter is open-only** (opt-in `--include-closed` for closed archive stamps). Optional `--author` / `--author-mine` scopes plan/apply to matching `author.login` (AND with open-only; digest surfaces the filter) (#3129). Dry-run by default: operator digest with totals (scanned/planned/already_triaged/no_match/closed_skipped/author_skipped/errors), breakdown by **state / rule / action**, and samples (not a full dump as primary UX); `--json` includes the same aggregates. `--apply` writes via the SCM label client / repo-mutation boundary in **batches** (`--batch-size`, default 10) with **rate-limit delay** (`--delay-ms`, default 1000), reports partial failures, and is idempotent on re-run (already-`triaged` skip). Missing labels fail closed per issue with a create-label hint. **Never** calls `triage:accept` and **never** writes `proposed/` xBRIEFs. Wave 3 (agent Tier-2 comments) remains out of scope.
508
+ - `task triage:classify -- --mirror [--apply] [--re-enrich] [--include-closed] [--author LOGIN|@me] [--repo owner/name] [--batch-size N] [--delay-ms N] [--sample-limit N] [--json]` -- **Bootstrap mass-triage / Tier-1 SCM label mirror (#1423 Wave 1–2 / #3125, #3129, #3197).** Runs the existing classify engine over the github-issue cache and mirrors outcomes as labels (`triaged` idempotency marker + optional `plan.policy.triageLabelMirror.actionLabels`). **Default state filter is open-only** (opt-in `--include-closed` for closed archive stamps). Optional `--author` / `--author-mine` scopes plan/apply to matching `author.login` (AND with open-only; digest surfaces the filter) (#3129). Dry-run by default: operator digest with totals (scanned/planned/already_triaged/no_match/closed_skipped/author_skipped/errors), breakdown by **state / rule / action**, and samples (not a full dump as primary UX); `--json` includes the same aggregates. `--apply` writes via the SCM label client / repo-mutation boundary in **batches** (`--batch-size`, default 10) with **rate-limit delay** (`--delay-ms`, default 1000), reports partial failures, and is idempotent on re-run (already-`triaged` skip). **Re-run vs re-enrich (#3124 / #3197):** default re-run keeps the one-shot stamp (`skipped_already_triaged`). After `actionLabels` / auto-classify rule / hold-marker changes, opt in with **`--re-enrich`** (still dry-run by default; pair with `--apply` to write) to re-classify already-stamped issues and plan **additive** label deltas only (v1 never removes obsolete chips; never full reconcile). Digest distinguishes `kind=first-time` vs `kind=re-enrich` planned/applied rows (`planned_kind` / `re_enrich_planned`). Missing labels fail closed per issue with a create-label hint. **Never** calls `triage:accept` and **never** writes `proposed/` xBRIEFs. Wave 3 (agent Tier-2 comments) remains out of scope.
507
509
 
508
510
  ### Cache Tasks
509
511
 
@@ -1,6 +1,6 @@
1
1
  # Consumer check contract (`verify:consumer-check-contract`)
2
2
 
3
- Refs: #3145 · Related: #3070 consumer gate integrity, #1519 check:consumer
3
+ Refs: #3145 · Related: #3070 consumer gate integrity, #1519 check:consumer · Policy: #3189 coverageDebt / checkResume
4
4
 
5
5
  ## Problem
6
6
 
@@ -25,3 +25,40 @@ It fails with a concrete repair path when definitions or explicit check deps omi
25
25
  ## Relation to #3070
26
26
 
27
27
  `consumer-gate-integrity` proves Taskfile includes resolve. This gate proves the **required enforcement set** is present and composed — not merely that a random verify task exists.
28
+
29
+ ## Coverage-debt hatch and local check resume (#3189)
30
+
31
+ Release-born hatch and suite-stamp features must not expand to consumers as silent defaults. Project policy lives under `plan.policy` in PROJECT-DEFINITION:
32
+
33
+ | Field | Shape | Fail-closed when unset |
34
+ |---|---|---|
35
+ | `coverageDebt` | `status: unset \| decided`, `mode: off \| warn \| hatch`, `autoFile` (hatch only; default false) | mode off — no hatch soft-pass |
36
+ | `checkResume` | `status: unset \| decided`, `localStamp: off \| on`, `ciTrustsLocalStamp: false` (fixed v1) | localStamp off; **CI never trusts a laptop stamp** |
37
+
38
+ **Unset vs decided-off:** Unset keeps fail-closed *behavior* and still **nags** on interactive mutation session-start. Decided-off is quiet. Inspect with `deft policy:show --field=coverageDebt` and `--field=checkResume`. Doctor check `coverage-check-resume-policy` surfaces undecided as an **advisory skip** (never hard-fails doctor or `check:consumer`).
39
+
40
+ ### Skippable session nudge
41
+
42
+ On interactive cold or re-arm **mutation** session-start, when either field is unset:
43
+
44
+ - **Why:** long checks fail late or barely miss coverage; the project chooses fail-closed, warn, or hatch with a debt issue on **this** repo; local machines may resume a green suite at the same HEAD; CI must not trust a laptop stamp.
45
+ - **What:** one bundled choice — **Strict** (recommended), **Hatch-aware**, or **Later** (plus Discuss / Back per #1470).
46
+ - **Later** does **not** set `status=decided`; the next ritual nags again.
47
+ - **Stop nag** only after Strict / Hatch-aware (preset write) or **dismiss-with-reason** (visible on policy:show / doctor).
48
+ - Headless / CI / non-TTY: nudge is skipped (fail-open; never blocks).
49
+
50
+ ### Expansion gates (after decided)
51
+
52
+ | Feature | Expand to consumers? | Gate |
53
+ |---|---|---|
54
+ | Fast-before-slow gate ordering | Yes (universal UX) | No policy wait |
55
+ | Local suite stamp resume | When `checkResume.localStamp=on` | Local only; CI ignores |
56
+ | Coverage hatch / auto-file | When `coverageDebt.mode=hatch` (+ optional `autoFile`) | Ledger on **this** consumer repo |
57
+ | CI trusts local stamp | **Not v1** | Separate RFC |
58
+
59
+ ### Non-goals
60
+
61
+ - Silent default-on hatch for consumers.
62
+ - Auto-filing coverage-debt issues on **deftai/directive** from a consumer tree — the ledger is always **this** repo.
63
+ - USER.md as source of truth for ship bars (personal never weakens the project bar).
64
+ - Blocking headless CI on the nudge.
@@ -80,6 +80,12 @@ rm .deft-directive-disable
80
80
  # Start a NEW agent session
81
81
  ```
82
82
 
83
+ ## Agent self-serve ban under UAT (#3186)
84
+
85
+ Operators plant and remove this flag **outside** an agent session (or with a prior human-origin grant). Under **active UAT**, PreToolUse classifies Shell writes that create `.deft-directive-disable` (and permanent opt-out `.no-deft-directive`) as **settings** and **denies** them without a human grant — same posture as `authz:grant` (#3110). Agents must not self-serve the kill-switch to bypass Write / push / PR / merge gates.
86
+
87
+ Related: `policy:allow-bot-merge`, `policy:allow-direct-commits`, and `policy:disable-directive` (peers) are also settings under UAT and require a human grant.
88
+
83
89
  ## Non-goals (v1)
84
90
 
85
91
  - ⊗ Auto-delete `.deft/` or uninstall deposit
package/main.md CHANGED
@@ -58,6 +58,22 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
58
58
 
59
59
  See #634, #642. See [ADR-001](./docs/decisions/ADR-001.md) for the token-economics rationale behind this ordering (vBRIEF-as-canonical for the agentic-consumed surface).
60
60
 
61
+ ## Self-Improving, Not Self-Editing (#3164)
62
+
63
+ **Stance:** Directive is **self-improving**, not **self-editing**. Improvement goes through formal gates (**propose-not-apply**).
64
+
65
+ Continual-Harness-class hosts may rewrite prompts, skills, and memory mid-run. Directive does not. A running session must not mutate live operating rules in place.
66
+
67
+ - ! Directive MUST NOT self-edit live operating rules mid-run (managed AGENTS.md, pinned skills, policy flags, and other constitution-tier content)
68
+ - ! Refine and meta-loops **propose** changes; issues, PRs, and quality gates **dispose**
69
+ - ! Learn between merges — not by mid-session rewrite of the constitution
70
+ - ? Prose lessons (`meta/lessons.md`; Continuous Improvement below) MAY stay agent-writable. They sit at the bottom of the Rule Authority ladder and cannot override structural rules
71
+ - ⊗ Treat mid-run self-edit of constitution, skills, or policy as the default learning model
72
+
73
+ This is not timidity; it is identity. It follows from the Rule Authority ladder above and from safety via formal gates, not alignment (#1200). A framework whose value is that the gates sit outside the agent cannot let a session rewrite the gates' substrate and stay coherent.
74
+
75
+ Parent epic: #3179 (self-improving under gates). Trajectory / refine constraint: #2741 — refine proposes; gates dispose. Proposer runtime (SkillOpt / skill-variant) is tracked on #2436 / #1307 and is out of scope for this stance naming.
76
+
61
77
  **Decision Making:**
62
78
  - ! Follow established patterns in current context
63
79
  - ~ Question assumptions and probe for clarity
@@ -277,6 +293,7 @@ See [`skills/deft-directive-refinement/SKILL.md`](./content/skills/deft-directiv
277
293
  - ~ When repeated correction or better approach found, codify in `./lessons.md`
278
294
  - ? Modify `./lessons.md` without prior approval
279
295
  - ~ When using codified instruction, inform user which rule was applied
296
+ - ! Promote constitution-tier improvements (skills, policy, managed AGENTS rules) through issue / PR / quality gate — not mid-run self-edit (see [Self-Improving, Not Self-Editing (#3164)](#self-improving-not-self-editing-3164))
280
297
 
281
298
  **Observation:**
282
299
  - ~ Think beyond immediate task
@@ -8,6 +8,16 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
8
8
 
9
9
  ---
10
10
 
11
+ ## Self-Improving, Not Self-Editing (#3164)
12
+
13
+ Directive improves **across merges** through issues, PRs, and quality gates. It does not rewrite live constitution rules mid-run (**propose-not-apply**).
14
+
15
+ Constitution (managed AGENTS.md, pinned skills, policy) stays gated. Playbook-tier prose (e.g. lessons) may stay agent-writable because it sits at the bottom of the Rule Authority ladder.
16
+
17
+ Full stance and MUST/MAY bullets: [main.md § Self-Improving, Not Self-Editing](../../main.md#self-improving-not-self-editing-3164). Parent epic #3179; safety-via-gates #1200; trajectory/refine #2741.
18
+
19
+ ---
20
+
11
21
  ## Deterministic > Probabilistic
12
22
 
13
23
  Prefer deterministic components for repeatable actions over probabilistic ones.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-content",
3
- "version": "0.97.0",
3
+ "version": "0.98.0",
4
4
  "description": "Shippable Directive framework content in the consumer .deft/core/ layout (C1 flatten), plus the engine surfaces (.githooks/, Taskfile.yml, tasks/) the deposit wires. Python-free per #2022 Phase 3. Refs #11, #1669, #1967.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -147,6 +147,23 @@
147
147
  "body": "# Deft Directive Interview\n\nDeterministic interview loop that any skill can invoke to gather structured user input.\n\nLegend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.\n\n## When to Use\n\n- Another skill needs to gather structured input from the user (e.g. deft-directive-setup Phase 1/Phase 2)\n- User says \"interview loop\", \"q&a loop\", or \"run interview loop\"\n- A workflow requires a series of focused questions with explicit defaults and confirmation before proceeding\n\n## Interview Loop\n\n### Rule 1: One Question Per Turn\n\n! Ask ONE focused question per step. After the user answers, send the NEXT question in a new message. Repeat until all questions for the current interview are answered.\n\n- ⊗ Include two or more questions in the same message under any circumstances\n- ⊗ List upcoming questions -- only show the current one\n- ⊗ Combine the current question with a summary of previous answers unless explicitly at the confirmation gate\n\n### Rule 2: Numbered Options with Stated Default\n\n! Every question MUST present numbered answer options. Exactly one option MUST be marked as the default using the `[default: N]` notation inline.\n\nExample:\n```\nWhich deployment platform?\n\n 0. Discuss with agent (pause interview to talk it through)\n ─────────────────────────────────────────────────────────\n 1. Cross-platform (Linux / macOS / Windows)\n 2. Web / Cloud [default: 2]\n 3. Embedded / low-resource\n 4. Other / I don't know\n\nEnter confirm / b back / 0 discuss\n```\n\n- ! The default MUST be stated inline with the option (e.g. `[default: 2]`), not in a separate line or footnote\n- ! If no option is objectively better, pick the most common choice and mark it as default\n- ! Option `0. Discuss with agent` MUST appear in slot 0 at the top of the options block, visually separated from the numbered answer options (e.g. by a horizontal rule or blank line) so it is not confused with `Other / I don't know`\n- ! A persistent one-line legend MUST appear directly under the options block on every question (see Rule 11)\n\n#### Always-Structured Rendering (Option A)\n\n! EVERY user-facing question MUST render via the structured question tool: click-commit on hosts that have one (e.g. Warp `ask_user_question`); plain-text with typed response otherwise. The agent MUST NOT emit a user-facing question as conversational prose regardless of whether the final answer content is enumerable or freeform. This is the Option A always-structured rendering rule (#478).\n\n! **Host-portable numeric labels (#1563).** For deterministic menus, a structured question tool satisfies this rule only when it visibly preserves each canonical numeric option label (for example, `1. Web / Cloud`) and returns the numeric selection or exact displayed option text. If the host UI may replace numbers with alphabetic affordances, unlabeled buttons, or any non-canonical choice labels, render the numbered menu in chat with typed response instead. Fallback chat replies map only to the displayed number or exact displayed option text; do not infer from host-added letters unless those letters were actually displayed.\n\n! Freeform answer collection, when needed, MUST be a two-step flow:\n\n1. Structured-tool call with options such as `[Draft for me (I'll propose content) / Write my own / Defer / Discuss / Back]`.\n2. If the user picks \"Write my own\", a follow-up plain-text prompt collects the freeform content. The prompt itself is a non-question status message (\"Type your answer below.\") -- the enumerable path choice has already been captured by the structured tool.\n\n! The ONLY permissible plain-text-to-user emissions are:\n\n1. The Rule 6 Confirmation Gate (typed plain-text commit, see Rule 6's Click-Commit Hosts subsection).\n2. Agent-initiated status updates that do NOT ask the user to choose anything (e.g. \"Files written. Next I'll read X.\").\n\n- ⊗ Emit a user-facing question outside a structured-tool call because the answer content is prose, because a preamble is long, because the question \"feels conversational,\" or because the prior question was plain-text. None of those are valid reasons.\n- ⊗ Present a user-facing choice as plain-text prose when a structured question tool is available on the host.\n\n#### Click-Commit Rendering\n\n! When the host's structured question tool is single-shot / click-commit (the tool returns the user's selection atomically with no separate Enter step -- e.g. Warp `ask_user_question`), Rules 8, 9, and 10 MUST be rendered as explicit clickable options in the options list on every applicable question. The `Enter confirm / b back / 0 discuss` keystroke legend from Rule 11 is a plain-text-mode affordance only.\n\nClick-commit options block shape:\n\n```\n[ Back -- revisit step N-1 ]\n[ Discuss with agent (pause interview) ]\n----------------------------------------\n[ Answer option 1 ]\n[ Answer option 2 [default] ]\n[ Answer option 3 ]\n[ Other / I don't know ]\n```\n\n- ! `Back` MUST appear on every question EXCEPT the first (nothing to go back to). Picking `Back` returns to question N-1 with its previously captured answer displayed.\n- ! `Discuss with agent` MUST appear on every question. Picking it enters Rule 10 freeform mode; the agent MUST re-render the same question after the discussion resolves.\n- ! Answer options MUST render with the default marker (e.g. `[default]` appended to the option label) since the `[default: N]` inline notation is not rendered by most click-commit tools.\n- ⊗ Omit `Back` on any question except the first.\n- ⊗ Omit `Discuss with agent` on any question.\n- ⊗ Treat a click-commit tool's returned selection as a Rule-8 confirmed commit. Click-commit tools do NOT satisfy Rule 8 -- there is no keyboard Enter step, so the click is both selection and commit. The Rule 6 Confirmation Gate (rendered as plain-text typed commit on click-commit hosts) provides the only typed-commit step in the flow.\n\n#### Preamble Placement\n\n~ Explanatory preamble (context, consequences, what-happens-next) MAY appear as plain markdown ABOVE the structured-tool call. The question itself -- the sentence that asks for a decision -- MUST be encoded in the structured tool's `question` field, and the enumerable options MUST be encoded in the tool's `options` field.\n\n- ⊗ Render a user-facing question as plain-text because you wanted to include preamble -- preamble belongs above the tool call, not instead of it.\n\n### Rule 3: Explicit \"Other / I Don't Know\" Escape\n\n! Every question MUST include an escape option. The last numbered option MUST be either:\n- \"Other (please specify)\" -- for open-ended alternatives\n- \"I don't know\" -- when the user may lack context to answer\n- \"Other / I don't know\" -- combined form (preferred)\n\n- ! The `Other / I don't know` escape is SEPARATE from the slot-0 `Discuss with agent` option (Rule 10). The two options MUST both be present and MUST NOT be merged into a single entry\n- ⊗ Present a question with no escape option -- the user must always have a way out\n- ⊗ Use the `Other / I don't know` entry as the discuss/freeform escape -- slot 0 `Discuss with agent` is the discuss escape (Rule 10)\n- ~ When the user selects the escape option, follow up with a brief open-ended prompt to capture their input or acknowledge the gap\n\n### Rule 4: Depth Gate\n\n! Keep asking until no material ambiguity remains before artifact generation. The interview is NOT complete until the calling skill's required inputs are all captured with sufficient specificity to generate the target artifact.\n\n- ! If an answer introduces new ambiguity (e.g. user selects \"Other\" and describes something that requires follow-up), ask clarifying questions before moving on\n- ! Do not truncate the interview to save time -- completeness takes priority over brevity\n- ~ The calling skill defines what \"sufficient specificity\" means by providing a list of required fields in the handoff contract\n\n### Rule 5: Default Acceptance\n\n! When a question has a stated default, the user may accept it with any of the following responses:\n- Bare enter / empty response\n- \"yes\", \"y\", \"ok\", \"default\", \"keep\"\n\n! When the user types the default option number (e.g. \"2\"), this is treated as a numeric selection — Rule 8 applies (echo selection, wait for confirmation). It is NOT treated as a bare acceptance like \"yes\" or Enter.\n\n! Do NOT re-ask the question when the user accepts the default via a non-numeric response. Record the default value and proceed to the next question.\n\n- ⊗ Re-ask a question because the user's acceptance was \"too brief\" -- any of the listed responses is a valid acceptance\n- ⊗ Interpret an empty response as a refusal or skip\n\n### Rule 6: Confirmation Gate\n\n! After ALL questions are answered (depth gate satisfied), display a summary of ALL captured answers in a clearly formatted list and require explicit yes/no confirmation before proceeding.\n\nFormat:\n```\nHere are the values I captured:\n\n- **Field 1**: value\n- **Field 2**: value\n- **Field 3**: value\n...\n\nConfirm these values? (yes / no)\n```\n\n- ! Accept only explicit affirmative responses (`yes`, `confirmed`, `approve`) -- reject vague responses (`proceed`, `do it`, `go ahead`)\n- ~ Note: The confirmation gate is intentionally stricter than Rule 5 (default-acceptance). Rule 5 accepts casual responses like `ok` for individual question defaults because the cost of a wrong default is low (one field, correctable at the confirmation gate). The confirmation gate guards the entire artifact -- accepting `ok` here risks generating artifacts from auto-filled or misunderstood values. This asymmetry is by design.\n- ! If the user says `no`: ask which values to correct, re-ask those specific questions only (do not restart the full interview), then re-display the updated summary and re-confirm\n- ! If any value appears to be auto-generated filler (repeated default text, placeholder strings, or values that echo the question prompt), warn the user explicitly before confirming\n- ⊗ Proceed to artifact generation without displaying the summary and receiving explicit confirmation\n\n#### Click-Commit Hosts: Plain-Text Confirmation Gate\n\n! When the host's structured question tool is click-commit, the Confirmation Gate MUST be rendered as plain-text requiring a typed response (`yes` / `no` / `back` / `discuss`). It MUST NOT be rendered via the click-commit structured tool.\n\n- ! Accept only explicit affirmative tokens (`yes`, `confirmed`, `approve`) per the existing Rule 6 strictness. Reject click-level tokens and vague responses (`ok`, `proceed`, `do it`).\n- ! The typed commit provides the Enter-confirm semantic that Rule 8 was written to guarantee. This is the only place in the click-commit flow where a true commit step exists.\n- ⊗ Render the Confirmation Gate via a click-commit structured tool -- this removes the last safeguard against misclicks on the final answer.\n\n#### Mode Restore After the Gate\n\n! After the Rule 6 Confirmation Gate commits (user typed an affirmative token), the plain-text rendering mode is RELEASED. The next user-facing question MUST return to structured-tool rendering (click-commit on hosts that have one; plain-text with typed response otherwise, per Rule 2's Always-Structured Rendering). The plain-text gate does NOT establish a sticky mode for subsequent prompts.\n\n- ⊗ Render the next user-facing question as plain-text conversational prose because the Rule 6 Gate was just in plain-text mode.\n\n### Rule 7: Structured Handoff Contract\n\n! When the interview is complete (confirmation gate passed), the skill exits with an **answers map** -- a structured key-value representation of all captured answers that the calling skill uses to generate artifacts.\n\nThe answers map format:\n```json\n{\n \"field_1\": \"captured value\",\n \"field_2\": \"captured value\",\n \"field_3\": [\"list\", \"if\", \"multi-select\"],\n ...\n}\n```\n\n- ! The calling skill defines the expected keys in its invocation of deft-directive-interview\n- ! The answers map MUST contain a value for every required key defined by the calling skill\n- ! Optional keys may be omitted if the user did not provide input and no default was applicable\n- ~ The calling skill is responsible for validating the answers map against its own schema and requesting re-interview for any missing or invalid fields\n\n## Output Targets\n\nInterview output writes to `specification.xbrief.json` `plan.narratives` — the xBRIEF draft is the sole authoritative output. PRD.md is never generated. All xBRIEFs target the canonical v0.6 schema (`xbrief/schemas/xbrief-core.schema.json`, strict `const: \"0.6\"`); see [`../../conventions/references.md`](../../conventions/references.md).\n\nWhen the interview captures origin provenance (e.g. the user links to a GitHub issue or Jira ticket), include a `references` entry in the canonical form documented in [`../../conventions/references.md`](../../conventions/references.md):\n\n```json\n\"references\": [\n {\n \"uri\": \"https://github.com/{owner}/{repo}/issues/{N}\",\n \"type\": \"x-xbrief/github-issue\",\n \"title\": \"Issue #{N}: {issue title}\"\n }\n]\n```\n\n### Full Path Output\n\n! On the Full path, the interview populates `specification.xbrief.json` `plan.narratives` with `xBRIEFInfo.version: \"0.6\"`, `status: draft`, and rich keys:\n\n- `ProblemStatement`: What problem this project solves\n- `Goals`: High-level project goals\n- `UserStories`: User stories in standard format\n- `Requirements`: Structured requirements (FR-N: functional, NFR-N: non-functional)\n- `SuccessMetrics`: Measurable success criteria\n- `Architecture`: System design and technical architecture\n- `Overview`: Brief project summary\n\n! All narrative values MUST be plain strings — never objects or arrays.\n\n! The human approval gate reviews the xBRIEF draft narratives directly — reviewing the narratives IS the approval step. On approval, update `status` to `approved` and generate downstream scope xBRIEFs.\n\n### Light Path Output\n\n! On the Light path, the interview populates `specification.xbrief.json` with `status: draft` and slim narratives:\n\n- `Overview`: Brief project summary\n- `Architecture`: System design description\n\n! On approval, update `status` to `approved`. Scope xBRIEFs are then created in `xbrief/proposed/` for each identified work item.\n\n### PRD.md (deprecated — never authoritative)\n\nPRD.md is not generated as part of the interview workflow on either path. The `specification.xbrief.json` xBRIEF draft is the sole source of truth.\n\n- ? If stakeholders require a traditional PRD document, run `task prd:render` to export a read-only `PRD.md` from `plan.narratives`\n- ! PRD.md is never authoritative — `specification.xbrief.json` is the source of truth\n- ⊗ Generate an authoritative PRD.md during the interview process\n- ⊗ Treat PRD.md as a source of truth — it is a generated export artifact\n\n## Invocation Contract\n\ndeft-directive-interview supports two usage modes:\n\n### Embedded Mode\n\nThe calling skill references deft-directive-interview rules inline (e.g. \"this phase follows the deterministic interview loop defined in `skills/deft-directive-interview/SKILL.md`\") and applies the rules directly within its own question sequence. No formal contract object is needed -- the calling skill embeds the question definitions and field requirements in its own SKILL.md. This is the current approach used by `skills/deft-directive-setup/SKILL.md` Phase 1 and Phase 2.\n\n### Delegation Mode\n\nThe calling skill explicitly invokes deft-directive-interview as a sub-skill and passes a formal contract object. When using delegation mode, the calling skill MUST provide:\n\n1. **Required fields**: list of field names that must be captured (the depth gate uses this to determine completeness)\n2. **Question definitions**: for each field, the question text, numbered options (if applicable), and default value\n3. **Optional fields**: list of field names that may be skipped\n\nThe calling skill MAY provide:\n- **Context preamble**: a brief description of why these questions are being asked (shown to the user before the first question)\n- **Validation rules**: constraints on acceptable values for specific fields\n\n### Rule 8: Deterministic Selection Confirmation\n\n! After the user enters a number to select an option, the agent MUST echo the selected option text and wait for explicit confirmation before advancing to the next question. The confirm-after-number-press step is MANDATORY -- number entry alone MUST NOT advance the interview.\n\nExample:\n```\nWhich deployment platform?\n\n 0. Discuss with agent (pause interview to talk it through)\n ────────────────────────────────────────────────────────\n 1. Cross-platform (Linux / macOS / Windows)\n 2. Web / Cloud [default: 2]\n 3. Embedded / low-resource\n 4. Other / I don't know\n\nEnter confirm / b back / 0 discuss\n\n> User: 1\n\nYou selected: **1. Cross-platform (Linux / macOS / Windows)**\nPress Enter to confirm, type a different number to change, or `b` to go back.\n```\n\n- ! Show the selected option text (with its number) after each number entry -- the user must see what was selected\n- ! Wait for Enter / explicit confirmation before advancing -- do NOT auto-advance on number press, under any circumstances\n- ! If the user types a different number instead of confirming, switch to that option and re-confirm (echo the new selection and wait for Enter again)\n- ! Accept `b`, `back`, or `prev` at the confirm prompt as a shortcut to back-navigation (Rule 9) without first requiring confirmation of the selection\n- ⊗ Auto-advance to the next question immediately after the user presses a number key\n- ⊗ Skip the echo step -- even when the selection \"seems obvious\" the agent MUST display the selected option text back to the user before advancing\n\n### Rule 9: Backward Navigation\n\n! The agent MUST support backward navigation during the interview. At any question (including at the confirm-after-number prompt from Rule 8), the user may type `back`, `prev`, or `b` to return to the previous question and change their answer.\n\n- ! When the user navigates back, re-display the previous question with the previously selected answer shown\n- ! The user may change the answer or confirm the existing one\n- ! The back-navigation affordance MUST be visible on every question via the persistent one-line legend (Rule 11) -- not only announced once at the start of the interview\n- ~ The agent SHOULD additionally inform the user of backward navigation availability at the start of the interview (e.g. \"Type 'b' / 'back' at any question to revisit the previous answer\")\n- ⊗ Refuse to let the user revisit previous answers during the interview\n- ⊗ Hide the back-navigation affordance -- the legend under each question MUST name the `b` key explicitly\n\n### Rule 10: Freeform Conversation Escape (Slot 0 -- Discuss with agent)\n\n! Every deterministic question MUST include an option `0` that pauses the structured flow and opens a freeform conversation with the agent. Option 0 is the self-describing **Discuss with agent** escape hatch and is DISTINCT from `Other / I don't know` (Rule 3).\n\n- ! Option 0 label: `0. Discuss with agent` (a short self-describing clarifier such as `(pause interview to talk it through)` MAY follow on the same line)\n- ! Option 0 MUST render in slot 0 at the top of the options block, visually separated from the numbered answer options (e.g. by a horizontal rule or blank line) so it is not confused with slot `Other / I don't know`\n- ! The slot-0 label MUST be self-describing -- do NOT use generic labels like `Other..`, `Escape`, or `Pause` without the `Discuss with agent` phrasing\n- ! When the user selects 0, the agent enters a freeform conversation mode where the user can ask clarifying questions, request more context about the options, or explain nuance\n- ! The agent MUST explicitly resume the deterministic flow when the conversation is resolved: re-display the same question (with the full options block and legend) and wait for a numbered answer\n- ⊗ Continue the deterministic flow while in freeform conversation mode\n- ⊗ Omit option 0 from any deterministic question\n- ⊗ Place option 0 at the bottom of the options block or inline with numbered answers -- slot 0 MUST be first, visually separated\n- ⊗ Merge slot-0 `Discuss with agent` with `Other / I don't know` -- they are distinct affordances\n\n### Rule 11: Persistent Legend Under Each Question\n\n! Every deterministic question MUST surface the confirm / back / discuss affordances on every question (not only at the start of the interview). The way those affordances are surfaced depends on the host's rendering mode -- see Rule 2's Click-Commit Rendering and Always-Structured Rendering subsections.\n\nCanonical legend text (plain-text mode):\n\n```\nEnter confirm / b back / 0 discuss\n```\n\n#### Plain-Text Rendering Mode\n\n! In plain-text rendering mode, the keystroke legend above MUST be rendered directly under the options block on every deterministic question, including re-displayed questions after back-navigation (Rule 9) or freeform resume (Rule 10).\n\n- ! The legend MUST be present under every deterministic question in plain-text mode\n- ! The legend MUST name all three affordances: `Enter` (confirm selection -- Rule 8), `b` (back -- Rule 9), `0` (discuss -- Rule 10)\n- ~ The legend SHOULD appear as a single line directly below the options block, separated by a blank line\n- ? Additional hints (e.g. `c cancel`) MAY be appended with `/` separators, but the three canonical affordances MUST always be present\n- ⊗ Omit the legend from any plain-text-mode deterministic question -- every question MUST carry it\n- ⊗ Replace the canonical affordance labels with non-self-describing abbreviations (e.g. `↵`, `←`) without also spelling them out\n\n#### Click-Commit Rendering Mode\n\n! In click-commit rendering mode, the confirm / back / discuss affordances are rendered as clickable options in the options list per Rule 2's Click-Commit Rendering subsection:\n\n- `Back -- revisit step N-1` renders as a clickable option on every question except the first\n- `Discuss with agent (pause interview)` renders as a clickable option on every question\n- Commit is implicit in the click-commit return; the Rule 6 plain-text Confirmation Gate is the only typed-commit step before file writes\n\n! The keystroke legend MAY be omitted in click-commit rendering because the host tool does not accept keystroke input. The affordances themselves MUST still be present as clickable options (not omitted).\n\n- ⊗ Omit `Back` (except on the first question) or `Discuss with agent` from any click-commit rendering -- the affordances survive the mode change even though the keystroke legend does not\n\n## IP Risk Probe (#738)\n\n! When the interview captures the project description (and any\nresearch-phase notes), the agent MUST run the IP-risk heuristic from\n[`references/ip-risk.md`](../../references/ip-risk.md) -- canonical\nimplementation `scripts/ip_risk.py:detect_ip_terms` -- before generating\nthe SPECIFICATION.\n\nThe heuristic is permissive on purpose: recognizable IP names (Magic:\nThe Gathering, Pokemon, etc.), fictional-universe terms (Hogwarts,\nTatooine), branded characters (Mickey Mouse, Spider-Man), sports leagues\n(NFL, NBA), and trademarked products (iPhone, Xbox) all trigger a hit.\n\n! When `detect_ip_terms` returns at least one hit:\n\n1. ! Ask the explicit monetization-intent question (canonical wording in\n `references/ip-risk.md` `## Question Script`). The user MUST choose\n between **personal use only** and **commercial use** -- defaulting to\n `commercial` when intent is unclear, because the stricter checklist is\n the wrong-side-of-safe choice.\n2. ! Emit the plain-English risk summary from\n `scripts/ip_risk.py:plain_risk_summary(hits, intent)` into the\n interview output AND into an `IPRisk` narrative on the\n `specification.xbrief.json` draft. The summary opens with `not legal\n advice`, names the detected categories, and (for commercial intent)\n states explicitly that lawyer consultation is **not optional output**\n from this interview.\n3. ! Inject the three protection scope items from\n `scripts/ip_risk.py:ip_risk_scope_items(intent)` into the spec\n xBRIEF's `plan.items` array (disclaimer stub `IP-1`, API-only-asset\n policy `IP-2`, hosting policy `IP-3`). The items flow naturally into\n the rendered SPECIFICATION.md via the existing\n `scripts/spec_render.py` pipeline -- no spec_render.py modification\n is required.\n4. ! For commercial intent, the lawyer-consultation recommendation is\n non-optional output -- the interview MUST surface it before the\n confirmation gate.\n\n- ⊗ Skip the IP-risk probe when the project description references\n third-party intellectual property -- the heuristic is the gate\n whether or not the agent thinks the project is \"obviously fine\".\n- ⊗ Treat the absence of detected terms as proof that the project is\n IP-free -- the heuristic only knows about the curated lists in\n `scripts/ip_risk.py`. When the description is vague, the agent SHOULD\n ask directly \"is this based on a game / film / sports league /\n brand?\".\n- ⊗ Provide legal advice. Deft is not a law firm -- the only\n recommendation it makes is **consult a lawyer**.\n\n## Plain-English UX (#740)\n\n! Every user-facing question, summary, and output line in this skill\nMUST follow the rules in\n[`references/plain-english-ux.md`](../../references/plain-english-ux.md):\n\n1. ! Every technical question MUST carry a one-line plain-English\n context note as preamble above the structured-tool call. The\n preamble explains the *consequence* of the choice in non-technical\n terms.\n2. ! Every acronym MUST be defined inline on first use within a skill\n session: `PRD (Product Requirements Document)`, `NFR (Non-Functional\n Requirement)`, `FR (Functional Requirement)`, `SPEC (Specification)`,\n `API (Application Programming Interface)`, `DB (Database)`, `CI\n (Continuous Integration)`, `MVP (Minimum Viable Product)`. Subsequent\n uses MAY drop the expansion.\n3. ! When a skill recommends a framework, library, language, or tool,\n the recommendation MUST either explain the rationale in plain-English\n terms the user actually cares about (cost, speed, hiring, hosting\n compatibility, etc.) OR remove the rationale entirely and present it\n as an overridable default. ⊗ Drop a framework name with a one-word\n `industry-standard` / `modern` / `scalable` justification.\n\n- ⊗ Drop a technical question on the user without a plain-English\n context note above it.\n- ⊗ Use an acronym for the first time without inlining `(full\n expansion)`.\n- ⊗ Treat \"the user can ask their other AI to translate\" as an\n acceptable mitigation for jargon.\n\n## Approval Menus After PRD and SPEC Review (#740, refs #767)\n\n! After every PRD review AND every SPECIFICATION review, the agent MUST\npresent an explicit numbered approval menu using the canonical shapes\nfrom `references/plain-english-ux.md` `## Rule 4`. The menu replaces\nambiguous buttons like `Accept / Refine / Edit` with action-shaped\nlabels.\n\nCanonical PRD-review menu:\n\n```\nWhat would you like to do with the PRD (Product Requirements Document)?\n\n 1. Approve and continue (lock the PRD, generate the SPECIFICATION)\n 2. Suggest changes (you describe what to change; the agent rewrites)\n 3. Edit yourself (you edit the PRD directly; the agent waits)\n 4. Discuss\n 5. Back\n\nEnter confirm / b back / 0 discuss\n```\n\nCanonical SPECIFICATION-review menu:\n\n```\nWhat would you like to do with the SPECIFICATION?\n\n 1. Approve and continue (lock the SPEC, proceed to implementation)\n 2. Suggest changes (you describe what to change; the agent rewrites)\n 3. Edit yourself (you edit the SPEC directly; the agent waits)\n 4. Discuss\n 5. Back\n\nEnter confirm / b back / 0 discuss\n```\n\n! Every numbered approval menu MUST follow the #767 deterministic-\nquestion framework rule: the **final two numbered options MUST be\n`Discuss` and `Back`**, in that order. This is consistent with this\nskill's Rule 9 (back-navigation) and Rule 10 (slot-0 discuss-with-agent\nescape) and provides a uniform exit path on every menu. The framework\nrule itself is being landed by Agent 1 in #767; once\n`contracts/deterministic-questions.md` exists, this skill MUST defer to\nit for the canonical wording.\n\n- ⊗ Add a numbered approval menu where Discuss and Back are not the\n final two options.\n- ⊗ Use plain `Accept / Refine / Edit` buttons without explanatory\n parentheticals.\n\n## Diff-View Preface on PRD / SPEC Review (#740)\n\n! When a PRD or SPECIFICATION review surfaces a red/green diff, the\nagent MUST emit a one-line non-alarming preface ABOVE the diff that\nsets expectations -- red lines are removals, green lines are additions,\nNOT errors. Canonical preface:\n\n```\nHere's what changed since the previous draft. Red lines were removed,\ngreen lines were added. Nothing here is broken -- this is a normal\nreview.\n```\n\n? Alternatively, the agent MAY hide the diff entirely on the first\nreview pass and present a plain-English summary of changes instead;\nshow the diff only on the second pass or when the user explicitly asks\nfor it.\n\n- ⊗ Show a red/green diff at first review without a non-alarming\n preface.\n\n## Anti-Patterns\n\n- ⊗ Ask multiple questions in a single message -- one question per turn, always\n- ⊗ Proceed to artifact generation without the confirmation gate -- all captured answers must be displayed and explicitly confirmed\n- ⊗ Omit the default marker from any question -- every question must have a `[default: N]` option\n- ⊗ Omit the \"Other / I don't know\" escape from any question -- every question must have an escape option\n- ⊗ Omit option 0 (freeform conversation escape) from any deterministic question\n- ⊗ Re-ask a question after the user accepted the default -- move on immediately\n- ⊗ Skip the depth gate and generate artifacts with known ambiguity remaining\n- ⊗ Exit the interview without producing a structured answers map for the calling skill\n- ⊗ Combine interview questions with artifact generation in the same message\n- ⊗ Generate an authoritative PRD.md — interview output targets `specification.xbrief.json` narratives only\n- ⊗ Treat PRD.md as a source of truth — it is a read-only export via `task prd:render`\n- ⊗ Auto-advance to the next question on number press without echoing the selection and waiting for confirmation\n- ⊗ Refuse backward navigation during the interview -- the user must be able to revisit previous answers\n- ⊗ Render a deterministic question without the persistent `Enter confirm / b back / 0 discuss` legend directly below the options block **in plain-text rendering mode** (Rule 11 Plain-Text Rendering Mode) -- in click-commit rendering mode the keystroke legend MAY be omitted per Rule 11 Click-Commit Rendering Mode, provided `Back` and `Discuss with agent` still render as clickable options\n- ⊗ Use `Pause`, `Escape`, `Other..`, or any non-self-describing label for slot 0 -- the label MUST be `Discuss with agent` (Rule 10)\n- ⊗ Place slot-0 `Discuss with agent` at the bottom of the options block or merge it with `Other / I don't know` -- slot 0 MUST be first and visually distinct (Rule 10)\n- ⊗ Emit a user-facing question as conversational prose outside the structured tool because the answer content is prose, because the preamble is long, because the question \"feels conversational,\" or because the prior question was plain-text (Rule 2 Always-Structured Rendering, #478)\n- ⊗ Omit `Back` or `Discuss with agent` as clickable options in click-commit rendering (Rule 2 Click-Commit Rendering, #477)\n- ⊗ Treat a click-commit tool's atomic return as a Rule-8-compliant confirmed commit (Rule 2 Click-Commit Rendering, #477)\n- ⊗ Render the Rule 6 Confirmation Gate via a click-commit structured tool on a click-commit host -- the gate MUST be plain-text with a typed `yes` commit (Rule 6 Click-Commit Hosts, #477)\n- ⊗ Render the next user-facing question as plain-text conversational prose because the Rule 6 Gate was just rendered in plain-text -- plain-text mode is released after the typed commit (Rule 6 Mode Restore, #478)\n- ⊗ Render a user-facing question as plain-text because you wanted to include a long preamble -- preamble belongs above the tool call, not instead of it (Rule 2 Preamble Placement, #478)\n",
148
148
  "frontmatter_extra": null
149
149
  },
150
+ {
151
+ "id": "deft-directive-portfolio-priority",
152
+ "description": "Pre-promotion portfolio prioritization: cluster competing open RFCs/issues from the local github-issue cache, emit a propose-not-apply priority brief (conflict matrix, shortlist, park), with epistemic citation gates, then hand off for operator dispose. Use when the operator asks for portfolio priority, a priority brief, competing RFCs, or to cluster open issues. Do NOT trigger on single-item \"what's next?\" ranking (deft-directive-triage / triage:queue) or post-promotion capacity prioritization (#1419/#1511).",
153
+ "triggers": [
154
+ "portfolio priority",
155
+ "priority brief",
156
+ "competing RFCs",
157
+ "cluster open issues",
158
+ "pre-promotion portfolio",
159
+ "portfolio prioritization",
160
+ "rank this backlog slice"
161
+ ],
162
+ "path": "skills/deft-directive-portfolio-priority/SKILL.md",
163
+ "version": "0.1",
164
+ "body": "# Deft Directive Portfolio Priority\n\nPre-promotion **portfolio prioritization pass**: cluster competing RFCs/issues from the local github-issue cache, name conflicts/supersession, emit a **propose-not-apply** priority brief (shortlist + park), then hand off for operator dispose. Parent process + dogfood: **#3198**. Worked example: **#3200** / [`docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md`](../../../docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md).\n\nLegend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.\n\n## When to Use\n\n- Maintainer wants a **portfolio priority** brief across competing open issues / RFCs\n- Operator says: portfolio priority, priority brief, competing RFCs, cluster open issues, pre-promotion portfolio, rank this backlog slice\n- After a large classify / label-mirror partition when humans still need a shortlist (B-side; not the ranker)\n- ⊗ Do NOT use for single-issue \"what's next?\" ranking — that is `deft-directive-triage` + `task triage:queue`\n- ⊗ Do NOT use for post-promotion capacity buckets — that is #1419 / #1511\n- ⊗ Do NOT use for review-handoff briefs — that is #1590\n\n## Stance (hard)\n\n- ! **Propose-not-apply** (#3179): the brief proposes; the operator disposes\n- ⊗ SCM label writes, mirror `--apply`, or re-enrich apply from this skill\n- ⊗ `task triage:accept` / reject / defer / needs-ac / mark-duplicate / bulk-*\n- ⊗ Scope lifecycle (`scope:promote` / `activate` / `complete` / `cancel`) from this skill\n- ⊗ Treat the brief as a decision record — dispose into #1396 / plan-sequence / operator comment\n\n## Inputs\n\n- ! Local `.deft-cache/github-issue/` (bodies available)\n- ! Classify partition via `task triage:classify -- --mirror` (or cached JSON digest) as **FILTER only**\n- ~ Optional theme slice: label family, title prefix, author, epic parent\n\n## Phases\n\n### Phase 0 — Cache freshness\n\n1. ! Run `task verify:cache-fresh`. Exit 0 → proceed. Exit 1 → refresh (`task cache:fetch-all -- --source=github-issue --repo OWNER/NAME`) or **disclose stale** and continue only with operator consent. Exit 2 → `task triage:bootstrap` first.\n2. ~ When zero cache entries, bootstrap/fetch before clustering (#2575).\n3. ⊗ Cluster against a silent stale cache without disclosing staleness.\n\n### Phase 1 — Classify as FILTER only\n\n1. ! Run `task triage:classify -- --mirror` (dry-run; optional `--json`) to partition dispositions (`no_match` / escalate / defer / already planned).\n2. ! Use classify outcomes only to **filter** the portfolio slice (e.g. deep-dive candidates from `no_match`; keep escalate on an interrupt queue).\n3. ⊗ Use classify disposition chips as the **ranker** or final shortlist order.\n4. ⊗ Pass `--apply` or `--re-enrich` with intent to write labels from this skill.\n\n### Phase 2 — Theme / slice selection\n\n1. ! Name an **explicit slice** (label family, title theme, author, or epic). Unattended full-backlog ranking is out of scope.\n2. ! Record scope counts: open in slice, classify partitions inside the slice, exclusions.\n3. ~ Prefer one coherent theme per brief (pilot: patterns + `no_match`).\n\n### Phase 3 — Cluster + conflict matrix + shortlist + park\n\n1. ! Group candidates into **families** (title/theme/body overlap, shared parent epic, layered pack).\n2. ! Build a **conflict / supersession matrix** per family: overlapping roots, pack-vs-atom, research-vs-product.\n3. ! Emit an ordered **shortlist** (deep dive / promote candidates) and a **park list** with park reasons.\n4. ! Separate **interrupt / non-portfolio** rows (escalate, hold, already dispositioned) so they are not ranked against park list.\n5. ⊗ Claim \"#X supersedes #Y\" from titles alone — see Phase 4 epistemic gates.\n\n### Phase 4 — Epistemic gates (MUST)\n\nBefore citing any `#N` in the brief:\n\n1. ! **Verify existence + state** for every cited issue via live `gh api repos/OWNER/NAME/issues/N` (REST) **or** a proven-fresh cache entry whose state matches the claim.\n2. ! **Read the body** of every issue used for shortlist, park-as-superseded, pack membership, or \"decided\" claims.\n3. ! When claiming decided / superseded / closed-by-comment, also **read comments** (REST `issues/N/comments`).\n4. ! State **open/closed accurately**; never invent issue numbers.\n5. ⊗ **Title-only supersession** or ownership claims.\n6. ⊗ Cite PRs as issues without filtering `pull_request` on mixed issue lists.\n\n### Phase 5 — Emit priority brief\n\n! Write markdown matching the pilot section shape (fill with this run's slice):\n\n1. **Header** — process issue (#3198 or project equivalent), parent tracker if any, generated timestamp, stance (propose-not-apply), epistemic method\n2. **Dispose path** — not a decision record; points at #1396 / plan-sequence / operator comment\n3. **Scope of this pass** — counts table (slice filters + partition sizes)\n4. **Interrupt / non-portfolio** — escalate/hold/already planned (do not rank here)\n5. **Conflict / family matrix** — families with verified state + body claims + recommendation\n6. **Shortlist** — ordered P1/P2/… with why\n7. **Park list** — classes, examples, park reasons\n8. **Epistemic limits** — what was cache-only, what needs follow-up probe\n9. **Operator dispose checklist** — accept/edit shortlist, accept park, record dispose, optional plan-sequence; do not treat as mirror apply\n\n~ Prefer writing under `docs/analysis/YYYY-MM-DD-portfolio-priority-brief-<slice>.md` for durable dogfood; chat-only is allowed if the operator declines a file.\n\n~ Link the worked example: #3200 and `docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md` (may live on the pilot PR until merged).\n\n### Phase 6 — Dispose checklist (hand off)\n\n1. ! Present the dispose checklist to the operator (accept/edit shortlist + park; record dispose; optional plan-sequence for P1).\n2. ! Point dispose targets: **#1396**-shaped decision log (or interim issue comment), and/or `task plan-sequence:set`.\n3. ⊗ Auto-promote shortlist into plan-sequence without explicit operator dispose.\n4. ⊗ Exit treating the brief alone as durable prioritization memory (#2741 class).\n\n## Anti-Patterns\n\n- ⊗ SCM label writes / mirror apply / re-enrich apply from this skill\n- ⊗ `triage:accept` or any triage decision verb from this skill\n- ⊗ Scope lifecycle mutations from this skill\n- ⊗ Title-only supersession or unverified `#N` citations\n- ⊗ Full open-backlog unattended ranking without an explicit slice\n- ⊗ Replacing `triage:queue` for buildable work selection\n- ⊗ Treating the brief as the decision record without dispose\n\n## EXIT\n\n! Confirm: `deft-directive-portfolio-priority complete -- exiting skill.`\n~ Chain: operator dispose (#1396 / plan-sequence) · `deft-directive-triage` for single-item queue work · `deft-directive-refinement` after accept on a different path · do **not** chain into mirror apply.\n⊗ Silent exit.\n\n## References\n\n- #3198 process + dogfood · #3201 this skill · #3200 / patterns pilot brief\n- #1396 decision log · #3179 propose-not-apply · #1423 / #3197 classify filter only\n- Siblings: `deft-directive-triage`, `deft-directive-refinement` — not #1419/#1511 post-promotion\n",
165
+ "frontmatter_extra": "triggers:\n - portfolio priority\n - priority brief\n - competing RFCs\n - cluster open issues\n - pre-promotion portfolio\n - portfolio prioritization\n - rank this backlog slice\nmetadata:\n clawdbot:\n requires:\n bins: [\"gh\"]"
166
+ },
150
167
  {
151
168
  "id": "deft-directive-pre-pr",
152
169
  "description": "Iterative pre-PR quality improvement loop. Use before pushing a branch for PR creation -- after completing implementation but before task check. Cycles through Read-Write-Lint-Diff until a full pass produces zero changes.",
@@ -219,7 +236,7 @@
219
236
  ],
220
237
  "path": "skills/deft-directive-release/SKILL.md",
221
238
  "version": "0.1",
222
- "body": "# Deft Directive Release\n\nStructured 8-phase workflow for cutting a v0.X.Y release of the deft framework. Operationalizes the `task release` / `task release:publish` / `task release:rollback` / `task release:e2e` surface introduced in #716 (safety hardening of #74).\n\nLegend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.\n\n**See also**: [deft-directive-swarm](../deft-directive-swarm/SKILL.md) Phase 6 Step 5 (Slack announcement template re-used by Phase 8 below) | [deft-directive-review-cycle](../deft-directive-review-cycle/SKILL.md) (user-gate pattern) | [deft-directive-refinement](../deft-directive-refinement/SKILL.md) (conversational phased flow).\n\n## Platform Requirements\n\n! GitHub as the SCM platform; the **GitHub CLI (`gh`)** must be installed and authenticated. The full pipeline plus the rehearsal target (`task release:e2e`) all dispatch through `gh`.\n\n## Branch-Protection Policy Guard\n\n! Before any Phase 1 state mutation, run the skill-level branch-policy guard documented in `scripts/policy.py` / `scripts/preflight_branch.py` (#746 / #747). Releases run on the configured base branch (default `master`), so the operator MUST be on the explicit-opt-in side of the policy before the pipeline starts writing files.\n\n**Preferred path — typed direct-commit policy opt-out (#1553).** For a release session on the default branch, prefer the audited typed flag over the emergency env-var bypass:\n\n```\ntask policy:allow-direct-commits -- --confirm\n```\n\nThis writes `plan.policy.allowDirectCommitsToMaster = true` on `xbrief/PROJECT-DEFINITION.xbrief.json` with a capability-cost disclosure. After the release completes (or if the session aborts), restore enforcement **and commit the restore in the same closeout** (#2623):\n\n```\ntask policy:enforce-branches\n# enforce flips the typed flag to false locally — the commit that lands that\n# flip cannot use the typed opt-in anymore. Scope the emergency env bypass to\n# ONLY this closeout commit+push (do NOT export it for the whole session):\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git add xbrief/PROJECT-DEFINITION.xbrief.json meta/policy-changes.log\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git commit -m \"chore(policy): restore branch protection after vX.Y.Z\"\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git push origin HEAD\n```\n\n⊗ Leave `allowDirectCommitsToMaster=true` on origin after publish. ⊗ Run `policy:enforce-branches` and leave the dirty restore under protection ON without committing (forces a follow-up PR — the v0.79.0 / #2619 failure mode).\n\n**Branch-guard probe (either path).** Regardless of which opt-out path you chose, confirm the guard passes before Phase 1 mutates state:\n\n```\nuv run python scripts/preflight_branch.py --project-root . --quiet || exit 1\n```\n\nor invoke `task verify:branch`. This is the canonical surface that surfaces the policy state to the operator before the pipeline starts writing files. The release pipeline's other safety surfaces (the dirty-tree guard, base-branch check, `task ci:local` gate) remain independent of this check.\n\n**Emergency env-var bypass — narrow scope only (#1553).** `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` is process-wide: every child process, nested test, and temporary repository spawned from the same shell inherits it. During the v0.43.0 release attempt, wrapping the entire `task release` invocation in this env var let the bypass leak into the Step 5 `task ci:local` preflight, which caused `TestWriteConsumerGitHooks_VendoredCommitBlocked_RealGit` to fail because the vendored test repo allowed a direct `master` commit the test expected the hook to block.\n\n- ! Prefer `task policy:allow-direct-commits -- --confirm` for release sessions instead of exporting `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` for the whole shell.\n- ⊗ Wrap `task release`, `task ci:local`, or `task check` in `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` -- the env var is inherited by every subprocess and can produce false preflight failures before any release mutation.\n- ? If the env-var path is unavoidable, scope it to a **single** branch-guard probe only (e.g. `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 task verify:branch`) and do NOT export it for the release session. The release pipeline itself passes the bypass only in scoped subprocess `env=` for its authorised commit/tag/push mutations (#867); operators MUST NOT mirror that pattern at the shell level.\n\nThe release pipeline's Step 9/10/11 git mutations carry the bypass in subprocess `env=` only (`scripts/release.py::_release_subprocess_env`, #867) so the parent shell stays clean. Operator-side env-var exports defeat that isolation.\n\n## Deterministic Questions Contract\n\n! Every numbered-menu prompt rendered in this skill (Phase 1 version-bump magnitude check, Phase 2 dry-run review `yes`/`back`/`quit`, Phase 5 optional `defer`/`rollback`/`Discuss` (happy path auto-publishes after draft QA)) MUST follow [`../../contracts/deterministic-questions.md`](../../contracts/deterministic-questions.md): the final two numbered options MUST be `Discuss` and `Back`, in that order. Existing `back`/`quit` options remain valid; this contract simply adds `Discuss` as a peer alongside `Back`. The Discuss-pause semantic is documented verbatim in the contract -- implicit resumption is forbidden.\n\n## When to Use\n\n- User says \"release\", \"cut release\", \"v0.X.Y\", \"publish release\", \"ship a release\"\n- The framework's `[Unreleased]` CHANGELOG section is non-empty and the operator wants to cut a tagged release\n- A previous release rehearsal succeeded and the operator is ready for the production cut\n\n## Phase 1 — Pre-flight\n\n! Validate the local + remote state before any irreversible action.\n\n\n### Parallel prep — #1880 Gap D (#2692)\n\n! Phase 1 long steps (`task reconcile:issues -- --apply-lifecycle-fixes`, cache refresh when ritual-stale, `task ci:local` / `task check`) and Phase 3 `task release:e2e` MUST be backgrounded or subagent-dispatched when the host supports it (Cursor: Task tool `run_in_background: true`), with progress surfaced via DONE/heartbeat — same ownership as review-cycle / merge-ready workers (#1880 Gap D). The operator conversation MUST stay interactive for version magnitude confirmation, `--summary`, and the Phase 2 dry-run `yes`/`back`/`quit` gate while prep runs.\n\n! **Checklist:** Phase 1 prep parallelized — long prep started in background before (or while) collecting version magnitude / summary / npm irrevocability disclosure.\n\n! On Windows PowerShell, do NOT wrap long task output in `Select-Object -Last` (it buffers until the process exits); stream to the terminal or log to a file and read incrementally. See `scm/github.md` § #2646 / Windows encoding guidance for related PS pitfalls.\n\n⊗ Foreground-block the operator chat on reconcile / `ci:local` / `release:e2e` when background dispatch is available (#1880 Gap D / #2692).\n\n### Fixable check failure — file-and-merge before resume (#2859)\n\n! When Step 4 (`task ci:local` or `task check`) fails on a **fixable product or test defect** (hang, failing test, validation bug — not operator env misconfiguration), the release cut MUST pause and route the blocker through normal issue → xBRIEF → feature branch → PR → merge before resuming Phase 1.\n\n? **Step 5 branch-coverage threshold misses** during `task release` (Vitest branch coverage below 85% with no other failure mode) are carved out to § Step 5 branch-coverage threshold — open-issue ledger hatch (#2866) below — not this file-and-merge path.\n\n**Required path:**\n1. File a GitHub issue with root cause, recurrence signature, and acceptance criteria.\n2. Ingest / promote / activate scope xBRIEF; implement on a feature branch with `drive-to: merge-ready`.\n3. Merge; confirm `task check` / `ci:local` is green for the failure mode.\n4. Resume the release cut from Phase 1 (re-run Step 4).\n\n⊗ Lead with an inline-only hotfix on the release branch / default branch without a tracked issue and merged PR.\n⊗ Suggest untracked `--skip-ci` or `--allow-skip-ci` without a **tracked issue number** as the first recovery when the failure is a clear, shippable bug.\n⊗ Continue the cut with \"raise timeouts,\" \"fix it here,\" or other shortcut theater before file-and-merge completes.\n\n? **`--skip-ci` / `--allow-skip-ci=#N` remains valid** only under explicit operator incident review with a **tracked issue** cited on the flag (Phase 4 contract). It is NOT a substitute for filing and merging a fixable defect discovered in Phase 1.\n\n**AGENTS.md bulk rejected (#2859):** Expanding `AGENTS.md` / `content/templates/agents-entry.md` with an always-on pin for this release-phase reminder was considered and **rejected**. This rule lives in the release skill (and optional lesson); do NOT add AGENTS.md / agents-entry bulk for it.\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Fixable check failure during release for the operator runbook and the existing `--allow-skip-ci=#N` incident contract.\n\n### Step 5 branch-coverage threshold — open-issue ledger hatch (#2866)\n\n! When **`task release` Step 5** fails on Vitest coverage below the 85% goal, apply this hatch **only** when **branches** is the **sole** metric below 85% (hairline branch miss — lines, functions, and statements all ≥ 85%). Confirm via the Step 5 failure output or `task coverage:hotspots` before hatching. If **any other metric** is also below 85%, or the failure is a hang / failing test / non-coverage defect, STOP — use § Fixable check failure — file-and-merge before resume (#2859) instead.\n\n**Runtime disclosure (#2573):** `--allow-coverage-debt=#N` sets vitest coverage thresholds to zero for the release Step 5 run (`vitest.config.ts`). The hatch is justified only for branch-only hairlines; the filed debt issue MUST require restoring **all four metrics** (lines, functions, branches, statements) to ≥ 85% before close.\n\n**Open-issue ledger (release-scoped only):**\n\n1. Check for an **open coverage-debt tracking issue** (unpaid hatch from a prior cut) — union all three probes and dedupe by issue number:\n - **Marker search** (new-format debt issues):\n ```\n gh issue list --repo <owner>/<repo> --state open --search \"coverage-debt in:title,body\" --limit 20\n gh issue list --repo <owner>/<repo> --state open --search \"allow-coverage-debt in:body\" --limit 20\n ```\n - **CHANGELOG citation scan** (legacy hatch issues filed before markers were mandatory): parse `CHANGELOG.md` `[Unreleased]` plus the last three `## [version]` sections for `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations; for each `#N`, run `gh issue view N --json state --jq .state` — `OPEN` counts as unpaid debt even when title/body lack the new markers (#2866).\n2. If **no open coverage-debt issue exists** → file `#N` with title prefix `coverage-debt:` and body containing both markers `coverage-debt` and `--allow-coverage-debt`, documenting measured metrics, branch-only trigger, and acceptance criteria (restore **all four** coverage metrics ≥ 85%). Continue the cut with `--allow-coverage-debt=#N` on `task release` (PowerShell-safe: `--allow-coverage-debt=N` or `--allow-coverage-debt=\"#N\"` — see Anti-Patterns #2621). The open `#N` remains WIP until coverage is restored and the issue is closed.\n3. If an **open coverage-debt issue from a prior hatch still exists** → ⊗ soft-pass again; restore real coverage (all four metrics ≥ 85%) and close the debt issue before the cut proceeds.\n\n⊗ Auto-pass on a near-miss band without `#N` (#2573).\n⊗ Silent soft-pass with no tracked issue.\n⊗ File a debt issue without `coverage-debt` / `allow-coverage-debt` markers in title or body — the ledger query will miss it and permit a consecutive soft-pass (#2866).\n⊗ Use this carve-out when lines, functions, or statements are also below 85%, or for hangs, failing tests, or non-coverage Step 5 failures — those stay under #2859 file-and-merge.\n⊗ Treat file-debt-then-hatch as the default for ordinary PR / `task check` work outside a release cut — this hatch is release-scoped only.\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Coverage debt hatch during release.\n\n### Routine vs hard cut for Step 5 (#2953)\n\n! Treat **hard cut** as the default: full Step 5 (`task check` + Vitest coverage) with no `--skip-ci`.\n\n~ Treat **routine cut** as the same full Step 5, sped up by default-excluding `.deft-scratch/` (and legacy `swarm-worktrees/`) from content/link/path/build-dist walks, plus pre-cut hygiene (green required checks on the tip SHA; prune stale `.deft-scratch/worktrees`).\n\n! Confirm required CI is green on the tip you will tag when aiming for a calm routine cut — green tip CI is a precondition, not a replacement for Step 5.\n\n⊗ Invent a silent lighter Step 5 that skips or soft-passes coverage without an explicit hatch (`--allow-coverage-debt=#N` for branch-only hairlines, or `--skip-ci` + `--allow-skip-ci=#N` for incidents). Silent soft-pass is forbidden in every mode.\n\n~ Prefer pruning abandoned worktrees / cutting from a clean clone over re-scanning scratch noise.\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Routine vs hard cut for Step 5 (#2953).\n\n\n~ **Frozen Go-installer bridge (#1912 / #1972 / #1987):** by default a release tag *above* the frozen line (the `LAST_GO_INSTALLER` constant in `packages/core/src/legacy-bridge/sot.ts`) will NOT rebuild the 6 Go binaries -- the CI `freeze-gate` job in `.github/workflows/release.yml` skips the build (the run stays green; npm still ships from the separate `npm-publish.yml`). If this release must rebuild the Go installer, follow the runbook in [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Frozen Go-installer bridge: roll `LAST_GO_INSTALLER` forward to the cut tag BEFORE tagging (pinning to the exact cut tag both releases the gate AND re-freezes at the new line), then see that section's \"After the release\" step for the re-pin.\n\n1. ! Verify the operator is on the configured base branch (default `master`) and the working tree is clean\n2. ! Confirm the next version number (`X.Y.Z`) with the user. Major / minor / patch decision flows from the `[Unreleased]` content (breaking change → major; new feature → minor; fix-only → patch)\n3. ! Inspect `[Unreleased]` content vs the proposed version bump. If a breaking change appears in `### Changed` / `### Removed` but only a patch is proposed, surface the mismatch and ask the user to choose\n4. ! Verify `task ci:local` passes locally (or `task check` as the graceful-degradation fallback per `tasks/release.yml` line 9-10). The `task release` script will refuse to proceed otherwise -- but Phase 1 catches it earlier — **on failure from a fixable defect, STOP and follow § Fixable check failure below (#2859); do NOT proceed to step 5**\n5. ! Verify `gh auth status` reports authenticated (`task release` will refuse otherwise)\n6. ! **Run `task reconcile:issues -- --apply-lifecycle-fixes` to clear any closed-issue / non-completed-folder xBRIEFs before invoking `task release`** (#734). The release pipeline carries the deterministic gate at Step 3 (`scripts/release.py::check_vbrief_lifecycle_sync`, refuses with `EXIT_VIOLATION` on any Section (c) mismatch), but Phase 1 is the operator's first-line defence -- running the apply-mode flag here is the canonical clean path; `--allow-vbrief-drift` on the pipeline exists only as the explicit-acknowledgment escape hatch (analogous to `--allow-dirty`). The recurrence record is the v0.21.0 cut, which surfaced 13 stranded xBRIEFs (8 cycle-relevant + 5 historical residue) post-publish; the gate now blocks that drift before any irreversible action\n7. ! **Verify the proposed `v<version>` tag is not already in use locally, on origin, or as a published GitHub release** (#784). The release pipeline carries the deterministic gate at Step 4 (`scripts/release.py::check_tag_available`, refuses with `EXIT_VIOLATION` before any state mutation -- CHANGELOG promotion, ROADMAP refresh, build, commit), but Phase 1 is the operator's first-line defence. Quickly probe with `git tag -l v<version>` (local), `git ls-remote --tags origin refs/tags/v<version>` (remote), and `gh release view v<version> --repo <owner>/<repo>` (release-only, where `gh release view` exits 0 only when the release exists). The recurrence record is the v0.22.0 → v0.23.0 release attempt on 2026-05-01: the operator typed `0.22.0` (the prior release from 12 hours earlier) and the legacy pipeline ran 8 steps before failing at `git tag` -- leaving a wrong-version local commit + `dist/deft-0.22.0.zip` orphan + manual `git reset --hard` recovery. The new pre-flight gate blocks that mode before any irreversible action\n8. ! **Verify the npm credential path is configured before cutting the tag** (#1910, #1909). A `v*` tag now auto-triggers `.github/workflows/npm-publish.yml`, which publishes the four `@deftai/directive*` packages with `npm publish --provenance`. Confirm the publish path can authenticate: either the `NPM_TOKEN` repo secret is present (`gh secret list --repo <owner>/<repo>` shows `NPM_TOKEN`) OR an npm OIDC trusted publisher is configured for the `@deftai/directive*` packages. If neither is in place, WARN loudly that the tag will fire a publish job that fails (red X on the tag, no packages) -- the operator may still proceed for a GitHub-only release, but the npm channel will not land until #1909's credential is provisioned. Cross-reference #1909.\n9. ! **Disclose npm irrevocability before any tag push (#1972, #2002).** A `v<version>` tag push is the **real npm publish gate** -- NOT Phase 5 or `task release:publish`. Tag push fires `.github/workflows/npm-publish.yml` in a separate workflow that is NOT draft-gated; npm packages ship immediately and **cannot be retracted** (`npm unpublish` is forbidden). Recovery is forward-only: deprecate, dist-tag, or ship a patch. The operator's explicit `yes` in Phase 2 (dry-run) and the decision to invoke `task release` in Phase 4 are the last human gates before npm goes live. Phase 5 only controls GitHub release visibility (draft → public); it does NOT gate npm.\n10. ~ Ask the operator for an optional one-line release **summary** (recommended 80-160 chars; can be skipped). The summary is the canonical narrative for THIS release across three audiences: (a) injected as a Markdown blockquote at the top of the promoted `CHANGELOG.md [<version>]` section, (b) auto-flowed into the GitHub release body via the existing `_section_for_version` pickup, and (c) populated VERBATIM into the Phase 8 Slack `*Summary*:` slot. Capture the wording once here; do NOT regenerate per-audience downstream\n\n⊗ Skip the version-bump magnitude check -- a patch release that ships breaking changes is the kind of regression that Repair Authority [AXIOM] (#709) is designed to prevent.\n\n⊗ Skip the xBRIEF-lifecycle-sync check (#734); the gate exists because operators consistently forget the manual `task scope:complete` move step. The v0.21.0 cut surfaced 13 stranded xBRIEFs (8 cycle-relevant + 5 historical residue) post-publish as the recurrence record this gate prevents. If `task release` reports `[3/13] Pre-flight xBRIEF lifecycle sync... FAIL (<count> mismatches; run task reconcile:issues -- --apply-lifecycle-fixes to fix)`, the canonical recovery is the apply-mode invocation -- `--allow-vbrief-drift` is reserved for cases where the operator has explicitly reviewed the drift and chosen to defer the lifecycle reconcile to the next refinement pass (e.g. an emergency hot-fix release).\n\n⊗ Skip the tag-availability check (#784); the gate exists because the legacy 12-step pipeline only invoked `git tag` at Step 9, after Steps 1-8 had already mutated state (CHANGELOG promoted, ROADMAP refreshed, dist built, release commit made locally). A duplicate-tag failure at Step 9 stranded the operator with an unpushed wrong-version commit + orphaned `dist/deft-<wrong>.zip` artifact + manual `git reset --hard` recovery (forbidden by AGENTS.md SCM rules without explicit permission). The recurrence record is the v0.22.0 → v0.23.0 release attempt on 2026-05-01. If `task release` reports `[4/13] Pre-flight tag availability... FAIL (<surface> tag v<version> already exists ...)`, the canonical recovery is to choose a different version (the most likely cause is operator typo of a prior release).\n\n⊗ Hand-write a different one-line narrative for each of the three downstream surfaces (CHANGELOG / GitHub release / Slack) -- that drift is exactly the gap the `--summary` flag is designed to close. If the operator insists on per-audience tone, populate the canonical `--summary` ONCE here and document the deviation in the Phase 8 anti-pattern.\n\n## Phase 2 — Dry-run review\n\n! Invoke `task release -- <version> --dry-run --skip-tag --skip-release` and present the plan to the user. If Phase 1 collected an operator summary, also pass `--summary \"<text>\"` so the dry-run preview reflects the canonical narrative the operator just authored.\n\n```\ntask release -- <version> --dry-run --skip-tag --skip-release --summary \"<text>\"\n```\n\nThe dry-run prints `[N/13] <step>... DRYRUN (would <action>)` for every pipeline step (Step 13 is the post-create verify-isDraft gate added by #724; Step 4 is the tag-availability pre-flight gate added by #784). Step 6 (CHANGELOG promotion) surfaces whether a summary was supplied (truncated to ~60 chars in the preview) so the operator can validate the wording before any file is written. Capture the output and present it to the user, then wait for explicit confirmation before continuing.\n\n! Wait for explicit user confirmation: `yes` / `back` / `quit`. Remind the operator that Phase 4's tag push will irrevocably publish npm (#1972) -- this `yes` is the last safe abort before that channel opens.\n- `yes` (or `confirmed` / `approve`) → proceed to Phase 3\n- `back` → return to Phase 1 for re-validation (e.g. user wants to amend the version or `[Unreleased]` content)\n- `quit` → abort the workflow cleanly; no state changes\n\n⊗ Skip the dry-run preview. The dry-run is the operator's last opportunity to catch a bad version number, malformed CHANGELOG, or wrong base branch before the pipeline starts writing files.\n\n## Phase 3 — E2E sanity\n\n! Invoke `task release:e2e` against an auto-created temp repo to verify the full pipeline shape works end-to-end before touching the real repo.\n\n! **#1880 Gap D (#2692):** `task release:e2e` is a long-running step — MUST background / subagent-dispatch it when the host supports it so Phase 2 confirmation and other human gates stay interactive. Do not wrap its output in PowerShell `Select-Object -Last`.\n\n```\ntask release:e2e\n```\n\nThe harness provisions `deftai/deftai-release-test-<ts>-<uuid6>`, runs the smoke-test rehearsal, and **by default keeps** the temp repo (#2572). Stderr always includes the full `owner/slug` and a copy-pasteable manual-cleanup command (`gh repo delete <owner>/<slug> --yes`). Privileged environments (CI or an operator with `delete_repo`) MAY pass `task release:e2e -- --destroy-repo` to attempt auto-delete; destroy failure emits a WARN and does **not** block Phase 4 when the rehearsal succeeded.\n\n! After Phase 3, the agent MUST NOT retry or escalate temp-repo deletion. Include any leftover temp repo(s) in the phase summary for the operator to clean up manually.\n\n! Treat a non-zero exit from `task release:e2e` as a hard refusal to proceed to Phase 4. Surface the diagnostic and ask whether to debug (return to Phase 1) or abort (`quit`).\n\n? **Skip allowed** when the operator has just run `task release:e2e` successfully against the same branch in the past 30 minutes. Note the prior run timestamp in the user-facing summary.\n\n! **`task release:e2e` now also rehearses the npm publish (#1910).** Unless `--skip-npm` is passed (or `npm` is absent from PATH, which soft-skips), the rehearsal runs `npm publish --dry-run --access public` for all four `@deftai/directive*` packages against the throwaway clone in dependency order (types -> core -> content -> cli), after `pnpm install` + `pnpm -w run build` and a version-alignment pass. This catches a broken `files` allowlist, a version-drift bug, or a dependency-order error BEFORE the real `v*` tag fires the publish workflow -- without touching the real registry. The install+build exceeds the <90s fast budget, so pass `task release:e2e -- --skip-npm` when you only need the GitHub-pipeline shape check.\n\n! **Tag -> npm coupling + irrevocability (#1910, #1972, #2002).** A `v<version>` tag is a TWO-channel action: the GitHub release (this skill's pipeline) AND `.github/workflows/npm-publish.yml`, which runs in a SEPARATE workflow that does NOT block the GitHub release and is NOT draft-gated. The npm workflow derives the published version from the tag (`${GITHUB_REF_NAME#v}`); this skill owns the version chosen in Phase 1. These MUST stay consistent -- the tag you cut IS the npm version that ships; there is no separate npm version bump. **npm publish is irrevocable** (#1972): once the tag fires, packages are live on npm and cannot be unpulled; `task release:rollback` does NOT retract npm (forward-only recovery). A red npm job on a green GitHub release means the npm channel did not ship (verify in Phase 5/7).\n\n## Phase 4 — Production draft\n\n! **Last human gate before npm (#1972, #2002).** Immediately before invoking `task release`, re-state that the tag push in this step will irrevocably publish all four `@deftai/directive*` packages to npm via `.github/workflows/npm-publish.yml`. There is no undo on npm; only forward recovery (deprecate / dist-tag / patch). Proceed only when the operator explicitly confirms.\n\n! Invoke `task release -- <version>` (NO `--dry-run`, NO `--skip-tag`, NO `--skip-release`, NO `--skip-ci`). If Phase 1 collected an operator summary, pass `--summary \"<text>\"` so the production cut writes the same blockquote the dry-run previewed.\n\n⊗ Use `--skip-ci` on a production cut except under explicit operator incident review — it skips Step 5 vitest coverage and ships untested npm builds (#2652). When unavoidable, pass `--allow-skip-ci=#N` citing the tracked issue; Step 5 emits a loud WARN. See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Vitest coverage hang recovery. The next patch after the hang fix must cut without `--skip-ci`.\n\n```\ntask release -- <version> --summary \"<text>\"\n```\n\nPer #716 default-draft hardening, this lands the release as a `--draft` on the real repo. Binaries upload via release.yml CI, but the artifact is NOT yet visible to consumers. The operator-authored summary becomes part of the promoted `CHANGELOG.md [<version>]` section AND the GitHub release body (auto-pickup via `_section_for_version`). The same wording is the canonical source for the Phase 8 Slack `*Summary*:` slot.\n\n! **Maintainer-mode release notes auto-lead with an \"Upgrading from an older version?\" banner (#1413).** When the cut targets the canonical framework repo (`deftai/directive`), `scripts/release.py` Step 12 prepends the banner from the editable template at `.github/release-notes/upgrade-banner.md` to the notes passed to `gh release create` (via `_prepend_upgrade_banner`). The banner points consumers at the canonical `deft-install --yes --upgrade --repo-root . --json` upgrade command and #1411. This is **GitHub-release-body-only** -- it is NEVER injected into `CHANGELOG.md`, so the CHANGELOG section and the release body intentionally differ by this leading block. To change the wording, edit the template file; do not hand-edit the published release body. **Consumer-mode releases (any non-`deftai/directive` repo) are unaffected** -- a downstream project that vendors the release pipeline never inherits deft's upgrade guidance. A missing/unreadable template degrades gracefully (notes ship without the banner; the cut is never blocked).\n\n! **Verify isDraft within 5 seconds; flip immediately if not (#724).** Immediately after `gh release create --draft` returns success, `scripts/release.py` Step 11 polls `gh release view v<version> --json isDraft` up to 5 times at 1-second intervals. If the release exists with `isDraft=false`, the pipeline auto-flips it via `gh release edit v<version> --draft=true` and emits a `WARNING: release landed as public; flipping to draft (defense-in-depth, see #724)` line. This closes the ~90-second public-exposure window observed during the v0.21.0 cut where a manual recovery created a public release before the operator noticed and flipped it. The verify gate is defense in depth even when `--draft` was passed correctly: it catches the case where `gh release create` partially succeeded (release record written, error returned) AND the operator-error variant where an alternate code path sent the release without `--draft`. A release-not-found-within-budget result emits a WARN and does NOT fail the pipeline (release.yml CI may still be processing).\n\n! Wait for `task release` to exit 0 before continuing. A non-zero exit means the pipeline halted partway through; consult Phase 7's `task release:rollback` recovery before retrying.\n\n⊗ Pass `--no-draft` here unless the operator has explicitly opted into direct-publish (e.g. automated security patch). The default-draft contract is the foundation of the safety hardening surface.\n\n⊗ Skip the post-create verify-isDraft gate -- the gate is the only reliable safety net against \"create call exited 0 but the release somehow landed as public\" variants (#724). If `task release` is invoked manually outside the canonical `scripts/release.py` flow, the operator MUST run `gh release view v<version> --json isDraft` followed by `gh release edit --draft=true` on `isDraft=false` BEFORE handing off to Phase 5.\n\n## Phase 5 — GitHub draft QA (optional; NOT the npm authority gate)\n\n! After `task release` exits 0, QA the **GitHub draft release** only. npm packages typically **already shipped** when the tag push in Phase 4 fired `.github/workflows/npm-publish.yml` (#1972, #2002). Phase 5 is NOT a \"user-only authority before going live\" gate for the release as a whole -- it is optional draft QA for GitHub assets, notes, and binaries.\n\n1. ! **Verify npm publish status FIRST (in parallel with draft inspection).** Run `gh run list --workflow=npm-publish.yml --repo <owner>/<repo> --limit 5` and confirm the tag run for `v<version>` is `completed`/`success`. If npm failed, surface immediately -- the GitHub draft QA is secondary to a red npm channel.\n2. ! Run `gh release view v<version> --json url,name,body,assets,isDraft --repo <owner>/<repo>` and present the output to the user\n3. ! Surface the asset list (size + filename) so the user can verify binaries uploaded correctly\n4. ! Surface the auto-generated release notes (or the CHANGELOG section that was promoted into the release body)\n\n### Happy path (default when npm succeeded and draft assets look correct)\n\n! When the npm workflow succeeded AND draft assets/notes pass inspection, **auto-proceed to Phase 6 Publish branch** -- run `task release:publish -- <version>` without a redundant human publish prompt (#2002). npm already shipped at tag push; waiting for a separate `publish` confirmation does not protect the npm channel.\n\n? **Operator override:** if the operator wants to hold the GitHub release in draft (e.g. embargo, last-minute notes edit), they MAY say `defer` before auto-publish runs.\n\n### Exception paths (operator-initiated)\n\n- `rollback` → proceed to Phase 6 (Rollback branch). **Reminder:** rollback unwinds the GitHub release only; npm packages already published at tag push are NOT retracted (#1972).\n- `defer` → halt and exit. Surface the draft URL so the operator can return later with `task release:publish -- <version>` or `task release:rollback -- <version>`.\n\n⊗ Treat Phase 5 as the npm publish-authority gate -- npm ships at tag push (Phase 4), not at `task release:publish`. A human `publish` prompt here is redundant when npm already succeeded and only delays flipping the GitHub draft to public.\n⊗ Skip npm workflow verification in Phase 5 and defer it entirely to post-publish Phase 7 -- npm status MUST be checked before or in parallel with the GitHub publish flip.\n\n## Phase 6 — Publish or rollback\n\n! Branch on the Phase 5 outcome. The happy path auto-enters the Publish branch when npm succeeded and draft QA passed (#2002).\n\n### Publish branch (happy path auto-run, or resumed after `defer`)\n\n```\ntask release:publish -- <version>\n```\n\nThe companion script flips `--draft=false`, then re-reads the release to verify `isDraft == false` actually flipped. State machine:\n- `draft` found → flip to public; verify; exit 0\n- already `published` → exit 0 no-op (idempotent re-runs are safe)\n- `not-found` → exit 1 (cannot publish a missing release)\n- gh-error → exit 1 with diagnostic\n\n! Wait for `task release:publish` to exit 0 before continuing. On the happy path this runs immediately after Phase 5 draft QA without a separate human publish prompt.\n\n### Rollback branch (user said `rollback`)\n\n```\ntask release:rollback -- <version>\n```\n\nThe state-aware unwind detects the post-release state and applies the matching tiered recovery. Time-windowed download-count guard:\n- release age `< 5 min` → threshold = 0 (rollback safe; nobody noticed yet)\n- release age `5-30 min` → threshold = max(`--allow-low-downloads`, 10) (filters bot fetches)\n- release age `> 30 min` → refuse without `--allow-data-loss`\n\nThree escape hatches (escalating warnings):\n- `--allow-low-downloads N` -- accept up to N downloads\n- `--allow-data-loss` -- accept any count (consumer impact)\n- `--force-strict-0` -- require exactly 0 regardless of release age\n\nRace-condition mitigation: `download_count` is double-read with a 5s sleep between reads; rollback only proceeds if both reads agree below threshold.\n\n! When the guard refuses, surface the recommendation to the user: rollback is risky on a released artifact with non-zero downloads. Prefer the **hot-fix path** (cut the next patch with a withdrawal note in `[Unreleased]/Changed` rather than deleting the broken release).\n\n! **`task release:rollback` does NOT retract npm (#1972, #2002).** Rollback unwinds GitHub release state (draft/public, tag, assets) only. npm packages published at tag push remain on the registry irrevocably. Recovery is forward-only: deprecate the bad version, move a dist-tag, or ship a patch release.\n\n## Phase 7 — Post-publish verification\n\n! Only enter Phase 7 if Phase 6 took the Publish branch (rollback branch ends here with the unwind log).\n\n1. ! **Re-verify npm publish landed (#1910, #1909, #2002).** Phase 5 checked workflow status before the GitHub publish flip; Phase 7 confirms registry truth AFTER `task release:publish`. For each of `@deftai/directive-types`, `@deftai/directive-core`, `@deftai/directive-content`, and `@deftai/directive`, run `npm view <pkg>@<version> version` (expect `<version>`) and confirm provenance on the npm page. A green GitHub release with missing npm packages means consumers cannot `npm i -g @deftai/directive@<version>` -- escalate immediately. (Real-registry verification depends on #1909's credential; until then verify workflow-run status and flag credential gaps.)\n2. ! Verify GitHub auto-closed the discrete-task issue(s) referenced via `Closes #N` in the release notes (mirrors `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 2)\n3. ! Run `gh issue view <N> --json state --jq .state` for each closed issue. If any didn't auto-close, manually close with `gh issue close <N> --comment \"Closed by release v<version> (squash auto-close did not trigger)\"` (Layer 1, #167)\n4. ! Verify ROADMAP.md correctness via `task roadmap:render` (the release pipeline already invoked this; Phase 7 is the second-pass sanity check)\n5. ! Verify binaries are downloadable from the public release URL: `gh release view v<version> --json assets --jq '.assets[].url'` and curl one to confirm 200 OK\n6. ! For any umbrella / staying-OPEN issue (`Refs #N`) referenced in the release notes, run the Layer 3 reopen sweep from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1: any protected issue that auto-closed MUST be reopened with a comment citing #701\n\n⊗ Skip the post-publish verification. The closing-keyword false-positive (Layer 1 / Layer 2 / Layer 3) and the incremental-renderer-drift (#641, #614) are exactly the kind of issues that surface only AFTER a release is public.\n\n## Phase 8 — Slack announcement\n\n! Generate the canonical Slack release announcement and present it to the user for copy-paste, re-using the template from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 5.\n\nThe announcement block MUST include:\n\n```\n:rocket: *deft v<version>* -- <release title>\n\n*Summary*: <one-sentence description of the release scope>\n\n*Key Changes*:\n- <bullet per significant change, 3-5 items max>\n\n*Stats*: 1 release | ~<duration> elapsed | <N> commits since v<previous>\n*Release*: <GitHub release URL>\n```\n\n! Populate version from the freshly-published `gh release view v<version>` output. Populate release title from the CHANGELOG section heading (or the GitHub release title). Summarize key changes from the promoted `[Unreleased]` -> `[<version>]` CHANGELOG section (NOT raw commit messages). Populate stats from `git log v<previous>..v<version> --oneline | wc -l`.\n\n! Populate the `*Summary*:` slot VERBATIM from the operator-authored blockquote at the top of the CHANGELOG `[<version>]` section (the line beginning with `> ` immediately after the `## [<version>] - <date>` heading). The Phase 1 prompt + Phase 4 `--summary` flag exist precisely so this populate step is mechanical -- one canonical narrative authored once at Phase 1, propagated through Phase 4 promotion, and copy-pasted here without re-authoring. If the CHANGELOG section has no blockquote (operator skipped the Phase 1 prompt), generate a one-sentence summary from the `### Added` / `### Changed` bullets and surface to the operator that this is a regenerated narrative (NOT canonical) so they can decide whether to amend the CHANGELOG before publishing.\n\n! Present the block as a code-fenced snippet the user can copy directly. Do NOT post to Slack from inside this skill -- the user owns the actual broadcast.\n\n## Skill Completion\n\n! When Phase 8 completes (or when Phase 5 took the `defer` / `quit` path, or when Phase 6 completed the rollback branch), explicitly confirm skill exit:\n\n```\ndeft-directive-release complete -- exiting skill.\nNext: <one-line guidance>\n```\n\nWhere `<one-line guidance>` is one of:\n- \"release v<version> live -- monitor consumer reports for ~24h before cutting v<next>\"\n- \"release v<version> rolled back -- the underlying defect needs a hot-fix in the next CHANGELOG entry\"\n- \"release deferred -- resume by running `task release:publish -- <version>` (GitHub only; npm already shipped at tag push) or `task release:rollback -- <version>` (GitHub unwind only; npm is forward-recovery) when ready\"\n\n⊗ Exit silently without confirming completion or providing next-step guidance.\n\n## Anti-Patterns\n\n- ⊗ Foreground-block the operator chat on Phase 1 long prep (`reconcile:issues`, cache refresh, `ci:local` / `check`) or Phase 3 `release:e2e` when background / subagent dispatch is available (#1880 Gap D / #2692) — the interactive channel must stay free for version confirmation, `--summary`, and the Phase 2 dry-run gate\n- ⊗ Wrap long release-prep task output in PowerShell `Select-Object -Last` — it buffers until exit and makes the session look hung (#2692)\n- ⊗ Run `task release` without a Phase 2 dry-run preview -- the dry-run is the only safe place to catch a bad version, malformed CHANGELOG, or wrong base branch\n- ⊗ Skip Phase 3 (e2e rehearsal) on the assumption that \"the dry-run is enough\" -- the e2e harness catches gh-CLI auth issues, repo permission gaps, and pipeline-shape regressions that the dry-run cannot detect\n- ⊗ Pass `--no-draft` to `task release` without explicit operator opt-in -- the default-draft contract is the foundation of the safety hardening surface\n- ⊗ Treat Phase 5 as the npm authority gate or require a redundant human `publish` prompt when npm already succeeded -- npm ships at tag push (#1972); Phase 5 is GitHub draft QA only\n- ⊗ Expect `task release:rollback` to retract npm packages -- rollback is GitHub-only; npm recovery is forward-only (deprecate / dist-tag / patch)\n- ⊗ Run `task release:rollback` against a release that has > 30 minutes of consumer-driven downloads without first weighing the hot-fix path -- a withdrawal note in the next patch is almost always less disruptive than deleting a public artifact\n- ⊗ Use `--allow-data-loss` without first reading the script docstring's hot-fix-path recommendation -- the flag is an explicit acknowledgment of consumer impact, not a default\n- ⊗ Skip the Phase 7 Layer 3 reopen sweep -- protected umbrellas can auto-close on a release-merge squash even when the release notes use `Refs #N` only\n- ⊗ Post the Phase 8 Slack announcement directly from this skill -- the user owns the broadcast; the skill only generates the template\n- ⊗ Hardcode `master` as the base branch -- delegate to the configured base branch from `task release --base-branch <branch>`\n- ⊗ Skip the post-create verify-isDraft gate (#724) -- a successful `gh release create` exit code does NOT prove the release actually landed in draft state; the 5-second poll-and-flip gate in `scripts/release.py` Step 11 is the only safety net against operator-error variants and partial-success races, and any manual recovery path that bypasses `scripts/release.py` MUST run `gh release view --json isDraft` followed by `gh release edit --draft=true` on `isDraft=false` before handing off to Phase 5\n- ⊗ Manually rewrite the Phase 8 Slack `*Summary*:` line to deviate from the CHANGELOG `[<version>]` blockquote -- the canonical narrative is authored ONCE at Phase 1 via `--summary` and propagates verbatim across all three audiences (CHANGELOG / GitHub release body / Slack). Per-audience hand-edits create documentation drift that the deterministic `--summary` flow is designed to prevent. If the operator wants Slack-specific tone, fold it into the canonical Phase 1 wording before passing `--summary`, OR amend the CHANGELOG blockquote BEFORE Phase 8 so all three surfaces stay aligned\n- ⊗ Export `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` for the entire release session or wrap `task release` / `task ci:local` in it (#1553) -- the env var is process-wide and leaks into nested tests and temporary repos, producing false preflight failures. Prefer `task policy:allow-direct-commits -- --confirm` and restore with `task policy:enforce-branches` after the cut (closeout commit+push may use a **scoped** env prefix on those three git commands only — see Branch-Protection Policy Guard, #2623)\n- ⊗ Pass `--allow-coverage-debt=#N` unquoted on Windows PowerShell (#2621) -- `#` starts a comment and silently drops the issue number. Use `--allow-coverage-debt=N` or `--allow-coverage-debt=\"#N\"`\n- ⊗ Soft-pass coverage debt while an **open** coverage-debt issue from a prior hatch still exists (#2866 / #2573) -- restore real branch coverage >= 85% and close the debt issue before reusing `--allow-coverage-debt`; the ledger is open GitHub issues, not prior CHANGELOG citations (#2618 superseded by open-issue ledger)\n",
239
+ "body": "# Deft Directive Release\n\nStructured 8-phase workflow for cutting a v0.X.Y release of the deft framework. Operationalizes the `task release` / `task release:publish` / `task release:rollback` / `task release:e2e` surface introduced in #716 (safety hardening of #74).\n\nLegend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.\n\n**See also**: [deft-directive-swarm](../deft-directive-swarm/SKILL.md) Phase 6 Step 5 (Slack announcement template re-used by Phase 8 below) | [deft-directive-review-cycle](../deft-directive-review-cycle/SKILL.md) (user-gate pattern) | [deft-directive-refinement](../deft-directive-refinement/SKILL.md) (conversational phased flow).\n\n## Platform Requirements\n\n! GitHub as the SCM platform; the **GitHub CLI (`gh`)** must be installed and authenticated. The full pipeline plus the rehearsal target (`task release:e2e`) all dispatch through `gh`.\n\n## Branch-Protection Policy Guard\n\n! Before any Phase 1 state mutation, run the skill-level branch-policy guard documented in `scripts/policy.py` / `scripts/preflight_branch.py` (#746 / #747). Releases run on the configured base branch (default `master`), so the operator MUST be on the explicit-opt-in side of the policy before the pipeline starts writing files.\n\n**Preferred path — typed direct-commit policy opt-out (#1553).** For a release session on the default branch, prefer the audited typed flag over the emergency env-var bypass:\n\n```\ntask policy:allow-direct-commits -- --confirm\n```\n\nThis writes `plan.policy.allowDirectCommitsToMaster = true` on `xbrief/PROJECT-DEFINITION.xbrief.json` with a capability-cost disclosure. After the release completes (or if the session aborts), restore enforcement **and commit the restore in the same closeout** (#2623):\n\n```\ntask policy:enforce-branches\n# enforce flips the typed flag to false locally — the commit that lands that\n# flip cannot use the typed opt-in anymore. Scope the emergency env bypass to\n# ONLY this closeout commit+push (do NOT export it for the whole session):\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git add xbrief/PROJECT-DEFINITION.xbrief.json meta/policy-changes.log\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git commit -m \"chore(policy): restore branch protection after vX.Y.Z\"\nDEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 git push origin HEAD\n```\n\n⊗ Leave `allowDirectCommitsToMaster=true` on origin after publish. ⊗ Run `policy:enforce-branches` and leave the dirty restore under protection ON without committing (forces a follow-up PR — the v0.79.0 / #2619 failure mode).\n\n**Branch-guard probe (either path).** Regardless of which opt-out path you chose, confirm the guard passes before Phase 1 mutates state:\n\n```\nuv run python scripts/preflight_branch.py --project-root . --quiet || exit 1\n```\n\nor invoke `task verify:branch`. This is the canonical surface that surfaces the policy state to the operator before the pipeline starts writing files. The release pipeline's other safety surfaces (the dirty-tree guard, base-branch check, `task ci:local` gate) remain independent of this check.\n\n**Emergency env-var bypass — narrow scope only (#1553).** `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` is process-wide: every child process, nested test, and temporary repository spawned from the same shell inherits it. During the v0.43.0 release attempt, wrapping the entire `task release` invocation in this env var let the bypass leak into the Step 5 `task ci:local` preflight, which caused `TestWriteConsumerGitHooks_VendoredCommitBlocked_RealGit` to fail because the vendored test repo allowed a direct `master` commit the test expected the hook to block.\n\n- ! Prefer `task policy:allow-direct-commits -- --confirm` for release sessions instead of exporting `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` for the whole shell.\n- ⊗ Wrap `task release`, `task ci:local`, or `task check` in `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` -- the env var is inherited by every subprocess and can produce false preflight failures before any release mutation.\n- ? If the env-var path is unavoidable, scope it to a **single** branch-guard probe only (e.g. `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1 task verify:branch`) and do NOT export it for the release session. The release pipeline itself passes the bypass only in scoped subprocess `env=` for its authorised commit/tag/push mutations (#867); operators MUST NOT mirror that pattern at the shell level.\n\nThe release pipeline's Step 9/10/11 git mutations carry the bypass in subprocess `env=` only (`scripts/release.py::_release_subprocess_env`, #867) so the parent shell stays clean. Operator-side env-var exports defeat that isolation.\n\n## Deterministic Questions Contract\n\n! Every numbered-menu prompt rendered in this skill (Phase 1 version-bump magnitude check, Phase 2 dry-run review `yes`/`back`/`quit`, Phase 5 optional `defer`/`rollback`/`Discuss` (happy path auto-publishes after draft QA)) MUST follow [`../../contracts/deterministic-questions.md`](../../contracts/deterministic-questions.md): the final two numbered options MUST be `Discuss` and `Back`, in that order. Existing `back`/`quit` options remain valid; this contract simply adds `Discuss` as a peer alongside `Back`. The Discuss-pause semantic is documented verbatim in the contract -- implicit resumption is forbidden.\n\n## When to Use\n\n- User says \"release\", \"cut release\", \"v0.X.Y\", \"publish release\", \"ship a release\"\n- The framework's `[Unreleased]` CHANGELOG section is non-empty and the operator wants to cut a tagged release\n- A previous release rehearsal succeeded and the operator is ready for the production cut\n\n## Phase 1 — Pre-flight\n\n! Validate the local + remote state before any irreversible action.\n\n\n### Parallel prep — #1880 Gap D (#2692)\n\n! Phase 1 long steps (`task reconcile:issues -- --apply-lifecycle-fixes`, cache refresh when ritual-stale, `task ci:local` / `task check`) and Phase 3 `task release:e2e` MUST be backgrounded or subagent-dispatched when the host supports it (Cursor: Task tool `run_in_background: true`), with progress surfaced via DONE/heartbeat — same ownership as review-cycle / merge-ready workers (#1880 Gap D). The operator conversation MUST stay interactive for version magnitude confirmation, `--summary`, and the Phase 2 dry-run `yes`/`back`/`quit` gate while prep runs.\n\n! **Checklist:** Phase 1 prep parallelized — long prep started in background before (or while) collecting version magnitude / summary / npm irrevocability disclosure.\n\n! On Windows PowerShell, do NOT wrap long task output in `Select-Object -Last` (it buffers until the process exits); stream to the terminal or log to a file and read incrementally. See `scm/github.md` § #2646 / Windows encoding guidance for related PS pitfalls.\n\n⊗ Foreground-block the operator chat on reconcile / `ci:local` / `release:e2e` when background dispatch is available (#1880 Gap D / #2692).\n\n### Fixable check failure — file-and-merge before resume (#2859)\n\n! When Step 4 (`task ci:local` or `task check`) fails on a **fixable product or test defect** (hang, failing test, validation bug — not operator env misconfiguration), the release cut MUST pause and route the blocker through normal issue → xBRIEF → feature branch → PR → merge before resuming Phase 1.\n\n? **Step 5 branch-coverage threshold misses** during `task release` (Vitest branch coverage below 85% with no other failure mode) are carved out to § Step 5 branch-coverage threshold — open-issue ledger hatch (#2866) below — not this file-and-merge path.\n\n**Required path:**\n1. File a GitHub issue with root cause, recurrence signature, and acceptance criteria.\n2. Ingest / promote / activate scope xBRIEF; implement on a feature branch with `drive-to: merge-ready`.\n3. Merge; confirm `task check` / `ci:local` is green for the failure mode.\n4. Resume the release cut from Phase 1 (re-run Step 4).\n\n⊗ Lead with an inline-only hotfix on the release branch / default branch without a tracked issue and merged PR.\n⊗ Suggest untracked `--skip-ci` or `--allow-skip-ci` without a **tracked issue number** as the first recovery when the failure is a clear, shippable bug.\n⊗ Continue the cut with \"raise timeouts,\" \"fix it here,\" or other shortcut theater before file-and-merge completes.\n\n? **`--skip-ci` / `--allow-skip-ci=#N` remains valid** only under explicit operator incident review with a **tracked issue** cited on the flag (Phase 4 contract). It is NOT a substitute for filing and merging a fixable defect discovered in Phase 1.\n\n**AGENTS.md bulk rejected (#2859):** Expanding `AGENTS.md` / `content/templates/agents-entry.md` with an always-on pin for this release-phase reminder was considered and **rejected**. This rule lives in the release skill (and optional lesson); do NOT add AGENTS.md / agents-entry bulk for it.\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Fixable check failure during release for the operator runbook and the existing `--allow-skip-ci=#N` incident contract.\n\n### Step 5 branch-coverage threshold — open-issue ledger hatch (#2866 / #3187)\n\n! When **`task release` Step 5** fails on Vitest coverage below the 85% goal, apply this hatch **only** when **branches** is the **sole** metric below 85% (hairline branch miss — lines, functions, and statements all ≥ 85%). Confirm via the Step 5 failure output, `coverage/coverage-final.json`, or `task coverage:hotspots` before hatching. If **any other metric** is also below 85%, or the failure is a hang / failing test / non-coverage defect, STOP — use § Fixable check failure — file-and-merge before resume (#2859) instead.\n\n**Runtime disclosure (#2573):** `--allow-coverage-debt=#N` sets vitest coverage thresholds to zero for the release Step 5 run (`vitest.config.ts`). The hatch is justified only for branch-only hairlines; the filed debt issue MUST require restoring **all four metrics** (lines, functions, branches, statements) to ≥ 85% before close.\n\n**Auto-hatch in `task release` Step 5 (#3187):** the pipeline classifies a non-zero Step 5 as `REAL_FAILURE` | `BRANCH_HAIRLINE` | `OTHER_COVERAGE` | `UNKNOWN` after **one** suite run.\n\n1. `REAL_FAILURE` | `OTHER_COVERAGE` | `UNKNOWN` → **fail closed** (no auto-issue). Route product defects via #2859.\n2. `BRANCH_HAIRLINE` + **any open** coverage-debt ledger entry → **fail closed** (\"restore coverage; close `#N` first\") — consecutive soft-pass ban (#2866).\n3. `BRANCH_HAIRLINE` + **empty** open debt ledger → **auto-file** `#N` with markers `coverage-debt` and `--allow-coverage-debt`, measured metrics, cut version, and acceptance (all four metrics ≥ 85%); mark Step 5 `PASS_WITH_DEBT(#N)`; **continue the cut without re-running vitest**. Loud stderr banner + cite `#N` in CHANGELOG / release notes.\n\n! **File before continue** — never soft-pass without a durable issue number.\n\n? Operators MAY still pass `--allow-coverage-debt=#N` manually (PowerShell-safe: `--allow-coverage-debt=N` or `--allow-coverage-debt=\"#N\"` — #2621) when re-running after a pre-filed debt issue.\n\n**SHA suite stamp (#3187, coordinates with #3188 check ordering):** after suite **green** or `PASS_WITH_DEBT`, write a local SHA-bound stamp under `.deft/release-suite-stamp.json` (gitignored). Re-entry at the **same clean HEAD** skips the suite; dirty tree or different HEAD invalidates the stamp. ⊗ CI never trusts the stamp (GHA has no committed stamp; `CI`/`GITHUB_ACTIONS` force miss).\n\n**Open-issue ledger (release-scoped only):**\n\n1. Check for an **open coverage-debt tracking issue** (unpaid hatch from a prior cut) — union all three probes and dedupe by issue number:\n - **Marker search** (new-format debt issues):\n ```\n gh issue list --repo <owner>/<repo> --state open --search \"coverage-debt in:title,body\" --limit 20\n gh issue list --repo <owner>/<repo> --state open --search \"allow-coverage-debt in:body\" --limit 20\n ```\n - **CHANGELOG citation scan** (legacy hatch issues filed before markers were mandatory): parse `CHANGELOG.md` `[Unreleased]` plus the last three `## [version]` sections for `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations; for each `#N`, probe issue state via REST — `OPEN` (or unknown) counts as unpaid debt even when title/body lack the new markers (#2866).\n2. If **no open coverage-debt issue exists** → auto-hatch files `#N` (or operator files manually) with title prefix `coverage-debt:` and body containing both markers. The open `#N` remains WIP until coverage is restored and the issue is closed.\n3. If an **open coverage-debt issue from a prior hatch still exists** → ⊗ soft-pass again; restore real coverage (all four metrics ≥ 85%) and close the debt issue before the cut proceeds.\n\n**Framework-release-first (#3187):** auto-hatch applies to framework `task release` Step 5 (e.g. deftai/directive). Consumer expansion of auto-hatch is blocked on project `plan.policy.coverageDebt` (#3189) — refuse when `status=unset` or `mode=off`. Do not auto-file debt on the framework repo from consumer trees; consumer ledger is always the consumer repo.\n\n⊗ Auto-pass on a near-miss band without `#N` (#2573).\n⊗ Silent soft-pass with no tracked issue.\n⊗ File a debt issue without `coverage-debt` / `allow-coverage-debt` markers in title or body — the ledger query will miss it and permit a consecutive soft-pass (#2866).\n⊗ Use this carve-out when lines, functions, or statements are also below 85%, or for hangs, failing tests, or non-coverage Step 5 failures — those stay under #2859 file-and-merge.\n⊗ Treat file-debt-then-hatch as the default for ordinary PR / `task check` work outside a release cut — this hatch is release-scoped only.\n⊗ Trust a laptop suite stamp in CI or after HEAD/tree drift (#3187).\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Coverage debt hatch during release.\n\n### Routine vs hard cut for Step 5 (#2953)\n\n! Treat **hard cut** as the default: full Step 5 (`task check` + Vitest coverage) with no `--skip-ci`.\n\n~ Treat **routine cut** as the same full Step 5, sped up by default-excluding `.deft-scratch/` (and legacy `swarm-worktrees/`) from content/link/path/build-dist walks, plus pre-cut hygiene (green required checks on the tip SHA; prune stale `.deft-scratch/worktrees`).\n\n! Confirm required CI is green on the tip you will tag when aiming for a calm routine cut — green tip CI is a precondition, not a replacement for Step 5.\n\n⊗ Invent a silent lighter Step 5 that skips or soft-passes coverage without an explicit hatch (`--allow-coverage-debt=#N` for branch-only hairlines, or `--skip-ci` + `--allow-skip-ci=#N` for incidents). Silent soft-pass is forbidden in every mode.\n\n~ Prefer pruning abandoned worktrees / cutting from a clean clone over re-scanning scratch noise.\n\nSee [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Routine vs hard cut for Step 5 (#2953).\n\n\n~ **Frozen Go-installer bridge (#1912 / #1972 / #1987):** by default a release tag *above* the frozen line (the `LAST_GO_INSTALLER` constant in `packages/core/src/legacy-bridge/sot.ts`) will NOT rebuild the 6 Go binaries -- the CI `freeze-gate` job in `.github/workflows/release.yml` skips the build (the run stays green; npm still ships from the separate `npm-publish.yml`). If this release must rebuild the Go installer, follow the runbook in [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Frozen Go-installer bridge: roll `LAST_GO_INSTALLER` forward to the cut tag BEFORE tagging (pinning to the exact cut tag both releases the gate AND re-freezes at the new line), then see that section's \"After the release\" step for the re-pin.\n\n1. ! Verify the operator is on the configured base branch (default `master`) and the working tree is clean\n2. ! Confirm the next version number (`X.Y.Z`) with the user. Major / minor / patch decision flows from the `[Unreleased]` content (breaking change → major; new feature → minor; fix-only → patch)\n3. ! Inspect `[Unreleased]` content vs the proposed version bump. If a breaking change appears in `### Changed` / `### Removed` but only a patch is proposed, surface the mismatch and ask the user to choose\n4. ! Verify `task ci:local` passes locally (or `task check` as the graceful-degradation fallback per `tasks/release.yml` line 9-10). The `task release` script will refuse to proceed otherwise -- but Phase 1 catches it earlier — **on failure from a fixable defect, STOP and follow § Fixable check failure below (#2859); do NOT proceed to step 5**\n5. ! Verify `gh auth status` reports authenticated (`task release` will refuse otherwise)\n6. ! **Run `task reconcile:issues -- --apply-lifecycle-fixes` to clear any closed-issue / non-completed-folder xBRIEFs before invoking `task release`** (#734). The release pipeline carries the deterministic gate at Step 3 (`scripts/release.py::check_vbrief_lifecycle_sync`, refuses with `EXIT_VIOLATION` on any Section (c) mismatch), but Phase 1 is the operator's first-line defence -- running the apply-mode flag here is the canonical clean path; `--allow-vbrief-drift` on the pipeline exists only as the explicit-acknowledgment escape hatch (analogous to `--allow-dirty`). The recurrence record is the v0.21.0 cut, which surfaced 13 stranded xBRIEFs (8 cycle-relevant + 5 historical residue) post-publish; the gate now blocks that drift before any irreversible action\n7. ! **Verify the proposed `v<version>` tag is not already in use locally, on origin, or as a published GitHub release** (#784). The release pipeline carries the deterministic gate at Step 4 (`scripts/release.py::check_tag_available`, refuses with `EXIT_VIOLATION` before any state mutation -- CHANGELOG promotion, ROADMAP refresh, build, commit), but Phase 1 is the operator's first-line defence. Quickly probe with `git tag -l v<version>` (local), `git ls-remote --tags origin refs/tags/v<version>` (remote), and `gh release view v<version> --repo <owner>/<repo>` (release-only, where `gh release view` exits 0 only when the release exists). The recurrence record is the v0.22.0 → v0.23.0 release attempt on 2026-05-01: the operator typed `0.22.0` (the prior release from 12 hours earlier) and the legacy pipeline ran 8 steps before failing at `git tag` -- leaving a wrong-version local commit + `dist/deft-0.22.0.zip` orphan + manual `git reset --hard` recovery. The new pre-flight gate blocks that mode before any irreversible action\n8. ! **Verify the npm credential path is configured before cutting the tag** (#1910, #1909). A `v*` tag now auto-triggers `.github/workflows/npm-publish.yml`, which publishes the four `@deftai/directive*` packages with `npm publish --provenance`. Confirm the publish path can authenticate: either the `NPM_TOKEN` repo secret is present (`gh secret list --repo <owner>/<repo>` shows `NPM_TOKEN`) OR an npm OIDC trusted publisher is configured for the `@deftai/directive*` packages. If neither is in place, WARN loudly that the tag will fire a publish job that fails (red X on the tag, no packages) -- the operator may still proceed for a GitHub-only release, but the npm channel will not land until #1909's credential is provisioned. Cross-reference #1909.\n9. ! **Disclose npm irrevocability before any tag push (#1972, #2002).** A `v<version>` tag push is the **real npm publish gate** -- NOT Phase 5 or `task release:publish`. Tag push fires `.github/workflows/npm-publish.yml` in a separate workflow that is NOT draft-gated; npm packages ship immediately and **cannot be retracted** (`npm unpublish` is forbidden). Recovery is forward-only: deprecate, dist-tag, or ship a patch. The operator's explicit `yes` in Phase 2 (dry-run) and the decision to invoke `task release` in Phase 4 are the last human gates before npm goes live. Phase 5 only controls GitHub release visibility (draft → public); it does NOT gate npm.\n10. ~ Ask the operator for an optional one-line release **summary** (recommended 80-160 chars; can be skipped). The summary is the canonical narrative for THIS release across three audiences: (a) injected as a Markdown blockquote at the top of the promoted `CHANGELOG.md [<version>]` section, (b) auto-flowed into the GitHub release body via the existing `_section_for_version` pickup, and (c) populated VERBATIM into the Phase 8 Slack `*Summary*:` slot. Capture the wording once here; do NOT regenerate per-audience downstream\n\n⊗ Skip the version-bump magnitude check -- a patch release that ships breaking changes is the kind of regression that Repair Authority [AXIOM] (#709) is designed to prevent.\n\n⊗ Skip the xBRIEF-lifecycle-sync check (#734); the gate exists because operators consistently forget the manual `task scope:complete` move step. The v0.21.0 cut surfaced 13 stranded xBRIEFs (8 cycle-relevant + 5 historical residue) post-publish as the recurrence record this gate prevents. If `task release` reports `[3/13] Pre-flight xBRIEF lifecycle sync... FAIL (<count> mismatches; run task reconcile:issues -- --apply-lifecycle-fixes to fix)`, the canonical recovery is the apply-mode invocation -- `--allow-vbrief-drift` is reserved for cases where the operator has explicitly reviewed the drift and chosen to defer the lifecycle reconcile to the next refinement pass (e.g. an emergency hot-fix release).\n\n⊗ Skip the tag-availability check (#784); the gate exists because the legacy 12-step pipeline only invoked `git tag` at Step 9, after Steps 1-8 had already mutated state (CHANGELOG promoted, ROADMAP refreshed, dist built, release commit made locally). A duplicate-tag failure at Step 9 stranded the operator with an unpushed wrong-version commit + orphaned `dist/deft-<wrong>.zip` artifact + manual `git reset --hard` recovery (forbidden by AGENTS.md SCM rules without explicit permission). The recurrence record is the v0.22.0 → v0.23.0 release attempt on 2026-05-01. If `task release` reports `[4/13] Pre-flight tag availability... FAIL (<surface> tag v<version> already exists ...)`, the canonical recovery is to choose a different version (the most likely cause is operator typo of a prior release).\n\n⊗ Hand-write a different one-line narrative for each of the three downstream surfaces (CHANGELOG / GitHub release / Slack) -- that drift is exactly the gap the `--summary` flag is designed to close. If the operator insists on per-audience tone, populate the canonical `--summary` ONCE here and document the deviation in the Phase 8 anti-pattern.\n\n## Phase 2 — Dry-run review\n\n! Invoke `task release -- <version> --dry-run --skip-tag --skip-release` and present the plan to the user. If Phase 1 collected an operator summary, also pass `--summary \"<text>\"` so the dry-run preview reflects the canonical narrative the operator just authored.\n\n```\ntask release -- <version> --dry-run --skip-tag --skip-release --summary \"<text>\"\n```\n\nThe dry-run prints `[N/13] <step>... DRYRUN (would <action>)` for every pipeline step (Step 13 is the post-create verify-isDraft gate added by #724; Step 4 is the tag-availability pre-flight gate added by #784). Step 6 (CHANGELOG promotion) surfaces whether a summary was supplied (truncated to ~60 chars in the preview) so the operator can validate the wording before any file is written. Capture the output and present it to the user, then wait for explicit confirmation before continuing.\n\n! Wait for explicit user confirmation: `yes` / `back` / `quit`. Remind the operator that Phase 4's tag push will irrevocably publish npm (#1972) -- this `yes` is the last safe abort before that channel opens.\n- `yes` (or `confirmed` / `approve`) → proceed to Phase 3\n- `back` → return to Phase 1 for re-validation (e.g. user wants to amend the version or `[Unreleased]` content)\n- `quit` → abort the workflow cleanly; no state changes\n\n⊗ Skip the dry-run preview. The dry-run is the operator's last opportunity to catch a bad version number, malformed CHANGELOG, or wrong base branch before the pipeline starts writing files.\n\n## Phase 3 — E2E sanity\n\n! Invoke `task release:e2e` against an auto-created temp repo to verify the full pipeline shape works end-to-end before touching the real repo.\n\n! **#1880 Gap D (#2692):** `task release:e2e` is a long-running step — MUST background / subagent-dispatch it when the host supports it so Phase 2 confirmation and other human gates stay interactive. Do not wrap its output in PowerShell `Select-Object -Last`.\n\n```\ntask release:e2e\n```\n\nThe harness provisions `deftai/deftai-release-test-<ts>-<uuid6>`, runs the smoke-test rehearsal, and **by default keeps** the temp repo (#2572). Stderr always includes the full `owner/slug` and a copy-pasteable manual-cleanup command (`gh repo delete <owner>/<slug> --yes`). Privileged environments (CI or an operator with `delete_repo`) MAY pass `task release:e2e -- --destroy-repo` to attempt auto-delete; destroy failure emits a WARN and does **not** block Phase 4 when the rehearsal succeeded.\n\n! After Phase 3, the agent MUST NOT retry or escalate temp-repo deletion. Include any leftover temp repo(s) in the phase summary for the operator to clean up manually.\n\n! Treat a non-zero exit from `task release:e2e` as a hard refusal to proceed to Phase 4. Surface the diagnostic and ask whether to debug (return to Phase 1) or abort (`quit`).\n\n? **Skip allowed** when the operator has just run `task release:e2e` successfully against the same branch in the past 30 minutes. Note the prior run timestamp in the user-facing summary.\n\n! **`task release:e2e` now also rehearses the npm publish (#1910).** Unless `--skip-npm` is passed (or `npm` is absent from PATH, which soft-skips), the rehearsal runs `npm publish --dry-run --access public` for all four `@deftai/directive*` packages against the throwaway clone in dependency order (types -> core -> content -> cli), after `pnpm install` + `pnpm -w run build` and a version-alignment pass. This catches a broken `files` allowlist, a version-drift bug, or a dependency-order error BEFORE the real `v*` tag fires the publish workflow -- without touching the real registry. The install+build exceeds the <90s fast budget, so pass `task release:e2e -- --skip-npm` when you only need the GitHub-pipeline shape check.\n\n! **Tag -> npm coupling + irrevocability (#1910, #1972, #2002).** A `v<version>` tag is a TWO-channel action: the GitHub release (this skill's pipeline) AND `.github/workflows/npm-publish.yml`, which runs in a SEPARATE workflow that does NOT block the GitHub release and is NOT draft-gated. The npm workflow derives the published version from the tag (`${GITHUB_REF_NAME#v}`); this skill owns the version chosen in Phase 1. These MUST stay consistent -- the tag you cut IS the npm version that ships; there is no separate npm version bump. **npm publish is irrevocable** (#1972): once the tag fires, packages are live on npm and cannot be unpulled; `task release:rollback` does NOT retract npm (forward-only recovery). A red npm job on a green GitHub release means the npm channel did not ship (verify in Phase 5/7).\n\n## Phase 4 — Production draft\n\n! **Last human gate before npm (#1972, #2002).** Immediately before invoking `task release`, re-state that the tag push in this step will irrevocably publish all four `@deftai/directive*` packages to npm via `.github/workflows/npm-publish.yml`. There is no undo on npm; only forward recovery (deprecate / dist-tag / patch). Proceed only when the operator explicitly confirms.\n\n! Invoke `task release -- <version>` (NO `--dry-run`, NO `--skip-tag`, NO `--skip-release`, NO `--skip-ci`). If Phase 1 collected an operator summary, pass `--summary \"<text>\"` so the production cut writes the same blockquote the dry-run previewed.\n\n⊗ Use `--skip-ci` on a production cut except under explicit operator incident review — it skips Step 5 vitest coverage and ships untested npm builds (#2652). When unavoidable, pass `--allow-skip-ci=#N` citing the tracked issue; Step 5 emits a loud WARN. See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Vitest coverage hang recovery. The next patch after the hang fix must cut without `--skip-ci`.\n\n```\ntask release -- <version> --summary \"<text>\"\n```\n\nPer #716 default-draft hardening, this lands the release as a `--draft` on the real repo. Binaries upload via release.yml CI, but the artifact is NOT yet visible to consumers. The operator-authored summary becomes part of the promoted `CHANGELOG.md [<version>]` section AND the GitHub release body (auto-pickup via `_section_for_version`). The same wording is the canonical source for the Phase 8 Slack `*Summary*:` slot.\n\n! **Maintainer-mode release notes auto-lead with an \"Upgrading from an older version?\" banner (#1413).** When the cut targets the canonical framework repo (`deftai/directive`), `scripts/release.py` Step 12 prepends the banner from the editable template at `.github/release-notes/upgrade-banner.md` to the notes passed to `gh release create` (via `_prepend_upgrade_banner`). The banner points consumers at the canonical `deft-install --yes --upgrade --repo-root . --json` upgrade command and #1411. This is **GitHub-release-body-only** -- it is NEVER injected into `CHANGELOG.md`, so the CHANGELOG section and the release body intentionally differ by this leading block. To change the wording, edit the template file; do not hand-edit the published release body. **Consumer-mode releases (any non-`deftai/directive` repo) are unaffected** -- a downstream project that vendors the release pipeline never inherits deft's upgrade guidance. A missing/unreadable template degrades gracefully (notes ship without the banner; the cut is never blocked).\n\n! **Verify isDraft within 5 seconds; flip immediately if not (#724).** Immediately after `gh release create --draft` returns success, `scripts/release.py` Step 11 polls `gh release view v<version> --json isDraft` up to 5 times at 1-second intervals. If the release exists with `isDraft=false`, the pipeline auto-flips it via `gh release edit v<version> --draft=true` and emits a `WARNING: release landed as public; flipping to draft (defense-in-depth, see #724)` line. This closes the ~90-second public-exposure window observed during the v0.21.0 cut where a manual recovery created a public release before the operator noticed and flipped it. The verify gate is defense in depth even when `--draft` was passed correctly: it catches the case where `gh release create` partially succeeded (release record written, error returned) AND the operator-error variant where an alternate code path sent the release without `--draft`. A release-not-found-within-budget result emits a WARN and does NOT fail the pipeline (release.yml CI may still be processing).\n\n! Wait for `task release` to exit 0 before continuing. A non-zero exit means the pipeline halted partway through; consult Phase 7's `task release:rollback` recovery before retrying.\n\n⊗ Pass `--no-draft` here unless the operator has explicitly opted into direct-publish (e.g. automated security patch). The default-draft contract is the foundation of the safety hardening surface.\n\n⊗ Skip the post-create verify-isDraft gate -- the gate is the only reliable safety net against \"create call exited 0 but the release somehow landed as public\" variants (#724). If `task release` is invoked manually outside the canonical `scripts/release.py` flow, the operator MUST run `gh release view v<version> --json isDraft` followed by `gh release edit --draft=true` on `isDraft=false` BEFORE handing off to Phase 5.\n\n## Phase 5 — GitHub draft QA (optional; NOT the npm authority gate)\n\n! After `task release` exits 0, QA the **GitHub draft release** only. npm packages typically **already shipped** when the tag push in Phase 4 fired `.github/workflows/npm-publish.yml` (#1972, #2002). Phase 5 is NOT a \"user-only authority before going live\" gate for the release as a whole -- it is optional draft QA for GitHub assets, notes, and binaries.\n\n1. ! **Verify npm publish status FIRST (in parallel with draft inspection).** Run `gh run list --workflow=npm-publish.yml --repo <owner>/<repo> --limit 5` and confirm the tag run for `v<version>` is `completed`/`success`. If npm failed, surface immediately -- the GitHub draft QA is secondary to a red npm channel.\n2. ! Run `gh release view v<version> --json url,name,body,assets,isDraft --repo <owner>/<repo>` and present the output to the user\n3. ! Surface the asset list (size + filename) so the user can verify binaries uploaded correctly\n4. ! Surface the auto-generated release notes (or the CHANGELOG section that was promoted into the release body)\n\n### Happy path (default when npm succeeded and draft assets look correct)\n\n! When the npm workflow succeeded AND draft assets/notes pass inspection, **auto-proceed to Phase 6 Publish branch** -- run `task release:publish -- <version>` without a redundant human publish prompt (#2002). npm already shipped at tag push; waiting for a separate `publish` confirmation does not protect the npm channel.\n\n? **Operator override:** if the operator wants to hold the GitHub release in draft (e.g. embargo, last-minute notes edit), they MAY say `defer` before auto-publish runs.\n\n### Exception paths (operator-initiated)\n\n- `rollback` → proceed to Phase 6 (Rollback branch). **Reminder:** rollback unwinds the GitHub release only; npm packages already published at tag push are NOT retracted (#1972).\n- `defer` → halt and exit. Surface the draft URL so the operator can return later with `task release:publish -- <version>` or `task release:rollback -- <version>`.\n\n⊗ Treat Phase 5 as the npm publish-authority gate -- npm ships at tag push (Phase 4), not at `task release:publish`. A human `publish` prompt here is redundant when npm already succeeded and only delays flipping the GitHub draft to public.\n⊗ Skip npm workflow verification in Phase 5 and defer it entirely to post-publish Phase 7 -- npm status MUST be checked before or in parallel with the GitHub publish flip.\n\n## Phase 6 — Publish or rollback\n\n! Branch on the Phase 5 outcome. The happy path auto-enters the Publish branch when npm succeeded and draft QA passed (#2002).\n\n### Publish branch (happy path auto-run, or resumed after `defer`)\n\n```\ntask release:publish -- <version>\n```\n\nThe companion script flips `--draft=false`, then re-reads the release to verify `isDraft == false` actually flipped. State machine:\n- `draft` found → flip to public; verify; exit 0\n- already `published` → exit 0 no-op (idempotent re-runs are safe)\n- `not-found` → exit 1 (cannot publish a missing release)\n- gh-error → exit 1 with diagnostic\n\n! Wait for `task release:publish` to exit 0 before continuing. On the happy path this runs immediately after Phase 5 draft QA without a separate human publish prompt.\n\n### Rollback branch (user said `rollback`)\n\n```\ntask release:rollback -- <version>\n```\n\nThe state-aware unwind detects the post-release state and applies the matching tiered recovery. Time-windowed download-count guard:\n- release age `< 5 min` → threshold = 0 (rollback safe; nobody noticed yet)\n- release age `5-30 min` → threshold = max(`--allow-low-downloads`, 10) (filters bot fetches)\n- release age `> 30 min` → refuse without `--allow-data-loss`\n\nThree escape hatches (escalating warnings):\n- `--allow-low-downloads N` -- accept up to N downloads\n- `--allow-data-loss` -- accept any count (consumer impact)\n- `--force-strict-0` -- require exactly 0 regardless of release age\n\nRace-condition mitigation: `download_count` is double-read with a 5s sleep between reads; rollback only proceeds if both reads agree below threshold.\n\n! When the guard refuses, surface the recommendation to the user: rollback is risky on a released artifact with non-zero downloads. Prefer the **hot-fix path** (cut the next patch with a withdrawal note in `[Unreleased]/Changed` rather than deleting the broken release).\n\n! **`task release:rollback` does NOT retract npm (#1972, #2002).** Rollback unwinds GitHub release state (draft/public, tag, assets) only. npm packages published at tag push remain on the registry irrevocably. Recovery is forward-only: deprecate the bad version, move a dist-tag, or ship a patch release.\n\n## Phase 7 — Post-publish verification\n\n! Only enter Phase 7 if Phase 6 took the Publish branch (rollback branch ends here with the unwind log).\n\n1. ! **Re-verify npm publish landed (#1910, #1909, #2002).** Phase 5 checked workflow status before the GitHub publish flip; Phase 7 confirms registry truth AFTER `task release:publish`. For each of `@deftai/directive-types`, `@deftai/directive-core`, `@deftai/directive-content`, and `@deftai/directive`, run `npm view <pkg>@<version> version` (expect `<version>`) and confirm provenance on the npm page. A green GitHub release with missing npm packages means consumers cannot `npm i -g @deftai/directive@<version>` -- escalate immediately. (Real-registry verification depends on #1909's credential; until then verify workflow-run status and flag credential gaps.)\n2. ! Verify GitHub auto-closed the discrete-task issue(s) referenced via `Closes #N` in the release notes (mirrors `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 2)\n3. ! Run `gh issue view <N> --json state --jq .state` for each closed issue. If any didn't auto-close, manually close with `gh issue close <N> --comment \"Closed by release v<version> (squash auto-close did not trigger)\"` (Layer 1, #167)\n4. ! Verify ROADMAP.md correctness via `task roadmap:render` (the release pipeline already invoked this; Phase 7 is the second-pass sanity check)\n5. ! Verify binaries are downloadable from the public release URL: `gh release view v<version> --json assets --jq '.assets[].url'` and curl one to confirm 200 OK\n6. ! For any umbrella / staying-OPEN issue (`Refs #N`) referenced in the release notes, run the Layer 3 reopen sweep from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1: any protected issue that auto-closed MUST be reopened with a comment citing #701\n\n⊗ Skip the post-publish verification. The closing-keyword false-positive (Layer 1 / Layer 2 / Layer 3) and the incremental-renderer-drift (#641, #614) are exactly the kind of issues that surface only AFTER a release is public.\n\n## Phase 8 — Slack announcement\n\n! Generate the canonical Slack release announcement and present it to the user for copy-paste, re-using the template from `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 5.\n\nThe announcement block MUST include:\n\n```\n:rocket: *deft v<version>* -- <release title>\n\n*Summary*: <one-sentence description of the release scope>\n\n*Key Changes*:\n- <bullet per significant change, 3-5 items max>\n\n*Stats*: 1 release | ~<duration> elapsed | <N> commits since v<previous>\n*Release*: <GitHub release URL>\n```\n\n! Populate version from the freshly-published `gh release view v<version>` output. Populate release title from the CHANGELOG section heading (or the GitHub release title). Summarize key changes from the promoted `[Unreleased]` -> `[<version>]` CHANGELOG section (NOT raw commit messages). Populate stats from `git log v<previous>..v<version> --oneline | wc -l`.\n\n! Populate the `*Summary*:` slot VERBATIM from the operator-authored blockquote at the top of the CHANGELOG `[<version>]` section (the line beginning with `> ` immediately after the `## [<version>] - <date>` heading). The Phase 1 prompt + Phase 4 `--summary` flag exist precisely so this populate step is mechanical -- one canonical narrative authored once at Phase 1, propagated through Phase 4 promotion, and copy-pasted here without re-authoring. If the CHANGELOG section has no blockquote (operator skipped the Phase 1 prompt), generate a one-sentence summary from the `### Added` / `### Changed` bullets and surface to the operator that this is a regenerated narrative (NOT canonical) so they can decide whether to amend the CHANGELOG before publishing.\n\n! Present the block as a code-fenced snippet the user can copy directly. Do NOT post to Slack from inside this skill -- the user owns the actual broadcast.\n\n## Skill Completion\n\n! When Phase 8 completes (or when Phase 5 took the `defer` / `quit` path, or when Phase 6 completed the rollback branch), explicitly confirm skill exit:\n\n```\ndeft-directive-release complete -- exiting skill.\nNext: <one-line guidance>\n```\n\nWhere `<one-line guidance>` is one of:\n- \"release v<version> live -- monitor consumer reports for ~24h before cutting v<next>\"\n- \"release v<version> rolled back -- the underlying defect needs a hot-fix in the next CHANGELOG entry\"\n- \"release deferred -- resume by running `task release:publish -- <version>` (GitHub only; npm already shipped at tag push) or `task release:rollback -- <version>` (GitHub unwind only; npm is forward-recovery) when ready\"\n\n⊗ Exit silently without confirming completion or providing next-step guidance.\n\n## Anti-Patterns\n\n- ⊗ Foreground-block the operator chat on Phase 1 long prep (`reconcile:issues`, cache refresh, `ci:local` / `check`) or Phase 3 `release:e2e` when background / subagent dispatch is available (#1880 Gap D / #2692) — the interactive channel must stay free for version confirmation, `--summary`, and the Phase 2 dry-run gate\n- ⊗ Wrap long release-prep task output in PowerShell `Select-Object -Last` — it buffers until exit and makes the session look hung (#2692)\n- ⊗ Run `task release` without a Phase 2 dry-run preview -- the dry-run is the only safe place to catch a bad version, malformed CHANGELOG, or wrong base branch\n- ⊗ Skip Phase 3 (e2e rehearsal) on the assumption that \"the dry-run is enough\" -- the e2e harness catches gh-CLI auth issues, repo permission gaps, and pipeline-shape regressions that the dry-run cannot detect\n- ⊗ Pass `--no-draft` to `task release` without explicit operator opt-in -- the default-draft contract is the foundation of the safety hardening surface\n- ⊗ Treat Phase 5 as the npm authority gate or require a redundant human `publish` prompt when npm already succeeded -- npm ships at tag push (#1972); Phase 5 is GitHub draft QA only\n- ⊗ Expect `task release:rollback` to retract npm packages -- rollback is GitHub-only; npm recovery is forward-only (deprecate / dist-tag / patch)\n- ⊗ Run `task release:rollback` against a release that has > 30 minutes of consumer-driven downloads without first weighing the hot-fix path -- a withdrawal note in the next patch is almost always less disruptive than deleting a public artifact\n- ⊗ Use `--allow-data-loss` without first reading the script docstring's hot-fix-path recommendation -- the flag is an explicit acknowledgment of consumer impact, not a default\n- ⊗ Skip the Phase 7 Layer 3 reopen sweep -- protected umbrellas can auto-close on a release-merge squash even when the release notes use `Refs #N` only\n- ⊗ Post the Phase 8 Slack announcement directly from this skill -- the user owns the broadcast; the skill only generates the template\n- ⊗ Hardcode `master` as the base branch -- delegate to the configured base branch from `task release --base-branch <branch>`\n- ⊗ Skip the post-create verify-isDraft gate (#724) -- a successful `gh release create` exit code does NOT prove the release actually landed in draft state; the 5-second poll-and-flip gate in `scripts/release.py` Step 11 is the only safety net against operator-error variants and partial-success races, and any manual recovery path that bypasses `scripts/release.py` MUST run `gh release view --json isDraft` followed by `gh release edit --draft=true` on `isDraft=false` before handing off to Phase 5\n- ⊗ Manually rewrite the Phase 8 Slack `*Summary*:` line to deviate from the CHANGELOG `[<version>]` blockquote -- the canonical narrative is authored ONCE at Phase 1 via `--summary` and propagates verbatim across all three audiences (CHANGELOG / GitHub release body / Slack). Per-audience hand-edits create documentation drift that the deterministic `--summary` flow is designed to prevent. If the operator wants Slack-specific tone, fold it into the canonical Phase 1 wording before passing `--summary`, OR amend the CHANGELOG blockquote BEFORE Phase 8 so all three surfaces stay aligned\n- ⊗ Export `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` for the entire release session or wrap `task release` / `task ci:local` in it (#1553) -- the env var is process-wide and leaks into nested tests and temporary repos, producing false preflight failures. Prefer `task policy:allow-direct-commits -- --confirm` and restore with `task policy:enforce-branches` after the cut (closeout commit+push may use a **scoped** env prefix on those three git commands only — see Branch-Protection Policy Guard, #2623)\n- ⊗ Pass `--allow-coverage-debt=#N` unquoted on Windows PowerShell (#2621) -- `#` starts a comment and silently drops the issue number. Use `--allow-coverage-debt=N` or `--allow-coverage-debt=\"#N\"`\n- ⊗ Soft-pass coverage debt while an **open** coverage-debt issue from a prior hatch still exists (#2866 / #2573 / #3187) -- restore real branch coverage >= 85% and close the debt issue before reusing `--allow-coverage-debt` or expecting auto-hatch; the ledger is open GitHub issues, not prior CHANGELOG citations (#2618 superseded by open-issue ledger)\n- ⊗ Re-run the full Step 5 suite after a legal branch-only hairline when auto-hatch already filed `#N` and continued (`PASS_WITH_DEBT`) — that is the ceremony tax #3187 removes\n- ⊗ Trust `.deft/release-suite-stamp.json` in CI or after HEAD/tree drift (#3187)\n",
223
240
  "frontmatter_extra": null
224
241
  },
225
242
  {
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: deft-directive-portfolio-priority
3
+ description: >-
4
+ Pre-promotion portfolio prioritization: cluster competing open RFCs/issues
5
+ from the local github-issue cache, emit a propose-not-apply priority brief
6
+ (conflict matrix, shortlist, park), with epistemic citation gates, then hand
7
+ off for operator dispose. Use when the operator asks for portfolio priority,
8
+ a priority brief, competing RFCs, or to cluster open issues. Do NOT trigger
9
+ on single-item "what's next?" ranking (deft-directive-triage / triage:queue)
10
+ or post-promotion capacity prioritization (#1419/#1511).
11
+ triggers:
12
+ - portfolio priority
13
+ - priority brief
14
+ - competing RFCs
15
+ - cluster open issues
16
+ - pre-promotion portfolio
17
+ - portfolio prioritization
18
+ - rank this backlog slice
19
+ metadata:
20
+ clawdbot:
21
+ requires:
22
+ bins: ["gh"]
23
+ ---
24
+ <!-- AUTO-GENERATED by task packs:render -- DO NOT EDIT MANUALLY -->
25
+ <!-- Purpose: rendered skill -->
26
+ <!-- Source of truth: packs/skills/skills-pack-0.1.json -->
27
+ <!-- Regenerate with: task packs:render -->
28
+ <!-- Edit the source, not this file. Slice instead of loading every SKILL.md: task packs:slice skills by-trigger --trigger <kw> (or list) -->
29
+
30
+ # Deft Directive Portfolio Priority
31
+
32
+ Pre-promotion **portfolio prioritization pass**: cluster competing RFCs/issues from the local github-issue cache, name conflicts/supersession, emit a **propose-not-apply** priority brief (shortlist + park), then hand off for operator dispose. Parent process + dogfood: **#3198**. Worked example: **#3200** / [`docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md`](../../../docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md).
33
+
34
+ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
35
+
36
+ ## When to Use
37
+
38
+ - Maintainer wants a **portfolio priority** brief across competing open issues / RFCs
39
+ - Operator says: portfolio priority, priority brief, competing RFCs, cluster open issues, pre-promotion portfolio, rank this backlog slice
40
+ - After a large classify / label-mirror partition when humans still need a shortlist (B-side; not the ranker)
41
+ - ⊗ Do NOT use for single-issue "what's next?" ranking — that is `deft-directive-triage` + `task triage:queue`
42
+ - ⊗ Do NOT use for post-promotion capacity buckets — that is #1419 / #1511
43
+ - ⊗ Do NOT use for review-handoff briefs — that is #1590
44
+
45
+ ## Stance (hard)
46
+
47
+ - ! **Propose-not-apply** (#3179): the brief proposes; the operator disposes
48
+ - ⊗ SCM label writes, mirror `--apply`, or re-enrich apply from this skill
49
+ - ⊗ `task triage:accept` / reject / defer / needs-ac / mark-duplicate / bulk-*
50
+ - ⊗ Scope lifecycle (`scope:promote` / `activate` / `complete` / `cancel`) from this skill
51
+ - ⊗ Treat the brief as a decision record — dispose into #1396 / plan-sequence / operator comment
52
+
53
+ ## Inputs
54
+
55
+ - ! Local `.deft-cache/github-issue/` (bodies available)
56
+ - ! Classify partition via `task triage:classify -- --mirror` (or cached JSON digest) as **FILTER only**
57
+ - ~ Optional theme slice: label family, title prefix, author, epic parent
58
+
59
+ ## Phases
60
+
61
+ ### Phase 0 — Cache freshness
62
+
63
+ 1. ! Run `task verify:cache-fresh`. Exit 0 → proceed. Exit 1 → refresh (`task cache:fetch-all -- --source=github-issue --repo OWNER/NAME`) or **disclose stale** and continue only with operator consent. Exit 2 → `task triage:bootstrap` first.
64
+ 2. ~ When zero cache entries, bootstrap/fetch before clustering (#2575).
65
+ 3. ⊗ Cluster against a silent stale cache without disclosing staleness.
66
+
67
+ ### Phase 1 — Classify as FILTER only
68
+
69
+ 1. ! Run `task triage:classify -- --mirror` (dry-run; optional `--json`) to partition dispositions (`no_match` / escalate / defer / already planned).
70
+ 2. ! Use classify outcomes only to **filter** the portfolio slice (e.g. deep-dive candidates from `no_match`; keep escalate on an interrupt queue).
71
+ 3. ⊗ Use classify disposition chips as the **ranker** or final shortlist order.
72
+ 4. ⊗ Pass `--apply` or `--re-enrich` with intent to write labels from this skill.
73
+
74
+ ### Phase 2 — Theme / slice selection
75
+
76
+ 1. ! Name an **explicit slice** (label family, title theme, author, or epic). Unattended full-backlog ranking is out of scope.
77
+ 2. ! Record scope counts: open in slice, classify partitions inside the slice, exclusions.
78
+ 3. ~ Prefer one coherent theme per brief (pilot: patterns + `no_match`).
79
+
80
+ ### Phase 3 — Cluster + conflict matrix + shortlist + park
81
+
82
+ 1. ! Group candidates into **families** (title/theme/body overlap, shared parent epic, layered pack).
83
+ 2. ! Build a **conflict / supersession matrix** per family: overlapping roots, pack-vs-atom, research-vs-product.
84
+ 3. ! Emit an ordered **shortlist** (deep dive / promote candidates) and a **park list** with park reasons.
85
+ 4. ! Separate **interrupt / non-portfolio** rows (escalate, hold, already dispositioned) so they are not ranked against park list.
86
+ 5. ⊗ Claim "#X supersedes #Y" from titles alone — see Phase 4 epistemic gates.
87
+
88
+ ### Phase 4 — Epistemic gates (MUST)
89
+
90
+ Before citing any `#N` in the brief:
91
+
92
+ 1. ! **Verify existence + state** for every cited issue via live `gh api repos/OWNER/NAME/issues/N` (REST) **or** a proven-fresh cache entry whose state matches the claim.
93
+ 2. ! **Read the body** of every issue used for shortlist, park-as-superseded, pack membership, or "decided" claims.
94
+ 3. ! When claiming decided / superseded / closed-by-comment, also **read comments** (REST `issues/N/comments`).
95
+ 4. ! State **open/closed accurately**; never invent issue numbers.
96
+ 5. ⊗ **Title-only supersession** or ownership claims.
97
+ 6. ⊗ Cite PRs as issues without filtering `pull_request` on mixed issue lists.
98
+
99
+ ### Phase 5 — Emit priority brief
100
+
101
+ ! Write markdown matching the pilot section shape (fill with this run's slice):
102
+
103
+ 1. **Header** — process issue (#3198 or project equivalent), parent tracker if any, generated timestamp, stance (propose-not-apply), epistemic method
104
+ 2. **Dispose path** — not a decision record; points at #1396 / plan-sequence / operator comment
105
+ 3. **Scope of this pass** — counts table (slice filters + partition sizes)
106
+ 4. **Interrupt / non-portfolio** — escalate/hold/already planned (do not rank here)
107
+ 5. **Conflict / family matrix** — families with verified state + body claims + recommendation
108
+ 6. **Shortlist** — ordered P1/P2/… with why
109
+ 7. **Park list** — classes, examples, park reasons
110
+ 8. **Epistemic limits** — what was cache-only, what needs follow-up probe
111
+ 9. **Operator dispose checklist** — accept/edit shortlist, accept park, record dispose, optional plan-sequence; do not treat as mirror apply
112
+
113
+ ~ Prefer writing under `docs/analysis/YYYY-MM-DD-portfolio-priority-brief-<slice>.md` for durable dogfood; chat-only is allowed if the operator declines a file.
114
+
115
+ ~ Link the worked example: #3200 and `docs/analysis/2026-08-07-portfolio-priority-brief-patterns-pilot.md` (may live on the pilot PR until merged).
116
+
117
+ ### Phase 6 — Dispose checklist (hand off)
118
+
119
+ 1. ! Present the dispose checklist to the operator (accept/edit shortlist + park; record dispose; optional plan-sequence for P1).
120
+ 2. ! Point dispose targets: **#1396**-shaped decision log (or interim issue comment), and/or `task plan-sequence:set`.
121
+ 3. ⊗ Auto-promote shortlist into plan-sequence without explicit operator dispose.
122
+ 4. ⊗ Exit treating the brief alone as durable prioritization memory (#2741 class).
123
+
124
+ ## Anti-Patterns
125
+
126
+ - ⊗ SCM label writes / mirror apply / re-enrich apply from this skill
127
+ - ⊗ `triage:accept` or any triage decision verb from this skill
128
+ - ⊗ Scope lifecycle mutations from this skill
129
+ - ⊗ Title-only supersession or unverified `#N` citations
130
+ - ⊗ Full open-backlog unattended ranking without an explicit slice
131
+ - ⊗ Replacing `triage:queue` for buildable work selection
132
+ - ⊗ Treating the brief as the decision record without dispose
133
+
134
+ ## EXIT
135
+
136
+ ! Confirm: `deft-directive-portfolio-priority complete -- exiting skill.`
137
+ ~ Chain: operator dispose (#1396 / plan-sequence) · `deft-directive-triage` for single-item queue work · `deft-directive-refinement` after accept on a different path · do **not** chain into mirror apply.
138
+ ⊗ Silent exit.
139
+
140
+ ## References
141
+
142
+ - #3198 process + dogfood · #3201 this skill · #3200 / patterns pilot brief
143
+ - #1396 decision log · #3179 propose-not-apply · #1423 / #3197 classify filter only
144
+ - Siblings: `deft-directive-triage`, `deft-directive-refinement` — not #1419/#1511 post-promotion
@@ -114,12 +114,24 @@ The release pipeline's Step 9/10/11 git mutations carry the bypass in subprocess
114
114
 
115
115
  See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Fixable check failure during release for the operator runbook and the existing `--allow-skip-ci=#N` incident contract.
116
116
 
117
- ### Step 5 branch-coverage threshold — open-issue ledger hatch (#2866)
117
+ ### Step 5 branch-coverage threshold — open-issue ledger hatch (#2866 / #3187)
118
118
 
119
- ! When **`task release` Step 5** fails on Vitest coverage below the 85% goal, apply this hatch **only** when **branches** is the **sole** metric below 85% (hairline branch miss — lines, functions, and statements all ≥ 85%). Confirm via the Step 5 failure output or `task coverage:hotspots` before hatching. If **any other metric** is also below 85%, or the failure is a hang / failing test / non-coverage defect, STOP — use § Fixable check failure — file-and-merge before resume (#2859) instead.
119
+ ! When **`task release` Step 5** fails on Vitest coverage below the 85% goal, apply this hatch **only** when **branches** is the **sole** metric below 85% (hairline branch miss — lines, functions, and statements all ≥ 85%). Confirm via the Step 5 failure output, `coverage/coverage-final.json`, or `task coverage:hotspots` before hatching. If **any other metric** is also below 85%, or the failure is a hang / failing test / non-coverage defect, STOP — use § Fixable check failure — file-and-merge before resume (#2859) instead.
120
120
 
121
121
  **Runtime disclosure (#2573):** `--allow-coverage-debt=#N` sets vitest coverage thresholds to zero for the release Step 5 run (`vitest.config.ts`). The hatch is justified only for branch-only hairlines; the filed debt issue MUST require restoring **all four metrics** (lines, functions, branches, statements) to ≥ 85% before close.
122
122
 
123
+ **Auto-hatch in `task release` Step 5 (#3187):** the pipeline classifies a non-zero Step 5 as `REAL_FAILURE` | `BRANCH_HAIRLINE` | `OTHER_COVERAGE` | `UNKNOWN` after **one** suite run.
124
+
125
+ 1. `REAL_FAILURE` | `OTHER_COVERAGE` | `UNKNOWN` → **fail closed** (no auto-issue). Route product defects via #2859.
126
+ 2. `BRANCH_HAIRLINE` + **any open** coverage-debt ledger entry → **fail closed** ("restore coverage; close `#N` first") — consecutive soft-pass ban (#2866).
127
+ 3. `BRANCH_HAIRLINE` + **empty** open debt ledger → **auto-file** `#N` with markers `coverage-debt` and `--allow-coverage-debt`, measured metrics, cut version, and acceptance (all four metrics ≥ 85%); mark Step 5 `PASS_WITH_DEBT(#N)`; **continue the cut without re-running vitest**. Loud stderr banner + cite `#N` in CHANGELOG / release notes.
128
+
129
+ ! **File before continue** — never soft-pass without a durable issue number.
130
+
131
+ ? Operators MAY still pass `--allow-coverage-debt=#N` manually (PowerShell-safe: `--allow-coverage-debt=N` or `--allow-coverage-debt="#N"` — #2621) when re-running after a pre-filed debt issue.
132
+
133
+ **SHA suite stamp (#3187, coordinates with #3188 check ordering):** after suite **green** or `PASS_WITH_DEBT`, write a local SHA-bound stamp under `.deft/release-suite-stamp.json` (gitignored). Re-entry at the **same clean HEAD** skips the suite; dirty tree or different HEAD invalidates the stamp. ⊗ CI never trusts the stamp (GHA has no committed stamp; `CI`/`GITHUB_ACTIONS` force miss).
134
+
123
135
  **Open-issue ledger (release-scoped only):**
124
136
 
125
137
  1. Check for an **open coverage-debt tracking issue** (unpaid hatch from a prior cut) — union all three probes and dedupe by issue number:
@@ -128,15 +140,18 @@ See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Fixable check failure d
128
140
  gh issue list --repo <owner>/<repo> --state open --search "coverage-debt in:title,body" --limit 20
129
141
  gh issue list --repo <owner>/<repo> --state open --search "allow-coverage-debt in:body" --limit 20
130
142
  ```
131
- - **CHANGELOG citation scan** (legacy hatch issues filed before markers were mandatory): parse `CHANGELOG.md` `[Unreleased]` plus the last three `## [version]` sections for `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations; for each `#N`, run `gh issue view N --json state --jq .state` — `OPEN` counts as unpaid debt even when title/body lack the new markers (#2866).
132
- 2. If **no open coverage-debt issue exists** → file `#N` with title prefix `coverage-debt:` and body containing both markers `coverage-debt` and `--allow-coverage-debt`, documenting measured metrics, branch-only trigger, and acceptance criteria (restore **all four** coverage metrics ≥ 85%). Continue the cut with `--allow-coverage-debt=#N` on `task release` (PowerShell-safe: `--allow-coverage-debt=N` or `--allow-coverage-debt="#N"` — see Anti-Patterns #2621). The open `#N` remains WIP until coverage is restored and the issue is closed.
143
+ - **CHANGELOG citation scan** (legacy hatch issues filed before markers were mandatory): parse `CHANGELOG.md` `[Unreleased]` plus the last three `## [version]` sections for `--allow-coverage-debt=#N` / `allow-coverage-debt=#N` citations; for each `#N`, probe issue state via REST — `OPEN` (or unknown) counts as unpaid debt even when title/body lack the new markers (#2866).
144
+ 2. If **no open coverage-debt issue exists** → auto-hatch files `#N` (or operator files manually) with title prefix `coverage-debt:` and body containing both markers. The open `#N` remains WIP until coverage is restored and the issue is closed.
133
145
  3. If an **open coverage-debt issue from a prior hatch still exists** → ⊗ soft-pass again; restore real coverage (all four metrics ≥ 85%) and close the debt issue before the cut proceeds.
134
146
 
147
+ **Framework-release-first (#3187):** auto-hatch applies to framework `task release` Step 5 (e.g. deftai/directive). Consumer expansion of auto-hatch is blocked on project `plan.policy.coverageDebt` (#3189) — refuse when `status=unset` or `mode=off`. Do not auto-file debt on the framework repo from consumer trees; consumer ledger is always the consumer repo.
148
+
135
149
  ⊗ Auto-pass on a near-miss band without `#N` (#2573).
136
150
  ⊗ Silent soft-pass with no tracked issue.
137
151
  ⊗ File a debt issue without `coverage-debt` / `allow-coverage-debt` markers in title or body — the ledger query will miss it and permit a consecutive soft-pass (#2866).
138
152
  ⊗ Use this carve-out when lines, functions, or statements are also below 85%, or for hangs, failing tests, or non-coverage Step 5 failures — those stay under #2859 file-and-merge.
139
153
  ⊗ Treat file-debt-then-hatch as the default for ordinary PR / `task check` work outside a release cut — this hatch is release-scoped only.
154
+ ⊗ Trust a laptop suite stamp in CI or after HEAD/tree drift (#3187).
140
155
 
141
156
  See [`docs/RELEASING.md`](../../../docs/RELEASING.md) § Coverage debt hatch during release.
142
157
 
@@ -373,4 +388,6 @@ Where `<one-line guidance>` is one of:
373
388
  - ⊗ Manually rewrite the Phase 8 Slack `*Summary*:` line to deviate from the CHANGELOG `[<version>]` blockquote -- the canonical narrative is authored ONCE at Phase 1 via `--summary` and propagates verbatim across all three audiences (CHANGELOG / GitHub release body / Slack). Per-audience hand-edits create documentation drift that the deterministic `--summary` flow is designed to prevent. If the operator wants Slack-specific tone, fold it into the canonical Phase 1 wording before passing `--summary`, OR amend the CHANGELOG blockquote BEFORE Phase 8 so all three surfaces stay aligned
374
389
  - ⊗ Export `DEFT_ALLOW_DEFAULT_BRANCH_COMMIT=1` for the entire release session or wrap `task release` / `task ci:local` in it (#1553) -- the env var is process-wide and leaks into nested tests and temporary repos, producing false preflight failures. Prefer `task policy:allow-direct-commits -- --confirm` and restore with `task policy:enforce-branches` after the cut (closeout commit+push may use a **scoped** env prefix on those three git commands only — see Branch-Protection Policy Guard, #2623)
375
390
  - ⊗ Pass `--allow-coverage-debt=#N` unquoted on Windows PowerShell (#2621) -- `#` starts a comment and silently drops the issue number. Use `--allow-coverage-debt=N` or `--allow-coverage-debt="#N"`
376
- - ⊗ Soft-pass coverage debt while an **open** coverage-debt issue from a prior hatch still exists (#2866 / #2573) -- restore real branch coverage >= 85% and close the debt issue before reusing `--allow-coverage-debt`; the ledger is open GitHub issues, not prior CHANGELOG citations (#2618 superseded by open-issue ledger)
391
+ - ⊗ Soft-pass coverage debt while an **open** coverage-debt issue from a prior hatch still exists (#2866 / #2573 / #3187) -- restore real branch coverage >= 85% and close the debt issue before reusing `--allow-coverage-debt` or expecting auto-hatch; the ledger is open GitHub issues, not prior CHANGELOG citations (#2618 superseded by open-issue ledger)
392
+ - ⊗ Re-run the full Step 5 suite after a legal branch-only hairline when auto-hatch already filed `#N` and continued (`PASS_WITH_DEBT`) — that is the ceremony tax #3187 removes
393
+ - ⊗ Trust `.deft/release-suite-stamp.json` in CI or after HEAD/tree drift (#3187)