dw-kit 1.9.0 → 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/skills/dw-kit-report/SKILL.md +38 -7
- package/.dw/core/templates/v3/task.md +15 -22
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/lib/goal-store.mjs +2 -14
- package/src/lib/timeline-parser.mjs +54 -15
|
@@ -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]
|
|
@@ -66,14 +66,11 @@ 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
|
|
|
@@ -120,23 +117,19 @@ markers — those live only in Section 3).
|
|
|
120
117
|
## 3. Subtask Tracker
|
|
121
118
|
|
|
122
119
|
<!--
|
|
123
|
-
SINGLE SOURCE OF TRUTH for subtask status.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Section 2 ("Subtasks (in scope)") and this tracker are deliberately separate:
|
|
127
|
-
§2 owns the stable INTENT (name + actions + acceptance), this table owns the
|
|
128
|
-
churning STATUS. They are NOT merged — that separation is what keeps status in
|
|
129
|
-
exactly one place (drift-prevention). The Subtask name is restated here as a
|
|
130
|
-
short label only.
|
|
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).
|
|
131
123
|
|
|
132
|
-
|
|
133
|
-
|
|
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`).
|
|
134
127
|
-->
|
|
135
128
|
|
|
136
|
-
| # | Subtask |
|
|
137
|
-
|
|
138
|
-
| ST-1 |
|
|
139
|
-
| ST-2 |
|
|
129
|
+
| # | Subtask | Acceptance | Est | Status | Notes |
|
|
130
|
+
|---|---------|-----------|-----|--------|-------|
|
|
131
|
+
| ST-1 | {name} | {verifiable criterion} | — | ⬜ Pending | |
|
|
132
|
+
| ST-2 | {name} | ... | — | ⬜ Pending | |
|
|
140
133
|
|
|
141
134
|
Status legend: ⬜ Pending · 🟡 In Progress · ✅ Done · 🔴 Blocked · ⏸ Paused
|
|
142
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.9.
|
|
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
|
|
package/package.json
CHANGED
package/src/lib/goal-store.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, dirname } from 'node:path';
|
|
3
3
|
import { parseFrontmatter, stringifyFrontmatter } from './frontmatter.mjs';
|
|
4
|
+
import { parseSubtaskTracker } from './timeline-parser.mjs';
|
|
4
5
|
|
|
5
6
|
const GOALS_DIR = '.dw/goals';
|
|
6
7
|
const INDEX_FILE = '.dw/goals/goals-index.json';
|
|
@@ -145,7 +146,7 @@ export function computeGoalProgress(goalId, rootDir = process.cwd(), linkedTasks
|
|
|
145
146
|
if (!existsSync(taskFile)) continue;
|
|
146
147
|
const content = readFileSync(taskFile, 'utf8');
|
|
147
148
|
const tracker = extractTrackerSection(content);
|
|
148
|
-
for (const row of
|
|
149
|
+
for (const row of parseSubtaskTracker(tracker)) {
|
|
149
150
|
total++;
|
|
150
151
|
if (row.status.includes('✅') || /Done/i.test(row.status)) done++;
|
|
151
152
|
else if (row.status.includes('🟡') || /In Progress/i.test(row.status)) inProgress++;
|
|
@@ -163,19 +164,6 @@ function extractTrackerSection(content) {
|
|
|
163
164
|
return m ? m[0] : '';
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
function parseTrackerRows(section) {
|
|
167
|
-
const rows = [];
|
|
168
|
-
const lines = section.split('\n');
|
|
169
|
-
for (const line of lines) {
|
|
170
|
-
// Match table rows like: | ST-N | Description | Status | Date | Notes |
|
|
171
|
-
if (!/^\|\s*(ST|WS)-/.test(line)) continue;
|
|
172
|
-
const cells = line.split('|').map((c) => c.trim());
|
|
173
|
-
if (cells.length < 4) continue;
|
|
174
|
-
// cells[0] = "" (before first |), [1] = ID, [2] = subtask, [3] = status
|
|
175
|
-
rows.push({ id: cells[1], subtask: cells[2], status: cells[3] || '', date: cells[4] || '', notes: cells[5] || '' });
|
|
176
|
-
}
|
|
177
|
-
return rows;
|
|
178
|
-
}
|
|
179
167
|
|
|
180
168
|
export function removeIndexEntry(goalId, rootDir = process.cwd()) {
|
|
181
169
|
const index = readGoalIndex(rootDir);
|
|
@@ -29,29 +29,68 @@ export function parseTimeline(content) {
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Header-driven so the column ORDER is not load-bearing (#24): the §3 tracker
|
|
33
|
+
// may be `# | Subtask | Status | Date | Notes` (legacy) or
|
|
34
|
+
// `# | Subtask | Acceptance | Est | Status | Notes` (current) — we read columns
|
|
35
|
+
// by name. Falls back to legacy positional mapping when no header row is found,
|
|
36
|
+
// so older / hand-written tables keep parsing.
|
|
37
|
+
const TRACKER_COLS = {
|
|
38
|
+
id: ['#', 'id'],
|
|
39
|
+
name: ['subtask', 'task', 'name'],
|
|
40
|
+
status: ['status'],
|
|
41
|
+
date: ['date'],
|
|
42
|
+
notes: ['notes'],
|
|
43
|
+
acceptance: ['acceptance'],
|
|
44
|
+
est: ['est', 'estimate'],
|
|
45
|
+
};
|
|
46
|
+
const LEGACY_POS = { id: 0, name: 1, status: 2, date: 3, notes: 4 };
|
|
47
|
+
|
|
48
|
+
function splitRow(line) {
|
|
49
|
+
// Drop the empty cells produced by the leading/trailing pipes.
|
|
50
|
+
return line.split('|').map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
export function parseSubtaskTracker(sectionText) {
|
|
33
54
|
if (!sectionText) return [];
|
|
34
55
|
const lines = sectionText.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
56
|
+
let colMap = null; // canonical-name -> column index
|
|
35
57
|
const rows = [];
|
|
36
|
-
let inTable = false;
|
|
37
58
|
for (const line of lines) {
|
|
38
|
-
if (line.startsWith('
|
|
39
|
-
if (
|
|
40
|
-
const cells = line
|
|
59
|
+
if (!line.startsWith('|')) continue;
|
|
60
|
+
if (line.match(/^\|[\s|:-]+\|$/)) continue; // separator row
|
|
61
|
+
const cells = splitRow(line);
|
|
41
62
|
if (cells.length < 2) continue;
|
|
42
|
-
|
|
43
|
-
|
|
63
|
+
|
|
64
|
+
const low = cells.map((c) => c.toLowerCase());
|
|
65
|
+
// Header row: first cell is '#'/'id', or it names known columns. Detect once.
|
|
66
|
+
const looksLikeHeader = low[0] === '#' || low[0] === 'id'
|
|
67
|
+
|| low.includes('status') || low.includes('subtask');
|
|
68
|
+
if (!colMap && looksLikeHeader) {
|
|
69
|
+
colMap = {};
|
|
70
|
+
for (const [canon, aliases] of Object.entries(TRACKER_COLS)) {
|
|
71
|
+
const idx = low.findIndex((c) => aliases.includes(c));
|
|
72
|
+
if (idx >= 0) colMap[canon] = idx;
|
|
73
|
+
}
|
|
44
74
|
continue;
|
|
45
75
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
76
|
+
|
|
77
|
+
// Data row. Skip stray non-subtask rows when using positional fallback.
|
|
78
|
+
if (!colMap && !/^(ST|WS)-/i.test(cells[0]) && !/^\d/.test(cells[0])) continue;
|
|
79
|
+
if (cells.length < 3 && !colMap) continue;
|
|
80
|
+
|
|
81
|
+
const get = (canon) => {
|
|
82
|
+
const idx = colMap ? colMap[canon] : LEGACY_POS[canon];
|
|
83
|
+
return idx != null && idx >= 0 ? (cells[idx] ?? '') : '';
|
|
84
|
+
};
|
|
85
|
+
rows.push({
|
|
86
|
+
id: get('id'),
|
|
87
|
+
name: get('name'),
|
|
88
|
+
status: get('status'),
|
|
89
|
+
date: get('date'),
|
|
90
|
+
notes: get('notes'),
|
|
91
|
+
acceptance: get('acceptance'),
|
|
92
|
+
est: get('est'),
|
|
93
|
+
});
|
|
55
94
|
}
|
|
56
95
|
return rows;
|
|
57
96
|
}
|