dw-kit 1.7.0-rc.1 → 1.8.0-rc.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.
@@ -96,27 +96,25 @@ fi
96
96
  # v1.6 (ADR-0009 R2-2): Agent OS post-hoc check.
97
97
  # When ≥1 active claim exists, verify staged files fall within an active claim's write_scope.
98
98
  # Cooperative protocol — warn only (cannot prevent non-compliant agents).
99
- if [ -f "$DW_BIN" ] && command -v node >/dev/null 2>&1 && [ -n "$STAGED_FILES" ]; then
99
+ #
100
+ # Issue #13 Bug 3 fix: previous version inlined `node -e` importing
101
+ # `$CLAUDE_PROJECT_DIR/src/lib/agent-claim.mjs` — that path is cwd-relative and
102
+ # only exists in dw-kit's own repo, NOT in consumer projects. The block was
103
+ # silently no-op everywhere else. Replaced by `dw agent check-staged --stdin`
104
+ # which uses the installed binary. Prefer global `dw` on PATH; fall back to
105
+ # `node $DW_BIN` for in-repo local development (where DW_BIN points at the
106
+ # source bin script).
107
+ if [ -n "$STAGED_FILES" ]; then
100
108
  CLAIMS_DIR="$CLAUDE_PROJECT_DIR/.dw/cache/agents/claims"
101
109
  if [ -d "$CLAIMS_DIR" ] && ls "$CLAIMS_DIR"/*.json >/dev/null 2>&1; then
102
- OUT_OF_SCOPE=$(STAGED="$STAGED_FILES" node -e "
103
- const fs = require('fs');
104
- const path = require('path');
105
- (async () => {
106
- const { listClaims } = await import('$CLAUDE_PROJECT_DIR/src/lib/agent-claim.mjs');
107
- const { pathMatchesScope } = await import('$CLAUDE_PROJECT_DIR/src/lib/agent-conflict.mjs');
108
- const claims = listClaims(process.env.CLAUDE_PROJECT_DIR || '$CLAUDE_PROJECT_DIR').filter(c => c._live_status === 'created' || c._live_status === 'active');
109
- if (claims.length === 0) return;
110
- const allScopes = claims.flatMap(c => c.write_scope);
111
- const staged = (process.env.STAGED || '').split(/\r?\n/).filter(Boolean);
112
- const outside = staged.filter(f => !pathMatchesScope(f, allScopes));
113
- if (outside.length > 0) {
114
- console.log('Files NOT in any active claim write_scope:');
115
- for (const f of outside.slice(0, 10)) console.log(' ' + f);
116
- if (outside.length > 10) console.log(' ... +' + (outside.length - 10) + ' more');
117
- }
118
- })().catch(e => { /* fail-graceful */ });
119
- " 2>/dev/null || true)
110
+ if command -v dw >/dev/null 2>&1; then
111
+ OUT_OF_SCOPE=$(echo "$STAGED_FILES" | dw agent check-staged --stdin 2>/dev/null || true)
112
+ elif [ -f "$DW_BIN" ] && command -v node >/dev/null 2>&1; then
113
+ # Local dev fallback: in-repo dw-kit work where global `dw` not on PATH
114
+ OUT_OF_SCOPE=$(echo "$STAGED_FILES" | node "$DW_BIN" agent check-staged --stdin 2>/dev/null || true)
115
+ else
116
+ OUT_OF_SCOPE=""
117
+ fi
120
118
  if [ -n "$OUT_OF_SCOPE" ]; then
121
119
  echo "⚠️ Agent OS (ADR-0009 R2-2): post-hoc claim check" >&2
122
120
  echo "$OUT_OF_SCOPE" >&2
