duaer-spec 0.2.0 → 0.3.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.
@@ -1,16 +1,32 @@
1
1
  ---
2
- description: Features and hotfixes must go through Duaer; no code from verbal asks alone
2
+ description: Features and hotfixes must go through Duaer; job handoff before claiming done
3
3
  alwaysApply: true
4
4
  ---
5
5
 
6
6
  # Duaer required (NON-NEGOTIABLE)
7
7
 
8
- Treat agents as digital employees: assign a Brief (Spec) before code; accept
9
- only via converge. See `DUADER.md` and `.duaer/memory/constitution.md`.
8
+ Treat agents as **digital employees**: assign a Brief (Spec) before code; accept
9
+ via converge; only then claim the **job** is done. See `DUADER.md` and
10
+ `.duaer/memory/constitution.md`.
10
11
 
11
12
  If this rule conflicts with root `AGENTS.md` / `docs/agent/` (worktrees, commits,
12
13
  Issue/PR), **AGENTS.md wins**.
13
14
 
15
+ ## Handoff policy (job-level, not a repo lock)
16
+
17
+ Read `.duaer/delivery-policy.json` (`off` | `coach` | `strict`, default **coach**).
18
+
19
+ - This controls whether you may **report the active job complete** — not whether
20
+ git may commit or merge.
21
+ - Active job: `.duaer/active-job.json` → `feature`, else the latest `.duaer/specs/*`.
22
+ - Check with `duaer job .` before telling the user work is finished.
23
+
24
+ | Mode | Agent behavior when active job is not accepted |
25
+ |---|---|
26
+ | `off` | May continue; do not invent an accept stamp |
27
+ | `coach` | Warn; guide to specify / tasks / converge; **do not say "done"** |
28
+ | `strict` | **Refuse** to report delivery complete until `duaer job` shows accepted |
29
+
14
30
  ## When Duaer is mandatory
15
31
 
16
32
  Always write/update a feature spec (Brief) before coding for:
@@ -23,7 +39,7 @@ Forbidden: jump from chat to code with no `spec.md` / feature directory under `.
23
39
 
24
40
  ## Default flow
25
41
 
26
- `/duaer-specify` → `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` → `/duaer-converge`
42
+ `/duaer-specify` → `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` → `/duaer-converge` → `duaer job`
27
43
 
28
44
  ## Hotfix (still Spec, shortened)
29
45
 
@@ -31,5 +47,5 @@ Forbidden: jump from chat to code with no `spec.md` / feature directory under `.
31
47
 
32
48
  - Implement only from `tasks.md` when it exists
33
49
  - Update `.duaer/memory/project-context.md` when architecture changes
34
- - Run the verification named in the feature docs before merge
35
- - Before merge: `duaer check . --gate` (Spec present, no open tasks, `delivery.json` accepted)
50
+ - Run the verification named in the feature docs
51
+ - Before claiming done: `duaer job .` shows accepted (coach/strict)
@@ -265,15 +265,18 @@ Append to the **end** of `tasks.md`, per the append contract:
265
265
  path instead — do not stamp accepted).
266
266
  - Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."**
267
267
  - Include the summary counts of what was checked.
268
- - Tell the user they can run `duaer check . --gate` to verify the machine gate.
268
+ - Tell the user they can run `duaer job .` to see handoff status (policy default: coach).
269
+ - Reminder: this is **job acceptance**, not a repository merge lock.
269
270
  ### 8. Provide Next Actions (Handoff)
270
271
 
271
272
  - On `tasks_appended`: state how many tasks were appended under which phase, and recommend
272
273
  running `/duaer-implement` to complete them; note that a follow-up converge
273
274
  run will find fewer or no remaining items. Remind: `delivery.json` is `open` —
274
- `duaer check --gate` will fail until accepted.
275
- - On `converged`: recommend `duaer check . --gate`, then review / opening a PR. No further
276
- implement pass is needed for this feature's specified scope.
275
+ under coach/strict, do **not** claim the job is done; `duaer job .` will show unfinished.
276
+ - On `converged`: recommend `duaer job .`, then ask the user to review. No further
277
+ implement pass is needed for this feature's specified scope. Use the handoff script:
278
+ "✅ Job accepted — Brief satisfied per converge; ready for your review."
279
+ Do not frame this as a git/CI merge requirement.
277
280
 
278
281
  ### 9. Check for extension hooks
279
282
 
@@ -104,11 +104,23 @@ Given that feature description, do this:
104
104
  Write the actual resolved directory path value (for example, `specs/003-user-auth`), not the literal string `SPECIFY_FEATURE_DIRECTORY`.
105
105
  This allows downstream commands (`/duaer-plan`, `/duaer-tasks`, etc.) to locate the feature directory without relying on git branch name conventions.
106
106
 
107
+ - Also write / overwrite `.duaer/active-job.json` (job handoff pointer):
108
+ ```json
109
+ {
110
+ "schemaVersion": 1,
111
+ "feature": "<directory-name only, e.g. 003-user-auth>",
112
+ "featureDirectory": "<resolved feature dir>",
113
+ "updatedAt": "<ISO-8601 UTC>",
114
+ "source": "duaer-specify"
115
+ }
116
+ ```
117
+ `feature` is the basename under `.duaer/specs/` (or `specs/`) that `duaer job` resolves.
118
+
107
119
  **IMPORTANT**:
108
120
  - You must only create one feature per `/duaer-specify` invocation
109
121
  - The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
110
122
  - The spec directory and file are always created by this command, never by the hook
111
-
123
+ - Setting `active-job.json` makes this the job the employee must hand off; it does **not** lock git
112
124
  4. Load the resolved active `spec-template` file to understand required sections.
113
125
 
114
126
  5. **IF EXISTS**: Load `.duaer/memory/constitution.md` for project principles and governance constraints.
@@ -0,0 +1,6 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "mode": "coach",
4
+ "scope": "active",
5
+ "notes": "Job-level handoff. off=record only; coach=guide (default); strict=do not claim done until accepted. Not a git merge lock."
6
+ }
@@ -36,7 +36,13 @@ Do not expand scope without updating the spec and tasks.
36
36
  Mark tasks complete only when the stated verification (tests, manual checks,
37
37
  or converge) has been performed or explicitly waived in the feature docs.
38
38
 
