dw-kit 1.9.0-rc.1 → 1.9.1
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/.claude/hooks/stop-check.sh +10 -0
- package/.claude/skills/dw-decision/SKILL.md +2 -1
- package/.claude/skills/dw-kit-report/SKILL.md +38 -7
- package/.dw/core/schemas/decision-frontmatter.schema.json +54 -0
- package/.dw/core/schemas/goal-frontmatter.schema.json +2 -2
- package/.dw/core/schemas/task-frontmatter.schema.json +2 -2
- package/.dw/core/templates/v3/task.md +41 -19
- package/README.md +5 -2
- package/package.json +5 -3
- package/src/cli.mjs +33 -0
- package/src/commands/decision-index.mjs +45 -0
- package/src/commands/goal-delete.mjs +3 -1
- package/src/commands/goal-link.mjs +3 -1
- package/src/commands/goal-status.mjs +95 -0
- package/src/commands/lint-task.mjs +20 -0
- package/src/commands/task-index.mjs +47 -0
- package/src/commands/task-migrate.mjs +16 -5
- package/src/commands/task-new.mjs +6 -0
- package/src/commands/task-summary.mjs +4 -3
- package/src/lib/decision-store.mjs +146 -0
- package/src/lib/goal-store.mjs +42 -15
- package/src/lib/lint-rules.mjs +10 -1
- package/src/lib/task-store.mjs +164 -0
- package/src/lib/timeline-parser.mjs +54 -15
- package/.dw/core/PILLARS.md +0 -122
- package/CLAUDE.md +0 -44
|
@@ -85,6 +85,16 @@ if [ "$ACTIVE_TASK_FORMAT" = "v3" ] && [ -n "$ACTIVE_TASK" ]; then
|
|
|
85
85
|
fi
|
|
86
86
|
fi
|
|
87
87
|
|
|
88
|
+
# --- Refresh tasks-index@v1 (ADR-0017 doc contract) — fire-and-forget ---
|
|
89
|
+
# Keeps the adapter read-contract (.dw/tasks/tasks-index.json) fresh after manual
|
|
90
|
+
# task.md edits. Idempotent at the writer (writeTaskIndex sorts keys + skips the
|
|
91
|
+
# write when the task set is byte-identical), so an unchanged set produces ZERO
|
|
92
|
+
# git diff and never blocks a post-commit `git checkout` (#21). Silent; errors ignored.
|
|
93
|
+
DW_BIN="$PROJECT_DIR/bin/dw.mjs"
|
|
94
|
+
if [ -f "$DW_BIN" ] && [ -d "$PROJECT_DIR/.dw/tasks" ] && command -v node >/dev/null 2>&1; then
|
|
95
|
+
node "$DW_BIN" task index >/dev/null 2>&1 || true
|
|
96
|
+
fi
|
|
97
|
+
|
|
88
98
|
# --- Auto-handoff: append snippet to active task's tracking/timeline file if uncommitted ---
|
|
89
99
|
if [ "$HAS_UNCOMMITTED" = "1" ] && [ -n "$ACTIVE_TASK_FILE" ]; then
|
|
90
100
|
TS=$(date -u +"%Y-%m-%d %H:%M UTC")
|
|
@@ -73,12 +73,13 @@ Show preview (first 30 lines) rồi ask:
|
|
|
73
73
|
Save this ADR to .dw/decisions/{NNNN}-{title}.md? (Y/n)
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
Nếu yes → write file
|
|
76
|
+
Nếu yes → write file, sau đó chạy `dw decision index` (cập nhật `decisions-index@v1` — read-contract cho adapter, ADR-0017 v1.1), rồi print:
|
|
77
77
|
```
|
|
78
78
|
✓ ADR-{NNNN} created
|
|
79
79
|
Status: Proposed
|
|
80
80
|
Next: Review → change status to Accepted when approved
|
|
81
81
|
Related task: update task.md (v3) / tracking.md (v2) với reference
|
|
82
|
+
decisions-index refreshed (dw decision index)
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
## Quality Bar
|
|
@@ -17,6 +17,18 @@ $ARGUMENTS
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
## Nguyên tắc riêng tư (ĐỌC TRƯỚC — bắt buộc)
|
|
21
|
+
|
|
22
|
+
Skill này tạo issue trên **repo bên thứ ba `dv-workflow/dv-workflow`** (ngoài project của bạn). Vì vậy **KHÔNG bao giờ tự động đưa thông tin nhận dạng của project người report vào issue**:
|
|
23
|
+
|
|
24
|
+
- ❌ KHÔNG kèm `project.name` từ config (đây là tên codebase/đơn vị của người dùng).
|
|
25
|
+
- ❌ KHÔNG kèm tên task, mã ticket nội bộ (vd `PROJ-123`), đường dẫn file độc quyền, hay code snippet.
|
|
26
|
+
- ✅ CHỈ gửi: loại feedback, component của **dw-kit**, mô tả vấn đề (từ `$ARGUMENTS`), và môi trường tối thiểu (OS, shell, dw version).
|
|
27
|
+
|
|
28
|
+
Nếu chính `$ARGUMENTS` chứa thông tin nhận dạng nội bộ → cảnh báo người dùng và đề nghị lược bỏ. **Luôn preview + xin xác nhận trước khi gửi (Bước 4).**
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
20
32
|
## Bước 1: Thu Thập Context
|
|
21
33
|
|
|
22
34
|
**OS detection:**
|
|
@@ -24,9 +36,9 @@ $ARGUMENTS
|
|
|
24
36
|
uname -s 2>/dev/null || echo "Windows"
|
|
25
37
|
```
|
|
26
38
|
|
|
27
|
-
**dw version:** Đọc `_toolkit.core_version` từ `.dw/config/dw.config.yml`
|
|
39
|
+
**dw version:** Đọc `_toolkit.core_version` từ `.dw/config/dw.config.yml` (KHÔNG đọc `project.name`).
|
|
28
40
|
|
|
29
|
-
**
|
|
41
|
+
**Workflow context:** Có task In Progress không, và depth của nó (quick/standard/thorough) — chỉ để giúp reproduce. Ghi nhận CÓ/KHÔNG + depth, **KHÔNG lấy tên task**.
|
|
30
42
|
|
|
31
43
|
---
|
|
32
44
|
|
|
@@ -83,8 +95,8 @@ Ví dụ:
|
|
|
83
95
|
[Nội dung từ $ARGUMENTS — đầy đủ, rõ ràng]
|
|
84
96
|
|
|
85
97
|
## Context
|
|
86
|
-
-
|
|
87
|
-
- Command/skill liên quan: [nếu biết]
|
|
98
|
+
- Hoàn cảnh: ["general usage" hoặc "trong một task depth=X" — KHÔNG ghi tên task/ticket]
|
|
99
|
+
- Command/skill dw-kit liên quan: [nếu biết]
|
|
88
100
|
- Bước reproduce (nếu là bug):
|
|
89
101
|
1. ...
|
|
90
102
|
2. ...
|
|
@@ -95,12 +107,31 @@ Ví dụ:
|
|
|
95
107
|
- [ ] Minor — annoying, có workaround dễ
|
|
96
108
|
|
|
97
109
|
---
|
|
98
|
-
*Reported via `/dw:kit-report
|
|
110
|
+
*Reported via `/dw:kit-report`*
|
|
99
111
|
```
|
|
100
112
|
|
|
113
|
+
> Không thêm dòng `Project:` hay bất kỳ tên project/ticket/path nội bộ nào (xem Nguyên tắc riêng tư).
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Bước 4: Preview + Xác Nhận (BẮT BUỘC trước khi gửi)
|
|
118
|
+
|
|
119
|
+
Issue sẽ được tạo trên repo bên thứ ba **công khai với dw-kit team**. Trước khi gửi, hiện đầy đủ title + body sẽ post và CHỜ người dùng đồng ý:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
┌─ Sẽ gửi lên github.com/dv-workflow/dv-workflow ─────────────┐
|
|
123
|
+
TITLE: [title]
|
|
124
|
+
|
|
125
|
+
BODY:
|
|
126
|
+
[body đầy đủ]
|
|
127
|
+
└─ Gõ "ok" để gửi · hoặc sửa nội dung trước ──────────────────┘
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Trước khi hiện preview, **rà soát lần cuối**: nếu title/body (kể cả phần người dùng tự nhập trong `$ARGUMENTS`) còn chứa tên project, mã ticket nội bộ, đường dẫn file độc quyền, hay code snippet → cảnh báo rõ và đề nghị lược bỏ. **Chỉ sang Bước 5 sau khi người dùng xác nhận.**
|
|
131
|
+
|
|
101
132
|
---
|
|
102
133
|
|
|
103
|
-
## Bước
|
|
134
|
+
## Bước 5: Gửi Lên GitHub
|
|
104
135
|
|
|
105
136
|
**Kiểm tra `gh` CLI:**
|
|
106
137
|
```bash
|
|
@@ -141,7 +172,7 @@ In ra:
|
|
|
141
172
|
|
|
142
173
|
---
|
|
143
174
|
|
|
144
|
-
## Bước
|
|
175
|
+
## Bước 6: Xác Nhận
|
|
145
176
|
|
|
146
177
|
```
|
|
147
178
|
✓ Issue đã được gửi: https://github.com/dv-workflow/dv-workflow/issues/[N]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/dv-workflow/dv-workflow/blob/main/.dw/core/schemas/decision-frontmatter.schema.json",
|
|
4
|
+
"title": "decision-frontmatter",
|
|
5
|
+
"description": "Normalized ADR metadata for decisions-index@v1 (ADR-0017 v1.1). ADRs use two on-disk styles: YAML frontmatter (early, e.g. ADR-0001) OR markdown headers `# ADR-NNNN: Title` + `## Status:`/`## Date:`/`## Deciders:`/`## Related:`. The `dw decision index` parser tolerates both and projects to this normalized shape. New ADRs MAY adopt YAML frontmatter with these keys for lossless extraction.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["title", "status"],
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {
|
|
10
|
+
"title": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "ADR title (without the `ADR-NNNN:` prefix)"
|
|
13
|
+
},
|
|
14
|
+
"status": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"enum": ["Proposed", "Accepted", "Deprecated", "Superseded"],
|
|
17
|
+
"description": "Normalized lifecycle status keyword (extracted from a possibly-decorated status line, e.g. 'Accepted (Round 2, ...)')"
|
|
18
|
+
},
|
|
19
|
+
"status_raw": {
|
|
20
|
+
"type": ["string", "null"],
|
|
21
|
+
"description": "Original status string before normalization"
|
|
22
|
+
},
|
|
23
|
+
"date": {
|
|
24
|
+
"type": ["string", "null"],
|
|
25
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
26
|
+
"description": "First ISO date found in the Date field/header (YYYY-MM-DD)"
|
|
27
|
+
},
|
|
28
|
+
"deciders": {
|
|
29
|
+
"type": ["string", "null"],
|
|
30
|
+
"description": "Free-form deciders (names/roles)"
|
|
31
|
+
},
|
|
32
|
+
"related": {
|
|
33
|
+
"type": "array",
|
|
34
|
+
"items": { "type": "string", "pattern": "^ADR-\\d{3,4}$" },
|
|
35
|
+
"uniqueItems": true,
|
|
36
|
+
"description": "Referenced ADRs (from Related field/header + status line); excludes self. Causal-link candidates for org-memory ingest."
|
|
37
|
+
},
|
|
38
|
+
"supersedes": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"items": { "type": "string", "pattern": "^ADR-\\d{3,4}$" },
|
|
41
|
+
"uniqueItems": true,
|
|
42
|
+
"description": "ADRs this one supersedes"
|
|
43
|
+
},
|
|
44
|
+
"superseded_by": {
|
|
45
|
+
"type": ["string", "null"],
|
|
46
|
+
"pattern": "^ADR-\\d{3,4}$",
|
|
47
|
+
"description": "ADR that supersedes this one (from 'Superseded by ADR-NNNN' or frontmatter)"
|
|
48
|
+
},
|
|
49
|
+
"file": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Repo-relative path to the ADR markdown file"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -73,12 +73,12 @@
|
|
|
73
73
|
"icon": {
|
|
74
74
|
"type": ["string", "null"],
|
|
75
75
|
"maxLength": 8,
|
|
76
|
-
"description": "Optional emoji/icon for visual differentiation in portfolio cards
|
|
76
|
+
"description": "Optional emoji/icon for visual differentiation in portfolio cards."
|
|
77
77
|
},
|
|
78
78
|
"cycle": {
|
|
79
79
|
"type": ["string", "null"],
|
|
80
80
|
"maxLength": 32,
|
|
81
|
-
"description": "Optional temporal grouping label e.g. 'Q1 2026', 'v1.7-cycle', '2026 H1'
|
|
81
|
+
"description": "Optional temporal grouping label e.g. 'Q1 2026', 'v1.7-cycle', '2026 H1'. Portfolio groups goals by cycle."
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"schema_version": {
|
|
69
69
|
"type": "string",
|
|
70
|
-
"enum": ["v3.0", "v3.1"],
|
|
71
|
-
"description": "Frontmatter schema version. v3.1 adds optional parent_goal_id, contributing_goal_ids, summary per ADR-0010."
|
|
70
|
+
"enum": ["task@v3.0", "task@v3.1", "v3.0", "v3.1"],
|
|
71
|
+
"description": "Frontmatter schema version. Canonical form is namespaced (task@v3.0/task@v3.1) to match the repo-wide @v convention (goal@v1, tasks-index@v1); bare v3.0/v3.1 stay valid for back-compat. v3.1 adds optional parent_goal_id, contributing_goal_ids, summary per ADR-0010 (#22)."
|
|
72
72
|
},
|
|
73
73
|
"blockers": {
|
|
74
74
|
"type": "string",
|
|
@@ -8,7 +8,7 @@ owner: {name}
|
|
|
8
8
|
depth: quick | standard | thorough
|
|
9
9
|
related_adr: {ADR-NNNN | none}
|
|
10
10
|
target_ship: {milestone or TBD}
|
|
11
|
-
schema_version: v3.0
|
|
11
|
+
schema_version: task@v3.0
|
|
12
12
|
blockers: none
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -66,25 +66,42 @@ genuinely changes.
|
|
|
66
66
|
|
|
67
67
|
{Forcing function: deadline, incident, dependency, opportunity.}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
**ST-2: {Subtask name}**
|
|
76
|
-
- ...
|
|
69
|
+
<!--
|
|
70
|
+
Subtasks live in the Section 3 tracker (one list, with per-subtask Acceptance +
|
|
71
|
+
Est columns) — NOT duplicated here. Section 2 stays the stable intent contract;
|
|
72
|
+
Section 3 owns the subtask breakdown + status.
|
|
73
|
+
-->
|
|
77
74
|
|
|
78
75
|
### Out of Scope
|
|
79
76
|
|
|
80
77
|
- {Explicit exclusions — prevents scope creep}
|
|
81
78
|
|
|
82
|
-
###
|
|
79
|
+
### Functional Acceptance (handoff scenarios)
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
<!--
|
|
82
|
+
Feature behaviour in business / user language — readable by PM/BA/QC WITHOUT code
|
|
83
|
+
(no API/file/variable names). This is the cross-role handoff contract, NOT a full
|
|
84
|
+
test plan (`/dw:test-plan` expands it into QC cases). Per-component acceptance
|
|
85
|
+
lives with each subtask; this is the feature-level scenario layer.
|
|
86
|
+
-->
|
|
85
87
|
|
|
86
|
-
- [ ] {
|
|
87
|
-
- [ ] {
|
|
88
|
+
- [ ] {User-visible scenario, e.g., "User resets password and gets a confirmation email"}
|
|
89
|
+
- [ ] {Error / edge scenario, e.g., "Expired reset link shows a clear retry prompt"}
|
|
90
|
+
- [ ] {Measurable gate where relevant, e.g., "p95 latency <100ms"}
|
|
91
|
+
|
|
92
|
+
### Definition of Done (role lens)
|
|
93
|
+
|
|
94
|
+
<!--
|
|
95
|
+
Plain-language sign-off anchored to the outcome. Roles absent from `team.roles`
|
|
96
|
+
sign by proxy / degrade gracefully. These are `- [ ]` checkboxes (NOT status
|
|
97
|
+
markers — those live only in Section 3).
|
|
98
|
+
-->
|
|
99
|
+
|
|
100
|
+
- [ ] 🎯 **Goal** — result advances the linked goal's KR (update the goal)
|
|
101
|
+
- [ ] 📋 **BA** — matches the business expectation
|
|
102
|
+
- [ ] ✅ **QC** — handoff scenarios pass, incl. error cases + no regression
|
|
103
|
+
- [ ] 🛠 **TL** — approach/code sound, no new tech debt, automated checks pass
|
|
104
|
+
- [ ] 🤝 **Handoff** — docs sufficient + changes merged
|
|
88
105
|
|
|
89
106
|
### Dependencies
|
|
90
107
|
|
|
@@ -100,14 +117,19 @@ Measurable outcomes (not vibes):
|
|
|
100
117
|
## 3. Subtask Tracker
|
|
101
118
|
|
|
102
119
|
<!--
|
|
103
|
-
SINGLE SOURCE OF TRUTH for subtask status.
|
|
104
|
-
|
|
120
|
+
SINGLE SOURCE OF TRUTH for the subtask breakdown AND status. The subtask list is
|
|
121
|
+
NOT duplicated in Section 2 (#24). This is the only section where status markers
|
|
122
|
+
(⬜🟡✅🔴⏸) are allowed — Section 2 stays status-free (drift-prevention).
|
|
123
|
+
|
|
124
|
+
Columns (read by name, order-tolerant): Acceptance = per-subtask verifiable
|
|
125
|
+
criterion; Est = optional effort estimate (hours / story-points / t-shirt per
|
|
126
|
+
`estimation_unit`, `—` if unused, see `/dw:estimate`).
|
|
105
127
|
-->
|
|
106
128
|
|
|
107
|
-
| # | Subtask |
|
|
108
|
-
|
|
109
|
-
| ST-1 |
|
|
110
|
-
| ST-2 | ... | ⬜ Pending |
|
|
129
|
+
| # | Subtask | Acceptance | Est | Status | Notes |
|
|
130
|
+
|---|---------|-----------|-----|--------|-------|
|
|
131
|
+
| ST-1 | {name} | {verifiable criterion} | — | ⬜ Pending | |
|
|
132
|
+
| ST-2 | {name} | ... | — | ⬜ Pending | |
|
|
111
133
|
|
|
112
134
|
Status legend: ⬜ Pending · 🟡 In Progress · ✅ Done · 🔴 Blocked · ⏸ Paused
|
|
113
135
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> An AI development workflow toolkit for teams using agentic IDEs (Claude Code, Cursor) — from idea to review-ready commits.
|
|
4
4
|
|
|
5
|
-
**v1.
|
|
5
|
+
**v1.9.1** · `npm install -g dw-kit` · [Docs](docs/README.md) · [Get started](docs/get-started.md) · [Cheatsheet](docs/cheatsheet.md) · [Migration v1.3](MIGRATION-v1.3.md) · [Changelog](CHANGELOG.md)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -36,7 +36,10 @@ It’s designed for collaboration (Dev / Tech Lead / QA / PM) and keeps work aud
|
|
|
36
36
|
|
|
37
37
|
## Release notes
|
|
38
38
|
|
|
39
|
-
- **v1.
|
|
39
|
+
- **v1.9.0 (current, stable)** — substrate-complete for the realtime voice-orchestration Root Goal: persistent CLI-agent sessions, Telegram bridge, multi-workspace registry, browser voice MVP + orchestrator action-execution, multi-agent live debate, and the **DW Event/Document schema + adapter protocol** ([ADR-0011](.dw/decisions/0011-session-runtime-voice-orchestrator.md)/[0014](.dw/decisions/0014-orchestrator-action-execution.md)/[0015](.dw/decisions/0015-multi-agent-live-debate.md)/[0016](.dw/decisions/0016-dw-goal-outcome-pursuit-loop.md)/[0017](.dw/decisions/0017-dw-document-schema-index.md)). Plus team-dogfood fixes: `dw goal status` (#20), deterministic index writers (#21), `task@v3.x` schema_version converge (#22), v3 template Functional Acceptance + role-lens DoD + Est (#24).
|
|
40
|
+
- **v1.8.0** — remote-agent-first substrate: `dw session *`, `dw connector telegram`, `dw workspace *`, `dw voice` (bilingual en+vi, hybrid orchestrator, action-execution with voice confirm).
|
|
41
|
+
- **v1.7.0** — **Goals Layer (OKR-inspired)** ([ADR-0010](.dw/decisions/0010-goals-management-layer.md)): strategic layer above tasks — `dw goal *`, `goals-index@v1`, bidirectional task↔goal linking, portfolio + HTML view.
|
|
42
|
+
- **v1.6.0** — **Agent OS Multi-Agent Orchestration** ([ADR-0009](.dw/decisions/0009-agent-os-multi-agent-orchestration.md)): file-based cooperative protocol for Claude Code / Codex / Gemini / human agents to work from same `task.md` without conflict. 6-command CLI `dw agent *` (claim / release / expire / claims / reports / conflicts). Dual ownership model: `subtasks` (semantic) + `write_scope` (filesystem). Audit trail via committed `reports/` + `events.jsonl`; claims gitignored ephemeral.
|
|
40
43
|
- **v1.5.0 — Task Docs v3** ([ADR-0008](.dw/decisions/0008-task-docs-format-one-file-timeline.md)): single-file `task.md` replaces v2 `spec.md` + `tracking.md` (drift fixed structurally). 8-command CLI `dw task *` (show / new / view / watch / render / lint / migrate / rotate). Lean HTML dashboard for human dev orchestrator (~10KB, scan-in-5-seconds) + bounded SVG sidecar via `dw-kit-render` v0.2. Live preview server with auto-refresh. Migration script with `--dry-run`, `--diff`, `--rollback`. Lint strict-default blocks drift markers. See [`MIGRATION-v1.5.md`](MIGRATION-v1.5.md).
|
|
41
44
|
- **v1.4 (in progress)** — Optional **Review Render Pipeline** ([ADR-0007](.dw/decisions/0007-decoupled-review-render-pipeline.md)): `/dw:review --visual` plus a separate `dw-kit-render` package turn findings into SVG + PNG cards for PR comments / Slack / stakeholders. Pure JS + WASM, universal `npm install`, no system deps. See [`docs/review-renderer.md`](docs/review-renderer.md).
|
|
42
45
|
- **v1.3.6** (2026-05-14) — Supply-Chain Guard upgraded to 3-pillar architecture: OSV snapshot + curated IoC fixture (version-aware, wired into default scan) + **AI-Native NEW-package heuristic** that catches zero-day-ish risk at the AI-edit boundary. See [`CHANGELOG.md#v136--2026-05-14`](CHANGELOG.md#v136--2026-05-14) and [ADR-0006](.dw/decisions/0006-supply-chain-guard-heuristic.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dw-kit",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "AI development workflow toolkit — structured, quality-assured, team-ready. From requirements to dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"src/commands/",
|
|
13
13
|
"src/lib/",
|
|
14
14
|
".dw/core/",
|
|
15
|
+
"!.dw/core/PILLARS.md",
|
|
15
16
|
".dw/config/",
|
|
16
17
|
".dw/adapters/",
|
|
17
18
|
".dw/security/",
|
|
@@ -50,7 +51,6 @@
|
|
|
50
51
|
".claude/skills/dw-upgrade/",
|
|
51
52
|
".claude/templates/",
|
|
52
53
|
".claude/settings.json",
|
|
53
|
-
"CLAUDE.md",
|
|
54
54
|
"MIGRATION-v1.3.md",
|
|
55
55
|
"MIGRATION-v1.5.md",
|
|
56
56
|
"NOTICE",
|
|
@@ -65,7 +65,9 @@
|
|
|
65
65
|
"test:renderer": "cd packages/dw-kit-render && npm test",
|
|
66
66
|
"link": "npm link",
|
|
67
67
|
"test:e2e-local": "bash scripts/e2e-local-check.sh",
|
|
68
|
-
"gen:event-schemas": "node scripts/generate-event-schemas.mjs"
|
|
68
|
+
"gen:event-schemas": "node scripts/generate-event-schemas.mjs",
|
|
69
|
+
"audit:pack": "node scripts/audit-pack.mjs",
|
|
70
|
+
"prepublishOnly": "node scripts/audit-pack.mjs"
|
|
69
71
|
},
|
|
70
72
|
"keywords": [
|
|
71
73
|
"ai",
|
package/src/cli.mjs
CHANGED
|
@@ -195,6 +195,30 @@ export function run(argv) {
|
|
|
195
195
|
await taskSummaryCommand(taskName, opts);
|
|
196
196
|
});
|
|
197
197
|
|
|
198
|
+
taskCmd
|
|
199
|
+
.command('index')
|
|
200
|
+
.description('Regenerate .dw/tasks/tasks-index.json — DW Document Schema + Index v1.0 read-contract (ADR-0017)')
|
|
201
|
+
.option('--json', 'Print the machine-readable index (for external adapters)')
|
|
202
|
+
.option('--check', 'Print the current index without rebuilding')
|
|
203
|
+
.action(async (opts) => {
|
|
204
|
+
const { taskIndexCommand } = await import('./commands/task-index.mjs');
|
|
205
|
+
await taskIndexCommand(opts);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const decisionCmd = program
|
|
209
|
+
.command('decision')
|
|
210
|
+
.description('ADR operations (ADR-0017 v1.1 document contract)');
|
|
211
|
+
|
|
212
|
+
decisionCmd
|
|
213
|
+
.command('index')
|
|
214
|
+
.description('Regenerate .dw/decisions/decisions-index.json — decisions-index@v1 read-contract (ADR-0017 v1.1; consumed by external org-memory adapters)')
|
|
215
|
+
.option('--json', 'Print the machine-readable index (for adapters)')
|
|
216
|
+
.option('--check', 'Print the current index without rebuilding')
|
|
217
|
+
.action(async (opts) => {
|
|
218
|
+
const { decisionIndexCommand } = await import('./commands/decision-index.mjs');
|
|
219
|
+
await decisionIndexCommand(opts);
|
|
220
|
+
});
|
|
221
|
+
|
|
198
222
|
const agentCmd = program
|
|
199
223
|
.command('agent')
|
|
200
224
|
.description('Agent OS multi-agent orchestration (ADR-0009): claim · release · renew · expire · claims · reports · conflicts · check-staged · verify');
|
|
@@ -495,6 +519,15 @@ export function run(argv) {
|
|
|
495
519
|
await goalSetCommand(goalId, opts);
|
|
496
520
|
});
|
|
497
521
|
|
|
522
|
+
goalCmd
|
|
523
|
+
.command('status <goal-id> <new-status>')
|
|
524
|
+
.description('Transition lifecycle status (Draft|Active|Achieved|Pivoted); auto-bumps goal_version + emits goal_status_changed. Use `goal delete` to abandon.')
|
|
525
|
+
.option('--reason <text>', 'Optional reason, recorded in the goal_status_changed event')
|
|
526
|
+
.action(async (goalId, newStatus, opts) => {
|
|
527
|
+
const { goalStatusCommand } = await import('./commands/goal-status.mjs');
|
|
528
|
+
await goalStatusCommand(goalId, newStatus, opts);
|
|
529
|
+
});
|
|
530
|
+
|
|
498
531
|
goalCmd
|
|
499
532
|
.command('show [goal-id]')
|
|
500
533
|
.description('ANSI snapshot of a goal (no arg = list all)')
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { rebuildDecisionIndex, readDecisionIndex, decisionIndexFile } from '../lib/decision-store.mjs';
|
|
3
|
+
import { logEvent } from '../lib/telemetry.mjs';
|
|
4
|
+
|
|
5
|
+
// `dw decision index` — regenerate .dw/decisions/decisions-index.json
|
|
6
|
+
// (DW Document Schema + Index v1.0, ADR-0017 v1.1). Read-contract surface for
|
|
7
|
+
// external org-memory adapters.
|
|
8
|
+
// dw decision index rebuild + human summary
|
|
9
|
+
// dw decision index --json rebuild + print machine-readable index
|
|
10
|
+
// dw decision index --check print current index without rebuilding
|
|
11
|
+
export async function decisionIndexCommand(opts = {}) {
|
|
12
|
+
const rootDir = process.cwd();
|
|
13
|
+
const index = opts.check ? readDecisionIndex(rootDir) : rebuildDecisionIndex(rootDir);
|
|
14
|
+
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify(index, null, 2));
|
|
17
|
+
logEvent({ event: 'decision', action: 'index.json', name: Object.keys(index.decisions).length }, rootDir);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const decisions = index.decisions || {};
|
|
22
|
+
const ids = Object.keys(decisions);
|
|
23
|
+
const byStatus = {};
|
|
24
|
+
for (const id of ids) {
|
|
25
|
+
const s = decisions[id].status || 'Proposed';
|
|
26
|
+
byStatus[s] = (byStatus[s] || 0) + 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(chalk.bold(` decisions-index (${chalk.cyan(index.schema_version)}) — ${ids.length} ADR(s)`));
|
|
31
|
+
console.log(chalk.dim(` ${decisionIndexFile(rootDir)}`));
|
|
32
|
+
console.log();
|
|
33
|
+
if (ids.length === 0) {
|
|
34
|
+
console.log(chalk.dim(' (no ADRs — create one with /dw:decision)'));
|
|
35
|
+
} else {
|
|
36
|
+
for (const [status, n] of Object.entries(byStatus)) {
|
|
37
|
+
console.log(` ${String(n).padStart(3)} ${status}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(chalk.dim(' Tip: `dw decision index --json` for the machine-readable adapter contract.'));
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
logEvent({ event: 'decision', action: opts.check ? 'index.check' : 'index.rebuild', name: ids.length }, rootDir);
|
|
45
|
+
}
|
|
@@ -2,7 +2,7 @@ import { readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { parseFrontmatter, stringifyFrontmatter } from '../lib/frontmatter.mjs';
|
|
5
|
-
import { readGoal, removeIndexEntry, findLinkedTaskIds, goalDir, todayIso, nowUtc } from '../lib/goal-store.mjs';
|
|
5
|
+
import { readGoal, removeIndexEntry, syncIndexEntry, findLinkedTaskIds, goalDir, todayIso, nowUtc } from '../lib/goal-store.mjs';
|
|
6
6
|
import { logGoalEvent } from '../lib/goal-events.mjs';
|
|
7
7
|
import { logEvent } from '../lib/telemetry.mjs';
|
|
8
8
|
|
|
@@ -94,6 +94,8 @@ export async function goalDeleteCommand(goalId, opts = {}) {
|
|
|
94
94
|
fm.last_updated = todayIso();
|
|
95
95
|
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
96
96
|
writeFileSync(goal.file, stringifyFrontmatter(fm) + body, 'utf8');
|
|
97
|
+
// Keep goals-index in sync — otherwise `goal lint` flags status drift (#20).
|
|
98
|
+
syncIndexEntry(goalId, rootDir);
|
|
97
99
|
|
|
98
100
|
logGoalEvent({
|
|
99
101
|
event: 'goal_status_changed',
|
|
@@ -5,6 +5,7 @@ import { parseFrontmatter, stringifyFrontmatter } from '../lib/frontmatter.mjs';
|
|
|
5
5
|
import { readGoal, syncIndexEntry, todayIso } from '../lib/goal-store.mjs';
|
|
6
6
|
import { logGoalEvent } from '../lib/goal-events.mjs';
|
|
7
7
|
import { logEvent } from '../lib/telemetry.mjs';
|
|
8
|
+
import { bareSchemaVersion } from '../lib/task-store.mjs';
|
|
8
9
|
|
|
9
10
|
const TASKS_DIR = '.dw/tasks';
|
|
10
11
|
|
|
@@ -17,7 +18,8 @@ function writeTaskFrontmatter(taskId, mutator, rootDir = process.cwd()) {
|
|
|
17
18
|
const content = readFileSync(file, 'utf8');
|
|
18
19
|
const fm = parseFrontmatter(content);
|
|
19
20
|
const updated = mutator({ ...fm });
|
|
20
|
-
|
|
21
|
+
// Linking goal fields requires v3.1; bump v3.0 → canonical task@v3.1 (#22).
|
|
22
|
+
if (bareSchemaVersion(updated.schema_version) === 'v3.0') updated.schema_version = 'task@v3.1';
|
|
21
23
|
updated.last_updated = todayIso();
|
|
22
24
|
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
23
25
|
writeFileSync(file, stringifyFrontmatter(updated) + body, 'utf8');
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {
|
|
3
|
+
readGoal,
|
|
4
|
+
updateGoalFrontmatter,
|
|
5
|
+
syncIndexEntry,
|
|
6
|
+
goalStatusEnum,
|
|
7
|
+
todayIso,
|
|
8
|
+
} from '../lib/goal-store.mjs';
|
|
9
|
+
import { logGoalEvent } from '../lib/goal-events.mjs';
|
|
10
|
+
import { logEvent } from '../lib/telemetry.mjs';
|
|
11
|
+
|
|
12
|
+
// Statuses whose entry carries cascade + archive semantics owned by `goal delete`.
|
|
13
|
+
const DELEGATED_STATUSES = new Set(['Abandoned']);
|
|
14
|
+
// Closed states — reopening them is a backward move worth warning about.
|
|
15
|
+
const CLOSED_STATUSES = new Set(['Achieved', 'Abandoned']);
|
|
16
|
+
|
|
17
|
+
// Title-case a user-typed status so `active` matches the PascalCase enum (#20).
|
|
18
|
+
function normalizeStatus(input, allowed) {
|
|
19
|
+
if (!input) return input;
|
|
20
|
+
const exact = allowed.find((s) => s === input);
|
|
21
|
+
if (exact) return exact;
|
|
22
|
+
return allowed.find((s) => s.toLowerCase() === input.toLowerCase()) || input;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function goalStatusCommand(goalId, newStatusRaw, opts = {}) {
|
|
26
|
+
const rootDir = process.cwd();
|
|
27
|
+
|
|
28
|
+
const allowed = goalStatusEnum(rootDir);
|
|
29
|
+
if (!goalId || !newStatusRaw) {
|
|
30
|
+
console.error(chalk.red(`✗ Usage: dw goal status <goal-id> <${allowed.join('|')}> [--reason "..."]`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const newStatus = normalizeStatus(newStatusRaw, allowed);
|
|
35
|
+
if (!allowed.includes(newStatus)) {
|
|
36
|
+
console.error(chalk.red(`✗ Invalid status "${newStatusRaw}". Allowed: ${allowed.join(', ')}`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Abandonment is owned by `goal delete` (linked-task cascade + goal_archived
|
|
41
|
+
// event). Redirect rather than create a second, divergent abandon path (#20).
|
|
42
|
+
if (DELEGATED_STATUSES.has(newStatus)) {
|
|
43
|
+
console.error(chalk.red(`✗ Use \`dw goal delete ${goalId}\` to abandon a goal.`));
|
|
44
|
+
console.error(chalk.dim(' It handles linked-task cleanup (--cascade) and emits goal_archived.'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const goal = readGoal(goalId, rootDir);
|
|
49
|
+
if (!goal) {
|
|
50
|
+
console.error(chalk.red(`✗ Goal ${goalId} not found`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fromStatus = goal.fm.status || 'Draft';
|
|
55
|
+
if (fromStatus === newStatus) {
|
|
56
|
+
console.log(chalk.dim(` ${goalId} is already ${newStatus} — no change.`));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Warn (don't block) on reopening a closed goal — least surprise per ADR-0001.
|
|
61
|
+
if (CLOSED_STATUSES.has(fromStatus)) {
|
|
62
|
+
console.error(chalk.yellow(` ⚠ reopening a ${fromStatus} goal → ${newStatus}`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const changedBy = process.env.USER || process.env.USERNAME || 'unknown';
|
|
66
|
+
const oldVersion = goal.fm.goal_version || 1;
|
|
67
|
+
const newVersion = oldVersion + 1; // ADR-0010 Q4/C-1: status transitions are material → auto-bump
|
|
68
|
+
|
|
69
|
+
updateGoalFrontmatter(goalId, (fm) => {
|
|
70
|
+
fm.status = newStatus;
|
|
71
|
+
fm.goal_version = newVersion;
|
|
72
|
+
// Leaving any non-Abandoned target must clear a stale archive timestamp
|
|
73
|
+
// (schema: archived_at null = active). Covers reopen from a soft-delete.
|
|
74
|
+
if (fm.archived_at) fm.archived_at = null;
|
|
75
|
+
fm.last_updated = todayIso();
|
|
76
|
+
return fm;
|
|
77
|
+
}, rootDir);
|
|
78
|
+
syncIndexEntry(goalId, rootDir);
|
|
79
|
+
|
|
80
|
+
logGoalEvent({
|
|
81
|
+
event: 'goal_status_changed',
|
|
82
|
+
goal_id: goalId,
|
|
83
|
+
from_status: fromStatus,
|
|
84
|
+
to_status: newStatus,
|
|
85
|
+
changed_by: changedBy,
|
|
86
|
+
...(opts.reason ? { reason: opts.reason } : {}),
|
|
87
|
+
}, rootDir);
|
|
88
|
+
logEvent({ event: 'goal', action: 'status', name: goalId, from: fromStatus, to: newStatus }, rootDir);
|
|
89
|
+
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(chalk.green(` ✓ ${chalk.bold(goalId)} status ${fromStatus} → ${newStatus} (v${oldVersion} → v${newVersion})`));
|
|
92
|
+
if (opts.reason) console.log(chalk.dim(` Reason: ${opts.reason}`));
|
|
93
|
+
console.log(chalk.dim(' Logged to .dw/events-global.jsonl as goal_status_changed'));
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
@@ -106,6 +106,26 @@ export async function lintTaskCommand(taskName, opts = {}) {
|
|
|
106
106
|
level,
|
|
107
107
|
}, rootDir);
|
|
108
108
|
|
|
109
|
+
// tasks-index freshness advisory (ADR-0017 doc contract) — read-only, never mutates.
|
|
110
|
+
try {
|
|
111
|
+
const { readTaskIndex, listTaskIds, readTask } = await import('../lib/task-store.mjs');
|
|
112
|
+
const index = readTaskIndex(rootDir);
|
|
113
|
+
const idxKeys = new Set(Object.keys(index.tasks || {}));
|
|
114
|
+
const onDisk = listTaskIds(rootDir);
|
|
115
|
+
const missing = onDisk.filter((id) => !idxKeys.has(id));
|
|
116
|
+
let statusDrift = 0;
|
|
117
|
+
for (const id of onDisk) {
|
|
118
|
+
const entry = index.tasks?.[id];
|
|
119
|
+
if (!entry) continue;
|
|
120
|
+
const t = readTask(id, rootDir);
|
|
121
|
+
if (t && (t.fm.status || 'Draft') !== entry.status) statusDrift++;
|
|
122
|
+
}
|
|
123
|
+
if (missing.length || statusDrift) {
|
|
124
|
+
console.log(chalk.dim(` ℹ tasks-index stale (${missing.length} unindexed, ${statusDrift} status drift) — run \`dw task index\``));
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
} catch { /* advisory only */ }
|
|
128
|
+
|
|
109
129
|
if (level === 'strict' && totalErrors > 0) {
|
|
110
130
|
process.exit(1);
|
|
111
131
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { rebuildTaskIndex, readTaskIndex, taskIndexFile } from '../lib/task-store.mjs';
|
|
3
|
+
import { logEvent } from '../lib/telemetry.mjs';
|
|
4
|
+
|
|
5
|
+
// `dw task index` — regenerate .dw/tasks/tasks-index.json (DW Document Schema
|
|
6
|
+
// + Index v1.0, ADR-0017). Read-only contract surface for adapters.
|
|
7
|
+
// dw task index rebuild + human summary
|
|
8
|
+
// dw task index --json rebuild + print machine-readable index
|
|
9
|
+
// dw task index --check print current index without rebuilding (--json optional)
|
|
10
|
+
export async function taskIndexCommand(opts = {}) {
|
|
11
|
+
const rootDir = process.cwd();
|
|
12
|
+
const index = opts.check ? readTaskIndex(rootDir) : rebuildTaskIndex(rootDir);
|
|
13
|
+
|
|
14
|
+
if (opts.json) {
|
|
15
|
+
console.log(JSON.stringify(index, null, 2));
|
|
16
|
+
logEvent({ event: 'task', action: 'index.json', name: Object.keys(index.tasks).length }, rootDir);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tasks = index.tasks || {};
|
|
21
|
+
const ids = Object.keys(tasks);
|
|
22
|
+
const byStatus = {};
|
|
23
|
+
for (const id of ids) {
|
|
24
|
+
const s = tasks[id].status || 'Draft';
|
|
25
|
+
byStatus[s] = (byStatus[s] || 0) + 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(chalk.bold(` tasks-index (${chalk.cyan(index.schema_version)}) — ${ids.length} task(s)`));
|
|
30
|
+
console.log(chalk.dim(` ${taskIndexFile(rootDir)}`));
|
|
31
|
+
console.log();
|
|
32
|
+
if (ids.length === 0) {
|
|
33
|
+
console.log(chalk.dim(' (no tasks — scaffold one with `dw task new <name>`)'));
|
|
34
|
+
} else {
|
|
35
|
+
for (const [status, n] of Object.entries(byStatus)) {
|
|
36
|
+
console.log(` ${String(n).padStart(3)} ${status}`);
|
|
37
|
+
}
|
|
38
|
+
const linked = ids.filter((id) => tasks[id].parent_goal_id).length;
|
|
39
|
+
console.log();
|
|
40
|
+
console.log(chalk.dim(` ${linked}/${ids.length} linked to a parent goal`));
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(chalk.dim(' Tip: `dw task index --json` for the machine-readable adapter contract.'));
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
logEvent({ event: 'task', action: opts.check ? 'index.check' : 'index.rebuild', name: ids.length }, rootDir);
|
|
47
|
+
}
|