@@ -1,35 +1,35 @@
1
- ---
2
- date: [ISO timestamp — e.g. 2026-04-02T14:30:00]
3
- from: [agent-role — researcher | planner | developer | reviewer | debugger]
4
- to: [agent-role — planner | developer | user]
5
- task: [task-name]
6
- status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
7
- ---
8
-
9
- ## Summary
10
-
11
- [1-3 câu tóm tắt kết quả / findings]
12
-
13
- ## Details
14
-
15
- [Chi tiết findings, decisions, hoặc implementation notes]
16
-
17
- ## Concerns (nếu DONE_WITH_CONCERNS)
18
-
19
- - [Concern 1]
20
- - [Concern 2]
21
-
22
- ## Blockers (nếu BLOCKED)
23
-
24
- - **Blocker**: [Mô tả vấn đề]
25
- - **Owner**: [Ai cần resolve]
26
- - **Unblock by**: [Action cần làm]
27
-
28
- ## Needs (nếu NEEDS_CONTEXT)
29
-
30
- - [ ] [Thông tin cần thêm]
31
- - [ ] [Quyết định cần từ user/TL]
32
-
33
- ## Next Steps
34
-
35
- - [Bước tiếp theo đề xuất]
1
+ ---
2
+ date: [ISO timestamp — e.g. 2026-04-02T14:30:00]
3
+ from: [agent-role — researcher | planner | developer | reviewer | debugger]
4
+ to: [agent-role — planner | developer | user]
5
+ task: [task-name]
6
+ status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
7
+ ---
8
+
9
+ ## Summary
10
+
11
+ [1-3 câu tóm tắt kết quả / findings]
12
+
13
+ ## Details
14
+
15
+ [Chi tiết findings, decisions, hoặc implementation notes]
16
+
17
+ ## Concerns (nếu DONE_WITH_CONCERNS)
18
+
19
+ - [Concern 1]
20
+ - [Concern 2]
21
+
22
+ ## Blockers (nếu BLOCKED)
23
+
24
+ - **Blocker**: [Mô tả vấn đề]
25
+ - **Owner**: [Ai cần resolve]
26
+ - **Unblock by**: [Action cần làm]
27
+
28
+ ## Needs (nếu NEEDS_CONTEXT)
29
+
30
+ - [ ] [Thông tin cần thêm]
31
+ - [ ] [Quyết định cần từ user/TL]
32
+
33
+ ## Next Steps
34
+
35
+ - [Bước tiếp theo đề xuất]
@@ -0,0 +1,54 @@
1
+ # ============================================================================
2
+ # dw-kit Agent Runtime Config — v1
3
+ # Reference: docs/dw_remote_agent_first_context_strategy.md §8 Priority 3
4
+ # ============================================================================
5
+ # Used by `dw session start --agent <name>`. Each agent is a thin descriptor
6
+ # of an EXTERNAL CLI coding agent dw-kit orchestrates (Claude Code, Codex,
7
+ # Gemini, etc.). dw-kit does NOT bundle these agents — they must be installed
8
+ # separately. Per research §14: "Do not replace ... Orchestrate them."
9
+ # ----------------------------------------------------------------------------
10
+
11
+ schema_version: agents@v1
12
+
13
+ agents:
14
+ # Anthropic Claude Code — non-interactive print mode.
15
+ # Install: https://docs.claude.com/en/docs/claude-code
16
+ # F-18 (Win32): goal_mode=stdin so the prompt goes via the child's stdin
17
+ # instead of an arg. Win32 PATHEXT resolution requires shell:true for
18
+ # .cmd shims (claude.cmd), and shell:true + args[] (Node DEP0190) is unsafe
19
+ # for multi-line / quoted prompts — stdin keeps user content out of the
20
+ # shell command line entirely.
21
+ claude:
22
+ command: claude
23
+ args: ["--print"]
24
+ goal_mode: stdin
25
+ env: {}
26
+ description: "Claude Code (Anthropic) — non-interactive print mode"
27
+
28
+ # OpenAI Codex CLI — non-interactive exec mode.
29
+ # Install: npm install -g @openai/codex
30
+ codex:
31
+ command: codex
32
+ args: ["exec"]
33
+ goal_mode: stdin
34
+ env: {}
35
+ description: "Codex CLI (OpenAI) — non-interactive exec mode"
36
+
37
+ # Google Gemini CLI — placeholder shape; adjust args once installed.
38
+ # Install: see Gemini CLI docs (subject to change).
39
+ gemini:
40
+ command: gemini
41
+ args: ["chat", "--once"]
42
+ goal_mode: stdin
43
+ env: {}
44
+ description: "Gemini CLI (Google) — single-prompt mode [placeholder]"
45
+
46
+ # Hermetic test agent used by smoke-test. Echoes the goal then exits 0.
47
+ # NOT for real use — kept here so tests can `dw session start --agent _smoke_echo`
48
+ # without depending on any external binary.
49
+ _smoke_echo:
50
+ command: node
51
+ args: ["-e", "process.stdout.write(`SMOKE_ECHO_GOAL: ${process.argv[1] || ''}\\n`); setTimeout(() => process.exit(0), 50);"]
52
+ goal_mode: trailing-arg
53
+ env: {}
54
+ description: "Internal smoke-test echo agent (do not use for real work)"
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,64 @@
1
+ # ============================================================================
2
+ # dw-kit Connectors Config — v1
3
+ # Reference: docs/connector-telegram.md
4
+ # Goal: G-rgoal-realtime-orch phase 2 (chat platform adapters)
5
+ # ============================================================================
6
+ # Connectors bridge external chat platforms (Telegram, Zalo, Slack later) to
7
+ # the local dw session runtime. The chat side speaks "/status", "/sessions",
8
+ # "/start <agent> <goal>", "/logs <id>" etc.; the connector translates and
9
+ # calls the same session-store API as `dw session *`.
10
+ #
11
+ # SECRETS (bot tokens) should NEVER be committed. Prefer environment vars:
12
+ # DW_TG_BOT_TOKEN=... for Telegram
13
+ # This file holds policy (enabled, allow-list) and non-secret defaults.
14
+ # ----------------------------------------------------------------------------
15
+
16
+ schema_version: connectors@v1
17
+
18
+ telegram:
19
+ enabled: false # Safe default. Enable in connectors.local.yml (gitignored).
20
+ bot_token: "" # leave empty; pass via DW_TG_BOT_TOKEN env
21
+ allowed_user_ids: [] # numeric Telegram user IDs. Empty = bot rejects all. Maintainer keeps their own id in a local-only override (NOT committed — `.dw/config/` ships to npm).
22
+ default_workspace: "" # empty = use cwd at start time
23
+ default_agent: claude # used by /start when agent name omitted
24
+ long_poll_timeout_sec: 25 # Telegram supports up to 50; lower if network is flaky
25
+
26
+ # Voice channel (Phase 4 substrate of G-rgoal-realtime-orch).
27
+ # `dw voice` boots a localhost HTTP server with a browser page using Web
28
+ # Speech API for ASR + TTS. Optional orchestrator hybrid: when the regex
29
+ # parser misses, hand the transcript to the configured agent (Claude Code /
30
+ # Codex / Gemini) with a voice-aware system prompt.
31
+ voice:
32
+ # Default UI language for SpeechRecognition + TTS. User can override on the
33
+ # page via dropdown. Common values: en-US · vi-VN · ja-JP · ko-KR · zh-CN.
34
+ # Browser support varies — Chrome / Edge / Safari are best.
35
+ lang: en-US
36
+ # Extra languages to surface in the dropdown (beyond `lang` + en-US default).
37
+ extra_langs: [vi-VN]
38
+ # Server-side TTS fallback when no native browser/OS voice matches the
39
+ # selected language. F-23 (G-dogfood-v1.7): a fresh Windows install has
40
+ # no Vietnamese SAPI voice; we proxy MP3 from Google Translate's public
41
+ # TTS endpoint. Modes:
42
+ # auto — only when no native voice matches (default; balanced)
43
+ # always — always use server-side (best for cross-machine consistency)
44
+ # none — never use server-side (privacy: spoken text stays local)
45
+ # Privacy note: in `auto`/`always`, the spoken text leaves the machine to
46
+ # translate.google.com. Disable with `none` if that is unacceptable.
47
+ fallback_tts: auto
48
+ orchestrator:
49
+ enabled: false # opt-in. To enable WITHOUT committing it, add the
50
+ # same `voice:` section to `.dw/config/connectors.local.yml`
51
+ # (gitignored) — the local file overrides this template
52
+ # via deep-merge (same pattern as bot_token + allow-list).
53
+ agent: claude # must exist in .dw/config/agents.yml; CLI must be on PATH
54
+ timeout_ms: 30000 # max wait per turn (Claude is typically 2-8s)
55
+
56
+ # Future connectors land here:
57
+ #
58
+ # zalo:
59
+ # enabled: false
60
+ # ...
61
+ #
62
+ # slack:
63
+ # enabled: false
64
+ # ...
@@ -1,53 +1,53 @@
1
- # Agent Communication Protocol — dw-kit v1.2
2
-
3
- ## Mục Đích
4
-
5
- Khi một task lớn cần nhiều "vai" khác nhau (researcher → planner → developer), việc ghi lại kết quả từng bước giúp:
6
- - Team members (người hoặc agent) biết chính xác task đang ở đâu
7
- - Audit trail rõ ràng: ai quyết định gì, lúc nào
8
- - Session tiếp theo có thể tiếp tục mà không cần hỏi lại
9
-
10
- ## Convention: Reports Directory
11
-
12
- ```
13
- .dw/tasks/[task-name]/
14
- ├── [name]-context.md # Research findings
15
- ├── [name]-plan.md # Implementation plan
16
- ├── [name]-progress.md # Progress tracking
17
- └── reports/ # Agent communication (v1.2+)
18
- ├── 260402-1430-from-researcher-to-planner-analysis.md
19
- ├── 260402-1500-from-planner-to-developer-subtask-1.md
20
- └── 260402-1600-from-developer-to-reviewer-pr-ready.md
21
- ```
22
-
23
- **Filename format**: `[YYMMDD-HHMM]-from-[role]-to-[role]-[description].md`
24
-
25
- ## Status Codes
26
-
27
- | Status | Nghĩa |
28
- |--------|-------|
29
- | `DONE` | Hoàn thành, output sẵn sàng để dùng |
30
- | `DONE_WITH_CONCERNS` | Xong nhưng có điểm đáng chú ý / cần review |
31
- | `BLOCKED` | Bị chặn, cần action từ bên ngoài để tiếp tục |
32
- | `NEEDS_CONTEXT` | Thiếu thông tin, cần human confirm |
33
-
34
- ## Khi Nào Tạo Report
35
-
36
- - Sau khi `dw-research` hoàn thành → report `from-researcher-to-planner`
37
- - Sau khi `dw-plan` approved → report `from-planner-to-developer`
38
- - Khi phát hiện blocker trong execute → report `from-developer-to-user` với `BLOCKED`
39
- - Sau khi review xong → report `from-reviewer-to-developer`
40
-
41
- ## Khi Nào KHÔNG Cần Report
42
-
43
- - Tasks `quick` depth (≤2 files, hotfix) → không cần overhead này
44
- - Solo dev, single session → progress.md đã đủ
45
- - Thông tin đã có trong context.md / plan.md → không duplicate
46
-
47
- ## Template
48
-
49
- Dùng `.claude/templates/agent-report.md`
50
-
51
- ## Lưu Ý Quan Trọng
52
-
53
- Reports là **cho con người đọc**, không phải protocol cho AI. Claude Code đã communicate qua conversation context. Reports giúp team members theo dõi task cross-session, không phải AI-to-AI messaging.
1
+ # Agent Communication Protocol — dw-kit v1.2
2
+
3
+ ## Mục Đích
4
+
5
+ Khi một task lớn cần nhiều "vai" khác nhau (researcher → planner → developer), việc ghi lại kết quả từng bước giúp:
6
+ - Team members (người hoặc agent) biết chính xác task đang ở đâu
7
+ - Audit trail rõ ràng: ai quyết định gì, lúc nào
8
+ - Session tiếp theo có thể tiếp tục mà không cần hỏi lại
9
+
10
+ ## Convention: Reports Directory
11
+
12
+ ```
13
+ .dw/tasks/[task-name]/
14
+ ├── [name]-context.md # Research findings
15
+ ├── [name]-plan.md # Implementation plan
16
+ ├── [name]-progress.md # Progress tracking
17
+ └── reports/ # Agent communication (v1.2+)
18
+ ├── 260402-1430-from-researcher-to-planner-analysis.md
19
+ ├── 260402-1500-from-planner-to-developer-subtask-1.md
20
+ └── 260402-1600-from-developer-to-reviewer-pr-ready.md
21
+ ```
22
+
23
+ **Filename format**: `[YYMMDD-HHMM]-from-[role]-to-[role]-[description].md`
24
+
25
+ ## Status Codes
26
+
27
+ | Status | Nghĩa |
28
+ |--------|-------|
29
+ | `DONE` | Hoàn thành, output sẵn sàng để dùng |
30
+ | `DONE_WITH_CONCERNS` | Xong nhưng có điểm đáng chú ý / cần review |
31
+ | `BLOCKED` | Bị chặn, cần action từ bên ngoài để tiếp tục |
32
+ | `NEEDS_CONTEXT` | Thiếu thông tin, cần human confirm |
33
+
34
+ ## Khi Nào Tạo Report
35
+
36
+ - Sau khi `dw-research` hoàn thành → report `from-researcher-to-planner`
37
+ - Sau khi `dw-plan` approved → report `from-planner-to-developer`
38
+ - Khi phát hiện blocker trong execute → report `from-developer-to-user` với `BLOCKED`
39
+ - Sau khi review xong → report `from-reviewer-to-developer`
40
+
41
+ ## Khi Nào KHÔNG Cần Report
42
+
43
+ - Tasks `quick` depth (≤2 files, hotfix) → không cần overhead này
44
+ - Solo dev, single session → progress.md đã đủ
45
+ - Thông tin đã có trong context.md / plan.md → không duplicate
46
+
47
+ ## Template
48
+
49
+ Dùng `.claude/templates/agent-report.md`
50
+
51
+ ## Lưu Ý Quan Trọng
52
+
53
+ Reports là **cho con người đọc**, không phải protocol cho AI. Claude Code đã communicate qua conversation context. Reports giúp team members theo dõi task cross-session, không phải AI-to-AI messaging.
@@ -114,7 +114,27 @@
114
114
  },
