Skip to content

majordomus finish

Evaluate the finish contract and refuse the outcome if any line of it is unmet.

Evaluate the finish contract and refuse the outcome if any line of it is unmet.

  • state-mutating
  • task
  • active task optional
  • --json

--check evaluates without writing and exits 0 when no task is active, so a pre-push hook never blocks a repository with nothing to enforce.

Syntax

majordomus finish --outcome <completed|partial|blocked|no_match|failed> [--verify-command "<cmd>"] [--note <file>]

Lifecycle

  1. Set up
  2. Begin
  3. Work
  4. Verify
  5. Conclude
  6. Inspect

Other commands at this stage:handover, adr.

Reads

  • .ai/repo/policy.yaml
  • .ai/repo/profiles/
  • .ai/local/state/current.yaml
  • .ai/local/state/open-questions.md
  • .ai/local/state/handovers/
  • git status
  • git diff

Writes

  • .ai/local/state/current.yaml
  • .ai/local/state/archive/
  • .ai/local/state/ledger.jsonl

Exit codes

Only the codes this command can produce. The full contract is on the commands index.

  • 0 ok
  • 2 usage
  • 10 contract unmet
  • 12 missing artifact
  • 15 refused

The finish contract

Choose a situation and see whether the outcome is accepted or refused, and for which reasons. Every scenario here is executed against the real binary by test/cases/34_command_fixtures.sh, so this page cannot claim behaviour the tool does not have.

Nothing in place yet

no open blocker
yes
note present
no
scope respected
yes
verification given
no

Given this repository

# ... and an active task scoped to lib, with work done inside that scope.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" start "narrow the parser" --scope lib >/dev/null
echo work >> lib/a

Run

$ majordomus finish --outcome completed

Output contains

  • FAIL verification
  • FAIL note
  • refused

exit 10

Two contract lines are unmet and both are named at once. A worker who fixes one and is refused again for the next learns the contract one round trip at a time, so every cause is reported together.

Work outside the claimed scope

no open blocker
yes
note present
yes
scope respected
no
verification given
yes

Given this repository

# An active task scoped to lib, with a change made under docs that it never claimed.
. "$FIXTURE_SETUP/active-task.sh"
echo stray >> docs/d

Run

$ majordomus finish --outcome completed --verify-command true

Output contains

  • FAIL scope
  • outside claimed scope

exit 10

The task claimed lib. A change under docs is work the task did not claim, and it is refused whatever else is in place.

An unresolved question is open

no open blocker
no
note present
yes
scope respected
yes
verification given
yes

Given this repository

# An active task with an unresolved question open against it.
. "$FIXTURE_SETUP/active-task.sh"
"$MJ" question add "does the parser need to accept tabs?" >/dev/null

Run

$ majordomus finish --outcome completed --verify-command true

Output contains

  • FAIL blockers

exit 10

An open question refuses completed, and only completed. The same task can be finished as blocked, which is the honest outcome when the answer is not yours to give.

The verification command runs and fails

no open blocker
yes
note present
yes
scope respected
yes
verification given
no

Given this repository

# An active task with the note the outcome requires already written.
. "$FIXTURE_SETUP/active-task.sh"
printf '# Objective\no\n# Current State\nc\n# Next Action\nn\n' | "$MJ" handover >/dev/null

Run

$ majordomus finish --outcome completed --verify-command false

Output contains

  • FAIL verification
  • exit 1

exit 10

The command is run, not asserted. Its exit code and duration are recorded whatever it returns, so "verification ran" cannot be claimed without something having actually run.

finish --check with no active task

no open blocker
yes
note present
yes
scope respected
yes
verification given
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus finish --check

Output contains

  • nothing to enforce

exit 0

This is why a pre-push hook can run finish --check unconditionally: a repository with no active task is not a failing repository.

Everything in place

no open blocker
yes
note present
yes
scope respected
yes
verification given
yes

Given this repository

# An active task with the note the outcome requires already written.
. "$FIXTURE_SETUP/active-task.sh"
printf '# Objective\no\n# Current State\nc\n# Next Action\nn\n' | "$MJ" handover >/dev/null

Run

$ majordomus finish --outcome completed --verify-command true

Output contains

  • completed

exit 0

Accepted, and the ledger records the outcome together with the verification that earned it.

Reference

Evaluate the finish contract. Refuse if unmet.

Reads: policy, profile, current.yaml, git, ledger. Writes: on success, current.yaml outcome set to the given value, one task.finished ledger line carrying the evaluated checklist and the verification result, and a copy of --note under state/completed/<id>.md when given.

Arguments: --outcome completed|partial|blocked|no_match|failed required. --verify-command "<cmd>" runs the project's own verification in the repository root and records its exit code, duration, and command. --note <file> supplies the completion note; otherwise the newest handover naming this task is used. --check evaluates scope and state without writing and exits 0 when no task is active or the task is already finished, so a pre-push hook never blocks a repository with nothing to enforce.

Profile requirements are also evaluated for completed: regression_test_required passes when a touched path looks like a test (test/, spec/, _test., .spec.); decision_record_required passes when decisions.md contains Task: <id>. The regression check is deliberately crude and says so in its message.

Contract for completed:

scope respected        touched files within claimed paths
verification ran       --verify-command exited 0, recorded
state updated          current.yaml is at HEAD or advanced, not diverged
no open blockers       open-questions.md has no unresolved entry for this task
note present           newest handover or completion note has required sections

Every line of the contract is evaluated and printed, pass or fail, so that a refusal says exactly what is missing. partial and blocked require a note with # Next Action; no_match and failed require # Reason; all four skip the verification line, and blocked skips the blockers line. Nothing is written when any line fails. Finishing an already finished task is refused (15).

$ majordomus finish --outcome completed --verify-command "make test"
OK   scope         12 files, all within lib/auth
OK   verification  make test — exit 0, 41s
OK   state         exact (head 9b1e2d4)
FAIL blockers      open-questions.md: "token refresh window — needs product decision" unresolved  [reproduce: grep -n 'unresolved' .ai/local/state/open-questions.md]
OK   note          handover 20260903T201455Z--main--9b1e2d4--c0ffee.md
finish: refused, 1 unmet
blocking doctrines:
- majordomus.blocker-resolution
$ echo $?
10

The contract is not a list inside finish. It is the set of doctrines whose enforced_by names finish, selected by this repository's verification.finish_requires. A requirement in the policy that no doctrine defines is reported and refuses, rather than being ignored. See DOCTRINE.md.


Evidence

Every case below runs in CI on Linux and macOS. The site refuses to build if a public command has no behavioural case, no negative case, or no demonstration.

Guarantees this command carries