39
- Before merge, run `duaer check . --gate`. It fails on missing Spec, open
40
- `- [ ]` tasks, or `delivery.json` that is not `status: "accepted"`. Converge
41
- assessment is still agent-assisted; the gate makes the **handoff state**
42
- machine-checkable.
39
+ **Job handoff (not a git lock):** before telling the user the work is finished,
40
+ run `duaer job .`. Read `.duaer/delivery-policy.json`:
41
+
42
+ - `coach` (default): if the active job is not `accepted`, warn and guide — do
43
+ **not** claim "done"
44
+ - `strict`: refuse to report delivery complete until accepted
45
+ - `off`: record only
46
+
47
+ Converge still stamps `delivery.json`. Judgment Spec↔code remains
48
+ agent-assisted; the stamp makes the **job** checkable.
package/ADOPT.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Adopt duaer-spec
2
2
 
3
- Onboard a **digital employee** into another repository: install the Duaer
4
- delivery OS (method) and the agent-ops contract (how they are allowed to work).
3
+ Onboard a **digital employee**: install the Duaer delivery OS (method) and the
4
+ agent-ops contract. Controllability is **job handoff**, not a git merge lock.
5
5
 
6
6
  ## Recommended: CLI
7
7
 
@@ -11,7 +11,7 @@ From the target project (Node 18+):
11
11
  npx duaer-spec init --here
12
12
 
13
13
  # Pin a release
14
- npx github:fujiezee/duaer-spec@v0.2.0 duaer init --here
14
+ npx github:fujiezee/duaer-spec@v0.3.0 duaer init --here
15
15
 
16
16
  # Lite (method only) / ops only / other integration branch
17
17
  npx duaer-spec init --here --method
@@ -22,7 +22,8 @@ From a clone of this repo:
22
22
 
23
23
  ```bash
24
24
  node bin/duaer.mjs init /path/to/project --all
25
- node bin/duaer.mjs check /path/to/project
25
+ node bin/duaer.mjs job /path/to/project
26
+ node bin/duaer.mjs policy /path/to/project
26
27
  ```
27
28
 
28
29
  `--force` overwrites managed files.
@@ -30,11 +31,11 @@ node bin/duaer.mjs check /path/to/project
30
31
  Then:
31
32
 
32
33
  1. **Orient** — edit `.duaer/memory/constitution.md` and `project-context.md`
33
- 2. **Confirm workplace** — `docs/baseline.md` (integration branch)
34
- 3. **Assign → work → accept → gate** — `/duaer-specify` → `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` → `/duaer-converge` → `duaer check . --gate`
34
+ 2. **Policy** — `duaer policy .` (default `coach`; optional `off` | `strict`)
35
+ 3. **Assign → work → accept → handoff** — `/duaer-specify` → → `/duaer-converge` → `duaer job .`
35
36
 
36
- A job without Spec is not assigned. Open tasks or a non-`accepted` `delivery.json`
37
- is not accepted. `duaer check --gate` is the machine merge check.
37
+ A job without Spec is not assigned. An unaccepted active job must not be reported
38
+ as done under `coach`/`strict`. Git is not blocked by default.
38
39
 
39
40
  ## Manual copy (optional)
40
41
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-09-15
4
+
5
+ ### Job handoff (not a repo lock)
6
+
7
+ - Default policy **`coach`**: guide the employee; do not claim "done" until the
8
+ **active job** is accepted — git merge is not blocked
9
+ - `.duaer/delivery-policy.json` modes: `off` | `coach` | `strict`
10
+ - `.duaer/active-job.json` set by `/duaer-specify`; `duaer job` reports status
11
+ - `duaer policy` to show/set mode
12
+ - `duaer check` defaults to active-job scope; `--strict` / policy `strict` optional
13
+ - `--gate` kept as deprecated alias for `--strict` (job handoff, not CI)
14
+ - `--all-jobs` optional; historical features no longer fail the default path
15
+ - Docs/rules/constitution demote merge-gate language
16
+
17
+ ### E2E
18
+
19
+ - E2E-004–007 for coach default, strict optional, active-job scope
20
+
3
21
  ## 0.2.0 — 2026-09-15
4
22
 
5
23
  ### Controllable delivery (machine gate)
@@ -46,9 +64,6 @@ First public methodology release.
46
64
  ### Install
47
65
 
48
66
  ```bash
49
- # From GitHub (works now)
50
67
  npx github:fujiezee/duaer-spec@v0.1.0 duaer init --here
51
-
52
- # From npm (after publish)
53
68
  npx duaer-spec init --here
54
69
  ```
package/DUADER.md CHANGED
@@ -1,59 +1,64 @@
1
1
  # Duaer methodology
2
2
 
3
3
  **duaer-spec** owns this method. Treat AI coding agents as **digital employees**:
4
- you assign a Brief (Spec), they follow a fixed work order, and delivery only
5
- counts after **converge** accepts the result against that Brief.
4
+ you assign a Brief (Spec), they follow a fixed work order, and the **job** only
5
+ counts as done after **converge** accepts it then `duaer job` shows accepted.
6
6
 
7
7
  Artifacts live under `.duaer/`; Cursor skills are `duaer-*` at `.cursor/skills/`.
8
8
 
9
9
  **Precedence:** root [`AGENTS.md`](AGENTS.md) / [`docs/agent/`](docs/agent/) win
10
10
  over anything here for isolation, commits, and Issue/PR gates.
11
11
 
12
+ **Handoff is job-level**, not a repository merge lock. Policy:
13
+ `.duaer/delivery-policy.json` (`off` | `coach` | `strict`, default `coach`).
14
+
12
15
  ## Read before work
13
16
 
14
17
  1. `.duaer/memory/constitution.md` — process principles
15
18
  2. `.duaer/memory/project-context.md` — as-is implementation truth
16
19
  3. `.duaer/memory/testing.md` — verification expectations
17
- 4. Active feature under `.duaer/specs/<nnn-slug>/` when one exists
20
+ 4. Active job: `.duaer/active-job.json` → feature under `.duaer/specs/<nnn-slug>/`
18
21
 
19
22
  ## Standard path
20
23
 
21
24
  **Full (recommended):**
22
25
 
23
26
  1. `/duaer-constitution` — only when principles change
24
- 2. `/duaer-specify` — **Assign**: what / why / acceptance (not stack trivia)
27
+ 2. `/duaer-specify` — **Assign**: Brief + set `active-job.json`
25
28
  3. `/duaer-clarify` — optional