115
115
  "worktree_path": {
116
116
  "type": "string",
117
- "description": "If agent operates in a git worktree per R2-3, the worktree path. .dw/cache/worktrees/{agent-id}/"
117
+ "description": "Absolute path to the git worktree this agent operates in (issue #15). dw does NOT create or delete this directory — the caller harness owns lifecycle (`git worktree add` / `git worktree remove`). When two claims declare distinct worktree_path values, dw conflict detection skips write_scope comparison between them (physical isolation). One or both null → conservative; assume overlap."
118
+ },
119
+ "original_lease_expires": {
120
+ "type": "string",
121
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
122
+ "description": "Issue #15: lease_expires at claim creation time (immutable). Renewals do NOT change this — caps total lease window via max_renewals."
123
+ },
124
+ "renewed_at": {
125
+ "type": "string",
126
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
127
+ "description": "Issue #15: timestamp of last `dw agent renew` call. Stamped on each renewal; previous value overwritten."
128
+ },
129
+ "renewal_count": {
130
+ "type": "integer",
131
+ "minimum": 0,
132
+ "description": "Issue #15: number of times this claim has been renewed. Bounded by MAX_RENEWALS (default 3) to prevent unbounded lease compounding."
133
+ },
134
+ "previous_lease_expires": {
135
+ "type": "string",
136
+ "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z$",
137
+ "description": "Issue #15: lease_expires value immediately before the most recent renewal. Provides self-describing renewal history without depending on events.jsonl."
118
138
  }
