@web42/stask 0.1.5 → 0.1.7

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.
@@ -9,7 +9,7 @@ import fs from 'fs';
9
9
  import { createHash } from 'crypto';
10
10
  import { CONFIG, getWorkspaceLibs } from '../lib/env.mjs';
11
11
  import { withTransaction } from '../lib/tx.mjs';
12
- import { syncTaskToSlack } from '../lib/slack-row.mjs';
12
+ import { syncTaskToSlack, setThreadRef } from '../lib/slack-row.mjs';
13
13
 
14
14
  function parseArgs(argv) {
15
15
  const args = {};
@@ -21,6 +21,31 @@ function parseArgs(argv) {
21
21
  return args;
22
22
  }
23
23
 
24
+ /**
25
+ * Discover the list item's comment thread on the list channel.
26
+ * Slack Lists internally use a channel (list ID with F→C prefix swap).
27
+ * Each list item gets a thread whose ts shares the same epoch second
28
+ * as the item's date_created.
29
+ *
30
+ * @param {object} slackApi - Slack API module
31
+ * @param {string} listChannelId - List channel ID (C-prefixed)
32
+ * @param {number} itemDateCreated - Item's date_created (Unix epoch)
33
+ */
34
+ async function discoverListItemThread(slackApi, listChannelId, itemDateCreated) {
35
+ const epoch = String(itemDateCreated);
36
+ const oldest = String(itemDateCreated - 2);
37
+ const latest = String(itemDateCreated + 5);
38
+
39
+ for (let attempt = 0; attempt < 3; attempt++) {
40
+ const result = await slackApi.getChannelHistory(listChannelId, { oldest, latest, limit: 10 });
41
+ const messages = result.messages || [];
42
+ const match = messages.find(m => m.ts && m.ts.startsWith(epoch + '.'));
43
+ if (match) return match.ts;
44
+ await new Promise(r => setTimeout(r, 1000));
45
+ }
46
+ return null;
47
+ }
48
+
24
49
  export async function run(argv) {
25
50
  const args = parseArgs(argv);
26
51
 
@@ -84,5 +109,29 @@ export async function run(argv) {
84
109
  }
85
110
  );
86
111
 
112
+ // Post-commit: discover list item thread and post creation message (best-effort)
113
+ try {
114
+ const listChannelId = CONFIG.slack.listId.replace(/^F/, 'C');
115
+ const db = libs.trackerDb.getDb();
116
+ // Get the Slack item's date_created (Unix epoch) to match the thread ts
117
+ const { getSlackRowId } = await import('../lib/slack-row.mjs');
118
+ const rowId = getSlackRowId(db, result.taskId);
119
+ const itemInfo = await libs.slackApi.slackApiRequest('POST', '/slackLists.items.info', {
120
+ list_id: CONFIG.slack.listId, id: rowId,
121
+ });
122
+ const dateCreated = itemInfo.record?.date_created;
123
+ if (dateCreated) {
124
+ const threadTs = await discoverListItemThread(libs.slackApi, listChannelId, dateCreated);
125
+ if (threadTs) {
126
+ const humanMention = `<@${CONFIG.human.slackUserId}>`;
127
+ const msg = `Creating this thread to discuss *${result.taskId}: ${args.name}*. This will be the thread where we post updates and talk about this task.\n\n${humanMention} spec is ready for your review. Let me know what you think!`;
128
+ await libs.slackApi.postMessage(listChannelId, msg, { threadTs });
129
+ setThreadRef(db, result.taskId, listChannelId, threadTs);
130
+ }
131
+ }
132
+ } catch (err) {
133
+ console.error(`WARNING: Thread linking failed: ${err.message}`);
134
+ }
135
+
87
136
  console.log(`Created ${result.taskId}: "${args.name}" | Status: To-Do | Assigned: ${CONFIG.human.name} | Spec: ${fileId}`);
88
137
  }
@@ -11,7 +11,7 @@ import path from 'path';
11
11
  import { execFileSync, execSync } from 'child_process';
12
12
  import { CONFIG, LIB_DIR, getWorkspaceLibs } from '../lib/env.mjs';
13
13
  import { withDb } from '../lib/tx.mjs';
