version: '3'

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

# scope lifecycle tasks.
#
# IMPORTANT: every task sets ``dir: '{{.USER_WORKING_DIR}}'`` so
# engine:invoke CWD is the consumer project root, NOT the framework tree.
# DEFT_ROOT is defined locally in this file's `vars:` block via
# ``{{joinPath .TASKFILE_DIR ".."}}`` -- see ../Taskfile.yml for why
# root-level definition is avoided.
#
# Handler: packages/cli/src/scope-lifecycle.ts via engine:invoke
# (packages/cli/dist). Help registry:
# packages/core/src/triage/help/registry-data.ts (edit in place).
#
# CLI_ARGS: passed raw / unquoted. go-task shell-escapes CLI_ARGS with
# single quotes on its own, so wrapping the interpolation in
# Taskfile-level double quotes produces ``"'path'"`` at dispatch time
# on Windows (#577). All scope:* commands therefore forward
# ``{{.CLI_ARGS}}`` bare -- matching migrate.yml / prd.yml / issue.yml /
# reconcile.yml. The TypeScript handler owns path resolution; the task
# forwards ``--project-root`` so detection never silently falls back to
# the framework tree (#535).

tasks:

  promote:
    # Single: deft scope:promote -- xbrief/proposed/<file>.xbrief.json
    # Batch (#3011): deft scope:promote -- --batch  (all proposed/)
    #                deft scope:promote -- --batch path1 path2 [--force]
    desc: "Promote a vBRIEF scope: proposed/ -> pending/ (status: pending). Batch: --batch [paths...] (#3011)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle promote {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  activate:
    desc: "Activate a vBRIEF scope: pending/ -> active/ (status: running)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle activate {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  complete:
    desc: "Complete a vBRIEF scope: active/ -> completed/ (status: completed)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle complete {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  fail:
    # Terminal ``failed`` transition (#614). Parallels ``scope:complete``
    # (same source + destination folders) and differs only in the
    # resulting ``plan.status`` (``failed`` vs ``completed``). Semantic
    # contract: use ``scope:fail`` when a scope was attempted but could
    # not be completed (external blocker, infeasibility discovered
    # mid-flight, deadline hit, agent exhausted retries) and should NOT
    # be cancelled. ``scope:cancel`` remains the right call when the
    # scope is no longer wanted / superseded / obsolete. The canonical
    # v0.6 Status enum includes ``failed`` (see
    # vbrief/schemas/vbrief-core.schema.json).
    desc: "Fail a vBRIEF scope: active/ -> completed/ (status: failed)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle fail {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  cancel:
    desc: "Cancel a vBRIEF scope: any folder -> cancelled/ (status: cancelled)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle cancel {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  restore:
    desc: "Restore a cancelled vBRIEF scope: cancelled/ -> proposed/ (status: proposed)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle restore {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  block:
    desc: "Block a vBRIEF scope: stays in active/ (status: blocked)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle block {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  unblock:
    desc: "Unblock a vBRIEF scope: stays in active/ (status: running)"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-lifecycle unblock {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  decompose:
    desc: "Apply/check an approved epic/phase -> story decomposition draft"
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-decompose {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  # Operator-driven demote (D1 / #1121). Moves a vBRIEF scope from
  # vbrief/pending/ back to vbrief/proposed/ and appends a structured
  # audit entry (including a `demote_meta` block) to
  # vbrief/.eval/scope-lifecycle.jsonl. Two modes are supported:
  #   * Single: `deft scope:demote -- path/to/foo.vbrief.json [--reason TEXT]`
  #   * Batch:  `deft scope:demote -- --batch [--older-than-days 45]`
  # The 30%-threshold falsification gate from D17 was explicitly dropped
  # per Current Shape Decision 2 on #1121; lightweight metrics over the
  # audit log live separately at #1180.
  demote:
    desc: "Demote a vBRIEF scope: pending/ -> proposed/ (#1121). Single-file or --batch --older-than-days N (default 45)."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope-demote {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  # First-adoption / renewal of approved-scope digests (#3205).
  # Mint a human-origin .deft/approved-scope/<plan-id>.json so
  # verify:scope-provenance can authorize pending→active and operator-approved
  # expansion without same-PR self-authorization.
  #   deft scope:record-approved-scope -- xbrief/pending/story.xbrief.json --actor scott --confirm
  #   include-only fallback: task deft:scope:record-approved-scope -- xbrief/pending/story.xbrief.json --actor scott --confirm
  #   deft scope:record-approved-scope -- xbrief/active/story.xbrief.json --actor scott --kind renewed-approval --confirm
  record-approved-scope:
    desc: "Record human-approved file_scope digest under .deft/approved-scope/<plan-id>.json (#3205). Requires --actor <human>. Refuses agent stamps. Path-binds pending→active. Commit on merge base before activation/expansion PR."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope:record-approved-scope {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  record-observable-scope:
    desc: "Record human-minted observable UI contract under .deft/observable-scope/<plan-id>.json (#4495 / #3110). Requires --actor <human> --confirm. Refuses agent stamps and worker-declared baselineRef. Commit on merge base before the UI change PR."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope:record-observable-scope {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'

  record-intent-constraint:
    desc: "Record human-minted intent-constraint contract under .deft/intent-constraint/<plan-id>.json (#4541 / #3110). Requires --actor <human> --confirm. Refuses agent stamps and worker-declared baselineRef. Commit on merge base before the implementation change PR."
    dir: '{{.USER_WORKING_DIR}}'
    deps:
      - task: :engine:_ts-build
    cmds:
      - task: :engine:invoke
        vars:
          ENGINE_CMD: 'scope:record-intent-constraint {{.CLI_ARGS}} --project-root "{{.USER_WORKING_DIR}}"'