119
139
  },
120
140
  "allOf": [
package/CLAUDE.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Workflow toolkit codebase. Rules live in `.claude/rules/` (auto-loaded).
4
4
 
5
5
  **v2.0 direction:** Context-First SDLC Governance Layer (5 pillars — see `.dw/core/PILLARS.md`)
6
- **Current:** v1.6.0-rc.1 on npm `rc` tag (2026-05-20) + **v1.7.0-rc.1 candidate** on `feat/goals-okr-v1.7` shipping ADR-0010 Goals Management Layer (Accepted Round 2 all 4 Critical + 7 Warnings + 4 Suggestions resolved; `bcurts/agentchattr` summary primitive + bigokr icon/cycle/progress/constellation visualization borrowed; 150/150 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). v1.4 cuts pending telemetry.
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) pending codification.
7
7
 
8
8
  ---
9
9
 
package/bin/dw.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const [major] = process.versions.node.split('.').map(Number);
4
4
  if (major < 18) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dw-kit",
3
- "version": "1.7.0-rc.1",
3
+ "version": "1.8.0-rc.1",
4
4
  "description": "AI development workflow toolkit — structured, quality-assured, team-ready. From requirements to dashboard.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -180,6 +180,7 @@ export function run(argv) {
180
180
  .description('Watch task.md for changes and live-reload the HTML preview in browser (local server, debounced)')