14
- import { syncTaskToSlack } from '../lib/slack-row.mjs';
14
+ import { syncTaskToSlack, getThreadRef } from '../lib/slack-row.mjs';
15
15
  import { isTaskClaimable } from '../lib/session-tracker.mjs';
16
16
  import { getLeadAgent } from '../lib/roles.mjs';
17
17
 
@@ -348,7 +348,9 @@ These are NOT from ${CONFIG.human.name}. Send a Slack DM to ${CONFIG.human.name}
348
348
  }
349
349
 
350
350
  if (action && prompt) {
351
- pendingTasks.push({ taskId, taskName: task['Task Name'], status, parent: task['Parent'], specFileId, action, prompt });
351
+ const threadRef = getThreadRef(db, isSubtask ? task['Parent'] : taskId);
352
+ const thread = threadRef ? { channelId: threadRef.channelId, threadTs: threadRef.threadTs } : null;
353
+ pendingTasks.push({ taskId, taskName: task['Task Name'], status, parent: task['Parent'], specFileId, action, prompt, thread });
352
354
  }
353
355
  }
354
356
 
package/commands/show.mjs CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { withDb } from '../lib/tx.mjs';
8
+ import { getThreadRef } from '../lib/slack-row.mjs';
8
9
 
9
10
  export async function run(argv) {
10
11
  const taskId = argv[0];
@@ -34,6 +35,11 @@ export async function run(argv) {
34
35
  if (task['QA Report 3'] !== 'None') console.log(` QA Report 3: ${task['QA Report 3']}`);
35
36
  if (task['Blocker'] !== 'None') console.log(` Blocker: ${task['Blocker']}`);
36
37
 
38
+ const threadRef = getThreadRef(db, taskId);
39
+ if (threadRef) {
40
+ console.log(` Thread: ${threadRef.channelId}:${threadRef.threadTs}`);
41
+ }
42
+
37
43
  console.log(` Created: ${task['created_at']}`);
38
44
  console.log(` Updated: ${task['updated_at']}`);
39
45
 
package/lib/env.mjs CHANGED
@@ -76,6 +76,7 @@ export function loadEnv() {
76
76
  if (config.slack?.listId === 'FROM_ENV' && process.env.LIST_ID) {
77
77
  config.slack.listId = process.env.LIST_ID;
78
78
  }
79
+
79
80
  }
80
81
 
81
82
  // ─── Local lib imports (all bundled in stask/lib/) ─────────────────
package/lib/slack-api.mjs CHANGED
@@ -187,6 +187,26 @@ export async function updateListCells(listId, cells) {
187
187
  });
188
188
  }
189
189
 
190
+ /**
191
+ * Fetch recent messages from a channel (conversations.history).
192
+ */
193
+ export async function getChannelHistory(channelId, { limit = 20, oldest, latest } = {}) {
194
+ const payload = { channel: channelId, limit };
195
+ if (oldest) payload.oldest = oldest;
196
+ if (latest) payload.latest = latest;
197
+ return slackApiRequest('POST', '/conversations.history', payload);
198
+ }
199
+
200
+ /**
201
+ * Post a message to a channel (optionally as a thread reply).
202
+ * Returns the full Slack response including ts (message timestamp).
203
+ */
204
+ export async function postMessage(channelId, text, { threadTs, unfurlLinks } = {}) {
205
+ const payload = { channel: channelId, text, unfurl_links: unfurlLinks ?? false };
206
+ if (threadTs) payload.thread_ts = threadTs;
207
+ return slackApiRequest('POST', '/chat.postMessage', payload);
208
+ }
209
+
190
210
  /**
191
211
  * Delete a row from a Slack List.
192
212
  */
package/lib/slack-row.mjs CHANGED
@@ -26,6 +26,9 @@ let _rowTableCreated = false;
26
26
  function ensureRowIdsTable(db) {
27
27
  if (_rowTableCreated) return;
28
28
  db.exec(ROW_IDS_TABLE_SQL);
29
+ // Migrate: add thread tracking columns (no-op if already present)
30
+ try { db.exec('ALTER TABLE slack_row_ids ADD COLUMN channel_id TEXT'); } catch {}
31
+ try { db.exec('ALTER TABLE slack_row_ids ADD COLUMN thread_ts TEXT'); } catch {}
29
32
  _rowTableCreated = true;
30
33
  }
