duaer-spec 0.1.1 → 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/.cursor/rules/duaer-spec.mdc +1 -0
- package/.cursor/skills/duaer-converge/SKILL.md +46 -7
- package/.duaer/memory/constitution.md +5 -0
- package/ADOPT.md +4 -3
- package/CHANGELOG.md +16 -0
- package/DUADER.md +10 -8
- package/README.md +21 -7
- package/bin/duaer.mjs +187 -20
- package/docs/agent/change-checklist.md +2 -0
- package/docs/agent/e2e-test-plan.md +6 -0
- package/package.json +2 -2
|
@@ -32,3 +32,4 @@ Forbidden: jump from chat to code with no `spec.md` / feature directory under `.
|
|
|
32
32
|
- Implement only from `tasks.md` when it exists
|
|
33
33
|
- Update `.duaer/memory/project-context.md` when architecture changes
|
|
34
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)
|
|
@@ -71,10 +71,17 @@ of remaining work as a new, traceable task** at the bottom of `tasks.md` so that
|
|
|
71
71
|
This is **not** a diff tool and does **not** track changes. It assesses the present state
|
|
72
72
|
of the code relative to the feature's artifacts — no git, no branch comparison, no history.
|
|
73
73
|
|
|
74
|
+
**Delivery stamp:** Always update `FEATURE_DIR/delivery.json` after assessment (see Step 7).
|
|
75
|
+
That file is what `duaer check --gate` reads for machine-checkable acceptance.
|
|
76
|
+
|
|
74
77
|
## Operating Constraints
|
|
75
78
|
|
|
76
|
-
**APPEND-ONLY,
|
|
77
|
-
|
|
79
|
+
**APPEND-ONLY FOR TASKS, STAMP FOR DELIVERY**: The command's writes are limited to:
|
|
80
|
+
|
|
81
|
+
1. appending a new `## Phase N: Convergence` section to `tasks.md` when findings exist;
|
|
82
|
+
2. writing / overwriting `FEATURE_DIR/delivery.json` (the machine-readable accept stamp).
|
|
83
|
+
|
|
84
|
+
It MUST NOT:
|
|
78
85
|
|
|
79
86
|
- modify `spec.md` or `plan.md` in any way;
|
|
80
87
|
- rewrite, renumber, reorder, or delete any existing task (including tasks from a prior
|
|
@@ -83,7 +90,8 @@ of the code relative to the feature's artifacts — no git, no branch comparison
|
|
|
83
90
|
job of `/duaer-implement`.
|
|
84
91
|
|
|
85
92
|
When the codebase already satisfies everything, the command MUST leave `tasks.md`
|
|
86
|
-
**byte-for-byte unchanged** (no empty Convergence header) and
|
|
93
|
+
**byte-for-byte unchanged** (no empty Convergence header) and still write
|
|
94
|
+
`delivery.json` with `status: "accepted"`.
|
|
87
95
|
|
|
88
96
|
**Constitution Authority**: The project constitution (`.duaer/memory/constitution.md`) is
|
|
89
97
|
**non-negotiable**. Code that violates a MUST principle is the highest-severity finding and
|
|
@@ -221,20 +229,51 @@ Append to the **end** of `tasks.md`, per the append contract:
|
|
|
221
229
|
`CRITICAL`.
|
|
222
230
|
4. Never reuse or renumber existing IDs. If a prior Convergence phase exists, add a new,
|
|
223
231
|
separately-numbered one below it — do not touch the old one.
|
|
232
|
+
5. **Write / overwrite** `FEATURE_DIR/delivery.json`:
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"schemaVersion": 1,
|
|
237
|
+
"status": "open",
|
|
238
|
+
"outcome": "tasks_appended",
|
|
239
|
+
"checkedAt": "<ISO-8601 UTC>",
|
|
240
|
+
"openTasks": <count of - [ ] in tasks.md after append>,
|
|
241
|
+
"findings": <number of findings appended>,
|
|
242
|
+
"source": "duaer-converge"
|
|
243
|
+
}
|
|
244
|
+
```
|
|
224
245
|
|
|
225
246
|
**If there are no actionable findings** (`converged` outcome):
|
|
226
247
|
|
|
227
248
|
- Do **not** modify `tasks.md` at all — no empty phase header.
|
|
249
|
+
- **Write / overwrite** `FEATURE_DIR/delivery.json` with:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"schemaVersion": 1,
|
|
254
|
+
"status": "accepted",
|
|
255
|
+
"outcome": "converged",
|
|
256
|
+
"checkedAt": "<ISO-8601 UTC>",
|
|
257
|
+
"openTasks": 0,
|
|
258
|
+
"findings": 0,
|
|
259
|
+
"source": "duaer-converge"
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Set `openTasks` to the count of unchecked `- [ ]` lines currently in `tasks.md`
|
|
264
|
+
(must be `0` for `status: "accepted"`; if any remain, treat as `tasks_appended`
|
|
265
|
+
path instead — do not stamp accepted).
|
|
228
266
|
- Report: **"✅ Converged — the implementation satisfies the spec, plan, and tasks."**
|
|
229
267
|
- Include the summary counts of what was checked.
|
|
230
|
-
|
|
268
|
+
- Tell the user they can run `duaer check . --gate` to verify the machine gate.
|
|
231
269
|
### 8. Provide Next Actions (Handoff)
|
|
232
270
|
|
|
233
271
|
- On `tasks_appended`: state how many tasks were appended under which phase, and recommend
|
|
234
272
|
running `/duaer-implement` to complete them; note that a follow-up converge
|
|
235
|
-
run will find fewer or no remaining items.
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
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.
|
|
238
277
|
|
|
239
278
|
### 9. Check for extension hooks
|
|
240
279
|
|
|
@@ -35,3 +35,8 @@ Do not expand scope without updating the spec and tasks.
|
|
|
35
35
|
|
|
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
|
+
|
|
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.
|
package/ADOPT.md
CHANGED
|
@@ -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.
|
|
14
|
+
npx github:fujiezee/duaer-spec@v0.2.0 duaer init --here
|
|
15
15
|
|
|
16
16
|
# Lite (method only) / ops only / other integration branch
|
|
17
17
|
npx duaer-spec init --here --method
|
|
@@ -31,9 +31,10 @@ Then:
|
|
|
31
31
|
|
|
32
32
|
1. **Orient** — edit `.duaer/memory/constitution.md` and `project-context.md`
|
|
33
33
|
2. **Confirm workplace** — `docs/baseline.md` (integration branch)
|
|
34
|
-
3. **Assign → work → accept** — `/duaer-specify` → `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` → `/duaer-converge`
|
|
34
|
+
3. **Assign → work → accept → gate** — `/duaer-specify` → `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` → `/duaer-converge` → `duaer check . --gate`
|
|
35
35
|
|
|
36
|
-
A job without Spec is not assigned.
|
|
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
38
|
|
|
38
39
|
## Manual copy (optional)
|
|
39
40
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-15
|
|
4
|
+
|
|
5
|
+
### Controllable delivery (machine gate)
|
|
6
|
+
|
|
7
|
+
- `/duaer-converge` writes `.duaer/specs/<feature>/delivery.json`
|
|
8
|
+
(`accepted` | `open`)
|
|
9
|
+
- `duaer check` reports workplace **and** delivery status
|
|
10
|
+
- `duaer check --gate` fails on missing Spec, open tasks, or non-accepted stamp
|
|
11
|
+
- Constitution, DUADER, README, ADOPT, and merge checklist require the gate
|
|
12
|
+
- Honest scope: gate checks **handoff state**; Spec↔code judgment remains
|
|
13
|
+
agent-assisted
|
|
14
|
+
|
|
15
|
+
### E2E
|
|
16
|
+
|
|
17
|
+
- E2E-003 / E2E-004 / E2E-005 for delivery report and gate
|
|
18
|
+
|
|
3
19
|
## 0.1.1 — 2026-09-15
|
|
4
20
|
|
|
5
21
|
### Docs / product identity
|
package/DUADER.md
CHANGED
|
@@ -28,12 +28,13 @@ over anything here for isolation, commits, and Issue/PR gates.
|
|
|
28
28
|
6. `/duaer-tasks` — checkbox task breakdown
|
|
29
29
|
7. `/duaer-analyze` — optional consistency check
|
|
30
30
|
8. `/duaer-implement` — implement tasks only
|
|
31
|
-
9. `/duaer-converge` — **Accept**: compare result to Spec; append remaining tasks if gaps
|
|
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
|
|
32
33
|
|
|
33
|
-
**Small feature:** specify → plan → tasks → implement → converge
|
|
34
|
+
**Small feature:** specify → plan → tasks → implement → converge → `check --gate`
|
|
34
35
|
|
|
35
|
-
**Hotfix:** specify (mark hotfix) → tasks → implement → converge
|
|
36
|
-
Never skip specify (assign)
|
|
36
|
+
**Hotfix:** specify (mark hotfix) → tasks → implement → converge → `check --gate`
|
|
37
|
+
Never skip specify (assign), converge (accept), or the gate.
|
|
37
38
|
|
|
38
39
|
## Install
|
|
39
40
|
|
|
@@ -48,10 +49,11 @@ installs this repository's method files into the target project.
|
|
|
48
49
|
|
|
49
50
|
```text
|
|
50
51
|
.duaer/specs/<nnn-slug>/
|
|
51
|
-
spec.md
|
|
52
|
-
plan.md
|
|
53
|
-
tasks.md
|
|
54
|
-
|
|
52
|
+
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
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
## Related
|
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
**digital employees**.
|
|
5
5
|
|
|
6
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
|
|
8
|
-
|
|
7
|
+
Brief (Spec), follows a fixed work order, and only counts as done when the
|
|
8
|
+
**delivery gate** passes — not after a lucky chat.
|
|
9
9
|
|
|
10
10
|
Cursor is the labor. Duaer is hire → assign → accept.
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ Cursor is the labor. Duaer is hire → assign → accept.
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npx duaer-spec init --here
|
|
16
|
-
# or pin: npx github:fujiezee/duaer-spec@v0.
|
|
16
|
+
# or pin: npx github:fujiezee/duaer-spec@v0.2.0 duaer init --here
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```bash
|
|
@@ -25,6 +25,7 @@ npx duaer-spec init --here --ops --branch develop
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
Verify: `npx duaer-spec check .`
|
|
28
|
+
Merge gate: `npx duaer-spec check . --gate`
|
|
28
29
|
Details: [`ADOPT.md`](ADOPT.md)
|
|
29
30
|
|
|
30
31
|
## How you run them
|
|
@@ -34,10 +35,22 @@ Details: [`ADOPT.md`](ADOPT.md)
|
|
|
34
35
|
| **Hire** | Install SOP + gates into the repo | `duaer init` |
|
|
35
36
|
| **Assign** | Write the Brief (what / why / acceptance) | `/duaer-specify` |
|
|
36
37
|
| **Work** | Plan, break down, implement | `/duaer-plan` → `/duaer-tasks` → `/duaer-implement` |
|
|
37
|
-
| **Accept** |
|
|
38
|
+
| **Accept** | Converge stamps `delivery.json`; gaps stay open | `/duaer-converge` |
|
|
39
|
+
| **Gate** | Machine check before merge | `duaer check . --gate` |
|
|
38
40
|
|
|
39
|
-
Never skip **Assign** or **Accept**. Hotfix may shorten the middle; Spec
|
|
40
|
-
converge stay.
|
|
41
|
+
Never skip **Assign** or **Accept**. Hotfix may shorten the middle; Spec,
|
|
42
|
+
converge, and the gate stay.
|
|
43
|
+
|
|
44
|
+
### What the gate actually enforces
|
|
45
|
+
|
|
46
|
+
`duaer check --gate` fails when any feature under `.duaer/specs/` has:
|
|
47
|
+
|
|
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.
|
|
41
54
|
|
|
42
55
|
## Two layers
|
|
43
56
|
|
|
@@ -51,7 +64,7 @@ When they conflict, **agent ops win** — process beats improvisation.
|
|
|
51
64
|
## Default loop
|
|
52
65
|
|
|
53
66
|
```text
|
|
54
|
-
constitution → specify → plan → tasks → implement → converge
|
|
67
|
+
constitution → specify → plan → tasks → implement → converge → check --gate
|
|
55
68
|
```
|
|
56
69
|
|
|
57
70
|
Small change: `specify → plan → tasks → implement → converge`
|
|
@@ -69,6 +82,7 @@ AGENTS.md Agent-ops contract (how employees operate)
|
|
|
69
82
|
DUADER.md Methodology conventions (how work is briefed)
|
|
70
83
|
ADOPT.md Onboarding guide
|
|
71
84
|
.duaer/ Memory, templates, workflows, scripts
|
|
85
|
+
.duaer/specs/<feature>/ Brief + tasks + delivery.json stamp
|
|
72
86
|
.cursor/rules/ Agent-ops + Duaer rules
|
|
73
87
|
.cursor/skills/ duaer-* skills
|
|
74
88
|
docs/agent/ Workflow detail + checklists
|
package/bin/duaer.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
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]
|
|
6
|
+
* duaer check [dir] [--workplace|--delivery|--gate]
|
|
7
7
|
* duaer version
|
|
8
8
|
* duaer help
|
|
9
9
|
*/
|
|
@@ -26,13 +26,13 @@ const PKG = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8'))
|
|
|
26
26
|
|
|
27
27
|
const USAGE = `duaer — Delivery OS for AI digital employees (duaer-spec ${PKG.version})
|
|
28
28
|
|
|
29
|
-
Hire agents into a repo, assign Briefs (Specs), accept only when
|
|
29
|
+
Hire agents into a repo, assign Briefs (Specs), accept only when the gate passes.
|
|
30
30
|
|
|
31
31
|
Usage:
|
|
32
|
-
duaer init [dir] [options]
|
|
33
|
-
duaer check [dir]
|
|
34
|
-
duaer version
|
|
35
|
-
duaer help
|
|
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
36
|
|
|
37
37
|
Init options:
|
|
38
38
|
--all Full hire: agent ops + method (default)
|
|
@@ -42,11 +42,17 @@ Init options:
|
|
|
42
42
|
--branch <n> Integration branch for baseline note (default: main)
|
|
43
43
|
--here Same as dir=.
|
|
44
44
|
|
|
45
|
+
Check options:
|
|
46
|
+
--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)
|
|
51
|
+
|
|
45
52
|
Examples:
|
|
46
53
|
npx duaer-spec init --here
|
|
47
|
-
npx github:fujiezee/duaer-spec duaer init --here
|
|
48
|
-
node bin/duaer.mjs init ../my-app --all
|
|
49
54
|
duaer check .
|
|
55
|
+
duaer check . --gate
|
|
50
56
|
`
|
|
51
57
|
|
|
52
58
|
function parseArgs(argv) {
|
|
@@ -57,6 +63,9 @@ function parseArgs(argv) {
|
|
|
57
63
|
mode: 'all',
|
|
58
64
|
force: false,
|
|
59
65
|
branch: 'main',
|
|
66
|
+
workplace: false,
|
|
67
|
+
delivery: false,
|
|
68
|
+
gate: false,
|
|
60
69
|
}
|
|
61
70
|
const rest = args.slice(1)
|
|
62
71
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -66,6 +75,9 @@ function parseArgs(argv) {
|
|
|
66
75
|
else if (a === '--ops') out.mode = 'ops'
|
|
67
76
|
else if (a === '--force') out.force = true
|
|
68
77
|
else if (a === '--here') out.dir = '.'
|
|
78
|
+
else if (a === '--workplace') out.workplace = true
|
|
79
|
+
else if (a === '--delivery') out.delivery = true
|
|
80
|
+
else if (a === '--gate') out.gate = true
|
|
69
81
|
else if (a === '--branch') {
|
|
70
82
|
out.branch = rest[++i]
|
|
71
83
|
if (!out.branch) throw new Error('--branch requires a value')
|
|
@@ -89,7 +101,6 @@ function copyPath(from, to, { force }) {
|
|
|
89
101
|
if (existsSync(to) && !force) {
|
|
90
102
|
const st = statSync(to)
|
|
91
103
|
if (st.isDirectory()) {
|
|
92
|
-
// merge: copy children carefully
|
|
93
104
|
for (const name of readdirSync(from)) {
|
|
94
105
|
copyPath(join(from, name), join(to, name), { force })
|
|
95
106
|
}
|
|
@@ -228,16 +239,94 @@ Next (digital employee loop):
|
|
|
228
239
|
2. Workplace — confirm docs/baseline.md (integration branch: ${opts.branch})
|
|
229
240
|
3. Assign — /duaer-specify (Brief: what / why / acceptance)
|
|
230
241
|
4. Work — /duaer-plan → /duaer-tasks → /duaer-implement
|
|
231
|
-
5. Accept — /duaer-converge (
|
|
232
|
-
6.
|
|
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
|
|
233
245
|
|
|
234
|
-
No Spec = not assigned.
|
|
246
|
+
No Spec = not assigned. Open tasks or open delivery.json = not accepted.
|
|
235
247
|
See DUADER.md and AGENTS.md in the target project.
|
|
236
248
|
`)
|
|
237
249
|
}
|
|
238
250
|
|
|
239
|
-
function
|
|
240
|
-
|
|
251
|
+
function countOpenTasks(tasksPath) {
|
|
252
|
+
if (!existsSync(tasksPath)) return null
|
|
253
|
+
const text = readFileSync(tasksPath, 'utf8')
|
|
254
|
+
const open = (text.match(/^\s*-\s*\[\s\]/gm) || []).length
|
|
255
|
+
const done = (text.match(/^\s*-\s*\[x\]/gim) || []).length
|
|
256
|
+
return { open, done }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function readDelivery(path) {
|
|
260
|
+
if (!existsSync(path)) return null
|
|
261
|
+
try {
|
|
262
|
+
return JSON.parse(readFileSync(path, 'utf8'))
|
|
263
|
+
} catch {
|
|
264
|
+
return { status: 'invalid', error: 'unreadable delivery.json' }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function listFeatures(target) {
|
|
269
|
+
const root = join(target, '.duaer', 'specs')
|
|
270
|
+
if (!existsSync(root)) return []
|
|
271
|
+
return readdirSync(root)
|
|
272
|
+
.filter((name) => {
|
|
273
|
+
try {
|
|
274
|
+
return statSync(join(root, name)).isDirectory()
|
|
275
|
+
} catch {
|
|
276
|
+
return false
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
.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
|
+
}
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
name,
|
|
320
|
+
hasSpec,
|
|
321
|
+
tasks,
|
|
322
|
+
stamp,
|
|
323
|
+
verdict,
|
|
324
|
+
blockers,
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function checkWorkplace(target) {
|
|
241
330
|
const checks = [
|
|
242
331
|
['.duaer/memory/constitution.md', 'method'],
|
|
243
332
|
['DUADER.md', 'method'],
|
|
@@ -248,17 +337,95 @@ function cmdCheck(dir) {
|
|
|
248
337
|
['docs/agent/workflow.md', 'ops'],
|
|
249
338
|
]
|
|
250
339
|
let missing = 0
|
|
251
|
-
console.log(
|
|
340
|
+
console.log('## Workplace\n')
|
|
252
341
|
for (const [rel, kind] of checks) {
|
|
253
342
|
const ok = existsSync(join(target, rel))
|
|
254
343
|
console.log(`${ok ? 'ok ' : 'MISS'} [${kind}] ${rel}`)
|
|
255
344
|
if (!ok) missing++
|
|
256
345
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
346
|
+
return missing
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function checkDelivery(target, { gate }) {
|
|
350
|
+
const features = listFeatures(target)
|
|
351
|
+
console.log('\n## Delivery\n')
|
|
352
|
+
if (features.length === 0) {
|
|
353
|
+
console.log('ok no features under .duaer/specs/ (nothing to accept)')
|
|
354
|
+
return { blocked: 0, unaccepted: 0, features: 0 }
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let blocked = 0
|
|
358
|
+
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
|
+
}
|
|
368
|
+
if (f.verdict === 'blocked') blocked++
|
|
369
|
+
if (f.verdict === 'unaccepted') unaccepted++
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (gate) {
|
|
373
|
+
console.log('\nGate mode: every feature must be accepted.')
|
|
374
|
+
}
|
|
375
|
+
return { blocked, unaccepted, features: features.length }
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function cmdCheck(opts) {
|
|
379
|
+
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
|
|
383
|
+
|
|
384
|
+
console.log(`Checking ${target}${opts.gate ? ' (gate)' : ''}\n`)
|
|
385
|
+
|
|
386
|
+
let workplaceMissing = 0
|
|
387
|
+
if (runWorkplace) {
|
|
388
|
+
workplaceMissing = checkWorkplace(target)
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let delivery = { blocked: 0, unaccepted: 0, features: 0 }
|
|
392
|
+
if (runDelivery) {
|
|
393
|
+
delivery = checkDelivery(target, { gate: opts.gate })
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let fail = false
|
|
397
|
+
if (workplaceMissing) {
|
|
398
|
+
console.log(`\n${workplaceMissing} workplace file(s) missing — run: duaer init ${opts.dir} --all`)
|
|
399
|
+
fail = true
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (runDelivery) {
|
|
403
|
+
if (delivery.blocked) {
|
|
404
|
+
console.log(`\n${delivery.blocked} feature(s) blocked — close open tasks / fix Spec / re-run converge`)
|
|
405
|
+
fail = true
|
|
406
|
+
}
|
|
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
|
+
}
|
|
418
|
+
|
|
419
|
+
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.')
|
|
424
|
+
} else {
|
|
425
|
+
console.log('\nDelivery report done.')
|
|
426
|
+
}
|
|
260
427
|
} else {
|
|
261
|
-
|
|
428
|
+
process.exitCode = 1
|
|
262
429
|
}
|
|
263
430
|
}
|
|
264
431
|
|
|
@@ -278,7 +445,7 @@ function main() {
|
|
|
278
445
|
cmdInit(opts)
|
|
279
446
|
break
|
|
280
447
|
case 'check':
|
|
281
|
-
cmdCheck(opts
|
|
448
|
+
cmdCheck(opts)
|
|
282
449
|
break
|
|
283
450
|
case 'version':
|
|
284
451
|
case '--version':
|
|
@@ -94,6 +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
98
|
- [ ] Branch refreshed against latest `main`.
|
|
98
99
|
- [ ] Merged into local `main` (and/or remote PR/MR when required).
|
|
99
100
|
- [ ] Only this request's logical changes included.
|
|
@@ -121,6 +122,7 @@ See [spec update guidance](workflow.md#3-spec-update-guidance).
|
|
|
121
122
|
| 3 | Impacted specs updated | [R1](workflow.md#r1--spec-first--spec-sync) |
|
|
122
123
|
| 4 | E2E docs updated or confirmed unnecessary | [R3](workflow.md#r3--e2e-coverage-doc) |
|
|
123
124
|
| 5 | Targeted validation done or waived with reason | Development loop |
|
|
125
|
+
| 5a | `duaer check . --gate` when features exist | Delivery OS gate |
|
|
124
126
|
| 6 | Conventional commits | [R2](workflow.md#r2--commit-per-change) |
|
|
125
127
|
| 7 | No secrets or local data | [§4](workflow.md#4-what-never-to-commit) |
|
|
126
128
|
| 8 | Merged into `main`; worktree and branch removed | [R4](workflow.md#r4--request-branch--worktree--merge-gate) · [§6.1](#61-merge-cleanup) |
|
|
@@ -27,6 +27,9 @@ 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
33
|
|
|
31
34
|
## Traceability
|
|
32
35
|
|
|
@@ -36,3 +39,6 @@ Keep this matrix current when scenarios or specs change.
|
|
|
36
39
|
|---|---|---|
|
|
37
40
|
| E2E-001 | `bin/duaer.mjs` init banner | Controllable-delivery onboarding |
|
|
38
41
|
| 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-005 | `/duaer-converge` → `delivery.json` | Accept stamp |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duaer-spec",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Delivery OS for AI digital employees: Spec briefs, agent ops,
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Delivery OS for AI digital employees: Spec briefs, agent ops, check --gate handoffs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"duaer": "bin/duaer.mjs",
|