version: '3'

vars:
  DEFT_ROOT: '{{joinPath .TASKFILE_DIR ".."}}'

tasks:

  stubs:
    desc: Scan source files for stub patterns (TODO, FIXME, HACK, return null, bare pass)
    deps:
      - task: :engine:_ts-build
    cmds:
      # Oracle/fallback (parity): scripts/verify-stubs.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-stubs'

  links:
    desc: Validate internal links in markdown files
    deps:
      - task: :engine:_ts-build
    cmds:
      # Oracle/fallback (parity): scripts/validate-links.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'validate-links'

  rule-ownership:
    desc: "Verify the Rule Ownership Map (conventions/rule-ownership.json) is in sync with its owner files (#635)"
    deps:
      - task: :engine:_ts-build
    cmds:
      # Oracle/fallback (parity): scripts/rule_ownership_lint.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'rule-ownership-lint --root "{{.DEFT_ROOT}}"'

  biome-config:
    desc: "Guard against #2190 (biome check . flipping recommended-preset diagnostic severity between warning and error non-deterministically): asserts biome.json declares an explicit non-error severity for noUnusedVariables and noNonNullAssertion. Three-state exit (0 clean / 1 missing/error severity / 2 config error)."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it reads THIS repo's own biome.json, so it
    # targets DEFT_ROOT (not USER_WORKING_DIR) -- a consumer deposit has no
    # TS engine / biome.json to guard. Mirrors verify:content-manifest.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-biome-config --project-root "{{.DEFT_ROOT}}"'

  telemetry-coverage:
    desc: "Dead-surface detector for run-summary event kinds (#3362). Every RUN_SUMMARY_EVENT_KINDS member needs a production caller and a field-shaped fixture. Default is warn-only; pass --enforce to fail closed. -- task verify:telemetry-coverage [-- --enforce]"
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: scans THIS repo's packages/core/src emitters.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-telemetry-coverage --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'

  contained-writes:
    desc: "Inventory raw product write sinks outside the contained-write allowlist (#2951 / #2980). Default CLI remains fail-open; task check wires --enforce fail-closed. -- task verify:contained-writes [-- --enforce]"
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: scans packages/core/src in THIS repo.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-contained-writes --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'

  content-manifest:
    desc: "Verify the Content Manifest (conventions/content-manifest.json) classifies every git-tracked top-level entry (#1821). Fails on an unclassified entry, a stale classified path, an invalid bucket, or a duplicate path. Wave-1 shippability audit for the engine/content split (#1669)."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it classifies THIS repo's top-level tree, so it
    # targets DEFT_ROOT (not USER_WORKING_DIR) -- a consumer install has no
    # content/ + engine/ top-level split to audit. Mirrors verify:rule-ownership.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-content-manifest --project-root "{{.DEFT_ROOT}}"'

  deposit-closure:
    desc: "C1 declared deposit closure against a staged pack root (#3900 / #3601). Resolves contracts/deposit-required-paths.json; does not infer paths from AGENTS.md. Three-state (0 clean / 1 missing / 2 config)."
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-deposit-closure --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'

  consumer-hard-stops:
    desc: "Enumerate open consumer hard-stops by privileged labels (adoption-blocker, blocks-release-tag) only (#3900 / #3713 / #3969). Does not read issue bodies. Fail-closed at release."
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-consumer-hard-stops --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'

  closing-keywords:
    desc: "Wire task pr:check-closing-keywords into the check graph (#737 / #3969). FP mode against the merge-base, or --pr when GITHUB_PR_NUMBER is set."
    deps:
      - task: :engine:_ts-build
    dir: '{{.USER_WORKING_DIR}}'
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-closing-keywords {{.CLI_ARGS}}'

  docs-impact:
    desc: "Declared-versus-touched documentation-impact check (#4099). --pr uses REST; --body-file for local. Fork PRs stay read-only."
    deps:
      - task: :engine:_ts-build
    dir: '{{.USER_WORKING_DIR}}'
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'docs-impact --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  license-sync:
    desc: "Drift guard for root LICENSE ↔ content/LICENSE.md and published package.json license fields (#2902). Three-state exit (0 clean / 1 drift / 2 config). Framework-source only."
    # Framework-source-only gate: reads THIS repo's LICENSE + package manifests.
    # Plain node script (no engine build required).
    cmds:
      - node "{{.DEFT_ROOT}}/scripts/verify-license-sync.mjs" --project-root "{{.DEFT_ROOT}}"

  skill-external-fetch-gate:
    desc: "Verify shipped skills do not pair external fetch/follow-through with execute/install without Security context mitigation (#1936 / #1532)."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: scans THIS repo's content/skills tree.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-skill-external-fetch-gate --project-root "{{.DEFT_ROOT}}"'

  semantic-single-source:
    desc: "C2 semantic single-source conformance (#3600 / #3899): shipped authoring MUST lines name exactly one xBRIEF write version, the version setup writes. Fails on a mutated staged pack. Three-state (0 clean / 1 drift / 2 config). -- task verify:semantic-single-source [-- --project-root <pack-or-source>]"
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: scans shipped authoring surfaces in THIS repo
    # (or a staged pack root via --project-root / --pack-root).
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-semantic-single-source --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'

  contract-drift:
    desc: "Drift gate for the public contract layer (#1799). Asserts packages/types/schemas/vbrief-core-0.6.schema.json matches content/vbrief/schemas/vbrief-core.schema.json and that @deftai/directive-types Status/version constants align with the schema. Three-state exit (0 clean / 1 drift / 2 config error)."
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-contract-drift --project-root "{{.DEFT_ROOT}}"'

  cursor-tier1:
    desc: "Framework-source content gate (#1877): assert Cursor (cursor-composer / cursor-cloud-agent, Task-tool primitive) is enumerated as a Tier-1 descriptor in the swarm Phase 3 capability matrix AND the review-cycle monitoring tier table. Fails when a doc edit drops the Cursor descriptor and silently re-opens the Tier-3 blocking-poll misclassification. Three-state exit (0 clean / 1 missing marker / 2 config error)."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it scans THIS repo's two skill docs, so it
    # targets DEFT_ROOT (not USER_WORKING_DIR) -- a consumer install renders
    # these skills under .deft/core and does not author them. Mirrors
    # verify:content-manifest / verify:rule-ownership.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-cursor-tier1 --project-root "{{.DEFT_ROOT}}"'

  openclaw-tier1:
    desc: "Framework-source content gate (#2875): assert OpenClaw (openclaw descriptor, sessions_spawn primitive) is enumerated as Tier 1 in the swarm Phase 3 capability matrix AND routing accepts openclaw as a dispatch_provider. Fails when a doc/code edit drops the OpenClaw descriptor and silently re-opens grok-build / generic-terminal misclassification. Three-state exit (0 clean / 1 missing marker / 2 config error)."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it scans THIS repo's swarm skill + routing
    # module, so it targets DEFT_ROOT (not USER_WORKING_DIR). Mirrors
    # verify:cursor-tier1 / verify:content-manifest.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-openclaw-tier1 --project-root "{{.DEFT_ROOT}}"'

  go-freeze:
    desc: "Tier-1 freeze gate for the legacy Go-installer bridge (#1912). Advisory while the Tier-0 SoT (lastGoInstaller) is null; once the operator pins it, fails when cmd/deft-install is bumped above the frozen tag. Three-state exit (0 ok / 1 violation / 2 config error). Emergency bypass: DEFT_ALLOW_GO_INSTALLER_BUMP=1."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it inspects THIS repo's cmd/deft-install
    # version constant + the Tier-0 SoT, so it targets DEFT_ROOT (not
    # USER_WORKING_DIR) -- a consumer install has no Go-installer source to
    # freeze. Mirrors verify:content-manifest.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-go-freeze --project-root "{{.DEFT_ROOT}}"'

  bridge-drift:
    desc: "Tier-1 cross-surface drift gate for the legacy Go-installer bridge (#1912). Asserts no surface marked with the bridge sentinel hardcodes a Go-installer version instead of reading the Tier-0 SoT (lastGoInstaller). Three-state exit (0 clean / 1 drift / 2 config error). Passes whether or not the UPGRADING/doctor surfaces exist yet."
    deps:
      - task: :engine:_ts-build
    # Framework-source-only gate: it scans THIS repo's bridge-version surfaces
    # against the Tier-0 SoT, so it targets DEFT_ROOT. Mirrors
    # verify:content-manifest / verify:scm-boundary.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-bridge-drift --project-root "{{.DEFT_ROOT}}"'

  branch:
    desc: "Detection-bound branch-protection gate (#747). Reads plan.policy.allowDirectCommitsToMaster from PROJECT-DEFINITION."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:branch --project-root "{{.USER_WORKING_DIR}}" --allow-missing-project-definition'

  gh-auth:
    # Worker-envelope GitHub auth validation (#1557). Thin task wrapper over the
    # existing `github-auth-modes` engine verb, added by #3483: the swarm skill
    # named `task verify:gh-auth` at MUST/⊗ level (references/core-phase-3.md
    # Step 3, references/core-ops.md) but only the engine verb ever shipped, so
    # the documented invocation exited 200 ("task does not exist").
    #
    # NOTE: NO `sources:` / `generates:` per conventions/task-caching.md --
    # this probes live process/network state through {{.CLI_ARGS}} flags and a
    # cached skip would return a stale verdict.
    desc: "Validate GitHub auth from the worker envelope (#1557): host-gh vs injected-token mode, gh auth status, and API reachability. Fails closed on missing_injected_token in cloud-headless. -- task verify:gh-auth [-- --json] [--repo OWNER/NAME] [--github-auth-mode host-gh|injected-token]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      # Bare CLI_ARGS per the verify:encoding convention -- go-task shell-escapes pass-through args.
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'github-auth-modes {{.CLI_ARGS}}'

  routing:
    desc: "Operator coding sub-agent model routing gate (#1739). Pre-dispatch (default): fails when a dispatched worker role has no decision in .deft/routing.local.json. Pass --advise for the non-blocking session-start disclosure; --roles a,b to widen the gated set; --provider to override the runtime."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      # Bare CLI_ARGS per the verify:encoding convention -- go-task shell-escapes pass-through args.
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'swarm-routing-verify --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  hooks-installed:
    desc: "Assert the deft git hooks are installed AND functional -- core.hooksPath set, hooks dir + pre-commit/pre-push present, and the gate scripts resolve in this layout (own-repo or vendored). Fails loud on the #1463 false-green (wired but non-functional). Run `task setup` / re-run the installer if this fails (#747, #1463)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # No sources:/generates: -- a cached cmds skip would mask a hooks dir /
    # gate-script that was deleted after the last run.
    cmds:
      # Oracle/fallback (parity): scripts/verify_hooks_installed.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:hooks-installed --project-root "{{.USER_WORKING_DIR}}"'

  encoding:
    desc: "Detect PS 5.1 non-ASCII round-trip corruption (#798). Scans tracked text files for U+FFFD, CP1252/CP437-as-UTF-8 mojibake, and unexpected BOM. Defaults to --all; pass --staged for the pre-commit invocation."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md`: NO `sources:` / `generates:` because
    # user-facing flags (--staged / --all / --allow-list <path>) MUST NOT be
    # silently swallowed by go-task's incremental-build cache. The same
    # rationale that gated `task release` (#74) and `task pr:check-protected-issues`
    # (#702) applies here -- the gate accepts recovery flags that the cache
    # layer would otherwise discard.
    cmds:
      # Oracle/fallback (parity): scripts/verify_encoding.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:encoding --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  forward-coverage:
    desc: "Forward-coverage gate (#1310 / #3514): fail-closed when a NEW source file is added without a corresponding test in the SAME diff; also report uncovered added/modified branches by intersecting coverage-final.json with the diff (warn-first; --enforce fail-closes). The 90% per-diff threshold is not the 75 global floor. Defaults to the HEAD-relative diff; pass --staged for pre-commit. Three-state exit (0 clean or warn / 1 missing existence or enforced diff findings / 2 config error)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md`: NO `sources:` / `generates:` because
    # user-facing flags (--staged / --head / --allow-list <path> / --quiet / --enforce) MUST
    # NOT be silently swallowed by go-task's incremental-build cache (same
    # rationale as verify:encoding above).
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:forward-coverage --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  test-boundary:
    desc: "Test/source boundary gate (#3145): reject recognized test artifacts under production roots and production references to test/fixture roots unless allowlisted or classified production-liveness. Policy from .deft/test-boundary.policy.json or plan.policy.testBoundary; defaults are warn-only (migration). Flags: --enforce / --warn / --policy <path> / --quiet. Three-state exit (0 clean or warn / 1 violation / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:test-boundary --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  scope-provenance:
    desc: "Approved-scope provenance gate (#3145): same-PR active xBRIEF file_scope expansion cannot self-authorize new paths. Compares against .deft/approved-scope/<plan-id>.json digests; requires renewed human approval for expansion. Missing digests warn by default; pass --enforce to fail closed. Flags: --base-ref / --enforce / --quiet. Three-state exit (0 clean or warn / 1 self-auth / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:scope-provenance --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  observable-scope:
    desc: "Fail-closed observable UI scope contract (#4495): minted allowedChanges versus parse5+typescript oracle on base-pinned surfaces. Unset policy plus UI file types is inferred-defaults-warn (exit 0 with findings). Flags: --origin-ref / --staged / --quiet. Three-state exit (0 skip/warn/pass / 1 unlisted delta or missing mint / 2 config). Does not accept --base-ref."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:observable-scope --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  intent-constraint:
    desc: "Fail-closed intent-constraint snapshot (#4541): throw/reject/abort sites and new numeric consts in changed production .ts/.js versus merge-base pin. Human mint for value/unit/rejectionScope. Tests and in-scope paths are not authority. Flags: --origin-ref / --staged / --quiet. Three-state exit (0 skip/pass / 1 unapproved fact or missing mint / 2 config). Does not accept --base-ref."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:intent-constraint --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  evaluator-surface:
    desc: "Declared evaluator-surface diff gate (#4386 / #3156): fail when Taskfile.yml, tasks/verify.yml, gate-lists, coverage config, or the detector itself changes without xbrief/evaluator-surface-disposition.json covering those paths. Disclosure only -- a commit-body issue/PR URL is not #3164 authorization. Prior color is unobserved. Flags: --base-ref / --staged / --path / --quiet. Three-state exit (0 skip or disclosed / 1 missing disposition / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:evaluator-surface --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  consumer-test-lane:
    desc: "Consumer declared-test-command lane (#4386): run plan.policy.testCommand or package.json scripts.test. Skip when undeclared -- do not invent go test ./... or a shipped-library default. Does not replace verify:ac (PRODUCT_FIRST_AC_GATE). Flags: --quiet. Three-state exit (0 skip or pass / 1 declared command failed / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:consumer-test-lane --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  consumer-check-contract:
    desc: "Consumer check-composition contract (#3145): fail when tasks/verify.yml or check aggregate omit required enforcement gates (verify:test-boundary, verify:scope-provenance, verify:consumer-check-contract, verify:evaluator-surface, verify:observable-scope, verify:intent-constraint). CI omissions warn by default. Flags: --framework-source / --enforce / --warn / --quiet. Three-state exit (0 clean or warn / 1 missing / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:consumer-check-contract --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  vbrief-conformance:
    desc: "Detect bare non-conformant vBRIEF keys (#1620). Scans tracked vbrief/**/*.vbrief.json and flags any document/plan/item key that is not 0.6 spec-core, x-directive/-namespaced, or x-vbrief/-namespaced. plan.policy + plan.completedNote carry a TEMPORARY allow-list pending vBRIEF #12. Defaults to --all; pass --staged for the pre-commit invocation."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md`: NO `sources:` / `generates:` because
    # user-facing flags (--staged / --all / --allow-list <path>) MUST NOT be
    # silently swallowed by go-task's incremental-build cache (same rationale
    # as verify:encoding above).
    cmds:
      # Oracle/fallback (parity): scripts/verify_vbrief_conformance.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'vbrief-validate conformance --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  destructive-gh-verbs:
    desc: "Detection-bound gate for destructive gh verbs (#1019). Runs preflight-gh --self-test so the fixture-vs-classifier contract fails CI on drift. Override via DEFT_ALLOW_DESTRUCTIVE_GH_VERBS=1 (per-shell emergency bypass)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # No sources:/generates: -- the self-test fixture table is the
    # invariant under test; we want the gate to re-run on every
    # `task check` invocation regardless of whether the classifier file
    # mtime moved. Caching here would silently mask a regression.
    cmds:
      # Oracle/fallback (parity): scripts/preflight_gh.py --self-test (#1854 s5).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'preflight-gh --self-test {{.CLI_ARGS}}'

  cache-fresh:
    desc: "Pre-`start_agent` cache-freshness gate (#1127). Refuses implementation dispatch when the triage cache is stale, missing, or the target issue's latest decision is not `accept`. Subscription-aware via plan.policy.triageScope[] (D12 / #1131). Flags: --for-issue N / --max-age-hours N / --allow-stale / --repo OWNER/NAME / --allow-missing-bootstrap (consumed by the framework's own `task check` so a fresh checkout passes -- consumers leave it OFF)."
    dir: '{{.USER_WORKING_DIR}}'
    # Runtime/session + pre-dispatch gate: no engine:_ts-build / pnpm build (#2181).
    # Parent `task check` still builds via its own engine:_ts-build dep first.
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (--for-issue / --max-age-hours / --allow-stale / --repo) that
    # go-task's incremental-build cache would silently swallow. The
    # `--allow-missing-bootstrap` flag below keeps the framework's own
    # `task check` green on a fresh checkout that has not yet run
    # `task triage:bootstrap`; consumer projects leave it off so a
    # missing cache fails their `task check` loudly.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'preflight-cache --project-root "{{.USER_WORKING_DIR}}" --allow-missing-bootstrap {{.CLI_ARGS}}'

  codebase-map-fresh:
    desc: "Drift gate for the generated .planning/codebase/MAP.md projection (#1595 PR4)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per conventions/task-caching.md: no sources/generates because this gate
    # forwards output/artifact overrides through CLI_ARGS.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'codebase-map-fresh --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'


  spec-prd-fresh:
    desc: "Fail-closed SPECIFICATION.md / PRD.md render-freshness gate (#4086). Re-renders to a buffer and fails on banner or projection diff. Banner canon and full projection freshness are separate assertions. Modeled on verify:codebase-map-fresh."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per conventions/task-caching.md: no sources/generates because this gate
    # forwards project-root through CLI_ARGS.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:spec-prd-fresh --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  rule-map-fresh:
    desc: "Fail-closed docs/RULE-MAP.md renderer freshness (#4095). Alias for docs:rule-map:check. Asserts byte-identical renderer output, filled grouping purposes, pack entries from named arrays, and Taskfile declaration counts. Not a consumer gate."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :docs:rule-map:check

  session-ritual:
    desc: "Fail-closed session ritual verifier (#1348). Flags: --tier quick|gated / --json. Set DEFT_SESSION_RITUAL_SKIP=1 for headless workers and CI."
    dir: '{{.USER_WORKING_DIR}}'
    # Runtime/session dispatch: no engine:_ts-build / pnpm build (#2181).
    # No sources/generates: this gate is time-, HEAD-, and worktree-sensitive.
    cmds:
      # Oracle/fallback (parity): scripts/verify_session_ritual.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-session-ritual --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  plan-sequence:
    desc: "Fail-closed ordered-plan target check (#2402). Flags: --target-kind <kind> --target <id> / --json. Skips (exit 0) when no active sequence."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-plan-sequence --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  tools:
    desc: "Detect required Deft host tools and print install or manual guidance (#1187). Flags: --install / --yes / --json"
    dir: '{{.USER_WORKING_DIR}}'
    # Runtime/session dispatch: no engine:_ts-build / pnpm build (#2181).
    cmds:
      # Oracle/fallback (parity): scripts/verify_tools.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:tools {{.CLI_ARGS}}'

  scm-boundary:
    desc: "Detection-bound gate against raw `gh` / `ghx` subprocess calls (#1145 / N5). TypeScript verbs route through packages/core/src/scm/call.ts. The scanner still globs retired scripts/triage_*.py paths (vacuous; owner packages/core/src/verify-source/scm-boundary.ts SCOPE_GLOBS). Three-state exit (0 clean / 1 violations / 2 config error). Document an exception via `--allow-list <path>`."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (`--allow-list <path>` / `--quiet`) that go-task's incremental-build
    # cache would silently swallow.
    cmds:
      # Oracle/fallback (parity): scripts/verify_scm_boundary.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-scm-boundary --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  xbrief-drift:
    desc: "Data-plane drift gate for the #2109 vbrief->xbrief rename. FAILS when a NEW legacy-layout token is reintroduced: a tracked `*.vbrief.json` artifact, a tracked file under a top-level `vbrief/` lifecycle dir, or a bare `x-vbrief/` reference type inside a canonical `xbrief/**/*.xbrief.json` corpus artifact. Sanctioned back-compat shims (the Part 1 layout-resolver fallback, the EXTENSION_PREFIXES legacy entry, the #2110 migrate path, the #1650 policy fallback) are TS source -- outside the scanned data plane -- and the legacy fixture trees (tests/, content/vbrief/, docs/, history/, xbrief/migration/) are allowlisted. Three-state exit (0 clean / 1 drift / 2 config error). Document an exception via `--allow-list <path>`."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (`--allow-list <path>` / `--staged` / `--quiet`) that go-task's
    # incremental-build cache would silently swallow.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:xbrief-drift --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  story-ready:
    desc: "Deterministic story-start Gate 0 (#1378 Story C). Inspects working-tree cleanliness, the target vBRIEF lifecycle (active/ + plan.status==running), and the dispatch envelope's `## Allocation context` consent token (Story A schema). Three-state exit (0 ready / 1 not ready / 2 config error). -- task verify:story-ready -- --vbrief-path <active-story-path> [--allocation-context <dispatch-envelope-file>] [--allow-dirty] [--json]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (--vbrief-path / --allocation-context / --allow-dirty / --json) that
    # go-task's incremental-build cache would silently swallow. Intentionally
    # NOT wired into the `task check` aggregate: Gate 0 is a pre-`start_agent`
    # dispatch gate that requires a concrete --vbrief-path, which a generic
    # `task check` run has no way to supply.
    cmds:
      # Oracle/fallback (parity): scripts/preflight_story_start.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:story-ready --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  investigation:
    desc: "Validate a forensic investigation ledger (#1621). Promotes the forensic-research validator into a deterministic gate: the close check before Wave 5 / any causal chat. Three-state exit (0 close-ready / 1 hard failures / 2 config error). Intentionally NOT in the `task check` aggregate -- it requires a concrete --ledger path. -- task verify:investigation -- --ledger .tmp/investigations/<id>/investigation.vbrief.json [--json]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (--ledger / --json) that go-task's incremental-build cache would
    # silently swallow.
    cmds:
      # Oracle/fallback (parity): scripts/verify_investigation.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:investigation --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  architecture-sor:
    desc: "Diff-time system-of-record architecture gate. Scans stateful persistence signals and requires a matching architecture.systemOfRecord record. Example: task verify:architecture-sor -- --base-ref origin/main [--story-path <path>]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per conventions/task-caching.md: no sources/generates because this gate
    # forwards --base-ref, --story-path, and --json through CLI_ARGS.
    cmds:
      # Oracle/fallback (parity): scripts/preflight_architecture_sor.py (#1854 s5).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'architecture-preflight-sor --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  capacity:
    desc: "Three-state ADVISORY capacity gate (#1419 Slice 4). Reports trailing-window target-vs-actual bucket mix from plan.policy.capacityAllocation; exits 0 in the default advise posture (and on insufficient sample / unconfigured policy), 1 only under an explicit enforce posture with a sampled deficit, 2 on config error. DELIBERATELY NOT in the `task check` aggregate -- capacity must never fail-closed on the framework tree."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (--project-root / --quiet) that go-task's incremental-build cache would
    # silently swallow. Advisory-only: this target is intentionally absent
    # from the `task check` deps so a capacity deficit cannot wedge the
    # framework's own self-check (advise-mode discipline, #1419).
    cmds:
      # Oracle/fallback (parity): scripts/verify_capacity.py (#1828 Wave 8 / #1854 s3).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-capacity --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  judgment-gates:
    desc: "Three-state ADVISORY judgment-gate engine (#1419 Slice 3). Evaluates a candidate change (diff paths / labels / body) against plan.policy.judgmentGates + four default-on universal safety gates (secrets / infra / AGENTS.md+skills / installer). Advisory by default (always exits 0); opt-in --enforce fails closed (exit 1) when a mechanical block-tier gate fires without a recorded clearance; exit 2 on config error. -- task verify:judgment-gates [-- --base-ref <ref>] [--path P] [--label L] [--enforce] [--json]. DELIBERATELY NOT in the `task check` aggregate -- judgment gates must never fail-closed on the framework tree (advise -> observe -> block rollout, #1419)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}}
    # (--base-ref / --path / --label / --body / --state / --enforce / --json /
    # --quiet) that go-task's incremental-build cache would silently swallow.
    # Advisory-only: this target is intentionally absent from the `task check`
    # deps so a gate finding cannot wedge the framework's own self-check. The
    # directive-side invocation never passes --enforce, so the gate stays
    # advisory on the framework's own tree (advise-mode discipline, #1419).
    cmds:
      # Oracle/fallback (parity): scripts/verify_judgment_gates.py (#1828 Wave 8).
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:judgment-gates --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  wip-cap:
    desc: "Pre-merge re-validation that pending/+active/ count is within plan.policy.wipCap (#1124 / D4 of #1119). Catches stale-branch merges + --force overrides. Default cap is 20 (#2319; raised from the original 10 per umbrella #1119 Current Shape v3). The framework's own task check passes --allow-over-cap during landing-day overage; consumer projects MUST NOT pass that flag."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` /
    # `generates:` because the gate forwards user-facing flags via
    # {{.CLI_ARGS}} (--allow-over-cap / --quiet) that go-task's
    # incremental-build cache would silently swallow. The framework's
    # own `task check` invocation in Taskfile.yml passes
    # `--allow-over-cap` so deft's landing-day overage (pending/+active/
    # >> 10; resolved via D1 scope:demote --batch per umbrella v3) does
    # not break framework self-check; consumer projects leave the flag
    # OFF so a stale-branch / --force-merge over-cap state fails their
    # `task check` loudly.
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:wip-cap --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  lifecycle-visible:
    desc: "Warn when git ignore/exclude/skip-worktree hides xbrief/vbrief lifecycle roots (#3505), including file-only and date-range globs. Per-clone, not per-commit — not on task check. Default warn-only; pass --enforce to fail closed. -- task verify:lifecycle-visible [-- --enforce]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:lifecycle-visible --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  orphan-active:
    desc: "Fail-closed orphan-active guard (#2321 / #3429). Detects xbrief/active/ briefs with plan.status==running whose referenced GitHub issues are all closed and/or whose linked PR is merged — the stop-at:pr-open lifecycle leak. Confirmed shipped remediates with task scope:complete / scope:cancel or swarm finalize. Unresolved GitHub lookup still exits 1 but prints a retry, not scope:complete. Three-state exit (0 clean / 1 orphan / 2 config). Pass --issue N to scan one origin after merge; --changed-only [--base-ref <ref>] for the merge-chokepoint form that evaluates only briefs the candidate's own diff touches (#3893; falls back to the repo-wide sweep at the delivery tip or on an unresolvable base ref); --skip-gh to rely on triage cache only. Issue state resolves by query shape (#3767): scoped --issue N takes an authoritative read and stays fail-closed on unknown; the unscoped sweep uses one complete open-issue inventory and stays fail-open on unknown. A cache hit counts only within 15 minutes, and every run prints the basis of its verdict — see content/docs/orphan-active-verdict-basis.md."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:orphan-active --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  pr-closeout-attestable:
    desc: "Fail-closed merge-time closeout gate (#3781). Refuses a merge when the PR's structured closing references name an issue whose brief is still running in xbrief/active/ with acceptance criteria carrying neither x-directive/evidence nor x-directive/disposition. The trigger is the closing reference, 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. Reuses evaluateAcceptanceEvidenceGate, the same rule scope:complete enforces. A PR that leaves an unattested brief WITHOUT closing its issue is unaffected. Three-state exit (0 attestable or closes nothing / 1 unattested closeout / 2 config or closing-reference lookup error). -- task verify:pr-closeout-attestable -- --pr N [--repo OWNER/REPO]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:pr-closeout-attestable --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  completed-write-guard:
    desc: "Fail-closed completed/ write guard (#3679). Newly added xbrief/completed/ (or vbrief/completed/) artifacts must have been written by scope:complete / scope:fail. Historical corpus is advisory via doctor. Remediation names the leftover land PR (#3476). Three-state exit (0 clean / 1 unguarded add / 2 config). Does not change verify:completed-tracked."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:completed-write-guard --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  completed-tracked:
    desc: "Fail-closed completed-tracked land guard (#3264 / #1358 / #3476). Closed scoped GitHub issues with a known lifecycle xBRIEF origin must have a tracked xbrief/completed/ or xbrief/cancelled/ artifact on the delivery tip. --issue N is the drive-to DONE form. Remediation: task swarm:finalize-cohort or a lifecycle PR. Three-state exit (0 clean / 1 missing land / 2 config). Pass --skip-gh to rely on triage cache only; --tip to override delivery tip."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:completed-tracked --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  literal-ac:
    desc: "Literal acceptance-command verification (#3267). Run stated acceptance commands from the active (or --xbrief) scope artifact VERBATIM before done — same flags/cwd. Capture-only audit: --capture-only. Three-state exit (0 pass or none stated / 1 command failed / 2 config). Extends #973; required on ceremony dial rapid/minimal (#3214). Prefer verify:ac (#3284) as the product-first primary name."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:literal-ac --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  ac:
    desc: "Product-first acceptance gate (#3284). Runs plan.acceptance.commands (or #3267 literal ledger) FIRST and never skippable when commands exist. Check composition uses --soft-missing-xbrief. Rapid ceremony = AC-only; pressure/degraded makes hygiene advisory. Capture-only: --capture-only. Three-state exit (0 pass or none / 1 fail / 2 config). Architecture for #3267."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:ac --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  agents-md-budget:
    desc: "Layered AGENTS.md budget instrument (#645 + #2450). Fail-closed relative ratchet: counts the managed section and the unmanaged region separately (the #1309 propagation duplicates content across the marker) and fails when either region grows past plan.policy.agentsMdBudget. Seeded at current size, so it ships green; growth past the ratchet fails. ADVISORY absolute north-star: also reports managed-section size vs ≤8 KB / ~2k tok (#2372 layered instrument) without affecting exit codes in Wave 1. Three-state exit (0 within / 1 over ratchet / 2 config error)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}} (--quiet).
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:agents-md-budget --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  eval-health-relocation:
    desc: "Fail-closed eval:health no-regression gate for epic #2369 rule-relocation PRs (#2373). Classifies diffs touching AGENTS.md, content/templates/agents-entry.md, content/skills/**/SKILL.md, or content/packs/** and requires eval:health to not regress against the committed baseline at xbrief/.eval/results/eval-health-baseline.json. Skips (exit 0) when no relocation paths are in the diff. Seed baseline once with --seed-baseline after master is healthy. Three-state exit (0 pass or skip / 1 regression / 2 config error). -- task verify:eval-health-relocation [-- --base-ref <ref> | --staged] [--quiet] [--seed-baseline]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:eval-health-relocation --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  eval-triggers-relocation:
    desc: "Fail-closed eval:triggers gate when AGENTS.md / REFERENCES.md / trigger cases change (#1586). Skips (exit 0) when the diff does not touch trigger-routing homes. -- task verify:eval-triggers-relocation [-- --base-ref <ref> | --staged] [--quiet]"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:eval-triggers-relocation --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  agents-md-advisory:
    desc: "ADVISORY (never fail-closing) consumer AGENTS.md legibility signal (#2155). Counts the UNMANAGED (project-authored) region only -- the framework-owned managed section is excluded -- and compares it against the SOFT, operator-adjustable budget plan.policy.agentsMdAdvisory.unmanagedSoftMaxLines (generous default when unset). Consumer companion to the maintainer-only #645 ratchet; deliberately NOT a check:consumer dependency (advise->observe->enforce per #1419). Default posture ALWAYS exits 0; the opt-in --enforce flag promotes an over-budget region to a hard cap (exit 1). Raising the field is the documented, no-friction way to accept growth and silence the nudge."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    # Per `conventions/task-caching.md` (#574): NO `sources:` / `generates:`
    # because the gate forwards user-facing flags via {{.CLI_ARGS}} (--quiet / --enforce).
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify:agents-md-advisory --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'

  review-monitor:
    desc: "Fail-closed review-monitor gate (#2655): when Tier 1 sub-agent primitive is available, require a recorded active review-monitor before yield / Approach 3 / review ownership. Three-state exit (0 ready / 1 not ready / 2 config error)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-review-monitor {{.CLI_ARGS}}'

  l4-owner:
    desc: "Owner Continuity / L4 owner gate (#3090): exit 0 only if sticky review-owner lease is fresh or --review-cycle done (forbid freeform started/pending). Three-state exit (0 ready / 1 silent hold / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-l4-owner {{.CLI_ARGS}}'

  subagent-alive:
    desc: "Fail-closed worker liveness gate (#2824): missing/STALE heartbeats for in-flight drive-to:merge* workers exit 1 with REDISPATCH_OK. Three-state exit (0 alive / 1 stale-or-missing / 2 config error)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-subagent-alive {{.CLI_ARGS}}'

  subagent-steer:
    desc: "Parent-visible unread steer flag (#4286): exit 1 is STEER_PENDING, not REDISPATCH_OK. Three-state exit (0 none / 1 unread / 2 config)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'verify-subagent-steer {{.CLI_ARGS}}'
