@deftai/directive-content 0.107.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 +1 -1
- package/UPGRADING.md +22 -3
- package/commands.md +24 -2
- package/contracts/design-critique.md +117 -16
- 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/package.json +1 -1
- package/packs/skills/skills-pack-0.1.json +7 -7
- package/scm/github.md +31 -1
- package/skills/deft-directive-build/SKILL.md +1 -1
- package/skills/deft-directive-cost/SKILL.md +7 -11
- package/skills/deft-directive-design-critique/SKILL.md +8 -1
- package/skills/deft-directive-design-critique/references/motion-shape.md +19 -0
- package/skills/deft-directive-interview/SKILL.md +10 -10
- 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/tasks/engine.yml +2 -0
- package/tasks/occupancy.yml +24 -2
- package/tasks/prd.yml +4 -5
- package/tasks/session.yml +3 -3
- package/tasks/toolchain.yml +2 -2
- package/tasks/verify.yml +11 -1
- package/templates/agent-prompt-preamble.md +9 -2
- package/templates/agents-entry.md +6 -1
package/Taskfile.yml
CHANGED
|
@@ -738,7 +738,7 @@ tasks:
|
|
|
738
738
|
# tests/cli/test_release_e2e.py
|
|
739
739
|
# Refs #74, #233, #642, #635, #709, #710, #716, #718.
|
|
740
740
|
release:
|
|
741
|
-
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]"
|
|
742
742
|
deps: [ts:build]
|
|
743
743
|
dir: '{{.USER_WORKING_DIR}}'
|
|
744
744
|
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.
|
|
@@ -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,6 +306,7 @@ 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.
|
|
296
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`.
|
|
297
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.
|
|
@@ -324,7 +339,7 @@ task pr:finish-loop -- <N> # after a PR is open
|
|
|
324
339
|
|
|
325
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.
|
|
326
341
|
|
|
327
|
-
**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.
|
|
328
343
|
|
|
329
344
|
### Agent-host direct-write hooks (#2438, #2596)
|
|
330
345
|
|
|
@@ -396,7 +411,14 @@ After CLI/deposit upgrade, disk can show the new generation while a long-lived s
|
|
|
396
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.
|
|
397
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.
|
|
398
413
|
- ? Explicit read-only alignment only: `deft session:start -- --read-only` (no ritual-state write).
|
|
399
|
-
- ! **Worktree occupancy (#3433 / #3604):** 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.
|
|
400
422
|
- ~ Operators MAY still explicitly request full `deft session:start`, `deft triage:welcome`, sync, or doctor in read-only sessions.
|
|
401
423
|
|
|
402
424
|
### Assist / research posture (#1802)
|
|
@@ -13,6 +13,23 @@ This contract scaffolds the motion. Only the ADR-005 judgment gate and the conte
|
|
|
13
13
|
- ⊗ Pin this contract or `skills/deft-directive-design-critique` into `templates/agents-entry.md` or the AGENTS.md always-pin list. Discovery is on-demand via the Skills Index.
|
|
14
14
|
- ⊗ Auto-dispatch critics from this contract (#3578 / #1702). Operator (or parent after an operator verb) dispatches the next envelope from the brief template.
|
|
15
15
|
|
|
16
|
+
### The arc
|
|
17
|
+
|
|
18
|
+
**An arc is one recorded motion over one target revision**, from the Stop 1 write-back (or a voluntary dispatch) through accepted synthesis or the halt line. It holds one or more rounds, and therefore one or more ceilings. An arc is per-target, not per-issue: one issue carries several arcs over time, and one arc can span several issues.
|
|
19
|
+
|
|
20
|
+
The target is what the arc critiques. Under a refutation charter it is the recorded `refutation-target:`. Open critique names no refutation target, so the target there is the scope the write-back records. `### Target shape` describes the shapes that scope has taken.
|
|
21
|
+
|
|
22
|
+
Boundaries are read off the machinery in this document, not asserted here.
|
|
23
|
+
|
|
24
|
+
- A round takes a new ceiling. The converse does not hold: Amendment supersession under `### Audited residuals (panel bookkeeping)` records an amendment adopted as the ceiling **inside** round 2. Neither event opens an arc.
|
|
25
|
+
- Rounds accumulate inside one arc. The auto-stamp denominator is scoped to critic posts in this arc and keeps a Stop 4 retry's post, so a retry continues the arc it retries.
|
|
26
|
+
- Same-round siblings share one ceiling and one panel-deposit. A panel is one round, not N arcs.
|
|
27
|
+
- The arc stays open through the operator-gated loop until a verified synthesis is accepted, or until the halt line. Successor leans are moves inside that loop, so revising a lean before bind is not a boundary.
|
|
28
|
+
- A **recut** opens the next arc, and only after bind: it re-applies `design-critique:mechanism-shaped`, drops `design-critique:triage-ready`, and its new lean is not cleared by the older completed-arc record. That is a post-bind target revision.
|
|
29
|
+
|
|
30
|
+
- ! Read `arc` in this document as that unit.
|
|
31
|
+
- ⊗ Read a new ceiling, a new round, or a pre-bind lean revision as a new arc.
|
|
32
|
+
|
|
16
33
|
## Stop 1 — Gate
|
|
17
34
|
|
|
18
35
|
ADR-005 is vehicle-invariant. The gate never computes "is this triage mechanism-shaped."
|
|
@@ -65,6 +82,26 @@ Charter selection and spend permission are evaluated independently.
|
|
|
65
82
|
|
|
66
83
|
Default motion after a mechanism-shaped stamp: N=1 fresh open critique. If residual remains, one reiterating pass with a fresh critic that reads a disagreement map, then verified synthesis. Resume is optional sharpening ("does my prior finding still hold"), not the default reiterating agent. A permitted N≥3 does not change that default; the parent records the spend when it uses the permission.
|
|
67
84
|
|
|
85
|
+
### Target shape
|
|
86
|
+
|
|
87
|
+
Charter is what the critic is given. **Target shape is what is being critiqued.** The two are independent axes, and target shape selects neither the charter nor the spend, so it is not a row in either table above.
|
|
88
|
+
|
|
89
|
+
The default shape is one issue's premise, which every row above assumes. Two other shapes have been run.
|
|
90
|
+
|
|
91
|
+
| Target shape | The target | Exemplars |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| set-level | N issues as a remedy portfolio — whether they compose | #3781 / #3783 / #3790 (synthesis 5433848104, open critique); #3797 / #3798 / #3799 (synthesis 5434313019, refutation) |
|
|
94
|
+
| against-implementation | the design together with the diff that already implements it | #3610 with PR #3784 (synthesis 5434122672); #3796 with PR #3793 |
|
|
95
|
+
|
|
96
|
+
Those pairs are the whole record. Each shape has been run twice, which is not a settled pattern, and neither row grants a charter or a spend. The set-level pair is also the evidence for the axis being orthogonal: the same shape ran once under open critique and once under refutation.
|
|
97
|
+
|
|
98
|
+
**Set-level.** The arc anchors on one issue and takes its ceiling on that thread. The target is the portfolio claim, not any one issue's premise, and disposition is per-issue. The #3781 set arc closed one of the three as dominated and surfaced a fourth issue worth more than any of them.
|
|
99
|
+
|
|
100
|
+
**Against-implementation.** The implementation already exists, so critics judge the diff alongside the design. Tell them the PR's check status is unsettled, so a green review does not anchor them, and have author responses to earlier findings re-derived rather than accepted. The verdict has two parts — does the target survive, and should the PR merge — and they can differ. On #3610 the target survived 3/3 while the arc struck one acceptance criterion as an already-holding invariant and found two blocking defects a 5/5 review had missed.
|
|
101
|
+
|
|
102
|
+
- ? Record the target shape on the Stop 2 line when it is not a single issue. Two exemplars do not make it a required field.
|
|
103
|
+
- ⊗ Add a target shape as a charter row or a spend row. It is a third axis, and a row conflates two of them.
|
|
104
|
+
|
|
68
105
|
## Stop 3 — Critic envelope
|
|
69
106
|
|
|
70
107
|
### Parent-facing dispatch rules
|
|
@@ -111,7 +148,7 @@ The envelope is [`templates/design-critique-brief.md`](../templates/design-criti
|
|
|
111
148
|
Canonical panel-deposit:
|
|
112
149
|
|
|
113
150
|
```text
|
|
114
|
-
model:
|
|
151
|
+
model: <your-model-slug>
|
|
115
152
|
role: parent
|
|
116
153
|
|
|
117
154
|
panel-deposit
|
|
@@ -120,14 +157,18 @@ siblings: 3
|
|
|
120
157
|
input-ceiling: 5390001612
|
|
121
158
|
```
|
|
122
159
|
|
|
160
|
+
**Panel completeness is behavioural.** The deposit MUST above, and every sibling-completeness clause in this document, bind the parent. No code observes them. `evaluateCompletedArcRecord` reads a deposit only as evidence that an arc is in flight; it never counts critic posts and never compares a count against `siblings:`. `evaluateParentAudit` carries no round, sibling, or deposit field. Both halves hold: the obligation on the parent is real, and nothing machine-checks it. A parent that binds on a partial panel breaks this contract and no gate will stop it (#3850).
|
|
161
|
+
|
|
123
162
|
### Comment lead (model then role)
|
|
124
163
|
|
|
125
164
|
Comment-lead field. The first two lines of the triage write-back and of every critic, parent, and #3640 auto-posted comment name the LLM and the posting role. Keep the first line as `model: <slug>`. The second line is `role: triage|critic|parent`.
|
|
126
165
|
|
|
166
|
+
The `model:` line is a self-attestation. Nothing in this repository verifies which model produced a comment; do not treat it as provenance.
|
|
167
|
+
|
|
127
168
|
Canonical lead:
|
|
128
169
|
|
|
129
170
|
```text
|
|
130
|
-
model:
|
|
171
|
+
model: <your-model-slug>
|
|
131
172
|
role: critic
|
|
132
173
|
```
|
|
133
174
|
|
|
@@ -141,7 +182,7 @@ Closed role set (do not invent chips or extra roles in v1): `role: triage|critic
|
|
|
141
182
|
|
|
142
183
|
- ! First line of the triage write-back comment is `model: <slug>`.
|
|
143
184
|
- ! Second line of the triage write-back comment is `role: triage`.
|
|
144
|
-
- ! First line of every critic comment is `model: <slug>`
|
|
185
|
+
- ! First line of every critic comment is `model: <slug>` naming the model slug the critic self-attests.
|
|
145
186
|
- ! Second line of every critic comment is `role: critic`.
|
|
146
187
|
- ! Same first-two-lines on a Stop 4 retry critic (`role: critic`).
|
|
147
188
|
- ! Same first-two-lines on #3640 auto-posted table / synthesis-accepted comments (`role: parent`).
|
|
@@ -151,8 +192,8 @@ Closed role set (do not invent chips or extra roles in v1): `role: triage|critic
|
|
|
151
192
|
- ⊗ Put role in an issue label (`design-critique:critic`, author/role chips).
|
|
152
193
|
- ⊗ Put a GitHub login, author name, or role name in that lead line in place of the model.
|
|
153
194
|
- ⊗ Replace the model line with a role or GitHub login.
|
|
154
|
-
- ⊗
|
|
155
|
-
- ⊗
|
|
195
|
+
- ⊗ Omit the model line. Post `model: <slug>` on the comment; do not substitute a slug inferred from `verify:routing` or spawn metadata.
|
|
196
|
+
- ⊗ Omit the role line. Post `role: triage|critic|parent` on the comment; do not substitute a role inferred from `verify:routing` or spawn metadata.
|
|
156
197
|
|
|
157
198
|
## Stop 4 — Residual reiteration
|
|
158
199
|
|
|
@@ -172,11 +213,11 @@ Keep the arc in this contract until a verified synthesis is accepted.
|
|
|
172
213
|
- ! Operator (or parent after an operator verb) dispatches the next envelope.
|
|
173
214
|
- ! After each critic EXIT, parent posts a successor lean with proposed per-heading takes **before** printing `accept` / `retry differences` / `walk` / `walk all`. That posted lean is the first operator surface. Chat is not the record.
|
|
174
215
|
- ! Operator confirm or amend binds the proposed takes on that posted lean. Binding takes is not synthesis bind and does not stamp `design-critique:triage-ready`.
|
|
175
|
-
- ⊗ Bind synthesis or stamp `design-critique:triage-ready` while
|
|
216
|
+
- ⊗ Bind synthesis or stamp `design-critique:triage-ready` while same-round siblings remain unposted. The first lean after one critic EXIT is the take-offer, not the bind.
|
|
176
217
|
- ! Later successor leans follow accept-X or walk-end, or land before synthesis. This supersedes #3627's "successor lean only after accept-X" for the first lean after critic EXIT. Later leans may still follow accept-X / walk-end.
|
|
177
218
|
- ⊗ Print `accept` / `retry differences` / `walk` / `walk all` when no successor lean is posted for this critic EXIT. An empty-lean verb menu is a contract miss.
|
|
178
219
|
- ⊗ Auto-dispatch critics (#3578 / #1702).
|
|
179
|
-
- ⊗ Hand the arc to `triage:accept` / `scope:promote` until `design-critique:triage-ready
|
|
220
|
+
- ⊗ Hand the arc to `triage:accept` / `scope:promote` until the completed-arc record is present: `design-critique: synthesis accepted, because …` citing the accepted successor lean (and the verified-claims table when posted). Catalog chips (`design-critique:mechanism-shaped` / `design-critique:triage-ready`) are list-visible convenience, not clearance. A lone synthesis-accepted-shaped comment that does not cite an accepted lean does not unblock ingest.
|
|
180
221
|
- ⊗ Stamp `design-critique:triage-ready` at critic-post.
|
|
181
222
|
- ⊗ Add a `design-critique:critic-posted` chip or any author/role chip.
|
|
182
223
|
- ⊗ Critic writes issue labels.
|
|
@@ -187,7 +228,7 @@ Keep the arc in this contract until a verified synthesis is accepted.
|
|
|
187
228
|
After each critic EXIT, parent posts a successor `**Lean:**` comment with proposed per-heading takes. That posted lean is the first operator surface. Later successor leans follow accept-X or walk-end, or land before synthesis.
|
|
188
229
|
|
|
189
230
|
- ! After critic EXIT, post the successor lean before printing `accept` / `retry differences` / `walk` / `walk all`.
|
|
190
|
-
- ! Operator confirm or amend is what makes those takes bindable. An all-accept draft still goes through this offer. Confirming or amending an all-accept first lean binds those takes. It does not auto-stamp synthesis or `design-critique:triage-ready` while same-round siblings
|
|
231
|
+
- ! Operator confirm or amend is what makes those takes bindable. An all-accept draft still goes through this offer. Confirming or amending an all-accept first lean binds those takes. It does not auto-stamp synthesis or `design-critique:triage-ready` while same-round siblings remain unposted.
|
|
191
232
|
- ! Cite accepted critic ids/headings, the still-open residual, and the write-back or prior lean it supersedes.
|
|
192
233
|
- ! Carry a per-heading take on the successor lean: `accept-into-contract` | `disagree` | `defer`. Defer is not accepted.
|
|
193
234
|
- ! The successor lean is the disposition map. Do not post a third map type.
|
|
@@ -219,7 +260,7 @@ audit:<id> sha=<git-sha> pointer=<path:start-end|comment:<id>> reading=measured|
|
|
|
219
260
|
- ⊗ Mixed-basis laundering: one independently reproduced premise does not clear an unaudited load-bearing one.
|
|
220
261
|
- ! An unresolved marker is residual and blocks verified-synthesis bind.
|
|
221
262
|
- ⊗ Discharge a marker by promising a later pass.
|
|
222
|
-
- ! Auto-bind requires an all-accept disposition map AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings
|
|
263
|
+
- ! Auto-bind requires an all-accept disposition map AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings remain. This conjunct applies at Operator verbs auto-stamp and at Bind after accepted synthesis path 1.
|
|
223
264
|
- ! The brief envelope names unresolved marker ids as `audit-targets` (ids only, or `none`). It does not carry parent rationale.
|
|
224
265
|
- ! `evaluateParentAudit` fails closed on a missing token, a silently cleared marker, a parent self-clear, or an envelope that omits a named audit target.
|
|
225
266
|
|
|
@@ -241,9 +282,9 @@ Contract stops stay internal. Parent prints these phrases when they apply. They
|
|
|
241
282
|
- ! Do not print **retry differences** until residual headings are named on that map.
|
|
242
283
|
- ! Do not skip the first-lean offer because the draft is all-accept.
|
|
243
284
|
- ! Non-empty disagree set: print **walk** / **walk all** / **retry differences** / **accept**. Walk is an option, not the only path. Do not auto-start the walk.
|
|
244
|
-
- ! When the successor lean's per-heading map is total over a **non-empty** in-envelope classified-finding set, every heading is `accept-into-contract` (no `disagree`, no `defer`), AND zero unresolved audit markers, AND the operator has confirmed or amended that map, AND no unposted same-round siblings
|
|
285
|
+
- ! When the successor lean's per-heading map is total over a **non-empty** in-envelope classified-finding set, every heading is `accept-into-contract` (no `disagree`, no `defer`), AND zero unresolved audit markers, AND the operator has confirmed or amended that map, AND no unposted same-round siblings remain: parent auto-posts the verified-claims table as its own comment, then auto-posts `design-critique: synthesis accepted, because agents agreed (empty disagreement set)` and remaining-set-replaces the chip via `task scm:issue:design-critique-chip -- --issue N --chip triage-ready`. If that write misses, continue; do not halt. Do not print **accept synthesis**, **post the verified-claims table**, **walk**, or **walk all**.
|
|
245
286
|
- ⊗ Auto-stamp a parent-drafted all-accept map that the operator has not confirmed or amended.
|
|
246
|
-
- ⊗ Auto-stamp while
|
|
287
|
+
- ⊗ Auto-stamp while same-round siblings remain unposted.
|
|
247
288
|
- ⊗ Auto-stamp when any audit marker is unresolved.
|
|
248
289
|
- ! The auto-stamp denominator is the union of (a) classified headings from critic comments posted in this arc and (b) still-open residual headings on the latest successor lean. Classified headings in (a) are blocking and sharpening; footnotes stay in the walk-all census and are not in (a). Each critic's own post is in-envelope for the pass that dispatched it, including a Stop 4 retry that posts after the disagreement-map input ceiling. The input id ceiling bounds what the critic may read; it does not exclude that critic's own post from the denominator. Headings already `accept-into-contract` remain in the accepted set. Still-open residual headings persist in the denominator until they receive an explicit take on a successor lean. A retry may add headings. A retry that omits, renames, splits, or merges a still-open heading does not drop the prior heading unless the successor lean cites that prior heading and records the take. Uncited still-open headings remain `disagree` (walkable) and the map is not total. A successor-lean map is total only when every heading in that union has a take. Do not auto-stamp on a partial map.
|
|
249
290
|
- ! Parse classified headings only.
|
|
@@ -255,7 +296,7 @@ Contract stops stay internal. Parent prints these phrases when they apply. They
|
|
|
255
296
|
- ⊗ Mix walk and retry on the same finding in one turn.
|
|
256
297
|
- ⊗ Auto-post the verified-claims table on a non-empty disagree set.
|
|
257
298
|
|
|
258
|
-
Walk order for **walk all**: classified findings in order (blocking first, then sharpening, then footnotes — or the critic's numbering). For **walk**: only headings whose successor-lean take is `disagree`. For each: restated critic claim, parent take if it differs, then wait. Each decision is a thread comment (`Accept X` / skip / amend), citing critic comment id and finding heading. Chat is not the record. When the walk ends, parent offers to post a successor lean. The walk is not synthesis. When that successor lean is later total and all `accept-into-contract` over a non-empty classified-finding set AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings
|
|
299
|
+
Walk order for **walk all**: classified findings in order (blocking first, then sharpening, then footnotes — or the critic's numbering). For **walk**: only headings whose successor-lean take is `disagree`. For each: restated critic claim, parent take if it differs, then wait. Each decision is a thread comment (`Accept X` / skip / amend), citing critic comment id and finding heading. Chat is not the record. When the walk ends, parent offers to post a successor lean. The walk is not synthesis. When that successor lean is later total and all `accept-into-contract` over a non-empty classified-finding set AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings remain, the auto-table + auto-stamp path runs with no extra verb.
|
|
259
300
|
|
|
260
301
|
## Dual stop
|
|
261
302
|
|
|
@@ -315,18 +356,26 @@ Two bind paths authorize:
|
|
|
315
356
|
design-critique: synthesis accepted, because …
|
|
316
357
|
```
|
|
317
358
|
|
|
318
|
-
1. #3640 auto-stamp: when the successor lean map is total over the auto-stamp denominator (critic posts in this arc, including Stop 4 retry output, plus still-open residual headings) and that set is non-empty and every heading is `accept-into-contract` AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings
|
|
319
|
-
2. Explicit operator **accept synthesis** (or a listed short form). Parent may post that line and cite the verb. Then apply `design-critique:triage-ready` as the exclusive catalog chip via remaining-set write.
|
|
359
|
+
1. #3640 auto-stamp: when the successor lean map is total over the auto-stamp denominator (critic posts in this arc, including Stop 4 retry output, plus still-open residual headings) and that set is non-empty and every heading is `accept-into-contract` AND zero unresolved audit markers AND the operator has confirmed or amended that map AND no unposted same-round siblings remain, parent posts `design-critique: synthesis accepted, because agents agreed (empty disagreement set)` and remaining-set-replaces the chip to `design-critique:triage-ready` via `task scm:issue:design-critique-chip -- --issue N --chip triage-ready`. If that write misses, continue; do not halt. Do not print **accept synthesis**. Do not auto-stamp on a partial map, an unconfirmed parent draft, or when any audit marker is unresolved, or while same-round siblings remain unposted.
|
|
360
|
+
2. Explicit operator **accept synthesis** (or a listed short form), subject to the two non-empty refusals below. Parent may post that line and cite the verb. Then apply `design-critique:triage-ready` as the exclusive catalog chip via remaining-set write. If that write misses, continue; do not halt.
|
|
320
361
|
|
|
321
362
|
Closed catalog (last chip wins): `design-critique:mechanism-shaped` (in-flight, gate match) and `design-critique:triage-ready` (bound). No halt chip.
|
|
322
363
|
|
|
364
|
+
- ⊗ Bind path 2 when the critic posts zero classified headings (stub / blank). The same refusal path 1 carries at Operator verbs. Stop and inform. Do not stamp.
|
|
365
|
+
- ⊗ Bind path 2 on a footnote-only census. A footnote-only post is a valid census and is not a stub, but denominator set (a) is empty, so it carries no bind at either path.
|
|
323
366
|
- ! Exclusive replace is one merged remaining-set write: GET current labels, drop the other catalog names (`design-critique:mechanism-shaped` and `design-critique:triage-ready`), PUT/PATCH that list with the new chip. Other facets stay. Parent write path: `task scm:issue:design-critique-chip -- --issue N --chip triage-ready|mechanism-shaped [--repo OWNER/NAME]` (`deft scm issue design-critique-chip` dual-invoke). The verb GET-drops via `applyDesignCritiqueCatalogChip` / `designCritiqueChipApplyDelta` and one `ScmLabelClient.apply`. Inventory: `LabelClient.apply` / `mergeIssueLabels`.
|
|
324
367
|
- ⊗ `gh api POST .../labels` or additive `scm:issue:edit --add-label` for this facet.
|
|
325
368
|
- ⊗ Intercept mixed `scm issue edit` adds/removes for this facet.
|
|
326
369
|
- ⊗ General-purpose labels CLI.
|
|
327
|
-
- ! After
|
|
370
|
+
- ! After the completed-arc record is present, `triage:accept` / `scope:promote` / `issue:ingest` / build may proceed. Any identity may run those verbs. Same-session parent continuation is not required. GitHub Triage on the implementer is not required. They read the accepted verified synthesis (latest successor lean plus the verified-claims table).
|
|
371
|
+
- ! Ingest clearance cites the latest successor lean. An older completed-arc record does not clear a later recut lean. A panel-deposit is in-flight even when the catalog chip missed and no critic has posted.
|
|
372
|
+
- ! The lexical form of that citation, and the requirement that the occurrence be affirmative, are published in `## Citation grammar`. Ingest reads that grammar, not prose intent.
|
|
328
373
|
- ! Keep `plan.policy.judgmentGates` matching only `design-critique:mechanism-shaped`. After `triage-ready` replaces it, the issue leaves the gate match.
|
|
329
374
|
- ! Chip is list-visible state, not consent. Do not drop `mechanism-shaped` without the synthesis-accepted line (or the #3640 empty-disagreement path).
|
|
375
|
+
- ⊗ Treat `design-critique:triage-ready` as ingest clearance.
|
|
376
|
+
- ! Chip apply miss is non-blocking convenience. Do not invent a 403 HTTP parser. Any apply miss is the same miss. Do not use the halt line. Do not block ingest. Optional later remaining-set by a write-capable identity is hygiene.
|
|
377
|
+
- ! Leftover `design-critique:mechanism-shaped` after a chip apply miss does not block ingest. `judgmentGates` match is advisory/observe.
|
|
378
|
+
- ⊗ Use the halt line for a chip apply miss.
|
|
330
379
|
- ! Write-back `mechanism-shaped: true` is history after replace. Current-state authority is the last catalog chip.
|
|
331
380
|
- ! Recut (new lean) applies `design-critique:mechanism-shaped` with the same remaining-set write and drops `triage-ready`.
|
|
332
381
|
- ~ A live `design-critique:*` count!=1 check is SHOULD, not a new `judgmentGates` match.
|
|
@@ -336,6 +385,58 @@ Closed catalog (last chip wins): `design-critique:mechanism-shaped` (in-flight,
|
|
|
336
385
|
- ⊗ PUT a naive full wipe of every label.
|
|
337
386
|
- ⊗ Classify-mirror this facet.
|
|
338
387
|
|
|
388
|
+
## Citation grammar
|
|
389
|
+
|
|
390
|
+
Closed set (#3831). The completed-arc record clears ingest only when a citation matches an accepted form **and** the occurrence is affirmative. `evaluateCompletedArcRecord` reads both through one parser, `scanCitations` (`packages/core/src/design-critique/citation-grammar.ts`). Nothing else parses citations.
|
|
391
|
+
|
|
392
|
+
Citation keywords are `successor lean`, `lean`, `verified-claims table`, and `comment`. The id follows the keyword immediately: a colon and horizontal whitespace are the only things allowed between them.
|
|
393
|
+
|
|
394
|
+
Accepted forms, and nothing else:
|
|
395
|
+
|
|
396
|
+
1. bare decimal — `successor lean 12345678`
|
|
397
|
+
2. colon, following space optional — `successor lean: 12345678`, `successor lean:12345678`
|
|
398
|
+
3. balanced single-backtick decimal — `` successor lean `12345678` ``
|
|
399
|
+
4. emphasised keyword, `*` or `**`, with either id form — `**successor lean:** 12345678`
|
|
400
|
+
5. canonical comment permalink fragment — `#issuecomment-12345678`
|
|
401
|
+
6. canonical comment permalink path — `/issues/comments/12345678`
|
|
402
|
+
|
|
403
|
+
- ! Publish a form in this list before the parser accepts it. An unpublished spelling is not a citation.
|
|
404
|
+
- ⊗ Widen the accept set with `.*`, arbitrary decoration, or an open decorator class.
|
|
405
|
+
- ⊗ Accept a bold, italic, underscore, hash-prefixed, parenthesised, quoted, HTML-tagged, or display-text-link id. Those sit outside the closed set, and the refusal names the accepted forms.
|
|
406
|
+
- ⊗ Count every 8-or-more digit run in the body as a citation. Keyword adjacency and the two permalink targets are the whole anchor.
|
|
407
|
+
|
|
408
|
+
### Position predicate
|
|
409
|
+
|
|
410
|
+
Accepting an id is not accepting a citation. A match is classified by where it landed, and an occurrence that is not affirmative does not clear:
|
|
411
|
+
|
|
412
|
+
- inside a fenced code block, including a fence indented up to three spaces — prose that shows the form
|
|
413
|
+
- keyword inside an inline code span, including a span that opened on an earlier line — `` the parser wants `successor lean 12345678` shaped text ``
|
|
414
|
+
- in a blockquote, including an unmarked lazy-continuation line — `> they wrote: successor lean 12345678`
|
|
415
|
+
- struck through — `~~successor lean 12345678~~`
|
|
416
|
+
- explicitly negated within three words of the keyword — `do not use successor lean 12345678`
|
|
417
|
+
|
|
418
|
+
Those five are the whole refused set. An indented code block and an HTML comment are deliberately outside it: a four-space indent is also ordinary list-continuation content, so refusing it would block valid records more often than it would catch example text. Widening the refused set is a contract change, not an implementation detail.
|
|
419
|
+
|
|
420
|
+
- ! Classify the position of a match. Prior art is `classifyHit` (`packages/core/src/pr-closing-keywords/detect.ts`), which records where a hit landed.
|
|
421
|
+
- ! Read the enclosing block, not one physical line. A code span, a strikethrough run, and a blockquote all carry across a newline, and they end at the blank line.
|
|
422
|
+
- ! A quote block also ends at a fence delimiter, and a `>` line inside an open fence is example text rather than a marker. A quoted line in a fenced example does not refuse the citation that follows the closing fence.
|
|
423
|
+
- ! The negation form is explicit: `cannot`, `never`, `no longer`, an auxiliary plus `not`, or an auxiliary contraction ending in `n't`, closing within three plain words of the citation keyword and inside the same sentence.
|
|
424
|
+
- ! A negated verb of denial affirms the citation instead of refusing it, because the negation binds the verb and the citation sits in the complement clause. The verb set is closed: `deny`, `doubt`, `dispute`, `contest`, `question`. `we cannot deny that successor lean 12345678 binds` cites.
|
|
425
|
+
- ! That carve-out suspends a negation that already fired; it never refuses on its own, and it does not accept the citation outright. The complement clause carries the claim, so a negation anywhere in the rest of that sentence keeps the refusal: `we do not doubt that successor lean 12345678 does not bind` says the lean does not bind.
|
|
426
|
+
- ⊗ Read a trailing `that` as the complement-clause signal on its own. `that` is also a determiner, so `do not use that successor lean 12345678` and the cleft `the record is not that successor lean 12345678` stay refused, and a second negation before the keyword still binds.
|
|
427
|
+
- ⊗ Refuse on a negation word anywhere in the sentence prefix. `without a doubt, successor lean 12345678 is accepted` and `not only successor lean 12345678 but also the table` are affirmative citations, and refusing them blocks a valid record.
|
|
428
|
+
- ⊗ Strip the span instead. The established markdown scanners delete a code span with its contents, which destroys the digits.
|
|
429
|
+
|
|
430
|
+
### Which code-span convention governs
|
|
431
|
+
|
|
432
|
+
The intake cross-ref scanners (`packages/core/src/intake/markdown-scanners.ts`) delete code spans, so for them a backticked id is an example and never a reference. The citation scan takes the opposite polarity for the **id token only**: a balanced single-backtick id is an accepted citation, because arc comments are hand-written prose and the mandated lean heading is itself `**Lean:**`. A keyword inside a code span, and anything inside a fence, stays an example in both layers. The two conventions differ deliberately, and this paragraph is the record of which governs where.
|
|
433
|
+
|
|
434
|
+
### One parser, set membership, observed diagnostics
|
|
435
|
+
|
|
436
|
+
- ! Citation extraction and the verified-claims-table claim read the same parse. Two regexes answering one question let a decorated table id waive the table requirement and return `complete` with a null table id.
|
|
437
|
+
- ! Clearance is set membership: the record clears when the cited set contains the latest successor lean id. Position in the body does not select the lean, so citing the prior lean that `## Successor lean` requires cannot block.
|
|
438
|
+
- ! A block detail reports what was scanned, what was found, and the accepted forms. ⊗ Guess at a cause. A guessed detail sends the operator back to re-post the same body and reproduce the refusal.
|
|
439
|
+
|
|
339
440
|
## Failure and budget stop
|
|
340
441
|
|
|
341
442
|
- ! Failure/budget stop (#2442): Dual stop and Halt line. If a critic run fails or the arc exhausts its envelope, halt with the halt line. Do not thrash.
|
|
@@ -351,4 +452,4 @@ This motion ingests untrusted issue threads by design.
|
|
|
351
452
|
|
|
352
453
|
## Test surface
|
|
353
454
|
|
|
354
|
-
`packages/core/src/content-contracts/standards/design_critique_contract.test.ts` locks required pointer strings, the scaffolds framing, the comment-lead field as model then role from the closed set (not an issue label), the operator-gated loop (successor lean, operator verbs including walk / walk all, dual stop, halt line, exclusive remaining-set replace of the two catalog chips, #3640 auto-stamp on a non-empty all-accept map and no-stamp on stubs, first-lean recording obligation after critic EXIT), the parent-side substantiation token and independence rules, the Stop 1 exclusion (pre-critic premises outside the trigger) and `refutation-target:` field tokens rather than full body sentences, the composed auto-bind conjunct (all-accept map AND zero unresolved audit markers) at Operator verbs and Bind path 1, the variant-table evaluation rule (charter selection and spend permission evaluated independently), the critic-method heading and distinctive obligation tokens (exact class tokens, citations-are-claims, existing mechanisms, injection / swarm trigger nouns, failed-reviewer phrase, finding anatomy) rather than full body sentences, the brief-template forbidden-inputs list, and the thin router skill (existence, line cap, pointer resolution, no-normative-content). `evaluateParentAudit` locks the omission failure modes. This suite locks the SoT MUST and the thin skill pointer for the first-lean recording obligation, including the auto-stamp operator-confirm conjunct and the no-bind-while-unposted-same-round-siblings rule. It does not fail-close live parent turns. Runtime parent-turn detection only if `evaluateParentAudit` is extended; that extension is not required to ship the recording obligation.
|
|
455
|
+
`packages/core/src/content-contracts/standards/design_critique_contract.test.ts` locks required pointer strings, the scaffolds framing, the comment-lead field as model then role from the closed set (not an issue label), the operator-gated loop (successor lean, operator verbs including walk / walk all, dual stop, halt line, exclusive remaining-set replace of the two catalog chips, #3640 auto-stamp on a non-empty all-accept map and no-stamp on stubs, first-lean recording obligation after critic EXIT), the parent-side substantiation token and independence rules, the Stop 1 exclusion (pre-critic premises outside the trigger) and `refutation-target:` field tokens rather than full body sentences, the composed auto-bind conjunct (all-accept map AND zero unresolved audit markers) at Operator verbs and Bind path 1, the variant-table evaluation rule (charter selection and spend permission evaluated independently), the critic-method heading and distinctive obligation tokens (exact class tokens, citations-are-claims, existing mechanisms, injection / swarm trigger nouns, failed-reviewer phrase, finding anatomy) rather than full body sentences, the brief-template forbidden-inputs list, and the thin router skill (existence, line cap, pointer resolution, no-normative-content). `evaluateParentAudit` locks the omission failure modes. This suite locks the SoT MUST and the thin skill pointer for the first-lean recording obligation, including the auto-stamp operator-confirm conjunct and the no-bind-while-unposted-same-round-siblings rule. `evaluateCompletedArcRecord` locks ingest on the completed-arc record rather than a catalog chip. It does not fail-close live parent turns. `packages/core/src/design-critique/citation-grammar.test.ts` locks the `## Citation grammar` closed set, the refused positions, and the diagnostics surface; `packages/core/src/design-critique/completed-arc-record.test.ts` locks one parser for both questions, set membership against the latest lean, and the observation-echoing block details (#3831). Runtime parent-turn detection only if `evaluateParentAudit` is extended; that extension is not required to ship the recording obligation. Panel completeness is locked as contract text only. No predicate observes it on a live arc (#3850). `### The arc` and its derived boundaries, the `### Target shape` axis with its twice-run caveat, and the two bind-path-2 non-empty refusals are locked as contract text (#3797).
|
package/docs/delivery-attempt.md
CHANGED
|
@@ -102,7 +102,8 @@ task swarm:pre-dispatch -- --scope-id <story|issue|xbrief-id> --target-id <workt
|
|
|
102
102
|
Default unit key: `scopeId` + `targetId` + `workflowId=drive-to:merge-ready`.
|
|
103
103
|
Actions: `begin` (default), `complete` (`--status succeeded|failed|cancelled|blocked`), `cancel` (takeover step 1).
|
|
104
104
|
Monitors MUST run begin before any peer implement spawn; spawn only on exit 0.
|
|
105
|
-
Takeover: cancel prior attempt, then begin again — never concurrent dual active.
|
|
105
|
+
Takeover after REDISPATCH_OK: cancel prior attempt, then begin again — never concurrent dual active.
|
|
106
|
+
A killed worker stays `queued`/`running` until cancel; REDISPATCH_OK does not lift `DENY_DUPLICATE_ACTIVE` (#3730).
|
|
106
107
|
Implementation: `packages/core/src/swarm/pre-dispatch.ts` + `task swarm:pre-dispatch`.
|
|
107
108
|
|
|
108
109
|
## Skill routing
|
|
@@ -35,10 +35,15 @@ When a mutation `session:start` (cold or re-arm) loads payload surfaces into
|
|
|
35
35
|
runtime context, it binds the live generation:
|
|
36
36
|
|
|
37
37
|
```text
|
|
38
|
-
.deft/session-binds/<
|
|
38
|
+
.deft/session-binds/<sha256-slice>.json # multi-agent isolation (preferred)
|
|
39
39
|
.deft/session-bind.json # default / last-bind convenience
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
The per-session record is named by a 24-hex SHA-256 slice of the session id, so
|
|
43
|
+
a directory listing carries no fragment of the id (#3768). Records written
|
|
44
|
+
before that rename also carried a 32-character id prefix; they are read-accepted
|
|
45
|
+
until the next bind supersedes them.
|
|
46
|
+
|
|
42
47
|
Multi-agent hosts **must** pin session identity so one session cannot certify
|
|
43
48
|
another as current:
|
|
44
49
|
|
package/docs/getting-started.md
CHANGED
|
@@ -16,21 +16,18 @@ Before the mechanics below, it helps to see the whole loop. Directive is two con
|
|
|
16
16
|
|
|
17
17
|
## Prerequisites
|
|
18
18
|
|
|
19
|
-
- **Node 20+** and
|
|
19
|
+
- **Node 20+** and the package manager declared by the project (`npm` or `pnpm`) for live consumer gates and the npm distribution channel (see `.nvmrc` in the framework payload). npm is bundled with Node; a pnpm-pinned project enables pnpm through Corepack.
|
|
20
|
+
- **Git** and **GitHub CLI (`gh`)** are also checked by the consumer toolchain gate.
|
|
20
21
|
- **Go 1.22+** only if you use the bootstrap Go installer or build from source.
|
|
21
|
-
- **Python 3.11+**, **uv**,
|
|
22
|
+
- **Python 3.11+**, **uv**, and **task** for the framework-maintainer and session workflows that name them. The consumer toolchain probe itself does not check those tools. Run `directive toolchain:check --consumer --project-root .` after install.
|
|
22
23
|
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
## Installation
|
|
26
27
|
|
|
27
|
-
### npm (
|
|
28
|
+
### npm (canonical)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<!-- TODO(#1909): flip to npm-canonical and remove this "coming soon" notice when @deftai/directive is published -->
|
|
32
|
-
|
|
33
|
-
Once published, when Node is already available you will install Directive globally:
|
|
30
|
+
With Node already available, install Directive globally from npm:
|
|
34
31
|
|
|
35
32
|
```bash
|
|
36
33
|
npm i -g @deftai/directive
|
|
@@ -38,16 +35,18 @@ directive --version # primary command
|
|
|
38
35
|
deft --version # alias — same binary
|
|
39
36
|
```
|
|
40
37
|
|
|
41
|
-
One-shot without a global install
|
|
38
|
+
One-shot without a global install:
|
|
42
39
|
|
|
43
40
|
```bash
|
|
44
41
|
npx @deftai/directive doctor
|
|
45
42
|
npx @deftai/directive session:start
|
|
46
43
|
```
|
|
47
44
|
|
|
48
|
-
This npm path
|
|
45
|
+
This npm path has been the canonical distribution channel since v0.55.1. A pnpm-managed repository can install the same package with `pnpm add -g @deftai/directive` or project-locally with `pnpm add -D @deftai/directive`.
|
|
46
|
+
|
|
47
|
+
### Go installer (legacy bridge)
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
The Go installer is a frozen legacy bridge for older installs and source-oriented recovery. New consumer installs should use npm above; see [UPGRADING.md](../UPGRADING.md#one-time-migration-from-the-go-installer-legacy--npm) when migrating an existing Go-installer layout.
|
|
51
50
|
|
|
52
51
|
Download a platform installer from the [Directive release page](https://github.com/deftai/directive/releases) and run it from the project you want to adopt:
|
|
53
52
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Hook runtime unavailable: `deft-hook` is not on PATH
|
|
2
|
+
|
|
3
|
+
On a host that cannot execute `deft-hook`, the Cursor `preToolUse` registration is `failClosed: true`, so **every mutation is denied** — and because the binary never runs, no Directive code is left to say why. The visible symptom is an opaque non-zero exit, typically **127** (command not found).
|
|
4
|
+
|
|
5
|
+
Tracker: [#3785](https://github.com/deftai/directive/issues/3785). Related: [#3736](https://github.com/deftai/directive/issues/3736) (timeout), [#3571](https://github.com/deftai/directive/issues/3571) (unused-host recovery), [#2752](https://github.com/deftai/directive/issues/2752) (per-host toggles).
|
|
6
|
+
|
|
7
|
+
## Who hits this
|
|
8
|
+
|
|
9
|
+
The registration travels via git; the runtime does not. `.cursor/hooks.json` is trackable by design and the `.deft/core/` deposit is born-ignored, so any environment that gets the repo without a Node install of the CLI inherits the fence without the implementation:
|
|
10
|
+
|
|
11
|
+
- cloud agent VMs whose image has no Node and no global install
|
|
12
|
+
- CI runners and containers that never run `npm i -g @deftai/directive`
|
|
13
|
+
- a fresh clone on a new workstation
|
|
14
|
+
|
|
15
|
+
## Recovery is out of band
|
|
16
|
+
|
|
17
|
+
There is no in-session escape. Run this from a shell where Node and the CLI **are** available — your workstation, or the repo before you push it:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
deft policy:disable-host-hooks --host cursor --confirm
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That sets `plan.policy.hostHooks.cursor = false` in `xbrief/PROJECT-DEFINITION.xbrief.json`, and the next `deft update` strips the Cursor registration. Commit and push; the locked-out environment clears on its next pull or fresh clone.
|
|
24
|
+
|
|
25
|
+
⚠ Capability cost: disabling `hostHooks` for a host removes `deft-hook` pre-execution guardrails for anyone who later opens this repo in that host. The change is tracked and recorded to `meta/policy-changes.log`. Inspect with `deft policy:show --field=hostHooks`; reverse by setting the host back to `true` and running `deft update`.
|
|
26
|
+
|
|
27
|
+
## Preferred fix: make the runtime travel with the registration
|
|
28
|
+
|
|
29
|
+
If Cursor is a host you rely on, restore the runtime rather than removing the fence. Either:
|
|
30
|
+
|
|
31
|
+
- commit a `package.json` dependency on `@deftai/directive` — a published version or another spec a clone can fetch, since a location spec (`file:`, `link:`, `../directive`, an absolute path) resolves only on the machine that wrote it — and run `npm install` in the image or clone, which puts `deft-hook` in `node_modules/.bin`; or
|
|
32
|
+
- add `npm i -g @deftai/directive` to the image build.
|
|
33
|
+
|
|
34
|
+
`deft init` and `deft update` warn when a hook registration travels with the repository — already committed, or merely untracked and not ignored, so the next `git add` carries it — while neither anchor is present. The warning names the affected file and this document.
|
|
35
|
+
|
|
36
|
+
## ⊗ Do not hand-edit `failClosed` in the deposited hook file
|
|
37
|
+
|
|
38
|
+
Setting `failClosed: false` in `.cursor/hooks.json` clears the block exactly once. That file is a managed deposit: the next `deft update` rewrites the entries with `failClosed: true` and **silently re-arms the lockout**, usually long after anyone remembers editing it. Use the policy verb above, which is durable and tracked.
|
|
39
|
+
|
|
40
|
+
## Why the flags do not help
|
|
41
|
+
|
|
42
|
+
| Escape hatch | Why it does not reach this failure |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `.deft-directive-disable` | Evaluated *inside* `deft-hook`. When the binary is missing, nothing reads the flag. |
|
|
45
|
+
| `.no-deft-directive` | Consulted at `session.start` only; it never reaches `preToolUse`. |
|
|
46
|
+
| Host-side "skip hook" | A host concept. Directive has no such bypass. |
|
|
47
|
+
|
|
48
|
+
## Absent, crashed, and timed out are one class
|
|
49
|
+
|
|
50
|
+
The hook exit contract already decouples the exit code from the verdict: exit `0` means a decision was **rendered** — allow *or* deny — so every non-zero exit is by construction a *non-decision*. Absence, a crash, and a host timeout kill are the same state, and all three stay fail-closed.
|
|
51
|
+
|
|
52
|
+
They stay fail-closed because fail-open-on-absence is a bypass primitive, not a lenience: removing the binary is an ordinary shell call, so treating absence as allow would convert a self-inflicted denial into a bypass of the write fence, the intent ceiling, the session ritual, and the occupancy lease ([#3156](https://github.com/deftai/directive/issues/3156)). What these cases need is legibility and an out-of-band escape — this page — not a relaxed fence.
|
|
53
|
+
|
|
54
|
+
For a timeout specifically, retry the gated ritual first when machine load is the likely cause; the Cursor `tool.before` budget is sized for a gated ritual plus live readiness, so a repeated timeout is a real signal rather than noise.
|