31
34
 
@@ -45,6 +48,19 @@ function setSlackRowId(db, taskId, rowId) {
45
48
  `).run(taskId, rowId);
46
49
  }
47
50
 
51
+ export function getThreadRef(db, taskId) {
52
+ ensureRowIdsTable(db);
53
+ const row = db.prepare('SELECT channel_id, thread_ts FROM slack_row_ids WHERE task_id = ?').get(taskId);
54
+ if (!row?.channel_id || !row?.thread_ts) return null;
55
+ return { channelId: row.channel_id, threadTs: row.thread_ts };
56
+ }
57
+
58
+ export function setThreadRef(db, taskId, channelId, threadTs) {
59
+ ensureRowIdsTable(db);
60
+ db.prepare('UPDATE slack_row_ids SET channel_id = ?, thread_ts = ? WHERE task_id = ?')
61
+ .run(channelId, threadTs, taskId);
62
+ }
63
+
48
64
  // ─── Cell formatting (config-driven) ───────────────────────────────
49
65
 
50
66
  function formatCell(fieldName, value, columnId, taskRow) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web42/stask",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "SQLite-backed task lifecycle CLI with Slack sync for AI agent teams",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@ SQLite-backed task lifecycle management with Slack sync. Every operation goes th
10
10
  ## Core Rules
11
11
 
12
12
  1. **No task exists without a spec uploaded to Slack.** Every task must have a Spec value: `specs/<name>.md (F0XXXXXXXXX)`.
13
- 2. **SQLite is the single source of truth.** Never edit `tracker.db` directly — use `stask` commands only.
13
+ 2. **SQLite is the single source of truth.** Never edit `tracker.db` directly — use `npx @web42/stask` commands only.
14
14
  3. **Every parent task gets its own worktree.** The guard system creates it automatically on In-Progress.
15
15
  4. **PR merge = Done.** The Human merges on GitHub, the system auto-completes the task.
16
16
  5. **DB + Slack are transactional.** If Slack sync fails, the DB rolls back.
@@ -74,32 +74,66 @@ Guards run automatically before transitions. Checks run first (read-only); if al
74
74
 
75
75
  | Command | Purpose |
76
76
  |---------|---------|
77
- | `stask create --spec <path> --name "..." [--type Feature\|Bug\|Task]` | Create task (auto-uploads spec to Slack) |
78
- | `stask approve <task-id>` | Human approves spec (reassigns to Lead) |
79
- | `stask transition <task-id> <status>` | Transition status (guards enforce prerequisites) |
80
- | `stask subtask create --parent <id> --name "..." --assign <agent>` | Create subtask under parent |
81
- | `stask subtask done <subtask-id>` | Worker marks subtask Done (auto-cascades parent) |
82
- | `stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit QA verdict with report |
83
- | `stask assign <task-id> <name>` | Reassign a task |
84
- | `stask spec-update <task-id> --spec <path>` | Re-upload edited spec |
77
+ | `npx @web42/stask create --spec <path> --name "..." [--type Feature\|Bug\|Task]` | Create task (auto-uploads spec to Slack) |
78
+ | `npx @web42/stask approve <task-id>` | Human approves spec (reassigns to Lead) |
79
+ | `npx @web42/stask transition <task-id> <status>` | Transition status (guards enforce prerequisites) |
80
+ | `npx @web42/stask subtask create --parent <id> --name "..." --assign <agent>` | Create subtask under parent |
81
+ | `npx @web42/stask subtask done <subtask-id>` | Worker marks subtask Done (auto-cascades parent) |
82
+ | `npx @web42/stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit QA verdict with report |
83
+ | `npx @web42/stask assign <task-id> <name>` | Reassign a task |
84
+ | `npx @web42/stask spec-update <task-id> --spec <path>` | Re-upload edited spec |
85
85
 
86
86
  ### Read-only commands
87
87
 
88
88
  | Command | Purpose |
89
89
  |---------|---------|
90
- | `stask list [--status X] [--assignee Y] [--json]` | List tasks (filterable) |
91
- | `stask show <task-id> [--log]` | Show task details + subtasks + audit log |
92
- | `stask log [<task-id>] [--limit N]` | View audit log |
93
- | `stask heartbeat <agent-name>` | Returns pending work for an agent (JSON) |
94
- | `stask pr-status <task-id>` | Poll PR for comments/merge status |
95
- | `stask session claim\|release\|status <task-id>` | Manage session locks |
90
+ | `npx @web42/stask list [--status X] [--assignee Y] [--json]` | List tasks (filterable) |
91
+ | `npx @web42/stask show <task-id> [--log]` | Show task details + subtasks + audit log |
92
+ | `npx @web42/stask log [<task-id>] [--limit N]` | View audit log |
93
+ | `npx @web42/stask heartbeat <agent-name>` | Returns pending work for an agent (JSON) |
94
+ | `npx @web42/stask pr-status <task-id>` | Poll PR for comments/merge status |
95
+ | `npx @web42/stask session claim\|release\|status <task-id>` | Manage session locks |
96
96
 
97
97
  ### Sync commands
98
98
 
99
99
  | Command | Purpose |
100
100
  |---------|---------|
101
- | `stask sync` | Run one bidirectional sync cycle |
102
- | `stask sync-daemon start\|stop\|status` | Manage background sync daemon |
101
+ | `npx @web42/stask sync` | Run one bidirectional sync cycle |
102
+ | `npx @web42/stask sync-daemon start\|stop\|status` | Manage background sync daemon |
103
+
104
+ ## Thread Communication
105
+
106
+ Every task has a dedicated Slack thread linked to its list item. The thread reference (`channelId` + `threadTs`) is stored in the DB and included in `stask show` and `heartbeat` output.
107
+
108
+ **All agents MUST post updates to the task thread at every step.** Use the Slack API `chat.postMessage` with the thread's `channel` and `thread_ts` to post replies. The thread is the single place for all task communication.
109
+
110
+ ### What to post
111
+
112
+ - **Starting work** — "Starting work on T-XXX.2: Build login form"
113
+ - **Progress updates** — "Implemented the auth middleware, moving to the UI components"
114
+ - **Blockers or issues** — "Hit an issue: the API endpoint returns 500 on invalid tokens. Investigating."
115
+ - **Errors or failures** — "Tests failing on login redirect. Stack trace: ..."
116
+ - **Subtask completion** — "Subtask T-XXX.1 done. Pushed 3 commits to feature/xxx"
117
+ - **QA results** — "QA PASS: all 5 acceptance criteria verified. Screenshots attached."
118
+ - **Status transitions** — "Transitioning T-XXX to Testing"
119
+ - **Questions** — "Question for @yan: should the invite expire after 7 days or 30 days?"
120
+
121
+ ### How to post
122
+
123
+ The thread reference is in the heartbeat JSON as `thread.channelId` and `thread.threadTs`. Post using the Slack API:
124
+
125
+ ```
126
+ POST https://slack.com/api/chat.postMessage
127
+ {
128
+ "channel": "<thread.channelId>",
129
+ "thread_ts": "<thread.threadTs>",
130
+ "text": "<your update>"
131
+ }
132
+ ```
133
+
134
+ Use the `SLACK_TOKEN` from the environment for authorization.
135
+
136
+ **Post even when things go wrong.** Failed builds, test errors, unexpected behavior — all of it goes in the thread. Silence is worse than bad news.
103
137
 
104
138
  ## Rules for All Agents
105
139
 
@@ -109,5 +143,6 @@ Guards run automatically before transitions. Checks run first (read-only); if al
109
143
  4. **Commit and push before marking done.** Guards will block Testing if you don't.
110
144
  5. **PR merge = Done.** Never manually transition to Done.
111
145
  6. **All PR comments go through the Human.** External comments need explicit triage.
112
- 7. **Workers mark their own subtasks Done** via `stask subtask done <id>`.
146
+ 7. **Workers mark their own subtasks Done** via `npx @web42/stask subtask done <id>`.
113
147
  8. **Reference specs by Slack file ID** (e.g., `F0XXXXXXXXX`), never by local path.
148
+ 9. **Post every step to the task thread.** Every action, result, blocker, and question goes in the thread. No exceptions.
@@ -22,38 +22,38 @@ You are the **Lead**. You own each task from spec approval through PR merge. You
22
22
 
23
23
  | Command | When |
24
24
  |---------|------|
25
- | `stask heartbeat <your-name>` | Check what work you have pending |
26
- | `stask show <task-id>` | View task details, subtasks, and status |
27
- | `stask subtask create --parent <id> --name "..." --assign <worker>` | Break work into subtasks |
28
- | `stask transition <task-id> In-Progress` | Start work (auto-creates worktree) |
29
- | `stask transition <task-id> "Ready for Human Review"` | After QA pass + PR created |
30
- | `stask transition <task-id> Done` | After Human merges the PR |
31
- | `stask pr-status <task-id>` | Check PR comments and merge status |
32
- | `stask assign <task-id> <name>` | Reassign a task |
25
+ | `npx @web42/stask heartbeat <your-name>` | Check what work you have pending |
26
+ | `npx @web42/stask show <task-id>` | View task details, subtasks, and status |
27
+ | `npx @web42/stask subtask create --parent <id> --name "..." --assign <worker>` | Break work into subtasks |
28
+ | `npx @web42/stask transition <task-id> In-Progress` | Start work (auto-creates worktree) |
29
+ | `npx @web42/stask transition <task-id> "Ready for Human Review"` | After QA pass + PR created |
30
+ | `npx @web42/stask transition <task-id> Done` | After Human merges the PR |
31
+ | `npx @web42/stask pr-status <task-id>` | Check PR comments and merge status |
32
+ | `npx @web42/stask assign <task-id> <name>` | Reassign a task |
33
33
 
34
34
  ## When You Receive Work
35
35
 
36
36
  ### Spec Approved (To-Do, assigned to you)
37
- 1. Read the spec (use the Slack file ID from `stask show`)
38
- 2. Create subtasks: `stask subtask create --parent T-XXX --name "..." --assign <worker>`
39
- 3. Transition: `stask transition T-XXX In-Progress`
37
+ 1. Read the spec (use the Slack file ID from `npx @web42/stask show`)
38
+ 2. Create subtasks: `npx @web42/stask subtask create --parent T-XXX --name "..." --assign <worker>`
39
+ 3. Transition: `npx @web42/stask transition T-XXX In-Progress`
40
40
 
41
41
  ### QA Passed (Testing, reassigned to you)
42
42
  1. Read the spec, QA report, git log, and diff
43
43
  2. Create a draft PR: `gh pr create --draft` in the worktree
44
44
  3. Write a rich PR description (summary, changes, QA results, screenshots)
45
- 4. Transition: `stask transition T-XXX "Ready for Human Review"`
45
+ 4. Transition: `npx @web42/stask transition T-XXX "Ready for Human Review"`
46
46
 
47
47
  ### QA Failed (In-Progress, reassigned to you)
48
48
  1. Review the QA report — identify what failed
49
- 2. Create NEW fix subtasks: `stask subtask create --parent T-XXX --name "Fix: ..." --assign <worker>`
49
+ 2. Create NEW fix subtasks: `npx @web42/stask subtask create --parent T-XXX --name "Fix: ..." --assign <worker>`
50
50
  3. Workers fix in the same worktree (same branch, same PR)
51
51
  4. When fix subtasks are Done, auto-transitions back to Testing
52
52
 
53
53
  ### PR Feedback (Ready for Human Review, detected by heartbeat)
54
54
  1. Read the feedback and judge:
55
55
  - **Code change needed** (bug, wrong behavior, missing feature):
56
- - `stask transition T-XXX In-Progress`
56
+ - `npx @web42/stask transition T-XXX In-Progress`
57
57
  - Create fix subtasks, delegate to Workers
58
58
  - After fixes: QA re-tests, you update PR, transition back to RHR
59
59
  - **Cosmetic fix** (PR description, naming):
@@ -61,7 +61,22 @@ You are the **Lead**. You own each task from spec approval through PR merge. You
61
61
  2. The PR stays open. The branch stays the same. All prior data is preserved.
62
62
 
63
63
  ### PR Merged (detected by heartbeat)
64
- - Run `stask transition T-XXX Done`
64
+ - Run `npx @web42/stask transition T-XXX Done`
65
+
66
+ ## Thread Communication
67
+
68
+ **Post to the task thread at every step.** The thread reference is in your heartbeat output (`thread.channelId` + `thread.threadTs`). Use `chat.postMessage` with `thread_ts` to reply.
69
+
70
+ You must post when you:
71
+ - Receive a task and start planning subtasks
72
+ - Create each subtask (who it's assigned to, what it does)
73
+ - Transition the task to any new status
74
+ - Review a QA failure and plan fixes
75
+ - Create the PR (include the PR link)
76
+ - Address PR feedback
77
+ - Encounter any blocker or unexpected issue
78
+
79
+ Example: "Creating 3 subtasks for T-006: (1) Build invite API → Gilfoyle, (2) Build invite UI → Dinesh, (3) Add email notifications → Gilfoyle. Transitioning to In-Progress."
65
80
 
66
81
  ## Key Rules
67
82
 
@@ -70,3 +85,4 @@ You are the **Lead**. You own each task from spec approval through PR merge. You
70
85
  - **The PR is your responsibility.** Write a description that helps the Human review quickly.
71
86
  - **External PR comments** (not from Human): Send Human a Slack DM. Do NOT act on them.
72
87
  - **Old Done subtasks stay Done** when cycling back to In-Progress. Only create NEW fix subtasks.
88
+ - **Post every action to the task thread.** The thread is how the team stays informed.
@@ -20,9 +20,9 @@ You are **QA**. You test completed work against the spec's acceptance criteria.
20
20
 
21
21
  | Command | When |
22
22
  |---------|------|
23
- | `stask heartbeat <your-name>` | Check what tasks need testing |
24
- | `stask show <task-id>` | View task details and spec |
25
- | `stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit your verdict |
23
+ | `npx @web42/stask heartbeat <your-name>` | Check what tasks need testing |
24
+ | `npx @web42/stask show <task-id>` | View task details and spec |
25
+ | `npx @web42/stask qa <task-id> --report <path> --verdict PASS\|FAIL` | Submit your verdict |
26
26
 
27
27
  ## When You Receive Work
28
28
 
@@ -40,7 +40,7 @@ The heartbeat will tell you:
40
40
  4. Test each AC systematically
41
41
  5. Take screenshots as evidence for each AC
42
42
  6. Write your QA report (see format below)
43
- 7. Submit: `stask qa T-XXX --report <path> --verdict PASS` or `FAIL`
43
+ 7. Submit: `npx @web42/stask qa T-XXX --report <path> --verdict PASS` or `FAIL`
44
44
 
45
45
  ## QA Report Format
46
46
 
@@ -75,6 +75,24 @@ The heartbeat will tell you:
75
75
  [Any bugs found, edge cases, suggestions]
76
76
  ```
77
77
 
78
+ ## Thread Communication
79
+
80
+ **Post to the task thread at every step.** The thread reference is in your heartbeat output (`thread.channelId` + `thread.threadTs`). Use `chat.postMessage` with `thread_ts` to reply.
81
+
82
+ You must post when you:
83
+ - Start testing a task
84
+ - Begin testing each acceptance criterion
85
+ - Find a passing AC — "AC 1 (login redirect): PASS"
86
+ - Find a failing AC — "AC 3 (error message): FAIL — shows generic 500 instead of 'Invalid email'"
87
+ - Encounter test environment issues
88
+ - Submit your final verdict
89
+
90
+ Example: "Starting QA for T-005. Reading spec and setting up test environment."
91
+ Example: "AC 1 PASS: Health endpoint returns 200 with version field. AC 2 PASS: Response time < 100ms. AC 3 FAIL: Missing content-type header."
92
+ Example: "QA FAIL submitted for T-005. 1 of 3 ACs failed. See report for details."
93
+
94
+ **Post even when things go wrong.** Environment issues, build failures, unclear spec — post it all to the thread.
95
+
78
96
  ## Key Rules
79
97
 
80
98
  - **Test every acceptance criterion.** Don't skip any, even obvious ones.
@@ -82,6 +100,7 @@ The heartbeat will tell you:
82
100
  - **Be specific in failure reports.** The Lead needs to know exactly what failed and how to reproduce it.
83
101
  - **Don't fix bugs yourself.** Report them. The Lead will delegate fixes to Workers.
84
102
  - **3 failures = escalation.** After 3 consecutive FAIL verdicts, the task gets Blocked and escalated to the Human.
103
+ - **Post every step to the task thread.** Every AC result, every issue, every observation.
85
104
 
86
105
  ## QA Retry Slots
87
106
 
@@ -20,11 +20,11 @@ You are a **Worker**. You implement subtasks assigned to you by the Lead. You wr
20
20
 
21
21
  | Command | When |
22
22
  |---------|------|
23
- | `stask heartbeat <your-name>` | Check what subtasks are assigned to you |
24
- | `stask show <task-id>` | View task/subtask details and spec |
25
- | `stask subtask done <subtask-id>` | Mark your subtask as Done |
26
- | `stask session claim <task-id>` | Claim a session lock (prevents conflicts) |
27
- | `stask session release <task-id>` | Release your session lock |
23
+ | `npx @web42/stask heartbeat <your-name>` | Check what subtasks are assigned to you |
24
+ | `npx @web42/stask show <task-id>` | View task/subtask details and spec |
25
+ | `npx @web42/stask subtask done <subtask-id>` | Mark your subtask as Done |
26
+ | `npx @web42/stask session claim <task-id>` | Claim a session lock (prevents conflicts) |
27
+ | `npx @web42/stask session release <task-id>` | Release your session lock |
28
28
 
29
29
  ## When You Receive Work
30
30
 
@@ -42,7 +42,25 @@ The heartbeat will tell you:
42
42
  4. Test your changes locally
43
43
  5. `git add` + `git commit` with a clear message
44
44
  6. `git push` to the remote branch
45
- 7. `stask subtask done <your-subtask-id>`
45
+ 7. `npx @web42/stask subtask done <your-subtask-id>`
46
+
47
+ ## Thread Communication
48
+
49
+ **Post to the task thread at every step.** The thread reference is in your heartbeat output (`thread.channelId` + `thread.threadTs`). Use `chat.postMessage` with `thread_ts` to reply.
50
+
51
+ You must post when you:
52
+ - Start working on your subtask
53
+ - Make meaningful progress (e.g., "Implemented the API route, moving to tests")
54
+ - Hit any error, blocker, or unexpected behavior
55
+ - Have a question for the Lead or Human
56
+ - Finish and push your code
57
+ - Mark your subtask as Done
58
+
59
+ Example: "Starting T-005.1: Build CLI flags. Reading the spec now."
60
+ Example: "Hit an issue — the config loader doesn't support nested objects. Working around it by flattening."
61
+ Example: "T-005.1 done. Pushed 2 commits: added --serve flag and OpenClawAgentExecutor."
62
+
63
+ **Post even when things go wrong.** If tests fail, if something breaks, if you're confused — post it. Silence is the worst outcome.
46
64
 
47
65
  ## Key Rules
48
66
 
@@ -50,8 +68,9 @@ The heartbeat will tell you:
50
68
  - **Commit AND push before marking done.** The Testing guards check for both uncommitted changes and unpushed commits. If you don't push, the task will get stuck.
51
69
  - **One branch per task.** All Workers on the same parent task share one worktree and one branch. Coordinate with other Workers if needed.
52
70
  - **Don't transition the parent task.** The system auto-transitions to Testing when all subtasks are Done.
53
- - **Mark only YOUR subtask done.** Use `stask subtask done <your-id>`, not anyone else's.
54
- - **If you're blocked**, tell the Lead. Don't try to work around issues.
71
+ - **Mark only YOUR subtask done.** Use `npx @web42/stask subtask done <your-id>`, not anyone else's.
72
+ - **If you're blocked**, tell the Lead in the thread. Don't try to work around issues.
73
+ - **Post every step to the task thread.** No exceptions — every action, result, and question.
55
74
 
56
75
  ## Common Mistakes
57
76