dw-kit 1.8.0-rc.2 → 1.9.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.
Files changed (79) hide show
  1. package/.claude/hooks/stop-check.sh +10 -0
  2. package/.claude/rules/dw.md +2 -0
  3. package/.claude/skills/dw-decision/SKILL.md +2 -1
  4. package/.claude/skills/dw-goal/SKILL.md +206 -0
  5. package/.claude/skills/dw-goal-sync/SKILL.md +131 -0
  6. package/.claude/templates/agent-report.md +35 -35
  7. package/.dw/config/agents.yml +8 -0
  8. package/.dw/core/AGENTS.md +53 -53
  9. package/.dw/core/schemas/decision-frontmatter.schema.json +54 -0
  10. package/.dw/core/schemas/events/created.schema.json +33 -0
  11. package/.dw/core/schemas/events/debate_agent_failed.schema.json +42 -0
  12. package/.dw/core/schemas/events/debate_agent_replied.schema.json +44 -0
  13. package/.dw/core/schemas/events/debate_agent_started.schema.json +37 -0
  14. package/.dw/core/schemas/events/debate_completed.schema.json +36 -0
  15. package/.dw/core/schemas/events/debate_started.schema.json +47 -0
  16. package/.dw/core/schemas/events/goal_archived.schema.json +32 -0
  17. package/.dw/core/schemas/events/goal_created.schema.json +32 -0
  18. package/.dw/core/schemas/events/goal_field_updated.schema.json +35 -0
  19. package/.dw/core/schemas/events/goal_pivoted.schema.json +36 -0
  20. package/.dw/core/schemas/events/goal_status_changed.schema.json +40 -0
  21. package/.dw/core/schemas/events/goal_task_linked.schema.json +33 -0
  22. package/.dw/core/schemas/events/goal_task_unlinked.schema.json +33 -0
  23. package/.dw/core/schemas/events/index.json +185 -0
  24. package/.dw/core/schemas/events/orchestrator_cancelled.schema.json +29 -0
  25. package/.dw/core/schemas/events/orchestrator_completed.schema.json +38 -0
  26. package/.dw/core/schemas/events/orchestrator_confirm.schema.json +33 -0
  27. package/.dw/core/schemas/events/orchestrator_confirmed.schema.json +33 -0
  28. package/.dw/core/schemas/events/orchestrator_error.schema.json +29 -0
  29. package/.dw/core/schemas/events/orchestrator_pending_dropped.schema.json +29 -0
  30. package/.dw/core/schemas/events/orchestrator_pending_expired.schema.json +32 -0
  31. package/.dw/core/schemas/events/orchestrator_recommend_rejected.schema.json +37 -0
  32. package/.dw/core/schemas/events/orchestrator_recommended.schema.json +33 -0
  33. package/.dw/core/schemas/events/orchestrator_spawn_failed.schema.json +29 -0
  34. package/.dw/core/schemas/events/orchestrator_started.schema.json +33 -0
  35. package/.dw/core/schemas/events/orchestrator_timeout.schema.json +29 -0
  36. package/.dw/core/schemas/events/reconciled.schema.json +29 -0
  37. package/.dw/core/schemas/events/reconciled_stale.schema.json +29 -0
  38. package/.dw/core/schemas/events/session.created.schema.json +39 -0
  39. package/.dw/core/schemas/events/session.reconciled.schema.json +33 -0
  40. package/.dw/core/schemas/events/session.status_changed.schema.json +42 -0
  41. package/.dw/core/schemas/events/spawn_failed.schema.json +29 -0
  42. package/.dw/core/schemas/events/started.schema.json +59 -0
  43. package/.dw/core/schemas/events/stopped.schema.json +33 -0
  44. package/.dw/core/schemas/goal-frontmatter.schema.json +2 -2
  45. package/.dw/core/schemas/task-frontmatter.schema.json +2 -2
  46. package/.dw/core/templates/v3/task.md +38 -9
  47. package/.dw/security/advisory-snapshot.json +157 -0
  48. package/LICENSE +201 -21
  49. package/NOTICE +26 -0
  50. package/README.md +5 -2
  51. package/SECURITY.md +87 -0
  52. package/TRADEMARK.md +65 -0
  53. package/bin/dw.mjs +1 -1
  54. package/package.json +13 -5
  55. package/src/cli.mjs +33 -0
  56. package/src/commands/decision-index.mjs +45 -0
  57. package/src/commands/goal-delete.mjs +3 -1
  58. package/src/commands/goal-link.mjs +3 -1
  59. package/src/commands/goal-status.mjs +95 -0
  60. package/src/commands/lint-task.mjs +20 -0
  61. package/src/commands/task-index.mjs +47 -0
  62. package/src/commands/task-migrate.mjs +16 -5
  63. package/src/commands/task-new.mjs +6 -0
  64. package/src/commands/task-summary.mjs +4 -3
  65. package/src/commands/voice.mjs +590 -4
  66. package/src/lib/board-data.mjs +220 -0
  67. package/src/lib/debate.mjs +325 -0
  68. package/src/lib/decision-store.mjs +146 -0
  69. package/src/lib/event-schema.mjs +342 -0
  70. package/src/lib/goal-store.mjs +40 -1
  71. package/src/lib/lint-rules.mjs +10 -1
  72. package/src/lib/orchestrator.mjs +31 -9
  73. package/src/lib/session-store.mjs +36 -4
  74. package/src/lib/task-store.mjs +164 -0
  75. package/src/lib/voice-action.mjs +165 -0
  76. package/src/lib/voice-parser.mjs +13 -0
  77. package/.dw/config/connectors.local.yml +0 -38
  78. package/.dw/core/PILLARS.md +0 -122
  79. package/CLAUDE.md +0 -44
