@web42/stask 0.2.2 → 0.2.3

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.
@@ -4,7 +4,7 @@
4
4
  "projectRepoPath": ".",
5
5
  "worktreeBaseDir": "~/.stask/worktrees/your-project-name",
6
6
  "staleSessionMinutes": 30,
7
- "syncIntervalSeconds": 60,
7
+ "syncIntervalSeconds": 120,
8
8
  "maxQaRetries": 3,
9
9
 
10
10
  "human": {
@@ -80,7 +80,14 @@ function findStaskDir(startDir) {
80
80
  while (dir !== root) {
81
81
  const candidate = path.join(dir, '.stask', 'config.json');
82
82
  if (fs.existsSync(candidate)) {
83
- return path.join(dir, '.stask');
83
+ try {
84
+ const config = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
85
+ // Only accept project configs (must have 'project' field).
86
+ // Skips the central ~/.stask/config.json which only has slackToken.
87
+ if (config.project) {
88
+ return path.join(dir, '.stask');
89
+ }
90
+ } catch {}
84
91
  }
85
92
  dir = path.dirname(dir);
86
93
  }
package/lib/slack-api.mjs CHANGED
@@ -31,9 +31,11 @@ function log(level, msg) {
31
31
  }
32
32
 
33
33
  /**
34
- * JSON POST to Slack API.
34
+ * JSON POST to Slack API with automatic retry on rate limits.
35
+ * Respects Retry-After header, retries up to 3 times with exponential backoff.
35
36
  */
36
- export async function slackApiRequest(method, endpoint, data) {
37
+ export async function slackApiRequest(method, endpoint, data, _retryCount = 0) {
38
+ const MAX_RETRIES = 3;
37
39
  return new Promise((resolve, reject) => {
38
40
  const reqData = JSON.stringify(data);
39
41
  const req = https.request(new URL(`https://slack.com/api${endpoint}`), {
@@ -50,6 +52,17 @@ export async function slackApiRequest(method, endpoint, data) {
50
52
  try {
51
53
  const json = JSON.parse(body);
52
54
  if (!json.ok) {
55
+ // Rate limited — retry with backoff
56
+ if (json.error === 'ratelimited' && _retryCount < MAX_RETRIES) {
57
+ const retryAfter = parseInt(res.headers['retry-after'] || '5', 10);
58
+ const delay = Math.max(retryAfter, 2 ** _retryCount) * 1000;
59
+ logger.warn(`Rate limited on ${endpoint}, retrying in ${delay / 1000}s (attempt ${_retryCount + 1}/${MAX_RETRIES})`);
60
+ setTimeout(() => {
61
+ slackApiRequest(method, endpoint, data, _retryCount + 1)
62
+ .then(resolve).catch(reject);
63
+ }, delay);
64
+ return;
65
+ }
53
66
  reject(new Error(`Slack API error: ${json.error} (${json.detail || json.response_metadata?.messages?.join('; ') || 'no detail'})`));
54
67
  } else {
55
68
  resolve(json);
@@ -13,26 +13,10 @@ import fs from 'fs';
13
13
  import path from 'path';
14
14
  import { fileURLToPath } from 'url';
15
15
 
16
- import os from 'os';
17
16
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
- const STASK_HOME = process.env.STASK_HOME || path.join(os.homedir(), '.stask');
19
-
20
- // Load env before anything else (from STASK_HOME)
21
- const envPath = path.join(STASK_HOME, '.env');
22
- try {
23
- const lines = fs.readFileSync(envPath, 'utf-8').split('\n');
24
- for (const line of lines) {
25
- const match = line.match(/^([^#=]+)=(.*)$/);
26
- if (match) {
27
- const key = match[1].trim();
28
- const val = match[2].trim();
29
- if (!process.env[key]) process.env[key] = val;
30
- }
31
- }
32
- } catch (_) {}
33
17
 
34
- // Now import modules that need env
35
- import { loadEnv, CONFIG } from './env.mjs';
18
+ // Import env (uses resolve-home.mjs for project root resolution)
19
+ import { loadEnv, CONFIG, STASK_HOME } from './env.mjs';
36
20
  loadEnv();
37
21
 
38
22
  import { runSyncCycle } from './slack-sync.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web42/stask",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "SQLite-backed task lifecycle CLI with Slack sync for AI agent teams",
5
5
  "type": "module",
6
6
  "bin": {