26
29
  4. `/duaer-plan` — technical plan aligned with project-context
27
30
  5. `/duaer-checklist` — optional quality checklist
28
31
  6. `/duaer-tasks` — checkbox task breakdown
29
32
  7. `/duaer-analyze` — optional consistency check
30
33
  8. `/duaer-implement` — implement tasks only
31
- 9. `/duaer-converge` — **Accept**: compare result to Spec; write `delivery.json`; append remaining tasks if gaps
32
- 10. `duaer check . --gate` — **Gate**: fail merge if Spec missing, tasks open, or stamp not accepted
34
+ 9. `/duaer-converge` — **Accept**: compare to Spec; write `delivery.json`
35
+ 10. `duaer job .` — **Handoff**: may this job be reported done?
33
36
 
34
- **Small feature:** specify → plan → tasks → implement → converge → `check --gate`
37
+ **Small feature:** specify → plan → tasks → implement → converge → `duaer job`
35
38
 
36
- **Hotfix:** specify (mark hotfix) → tasks → implement → converge → `check --gate`
37
- Never skip specify (assign), converge (accept), or the gate.
39
+ **Hotfix:** specify (mark hotfix) → tasks → implement → converge → `duaer job`
40
+ Never skip specify (assign) or converge (accept). Under coach/strict, never claim
41
+ "done" while `duaer job` is unfinished.
38
42
 
39
43
  ## Install
40
44
 
41
45
  ```bash
42
46
  npx duaer-spec init --here
47
+ duaer policy . coach
43
48
  ```
44
49
 
45
- Or see [`ADOPT.md`](ADOPT.md). No third-party Spec CLI is required — `duaer`
46
- installs this repository's method files into the target project.
50
+ Or see [`ADOPT.md`](ADOPT.md).
47
51
 
48
52
  ## Feature directory shape
49
53
 
50
54
  ```text
55
+ .duaer/delivery-policy.json
56
+ .duaer/active-job.json
51
57
  .duaer/specs/<nnn-slug>/
52
58
  spec.md # Brief
53
- plan.md # after /duaer-plan
54
- tasks.md # after /duaer-tasks
55
- delivery.json # after /duaer-converge (accepted | open)
56
- … # research / contracts as needed
59
+ plan.md
60
+ tasks.md
61
+ delivery.json # accepted | open
57
62
  ```
58
63
 
59
64
  ## Related
package/README.md CHANGED
@@ -3,100 +3,83 @@
3
3
  **Duaer** is a delivery operating system for AI coding agents treated as
4
4
  **digital employees**.
5
5
 
6
- Agents write the code. Duaer makes the **handoff controllable**: every job has a
7
- Brief (Spec), follows a fixed work order, and only counts as done when the
8
- **delivery gate** passes — not after a lucky chat.
6
+ Agents write the code. Duaer makes the **job handoff controllable**: every job
7
+ has a Brief (Spec), follows a fixed work order, and only counts as done when
8
+ that **job** is accepted — not after a lucky chat.
9
9
 
10
- Cursor is the labor. Duaer is hire → assign → accept.
10
+ Cursor is the labor. Duaer is hire → assign → accept.
11
+ **Not** a git merge lock. Controllability is job etiquette + optional strictness.
11
12
 
12
13
  ## Install (onboard the employee)
13
14
 
14
15
  ```bash
15
16
  npx duaer-spec init --here
16
- # or pin: npx github:fujiezee/duaer-spec@v0.2.0 duaer init --here
17
+ # or pin: npx github:fujiezee/duaer-spec@v0.3.0 duaer init --here
17
18
  ```
18
19
 
19
20
  ```bash
20
- # Lite: method only (personal / small changes)
21
21
  npx duaer-spec init --here --method
22
-
23
- # Full ops on another integration branch
24
22
  npx duaer-spec init --here --ops --branch develop
25
23
  ```
26
24
 
27
- Verify: `npx duaer-spec check .`
28
- Merge gate: `npx duaer-spec check . --gate`
25
+ ```bash
26
+ duaer policy . # default: coach
27
+ duaer job . # active job handoff status
28
+ duaer check .
29
+ ```
30
+
29
31
  Details: [`ADOPT.md`](ADOPT.md)
30
32
 
31
33
  ## How you run them
32
34
 
33
35
  | Step | Meaning | Command |
34
36
  |---|---|---|
35
- | **Hire** | Install SOP + gates into the repo | `duaer init` |
36
- | **Assign** | Write the Brief (what / why / acceptance) | `/duaer-specify` |
37
+ | **Hire** | Install SOP into the project | `duaer init` |
38
+ | **Assign** | Brief + set active job | `/duaer-specify` |
37
39
  | **Work** | Plan, break down, implement | `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` |
38
- | **Accept** | Converge stamps `delivery.json`; gaps stay open | `/duaer-converge` |
39
- | **Gate** | Machine check before merge | `duaer check . --gate` |
40
+ | **Accept** | Converge stamps `delivery.json` | `/duaer-converge` |
41
+ | **Handoff** | See if this job may be reported done | `duaer job .` |
40
42
 
41
- Never skip **Assign** or **Accept**. Hotfix may shorten the middle; Spec,
42
- converge, and the gate stay.
43
+ ### Policy (job-level)
43
44
 
44
- ### What the gate actually enforces
45
+ Stored in `.duaer/delivery-policy.json`:
45
46
 
46
- `duaer check --gate` fails when any feature under `.duaer/specs/` has:
47
+ | Mode | Behavior |
48
+ |---|---|
49
+ | `off` | Record only |
50
+ | `coach` | **Default** — guide; do not claim "done" until accepted |
51
+ | `strict` | Refuse to report done; `duaer check --strict` exits 1 on unfinished job |
47
52
 
48
- - missing `spec.md`
49
- - open `- [ ]` tasks in `tasks.md`
50
- - missing `delivery.json`, or `status` other than `accepted`
51
-
52
- Converge judgment is still agent-assisted. The gate makes **handoff state**
53
- checkable (and CI-friendly), not a substitute for tests.
53
+ Scope is the **active job** (`.duaer/active-job.json`), not every historical feature.
54
+ Git commit/merge is never blocked by default.
54
55
 
55
56
  ## Two layers
56
57
 