@@ -0,0 +1,164 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { parseFrontmatter } from './frontmatter.mjs';
4
+
5
+ // DW Document Schema + Index v1.0 (ADR-0017) — task half.
6
+ // Mirrors goal-store.mjs / goals-index@v1: a committed manifest so external
7
+ // adapters read task state from one O(1) file instead of reverse-engineering
8
+ // every task.md.
9
+
10
+ const TASKS_DIR = '.dw/tasks';
11
+ const INDEX_FILE = '.dw/tasks/tasks-index.json';
12
+ const SCHEMA_VERSION = 'tasks-index@v1';
13
+
14
+ export function tasksDir(rootDir = process.cwd()) {
15
+ return join(rootDir, TASKS_DIR);
16
+ }
17
+
18
+ export function taskFile(taskId, rootDir = process.cwd()) {
19
+ return join(rootDir, TASKS_DIR, taskId, 'task.md');
20
+ }
21
+
22
+ export function taskIndexFile(rootDir = process.cwd()) {
23
+ return join(rootDir, INDEX_FILE);
24
+ }
25
+
26
+ function nowUtc() {
27
+ return new Date().toISOString().replace(/\.\d+Z$/, 'Z');
28
+ }
29
+
30
+ // Stable key order so the same task set always serializes byte-identically,
31
+ // regardless of writer path (syncTaskIndexEntry appends; rebuildTaskIndex uses
32
+ // readdirSync order, which is filesystem-dependent). Without this the two paths
33
+ // disagree and the post-commit hook refresh churns the file (#21).
34
+ function sortTasks(tasks) {
35
+ const sorted = {};
36
+ for (const id of Object.keys(tasks || {}).sort()) sorted[id] = tasks[id];
37
+ return sorted;
38
+ }
39
+
40
+ // schema_version naming (#22): task frontmatter accepts both the legacy bare
41
+ // form (v3.0/v3.1) and the canonical namespaced form (task@v3.0/task@v3.1) that
42
+ // matches the repo-wide @v convention (goal@v1, *-index@v1). New writes emit the
43
+ // namespaced form; readers tolerate both. `bareSchemaVersion` strips the prefix
44
+ // for version-comparison logic; `canonicalSchemaVersion` adds it for emission.
45
+ export function bareSchemaVersion(v) {
46
+ if (!v || typeof v !== 'string') return v;
47
+ return v.startsWith('task@') ? v.slice('task@'.length) : v;
48
+ }
49
+
50
+ export function canonicalSchemaVersion(v) {
51
+ if (!v || typeof v !== 'string') return v;
52
+ return v.startsWith('task@') ? v : `task@${v}`;
53
+ }
54
+
55
+ function emptyIndex() {
56
+ return { schema_version: SCHEMA_VERSION, last_updated: nowUtc(), tasks: {} };
57
+ }
58
+
59
+ export function readTaskIndex(rootDir = process.cwd()) {
60
+ const file = taskIndexFile(rootDir);
61
+ if (!existsSync(file)) return emptyIndex();
62
+ try {
63
+ const parsed = JSON.parse(readFileSync(file, 'utf8'));
64
+ if (!parsed || typeof parsed !== 'object' || !parsed.tasks) return emptyIndex();
65
+ return parsed;
66
+ } catch {
67
+ return emptyIndex();
68
+ }
69
+ }
70
+
71
+ export function writeTaskIndex(index, rootDir = process.cwd()) {
72
+ const file = taskIndexFile(rootDir);
73
+ const dir = dirname(file);
74
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
75
+ const tasks = sortTasks(index.tasks);
76
+ // Skip the write entirely when the task set is byte-identical to disk: this
77
+ // keeps the on-disk `last_updated` (the spec freshness signal, ADR-0017) and
78
+ // guarantees zero git diff. `last_updated` advances only on a real change.
79
+ const prior = readTaskIndex(rootDir);
80
+ if (existsSync(file) && JSON.stringify(sortTasks(prior.tasks)) === JSON.stringify(tasks)) {
81
+ return prior;
82
+ }
83
+ const updated = { schema_version: SCHEMA_VERSION, last_updated: nowUtc(), tasks };
84
+ writeFileSync(file, JSON.stringify(updated, null, 2) + '\n', 'utf8');
85
+ return updated;
86
+ }
87
+
88
+ export function listTaskIds(rootDir = process.cwd()) {
89
+ const dir = tasksDir(rootDir);
90
+ if (!existsSync(dir)) return [];
91
+ return readdirSync(dir).filter((entry) => {
92
+ if (entry === 'archive' || entry === 'ACTIVE.md') return false;
93
+ try {
94
+ const path = join(dir, entry);
95
+ return statSync(path).isDirectory() && existsSync(join(path, 'task.md'));
96
+ } catch {
97
+ return false;
98
+ }
99
+ });
100
+ }
101
+
102
+ export function readTask(taskId, rootDir = process.cwd()) {
103
+ const file = taskFile(taskId, rootDir);
104
+ if (!existsSync(file)) return null;
105
+ const content = readFileSync(file, 'utf8');
106
+ return { fm: parseFrontmatter(content), content, file };
107
+ }
108
+
109
+ export function extractTaskTitle(content) {
110
+ const m = content.match(/^#\s+(?:Timeline:|Spec:)?\s*(.+?)\s*$/m);
111
+ return m ? m[1].trim() : null;
112
+ }
113
+
114
+ // doc@v1 task projection — consumer-facing fields only (no body text).
115
+ function taskEntry(taskId, rootDir) {
116
+ const task = readTask(taskId, rootDir);
117
+ if (!task) return null;
118
+ const fm = task.fm || {};
119
+ return {
120
+ title: extractTaskTitle(task.content) || taskId,
121
+ status: fm.status || 'Draft',
122
+ phase: fm.phase || null,
123
+ owner: fm.owner || 'unknown',
124
+ depth: fm.depth || null,
125
+ related_adr: fm.related_adr || null,
126
+ target_ship: fm.target_ship || null,
127
+ parent_goal_id: fm.parent_goal_id && fm.parent_goal_id !== 'none' ? fm.parent_goal_id : null,
128
+ contributing_goal_ids: Array.isArray(fm.contributing_goal_ids) ? fm.contributing_goal_ids : [],
129
+ summary: fm.summary || null,
130
+ schema_version: fm.schema_version || null,
131
+ last_updated: fm.last_updated || new Date().toISOString().slice(0, 10),
132
+ };
133
+ }
134
+
135
+ export function syncTaskIndexEntry(taskId, rootDir = process.cwd()) {
136
+ const entry = taskEntry(taskId, rootDir);
137
+ if (!entry) return null;
138
+ const index = readTaskIndex(rootDir);
139
+ index.tasks[taskId] = entry;
140
+ writeTaskIndex(index, rootDir);
141
+ return entry;
142
+ }
143
+
144
+ export function removeTaskIndexEntry(taskId, rootDir = process.cwd()) {
145
+ const index = readTaskIndex(rootDir);
146
+ if (index.tasks[taskId]) {
147
+ delete index.tasks[taskId];
148
+ writeTaskIndex(index, rootDir);
149
+ return true;
150
+ }
151
+ return false;
152
+ }
153
+
154
+ // Authoritative full rebuild — scans .dw/tasks/ and rewrites the index.
155
+ // Idempotency (sorted keys + skip-when-identical) lives in writeTaskIndex, so an
156
+ // unchanged task set produces zero git diff and the stop-check refresh is safe.
157
+ export function rebuildTaskIndex(rootDir = process.cwd()) {
158
+ const index = emptyIndex();
159
+ for (const taskId of listTaskIds(rootDir)) {
160
+ const entry = taskEntry(taskId, rootDir);
161
+ if (entry) index.tasks[taskId] = entry;
162
+ }
163
+ return writeTaskIndex(index, rootDir);
164
+ }
@@ -150,6 +150,149 @@ function loadAgentsConfig(rootDir) {
150
150
  return yaml.load(readFileSync(p, 'utf8')) || { agents: {} };
151
151
  }
152
152
 
153
+ // F-43/F-47 (owner directive 2026-05-28): the spawned agent should know about
154
+ // common environment friction (SSL/TLS cert, Win32 PATHEXT + .cmd shells,
155
+ // missing CLIs, CRLF/LF, port-in-use, gitignored secrets) and try to
156
+ // diagnose + self-fix before escalating. Anything that touches infra (regen
157
+ // cert, install package, modify env vars) MUST surface for human approval.
158
+ const ENV_FRICTION_HINTS_EN = [
159
+ `Common environment friction to watch for and self-handle (per owner directive):`,
160
+ `- SSL/TLS: if HTTPS/curl/git fails with cert errors, check .dw/cache/voice/tls/; on Win32 try \`mkcert -install\` or regenerate via tls-helpers.mjs. If the project must call a vendor with self-signed certs, surface — do NOT silently set NODE_TLS_REJECT_UNAUTHORIZED.`,
161
+ `- Win32 spawn: claude/codex/gemini ship as .cmd shims; raw spawn(shell:false) fails ENOENT. Use src/lib/spawn-helpers.mjs (resolveCommandPath + spawnAgent). See F-18/F-29/F-40 for the recurring pattern.`,
162
+ `- Missing CLI: if "claude not on PATH" → ask user to install/reopen terminal; do not try alternative agents without permission.`,
163
+ `- CRLF/LF: bin/dw.mjs often shows phantom diffs on Win32. Ignore unless a real content change is present.`,
164
+ `- Port in use: 4500 is the voice server default; if EADDRINUSE, find the existing server (Get-CimInstance Win32_Process | Where CommandLine -like '*dw.mjs voice*') and reconcile before relaunching.`,
165
+ `- Gitignored secrets: never commit .dw/cache/voice/voice.token, .dw/config/connectors.local.yml, or anything under .dw/cache/. The privacy-block hook will reject; respect it.`,
166
+ `- Anything that touches infra (cert install, env vars, dependency add) → STOP and surface for human approval before applying.`,
167
+ ].join('\n');
168
+ const ENV_FRICTION_HINTS_VI = [
169
+ `Các friction môi trường thường gặp — tự chẩn đoán và xử lý nếu được (theo owner directive):`,
170
+ `- SSL/TLS: nếu HTTPS/curl/git lỗi cert, kiểm tra .dw/cache/voice/tls/; trên Win32 thử \`mkcert -install\` hoặc regenerate qua tls-helpers.mjs. Nếu phải gọi vendor có self-signed cert, BÁO LẠI — TUYỆT ĐỐI KHÔNG silently set NODE_TLS_REJECT_UNAUTHORIZED.`,
171
+ `- Win32 spawn: claude/codex/gemini là .cmd shims; raw spawn(shell:false) ENOENT. Dùng src/lib/spawn-helpers.mjs (resolveCommandPath + spawnAgent). Xem F-18/F-29/F-40.`,
172
+ `- Thiếu CLI: nếu "claude not on PATH" → hỏi user cài lại / mở terminal mới; KHÔNG tự đổi sang agent khác mà không xin phép.`,
173
+ `- CRLF/LF: bin/dw.mjs hay show diff giả trên Win32. Bỏ qua trừ khi có content change thật.`,
174
+ `- Port in use: 4500 mặc định cho voice server; nếu EADDRINUSE, tìm process cũ (Get-CimInstance Win32_Process | Where CommandLine -like '*dw.mjs voice*') và reconcile trước.`,
175
+ `- Secret bị gitignored: KHÔNG commit .dw/cache/voice/voice.token, .dw/config/connectors.local.yml, hoặc bất cứ thứ gì trong .dw/cache/. privacy-block hook sẽ reject; tôn trọng.`,
176
+ `- Bất cứ gì touch infra (cert install, env vars, thêm dependency) → DỪNG và báo human approve trước khi áp dụng.`,
177
+ ].join('\n');
178
+
179
+ // F-43: bare goal_id arg → full task-execution prompt. When the user says
180
+ // "khởi động agent cho goal X" / "start agent for goal X", the orchestrator
181
+ // often emits goal="G-X" — just an identifier. The spawned `claude --print`
182
+ // has no instruction what to DO and defaults to "explain this" mode (one-shot
183
+ // status reply, then exit). Expanding the id into a real task prompt gives
184
+ // the agent enough scaffolding to actually work on the goal autonomously.
185
+ //
186
+ // Exported for tests.
187
+ export function expandGoalIdToTaskPrompt(goalArg, rootDir, lang = 'en') {
188
+ if (typeof goalArg !== 'string') return goalArg;
189
+ const trimmed = goalArg.trim();
190
+ // Case 1: bare goal_id (G-{alphanumeric/hyphen, ≤32}) → expand from goal.md.
191
+ // Case 2 (F-47): short topic phrase (≤80 chars, no newlines, looks like a
192
+ // topic not a sentence) → wrap with a discovery preamble that tells the
193
+ // agent to grep .dw/goals/ for the topic and pick the matching goal before
194
+ // continuing per F-43. This catches the common voice flow where the user
195
+ // says "start agent for goal multi-agent" and the orchestrator emits
196
+ // goal="the multi-agent orchestration goal" — 47 chars of topic, no id.
197
+ const isBareGoalId = /^G-[A-Za-z0-9_-]{1,32}$/.test(trimmed);
198
+ const isShortTopic = !isBareGoalId
199
+ && !trimmed.includes('\n')
200
+ && trimmed.length > 0
201
+ && trimmed.length <= 80
202
+ // Likely-topic heuristic: must explicitly mention goal/task/rgoal/KR.
203
+ // Don't trigger on short generic prompts like "fix login bug" — only when
204
+ // the user (or orchestrator) clearly intended to reference a goal/task.
205
+ && /\b(goal|task|rgoal|kr-?[a-z]|mục tiêu|nhiệm vụ)\b/i.test(trimmed);
206
+
207
+ if (!isBareGoalId && !isShortTopic) return goalArg;
208
+
209
+ if (isBareGoalId) {
210
+ const goalDir = join(rootDir, '.dw', 'goals', trimmed);
211
+ const goalFile = join(goalDir, 'goal.md');
212
+ if (!existsSync(goalFile)) return goalArg; // unknown goal — leave for the agent to discover
213
+ }
214
+
215
+ // F-47 short topic branch: build discovery preamble + full workflow.
216
+ if (isShortTopic) {
217
+ const L = lang === 'vi' ? 'vi' : 'en';
218
+ if (L === 'vi') {
219
+ return [
220
+ `Người dùng yêu cầu làm việc trên topic: "${trimmed}".`,
221
+ ``,
222
+ `Bước 1 — Discovery:`,
223
+ `- Đọc .dw/goals/goals-index.json để xem danh sách goal_id + title.`,
224
+ `- Match "${trimmed}" với goal_id phù hợp nhất (so khớp keyword hoặc title substring).`,
225
+ `- Nếu không có match rõ ràng, ưu tiên goal Active gần đây nhất phù hợp ngữ cảnh.`,
226
+ ``,
227
+ `Bước 2 — Execute (theo F-43 workflow):`,
228
+ `1. Đọc .dw/goals/<matched-id>/goal.md (Section 1 + 3 + 5).`,
229
+ `2. Tìm linked task qua frontmatter "Linked tasks" hoặc grep parent_goal_id: <matched-id> trong .dw/tasks/.`,
230
+ `3. Đọc task.md v3 (Section 3 Subtask Tracker) — chọn subtask đầu tiên ⬜ Pending hoặc 🟡 In Progress.`,
231
+ `4. Thực hiện subtask (TDD nếu code thay đổi). Update Section 3 status sau mỗi subtask.`,
232
+ `5. Commit theo .claude/rules/commit-standards.md (English imperative, KHÔNG đính kèm Co-Authored-By: Claude).`,
233
+ `6. DỪNG trước push/release — báo cáo state hiện tại để human review.`,
234
+ ``,
235
+ ENV_FRICTION_HINTS_VI,
236
+ ``,
237
+ `Nếu KHÔNG có goal match cho "${trimmed}", báo lại và đề xuất tạo goal mới.`,
238
+ ].join('\n');
239
+ }
240
+ return [
241
+ `User asked to work on topic: "${trimmed}".`,
242
+ ``,
243
+ `Step 1 — Discovery:`,
244
+ `- Read .dw/goals/goals-index.json to list goal_id + title pairs.`,
245
+ `- Match "${trimmed}" against the closest goal_id (keyword overlap or title substring).`,
246
+ `- If no obvious match, prefer the most recent Active goal that fits context.`,
247
+ ``,
248
+ `Step 2 — Execute (per F-43 workflow):`,
249
+ `1. Read .dw/goals/<matched-id>/goal.md (Sections 1 + 3 + 5).`,
250
+ `2. Find the linked task via frontmatter "Linked tasks" or grep parent_goal_id: <matched-id> under .dw/tasks/.`,
251
+ `3. Read task.md v3 (Section 3 Subtask Tracker) — pick the first ⬜ Pending or 🟡 In Progress subtask.`,
252
+ `4. Execute the subtask (TDD if code changes). Update Section 3 status after each subtask.`,
253
+ `5. Commit per .claude/rules/commit-standards.md (English imperative, do NOT append Co-Authored-By: Claude).`,
254
+ `6. STOP before push/release — surface the diff + state for human review.`,
255
+ ``,
256
+ ENV_FRICTION_HINTS_EN,
257
+ ``,
258
+ `If NO goal matches "${trimmed}", report back and propose creating a new one.`,
259
+ ].join('\n');
260
+ }
261
+ const L = lang === 'vi' ? 'vi' : 'en';
262
+ if (L === 'vi') {
263
+ return [
264
+ `Tiếp tục thực hiện goal ${trimmed}.`,
265
+ ``,
266
+ `Quy trình bắt buộc:`,
267
+ `1. Đọc .dw/goals/${trimmed}/goal.md để load context (Section 1 Snapshot + Section 3 KR table + Section 5 Friction Journal).`,
268
+ `2. Tìm linked task qua "Linked tasks" trong frontmatter, hoặc grep parent_goal_id: ${trimmed} trong .dw/tasks/.`,
269
+ `3. Đọc task.md v3 (Section 3 Subtask Tracker) — chọn subtask đầu tiên có status ⬜ Pending hoặc 🟡 In Progress.`,
270
+ `4. Thực hiện subtask (TDD nếu code thay đổi). Update Section 3 status + Section 4 timeline sau mỗi subtask.`,
271
+ `5. Commit theo .claude/rules/commit-standards.md (English imperative, KHÔNG đính kèm Co-Authored-By: Claude).`,
272
+ `6. DỪNG trước push/release — báo cáo diff + state hiện tại để human review.`,
273
+ ``,
274
+ ENV_FRICTION_HINTS_VI,
275
+ ``,
276
+ `Nếu không có v3 task cho goal này, scaffold trước bằng "dw task new <name>". Nếu mọi subtask đã ✅ Done, báo cáo và đề xuất task tiếp theo.`,
277
+ ].join('\n');
278
+ }
279
+ return [
280
+ `Continue execution of goal ${trimmed}.`,
281
+ ``,
282
+ `Required workflow:`,
283
+ `1. Read .dw/goals/${trimmed}/goal.md to load context (Section 1 Snapshot + Section 3 KR table + Section 5 Friction Journal).`,
284
+ `2. Find the linked task via the "Linked tasks" field in frontmatter, or grep parent_goal_id: ${trimmed} under .dw/tasks/.`,
285
+ `3. Read the task.md v3 (Section 3 Subtask Tracker) — pick the first subtask with status ⬜ Pending or 🟡 In Progress.`,
286
+ `4. Execute the subtask (TDD if code changes). Update Section 3 status + Section 4 timeline after each subtask.`,
287
+ `5. Commit per .claude/rules/commit-standards.md (English imperative, do NOT append Co-Authored-By: Claude).`,
288
+ `6. STOP before push/release — surface the diff + current state for human review.`,
289
+ ``,
290
+ ENV_FRICTION_HINTS_EN,
291
+ ``,
292
+ `If no v3 task exists for this goal, scaffold one with "dw task new <name>" first. If every subtask is ✅ Done, report status and propose the next task.`,
293
+ ].join('\n');
294
+ }
295
+
153
296
  /**
154
297
  * Execute a validated action against session-store. Returns:
155
298
  * { ok, display, spoken, data }
@@ -186,6 +329,20 @@ export async function executeAction({ name, args, rootDir, lang = 'en-US' }) {
186
329
  }
187
330
 
188
331
  if (name === 'start_session') {
332
+ // F-43 (Critical): when args.goal is just a bare goal_id (e.g.
333
+ // "G-rgoal-realtime-orch"), claude --print receives only 23 chars and
334
+ // has no instruction what to DO — defaults to "explain this" mode →
335
+ // one-shot status report → exit. Expand bare goal_id into a full task
336
+ // prompt that tells the spawned agent to read the goal file + linked
337
+ // task and continue execution. The expansion is also handled by the
338
+ // orchestrator system prompt (Layer 2), but this is the safety net
339
+ // for direct callers + sloppy LLM outputs.
340
+ const originalGoal = args.goal;
341
+ const expanded = expandGoalIdToTaskPrompt(args.goal, rootDir, L);
342
+ const goalWasExpanded = expanded !== args.goal;
343
+ if (goalWasExpanded) {
344
+ args.goal = expanded;
345
+ }
189
346
  if (args.goal.length > GOAL_MAX_LEN) {
190
347
  return { ok: false, display: `Error: goal too long (${args.goal.length}>${GOAL_MAX_LEN})`, spoken: L === 'vi' ? 'Mục tiêu quá dài.' : 'Goal too long.' };
191
348
  }
@@ -227,6 +384,14 @@ export async function executeAction({ name, args, rootDir, lang = 'en-US' }) {
227
384
  }
228
385
  updateSessionStatus(state.session_id, { pid: child.pid, status: 'running' }, rootDir);
229
386
  appendEvent(state.session_id, { event: 'started', pid: child.pid, command: def.command, goal_mode: mode, via: 'voice-action' }, rootDir);
387
+ if (goalWasExpanded) {
388
+ appendEvent(state.session_id, {
389
+ event: 'goal_expanded',
390
+ from: originalGoal,
391
+ to_chars: args.goal.length,
392
+ reason: 'F-43: bare goal_id expanded to full task prompt',
393
+ }, rootDir);
394
+ }
230
395
  if (useDetached) child.unref();
231
396
  closeFd(fd);
232
397
  return {
@@ -109,6 +109,19 @@ const COMMAND_TABLE = [
109
109
  // <goal>" still works for explicit cases, but ambiguous natural phrasings
110
110
  // ("start claude với mục tiêu là...") now correctly fall through to the
111
111
  // orchestrator (LLM is better at parsing intent than regex).
112
+ // ADR-0015: debate trigger comes BEFORE start so "debate <topic>" isn't
113
+ // misread as "start a session for debate" (start patterns are looser).
114
+ name: 'debate',
115
+ patterns: [
116
+ /^(?:let'?s\s+)?debate\s*:?\s+(.+)$/i,
117
+ /^compare\s+(?:claude\s+and\s+codex|codex\s+and\s+claude|agents)\s+on\s+(.+)$/i,
118
+ /^tranh\s+luận\s*:?\s+(.+)$/i,
119
+ // VN mix: "tranh luận về <topic>" / "debate về <topic>"
120
+ /^(?:tranh\s+luận|debate)\s+về\s+(.+)$/i,
121
+ ],
122
+ extract: (m) => ({ topic: (m[1] || '').trim() }),
123
+ },
124
+ {
112
125
  name: 'start',
113
126
  patterns: [
114
127
  /^start\s+(?:a\s+)?(?:new\s+)?(?:session\s+)?(?:with|using)\s+([\w_-]+)(?:\s+(?:to\s+|for\s+|on\s+|doing\s+)?(.+))?$/i,
@@ -1,38 +0,0 @@
1
- # .dw/config/connectors.local.yml — GITIGNORED. Secrets live here.
2
- # Created/updated by `dw connector telegram setup`.
3
- # Token + allow-list overrides the template in .dw/config/connectors.yml.
4
- telegram:
5
- enabled: true
6
- bot_token: 8693679403:AAG9FrgUd5Ig9eDTAWnA9RqhbMnShRi3Si0
7
- allowed_user_ids:
8
- - 6603235862
9
-
10
- # Voice channel (Phase 4 substrate of G-rgoal-realtime-orch).
11
- # `dw voice` boots a localhost HTTP server with a browser page using Web
12
- # Speech API for ASR + TTS. Optional orchestrator hybrid: when the regex
13
- # parser misses, hand the transcript to the configured agent (Claude Code /
14
- # Codex / Gemini) with a voice-aware system prompt.
15
- voice:
16
- # Default UI language for SpeechRecognition + TTS. User can override on the
17
- # page via dropdown. Common values: en-US · vi-VN · ja-JP · ko-KR · zh-CN.
18
- # Browser support varies — Chrome / Edge / Safari are best.
19
- lang: en-US
20
- # Extra languages to surface in the dropdown (beyond `lang` + en-US default).
21
- extra_langs: [vi-VN]
22
- # Server-side TTS fallback when no native browser/OS voice matches the
23
- # selected language. F-23 (G-dogfood-v1.7): a fresh Windows install has
24
- # no Vietnamese SAPI voice; we proxy MP3 from Google Translate's public
25
- # TTS endpoint. Modes:
26
- # auto — only when no native voice matches (default; balanced)
27
- # always — always use server-side (best for cross-machine consistency)
28
- # none — never use server-side (privacy: spoken text stays local)
29
- # Privacy note: in `auto`/`always`, the spoken text leaves the machine to
30
- # translate.google.com. Disable with `none` if that is unacceptable.
31
- fallback_tts: auto
32
- orchestrator:
33
- enabled: true # opt-in. To enable WITHOUT committing it, add the
34
- # same `voice:` section to `.dw/config/connectors.local.yml`
35
- # (gitignored) — the local file overrides this template
36
- # via deep-merge (same pattern as bot_token + allow-list).
37
- agent: claude # must exist in .dw/config/agents.yml; CLI must be on PATH
38
- timeout_ms: 30000 # max wait per turn (Claude is typically 2-8s)
@@ -1,122 +0,0 @@
1
- # dw-kit v2.0 — 5 Pillar Architecture
2
-
3
- dw-kit v2.0 positions itself as a **Context-First SDLC Governance Layer** — not a prescriptive workflow engine. AI drives execution; dw-kit provides guardrails, context, and decision trail.
4
-
5
- **Framing inversion:** `prescriptive workflow → descriptive governance`. Moat is organizational memory compounding over time — something IDE tools (Cursor, Copilot) structurally cannot own because they are session-scoped.
6
-
7
- ---
8
-
9
- ## Pillar 1: GUARDS — Block unsafe actions
10
-
11
- **Role:** Non-negotiable safety boundaries. Enforced by hooks, zero discretion.
12
-
13
- **Components:**
14
- - `.claude/hooks/privacy-block.sh` — Prevent reading `.env*`, `credentials*`, `*.pem`, key files
15
- - `.claude/hooks/pre-commit-gate.sh` — Quality checks + sensitive data scan before commits
16
-
17
- **Obsolescence test:** AI gets smarter → safety becomes MORE important (velocity × risk). These hooks never obsolete.
18
-
19
- **Team impact:** Prevents accidents at individual dev level, reduces incident count team-wide.
20
-
21
- ---
22
-
23
- ## Pillar 2: SURFACES — Make state visible
24
-
25
- **Role:** Shared team context. Human and AI both read.
26
-
27
- **Components:**
28
- - `.dw/tasks/ACTIVE.md` — Auto-generated index of active tasks (TechLead cat to see team state)
29
- - `.dw/context/project-map.md` — Module structure and boundaries
30
- - `.dw/context/modules/*.md` — Per-module documentation
31
- - `CLAUDE.md` + `.claude/rules/dw.md` — Auto-injected conventions
32
-
33
- **Obsolescence test:** AI gets smarter → teams coordinate more ambitiously → surfaces become MORE load-bearing.
34
-
35
- **Team impact:** New dev onboards from surfaces alone. TechLead audits without asking devs "what are you doing?"
36
-
37
- ---
38
-
39
- ## Pillar 3: RECORDS — Capture decisions
40
-
41
- **Role:** Organizational memory. The WHY behind architectural choices.
42
-
43
- **Components:**
44
- - `.dw/decisions/{NNNN}-{title}.md` — ADRs with structured YAML header
45
- - `/dw:decision` skill — Interactive ADR wizard
46
- - Status tracking: `Proposed | Accepted | Deprecated | Superseded by ADR-{NNNN}`
47
-
48
- **Obsolescence test:** AI gets smarter → needs WHY context to avoid technically-correct but strategically-wrong decisions. ADRs become MORE valuable.
49
-
50
- **Team impact:** Replaces scattered Slack threads. 6-month-old devs find decision trail. New hires understand architecture fast.
51
-
52
- **Unique moat:** IDE tools (Cursor, Copilot) structurally can't own this — they're session-scoped, ADRs are cross-session artifacts.
53
-
54
- ---
55
-
56
- ## Pillar 4: BRIDGES — Connect across sessions
57
-
58
- **Role:** Continuity over time. Handle the "long chat → no handoff → team lost context" problem.
59
-
60
- **Components:**
61
- - `.dw/tasks/{task}/tracking.md` — Mutable progress log with friction journal
62
- - Stop hook auto-handoff — Appends session summary to tracking.md on uncommitted changes
63
- - (v2.0+) Living docs detection — Flag when code diverges from docs
64
-
65
- **Obsolescence test:** AI sessions remain ephemeral regardless of capability. Bridges always needed.
66
-
67
- **Team impact:** Pick-up-where-left-off works across devs and across sessions. TechLead sees real velocity from actual logs, not status meeting summaries.
68
-
69
- ---
70
-
71
- ## Pillar 5: TUNES — Behavioral knobs
72
-
73
- **Role:** Team/solo customization. Governs how Pillars 1-4 behave.
74
-
75
- **Components:**
76
- - `.dw/config/dw.config.yml` — Master config (depth, roles, flags)
77
- - `.dw/config/dw.config.local.yml` — Machine-specific override (gitignored)
78
- - Presets: `solo` · `team` · `enterprise`
79
- - Depth routing: `quick` · `standard` · `thorough`
80
- - Role system: `dev` · `techlead` · `ba` · `qc` · `pm`
81
-
82
- **Obsolescence test:** Team size and preferences always vary. Config always needed.
83
-
84
- **Team impact:** Same dw-kit serves vibe coder (preset solo) and enterprise team (preset enterprise) with same core.
85
-
86
- ---
87
-
88
- ## Pillar Cross-References
89
-
90
- | Question | Answer location |
91
- |----------|----------------|
92
- | "Is this safe to do?" | Pillar 1 (Guards) — hooks block |
93
- | "What's everyone working on?" | Pillar 2 (Surfaces) — ACTIVE.md |
94
- | "Why did we choose X?" | Pillar 3 (Records) — ADRs |
95
- | "Where were we yesterday?" | Pillar 4 (Bridges) — tracking.md |
96
- | "How does our team work?" | Pillar 5 (Tunes) — config + presets |
97
-
98
- ## Design Principles
99
-
100
- 1. **Descriptive, not prescriptive** — AI chooses approach; dw-kit supplies context + safety
101
- 2. **Obsolescence-aware** — Every feature passes "more valuable if AI smarter?" test
102
- 3. **Dual audience** — Solo + team from same core, different defaults
103
- 4. **Data-driven evolution** — Telemetry guides cut decisions, not gut-feel
104
- 5. **Escape hatches** — `--no-dw`, `legacy_features: true`, `DW_NO_TELEMETRY=1`
105
-
106
- ## Future: Pillar 6 — JANITORS (deferred to post-v2.0)
107
-
108
- **Status:** Draft, deferred — see `.dw/decisions/0003-pillar-6-janitors.md`
109
-
110
- **Role:** Reactive cleanup of AI-generated waste. Current 5 pillars are *preventive* (govern what goes in); Janitors governs *what stays*.
111
-
112
- **Rationale:** When 99% of code is AI-generated, prevention alone cannot scale. Inspired by urban waste management — cities use multi-tier systems (sort → collect → recycle → regulate), not just "don't litter."
113
-
114
- **Revisit:** After v2.0 GA (post 2026-08-15), based on real-world friction data.
115
-
116
- ## Versioning
117
-
118
- - **v1.3** — Foundation (new format, scaffolding, telemetry)
119
- - **v1.4** — Data-driven cuts based on telemetry evidence
120
- - **v2.0** — Unified release with full 5-pillar integration
121
- - **v2.1+** — Advanced features (auto-handoff LLM, cross-repo, dashboard UI)
122
- - **v2.2+** — Janitors pillar (if validated by v2.0 friction data)
package/CLAUDE.md DELETED
@@ -1,44 +0,0 @@
1
- # dw-kit (repo)
2
-
3
- Workflow toolkit codebase. Rules live in `.claude/rules/` (auto-loaded).
4
-
5
- **v2.0 direction:** Context-First SDLC Governance Layer (5 pillars — see `.dw/core/PILLARS.md`)
6
- **Current:** **v1.8.0-rc.1** (2026-05-25) — 4/5 phase substrate for [G-rgoal-realtime-orch](.dw/goals/G-rgoal-realtime-orch/goal.md) voice-meeting Root Goal: persistent CLI-agent session runtime (`dw session *`), Telegram chat bridge (`dw connector telegram setup`) with 1-command interactive wizard, multi-workspace registry (`dw workspace *`), and browser voice MVP (`dw voice`) with hybrid orchestrator fallback (Claude/Codex/Gemini), full bilingual UX (en + vi), voice-not-installed fallback via Google Translate TTS proxy. **v1.7.0 stable** on `main` (2026-05-24). 284/284 smoke tests. Active ADRs: ADR-0001 (Pragmatic Lean), ADR-0005/0006 (Supply-Chain Guard; sunset review 2026-08-12), ADR-0008 (Task Docs v3, v1.5), ADR-0009 (Agent OS, v1.6), ADR-0010 (Goals Layer, v1.7), ADR-0011 (Session Runtime + Voice Orchestrator, v1.8 — Accepted).
7
-
8
- ---
9
-
10
- ## Tech Stack
11
-
12
- - Runtime: Node.js ≥18, ESM (`.mjs`)
13
- - CLI: `commander` · UI: `enquirer`, `chalk` · Config: `js-yaml`, `ajv`
14
- - Tests: `node src/smoke-test.mjs`
15
-
16
- ## Repo Structure
17
-
18
- ```
19
- bin/ CLI entrypoint
20
- src/
21
- commands/ CLI subcommands (init, upgrade, dashboard, metrics, ...)
22
- lib/ Shared utilities (config, telemetry, active-index, ...)
23
- .claude/
24
- hooks/ Bash hooks (Guards pillar)
25
- rules/ dw.md (consolidated) + code-style + commit-standards
26
- skills/ Slash commands with dw:* namespace
27
- .dw/
28
- core/ WORKFLOW · THINKING · QUALITY · ROLES · PILLARS · templates/
29
- decisions/ ADRs (Records pillar)
30
- tasks/ Active + archive/ (Bridges pillar — via task.md v3 / tracking.md v2)
31
- metrics/ Local telemetry (events.jsonl)
32
- config/ dw.config.yml
33
- security/ IoC namespace fixture (Guards pillar — ADR-0005)
34
- research/ Investigation notes, RFC-style proposals, voter panel outputs
35
- ```
36
-
37
- ## Dev Notes
38
-
39
- - All source ESM — no CommonJS
40
- - `TOOLKIT_ROOT` resolved from `import.meta.url` in each command
41
- - Hooks python3-free (node only — Windows compat)
42
- - `dw-kit-evolve` + `dw-kit-audit` are maintainer-only — excluded from npm package
43
- - Published package files declared explicitly in `package.json#files`
44
- - Telemetry local-only, `DW_NO_TELEMETRY=1` to disable