@deftai/directive-content 0.106.0 → 0.108.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.
- package/Taskfile.yml +14 -1
- package/UPGRADING.md +24 -5
- package/commands.md +29 -4
- package/contracts/design-critique.md +369 -14
- package/contracts/issue-eval.md +77 -0
- package/contracts/path-write-fence.md +126 -1
- package/contracts/runtime-authority.md +2 -0
- package/contracts/scm-readiness.md +2 -2
- package/docs/delivery-attempt.md +2 -1
- package/docs/freshness-contract.md +6 -1
- package/docs/getting-started.md +10 -11
- package/docs/hook-runtime-unavailable.md +54 -0
- package/docs/orphan-active-verdict-basis.md +166 -0
- package/docs/scope-provenance.md +1 -1
- package/package.json +1 -1
- package/packs/skills/skills-pack-0.1.json +24 -10
- package/scm/github.md +65 -2
- package/skills/deft-directive-build/SKILL.md +2 -2
- package/skills/deft-directive-cost/SKILL.md +7 -11
- package/skills/deft-directive-design-critique/SKILL.md +22 -6
- package/skills/deft-directive-design-critique/references/motion-shape.md +19 -0
- package/skills/deft-directive-feedback/SKILL.md +11 -2
- package/skills/deft-directive-interview/SKILL.md +10 -10
- package/skills/deft-directive-issue-eval/SKILL.md +48 -0
- package/skills/deft-directive-release/SKILL.md +10 -6
- package/skills/deft-directive-review-cycle/SKILL.md +33 -0
- package/skills/deft-directive-setup/SKILL.md +53 -22
- package/skills/deft-directive-swarm/references/core-ops.md +4 -0
- package/skills/deft-directive-swarm/references/core-phase-1-2.md +1 -1
- package/skills/deft-directive-swarm/references/core-phase-3.md +3 -1
- package/skills/deft-directive-swarm/references/core-phase-4.md +11 -8
- package/skills/deft-directive-swarm/references/host-cursor.md +1 -0
- package/skills/deft-directive-swarm/references/host-grok-build.md +1 -0
- package/skills/deft-directive-triage/SKILL.md +3 -2
- package/tasks/engine.yml +4 -0
- package/tasks/feedback.yml +1 -1
- package/tasks/occupancy.yml +34 -1
- package/tasks/prd.yml +4 -5
- package/tasks/scm.yml +14 -2
- package/tasks/session.yml +13 -2
- package/tasks/toolchain.yml +2 -2
- package/tasks/triage-evaluate.yml +22 -0
- package/tasks/verify.yml +21 -1
- package/templates/agent-prompt-preamble.md +28 -4
- package/templates/agents-entry.md +10 -5
- package/templates/design-critique-brief.md +19 -5
package/Taskfile.yml
CHANGED
|
@@ -257,6 +257,10 @@ includes:
|
|
|
257
257
|
triage-classify:
|
|
258
258
|
taskfile: ./tasks/triage-classify.yml
|
|
259
259
|
optional: true
|
|
260
|
+
# #3648: `task triage:evaluate` Stage A isolated validity + parent WIP census.
|
|
261
|
+
triage-evaluate:
|
|
262
|
+
taskfile: ./tasks/triage-evaluate.yml
|
|
263
|
+
optional: true
|
|
260
264
|
# D2 (#1122): `task triage:summary` one-liner -- status surface invoked by
|
|
261
265
|
# the session-start ritual (N9 / #1149). Inner task `summary` is exposed
|
|
262
266
|
# as the user-facing alias `task triage:summary` in the alias block below.
|
|
@@ -444,6 +448,7 @@ tasks:
|
|
|
444
448
|
- verify:pack-drift
|
|
445
449
|
- verify-wip-cap-framework-self-check
|
|
446
450
|
- verify:orphan-active
|
|
451
|
+
- verify:completed-write-guard
|
|
447
452
|
- verify:agents-md-budget
|
|
448
453
|
- verify-eval-health-relocation-framework-check
|
|
449
454
|
- verify-eval-triggers-relocation-framework-check
|
|
@@ -475,6 +480,7 @@ tasks:
|
|
|
475
480
|
- verify:cache-fresh
|
|
476
481
|
- verify:wip-cap
|
|
477
482
|
- verify:orphan-active
|
|
483
|
+
- verify:completed-write-guard
|
|
478
484
|
- verify:test-boundary
|
|
479
485
|
- verify:scope-provenance
|
|
480
486
|
- verify:consumer-check-contract
|
|
@@ -732,7 +738,7 @@ tasks:
|
|
|
732
738
|
# tests/cli/test_release_e2e.py
|
|
733
739
|
# Refs #74, #233, #642, #635, #709, #710, #716, #718.
|
|
734
740
|
release:
|
|
735
|
-
desc: "Automate the v0.X.Y release flow (#74) -- task release -- <version> [--dry-run] [--skip-tag] [--skip-release] [--no-draft]"
|
|
741
|
+
desc: "Automate the v0.X.Y release flow (#74) -- task release -- <version> [--dry-run] [--skip-tag] [--skip-release] [--no-draft] [--allow-vbrief-drift]"
|
|
736
742
|
deps: [ts:build]
|
|
737
743
|
dir: '{{.USER_WORKING_DIR}}'
|
|
738
744
|
cmds:
|
|
@@ -991,6 +997,13 @@ tasks:
|
|
|
991
997
|
vars:
|
|
992
998
|
CLI_ARGS: "{{.CLI_ARGS}}"
|
|
993
999
|
|
|
1000
|
+
triage:evaluate:
|
|
1001
|
+
desc: "Evaluate issues off origin/master (validity + parent WIP + value). -- task triage:evaluate -- <N...> [--concurrency N] [--repo OWNER/NAME] [--json] (#3648)"
|
|
1002
|
+
cmds:
|
|
1003
|
+
- task: triage-evaluate:evaluate
|
|
1004
|
+
vars:
|
|
1005
|
+
CLI_ARGS: "{{.CLI_ARGS}}"
|
|
1006
|
+
|
|
994
1007
|
triage:queue:
|
|
995
1008
|
desc: "Print the ranked triage queue (#1128 / D11). -- task triage:queue [-- --repo OWNER/NAME] [--limit N]"
|
|
996
1009
|
cmds:
|
package/UPGRADING.md
CHANGED
|
@@ -8,6 +8,23 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
|
8
8
|
|
|
9
9
|
<!-- xbrief-backcompat-2111 -->
|
|
10
10
|
|
|
11
|
+
## Dual policy block recovery (#3609)
|
|
12
|
+
|
|
13
|
+
- **Applies when:** PROJECT-DEFINITION contains both legacy bare `plan.policy` and namespaced `plan["x-directive/policy"]`. The namespaced block wins reads, so the bare block is shadowed even when one or more values match.
|
|
14
|
+
- **Safe to auto-run:** No. A generic writer cannot know how to combine unrelated keys or resolve collisions without data loss.
|
|
15
|
+
- **Restart required:** No.
|
|
16
|
+
- **Recovery:**
|
|
17
|
+
1. Run `deft policy:show --field=plan.policy.allowDirectCommitsToMaster` and inspect stderr; the inspector warns but remains read-only and may exit 0.
|
|
18
|
+
2. Inventory the keys in both blocks.
|
|
19
|
+
3. Move every bare-only key into `plan["x-directive/policy"]`.
|
|
20
|
+
4. Resolve every key present in both blocks explicitly.
|
|
21
|
+
5. Delete bare `plan.policy`.
|
|
22
|
+
6. Run the selected writer (`deft policy:enforce-branches --actor <actor>` or confirmed `deft policy:allow-direct-commits --confirm --actor <actor>`) and `deft verify:vbrief-conformance --project-root .`.
|
|
23
|
+
|
|
24
|
+
Policy writers now fail with config exit 2 before changing PROJECT-DEFINITION or the policy audit log while both blocks coexist. Legacy-only projects remain supported: the next policy write migrates the complete legacy block to the namespaced key. For corpus-wide legacy-only cleanup, run `deft migrate:category-b`; it also fails closed on coexistence and is not a dual-block healer. Namespaced-only matching writes remain no-ops.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
11
28
|
## npm v12 install-time security defaults
|
|
12
29
|
|
|
13
30
|
npm v12 flips three install defaults from "on" to opt-in. The same features shipped on **npm 11.16.0+** with warnings so you can migrate before enforcement.
|
|
@@ -365,9 +382,9 @@ It is common after a manual folder rename or when agents stamp the current key n
|
|
|
365
382
|
`migrate:xbrief` touches your `AGENTS.md` in two distinct regions:
|
|
366
383
|
|
|
367
384
|
- **Managed section** (between the `<!-- deft:managed-section ... -->` / `<!-- /deft:managed-section -->` markers): regenerated wholesale by `agents:refresh`, so it always reflects the current framework layout. This region is rendered from the framework template — do not hand-edit it.
|
|
368
|
-
- **Unmanaged header/tail** (everything outside those markers — your project-specific `Session orientation`, `Lifecycle` examples, `Local dev` notes): preserved verbatim across upgrades so your consumer notes survive. Because it is preserved, a rename migration would otherwise leave stale `vbrief/` path literals here. `migrate:xbrief` now applies a **bounded, idempotent** rewrite over the unmanaged region only
|
|
385
|
+
- **Unmanaged header/tail** (everything outside those markers — your project-specific `Session orientation`, `Lifecycle` examples, `Local dev` notes): preserved verbatim across upgrades so your consumer notes survive. Because it is preserved, a rename migration would otherwise leave stale `vbrief/` path literals here. `migrate:xbrief` now applies a **bounded, idempotent** rewrite over the unmanaged region only after a first-time `vbrief/` → `xbrief/` conversion, replacing `.vbrief.json`, `vbrief:preflight`, and left-bounded `vbrief/` with a child segment (`vbrief/active/...`). Bare `vbrief/` directory mentions and `x-vbrief/` leftover prefixes are left in place. It prints a summary of the replacements.
|
|
369
386
|
|
|
370
|
-
If you upgraded before this fix landed and
|
|
387
|
+
If you upgraded before this fix landed and the unmanaged header still carries child-path `vbrief/` literals, `.vbrief.json`, or `vbrief:preflight`, `deft doctor` emits an `AGENTS.md header drift:` signpost. Hand-edit those path literals. `deft migrate:xbrief` does not patch AGENTS.md on an already-xbrief tree.
|
|
371
388
|
|
|
372
389
|
**Option A (canonical, #2065):** new installs scaffold a **bounded** unmanaged header only — a project one-liner plus a **Session orientation** pointer at the canonical sources below. Do **not** add freeform `Status`, `Next:`, or `Known Issues` blocks; they are retired because the framework preserves the header verbatim while `deft doctor` only checks the managed section (#794, #1308). Session orientation comes from:
|
|
373
390
|
|
|
@@ -649,11 +666,13 @@ This complements (does not replace) `deft verify:forward-coverage` (#1310 / #351
|
|
|
649
666
|
|
|
650
667
|
## Node runtime (#1828 / #1530)
|
|
651
668
|
|
|
652
|
-
After Wave 8, live deft gates run through the TypeScript engine. **Node.js and
|
|
669
|
+
After Wave 8, live deft gates run through the TypeScript engine. **The consumer toolchain probe requires Node.js and the package manager selected by the project (`npm` or `pnpm`).** That probe does not check Python, `uv`, or go-task; session and workflow gates keep their separately documented prerequisites.
|
|
653
670
|
|
|
654
671
|
- Install **Node 20+** (the framework pins `.nvmrc`; currently Node 24).
|
|
655
|
-
-
|
|
656
|
-
-
|
|
672
|
+
- If `package.json#packageManager` selects npm, use the npm bundled with Node. If it selects pnpm, enable pnpm via Corepack: `corepack enable && corepack prepare pnpm@latest --activate`.
|
|
673
|
+
- Consumer selection precedence is `DEFT_PACKAGE_MANAGER`, `package.json#packageManager`, `pnpm-lock.yaml`, `npm_config_user_agent`, then npm. An explicit unsupported manager fails with a supported-manager diagnostic; its raw value is never executed.
|
|
674
|
+
- Verify from your project root: `deft toolchain:check --consumer --project-root .`. The check probes the selected manager, names the selection source, and prints manager-specific remediation instead of failing later with an opaque stack trace.
|
|
675
|
+
- Framework maintainers still run `task toolchain:check`; that source-repository check remains pnpm-based.
|
|
657
676
|
|
|
658
677
|
---
|
|
659
678
|
|
package/commands.md
CHANGED
|
@@ -189,6 +189,20 @@ flowchart TD
|
|
|
189
189
|
|
|
190
190
|
---
|
|
191
191
|
|
|
192
|
+
## Branch-policy commands (#3609)
|
|
193
|
+
|
|
194
|
+
Directive exposes the logical field as `plan.policy.allowDirectCommitsToMaster`, but the writer stores it under `plan["x-directive/policy"]`. Use the public commands; do not hand-write bare `plan.policy`:
|
|
195
|
+
|
|
196
|
+
- Inspect: `deft policy:show --field=plan.policy.allowDirectCommitsToMaster`
|
|
197
|
+
- Enforce feature branches: `deft policy:enforce-branches --actor <actor>`
|
|
198
|
+
- Allow confirmed trunk work: `deft policy:allow-direct-commits --confirm --actor <actor>`
|
|
199
|
+
|
|
200
|
+
Every policy writer fails with config exit 2 when bare `plan.policy` and namespaced `plan["x-directive/policy"]` coexist, including when their branch values match. Recovery is lossless and manual: inventory both blocks, fold every bare-only key into the namespaced block, explicitly resolve each collision, delete bare `plan.policy`, then rerun the command. The failure writes neither PROJECT-DEFINITION nor `meta/policy-changes.log`.
|
|
201
|
+
|
|
202
|
+
Legacy-only projects may migrate through the next policy write or through corpus-wide `deft migrate:category-b`. The migration command also fails closed on coexistence; it is not a dual-block healer.
|
|
203
|
+
|
|
204
|
+
Setup uses these same commands for every interview track. Branch-based is persisted as explicit `false` unless the operator passes the trunk capability-cost confirmation; before reporting Phase 2 complete, setup reads the selected boolean back, confirms bare `plan.policy` is absent, and runs `deft verify:vbrief-conformance --project-root .`.
|
|
205
|
+
|
|
192
206
|
## Default-branch sync (`scm:sync-default`, #3391)
|
|
193
207
|
|
|
194
208
|
Open dest-targeted sync PRs from typed `baseBranch` to `deliveryBranch`. Consumes the shared detector (#3388) and `syncMaxFiles` (#3390).
|
|
@@ -292,7 +306,9 @@ Current status: the validation, extractor, provider, registry, generated MAP, an
|
|
|
292
306
|
- `task verify:cache-fresh` -- validate cache freshness where required.
|
|
293
307
|
- `task verify:capacity`, `task verify:wip-cap`, and `task verify:judgment-gates` -- policy/capacity gates.
|
|
294
308
|
- `task verify:orphan-active` -- fail closed when active/running xBRIEFs still point at closed issues or merged PRs (#2321). After merge, `task verify:orphan-active -- --issue N` scans briefs that reference that issue. Confirmed shipped prints `task scope:complete -- <path>`; unresolved lookup prints a retry remediation and still exits 1 (#3429). PR-only briefs stay on the unscoped scan or `task swarm:complete-cohort`.
|
|
309
|
+
- `task verify:pr-closeout-attestable -- --pr N` -- fail closed when merging PR `N` would close an issue whose brief is still `running` in `xbrief/active/` with acceptance criteria carrying neither `x-directive/evidence` nor `x-directive/disposition` (#3781). The trigger is the PR's structured closing references, **not** the branch diff: CI runs before the merge and the issue closes on it, so a diff-keyed gate can never fail the PR that creates the orphan, and the brief need not be in the diff at all. Reuses `evaluateAcceptanceEvidenceGate`, the same rule `scope:complete` enforces. The refusal names each unattested criterion and the exact shape it needs, narrowed to the evidence kinds that criterion's axis allows. A PR that leaves an unattested brief **without** closing its issue is unaffected. Reads the working tree at `--project-root`, which at merge time is the PR head checkout. Matching is repository-qualified and case-insensitive on the owner/repo slug: closing references are scoped to the PR's repository, so a same-numbered issue tracked by a brief in another repository cannot block the merge, a mixed-case slug for this repository still matches, and a bare tracking number reads as this repository. Three-state exit (0 attestable or closes nothing / 1 unattested closeout / 2 config or lookup error) -- an unresolved closing-reference lookup, or an unresolvable OWNER/REPO, is 2, never a pass. `task pr:wait-mergeable-and-merge` runs it as the last gate before the merge call.
|
|
295
310
|
- `task verify:lifecycle-visible` -- warn when a clone's ignore configuration hides `xbrief/` / `vbrief/` lifecycle roots (#3505). Uses `git check-ignore -v` on the stage dirs, a matching-extension sentinel under each, and bounded probes derived from ignore-rule globs in root, exclude, `core.excludesFile`, and nested lifecycle `.gitignore` files (so `2026-06-*.xbrief.json` / `2025-*.xbrief.json` cannot report clean), plus `git ls-files -v` for skip-worktree / assume-unchanged. Names the matching rule and source file. Warn-first from `session:start` (per-clone, not on `task check`). Selective `.triage-cache/*.jsonl` entries do not trip. Pass `--enforce` to fail closed.
|
|
311
|
+
- `task verify:completed-write-guard` -- fail closed when a newly added `xbrief/completed/` (or `vbrief/completed/`) artifact was not written by `scope:complete` / `scope:fail` (#3679). Artifacts larger than 1 MiB fail closed before read. Historical corpus is advisory via doctor. Remediation names the leftover land PR after a scope-provenance strip (#3476). `scope:complete` can stamp a brief already in `completed/`. Does not change `verify:completed-tracked`.
|
|
296
312
|
- `task verify:completed-tracked` -- fail closed when closed scoped issues lack a tracked `xbrief/completed/` or `xbrief/cancelled/` artifact on the delivery tip (#3264 / #3476); remediate with `task swarm:finalize-cohort` or a lifecycle PR. `task verify:completed-tracked -- --issue N` is the drive-to DONE form (delivery tip `origin/<deliveryBranch>`, not feature HEAD). Standalone verb (not part of `task check`); use `--tip HEAD` when validating an in-flight land branch. An unresolvable delivery tip fails closed (no silent HEAD fallback) -- fetch the delivery branch or pass an explicit `--tip`. Under `--skip-gh`, a named `--issue` with no cached state fails closed; the unscoped corpus scan keeps the offline allowance. Lifecycle-only lands (completed/cancelled xBRIEFs + optional CHANGELOG) use that verb plus finalize-cohort or the lifecycle PR. ⊗ Full `task check` / the TypeScript suite. ⊗ The drive-to story envelope (pre-pr + review-cycle + suite) for a file-copy land.
|
|
297
313
|
- `task verify:ac` -- product-first acceptance gate (#3284). Runs `plan.acceptance.commands` (or #3267 literal ledger) **verbatim** before done; records AC-source rung (`stated`/`derived`/`project_floor`). Empty commands require `none_stated: true`. Empty resolution is not a green run when the project has no suite floor (`soft_empty` + stamp-acceptance remedy, #3334). Primary name used first in `task check` (fail-fast); `--soft-missing-xbrief` for check composition. Rapid ceremony = AC-only; pressure/degraded makes hygiene advisory. `--capture-only` lists resolved commands without executing. Extends #3267 / #973.
|
|
298
314
|
- `task verify:literal-ac` -- #3267 mechanism alias for verbatim stated-command run (same flags/cwd); prefer `verify:ac` for product-first done-gate.
|
|
@@ -323,7 +339,7 @@ task pr:finish-loop -- <N> # after a PR is open
|
|
|
323
339
|
|
|
324
340
|
When the workflow needs an Approach 1 monitor, scope the Cursor leaf `stop-at: pr-open`. The orchestrator that owns the Task primitive must spawn the sibling review-monitor and claim the PR-anchored lease with `task review-monitor:register -- --pr <N> --monitor-agent-id <id> --platform-primitive cursor-task`; `task verify:review-monitor -- --pr <N>` remains the fail-closed proof of active GitHub ownership (sticky `<!-- deft:review-owner -->` comment — not local JSON). Release with `task review-monitor:release -- --pr <N>` when done. Owner Continuity / L4 handoff gate (#3090): `task verify:l4-owner -- --pr <N>` (or `deft verify:l4-owner --pr <N>`) exits 0 only when a sticky lease is fresh or `--review-cycle done` after Step 6; freeform `started`/`pending` is rejected. See `skills/deft-directive-review-cycle/SKILL.md` Owner Continuity Gate + Review Monitoring and `skills/deft-directive-swarm/SKILL.md` Phase 3.
|
|
325
341
|
|
|
326
|
-
**Worker liveness (#2824):** For in-flight `drive-to: merge*` Cursor leaves, monitors run `task verify:subagent-alive -- --require-agent <agent-id> [--scratch-dir <worktree>/.deft-scratch/subagent-status]` each poll iteration. Exit `1` prints `REDISPATCH_OK` — authorize takeover when the host still reports running but heartbeats are missing/STALE. Raw heartbeat sweep: `task agent:monitor`. See `docs/subagent-heartbeat.md` § Cursor false-alive.
|
|
342
|
+
**Worker liveness (#2824 / #3730):** For in-flight `drive-to: merge*` Cursor leaves, monitors run `task verify:subagent-alive -- --require-agent <agent-id> [--scratch-dir <worktree>/.deft-scratch/subagent-status]` each poll iteration. Exit `1` prints `REDISPATCH_OK` — authorize takeover when the host still reports running but heartbeats are missing/STALE. Takeover is `task swarm:pre-dispatch -- --action cancel` then begin; a killed worker stays `running` until that cancel. If gated ritual verify fails, run `session:start --rearm --session-id=<same>` first. Raw heartbeat sweep: `task agent:monitor`. See `docs/subagent-heartbeat.md` § Cursor false-alive.
|
|
327
343
|
|
|
328
344
|
### Agent-host direct-write hooks (#2438, #2596)
|
|
329
345
|
|
|
@@ -367,7 +383,7 @@ When the workflow needs an Approach 1 monitor, scope the Cursor leaf `stop-at: p
|
|
|
367
383
|
- Shared checklist SoT: `packages/core/src/session/compact-ritual.ts` (all host deposits derive from it).
|
|
368
384
|
- Codex project hooks are trust-gated by Codex. Directive can verify structural registration and command functionality, but reports trust separately as `manual-review-required` and interception as `not-directly-verified`; after an install or changed hook hash, open `/hooks` in Codex and review/approve the exact project hook commands. Runtime trust and real host interception cannot be inferred from the file or live shim probe alone.
|
|
369
385
|
- Directive writes only `.codex/hooks.json`; it does not parse or modify `.codex/config.toml`. Codex can also load inline hooks from `config.toml`, so avoid defining duplicate Directive commands there or they may run more than once. See the [Codex hooks documentation](https://learn.chatgpt.com/docs/hooks).
|
|
370
|
-
-
|
|
386
|
+
- Recognized Shell dest-forms (`git checkout -- <paths>`, `git restore`, `rm`/`rmdir`) share the active-xBRIEF write gate with Edit/Write, including assist/scratch, proposed-lifecycle, and story `file_scope` exemptions (#3438). Other shell-mediated file writes (`python -c`, `cmd /c copy`, obfuscated bash), richer unified-exec calls, and WebSearch stay unclassifiable and fail open. **Runtime authority (#1394 / #2711)** adds opt-in path allow/deny lists and graduated `scopes` (`edits`, `push`, `merge`) under `plan.policy.runtimeAuthority` — inspect with `deft policy:show --field=runtimeAuthority`. When `enabled: true`, PreToolUse denies classifiable direct-write targets outside `allowPaths` or matching `denyPaths` after ritual/scope/read-only gates; `scopes.edits` gates all direct writes. `scopes.push` / `scopes.merge` deny classifiable Shell/Bash (`git push`, `gh pr merge`) and classifiable MCP push/merge tool names; unclassifiable non-dest shell/MCP calls fail open (see `content/contracts/runtime-authority.md`). **Unified path write fence (#516 / #2443 / #2948 Wave 3):** PreToolUse also intersects project allow/deny with the active story’s `plan.metadata.swarm.file_scope` via `resolveWriteFence` (single evaluation SoT; optional `writeScope` alias normalizes at read-time only). Full contract: `content/contracts/path-write-fence.md`.
|
|
371
387
|
- **Human-origin authz + UAT mutation lease (#2944 / #2948 Wave 1)** — `deft authz:uat-start` / `authz:grant` / `authz:show`. When UAT is active, PreToolUse denies product/UI edits, push, PR create/advance, and merge without a named fix-cohort human-origin grant; tests, issue filing, and evidence/defect-capture writes stay allowed. Self-authored xBRIEF/lifecycle/dispatch tokens never satisfy implement gates. Contract: `content/contracts/human-origin-authz.md`.
|
|
372
388
|
- **Closed-verb release gates + AFK templates (#1095 / #2948 Wave 4 / #3527)** — `deft authz:grant -- --template release-publish --target <ver>` (also `release-cut`, `release-rollback`) mints Wave 1 operator-cli grants only. `task release` fails closed at the Step 10–11 tag-push / npm-publish boundary unless a matching grant exists or `DEFT_ALLOW_RELEASE_PUBLISH=1`. `deft release-publish` / `task release:publish` still fails closed before draft→public (same verb; not deleted). `--skip-tag` and dry-run stay ungated. No second session-auth mint engine. Contract: `content/contracts/closed-verb-authz.md`.
|
|
373
389
|
- **Structural scope:decompose apply grant (#3239 / #3291)** — after `scope:decompose --check` validates a draft, mint with `deft authz:grant -- --parent <parent.xbrief.json> --draft <draft.json> [--repo owner/name] [--single-use] --confirm` (typed phrase `mint` on a real TTY). Digests exact draft bytes and binds parent/target/worktree (and optional repo). Then `deft scope:decompose -- <parent> --draft <draft>`. `--check` stays ungated. Apply denies print the exact mint command.
|
|
@@ -395,7 +411,14 @@ After CLI/deposit upgrade, disk can show the new generation while a long-lived s
|
|
|
395
411
|
- ! **USER.md path (#2544):** resolve via `deft session:start` output (`USER.md resolved …`); default platform paths: Windows `%APPDATA%\deft\USER.md`, Unix `~/.config/deft/USER.md`; override `$DEFT_USER_PATH`; workspace `<project>/.deft/USER.md`. ⊗ Invent or search `~/.config/deft` on Windows — AppData Roaming is canonical.
|
|
396
412
|
- ! At mutation boundaries (code-writing, scope lifecycle moves, `start_agent`, commits, pushes, PR-from-local-changes, release work): run the mutable quick tier then gated verifier below before proceeding.
|
|
397
413
|
- ? Explicit read-only alignment only: `deft session:start -- --read-only` (no ritual-state write).
|
|
398
|
-
- ! **Worktree occupancy (#3433):** mutation `session:start` / `session:ready` claim a gitignored `.deft/occupancy.json` lease.
|
|
414
|
+
- ! **Worktree occupancy contract (#3433 / #3604 / #3611):** mutation `session:start` / `session:ready` claim a gitignored `.deft/occupancy.json` lease. Occupancy is cooperative host-session routing between processes that follow the protocol, not authentication against another same-user process: hook JSON and local owner IDs are forgeable. Name it for what it is (#3755): a **cooperative bearer-id boundary, not a lineage** — the lease admits whoever presents an id the record itself names, nothing observes parentage, and a dispatched child is admitted only because a grant records it. A live lease allows only a matching presented owner or member and a ritual state whose exact verified owner agrees; missing, conflicting, or mismatched identity fails closed. `--read-only` does not claim.
|
|
415
|
+
- ! **Host owner namespaces (#3611):** canonical owners are `host:<provider>:v1:<base64url(raw-id)>`. Codex uses payload `session_id` for the parent and its subagents. Claude Code uses `session_id`; `agent_id` does not replace the session-family owner. Cursor uses `conversation_id` and requires a simultaneously supplied `session_id` to agree; no Cursor subagent-granularity claim is made until verified. Grok has no assumed payload identity and retains explicit `--session-id` / `DEFT_SESSION_ID` ownership.
|
|
416
|
+
- ! **Lifecycle identity transport (#3611):** for Codex, Claude, and Cursor, PreToolUse rewrites only exact, simple canonical `deft` / `directive` lifecycle commands and source-repo `task <verb> [-- ...]` forms to add the host's `--session-id`. Direct CLI spellings are `session:start`, `session:ready`, `session:end`, `occupancy:steal`, `occupancy:release`, `occupancy:heartbeat`, and `swarm-launch`; the Task spelling for the last verb is `swarm:launch`. For owner-requiring lifecycle commands, the shell execution directory must be absent or realpath-align exactly with the hook project root. Path-bearing executables (`./deft`, `/path/deft`), path/destination flags (`--project-root`, `--paths`, path-valued `--stories`, `--output`, `--worktree-map`, `--gate-clearances`), `--no-audit`, consumer-repo Task indirection, Task aliases (`task deft:<verb>`), and compound, redirected, quoted, aliased, wrapped, or otherwise ambiguous forms are not auto-rewritten. A supported-host hook rejects a recognized lifecycle form outside the narrow rewrite surface until it carries the explicit matching `--session-id`; an execution-root mismatch on an owner-requiring command always denies. With a matching ID on other excluded forms, the original command remains under normal host permission handling. Manual/integrated-terminal callers must likewise pass one matching `--session-id` (or `DEFT_SESSION_ID`) through the complete lifecycle.
|
|
417
|
+
- ! **Transition, steal, and release (#3611):** an existing live UUID lease cannot silently become a host-namespaced owner. Prefer one aligned transition: `session:start --steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` (an exact host-mediated command receives the final option automatically). Bare `occupancy:steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` changes only the lease; direct writes remain denied unless ritual state already names the new owner. If they differ, align the same writer ID with `session:start --rearm --session-id=<same-session-id>` when re-arm is eligible, or cold `session:start --session-id=<same-session-id>` otherwise. The occupant drops a live lease with `occupancy:release` or `session:end`; a non-owner cannot clear it. Confirmed steal remains required for a live occupant and prints existing `claimed_at` / `heartbeat_at` / `last_write_at` when available.
|
|
418
|
+
- ! **Lease refresh (#3599):** a gated write by the owner re-stamps `heartbeat_at`, so a session that keeps working keeps its lease, up to the absolute age cap below. The re-stamp is floored at a quarter of the TTL to avoid rewriting the lease per event, and it records `last_write_at` separately — `heartbeat_at` moves on any lease touch, `last_write_at` only on a gated product write, which is what tells a would-be stealer that the occupant is mid-work. When the owner's own lease is inside the staleness window (three quarters of the TTL) and the automatic re-stamp could not run, the write gate says so instead of letting the lease lapse silently; a write that did re-stamp stays quiet, because the warning would name a state that write just cleared. A re-stamp blocked by the lock leaves the lease untouched and the gate re-decides against the file on disk: contention alone keeps the rightful owner writing, while a takeover that completed during the wait denies. Long quiet stretches produce no gated write to ride on, so the owner refreshes explicitly with `deft occupancy:heartbeat` (Task: `task occupancy:heartbeat`). Refresh extends a lease the caller already holds — it never claims a free worktree, never takes a foreign lease, and never mints an owner; an unheld lease exits non-zero. Under a supported host the exact command receives the matching `--session-id` automatically; manual and Grok callers pass `--session-id <id>` or `DEFT_SESSION_ID`.
|
|
419
|
+
- ! **Absolute lease age cap (#3599):** a lease is also dead 12 hours after `claimed_at`, whatever its heartbeat says, and no refresh path may extend that — `claimed_at` survives every re-stamp. Occupancy admits whoever presents the occupant's session id, so refresh alone would let any process holding that string keep a worktree forever and disable the crash-recovery reclaim the TTL exists for. The two dead states call for different responses and read differently: a stale heartbeat means refresh sooner; past the cap the tree is no longer held, so the capped holder's own gated writes are **refused** until it re-claims with `session:start --session-id=<your-session-id>` — allowing them would let the very bearer the cap bounds keep mutating a worktree a peer may claim at any moment. A lease that is both stale and capped reads as capped, because refreshing cannot revive it and the stale wording would send the holder somewhere that does not help. A different session is unaffected: taking over an aged-out tree is the reclaim the cap exists to enable. Twelve hours is sized by the stalled owner, not the busy one: refresh keys on writes, so an agent that finishes overnight and waits for its operator stops refreshing while staying legitimate, and 12 hours spans a 23:00 dispatch to a 09:00 handoff while still bounding reclaim inside a day. Reaching the cap costs one re-claim, never the work. A time cap cannot tell a stalled-but-live owner from a dead one — the only liveness signal on this path is a write; if that bites, the answer is a write-free liveness signal (explicit parked state, or refresh on non-write activity), not a larger number.
|
|
420
|
+
- ! **Lease membership (#3755):** the occupant admits a dispatched child with `deft occupancy:grant --session-id=<owner> --child-session-id=<child> --role <worker-role> [--ttl-minutes N] [--worktree <path>] [--host <id>] [--address <name>] [--join-protocol none|heartbeat-file|parent-message]` (Task: `task occupancy:grant`), and withdraws it with the same command plus `--revoke`. The grant records owner id, child id, worktree, role and expiry, so a write resolves to a session rather than to whoever held the string. Admission splits: **writes** are admitted for the owner **or** a valid unexpired member; **release, steal, heartbeat and cohort close-out stay owner-only**, so a grant never escalates into the lease itself. An expired grant is refused on read, expiry is clamped to the lease's own absolute age cap, a lease carries at most 32 live grants, and a steal or a fresh claim starts with none — grants belong to the lease that issued them. A member's write re-stamps `heartbeat_at` and `last_write_at` on the same quarter-TTL floor as the owner's: a tree a granted child is actively writing is in use, and letting it lapse would hand the worktree to a peer mid-edit. `claimed_at` is untouched, so the absolute age cap and the grant's own expiry still bound it. The verb is outside the narrow rewrite surface above, so pass `--session-id` (or `DEFT_SESSION_ID`) explicitly. All of it is cooperative, like the rest of this lease: it names who may write, it does not authenticate them. Ritual state stays single-owner, so a member writes under the occupant's ceremony: the composite hook write gate measures the verified ritual owner against the occupant that issued the grant rather than against the writer, and a revoked or expired member is refused there as well.
|
|
421
|
+
- ! **Lifetime and enforcement boundaries:** expired heartbeat (20 min) is free for claim-over-expired, and the release path may clear residue; no `reap` verb. No host-to-lease map or credential file is persisted; supported hooks re-derive their owner from each payload. Gated writes renew the heartbeat (#3599), so the 20-minute window runs from the owner's last write rather than from its claim, bounded by the absolute age cap above. Missing or drifted hooks remain #3742, and spawn tools remain outside occupancy enforcement. Join negotiation is owner-issued membership (`occupancy:grant`, #3755); the parked child-initiated `occupancy:request` queue stays unimplemented and is no longer named in remediation. `swarm:launch` carries the same explicit owner and persists `occupancy_session_id` in a cohort-keyed record; close-out uses that cohort entry as authority, requires any ambient owner to corroborate it, and never substitutes the current live lease owner. Swarm close-out stays `releaseSwarmOccupancy` on complete-cohort.
|
|
399
422
|
- ~ Operators MAY still explicitly request full `deft session:start`, `deft triage:welcome`, sync, or doctor in read-only sessions.
|
|
400
423
|
|
|
401
424
|
### Assist / research posture (#1802)
|
|
@@ -451,7 +474,7 @@ Agents use this signal to prefer portable syntax and quote zsh-sensitive data su
|
|
|
451
474
|
- Shallow probe (default hot path): PATH ladder `ghx` > `gh`, injected-token env presence, short `gh auth status`.
|
|
452
475
|
- Deep probe when `--with-network` / `DEFT_SESSION_START_NETWORK=1`: full `github-auth-modes` validation (API + optional repo).
|
|
453
476
|
- Session-start never hard-blocks on SCM absence (framework-local gates still run). When not ready it lists skipped SCM-dependent gates (`triage:queue`, `issue:ingest`, `pr:*`, `reconcile:issues`, `cache:fetch-all`, `scm:*`, ...).
|
|
454
|
-
- Explicit probe: `deft scm:status` (alias `scm:readiness`) -- exit `0` ready / `1` not ready / `2` config; flags `--json`, `--deep` / `--shallow`.
|
|
477
|
+
- Explicit probe: `deft scm:status` (alias `scm:readiness`) -- exit `0` ready / `1` not ready / `2` config; flags `--json`, `--deep` / `--shallow`, `--repo OWNER/REPO`, `--expected-login`. Deep validation derives the target repo and compares an expected user login when one is supplied (#3665). GitHub App installation identity is deferred to #3693.
|
|
455
478
|
- Credential bridging: host-gh (`gh auth login` in the execution env) or injected-token (`GH_TOKEN` / `GITHUB_TOKEN` / `GH_ENTERPRISE_TOKEN`). Never put token values in prompts or transcripts.
|
|
456
479
|
- Contract: `content/contracts/scm-readiness.md`; operator docs: `content/scm/github.md` § Mismatched/headless SCM readiness.
|
|
457
480
|
|
|
@@ -549,6 +572,7 @@ Directive does not guess your mix. Either you name the next units in order (**or
|
|
|
549
572
|
### Triage Tasks
|
|
550
573
|
|
|
551
574
|
- `task triage:bootstrap -- [--repo OWNER/NAME] [--limit N] [--state {open|closed|all}] [--batch-size N] [--delay-ms N]` -- seed the local triage cache and audit layer.
|
|
575
|
+
- `task triage:evaluate -- <N...> [--concurrency N] [--repo OWNER/NAME] [--json]` -- Stage A isolated issue-eval (#3648). Detached `origin/master` worktrees own validity; the parent owns WIP census (`xbrief/active`, `xbrief/pending`, `plan-sequence`); GitHub REST owns open PRs/issues. Verdicts land under `.deft-scratch/issue-eval/<sha12>/<invocation-id>/`. No GitHub writes. Fan-out default 4. Value may set `critique-recommend:`; it must not emit `design-critique: warranted | not warranted, because …`. Decisions stay existing `triage:*` verbs.
|
|
552
576
|
- `deft triage:queue --limit=10 [--author LOGIN|@me]` -- show ranked candidate work from cache-backed state. Optional `--author` / `--author-mine` filters to cache `author.login` (exact match; `@me` resolves via authenticated `gh`; comma allow-list; missing author disclosed as unknown) (#3129 / #1318 Layer 1). When the cache is empty, auto-populates from GitHub first (#2575) — do not conclude "nothing to do" from xBRIEF folders or live `gh issue list` alone (#2576).
|
|
553
577
|
- **Ordered-plan precedence (#2402):** when `.deft/plan-sequence.json` is active, bare "what's next?" / "next PR" / "proceed" bind to the current sequence entry via `deft plan-sequence:current` — they do **not** authorize `triage:queue` or adjacent backlog picks. Use `deft verify:plan-sequence --target-kind <kind> --target <id>` before opening a PR/branch/story/sub-agent. Sequence exhaustion fails closed until the operator names a new target or explicitly asks for queue/backlog selection ("what's the queue?", "build a cohort"). Set a sequence with `deft plan-sequence:set --file <json>`; advance with `deft plan-sequence:advance`; clear with `deft plan-sequence:clear`. Do not reuse triage queue `continuationNumbers` / `continuationOrder` for this state.
|
|
554
578
|
- `task triage:accept -- --issue <N> --repo OWNER/NAME [--auto-promote] [--force]` -- accept a candidate and ingest it as a proposed scope xBRIEF. Opt-in `--auto-promote` also promotes proposed→pending in the same action (#1136); `--force` is the WIP-cap override for that promote leg.
|
|
@@ -563,6 +587,7 @@ Directive does not guess your mix. Either you name the next units in order (**or
|
|
|
563
587
|
- `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.
|
|
564
588
|
- `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.
|
|
565
589
|
- **Triage write-back pairing (#1423 / ADR-005).** When the author stamps that a lean is mechanism-shaped, write **both**: body-text field `mechanism-shaped: true` (the artifact) **and** mirrored label `design-critique:mechanism-shaped` (what `plan.policy.judgmentGates` matches and lists show). Do not compute the classification. No stamp -> the design-critique gate never fires. Clearance is a separate line on the thread: `design-critique: warranted | not warranted, because ...` -- engine evaluates presence, shape, and authority, never the because-clause content. Advisory/observe; `verify:judgment-gates --enforce` stays opt-in unused; `scope:promote` does not gain `--enforce` in this story.
|
|
590
|
+
- **Design-critique catalog chip (#3642).** Parent attach of `triage-ready` / recut `mechanism-shaped` MUST use `task scm:issue:design-critique-chip -- --issue N --chip triage-ready|mechanism-shaped [--repo OWNER/NAME]` (or `deft scm issue design-critique-chip`). Closed catalog remaining-set replace, one write. ⊗ `gh api POST .../labels`. ⊗ additive `scm:issue:edit --add-label`.
|
|
566
591
|
- **Operator discovery for SCM label mirror (#3124).** Cold `session:start` (via `triage:welcome` default mode) surfaces a **throttled** tip until the first successful `--mirror` dry-run or operator ack — **not** on every re-arm. Tip teaches existence **and** get-the-most: dry-run `deft triage:classify -- --mirror` (open-only; `--include-closed` opt-in); `--apply` batches writes and **never** auto-accepts into `proposed/`; defaults only stamp **`triaged` on matches** (control stamp, not disposition); **board usability is greatly decreased without `actionLabels`** — recommend full five-chip map (`defer→triage:deferred`, `archive→triage:archived`, `accept→triage:lifecycle-linked`, `escalate→triage:needs-human` + always `triaged`); more matches → `plan.policy.triageAutoClassify` in PROJECT-DEFINITION; inspect via `deft policy:show --field=plan.policy.triageLabelMirror`; labels must exist on GitHub; applying `triaged` before action chips skips re-enrichment on re-run (use `--re-enrich`); point at consumer kit **#2611** (`content/docs/consumer-issue-label-kit.md`) — do not invent vocabulary. Dismiss without dry-run: `deft triage:classify -- --ack-discovery` (production entry for tip ack). Dry-run digests SHOULD footer-hint when `actionLabels` is empty or open `no_match` dominates. **Anti-swallow:** when the tip fires, agents MUST restate existence + get-the-most in the **user-visible** message (not absorb ceremony alone).
|
|
567
592
|
|
|
568
593
|
### Cache Tasks
|