57
58
  | Layer | Role | Where |
58
59
  |---|---|---|
59
- | **Agent ops** | How the employee is allowed to operate | [`AGENTS.md`](AGENTS.md), [`docs/agent/`](docs/agent/) |
60
- | **Duaer method** | What to build (Spec → Plan → Tasks → Implement → Converge) | [`DUADER.md`](DUADER.md), [`.duaer/`](.duaer/), [`.cursor/skills/`](.cursor/skills/) |
60
+ | **Agent ops** | How the employee may operate | [`AGENTS.md`](AGENTS.md), [`docs/agent/`](docs/agent/) |
61
+ | **Duaer method** | What to build | [`DUADER.md`](DUADER.md), [`.duaer/`](.duaer/), skills |
61
62
 
62
- When they conflict, **agent ops win** — process beats improvisation.
63
+ When they conflict, **agent ops win**.
63
64
 
64
65
  ## Default loop
65
66
 
66
67
  ```text
67
- constitution → specify → plan → tasks → implement → converge → check --gate
68
+ constitution → specify → plan → tasks → implement → converge → duaer job
68
69
  ```
69
70
 
70
- Small change: `specify → plan → tasks → implement → converge`
71
- Hotfix: `specify (hotfix) → tasks → implement → converge`
72
-
73
71
  Slash skills: `/duaer-specify`, `/duaer-plan`, `/duaer-tasks`, `/duaer-implement`,
74
72
  `/duaer-converge`, …
75
73
 
76
74
  ## Layout
77
75
 
78
76
  ```text
79
- bin/duaer.mjs CLI (duaer init | check | version)
80
- package.json npm package metadata
81
- AGENTS.md Agent-ops contract (how employees operate)
82
- DUADER.md Methodology conventions (how work is briefed)
83
- ADOPT.md Onboarding guide
84
- .duaer/ Memory, templates, workflows, scripts
85
- .duaer/specs/<feature>/ Brief + tasks + delivery.json stamp
86
- .cursor/rules/ Agent-ops + Duaer rules
87
- .cursor/skills/ duaer-* skills
88
- docs/agent/ Workflow detail + checklists
89
- docs/maintaining.md How maintainers evolve the method
90
- docs/npm-trusted-publishing.md npm Trusted Publishing (OIDC) setup
91
- examples/ Optional product overlays (not defaults)
77
+ bin/duaer.mjs CLI (init | check | job | policy | version)
78
+ .duaer/delivery-policy.json off | coach | strict
79
+ .duaer/active-job.json current job pointer
80
+ .duaer/specs/<feature>/ Brief + tasks + delivery.json
92
81
  ```
93
82
 
94
- ## Defaults (this repo)
95
-
96
- - Integration branch: **`main`**
97
- - One request → one branch + one worktree → merge → delete worktree
98
- - Push only when explicitly requested
99
-
100
83
  ## Learn more
101
84
 
102
85
  Method: [`DUADER.md`](DUADER.md) · Agent ops: [`AGENTS.md`](AGENTS.md)
package/bin/duaer.mjs CHANGED
@@ -3,7 +3,9 @@
3
3
  * duaer — CLI for the duaer-spec delivery OS (digital employees)
4
4
  *
5
5
  * duaer init [dir] [--all|--method|--ops] [--force] [--branch <name>]
6
- * duaer check [dir] [--workplace|--delivery|--gate]
6
+ * duaer check [dir] [--workplace|--delivery|--job|--all-jobs|--strict]
7
+ * duaer job [dir]
8
+ * duaer policy [dir] [off|coach|strict]
7
9
  * duaer version
8
10
  * duaer help
9
11
  */
@@ -24,35 +26,48 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
24
26
  const PKG_ROOT = resolve(__dirname, '..')
25
27
  const PKG = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8'))
26
28
 
29
+ const POLICY_MODES = new Set(['off', 'coach', 'strict'])
30
+
27
31
  const USAGE = `duaer — Delivery OS for AI digital employees (duaer-spec ${PKG.version})
28
32
 
29
- Hire agents into a repo, assign Briefs (Specs), accept only when the gate passes.
33
+ Job-level handoff (not a repo merge lock). Default policy: coach.
30
34
 
31
35
  Usage:
32
- duaer init [dir] [options] Hire / onboard into a project
33
- duaer check [dir] [options] Workplace + delivery status
34
- duaer version Print version
35
- duaer help Show this help
36
+ duaer init [dir] [options] Hire / onboard into a project
37
+ duaer check [dir] [options] Workplace + active-job handoff
38
+ duaer job [dir] Show the active job status
39
+ duaer policy [dir] [off|coach|strict] Show or set handoff policy
40
+ duaer version Print version
41
+ duaer help Show this help
36
42
 
37
43
  Init options:
38
44
  --all Full hire: agent ops + method (default)
39
- --method Lite: method only (.duaer, skills, DUADER.md, duaer-spec rule)
40
- --ops Ops only (AGENTS.md, docs/agent, ops rules)
45
+ --method Lite: method only
46
+ --ops Ops only
41
47
  --force Overwrite existing managed files
42
48
  --branch <n> Integration branch for baseline note (default: main)
43
49
  --here Same as dir=.
44
50
 
45
51
  Check options:
46
52
  --workplace Only verify install files
47
- --delivery Only report feature Brief / open tasks / delivery.json
48
- --gate Fail unless every feature is accepted (merge gate)
49
- (default) Workplace + delivery report; exit 1 on workplace miss
50
- or delivery blockers (missing Spec / open tasks / open stamp)
53
+ --delivery Alias for active-job handoff report
54
+ --job Active job only (default for delivery)
55
+ --all-jobs Report every feature under .duaer/specs/
56
+ --strict Exit 1 if active job is not accepted (optional)
57
+ --gate Deprecated alias for --strict (job handoff, not CI)
58
+
59
+ Policy modes (stored in .duaer/delivery-policy.json):
60
+ off Record only; never fail handoff
61
+ coach Default — guide / warn; do not claim "done" until accepted
62
+ strict Agents must not report delivery complete until accepted;
63
+ duaer check exits 1 on unfinished active job
51
64
 
52
65
  Examples:
53
66
  npx duaer-spec init --here
67
+ duaer job .
68
+ duaer policy . coach
54
69
  duaer check .
55
- duaer check . --gate
70
+ duaer check . --strict
56
71
  `
