dorfl 0.1.2 → 0.2.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/dist/advance-drivers.d.ts +1 -1
- package/dist/advance-drivers.d.ts.map +1 -1
- package/dist/advance-drivers.js +15 -3
- package/dist/advance-drivers.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +76 -4
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/advance-treeless-publish.d.ts +23 -0
- package/dist/advance-treeless-publish.d.ts.map +1 -1
- package/dist/advance-treeless-publish.js +41 -0
- package/dist/advance-treeless-publish.js.map +1 -1
- package/dist/advance.d.ts +59 -7
- package/dist/advance.d.ts.map +1 -1
- package/dist/advance.js +389 -82
- package/dist/advance.js.map +1 -1
- package/dist/advancing-lock.d.ts +30 -2
- package/dist/advancing-lock.d.ts.map +1 -1
- package/dist/advancing-lock.js +50 -0
- package/dist/advancing-lock.js.map +1 -1
- package/dist/agent-launch.d.ts +12 -0
- package/dist/agent-launch.d.ts.map +1 -1
- package/dist/agent-launch.js +22 -12
- package/dist/agent-launch.js.map +1 -1
- package/dist/agent-stop.d.ts +40 -2
- package/dist/agent-stop.d.ts.map +1 -1
- package/dist/agent-stop.js +30 -2
- package/dist/agent-stop.js.map +1 -1
- package/dist/apply-decide.d.ts +17 -3
- package/dist/apply-decide.d.ts.map +1 -1
- package/dist/apply-decide.js +36 -7
- package/dist/apply-decide.js.map +1 -1
- package/dist/apply-merge-action.d.ts +17 -5
- package/dist/apply-merge-action.d.ts.map +1 -1
- package/dist/apply-merge-action.js +43 -8
- package/dist/apply-merge-action.js.map +1 -1
- package/dist/apply-persist.d.ts +57 -26
- package/dist/apply-persist.d.ts.map +1 -1
- package/dist/apply-persist.js +166 -37
- package/dist/apply-persist.js.map +1 -1
- package/dist/apply-stuck-action.d.ts +151 -0
- package/dist/apply-stuck-action.d.ts.map +1 -0
- package/dist/apply-stuck-action.js +125 -0
- package/dist/apply-stuck-action.js.map +1 -0
- package/dist/brand.d.ts +12 -1
- package/dist/brand.d.ts.map +1 -1
- package/dist/brand.js +2 -1
- package/dist/brand.js.map +1 -1
- package/dist/cli-spinner.d.ts +1 -1
- package/dist/cli-spinner.js +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +320 -63
- package/dist/cli.js.map +1 -1
- package/dist/close-job-template.js +1 -1
- package/dist/complete.d.ts +6 -3
- package/dist/complete.d.ts.map +1 -1
- package/dist/complete.js +91 -37
- package/dist/complete.js.map +1 -1
- package/dist/config.d.ts +55 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -4
- package/dist/config.js.map +1 -1
- package/dist/continue-branch.d.ts.map +1 -1
- package/dist/continue-branch.js +22 -0
- package/dist/continue-branch.js.map +1 -1
- package/dist/decision-engine.d.ts +38 -10
- package/dist/decision-engine.d.ts.map +1 -1
- package/dist/decision-engine.js +10 -6
- package/dist/decision-engine.js.map +1 -1
- package/dist/do-autopick.d.ts.map +1 -1
- package/dist/do-autopick.js +11 -1
- package/dist/do-autopick.js.map +1 -1
- package/dist/do-config.d.ts +1 -1
- package/dist/do.d.ts +49 -56
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +419 -30
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +12 -4
- package/dist/env-config.js.map +1 -1
- package/dist/failure-cause.d.ts +3 -2
- package/dist/failure-cause.d.ts.map +1 -1
- package/dist/failure-cause.js +28 -1
- package/dist/failure-cause.js.map +1 -1
- package/dist/format.d.ts +6 -6
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +10 -27
- package/dist/format.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -4
- package/dist/frontmatter.d.ts.map +1 -1
- package/dist/frontmatter.js +27 -1
- package/dist/frontmatter.js.map +1 -1
- package/dist/gc.d.ts +65 -6
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +126 -9
- package/dist/gc.js.map +1 -1
- package/dist/github.d.ts +14 -0
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +73 -0
- package/dist/github.js.map +1 -1
- package/dist/harness.d.ts +25 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/install-ci-branch-protection.d.ts +132 -38
- package/dist/install-ci-branch-protection.d.ts.map +1 -1
- package/dist/install-ci-branch-protection.js +190 -48
- package/dist/install-ci-branch-protection.js.map +1 -1
- package/dist/install-ci-core.d.ts +21 -1
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +10 -3
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +32 -0
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +73 -0
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-skills.d.ts +123 -0
- package/dist/install-skills.d.ts.map +1 -0
- package/dist/install-skills.js +100 -0
- package/dist/install-skills.js.map +1 -0
- package/dist/intake-trigger-template.js +1 -1
- package/dist/intake.d.ts +17 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +23 -7
- package/dist/intake.js.map +1 -1
- package/dist/integration-core.d.ts +10 -7
- package/dist/integration-core.d.ts.map +1 -1
- package/dist/integration-core.js +174 -60
- package/dist/integration-core.js.map +1 -1
- package/dist/integrator.d.ts +1 -1
- package/dist/integrator.d.ts.map +1 -1
- package/dist/integrator.js +19 -3
- package/dist/integrator.js.map +1 -1
- package/dist/isolation.d.ts +2 -2
- package/dist/isolation.d.ts.map +1 -1
- package/dist/isolation.js +9 -0
- package/dist/isolation.js.map +1 -1
- package/dist/item-lock.d.ts +196 -129
- package/dist/item-lock.d.ts.map +1 -1
- package/dist/item-lock.js +329 -254
- package/dist/item-lock.js.map +1 -1
- package/dist/ledger-write.d.ts +31 -26
- package/dist/ledger-write.d.ts.map +1 -1
- package/dist/ledger-write.js +100 -122
- package/dist/ledger-write.js.map +1 -1
- package/dist/lifecycle-gather.d.ts +17 -0
- package/dist/lifecycle-gather.d.ts.map +1 -1
- package/dist/lifecycle-gather.js +7 -1
- package/dist/lifecycle-gather.js.map +1 -1
- package/dist/lifecycle-pools.d.ts +40 -3
- package/dist/lifecycle-pools.d.ts.map +1 -1
- package/dist/lifecycle-pools.js +26 -6
- package/dist/lifecycle-pools.js.map +1 -1
- package/dist/merge-question-surfacer.d.ts +15 -1
- package/dist/merge-question-surfacer.d.ts.map +1 -1
- package/dist/merge-question-surfacer.js +18 -3
- package/dist/merge-question-surfacer.js.map +1 -1
- package/dist/migrate-stuck-locks.d.ts +129 -0
- package/dist/migrate-stuck-locks.d.ts.map +1 -0
- package/dist/migrate-stuck-locks.js +355 -0
- package/dist/migrate-stuck-locks.js.map +1 -0
- package/dist/needs-attention.d.ts +293 -16
- package/dist/needs-attention.d.ts.map +1 -1
- package/dist/needs-attention.js +532 -57
- package/dist/needs-attention.js.map +1 -1
- package/dist/orphan-sidecar.d.ts +8 -4
- package/dist/orphan-sidecar.d.ts.map +1 -1
- package/dist/orphan-sidecar.js +35 -2
- package/dist/orphan-sidecar.js.map +1 -1
- package/dist/pi-harness.d.ts +16 -0
- package/dist/pi-harness.d.ts.map +1 -1
- package/dist/pi-harness.js +82 -2
- package/dist/pi-harness.js.map +1 -1
- package/dist/placement.d.ts +3 -3
- package/dist/placement.js +1 -1
- package/dist/prd-to-spec.d.ts.map +1 -1
- package/dist/prd-to-spec.js +9 -5
- package/dist/prd-to-spec.js.map +1 -1
- package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
- package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
- package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
- package/dist/protocol/TASKING-PROTOCOL.md +3 -1
- package/dist/protocol/WORK-CONTRACT.md +22 -18
- package/dist/protocol/task-template.md +1 -1
- package/dist/reap-branches.d.ts +12 -9
- package/dist/reap-branches.d.ts.map +1 -1
- package/dist/reap-branches.js +25 -7
- package/dist/reap-branches.js.map +1 -1
- package/dist/recover-isolated.d.ts +12 -0
- package/dist/recover-isolated.d.ts.map +1 -1
- package/dist/recover-isolated.js +5 -0
- package/dist/recover-isolated.js.map +1 -1
- package/dist/repo-config.d.ts +23 -2
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +53 -7
- package/dist/repo-config.js.map +1 -1
- package/dist/repo-mirror.d.ts.map +1 -1
- package/dist/repo-mirror.js +18 -2
- package/dist/repo-mirror.js.map +1 -1
- package/dist/review-verdict.js +1 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +54 -19
- package/dist/run.js.map +1 -1
- package/dist/scan.d.ts +17 -8
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -8
- package/dist/scan.js.map +1 -1
- package/dist/select-priority.d.ts +2 -2
- package/dist/select-priority.js +1 -1
- package/dist/select-priority.js.map +1 -1
- package/dist/sidecar-apply.js +1 -1
- package/dist/sidecar-apply.js.map +1 -1
- package/dist/sidecar.d.ts +45 -10
- package/dist/sidecar.d.ts.map +1 -1
- package/dist/sidecar.js +82 -2
- package/dist/sidecar.js.map +1 -1
- package/dist/skills/answer-questions/SKILL.md +89 -0
- package/dist/skills/capture-signal/SKILL.md +52 -0
- package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
- package/dist/skills/drive-tasks/SKILL.md +218 -0
- package/dist/skills/from-idea/SKILL.md +83 -0
- package/dist/skills/merge-prs/SKILL.md +70 -0
- package/dist/skills/orchestrate/SKILL.md +101 -0
- package/dist/skills/promote/SKILL.md +35 -0
- package/dist/skills/review/SKILL.md +16 -0
- package/dist/skills/setup/SKILL.md +258 -0
- package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
- package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
- package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
- package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
- package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
- package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
- package/dist/skills/setup/protocol/spec-template.md +71 -0
- package/dist/skills/setup/protocol/task-template.md +65 -0
- package/dist/skills/surface-questions/SKILL.md +16 -0
- package/dist/skills/to-spec/SKILL.md +34 -0
- package/dist/skills/to-task/SKILL.md +19 -0
- package/dist/skills/triage-observations/SKILL.md +78 -0
- package/dist/skills/work/SKILL.md +51 -0
- package/dist/slug-namespace.d.ts +5 -5
- package/dist/slug-namespace.js +5 -5
- package/dist/slug-namespace.js.map +1 -1
- package/dist/spec-complete.d.ts.map +1 -1
- package/dist/spec-complete.js +2 -3
- package/dist/spec-complete.js.map +1 -1
- package/dist/start.d.ts.map +1 -1
- package/dist/start.js +51 -57
- package/dist/start.js.map +1 -1
- package/dist/surface-gate.d.ts.map +1 -1
- package/dist/surface-gate.js +10 -3
- package/dist/surface-gate.js.map +1 -1
- package/dist/surface-persist.js +1 -1
- package/dist/surface-persist.js.map +1 -1
- package/dist/tasker-review-loop.d.ts +4 -3
- package/dist/tasker-review-loop.d.ts.map +1 -1
- package/dist/tasker-review-loop.js.map +1 -1
- package/dist/tasking-lock.d.ts +2 -2
- package/dist/tasking-lock.d.ts.map +1 -1
- package/dist/tasking-lock.js +53 -32
- package/dist/tasking-lock.js.map +1 -1
- package/dist/tasking.d.ts +40 -6
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +224 -12
- package/dist/tasking.js.map +1 -1
- package/dist/triage-persist.d.ts +10 -6
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +34 -4
- package/dist/triage-persist.js.map +1 -1
- package/dist/vendor/incur/agents.d.ts +58 -0
- package/dist/vendor/incur/agents.d.ts.map +1 -0
- package/dist/vendor/incur/agents.js +343 -0
- package/dist/vendor/incur/agents.js.map +1 -0
- package/dist/verify-workflow-template.js +1 -1
- package/dist/watch-session.d.ts +11 -3
- package/dist/watch-session.d.ts.map +1 -1
- package/dist/watch-session.js +94 -7
- package/dist/watch-session.js.map +1 -1
- package/dist/work-layout.d.ts +7 -3
- package/dist/work-layout.d.ts.map +1 -1
- package/dist/work-layout.js +6 -3
- package/dist/work-layout.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/package.json +2 -2
- package/src/advance-drivers.ts +16 -3
- package/src/advance-lifecycle-template.ts +89 -4
- package/src/advance-treeless-publish.ts +45 -0
- package/src/advance.ts +467 -87
- package/src/advancing-lock.ts +96 -1
- package/src/agent-launch.ts +37 -12
- package/src/agent-stop.ts +60 -2
- package/src/apply-decide.ts +36 -7
- package/src/apply-merge-action.ts +45 -7
- package/src/apply-persist.ts +228 -54
- package/src/apply-stuck-action.ts +260 -0
- package/src/brand.ts +14 -2
- package/src/cli-spinner.ts +1 -1
- package/src/cli.ts +401 -70
- package/src/close-job-template.ts +1 -1
- package/src/complete.ts +110 -52
- package/src/config.ts +107 -13
- package/src/continue-branch.ts +22 -0
- package/src/decision-engine.ts +55 -17
- package/src/do-autopick.ts +12 -1
- package/src/do-config.ts +1 -1
- package/src/do.ts +511 -38
- package/src/env-config.ts +12 -4
- package/src/failure-cause.ts +30 -1
- package/src/format.ts +10 -30
- package/src/frontmatter.ts +35 -5
- package/src/gc.ts +172 -9
- package/src/github.ts +78 -0
- package/src/harness.ts +25 -0
- package/src/index.ts +10 -0
- package/src/install-ci-branch-protection.ts +282 -57
- package/src/install-ci-core.ts +30 -3
- package/src/install-ci-github.ts +86 -0
- package/src/install-skills.ts +166 -0
- package/src/intake-trigger-template.ts +1 -1
- package/src/intake.ts +46 -11
- package/src/integration-core.ts +199 -65
- package/src/integrator.ts +22 -9
- package/src/isolation.ts +11 -2
- package/src/item-lock.ts +485 -341
- package/src/ledger-write.ts +135 -150
- package/src/lifecycle-gather.ts +29 -6
- package/src/lifecycle-pools.ts +67 -9
- package/src/merge-question-surfacer.ts +28 -4
- package/src/migrate-stuck-locks.ts +451 -0
- package/src/needs-attention.ts +857 -84
- package/src/orphan-sidecar.ts +47 -6
- package/src/pi-harness.ts +82 -2
- package/src/placement.ts +3 -3
- package/src/prd-to-spec.ts +13 -5
- package/src/reap-branches.ts +38 -14
- package/src/recover-isolated.ts +17 -0
- package/src/repo-config.ts +55 -6
- package/src/repo-mirror.ts +23 -1
- package/src/review-verdict.ts +1 -1
- package/src/run.ts +64 -18
- package/src/scan.ts +22 -8
- package/src/select-priority.ts +3 -3
- package/src/sidecar-apply.ts +1 -1
- package/src/sidecar.ts +138 -11
- package/src/slug-namespace.ts +7 -7
- package/src/spec-complete.ts +2 -3
- package/src/start.ts +53 -59
- package/src/surface-gate.ts +10 -3
- package/src/surface-persist.ts +1 -1
- package/src/tasker-review-loop.ts +4 -3
- package/src/tasking-lock.ts +52 -36
- package/src/tasking.ts +261 -17
- package/src/triage-persist.ts +56 -12
- package/src/vendor/incur/LICENSE +21 -0
- package/src/vendor/incur/README.md +19 -0
- package/src/vendor/incur/agents.ts +392 -0
- package/src/verify-workflow-template.ts +1 -1
- package/src/watch-session.ts +102 -7
- package/src/work-layout.ts +6 -3
- package/src/workspace.ts +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# The `work/` on-disk contract
|
|
2
|
+
|
|
3
|
+
The shared contract between the task PRODUCER (the tasking discipline) and the task CONSUMER (the runner / lifecycle). It is designed to be **conflict-safe for parallel agents**: every rule below avoids merge conflicts and lost updates.
|
|
4
|
+
|
|
5
|
+
## Location
|
|
6
|
+
|
|
7
|
+
`work/` lives **inside the target project repo**, versioned with that repo's code. Tasks reference that repo's code; work happens in clones/worktrees of that repo.
|
|
8
|
+
|
|
9
|
+
## Layout — three REGIME umbrellas: notes/ (capture) + tasks/ (build) + specs/ (spec lifecycle), plus questions/ + protocol/
|
|
10
|
+
|
|
11
|
+
The top level groups every tree by its GOVERNANCE REGIME, so a reader can tell what a folder MEANS without reading further: `notes/` are capture buckets (they do not flow), `tasks/` is the build board (status = folder), `specs/` is the spec lifecycle (status = folder), and `questions/` + `protocol/` are standalone top-level surfaces.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
work/
|
|
15
|
+
# ---- notes/ — CAPTURE BUCKETS: NOT status-governed; they do NOT flow/move ----
|
|
16
|
+
notes/
|
|
17
|
+
ideas/<slug>.md # proposed, pre-spec ideas — EDITABLE, deletable
|
|
18
|
+
observations/<slug>.md # spotted, unverified signals — APPEND-ONLY, deletable
|
|
19
|
+
findings/<slug>.md # VERIFIED external/domain ground truth — durable
|
|
20
|
+
|
|
21
|
+
# ---- tasks/ — the BUILD board: DURABLE status IS the folder; FLOW via `git mv` on `main` ----
|
|
22
|
+
# Task lifecycle (staging → pool → terminal):
|
|
23
|
+
tasks/
|
|
24
|
+
backlog/<slug>.md # STAGING: a task not yet admitted to the agent pool —
|
|
25
|
+
# review-first admission AND the human-control position
|
|
26
|
+
# (untrusted output lands here; a human promotes, OR drives
|
|
27
|
+
# it IN PLACE via `do --allow-backlog` — never promote-then-drive)
|
|
28
|
+
ready/<slug>.md # the AGENT POOL: built tasks, grabbable items eligible to claim
|
|
29
|
+
done/<slug>.md # completed (moved here durably on `main` at integration)
|
|
30
|
+
cancelled/<slug>.md # the task regime's "won't-proceed" terminal (lightweight ADR);
|
|
31
|
+
# the REASON (out-of-scope / superseded by <x> / duplicate /
|
|
32
|
+
# abandoned) lives in the item body as `reason:`
|
|
33
|
+
|
|
34
|
+
# ---- specs/ — the SPEC lifecycle: DURABLE status IS the folder; FLOW via `git mv` on `main` ----
|
|
35
|
+
# Spec lifecycle (staging → pool → tasked / terminal):
|
|
36
|
+
specs/
|
|
37
|
+
proposed/<slug>.md # STAGING: a spec not yet admitted to the auto-task pool —
|
|
38
|
+
# review-first admission AND the human-control position
|
|
39
|
+
# (untrusted/agent-authored output lands here; a human promotes,
|
|
40
|
+
# OR tasks it IN PLACE — TASKING-PROTOCOL.md §6 — never promote-then-task)
|
|
41
|
+
ready/<slug>.md # the AUTO-TASK POOL: specs eligible to be tasked into tasks
|
|
42
|
+
tasked/<slug>.md # TASKED, resting specs — the spec `done/` analogue; the
|
|
43
|
+
# SOURCE OF TRUTH for tasked-ness (see note below)
|
|
44
|
+
dropped/<slug>.md # the spec regime's "won't-proceed" terminal (REASON in the body)
|
|
45
|
+
|
|
46
|
+
# ---- questions/ — the "what needs me?" queue, kept TOP-LEVEL (NOT under notes/) ----
|
|
47
|
+
questions/<slug>.md # surfaced blockers a human must look at — glance-able top-level
|
|
48
|
+
|
|
49
|
+
# ---- protocol/ — the protocol reference docs ----
|
|
50
|
+
protocol/ # WORK-CONTRACT.md, CLAIM-PROTOCOL.md, the templates, VERSION
|
|
51
|
+
|
|
52
|
+
# ---- TRANSIENT IN-FLIGHT HOLDS: NOT on `main` — on per-item lock refs ----
|
|
53
|
+
# `in-progress` (claimed/building), `tasking` (a spec being tasked), and
|
|
54
|
+
# `advancing` (a tick holding an item) are NOT `main` folders. They collapse
|
|
55
|
+
# into ONE per-item lock on a hidden `refs/dorfl/lock/<type>-<slug>` ref:
|
|
56
|
+
# `action: implement|task|advance` (+ holder/since). `LockState` is
|
|
57
|
+
# `'active'` ONLY (the `stuck` lock state was retired — see `needs-attention`
|
|
58
|
+
# below and the 2026-07-14 addendum to ADR `ledger-status-on-per-item-lock-refs`).
|
|
59
|
+
# The lock is the IN-FLIGHT ACTIVE HOLD only: real CAS mutual-exclusion for a
|
|
60
|
+
# running claim/build/task/advance, always released at end-of-leg (success OR
|
|
61
|
+
# bounce). A bounced "needs a human" item is surfaced on `main` (see below), not
|
|
62
|
+
# parked on the lock. A human reads in-flight holds via `dorfl status`/`scan`
|
|
63
|
+
# (which read the lock refs) and reads parked-on-`main` items by `ls`-ing
|
|
64
|
+
# `work/questions/` / `work/tasks/ready/`.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> **The two won't-proceed terminals use DIFFERENT words ON PURPOSE — `tasks/cancelled/` vs `specs/dropped/` — and it is a CORRECTNESS rule, not taste.** A task and a spec can share a slug, and a single shared bare-slug terminal (`work/dropped/<slug>.md`) would COLLIDE a dropped task and a dropped spec on the same path. Namespacing each regime's terminal under its own umbrella (`tasks/cancelled/<slug>.md`, `specs/dropped/<slug>.md`) gives each its own slug space, so the collision cannot happen. A dropped OBSERVATION needs no terminal — notes leave by deletion. (Every reader keys by `(umbrella, slug)`, never a bare slug, so `tasks/ready/foo.md` and `specs/ready/foo.md` legitimately co-exist.)
|
|
68
|
+
|
|
69
|
+
### Three governance regimes + the substrate split (the key distinctions)
|
|
70
|
+
|
|
71
|
+
- **Work items' DURABLE positions are the folder** (specs: `specs/proposed`/`specs/ready`/`specs/tasked`/`specs/dropped`; tasks: `tasks/backlog`/`tasks/ready`/`tasks/done`/`tasks/cancelled`): **status = the folder**, transitions are `git mv` on `main`, each has one destiny. This is the conflict-safe core for the durable resting records. The ONLY moves ever made on `main` are these durable resting transitions: `tasks/ready → tasks/done`, `specs/ready → specs/tasked`, `tasks/ready → tasks/cancelled` (and `specs/ready → specs/dropped`). The per-regime terminals (`tasks/cancelled/`, `specs/dropped/`) are where an item that will not proceed for ANY reason (superseded, out-of-scope, duplicate, abandoned/obsolete) rests, with the REASON in the body (`reason:` line). They are deliberately NAMED differently per regime — see the slug-collision note above.
|
|
72
|
+
- **Transient status + locks are NOT on `main`** — they are per-item lock refs. `in-progress`/`needs-attention`/`tasking`/`advancing` are lock-ref state, not folders. A work branch cut from `main` therefore inherits NO transient status, so a continue/rebase is a plain rebase with nothing to drop. Eligibility/dependency resolution stay OFFLINE on `main` (`blockedBy → tasks/done/`, `taskedAfter → specs/tasked/`); only the operational "what's in flight" view (`status`/`scan`) reads the lock refs.
|
|
73
|
+
- **Capture buckets** (`notes/ideas`/`notes/observations`/`notes/findings`) are **NOT work items** and are **exempt from status = folder** — they are _notes_, not units of work. They do not move through statuses; they sit in their bucket, and the folder is the inbox (`ls work/notes/observations/` = the live signal list). They leave only by **deletion** (git history is the archive). A note may _spawn_ work (a task, an idea, an ADR) created independently — the note does not "become" or `git mv` into that work; it is simply deleted once it is no longer a useful signal. **Operational discharge test for a promoted note:** a note is dischargeable (deletable) the moment a **self-contained** artifact carries its signal — verify the spawned task/ADR actually contains the mechanism + fix shape (not just a back-pointer), then delete the note. Do NOT keep it until the spawned work lands in `tasks/done/`: a note stops being a live _signal_ the moment it is captured into actionable work, not when that work completes. If the spawned artifact is NOT self-contained, the bug is the artifact (fix it to carry the signal), not a reason to keep the note.
|
|
74
|
+
- **Deletion-on-apply is the SANCTIONED discharge — it is human-AUTHORED, so the capture-bucket rule below — a note "leaves the inbox **by deletion** the moment it stops being a live signal", a judgement only a human is authorised to make — does NOT bar it.** When the `advance` apply rung acts on a note whose question the human has ANSWERED, a decision agent reads that answer + the source note and emits a VERDICT, and the discharge DELETES the note in the SAME commit: a mint verdict (a new task / spec / ADR) rides the note's `git rm` in the same atomic commit as the new artifact's create; a delete-source verdict (the answer means "throw it away") is a standalone, revertible delete commit with the reason in the commit message. The cheap throw-away has a DIRECT path too — the human, the `answer-questions` skill, or the `dorfl` delete verb removes the source + its sidecar straight, no engine round-trip. Either way this is the human's ANSWER being EXECUTED, not the agent unilaterally destroying a live signal — that capture-bucket rule (the agent never deletes a live signal on its own judgement) only ever barred deleting an **un-answered** note; the human's answer IS the authorisation to delete. There is therefore **no `triaged:` / `needsAnswers:false` resting state** for a discharged note and **no `## Recommended: delete` recommend-and-retain hand-off** — a discharged note leaves the inbox, it does not rest there stamped "resolved". (Work ITEMS still leave via a terminal FOLDER, never by deletion; only notes discharge by deletion.)
|
|
75
|
+
|
|
76
|
+
> **Every capture-bucket note and every work item has a DIRECTION and a LIVENESS — never manufacture a backward artifact to look compliant.** Forward artifacts — a `tasks/ready/` task, an _open_ `notes/observations/` signal — describe work that is **pending or currently-signalled**, never the past. So: work that is **already done** does NOT get a task or observation back-filled to narrate it (a `tasks/ready/` task with pre-ticked acceptance criteria is a changelog wearing a spec's shape); completed work is recorded as a `tasks/done/` record landed _with_ the code plus the commit message, owned by whoever does the git transition. And a captured note is LIVE: it leaves the inbox **by deletion** the moment it stops being a live signal — a note annotated "resolved" and kept is a contradiction (there is no `resolved` status; discharge it by deleting it, its lasting product being the task/ADR/commit it spawned). This binds an agent invoked **outside** the runner too: building directly is fine when asked, but do not retroactively mint forward artifacts for it afterward.
|
|
77
|
+
|
|
78
|
+
### The three capture buckets (different by polarity + mutability)
|
|
79
|
+
|
|
80
|
+
| Bucket | What | Mutability | Leaves by |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| `notes/ideas/` | a _proposed_, pre-spec opportunity ("we might want to build this") | **editable** (refine the proposal in place) | deletion (when built/abandoned) |
|
|
83
|
+
| `notes/observations/` | an _observed, unverified_ signal ("I noticed something maybe wrong") | **append-only** (add `## Update` notes; don't rewrite what was seen) | deletion (when no longer a useful signal) |
|
|
84
|
+
| `notes/findings/` | _verified external/domain_ ground truth (a reverse-engineered protocol, an external API's real behaviour) | accumulates; durable | rarely — it is reference knowledge |
|
|
85
|
+
|
|
86
|
+
> **`findings/` is for EXTERNAL/DOMAIN ground truth, NOT internal post-mortems.** A finding is durable knowledge about a _world the software integrates with_ (e.g. a Bluetooth/hardware protocol we reverse-engineered, a third-party API's undocumented behaviour) — it accumulates, it does not "resolve". An _internal_ investigation (why a test flakes, a perf regression) is NOT a finding: it is a transient `notes/observations/` signal that drives a fix task and/or an ADR. **ADRs — the durable _why_ of OUR technical decisions — live in `docs/adr/`** (format: `ADR-FORMAT.md`, alongside this contract), never in `work/notes/findings/`. So: observation = "spotted, unverified"; finding = "verified external ground truth"; ADR = "what WE decided and why".
|
|
87
|
+
>
|
|
88
|
+
> **Every finding MUST carry a `source:` (provenance) — how, and how _currently_, the finding came to be believed.** A finding is only as true as the source it was derived from, so the source is what makes it _correctable_: if the source is later shown wrong (or stale), the finding can be revised and you can trace _why_ it was believed. There is deliberately **no separate `confidence:` field** — a bare confidence label is redundant at best and misleading at worst ("doc-verified" sounds authoritative until you learn the doc was last touched ten years ago). The honest signal lives IN a rich `source:` string: state _what_ the source is AND _how current_ it is, specifically enough that a reader can judge its weight themselves. Examples (weakest → strongest, by their own description):
|
|
89
|
+
>
|
|
90
|
+
> - `"derived from reading src/<the-integrating-module> @ <commit>"` — weakest: it assumes our code is correct, so the finding inherits any bug in it. (A code-derived finding describes the _external behaviour our code assumes_, NOT our code's internal shape — that is `CONTEXT.md`/`docs/`.)
|
|
91
|
+
> - `"<external API/spec> docs, retrieved 2026-06-09"` — a dated external authority (the date is what stops it silently going stale).
|
|
92
|
+
> - `"captured live API response 2026-06-09, trace in <path>"` — strongest.
|
|
93
|
+
> - `"told by maintainer @<name>, 2026-06"` / `"inferred from the test asserting it at <path>"` — whatever it actually was; write it plainly.
|
|
94
|
+
>
|
|
95
|
+
> Put `source:` in the finding's frontmatter (see below) and, when the provenance is non-obvious, expand on it in the body. A finding without a source is a `notes/observations/` signal, not a finding.
|
|
96
|
+
|
|
97
|
+
**For work items, DURABLE status is the folder a file lives in — never a frontmatter field.** Finishing / dropping / tasking-complete = moving the file between durable folders with `git mv` on `main`. This is what makes concurrent durable updates safe: two agents moving _different_ files never conflict. (Transient status — claimed/stuck/being-tasked — is NOT a folder move; it is a per-item lock ref, see above. Capture buckets are exempt too.)
|
|
98
|
+
|
|
99
|
+
### The spec lifecycle: `specs/ready/` (pool) → `specs/tasked/` on `main`; the tasking HOLD is a lock ref
|
|
100
|
+
|
|
101
|
+
A spec rests in `work/specs/ready/` (the auto-task pool) and, when tasked into tasks, moves durably to `work/specs/tasked/` on `main`. The **folder is the source of truth for tasked-ness**, exactly as `work/tasks/done/` is for tasks. Re-tasking a reshaped spec is `work/specs/tasked/ → work/specs/ready/` (reopen-to-ready, mirroring `tasks/done/ → tasks/ready/`).
|
|
102
|
+
|
|
103
|
+
**The tasking HOLD is a per-item lock, NOT a `work/tasking/` folder.** Tasking a spec acquires the unified per-item lock with `action: task` on `refs/dorfl/lock/spec-<slug>` — a create-only ref push that is self-arbitrating (winner creates it; a concurrent tasker loses the same CAS definitively, no retry budget), so a spec is never double-tasked. The spec body STAYS in `work/specs/ready/` while held (it does not move to a `tasking/` folder). On a **successful tasking** the release performs the durable `work/specs/ready/ → work/specs/tasked/` move on `main` in the SAME runner-owned commit that emits the `tasks/` items, then releases the lock. On an **aborted / unclear** tasking the lock is released with no `main` move (the spec already rests in `specs/ready/`), or the lock is marked `stuck` for a human.
|
|
104
|
+
|
|
105
|
+
- **Tasked-ness is RESIDENCE in `work/specs/tasked/` — the FOLDER, the SOLE signal.** There is no `tasked:` frontmatter marker; the folder is canonical. A spec whose lock is held `action: task` is _being tasked right now_; a spec in `specs/tasked/` _has been tasked_; a spec in `specs/ready/` is _to-task_.
|
|
106
|
+
- **Edit a spec when its tasking-lock is NOT held.** While the tasking lock is held the spec is mid-tasking; edit it before tasking starts or after it lands (in `specs/ready/` or `specs/tasked/`), not while the lock is held. (A human on a stale local checkout won't see the durable `git mv` until they fetch — the protocol guarantees no _silent corruption_, not no _human surprise_.)
|
|
107
|
+
- **Release fails loud on a concurrent edit (never a silent stale tasking).** If the held spec body was edited while the lock was held, the release detects it (the held content no longer matches the snapshot the lock took) and FAILS LOUD: the tasking is stale → re-task from the edited spec or mark the lock stuck. The release NEVER force-restores over the edit or emits tasks cut from a stale snapshot.
|
|
108
|
+
- **The human path needs no lock.** A human tasking locally with no agent running has no contention and may task on `main` directly — the lock is mandatory for the agent, optional for the human (parallel to "the runner never skips verify; the human may").
|
|
109
|
+
|
|
110
|
+
### Land = rebase + re-verify + advance (the durable-move invariant)
|
|
111
|
+
|
|
112
|
+
Every durable `main` move (a task's `tasks/ready → tasks/done`, a spec's `specs/ready → specs/tasked`, a `tasks/ready → tasks/cancelled`) is a LAND, and every land is the same mode-agnostic primitive: **fetch current `main` → rebase the work onto it → re-run `verify` (and review) on the rebased tree → advance.** A lost CAS / moved-`main` between gate and push INVALIDATES any prior green and re-arms the gate (re-rebase, re-`verify`, retry — never a `--force`, never an auto-resolved conflict). Merge mode runs it inline at the serialised land; propose mode runs it at the human checkpoint. Human review is ADDITIVE (intent/design/security), NEVER a substitute for the re-verify on the rebased tree. The durable _why_ — a clean `git` merge validates the AUTHORED context, never the LIVED context — lives in ADR `land-primitive-rebase-reverify-advance`.
|
|
113
|
+
|
|
114
|
+
### `needs-attention` — the post-claim "couldn't finish" state (surfaced on `main` as `needsAnswers:true` + a `stuck`-kind sidecar)
|
|
115
|
+
|
|
116
|
+
An item that was claimed and _attempted_ but could not complete is SURFACED ON `main` as a question sidecar instead of reaching `tasks/done/`. This is the single home for every "couldn't finish, a human must look" outcome — a failed acceptance gate (red tests), a rebase/merge conflict, a task the agent found too ambiguous to build, a timeout, or a rejected review. It is NOT a `main` folder move of the item, and (post `retire-stuck-lock-state`, spec `surface-stuck-as-questions-and-retire-stuck-lock-state`) it is NOT a lock state either. The bounce is ONE crash-safe transition: write / update `work/questions/<type>-<slug>.md` (a `stuck`-kind sidecar) with the reason (+ any agent-surfaced questions), set `needsAnswers: true` on the item body on `main`, then RELEASE the lock. The item body never moves (it rests in `tasks/ready/`, since claim does not relocate it). See the 2026-07-14 addendum to ADR `ledger-status-on-per-item-lock-refs`.
|
|
117
|
+
|
|
118
|
+
- **Who marks it:** the runner/human that owns the lock + `main` transitions — NOT the build agent (which never touches the lock ref or `main`). On a bounced job the runner writes the sidecar + flips `needsAnswers`, RELEASES the lock, and SAVES the recoverable work as a wip commit on the kept `work/<type>-<slug>` branch (pushed to the arbiter so it travels cross-machine).
|
|
119
|
+
- **Not claimable:** a `needsAnswers:true` item is `eligible:false` by construction, so it is not auto-picked; it IS visible — in `ls work/questions/`, in `git clone`, and in `dorfl status`/`scan`. `tasks/done/` on `main` and a `needsAnswers:true` `tasks/ready/` body cannot legitimately co-exist for the same slug (a bounce releases the lock cleanly; the two-substrate `done + stuck` co-existence case the pre-retirement lock had is gone).
|
|
120
|
+
- **Resolve / return path:** a human ANSWERS the sidecar; the existing apply rung drains the answer — `resolve` (continue: clear `needsAnswers` + delete the sidecar; the `work/<type>-<slug>` branch is left UNTOUCHED so the next claim continues from its tip), `resolve` + `resolveReset` (reset: delete the `work/<type>-<slug>` branch, then continue), or `dispose` (regime-polymorphic terminal: task → `git mv tasks/cancelled/`, spec → `git mv specs/dropped/`, observation → `git rm`). The direct human verbs `resume` / `requeue <slug>` / `release-lock` still work; they now target an in-flight or crash-orphan `active` lock, not a `stuck` lock. A crash-orphaned lock (the only class that can outlive a leg) is nameable and clearable via `release-lock <item>` (+ an orphan-lock report in `gc --ledger`); a lock whose entry name is NOT derivable from any current item-form (a pre-vocabulary-cutover `slice-<slug>` / `prd-<slug>` prefix, or a future rename) is cleared via `release-lock --entry <literal>`, which `gc --ledger` surfaces the exact invocation for — no raw `git push origin --delete refs/dorfl/lock/…` needed. There is no liveness heartbeat and no auto-sweep (a human asserts a lock is dead).
|
|
121
|
+
- This is a _post-claim_ state. (A separate _pre-claim_ "not ready" state is the STAGING folder `tasks/backlog/` — the position gate — not this.)
|
|
122
|
+
- **Branch self-conflicts cannot occur by construction.** Because NO transient status lands on `main` (a bounce is a lock amend, not a `git mv`), a work branch cut from `main` inherits no `needs-attention`/`tasking`/`advancing` markers, so a continue/rebase is a PLAIN rebase with nothing to drop. A genuine content conflict between two real lines of development still aborts → the item is marked stuck.
|
|
123
|
+
|
|
124
|
+
### Drift is a needs-attention signal (check the doc against reality first)
|
|
125
|
+
|
|
126
|
+
A spec and a task are **launch snapshots** — they capture intent at creation and are deliberately NOT kept in sync (current truth lives in `docs/adr/` + the code in `tasks/done/`). So by the time you act on one, it MAY have **drifted**: a dependency landed differently than the doc assumed, an ADR superseded a decision the doc relies on, a sibling task changed the seam it builds against.
|
|
127
|
+
|
|
128
|
+
**Discipline (applies whenever you investigate / task / claim / build):** before acting, **check the doc against reality** — the code in `tasks/done/`, the relevant ADRs, and sibling tasks it depends on. If you find a discrepancy that would make you build/task against a false premise, that is a **needs-attention candidate — do NOT silently proceed on the stale spec.** Route it per the item's kind:
|
|
129
|
+
|
|
130
|
+
- **A TASK that contradicts current reality** → route to needs-attention (surface a `work/questions/task-<slug>.md` sidecar + set `needsAnswers: true` on the task body + release the lock) with the discrepancy as the reason (the same mechanism as a red gate), rather than building on a stale assumption. A human answers the sidecar; the apply rung dispatches (`resolve` → continue, `dispose` → `tasks/cancelled/`). The body already rests in `tasks/ready/`. (Building on a stale task produces wrong-but-compiling work — the worst outcome.)
|
|
131
|
+
- **A SPEC that has drifted** (before tasking) → do NOT task it as-is. Set `needsAnswers: true` on the spec with the discrepancy in its body (or, if it is a small factual correction you are certain of, fix the spec first), so the tasker never emits tasks from a stale spec. A human reconciles, clears the flag, then it is tasked.
|
|
132
|
+
- **A SPEC that has drifted AFTER it was TASKED** (a mechanism it assumed got retired, a sibling decision superseded it) → do **NOT** move it back to `specs/proposed/`. `specs/proposed/` is the untrusted-admission STAGING position; moving an already-tasked spec there falsely un-records a tasking that really happened and ORPHANS the tasks it already emitted (they still sit in `tasks/backlog`/`tasks/ready` carrying `spec:`/`covers:` linkage to a spec that now claims it was never tasked). Tasked-ness is RESIDENCE in `specs/tasked/` and must never be silently rewound. Instead, two honest mechanisms (use the lighter one that fits):
|
|
133
|
+
- **Annotate in place (drifted, not yet re-decomposed).** Set `needsAnswers: true` on the spec **while it stays in `specs/tasked/`**, with the drift + what must be re-decomposed in its body, AND set `needsAnswers: true` on every emitted task that is now premised on the dead mechanism so no agent builds it. `needsAnswers: true` on a `specs/tasked/` spec is legal and means exactly _"tasked, but the spec has drifted — do not RE-task or rely on it until reconciled."_ The non-drifted emitted tasks are unaffected and stay promotable.
|
|
134
|
+
- **Reopen to re-decompose (the sanctioned move).** When you are ready to re-task from the reconciled spec, use the existing reopen path `specs/tasked/ → specs/ready/` (mirroring `tasks/done/ → tasks/ready/`), reconcile the spec, clear `needsAnswers`, then re-task — which emits corrected tasks. Supersede the stale emitted tasks into `tasks/cancelled/` (reason: superseded by the re-task). There is NO `specs/tasked/ → specs/proposed/` transition.
|
|
135
|
+
|
|
136
|
+
The rule is symmetric: _a discrepancy between a doc and reality is not something to paper over — it is exactly the "a human must look" signal `needs-attention` (tasks) / `needsAnswers` (specs) exists to carry._ Cheap to honour, and it stops drift from silently propagating into built work.
|
|
137
|
+
|
|
138
|
+
## Conflict-safety rules (non-negotiable)
|
|
139
|
+
|
|
140
|
+
1. **One file per item.** Never put two work items in one file. Disjoint files merge trivially.
|
|
141
|
+
2. **No shared index / manifest.** Do not maintain a `work/INDEX.md`, `work/list.json`, or any file every item touches — it is a guaranteed conflict point. Derive lists on demand with `ls work/tasks/ready/` / `grep`. (A hand-maintained index just goes stale.)
|
|
142
|
+
3. **An empty lifecycle folder is OPTIONAL — absence means "empty", never "broken".** The folders in the layout above (`tasks/backlog`/`ready`/`done`/`cancelled`, `specs/proposed`/`ready`/`tasked`/`dropped`, the `notes/*` buckets) describe the POSITIONS an item MAY rest in, not directories that must all exist at rest. Git does not track empty directories, so a position with no items in it simply has no folder on disk, and a reader/conductor MUST treat a missing lifecycle folder as the empty set (e.g. no `specs/proposed/` ⇒ "nothing awaiting promotion"), NOT as a misconfigured tree. A folder is CREATED implicitly the first time an item lands in it (the `git mv`/write that places the item), and may VANISH again when its last item leaves. So: never fail, warn, or auto-create-as-a-fixup on a missing lifecycle folder; derive each position's contents on demand (rule 2) and let an empty position be a no-op. (`setup` may scaffold a starter set for ergonomics, but the contract does not REQUIRE their continued existence — emptiness and absence are the same state.)
|
|
143
|
+
4. **Status = location, not a field.** See above.
|
|
144
|
+
5. **Content-derived slugs, never counters.** Use a URL-safe slug from the title (e.g. "Historical store schema" → `historical-store-schema`). NO monotonic integer IDs — two agents would both grab "next = 43". A short hash or date prefix is fine if disambiguation is needed (`historical-store-schema` or `2026-06-03-historical-store-schema`).
|
|
145
|
+
6. **Dependencies by slug, read-only.** `blockedBy: [other-slug]` references other items; an item never writes another item's file. The blocker owns its own status (its folder).
|
|
146
|
+
7. **Claim state is the per-item LOCK, never a frontmatter field (and not a folder move).** Claiming an item acquires its per-item lock (`refs/dorfl/lock/<type>-<slug>`, `action: implement`) — a create-only ref push that is self-arbitrating (the loser is definitively told "lost", no retry budget); the body STAYS in `tasks/ready/` (claim writes nothing to `main`, so an agent can claim even on a protected `main`). The holder/since ride the lock entry; `git` (the ref + its parentless commit) holds the authoritative record. There is NO `claimed_by` / `claimed_at` frontmatter, and no `git mv` into an `in-progress/` folder — the claimable predicate is "in the pool `tasks/ready/` on `main` AND no lock held on its ref".
|
|
147
|
+
8. **An item MAY carry a co-located `<slug>/` asset sidecar folder.** The item is ALWAYS the `<slug>.md` file (that is its identity and the only thing scanned). When an item needs companion resources — a `.patch`, a mockup image, a diagram, a sample payload — put them in a sibling folder of the SAME slug, `<umbrella>/<slug>/` (e.g. `notes/ideas/my-idea.md` + `notes/ideas/my-idea/fix.patch`). This is safe and disturbs NOTHING because every scanner lists a bucket by `isWorkItemFile` (= name ends in `.md`), so a sidecar folder is silently skipped — it is never mistaken for an item, and `(umbrella, slug)` addressing is unchanged. Rules: the sidecar is OPTIONAL and most items have none; it is OWNED by its `<slug>.md` (the markdown references its assets by relative path, e.g. `[the patch](<slug>/fix.patch)`); it shares the item's lifecycle (when the item is deleted, delete its sidecar too — a note leaves by deletion, and an orphaned sidecar is litter); and it is NOT a second item, so never put another item's `<slug>.md` inside it (that would hide it from scanning). This applies to ANY bucket (`notes/*`, `tasks/*`, `specs/*`), not just ideas, though ideas are the common case. It does NOT violate rule 1 (one file per ITEM) or rule 2 (no shared index) — the sidecar holds an item's OWN assets, not a manifest over many items.
|
|
148
|
+
|
|
149
|
+
## Task quality rule — tests must not touch the real environment
|
|
150
|
+
|
|
151
|
+
A task that makes code **write to a SHARED / GLOBAL location** — a real home/config dir, a system path, a shared service, or an **external tool's managed store** (e.g. another agent's session directory) — MUST, as an acceptance criterion, have its **tests ISOLATE that location** (point it at a temp/scratch dir via the relevant env var or config knob) **AND assert the real one is UNTOUCHED after the run**. State the _mechanism_, not just the outcome: name the env/config lever and note WHERE the path is resolved (in-process vs in a child), because that determines whether overriding a child's env is enough or the test process's own `process.env` must be set.
|
|
152
|
+
|
|
153
|
+
This is the generalisation of the git-config isolation tests already do (`GIT_CONFIG_GLOBAL=/dev/null`): the same discipline for ANY shared write target. A task that _moves_ a write into a shared location (e.g. "write sessions to the tool's default dir instead of the worktree") silently turns previously-isolated tests into ones that pollute — and a malformed fixture in a shared store can crash unrelated tools that read it. Corollary: a synthetic fixture written into any store an external tool reads MUST be VALID per that tool's contract (capture the contract as a `notes/findings/` doc).
|
|
154
|
+
|
|
155
|
+
## Field-naming convention
|
|
156
|
+
|
|
157
|
+
All frontmatter and config field names are **camelCase** (`humanOnly`, `needsAnswers`, `blockedBy`, `taskedAfter`, `autoBuild`) — matching the JSON config and the TypeScript that parses them (1:1 property mapping, no snake↔camel translation layer). No exceptions.
|
|
158
|
+
|
|
159
|
+
## Frontmatter (YAML)
|
|
160
|
+
|
|
161
|
+
### Task frontmatter
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
---
|
|
165
|
+
title: Human Readable Title
|
|
166
|
+
slug: historical-store-schema
|
|
167
|
+
spec: historical-store # slug of the work/specs/ready/<slug>.md this task derives from. REQUIRED iff `covers` is set; OMIT for a self-contained chore/refactor (covers: []).
|
|
168
|
+
humanOnly: true # gate axis 1 (DECIDED): a human must drive this. true | omitted. MOST OMIT IT.
|
|
169
|
+
needsAnswers: true # gate axis 2 (DISCOVERED): open questions block autonomous work. true | omitted.
|
|
170
|
+
blockedBy: [] # list of slugs that must reach tasks/done/ first; [] = startable now
|
|
171
|
+
covers: [] # optional: user-story numbers (within `spec`) this task covers
|
|
172
|
+
promptGuidance.testFirst: true # optional per-item NUDGE override: pin the test-first nudge ON or OFF for THIS task, regardless of the repo's resolved `promptGuidance.testFirst` policy. true | false | omitted (= inherit spec, else repo policy). NEVER an acceptance criterion — `verify` still decides pass/fail. See "`promptGuidance.*` per-item override" below.
|
|
173
|
+
---
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Spec frontmatter
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
---
|
|
180
|
+
title: Human Readable Title
|
|
181
|
+
slug: historical-store
|
|
182
|
+
issue: 123 # optional: the issue this spec was spawned from (the surviving thread)
|
|
183
|
+
humanOnly: true # optional: a human must drive the TASKING of this spec. true | omitted.
|
|
184
|
+
needsAnswers: true # optional: open questions block AUTO-tasking this spec. true | omitted.
|
|
185
|
+
taskedAfter: [] # optional: spec slugs that must be TASKED first (see below). [] = taskable now.
|
|
186
|
+
promptGuidance.testFirst: true # optional per-item NUDGE override: pin the test-first nudge ON or OFF for every task this spec fans out, regardless of the repo's resolved policy. A per-task override still wins over this. true | false | omitted (= inherit repo policy). See "`promptGuidance.*` per-item override" below.
|
|
187
|
+
# tasked-ness has NO frontmatter marker: it is RESIDENCE in work/specs/tasked/ (the release transition moves the spec there).
|
|
188
|
+
---
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Finding frontmatter
|
|
192
|
+
|
|
193
|
+
A finding (`work/notes/findings/<slug>.md`) is a capture-bucket note (no status flow), but it MUST declare its **provenance** so it stays correctable (see the findings box above):
|
|
194
|
+
|
|
195
|
+
```yaml
|
|
196
|
+
---
|
|
197
|
+
title: Human Readable Title
|
|
198
|
+
slug: external-api-behaviour
|
|
199
|
+
source: 'derived from src/<the-integrating-module> @ <commit>' # REQUIRED: what the source is AND how current (a date for external sources). Be specific & honest — there is NO separate confidence field; the source string carries the weight.
|
|
200
|
+
---
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
- `source` is **required** — a finding without it is a `notes/observations/` signal, not a finding. State it specifically (a file+commit, a doc URL, a captured trace), so a later "the source was wrong" can revise the finding traceably.
|
|
204
|
+
- A **code-derived** finding describes the _external behaviour our code assumes_, never our code's internal architecture (that is `CONTEXT.md` / a `docs/` overview). If you find yourself describing our own package layout, it is not a finding.
|
|
205
|
+
|
|
206
|
+
### The two autonomy axes: `humanOnly` (decided) × `needsAnswers` (discovered)
|
|
207
|
+
|
|
208
|
+
The autonomy gate is TWO orthogonal binary fields (both default to omitted = false), present on BOTH tasks and specs, plus the repo's `autoBuild` policy:
|
|
209
|
+
|
|
210
|
+
- **`humanOnly: true` — the DECIDED axis.** _Should a human drive this, regardless of how complete the spec is?_ A product/design/security/judgement call, or an `AGENTS.md`-type rule. Driven by a decision (in the spec conversation, or the tasker's own judgement). On a SPEC it means "a human must drive the tasking". On a TASK it is the NARROW "never-for-agents BY NATURE" guard (secrets/release/security) that **survives even when the task resides in the agent pool `work/tasks/ready/`**. Task `humanOnly` is NOT the tool for ordinary "a human should review this before the agent builds it" — that job belongs to POSITION (the runner births the task STAGED in `work/tasks/backlog/`; a human promotes the approved ones into the pool `work/tasks/ready/`). See "Task `humanOnly` is NARROW" below.
|
|
211
|
+
- **`needsAnswers: true` — the DISCOVERED axis.** _Are there unresolved questions blocking autonomous progress?_ The spec is incomplete; **the open questions live in the body**. Once answered, the flag is cleared and an agent may proceed.
|
|
212
|
+
- They are **orthogonal** — four honest states. e.g. `humanOnly:true, needsAnswers:false` = fully specified but a human must own it; `humanOnly:false, needsAnswers:true` = anyone can do it once the questions are answered.
|
|
213
|
+
- **Repo policy `autoBuild`** answers the question the _repo_ owns: _may agents auto-build undeclared items here?_ The build member of the symmetric per-action gate family (`autoBuild`/`autoTask`/`observationTriage`). Per-repo config key (`.dorfl.json`), resolved like `integration`: **CLI flag (`--auto-build` / `--no-auto-build`) > env (`DORFL_AUTO_BUILD`) > per-repo config > global config > built-in default (`false`)**.
|
|
214
|
+
|
|
215
|
+
**Predicate (same shape at both levels):** an item is **auto-eligible** iff `needsAnswers` is not `true` AND `humanOnly` is not `true` AND `autoBuild` is `true`. A human is never bound by it (a human may task/build a flagged item — the gate binds the agent, like the runner-vs-human stance on `verify`).
|
|
216
|
+
|
|
217
|
+
### `promptGuidance.*` per-item override (the same precedence shape as `humanOnly`/`autoBuild`)
|
|
218
|
+
|
|
219
|
+
The `promptGuidance` NAMESPACE is a per-repo + per-item layer of PROMPT-TEXT NUDGES the runner folds into the worker's in-band prompt (currently one member, `testFirst`; the namespace is designed to grow). It is CATEGORICALLY SEPARATE from the gate family (`verify`/`autoBuild`/`humanOnly`): a nudge changes the agent's DISPOSITION, never the acceptance bar — the `verify` gate still decides pass/fail regardless of any value here.
|
|
220
|
+
|
|
221
|
+
The repo policy resolves like every other gate-family field: **CLI flag > env (`DORFL_PROMPT_GUIDANCE_TEST_FIRST`) > per-repo config > global config > built-in default (`false`)**. On top of THAT, a single task or spec may OVERRIDE the resolved repo policy for THAT item only by setting `promptGuidance.<member>: true | false` in its frontmatter — the same repo-default-plus-item-override shape `humanOnly`/`autoBuild` use. The per-item precedence chain (highest → lowest) is:
|
|
222
|
+
|
|
223
|
+
1. **Per-task frontmatter** — the task's own `promptGuidance.<member>` line (when present).
|
|
224
|
+
2. **Per-spec frontmatter** — the spec's `promptGuidance.<member>` line, consulted ONLY when the task carries a `spec:` and the spec file is found in `work/specs/ready/` or `work/specs/tasked/`.
|
|
225
|
+
3. **Repo-resolved policy** — the value the chain above resolves to, with the built-in default `false`.
|
|
226
|
+
|
|
227
|
+
Each nudge member resolves INDEPENDENTLY — a task's `promptGuidance.testFirst` override never bleeds into a sibling member. A task with no `spec:` (a self-contained chore) MAY still carry the override; the spec layer is simply absent and the chain reads task ⇒ repo. A missing spec file is NOT an error: the override is OPTIONAL by design, so the chain silently falls through to the repo policy. Form: the frontmatter parser reads the DOTTED scalar form `promptGuidance.<member>: <bool>` (a single line, mirroring the flat shape `humanOnly`/`needsAnswers` use at the item level); a mistyped value (e.g. `"yes"`) reads as undefined — the same silent-on-malformed behaviour `humanOnly` has — never a silent coerce.
|
|
228
|
+
|
|
229
|
+
Authority: a per-item override binds the AGENT exactly like the gate-family overrides do. A human may always ignore it on a manual run (the prompt is generated; the human decides what to type).
|
|
230
|
+
|
|
231
|
+
### Task `humanOnly` is NARROW — POSITION carries "review-first"; `humanOnly` carries "never-by-nature"
|
|
232
|
+
|
|
233
|
+
Three orthogonal axes, each meaning EXACTLY one thing:
|
|
234
|
+
|
|
235
|
+
- **POSITION (folder, runner-deterministic, STRUCTURAL).** Whether a task is in the agent POOL (`work/tasks/ready/`) or in STAGING (`work/tasks/backlog/`) is computed by the runner from unforgeable inputs (the `originTrust` stamp, the per-repo placement policy, explicit operator flags). "A human should review this before an agent acts on it" is encoded HERE — the task is BIRTHED in `work/tasks/backlog/` (not eligible) and a human promotes the approved ones into `work/tasks/ready/`. The agent CREATES only in the staging folder; the runner OWNS every move + promotion.
|
|
236
|
+
- **Staging is review-first admission AND the human-control position — the same folder carries BOTH.** A staging folder (`work/tasks/backlog/`, `work/specs/proposed/`) is not just "not-yet-reviewed"; it is also where an item rests so a HUMAN can drive it WITHOUT an autonomous claimer competing. Promoting an item into the POOL (`work/tasks/ready/`, `work/specs/ready/`) is EXACTLY what makes it claimable-by-anyone: the moment it lands in the pool, an autonomous claimer can grab it — a CI `advance` leg or a local `run` daemon (both are pool-only by construction). So **promote-then-drive opens a COMPETITION WINDOW** (the autonomous claimer races the human who meant to drive the work). The safe path is the inverse: a human who wants to drive an item themselves DRIVES IT IN PLACE from staging, and promotes only when (if ever) they want to hand it to the pool. The two drive-in-place mechanisms: a SPEC is **tasked in place** from `work/specs/proposed/` (TASKING-PROTOCOL.md §6), and a task is **built in place** from `work/tasks/backlog/` via `do --allow-backlog`. "I want to drive this myself" therefore means "drive it in place", never "promote, then race to claim it first".
|
|
237
|
+
- **NATURE (`humanOnly`, agent/human judgement, ADVISORY).** Task `humanOnly: true` means "an agent must NEVER AUTONOMOUSLY take this BY NATURE" — the rare hard case (release/secrets/security/AGENTS.md-rule) that **survives even when the task resides in the pool `work/tasks/ready/`**. The autonomy gate predicate above is exactly this: a `humanOnly: true` task is never AUTONOMOUSLY claimed (it drops out of `run`/`advance`/auto-pick selection and the conductor's READY set), even from `work/tasks/ready/`. It is NOT, however, unbuildable: an EXPLICIT human-driven `dorfl do task:<slug>` (or `claim`) STILL builds it — the readiness guard does not consult `humanOnly` on the human path (a human is never bound by `humanOnly`; it means "a human must DRIVE this"), and explicit dispatch gates on the item's own readiness, not the autonomy policy (the pool gates the policy, not the explicit claim). So the invariant is precise: `humanOnly` gates AUTONOMOUS SELECTION, never an explicit human action. Spec `humanOnly` gates auto-tasking; no folder substitute, because the tasker's input is a single spec — it must be flagged in-band.
|
|
238
|
+
- As a corollary, `humanOnly` CAN be used off-label as a "keep CI/`run`/auto-pick OFF this task while I drive it by hand" latch (it excludes the task from every autonomous claimer, while explicit `do task:<slug>` still builds it). PREFER POSITION (leave it in staging `work/tasks/backlog/`) for that intent; reserve the flag for the genuine never-by-nature case. If you do use it as a latch, strip it once the task lands so it does not falsely mark the done record never-by-nature.
|
|
239
|
+
- **DISCOVERED (`needsAnswers`, agent judgement, ADVISORY).** Open questions block autonomous work.
|
|
240
|
+
|
|
241
|
+
Consequences for the tasker heuristic (the `to-task` skill / the tasker review loop):
|
|
242
|
+
|
|
243
|
+
- For the COMMON "a human should review this task first" case, the tasker does NOT stamp `humanOnly: true` — it lets the runner birth the task STAGED in `work/tasks/backlog/` (the position carries the review-first signal).
|
|
244
|
+
- The tasker flags `humanOnly: true` on a task ONLY when building THAT task is genuinely never-for-agents-by-nature (release pipeline, secrets handling, hard security boundaries, AGENTS.md prohibitions). If in doubt, leave `humanOnly` off and rely on the position — a human can always refuse to promote.
|
|
245
|
+
|
|
246
|
+
### Three honest integration modes for tasker output (`do spec:<slug>`)
|
|
247
|
+
|
|
248
|
+
The tasker-output integration combines `--propose`/`--merge` with the `tasksLandIn` placement default into three explicit, named modes:
|
|
249
|
+
|
|
250
|
+
| Mode | How to invoke | What lands where | When to use |
|
|
251
|
+
| --- | --- | --- | --- |
|
|
252
|
+
| **`--propose`** (PR path) | `do spec:<slug> --propose` (or the configured default) | A work branch pushed; a PR opened against `main`. Tasks land in the PR's tree (typically `work/tasks/backlog/`); review is the PR diff. | A repo with a host (GitHub, …) and a PR-based review culture. Code/implementation review ALWAYS uses this path — a diff cannot be folder-gated. |
|
|
253
|
+
| **`--merge` + land-in-staging** (PR-free review) | `do spec:<slug> --merge` with `tasksLandIn: pre-backlog` (or `--tasks-land-in pre-backlog`) | Tasks land DURABLY on `main` under `work/tasks/backlog/` (the staging folder, NOT eligible). A human promotes the approved ones `work/tasks/backlog/ → work/tasks/ready/`. | A bare / no-host / protected-`main` repo that still wants human review of ledger-file output. Review is a LEDGER POSITION a human moves, not an out-of-band PR. |
|
|
254
|
+
| **`--merge` + land-in-pool** (trusted no-review fast path) | `do spec:<slug> --merge` with `tasksLandIn: ready` (or `--tasks-land-in ready`) and a trusted origin | Tasks land on `main` directly in the agent POOL `work/tasks/ready/` — immediately eligible for `do` / auto-pick. | A trusted, fast-iteration repo where the tasker's output is trusted to enter the pool without ledger-position review. The runner-deterministic placement precedence still forces STAGING for an untrusted origin. |
|
|
255
|
+
|
|
256
|
+
Key rules:
|
|
257
|
+
|
|
258
|
+
- **Placement is runner-deterministic.** WHICH folder a task lands in is the runner's CALL from the `originTrust` stamp + `tasksLandIn` config + an explicit `--tasks-land-in` flag (precedence: explicit-flag > untrusted-forces-staging > configured default > built-in staging). The agent never sets it. (`tasksLandIn` names the TASK-side pool/staging slots, `ready`/`pre-backlog`.)
|
|
259
|
+
- **Code/implementation review is on the branch/PR path** — a code diff cannot be folder-gated. The position gate above is SCOPED to LEDGER-FILE output (tasking); the branch-based build review is unaffected.
|
|
260
|
+
- **`humanOnly` survives every mode.** A `humanOnly: true` task in the pool is still not agent-claimable — the position gate and the `humanOnly` gate are orthogonal.
|
|
261
|
+
|
|
262
|
+
### `taskedAfter` — spec tasking-order (enforced against `work/specs/tasked/`, NOT `tasks/done/`)
|
|
263
|
+
|
|
264
|
+
`taskedAfter: [other-spec]` on a spec is **distinct from** task `blockedBy`, and named differently because it gates a different verb against a different signal:
|
|
265
|
+
|
|
266
|
+
- **task `blockedBy`** gates **building** a task, resolved against `tasks/done/`.
|
|
267
|
+
- **spec `taskedAfter`** gates **tasking** a spec, resolved against `work/specs/tasked/` residence (i.e. the listed specs must already be tasked — reside in `work/specs/tasked/` — so this spec's emitted tasks can reference the real slugs of those specs' tasks in their `blockedBy`). This mirrors `blockedBy` → `tasks/done/` exactly: ordering resolves against folder residence, not a frontmatter marker.
|
|
268
|
+
|
|
269
|
+
It waits on **tasked-ness (`work/specs/tasked/`), not `tasks/done/`** on purpose: the reason B waits for A is that B's tasks need A's slugs to _exist_, which happens the moment A is tasked — not when A is fully built. Build-ordering between A's and B's actual work is then expressed where it belongs, in B's individual tasks' `blockedBy` (against `tasks/done/`). Enforced for the auto-tasker (it skips a spec whose `taskedAfter` specs do not yet reside in `work/specs/tasked/`); a human may task anyway.
|
|
270
|
+
|
|
271
|
+
### The `spec` link (required _when `covers` is set_)
|
|
272
|
+
|
|
273
|
+
`spec` names the source document this task was tasked from — the slug of a `work/specs/ready/<slug>.md` in the same repo. Its load-bearing job is to make `covers` unambiguous: `covers: [4]` means nothing without knowing _which_ spec's story 4. So the requirement tracks that job:
|
|
274
|
+
|
|
275
|
+
- **`spec` is REQUIRED iff `covers` is non-empty.** Any task that points into spec user stories MUST name the spec those numbers belong to (a task spanning multiple specs names its primary one in `spec` and references the others in prose).
|
|
276
|
+
- **`spec` MAY be omitted for a self-contained task** — a refactor, chore, build fix, or dependency bump that derives from no spec and covers no user stories (`covers: []`). Such a task MUST instead carry a clear, standalone _What to build_ + _Prompt_ (it is its own source of truth). This is **in contract** — not all work is feature work; only _feature_ work flows from a spec.
|
|
277
|
+
|
|
278
|
+
(Consequence, by design: a spec-less chore task is part of no spec's completion set — the "spec complete?" query counts only `spec:<slug>` tasks — which is correct, since a chore is not part of any feature's traceability.)
|
|
279
|
+
|
|
280
|
+
The body uses [task-template.md](task-template.md): What to build (end-to-end), Acceptance criteria (checkboxes), Blocked by (prose mirror of frontmatter), and a **Prompt** section — a self-contained instruction block that can be pasted into a fresh agent context, so an agent needs nothing but the file to start.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: <Human Readable Title>
|
|
3
|
+
slug: <url-safe-slug>
|
|
4
|
+
# issue: 123 # optional: the issue this spec was spawned from (the surviving thread)
|
|
5
|
+
# humanOnly: true # optional: a HUMAN must drive the tasking of this spec (a decision). OMIT otherwise.
|
|
6
|
+
# needsAnswers: true # optional: open questions block AUTO-tasking (spec incomplete). OMIT otherwise. List the questions in the body.
|
|
7
|
+
# taskedAfter: [] # optional: spec slugs that must be TASKED first (so this spec's tasks can reference their slugs in blockedBy).
|
|
8
|
+
# promptGuidance.testFirst: true # optional per-item NUDGE override: pin the test-first nudge ON (true) or OFF (false) for every task this spec fans out, regardless of the repo's resolved policy. A per-task override still wins over this. OMIT to inherit the repo policy. NEVER an acceptance criterion — `verify` still decides pass/fail.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
> Launch snapshot — records intent at creation, NOT maintained. Current truth: `docs/adr/` (decisions) + the code; remaining work: `work/tasks/ready/` tasks. (The technical-detail sections below are trimmed by `to-task` once the work is tasked — they move into tasks/ADRs and this spec settles to its durable framing: Problem / Solution / User Stories / Out of Scope.)
|
|
12
|
+
|
|
13
|
+
<!-- open-questions -->
|
|
14
|
+
<!--
|
|
15
|
+
TRANSIENT BLOCK — stripped by the apply rung on full resolution.
|
|
16
|
+
While the spec has unresolved questions blocking autonomous tasking:
|
|
17
|
+
1. Set `needsAnswers: true` in the frontmatter above.
|
|
18
|
+
2. List the questions under the `## Open questions` heading below.
|
|
19
|
+
3. Clear the flag (and let apply strip this block) once they are answered.
|
|
20
|
+
Delete the whole fenced block — markers and all — if the spec launches fully resolved.
|
|
21
|
+
-->
|
|
22
|
+
|
|
23
|
+
## Open questions
|
|
24
|
+
|
|
25
|
+
1. <question one>
|
|
26
|
+
2. <question two>
|
|
27
|
+
|
|
28
|
+
<!-- /open-questions -->
|
|
29
|
+
|
|
30
|
+
## Problem Statement
|
|
31
|
+
|
|
32
|
+
The problem the user faces, from the user's perspective.
|
|
33
|
+
|
|
34
|
+
## Solution
|
|
35
|
+
|
|
36
|
+
The solution, from the user's perspective.
|
|
37
|
+
|
|
38
|
+
## User Stories
|
|
39
|
+
|
|
40
|
+
A LONG, numbered list — the heart of the spec. Format:
|
|
41
|
+
|
|
42
|
+
1. As a <actor>, I want <feature>, so that <benefit>.
|
|
43
|
+
|
|
44
|
+
Cover all aspects of the feature, extensively.
|
|
45
|
+
|
|
46
|
+
### Autonomy notes (the two gate axes — set the frontmatter flags accordingly)
|
|
47
|
+
|
|
48
|
+
The spec now CARRIES the tasking gate (because an agent may auto-task it with no human in the loop). Record, in prose here AND as the frontmatter flags above:
|
|
49
|
+
|
|
50
|
+
- **`humanOnly` (DECIDED):** set `humanOnly: true` on the spec ONLY to mean "a human must drive the _tasking_ of this spec" (sole effect: an agent may not auto-task it). This is DISJOINT from task `humanOnly` — it does NOT propagate to or guide the tasks' gates (a `humanOnly` spec can yield fully agent-buildable tasks). The tasker sets each task's gate from that task's own build-nature.
|
|
51
|
+
- **`needsAnswers` (DISCOVERED):** are there open questions the spec has not yet resolved? If so, fill in the `## Open questions` block at the top of the spec (it carries the authoring instructions and the marker fence the apply rung uses to strip it on resolution) — the auto-tasker will refuse to task until they are answered and the flag cleared. Be HONEST: a flagged-incomplete spec is correct; a falsely-complete one produces wrongly-cut tasks. (Omit both flags if everything is resolved and straightforwardly agent-taskable.)
|
|
52
|
+
|
|
53
|
+
## Implementation Decisions
|
|
54
|
+
|
|
55
|
+
Decisions made at launch (modules to build/modify, interfaces, architectural choices, schema, API contracts, specific interactions). No file paths or code snippets (they go stale) — except a decision-encoding snippet from a prototype (state machine, reducer, schema, type shape), trimmed to the decision-rich part.
|
|
56
|
+
|
|
57
|
+
> Trimmed at tasking-time: this detail moves into the tasks (what to build) and, where it's a durable rationale, into an ADR (`docs/adr/`). It is here only to seed the tasking.
|
|
58
|
+
|
|
59
|
+
## Testing Decisions
|
|
60
|
+
|
|
61
|
+
What makes a good test (external behaviour, not implementation details); which modules/seams will be tested; prior art in the codebase.
|
|
62
|
+
|
|
63
|
+
> Also trimmed at tasking-time (moves into tasks' acceptance criteria / an ADR).
|
|
64
|
+
|
|
65
|
+
## Out of Scope
|
|
66
|
+
|
|
67
|
+
What is deliberately not being done (and, where useful, where it lives instead — e.g. an incubating idea in `work/notes/ideas/`).
|
|
68
|
+
|
|
69
|
+
## Further Notes
|
|
70
|
+
|
|
71
|
+
Anything else worth recording at launch.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: <Human Readable Title>
|
|
3
|
+
slug: <url-safe-slug>
|
|
4
|
+
spec: <source-spec-slug> # slug of the work/specs/ready/<slug>.md this task derives from. REQUIRED iff `covers` is set; OMIT for a self-contained chore/refactor (covers: []).
|
|
5
|
+
# humanOnly: true # gate axis 1 (DECIDED, NARROW): NEVER-for-agents BY NATURE (secrets/release/security). Survives even in the pool work/tasks/ready/. OMIT otherwise — "review this before the agent builds" is the POSITION's job (the task is BIRTHED in work/tasks/backlog/), NOT humanOnly's.
|
|
6
|
+
# needsAnswers: true # gate axis 2 (DISCOVERED): open questions block autonomous work. OMIT otherwise. List them in the body.
|
|
7
|
+
# promptGuidance.testFirst: true # optional per-item NUDGE override: pin the test-first nudge ON (true) or OFF (false) for THIS task, regardless of the repo's resolved policy. OMIT to inherit (spec, else repo). NEVER an acceptance criterion — `verify` still decides pass/fail.
|
|
8
|
+
blockedBy: [] # slugs that must reach work/tasks/done/ first; [] = startable now
|
|
9
|
+
covers: [] # optional: user-story numbers within `spec` this task covers
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<!-- open-questions -->
|
|
13
|
+
<!--
|
|
14
|
+
TRANSIENT BLOCK — stripped by the apply rung on full resolution.
|
|
15
|
+
While the task has unresolved questions blocking autonomous build:
|
|
16
|
+
1. Set `needsAnswers: true` in the frontmatter above.
|
|
17
|
+
2. List the questions under the `## Open questions` heading below.
|
|
18
|
+
3. Clear the flag (and let apply strip this block) once they are answered.
|
|
19
|
+
Delete the whole fenced block — markers and all — if the task launches fully resolved.
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
## Open questions
|
|
23
|
+
|
|
24
|
+
1. <question one>
|
|
25
|
+
2. <question two>
|
|
26
|
+
|
|
27
|
+
<!-- /open-questions -->
|
|
28
|
+
|
|
29
|
+
## What to build
|
|
30
|
+
|
|
31
|
+
A concise description of this vertical task — the end-to-end behaviour (a thin path through every layer: schema → logic → API/UI → tests), NOT a layer-by-layer implementation plan. Avoid specific file paths / code snippets (they go stale).
|
|
32
|
+
|
|
33
|
+
Exception: if a prototype produced a snippet that encodes a decision more precisely than prose (state machine, reducer, schema, type shape), inline just the decision-rich part and note it came from a prototype.
|
|
34
|
+
|
|
35
|
+
## Acceptance criteria
|
|
36
|
+
|
|
37
|
+
- [ ] Criterion 1 (verifiable / demoable on its own)
|
|
38
|
+
- [ ] Criterion 2
|
|
39
|
+
- [ ] Tests cover the new behaviour (mirror the repo's existing test style)
|
|
40
|
+
- [ ] **If this task makes code write to a SHARED / GLOBAL location** (a real home/config dir, a system path, a shared service, an external tool's managed store): tests ISOLATE that location (point it at a temp/scratch dir via the relevant env/config) AND assert the real one is UNTOUCHED after the run. Omit only if the task writes nothing outside its own temp fixtures.
|
|
41
|
+
|
|
42
|
+
## Blocked by
|
|
43
|
+
|
|
44
|
+
- None — can start immediately. (or: list the blocking slugs, mirroring `blockedBy` in the frontmatter.)
|
|
45
|
+
|
|
46
|
+
## Prompt
|
|
47
|
+
|
|
48
|
+
> Self-contained instructions to paste into a fresh agent context. An agent should be able to start from THIS FILE ALONE — no conversation history needed. State the goal, the relevant domain vocabulary, where to look in the codebase (by module/concept, not brittle paths), the seams to test at, and what "done" means. Reference any `work/notes/findings/*.md` or ADRs that constrain the work.
|
|
49
|
+
>
|
|
50
|
+
> FIRST, check this task against current reality (it is a launch snapshot and may have DRIFTED): does it still match the code in `tasks/done/`, the relevant ADRs, and the tasks it depends on? If a dependency landed differently than this task assumes, or an ADR superseded an assumption here, do NOT build on the stale premise — route the task to needs-attention with the discrepancy as the reason (WORK-CONTRACT.md "Drift is a needs-attention signal"). Building on a stale task produces wrong-but-compiling work.
|
|
51
|
+
>
|
|
52
|
+
> RECORD non-obvious in-scope decisions you make while building, DURABLY and LINKED from the done record. When the task did not specify some behaviour and you have to CHOOSE (a new refusal/exit code, a clamp that reaches a second code path, a fail-loud-vs-fail-safe asymmetry, keeping vs collapsing a now-redundant distinction), do not leave the choice silent for a reviewer to reverse-engineer. Surface it so it can be ratified: if it meets the ADR gate (hard to reverse + surprising without context + a real trade-off — see `ADR-FORMAT.md`), write the durable WHY as an ADR in `docs/adr/`; otherwise pick whichever durable home fits best — a module JSDoc at the choice site, an optional `## Decisions` block in the done record / PR description, or a dated observation note under `work/notes/observations/` — and link it from the done record so it is discoverable. An un-recorded in-scope decision is a review FINDING, not a silent default.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Claiming this task
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
# atomically claim it (works with a GitHub remote OR a local --bare remote):
|
|
60
|
+
dorfl claim <slug> --arbiter <remote> # default --arbiter origin
|
|
61
|
+
# then start work on the updated main:
|
|
62
|
+
git fetch <remote> && git switch -c work/<slug> <remote>/main
|
|
63
|
+
# on completion, in the work branch's PR/merge:
|
|
64
|
+
git mv work/tasks/ready/<slug>.md work/tasks/done/<slug>.md
|
|
65
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: surface-questions
|
|
3
|
+
description: "GATHER the open-judgement residue for ONE work/ item and EMIT questions; write nothing (mirrors review). Use as the advance engine's surface-question rung, or human-invoked for the no-runner path. Surface the residue; NEVER invent an answer."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# surface-questions
|
|
7
|
+
|
|
8
|
+
**The surface-questions discipline lives in `work/protocol/SURFACE-PROTOCOL.md`** (the in-band protocol doc every set-up repo carries; the source-of-truth is `skills/setup/protocol/SURFACE-PROTOCOL.md`). This skill is the **human-facing pointer** to that standard — the operator/agent entry point a person reaches for to invoke the discipline interactively. The standard itself (the two laws, the humility aid, what you compose, the emitted-question shape) is stated ONCE in the protocol doc so the autonomous runner and the human caller cannot drift.
|
|
9
|
+
|
|
10
|
+
## How to use
|
|
11
|
+
|
|
12
|
+
1. Read `work/protocol/SURFACE-PROTOCOL.md` in the repo you are working in.
|
|
13
|
+
2. For ONE `work/` item, apply its discipline: gather the open-judgement residue (composed `review` block-findings + the native observation-triage question + the item's pre-existing open questions) and formulate the open questions.
|
|
14
|
+
3. EMIT the `{item, questions:[…]}` shape it specifies; the caller routes/persists it (you write nothing — see "How the caller persists your questions" in the protocol doc).
|
|
15
|
+
|
|
16
|
+
> Why the standard lives in `work/protocol/`: a `surface-questions`-named discipline that the autonomous runner invokes BY NAME must be in-band in every set-up repo, not host-installed. Operator skills (this file) are human-facing and not copied.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: to-spec
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
description: 'Turn the current conversation into a launch spec file in work/specs/ready/ (tasked into tasks by to-task).'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# to-spec
|
|
8
|
+
|
|
9
|
+
Synthesize the current conversation + codebase understanding into a **spec** (the specification for a feature) written to `work/specs/ready/<slug>.md`. Do NOT interview the user — synthesize what you already know.
|
|
10
|
+
|
|
11
|
+
It writes a **file** (no issue tracker), and the spec is a **launch snapshot**, not a maintained document.
|
|
12
|
+
|
|
13
|
+
## A spec here is a LAUNCH SNAPSHOT (not maintained)
|
|
14
|
+
|
|
15
|
+
Write it once, fully, at launch. It captures intent + decisions _at creation time_. It is **not kept in sync** afterwards — current truth lives in `docs/adr/` (decisions) and the code; remaining work lives in `work/tasks/ready/` tasks. Every spec WILL be outrun by the work; that is normal and fine — do not fight it with ongoing spec maintenance. (The `to-task` skill performs a ONE-TIME trim at tasking-time, moving the now-redundant technical detail into tasks/ADRs so the spec settles to its durable framing — see that skill. After that, the spec is stable _because_ the stale-prone part was relocated, not because it is maintained.)
|
|
16
|
+
|
|
17
|
+
Put a one-line banner at the top of every spec you write:
|
|
18
|
+
|
|
19
|
+
> Launch snapshot — records intent at creation, NOT maintained. Current truth: `docs/adr/` + code; remaining work: `work/tasks/ready/`.
|
|
20
|
+
|
|
21
|
+
## Process
|
|
22
|
+
|
|
23
|
+
1. **Explore** the repo to understand current state (if not already). Use the project's domain glossary (`CONTEXT.md`) throughout, and respect ADRs in `docs/adr/` for the area you're touching.
|
|
24
|
+
|
|
25
|
+
2. **Sketch the seams** at which the feature will be tested. Prefer existing seams; use the highest seam possible. Confirm the seams match the user's expectations.
|
|
26
|
+
|
|
27
|
+
3. **Set the two autonomy axes (the spec now CARRIES the gate).** Because a spec may be AUTO-tasked by an agent with no human in the loop, decide and record both:
|
|
28
|
+
- **`humanOnly` (DECIDED):** set `humanOnly: true` on the spec ONLY to mean "a human must drive the TASKING of this spec" (its sole effect: an agent may not auto-task it). This is DISJOINT from any task's `humanOnly` — it does NOT propagate to or guide the gates of the tasks it produces (a `humanOnly` spec can yield fully agent-buildable tasks). Describe judgement-heavy areas in prose as ordinary domain context, but do NOT treat the spec flag as a way to pre-set task gates (the tasker decides each task's gate from that task's own build-nature — see the `to-task` skill §3b).
|
|
29
|
+
- **`needsAnswers` (DISCOVERED):** if the conversation did NOT fully resolve the spec, set `needsAnswers: true` and **list the open questions in the spec body** — the auto-tasker refuses to task until they are answered and the flag cleared. Be honest: flag an incomplete spec rather than let it produce wrongly-cut tasks. Omit both flags when everything is resolved and agent-taskable.
|
|
30
|
+
- If this spec's tasks will depend on another spec's tasks, set `taskedAfter: [other-spec]` so it is tasked in the right order. With auto-tasking the ordering must be pre-recorded here OR honestly flagged, since there may be no human at tasking time to supply it.
|
|
31
|
+
|
|
32
|
+
4. **Write** the spec to `work/specs/ready/<slug>.md` using [spec-template.md](work/protocol/spec-template.md), content-derived slug. Create `work/specs/ready/` lazily if absent.
|
|
33
|
+
|
|
34
|
+
**Do NOT** add an "update the spec" step anywhere — the spec is not synced after creation. **Git:** do NOT stage/commit/push — leave the file for review; report the path written. (Tasking it into `work/tasks/backlog/` is the separate `to-task` step.)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: to-task
|
|
3
|
+
disable-model-invocation: true
|
|
4
|
+
description: 'Task a spec or plan into independently-grabbable, file-based work tasks using tracer-bullet vertical tasks.'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# to-task
|
|
8
|
+
|
|
9
|
+
**The tasking discipline lives in `work/protocol/TASKING-PROTOCOL.md`** (the in-band protocol doc every set-up repo carries; the source-of-truth is `skills/setup/protocol/TASKING-PROTOCOL.md`). This skill is the **human-facing pointer** to that standard — the operator entry point a person reaches for to invoke the discipline interactively. The standard itself (the discipline, the two-axis gate guidance, the confidence check, and the emitted task shape) is stated ONCE in the protocol doc so the autonomous runner (the `do spec:<slug>` tasking path) and the human caller stay in step.
|
|
10
|
+
|
|
11
|
+
This skill stays **user-invoked** (`disable-model-invocation: true`): unlike `review` and `surface-questions` (model-invoked disciplines the runner spawns by name), the tasker is reached for explicitly — a human or operator decides to task a spec.
|
|
12
|
+
|
|
13
|
+
## How to use
|
|
14
|
+
|
|
15
|
+
1. Read `work/protocol/TASKING-PROTOCOL.md` in the repo you are working in.
|
|
16
|
+
2. Apply its discipline to the source `work/specs/ready/<slug>.md` (or design doc / plan): explore the codebase, draft the vertical tasks, quiz the user (or do the confidence check if no human is present), and write the task files under `work/tasks/backlog/`.
|
|
17
|
+
3. Trim the spec to its durable framing and move it `work/specs/ready/ → work/specs/tasked/` (the one-time hand-off transition the doc describes).
|
|
18
|
+
|
|
19
|
+
> Why the standard lives in `work/protocol/`: a discipline the autonomous runner invokes BY NAME (the `do spec:<slug>` tasking path reads it via `resolveProtocolDoc`) must be in-band in every set-up repo, not host-installed. Operator skills (this file) are human-facing and not copied.
|