181
181
  .option('-p, --port <port>', 'Local server port (auto-finds next available if busy)', parseInt)
182
182
  .option('--rotate-token', 'Regenerate .dw/cache/watch.token at startup (invalidates old browser sessions; C-3)')
183
+ .option('--no-open', 'Skip auto-opening browser tab (also implied by DW_NO_OPEN=1, CI=true, or non-TTY stdout — fixes #23)')
183
184
  .action(async (taskName, opts) => {
184
185
  const { taskWatchCommand } = await import('./commands/task-watch.mjs');
185
186
  await taskWatchCommand(taskName, opts);
@@ -196,7 +197,7 @@ export function run(argv) {
196
197
 
197
198
  const agentCmd = program
198
199
  .command('agent')
199
- .description('Agent OS multi-agent orchestration (ADR-0009): claim · release · claims · reports · conflicts');
200
+ .description('Agent OS multi-agent orchestration (ADR-0009): claim · release · renew · expire · claims · reports · conflicts · check-staged · verify');
200
201
 
201
202
  agentCmd
202
203
  .command('claim <task-id>')
@@ -224,6 +225,15 @@ export function run(argv) {
224
225
  await agentReleaseCommand(claimId, opts);
225
226
  });
226
227
 
228
+ agentCmd
229
+ .command('renew <claim-id>')
230
+ .description('Extend lease on an active claim WITHOUT changing claim_id (preserves audit continuity vs expire+reclaim). Capped at 3 renewals (issue #15)')
231
+ .option('-l, --lease <duration>', 'New lease window (e.g. 30m, 1h, 4h)', '1h')
232
+ .action(async (claimId, opts) => {
233
+ const { agentRenewCommand } = await import('./commands/agent-claim.mjs');
234
+ await agentRenewCommand(claimId, opts);
235
+ });
236
+
227
237
  agentCmd
228
238
  .command('expire <claim-id>')
229
239
  .description('Mark a claim as expired or invalidated (orchestrator action)')
@@ -265,6 +275,192 @@ export function run(argv) {
265
275
  await agentConflictsCommand(opts);
266
276
  });
267
277
 
278
+ agentCmd
279
+ .command('check-staged [files...]')
280
+ .description('Check staged files against active claim write_scopes (R2-2 cooperative; used by pre-commit-gate hook). Always exits 0; warning on stdout.')
281
+ .option('--stdin', 'Read file list from stdin (one path per line) instead of positional args')
282
+ .action(async (files, opts) => {
283
+ const { agentCheckStagedCommand } = await import('./commands/agent-check-staged.mjs');
284
+ await agentCheckStagedCommand(files, opts);
285
+ });
286
+
287
+ agentCmd
288
+ .command('verify <task-id>')
289
+ .description('Check events.jsonl well-formedness for a task (detects malformed JSON lines; exits 1 if any). Note: not full tamper-evidence — use git log for that.')
290
+ .option('-v, --verbose', 'Show raw line content for malformed entries')
291
+ .option('--no-strict', 'Report malformed lines but always exit 0')
292
+ .action(async (taskId, opts) => {
293
+ const { agentVerifyCommand } = await import('./commands/agent-verify.mjs');
294
+ await agentVerifyCommand(taskId, opts);
295
+ });
296
+
297
+ // ─ session: persistent CLI-agent session runtime (G-rgoal-realtime-orch phase 1) ─
298
+ const sessionCmd = program
299
+ .command('session')
300
+ .description('Persistent CLI-agent session runtime (orchestrates Claude Code / Codex / Gemini): start · list · show · logs · stop. Sessions survive SSH disconnect.');
301
+
302
+ sessionCmd
303
+ .command('start')
304
+ .description('Start a CLI coding agent as a detached session (survives SSH disconnect)')
305
+ .requiredOption('-a, --agent <name>', 'Agent name from .dw/config/agents.yml (e.g. claude, codex, gemini)')
306
+ .requiredOption('-g, --goal <text>', 'Goal/prompt to pass to the agent')
307
+ .option('-w, --workspace <path>', 'Working directory for the agent (default: cwd)')
308
+ .action(async (opts) => {
309
+ const { sessionStartCommand } = await import('./commands/session.mjs');
310
+ await sessionStartCommand(opts);
311
+ });
312
+
313
+ sessionCmd
314
+ .command('list')
315
+ .description('List active and recent sessions (this workspace, or --all-workspaces)')
316
+ .option('-s, --status <name>', 'Filter by status (running|completed|failed|stopped|exited|all)', 'all')
317
+ .option('-n, --limit <n>', 'Cap rows', parseInt)
318
+ .option('--all-workspaces', 'Aggregate sessions across every registered workspace (phase 3)')
319
+ .action(async (opts) => {
320
+ const { sessionListCommand } = await import('./commands/session.mjs');
321
+ await sessionListCommand(opts);
322
+ });
323
+
324
+ sessionCmd
325
+ .command('show <session-id>')
326
+ .description('Show full snapshot of a session (state + last 10 output lines)')
327
+ .action(async (sessionId) => {
328
+ const { sessionShowCommand } = await import('./commands/session.mjs');
329
+ await sessionShowCommand(sessionId);
330
+ });
331
+
332
+ sessionCmd
333
+ .command('logs <session-id>')
334
+ .description('Print session output log (default: full output.log; --events for JSON events)')
335
+ .option('-n, --tail <n>', 'Tail last N lines/events', parseInt)
336
+ .option('--events', 'Read events.jsonl instead of raw output.log')
337
+ .option('--bytes <n>', 'Cap output at N tail bytes', parseInt)
338
+ .action(async (sessionId, opts) => {
339
+ const { sessionLogsCommand } = await import('./commands/session.mjs');
340
+ await sessionLogsCommand(sessionId, opts);
341
+ });
342
+
343
+ sessionCmd
344
+ .command('stop <session-id>')
345
+ .description('Send SIGTERM (or --signal) to a session\'s process')
346
+ .option('--signal <name>', 'Signal to send (default SIGTERM)', 'SIGTERM')
347
+ .action(async (sessionId, opts) => {
348
+ const { sessionStopCommand } = await import('./commands/session.mjs');
349
+ await sessionStopCommand(sessionId, opts);
350
+ });
351
+
352
+ // ─ voice: browser-based voice MVP (G-rgoal phase 4 — KR-A destination) ─
353
+ program
354
+ .command('voice')
355
+ .description('Browser-based voice MVP — speak dw commands, hear results. Local server + Web Speech API. Spike scope: localhost only.')
356
+ .option('-p, --port <port>', 'Local server port (default 4500; auto-finds next free)', parseInt)
357
+ .option('--default-agent <name>', 'Default agent for "start ..." without explicit agent name')
358
+ .option('--no-open', 'Skip auto-opening browser tab (also implied by DW_NO_OPEN=1, CI=true, or non-TTY stdout)')
359
+ .action(async (opts) => {
360
+ const { voiceCommand } = await import('./commands/voice.mjs');
361
+ await voiceCommand(opts);
362
+ });
363
+
364
+ // ─ workspace: cross-project orchestration registry (G-rgoal phase 3) ─
365
+ const workspaceCmd = program
366
+ .command('workspace')
367
+ .description('Multi-workspace registry: register · list · remove · resolve. Lets one orchestrator drive sessions across multiple .dw/ projects on this host.');
368
+
369
+ workspaceCmd
370
+ .command('register')
371
+ .description('Register the current directory (or --path) as a named workspace')
372
+ .requiredOption('-n, --name <alias>', 'Short alias (a-z A-Z 0-9 . _ -, ≤64 chars)')
373
+ .option('-p, --path <abs>', 'Absolute path to workspace root (default: cwd)')
374
+ .action(async (opts) => {
375
+ const { workspaceRegisterCommand } = await import('./commands/workspace.mjs');
376
+ await workspaceRegisterCommand(opts);
377
+ });
378
+
379
+ workspaceCmd
380
+ .command('list')
381
+ .description('List registered workspaces (with session tally per workspace)')
382
+ .action(async (opts) => {
383
+ const { workspaceListCommand } = await import('./commands/workspace.mjs');
384
+ await workspaceListCommand(opts);
385
+ });
386
+
387
+ workspaceCmd
388
+ .command('remove')
389
+ .description('Remove a workspace from the registry')
390
+ .requiredOption('-n, --name <alias>', 'Workspace alias')
391
+ .action(async (opts) => {
392
+ const { workspaceRemoveCommand } = await import('./commands/workspace.mjs');
393
+ await workspaceRemoveCommand(opts);
394
+ });
395
+
396
+ workspaceCmd
397
+ .command('resolve')
398
+ .description('Print the absolute path of a registered workspace (scriptable)')
399
+ .requiredOption('-n, --name <alias>', 'Workspace alias')
400
+ .action(async (opts) => {
401
+ const { workspaceResolveCommand } = await import('./commands/workspace.mjs');
402
+ await workspaceResolveCommand(opts);
403
+ });
404
+
405
+ // ─ connector: chat platform adapters (G-rgoal phase 2) ─
406
+ const connectorCmd = program
407
+ .command('connector')
408
+ .description('Chat platform connectors (Telegram first; Zalo/Slack later). list · telegram start/check.');
409
+
410
+ connectorCmd
411
+ .command('list')
412
+ .description('Show configured connectors and their enabled/disabled status')
413
+ .action(async () => {
414
+ const { connectorListCommand } = await import('./commands/connector.mjs');
415
+ await connectorListCommand();
416
+ });
417
+
418
+ const telegramCmd = connectorCmd
419
+ .command('telegram')
420
+ .description('Telegram bot connector: bridges chat messages to dw session commands');
421
+
422
+ telegramCmd
423
+ .command('start')
424
+ .description('Start the long-poll loop (foreground). Ctrl+C / SIGTERM to stop.')
425
+ .option('--token <tok>', 'Override bot token (else DW_TG_BOT_TOKEN env or connectors.yml)')
426
+ .option('--base-url <url>', 'Override Telegram API base URL (test/mock only)')
427
+ .option('--once', 'Single getUpdates poll then exit (for diagnostics + tests)')
428
+ .action(async (opts) => {
429
+ const { connectorTelegramStartCommand } = await import('./commands/connector.mjs');
430
+ await connectorTelegramStartCommand(opts);
431
+ });
432
+
433
+ telegramCmd
434
+ .command('setup')
435
+ .description('Interactive wizard: prompts token, validates, auto-discovers your user_id, writes connectors.local.yml (gitignored)')
436
+ .option('--token <tok>', 'Pre-provide token (skips the password prompt)')
437
+ .option('--base-url <url>', 'Override Telegram API base URL (test/mock only)')
438
+ .action(async (opts) => {
439
+ const { connectorTelegramSetupCommand } = await import('./commands/connector.mjs');
440
+ await connectorTelegramSetupCommand(opts);
441
+ });
442
+
443
+ telegramCmd
444
+ .command('check')
445
+ .description('Sanity-check config + reach Telegram API (calls getMe)')
446
+ .option('--token <tok>', 'Override bot token')
447
+ .option('--base-url <url>', 'Override Telegram API base URL')
448
+ .action(async (opts) => {
449
+ const { connectorTelegramCheckCommand } = await import('./commands/connector.mjs');
450
+ await connectorTelegramCheckCommand(opts);
451
+ });
452
+
453
+ sessionCmd
454
+ .command('prune')
455
+ .description('Remove old terminated sessions (default: >=7d ended; never prunes running)')
456
+ .option('--older-than <dur>', 'Age cutoff like 7d / 24h / 90m / 30s', '7d')
457
+ .option('--status <list>', 'Comma-separated terminal statuses to consider (default: completed,failed,stopped,exited)', '')
458
+ .option('--dry-run', 'Show what would be removed without deleting')
459
+ .action(async (opts) => {
460
+ const { sessionPruneCommand } = await import('./commands/session.mjs');
461
+ await sessionPruneCommand(opts);
462
+ });
463
+
268
464
  const goalCmd = program
269
465
  .command('goal')
270
466
  .description('Goals Management Layer (ADR-0010): strategic layer above tasks. new · show · link · summary · portfolio · lint · bump · delete · view');