57
72
 
58
73
  function parseArgs(argv) {
@@ -65,7 +80,10 @@ function parseArgs(argv) {
65
80
  branch: 'main',
66
81
  workplace: false,
67
82
  delivery: false,
68
- gate: false,
83
+ job: false,
84
+ allJobs: false,
85
+ strict: false,
86
+ policyMode: null,
69
87
  }
70
88
  const rest = args.slice(1)
71
89
  for (let i = 0; i < rest.length; i++) {
@@ -77,12 +95,18 @@ function parseArgs(argv) {
77
95
  else if (a === '--here') out.dir = '.'
78
96
  else if (a === '--workplace') out.workplace = true
79
97
  else if (a === '--delivery') out.delivery = true
80
- else if (a === '--gate') out.gate = true
98
+ else if (a === '--job') out.job = true
99
+ else if (a === '--all-jobs') out.allJobs = true
100
+ else if (a === '--strict' || a === '--gate') out.strict = true
81
101
  else if (a === '--branch') {
82
102
  out.branch = rest[++i]
83
103
  if (!out.branch) throw new Error('--branch requires a value')
104
+ } else if (POLICY_MODES.has(a) && out.cmd === 'policy') {
105
+ out.policyMode = a
84
106
  } else if (a.startsWith('-')) {
85
107
  throw new Error(`Unknown flag: ${a}`)
108
+ } else if (out.cmd === 'policy' && POLICY_MODES.has(a)) {
109
+ out.policyMode = a
86
110
  } else {
87
111
  out.dir = a
88
112
  }
@@ -124,10 +148,62 @@ function writeIfNeeded(path, content, { force }) {
124
148
  console.log(`wrote ${path}`)
125
149
  }
126
150
 
151
+ function readJson(path, fallback = null) {
152
+ if (!existsSync(path)) return fallback
153
+ try {
154
+ return JSON.parse(readFileSync(path, 'utf8'))
155
+ } catch {
156
+ return fallback
157
+ }
158
+ }
159
+
160
+ function defaultPolicy() {
161
+ return {
162
+ schemaVersion: 1,
163
+ mode: 'coach',
164
+ scope: 'active',
165
+ notes:
166
+ 'Job-level handoff. off=record only; coach=guide (default); strict=do not claim done until accepted. Not a git merge lock.',
167
+ }
168
+ }
169
+
170
+ function policyPath(target) {
171
+ return join(target, '.duaer', 'delivery-policy.json')
172
+ }
173
+
174
+ function activeJobPath(target) {
175
+ return join(target, '.duaer', 'active-job.json')
176
+ }
177
+
178
+ function loadPolicy(target) {
179
+ const p = readJson(policyPath(target), null)
180
+ if (!p) return defaultPolicy()
181
+ const mode = POLICY_MODES.has(p.mode) ? p.mode : 'coach'
182
+ return { ...defaultPolicy(), ...p, mode }
183
+ }
184
+
185
+ function writePolicy(target, mode) {
186
+ const next = { ...defaultPolicy(), mode, updatedAt: new Date().toISOString() }
187
+ ensureDir(join(target, '.duaer'))
188
+ writeFileSync(policyPath(target), JSON.stringify(next, null, 2) + '\n')
189
+ return next
190
+ }
191
+
192
+ function writeDefaultPolicy(target, { force }) {
193
+ const path = policyPath(target)
194
+ if (existsSync(path) && !force) {
195
+ console.log(`skip (exists): ${path}`)
196
+ return
197
+ }
198
+ writeFileSync(path, JSON.stringify(defaultPolicy(), null, 2) + '\n')
199
+ console.log(' .duaer/delivery-policy.json (mode=coach)')
200
+ }
201
+
127
202
  function installMethod(target, opts) {
128
203
  console.log('Installing Duaer method…')
129
204
  copyPath(join(PKG_ROOT, '.duaer'), join(target, '.duaer'), opts)
130
205
  console.log(' .duaer/')
206
+ writeDefaultPolicy(target, opts)
131
207
 
132
208
  ensureDir(join(target, '.cursor', 'skills'))
133
209
  const skillsRoot = join(PKG_ROOT, '.cursor', 'skills')
@@ -234,17 +310,17 @@ function cmdInit(opts) {
234
310
  console.log(`
235
311
  Hired.
236
312
 
237
- Next (digital employee loop):
313
+ Next (digital employee loop — job handoff, not a repo lock):
238
314
  1. Orient — edit .duaer/memory/constitution.md and project-context.md
239
- 2. Workplace confirm docs/baseline.md (integration branch: ${opts.branch})
240
- 3. Assign — /duaer-specify (Brief: what / why / acceptance)
315
+ 2. Policy duaer policy . (default coach; optional: off | strict)
316
+ 3. Assign — /duaer-specify (sets active job + Brief)
241
317
  4. Work — /duaer-plan → /duaer-tasks → /duaer-implement
242
- 5. Accept — /duaer-converge (writes delivery.json; gaps stay open)
243
- 6. Gate — duaer check . --gate before merge
244
- 7. Policy — AGENTS.md wins over DUADER.md when they conflict
318
+ 5. Accept — /duaer-converge (stamps delivery.json)
319
+ 6. Job — duaer job . (see if this job can be reported done)
320
+ 7. Ops — AGENTS.md wins over DUADER.md when they conflict
245
321
 
246
- No Spec = not assigned. Open tasks or open delivery.json = not accepted.
247
- See DUADER.md and AGENTS.md in the target project.
322
+ No Spec = not assigned. Unaccepted active job = do not claim "done".
323
+ Git merge is not blocked by default.
248
324
  `)
249
325
  }
250
326
 
@@ -265,6 +341,55 @@ function readDelivery(path) {
265
341
  }
266
342
  }
267
343
 
344
+ function featureVerdict(name, dir) {
345
+ const specPath = join(dir, 'spec.md')
346
+ const tasksPath = join(dir, 'tasks.md')
347
+ const deliveryPath = join(dir, 'delivery.json')
348
+ const tasks = countOpenTasks(tasksPath)
349
+ const delivery = readDelivery(deliveryPath)
350
+ const hasSpec = existsSync(specPath)
351
+ const stamp = delivery?.status || 'none'
352
+ let verdict = 'accepted'
353
+ const blockers = []
354
+
355
+ if (!hasSpec) {
356
+ verdict = 'blocked'
357
+ blockers.push('missing spec.md')
358
+ }
359
+ if (tasks && tasks.open > 0) {
360
+ verdict = 'blocked'
361
+ blockers.push(`${tasks.open} open task(s)`)
362
+ }
363
+ if (stamp === 'open' || stamp === 'invalid') {
364
+ verdict = 'blocked'
365
+ blockers.push(`delivery.json status=${stamp}`)
366
+ }
367
+ if (hasSpec && tasks && tasks.open === 0 && (stamp === 'none' || !delivery)) {
368
+ verdict = 'unaccepted'
369
+ blockers.push('no delivery.json (run /duaer-converge)')
370
+ }
371
+ if (hasSpec && !tasks && (stamp === 'none' || !delivery)) {
372
+ verdict = 'unaccepted'
373
+ blockers.push('no tasks.md / no delivery.json')
374
+ }
375
+ if (stamp === 'accepted' && tasks && tasks.open === 0 && hasSpec) {
376
+ verdict = 'accepted'
377
+ blockers.length = 0
378
+ } else if (stamp === 'accepted' && (!tasks || tasks.open === 0) && hasSpec && !tasks) {
379
+ // accepted stamp with no tasks file — treat as accepted only if stamp says so
380
+ // keep unaccepted path above for !tasks && no stamp
381
+ }
382
+
383
+ // Stamp accepted but open tasks remain → blocked (fake stamp)
384
+ if (stamp === 'accepted' && tasks && tasks.open > 0) {
385
+ verdict = 'blocked'
386
+ blockers.length = 0
387
+ blockers.push('delivery.json accepted but open tasks remain')
388
+ }
389
+
390
+ return { name, hasSpec, tasks, stamp, verdict, blockers }
391
+ }
392
+
268
393
  function listFeatures(target) {
269
394
  const root = join(target, '.duaer', 'specs')
270
395
  if (!existsSync(root)) return []
@@ -277,53 +402,28 @@ function listFeatures(target) {
277
402
  }
278
403
  })
279
404
  .sort()
280
- .map((name) => {
281
- const dir = join(root, name)
282
- const specPath = join(dir, 'spec.md')
283
- const tasksPath = join(dir, 'tasks.md')
284
- const deliveryPath = join(dir, 'delivery.json')
285
- const tasks = countOpenTasks(tasksPath)
286
- const delivery = readDelivery(deliveryPath)
287
- const hasSpec = existsSync(specPath)
288
- const stamp = delivery?.status || 'none'
289
- let verdict = 'accepted'
290
- const blockers = []
291
-
292
- if (!hasSpec) {
293
- verdict = 'blocked'
294
- blockers.push('missing spec.md')
295
- }
296
- if (tasks && tasks.open > 0) {
297
- verdict = 'blocked'
298
- blockers.push(`${tasks.open} open task(s)`)
299
- }
300
- if (stamp === 'open' || stamp === 'invalid') {
301
- verdict = 'blocked'
302
- blockers.push(`delivery.json status=${stamp}`)
303
- }
304
- if (hasSpec && tasks && tasks.open === 0 && (stamp === 'none' || !delivery)) {
305
- // Brief exists and tasks clear, but never stamped by converge
306
- verdict = 'unaccepted'
307
- blockers.push('no delivery.json (run /duaer-converge)')
308
- }
309
- if (hasSpec && !tasks && (stamp === 'none' || !delivery)) {
310
- verdict = 'unaccepted'
311
- blockers.push('no tasks.md / no delivery.json')
312
- }
313
- if (stamp === 'accepted' && tasks && tasks.open === 0 && hasSpec) {
314
- verdict = 'accepted'
315
- blockers.length = 0
316
- }
405
+ .map((name) => featureVerdict(name, join(root, name)))
406
+ }
317
407
 
318
- return {
319
- name,
320
- hasSpec,
321
- tasks,
322
- stamp,
323
- verdict,
324
- blockers,
325
- }
326
- })
408
+ function resolveActiveJobName(target, features) {
409
+ const marker = readJson(activeJobPath(target), null)
410
+ if (marker?.feature && features.some((f) => f.name === marker.feature)) {
411
+ return marker.feature
412
+ }
413
+ if (features.length === 0) return null
414
+ // Prefer newest directory name (lexicographic works for NNN-slug)
415
+ return features[features.length - 1].name
416
+ }
417
+
418
+ function printFeature(f, { label } = {}) {
419
+ const taskInfo = f.tasks
420
+ ? `tasks open=${f.tasks.open} done=${f.tasks.done}`
421
+ : 'no tasks.md'
422
+ const prefix = label ? `${label} ` : ''
423
+ console.log(
424
+ `${prefix}${f.verdict.padEnd(10)} ${f.name} spec=${f.hasSpec ? 'yes' : 'NO'} stamp=${f.stamp} ${taskInfo}`,
425
+ )
426
+ for (const b of f.blockers) console.log(` · ${b}`)
327
427
  }
328
428
 
329
429
  function checkWorkplace(target) {
@@ -346,51 +446,91 @@ function checkWorkplace(target) {
346
446
  return missing
347
447
  }
348
448
 
349
- function checkDelivery(target, { gate }) {
449
+ function checkDelivery(target, { allJobs, policy }) {
350
450
  const features = listFeatures(target)
351
- console.log('\n## Delivery\n')
451
+ const activeName = resolveActiveJobName(target, features)
452
+ const active = features.find((f) => f.name === activeName) || null
453
+
454
+ console.log('\n## Handoff (jobs)\n')
455
+ console.log(`policy: ${policy.mode} scope: ${allJobs ? 'all' : 'active'}`)
456
+
352
457
  if (features.length === 0) {
353
- console.log('ok no features under .duaer/specs/ (nothing to accept)')
354
- return { blocked: 0, unaccepted: 0, features: 0 }
458
+ console.log('ok no features under .duaer/specs/ (nothing to hand off)')
459
+ return { blocked: 0, unaccepted: 0, features: 0, active: null, unfinished: false }
355
460
  }
356
461
 
462
+ if (allJobs) {
463
+ for (const f of features) {
464
+ printFeature(f, { label: f.name === activeName ? '★' : ' ' })
465
+ }
466
+ } else if (active) {
467
+ printFeature(active, { label: '★' })
468
+ console.log(`\nactive job: ${active.name} (from .duaer/active-job.json or latest spec)`)
469
+ } else {
470
+ console.log('ok no resolvable active job')
471
+ }
472
+
473
+ const focus = allJobs ? features : active ? [active] : []
357
474
  let blocked = 0
358
475
  let unaccepted = 0
359
- for (const f of features) {
360
- const taskInfo = f.tasks
361
- ? `tasks open=${f.tasks.open} done=${f.tasks.done}`
362
- : 'no tasks.md'
363
- const line = `${f.verdict.padEnd(10)} ${f.name} spec=${f.hasSpec ? 'yes' : 'NO'} stamp=${f.stamp} ${taskInfo}`
364
- console.log(line)
365
- if (f.blockers.length) {
366
- for (const b of f.blockers) console.log(` · ${b}`)
367
- }
476
+ for (const f of focus) {
368
477
  if (f.verdict === 'blocked') blocked++
369
478
  if (f.verdict === 'unaccepted') unaccepted++
370
479
  }
480
+ const unfinished = blocked + unaccepted > 0
481
+ return { blocked, unaccepted, features: focus.length, active, unfinished }
482
+ }
371
483
 
372
- if (gate) {
373
- console.log('\nGate mode: every feature must be accepted.')
484
+ function handoffAdvice(policy, unfinished, strictFlag) {
485
+ if (!unfinished) {
486
+ console.log('\nActive job accepted — employee may report delivery complete.')
487
+ return
488
+ }
489
+ if (policy.mode === 'off' && !strictFlag) {
490
+ console.log('\nHandoff incomplete (policy=off) — recorded only.')
491
+ return
492
+ }
493
+ if (policy.mode === 'coach' && !strictFlag) {
494
+ console.log(`
495
+ Coach: this job is not accepted yet.
496
+ → close open tasks, run /duaer-converge, then duaer job .
497
+ → do not tell the user the work is "done" until status=accepted
498
+ → git is not blocked; this is job etiquette, not a merge lock
499
+ `)
500
+ return
374
501
  }
375
- return { blocked, unaccepted, features: features.length }
502
+ console.log(`
503
+ Strict: active job not accepted — exit 1.
504
+ → /duaer-converge until delivery.json status=accepted and tasks clear
505
+ `)
376
506
  }
377
507
 
378
508
  function cmdCheck(opts) {
379
509
  const target = resolve(opts.dir)
380
- // Default: workplace + delivery. --gate alone is delivery-only (merge gate).
381
- const runWorkplace = opts.workplace || (!opts.delivery && !opts.gate)
382
- const runDelivery = opts.delivery || opts.gate || !opts.workplace
510
+ const policy = loadPolicy(target)
511
+ const strictFlag = opts.strict || policy.mode === 'strict'
512
+
513
+ // default: workplace + delivery; --workplace alone; job/delivery/strict/all-jobs → delivery
514
+ let wp = false
515
+ let del = false
516
+ if (opts.workplace && !opts.delivery && !opts.job && !opts.allJobs && !opts.strict) {
517
+ wp = true
518
+ } else if (opts.delivery || opts.job || opts.allJobs || opts.strict) {
519
+ del = true
520
+ if (opts.workplace) wp = true
521
+ } else {
522
+ wp = true
523
+ del = true
524
+ }
383
525
 
384
- console.log(`Checking ${target}${opts.gate ? ' (gate)' : ''}\n`)
526
+ console.log(`Checking ${target}${strictFlag ? ' (strict handoff)' : ''}\n`)
385
527
 
386
528
  let workplaceMissing = 0
387
- if (runWorkplace) {
388
- workplaceMissing = checkWorkplace(target)
389
- }
529
+ if (wp) workplaceMissing = checkWorkplace(target)
390
530
 
391
- let delivery = { blocked: 0, unaccepted: 0, features: 0 }
392
- if (runDelivery) {
393
- delivery = checkDelivery(target, { gate: opts.gate })
531
+ let delivery = { unfinished: false }
532
+ if (del) {
533
+ delivery = checkDelivery(target, { allJobs: opts.allJobs, policy })
394
534
  }
395
535
 
396
536
  let fail = false
@@ -399,36 +539,76 @@ function cmdCheck(opts) {
399
539
  fail = true
400
540
  }
401
541
 
402
- if (runDelivery) {
403
- if (delivery.blocked) {
404
- console.log(`\n${delivery.blocked} feature(s) blocked — close open tasks / fix Spec / re-run converge`)
542
+ if (del) {
543
+ handoffAdvice(policy, delivery.unfinished, opts.strict)
544
+ if (delivery.unfinished && strictFlag) {
405
545
  fail = true
406
546
  }
407
- if (opts.gate && delivery.unaccepted) {
408
- console.log(
409
- `\n${delivery.unaccepted} feature(s) not accepted — run /duaer-converge until delivery.json status=accepted`,
410
- )
411
- fail = true
412
- } else if (!opts.gate && delivery.unaccepted) {
413
- console.log(
414
- `\n${delivery.unaccepted} feature(s) not yet accepted (warning). Use --gate to fail the merge check.`,
415
- )
416
- }
417
547
  }
418
548
 
419
549
  if (!fail) {
420
- if (opts.gate) {
421
- console.log('\nGate passed — deliverable.')
422
- } else if (runWorkplace && !workplaceMissing) {
423
- console.log('\nWorkplace ready. Use --gate before merge when features exist.')
550
+ if (wp && !workplaceMissing && !del) {
551
+ console.log('\nWorkplace ready.')
552
+ } else if (!delivery.unfinished) {
553
+ console.log('\nCheck ok.')
424
554
  } else {
425
- console.log('\nDelivery report done.')
555
+ console.log('\nCheck ok (handoff still open under coach/off — see above).')
426
556
  }
427
557
  } else {
428
558
  process.exitCode = 1
429
559
  }
430
560
  }
431
561
 
562
+ function cmdJob(opts) {
563
+ const target = resolve(opts.dir)
564
+ const policy = loadPolicy(target)
565
+ const features = listFeatures(target)
566
+ const activeName = resolveActiveJobName(target, features)
567
+ const active = features.find((f) => f.name === activeName) || null
568
+
569
+ console.log(`Active job @ ${target}`)
570
+ console.log(`policy: ${policy.mode}\n`)
571
+ if (!active) {
572
+ console.log('No active job. Assign with /duaer-specify (writes .duaer/active-job.json).')
573
+ return
574
+ }
575
+ printFeature(active)
576
+ if (active.verdict === 'accepted') {
577
+ console.log('\nHandoff script: "✅ Job accepted — Brief satisfied per converge; ready for your review."')
578
+ } else {
579
+ console.log('\nHandoff script: "This job is not accepted yet — Spec/tasks/converge still open."')
580
+ if (policy.mode !== 'off') {
581
+ console.log('Do not claim delivery complete until duaer job shows accepted.')
582
+ }
583
+ }
584
+ }
585
+
586
+ function cmdPolicy(opts) {
587
+ const target = resolve(opts.dir)
588
+ if (opts.policyMode) {
589
+ if (!POLICY_MODES.has(opts.policyMode)) {
590
+ throw new Error(`Unknown policy mode: ${opts.policyMode}`)
591
+ }
592
+ const next = writePolicy(target, opts.policyMode)
593
+ console.log(`Wrote ${policyPath(target)}`)
594
+ console.log(`mode: ${next.mode}`)
595
+ return
596
+ }
597
+ const policy = loadPolicy(target)
598
+ const exists = existsSync(policyPath(target))
599
+ console.log(`Policy @ ${target}`)
600
+ console.log(`file: ${exists ? policyPath(target) : '(defaults; run init or: duaer policy . coach)'}`)
601
+ console.log(`mode: ${policy.mode}`)
602
+ console.log(`scope: ${policy.scope || 'active'}`)
603
+ console.log(`
604
+ Modes:
605
+ off record only
606
+ coach guide; do not claim done until accepted (default)
607
+ strict check fails + agents must not report done until accepted
608
+ Not a git merge lock.
609
+ `)
610
+ }
611
+
432
612
  function main() {
433
613
  let opts
434
614
  try {
@@ -447,6 +627,12 @@ function main() {
447
627
  case 'check':
448
628
  cmdCheck(opts)
449
629
  break
630
+ case 'job':
631
+ cmdJob(opts)
632
+ break
633
+ case 'policy':
634
+ cmdPolicy(opts)
635
+ break
450
636
  case 'version':
451
637
  case '--version':
452
638
  case '-v':
@@ -94,7 +94,7 @@ See [spec update guidance](workflow.md#3-spec-update-guidance).
94
94
 
95
95
  ## 6. Merge / PR
96
96
 
97
- - [ ] `duaer check . --gate` passed (or no `.duaer/specs/` features yet).
97
+ - [ ] Active job handoff considered: `duaer job .` (coach/strict: do not claim done if unfinished).
98
98
  - [ ] Branch refreshed against latest `main`.
99
99
  - [ ] Merged into local `main` (and/or remote PR/MR when required).
100
100
  - [ ] Only this request's logical changes included.
@@ -122,7 +122,7 @@ See [spec update guidance](workflow.md#3-spec-update-guidance).
122
122
  | 3 | Impacted specs updated | [R1](workflow.md#r1--spec-first--spec-sync) |
123
123
  | 4 | E2E docs updated or confirmed unnecessary | [R3](workflow.md#r3--e2e-coverage-doc) |
124
124
  | 5 | Targeted validation done or waived with reason | Development loop |
125
- | 5a | `duaer check . --gate` when features exist | Delivery OS gate |
125
+ | 5a | Active job handoff via `duaer job` (coach/strict) | Delivery OS not a git lock |
126
126
  | 6 | Conventional commits | [R2](workflow.md#r2--commit-per-change) |
127
127
  | 7 | No secrets or local data | [§4](workflow.md#4-what-never-to-commit) |
128
128
  | 8 | Merged into `main`; worktree and branch removed | [R4](workflow.md#r4--request-branch--worktree--merge-gate) · [§6.1](#61-merge-cleanup) |
@@ -27,9 +27,11 @@ the table for their app.
27
27
  |---|---|---|
28
28
  | E2E-001 | After `duaer init`, CLI prints hire → assign → accept next steps | manual |
29
29
  | E2E-002 | README positions Duaer as delivery OS for digital employees | manual |
30
- | E2E-003 | `duaer check --delivery` reports open tasks as blocked | manual |
31
- | E2E-004 | `duaer check --gate` fails without `delivery.json` accepted | manual |
32
- | E2E-005 | After converge stamps `accepted`, `--gate` passes | manual |
30
+ | E2E-003 | `duaer check --job` reports open tasks on active job | manual |
31
+ | E2E-004 | Default `coach` policy: unfinished job warns but check exits 0 | manual |
32
+ | E2E-005 | After converge stamps `accepted`, `duaer job` shows accepted | manual |
33
+ | E2E-006 | `duaer policy . strict` then unfinished job makes check exit 1 | manual |
34
+ | E2E-007 | `--all-jobs` is optional; default scope is active job only | manual |
33
35
 
34
36
  ## Traceability
35
37
 
@@ -37,8 +39,10 @@ Keep this matrix current when scenarios or specs change.
37
39
 
38
40
  | Scenario | Spec / feature | Notes |
39
41
  |---|---|---|
40
- | E2E-001 | `bin/duaer.mjs` init banner | Controllable-delivery onboarding |
42
+ | E2E-001 | `bin/duaer.mjs` init banner | Onboarding |
41
43
  | E2E-002 | `README.md` / `ADOPT.md` | Product identity |
42
- | E2E-003 | `bin/duaer.mjs` delivery check | Open `- [ ]` tasks |
43
- | E2E-004 | `bin/duaer.mjs` `--gate` | Merge gate |
44
+ | E2E-003 | active job delivery check | Open `- [ ]` |
45
+ | E2E-004 | `delivery-policy.json` coach | Not a repo lock |
44
46
  | E2E-005 | `/duaer-converge` → `delivery.json` | Accept stamp |
47
+ | E2E-006 | policy strict | Optional hardness |
48
+ | E2E-007 | `--all-jobs` vs active | Scope |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "duaer-spec",
3
- "version": "0.2.0",
4
- "description": "Delivery OS for AI digital employees: Spec briefs, agent ops, check --gate handoffs",
3
+ "version": "0.3.0",
4
+ "description": "Delivery OS for AI digital employees: job handoff (coach/strict), Spec briefs, agent ops",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "duaer": "bin/duaer.mjs",