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.
@@ -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
- **Task context:** Kiểm tra `.dw/tasks/`task nào đang In Progress?
41
+ **Workflow context:** 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
- - Task khi gặp vấn đề: [task name hoặc "general usage"]
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` | Project: [project.name từ config]*
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 4: Gửi Lên GitHub
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 5: Xác Nhận
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
- ### Subtasks (in scope)
70
-
71
- **ST-1: {Subtask name}**
72
- - {Concrete action}
73
- - Acceptance: {verifiable criterion}
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. This is the only section where
124
- status markers (⬜🟡✅🔴⏸) are allowed.
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
- `Est` is an optional effort estimate (hours / story-points / t-shirt per
133
- `estimation_unit`); leave `—` if unused. See `/dw:estimate`.
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 | Status | Date | Notes | Est |
137
- |---|---------|--------|------|-------|-----|
138
- | ST-1 | ... | Pending | — | | |
139
- | ST-2 | ... | Pending | — | | |
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.0** · `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)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dw-kit",
3
- "version": "1.9.0",
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": {
@@ -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 parseTrackerRows(tracker)) {
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('|---') || line.match(/^\|[\s|:-]+\|$/)) { inTable = true; continue; }
39
- if (!line.startsWith('|')) { inTable = false; continue; }
40
- const cells = line.split('|').map((c) => c.trim()).filter((_, i, arr) => i > 0 && i < arr.length - 1);
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
- if (!inTable && (cells[0].toLowerCase().startsWith('#') || cells[0].toLowerCase() === 'ws' || cells[0].toLowerCase() === 'st')) {
43
- // header row
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
- if (cells.length >= 3) {
47
- rows.push({
48
- id: cells[0],
49
- name: cells[1],
50
- status: cells[2],
51
- date: cells[3] || '',
52
- notes: cells[4] || '',
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
  }