claude-task-worker 0.79.0 → 0.81.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 (2) hide show
  1. package/dist/index.js +206 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1892,7 +1892,7 @@ function buildClaudeEnv(mode) {
1892
1892
  }
1893
1893
 
1894
1894
  // src/config.ts
1895
- import { readFileSync as readFileSync2 } from "node:fs";
1895
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1896
1896
  import { isAbsolute as isAbsolute2, join as join2, normalize, sep as SEP } from "node:path";
1897
1897
  var DEFAULT_UI_DESIGN_CONFIG = {
1898
1898
  enabled: false,
@@ -2016,11 +2016,44 @@ var WORKER_DEFAULTS = {
2016
2016
  pollingIntervalSeconds: 300,
2017
2017
  cooldownSeconds: 0,
2018
2018
  maxConcurrentTasks: 1
2019
+ },
2020
+ // 以下3つは定期ワーカー(createScheduledWorker)。実行間隔そのものは
2021
+ // SCHEDULE_INTERVAL_HOURS(24時間)と実行ログで決まり、pollingIntervalSeconds は
2022
+ // 「24時間経過したかを確認する頻度」でしかない。
2023
+ // model が opus なのは、成果物(CODING_GUIDELINES.md / .claude/requirements/ / DESIGN.md)が
2024
+ // 後続の全 Issue・全デザインの前提として読まれ、誤った一般化がそのまま下流の手戻りになるため。
2025
+ "update-coding-guidelines": {
2026
+ skill: "/claude-task-worker:update-coding-guidelines",
2027
+ model: "opus",
2028
+ advisorModel: "",
2029
+ effort: "high",
2030
+ pollingIntervalSeconds: 3600,
2031
+ cooldownSeconds: 0,
2032
+ maxConcurrentTasks: 1
2033
+ },
2034
+ "update-requirement-rules": {
2035
+ skill: "/claude-task-worker:update-requirement-rules",
2036
+ model: "opus",
2037
+ advisorModel: "",
2038
+ effort: "high",
2039
+ pollingIntervalSeconds: 3600,
2040
+ cooldownSeconds: 0,
2041
+ maxConcurrentTasks: 1
2042
+ },
2043
+ "update-design-md": {
2044
+ skill: "/claude-task-worker:update-design-md",
2045
+ model: "opus",
2046
+ advisorModel: "",
2047
+ effort: "high",
2048
+ pollingIntervalSeconds: 3600,
2049
+ cooldownSeconds: 0,
2050
+ maxConcurrentTasks: 1
2019
2051
  }
2020
2052
  };
2021
2053
  var DEFAULT_CONFIG = {
2022
2054
  fixReviewPointCallbackCommentMessage: "",
2023
2055
  uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG },
2056
+ lastRun: {},
2024
2057
  workers: {}
2025
2058
  };
2026
2059
  var CONFIG_PATH = join2(process.cwd(), "claude-task-worker.json");
@@ -2135,6 +2168,21 @@ function parseUiDesignEntry(val) {
2135
2168
  }
2136
2169
  return result;
2137
2170
  }
2171
+ function parseLastRunEntry(val) {
2172
+ if (typeof val !== "object" || val === null || Array.isArray(val)) {
2173
+ console.warn(`[config] invalid lastRun: expected object, ignoring`);
2174
+ return {};
2175
+ }
2176
+ const result = {};
2177
+ for (const [name, at] of Object.entries(val)) {
2178
+ if (typeof at === "string" && !Number.isNaN(Date.parse(at))) {
2179
+ result[name] = at;
2180
+ } else {
2181
+ console.warn(`[config] invalid lastRun.${name}: ${String(at)}, ignoring`);
2182
+ }
2183
+ }
2184
+ return result;
2185
+ }
2138
2186
  function loadConfig() {
2139
2187
  const configPath = CONFIG_PATH;
2140
2188
  let raw;
@@ -2142,11 +2190,14 @@ function loadConfig() {
2142
2190
  raw = JSON.parse(readFileSync2(configPath, "utf-8"));
2143
2191
  } catch (err) {
2144
2192
  if (err.code === "ENOENT") {
2145
- return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
2193
+ return { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, lastRun: {}, workers: {} };
2146
2194
  }
2147
2195
  throw err;
2148
2196
  }
2149
- const result = { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, workers: {} };
2197
+ const result = { ...DEFAULT_CONFIG, uiDesign: { ...DEFAULT_UI_DESIGN_CONFIG }, lastRun: {}, workers: {} };
2198
+ if ("lastRun" in raw) {
2199
+ result.lastRun = parseLastRunEntry(raw["lastRun"]);
2200
+ }
2150
2201
  if ("fixReviewPointCallbackCommentMessage" in raw) {
2151
2202
  const val = raw["fixReviewPointCallbackCommentMessage"];
2152
2203
  if (typeof val === "string") {
@@ -2173,6 +2224,36 @@ function getWorkerConfig(workerName) {
2173
2224
  const config = loadConfig();
2174
2225
  return config.workers[workerName] ?? { ...defaultsFor(workerName) };
2175
2226
  }
2227
+ function getLastRunAt(workerName) {
2228
+ let at;
2229
+ try {
2230
+ at = loadConfig().lastRun[workerName];
2231
+ } catch (err) {
2232
+ console.warn(`[config] failed to load lastRun, treating ${workerName} as never run: ${err}`);
2233
+ return void 0;
2234
+ }
2235
+ if (at === void 0) return void 0;
2236
+ const parsed = Date.parse(at);
2237
+ return Number.isNaN(parsed) ? void 0 : parsed;
2238
+ }
2239
+ function writeLastRun(repoRoot, workerName, at = /* @__PURE__ */ new Date()) {
2240
+ const path2 = join2(repoRoot, "claude-task-worker.json");
2241
+ let raw = {};
2242
+ try {
2243
+ const parsed = JSON.parse(readFileSync2(path2, "utf-8"));
2244
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
2245
+ raw = parsed;
2246
+ }
2247
+ } catch (err) {
2248
+ if (err.code !== "ENOENT") {
2249
+ console.warn(`[config] failed to read ${path2}, rewriting it with lastRun only: ${err}`);
2250
+ }
2251
+ }
2252
+ const current = typeof raw["lastRun"] === "object" && raw["lastRun"] !== null ? raw["lastRun"] : {};
2253
+ raw["lastRun"] = { ...current, [workerName]: at.toISOString() };
2254
+ writeFileSync2(path2, `${JSON.stringify(raw, null, 2)}
2255
+ `, "utf-8");
2256
+ }
2176
2257
  function getUiDesignConfig() {
2177
2258
  try {
2178
2259
  return loadConfig().uiDesign;
@@ -2722,13 +2803,13 @@ function isGeneratedWorktreeName(name) {
2722
2803
 
2723
2804
  // src/slack.ts
2724
2805
  import { exec } from "node:child_process";
2725
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2806
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
2726
2807
  import { homedir as homedir4 } from "node:os";
2727
2808
  import { join as join5 } from "node:path";
2728
2809
  import { promisify as promisify2 } from "node:util";
2729
2810
 
2730
2811
  // src/runcat.ts
2731
- import { mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
2812
+ import { mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
2732
2813
  import { homedir as homedir3 } from "node:os";
2733
2814
  import { dirname, join as join4 } from "node:path";
2734
2815
  var RUNCAT_OUT_FILE = process.env.RUNCAT_OUT_FILE ?? join4(process.env.HOME ?? homedir3(), ".claude", "runcat-usage.json");
@@ -2798,7 +2879,7 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2798
2879
  const tmp = join4(dir, `.runcat-${process.pid}-${Date.now()}.json`);
2799
2880
  try {
2800
2881
  mkdirSync2(dir, { recursive: true });
2801
- writeFileSync2(tmp, JSON.stringify(snapshot), "utf-8");
2882
+ writeFileSync3(tmp, JSON.stringify(snapshot), "utf-8");
2802
2883
  renameSync2(tmp, outFile);
2803
2884
  } catch (err) {
2804
2885
  rmSync(tmp, { force: true });
@@ -2849,7 +2930,7 @@ function readUsageCache() {
2849
2930
  }
2850
2931
  function writeUsageCache(data) {
2851
2932
  try {
2852
- writeFileSync3(USAGE_CACHE_PATH, JSON.stringify({ timestamp: Date.now(), data }));
2933
+ writeFileSync4(USAGE_CACHE_PATH, JSON.stringify({ timestamp: Date.now(), data }));
2853
2934
  } catch {
2854
2935
  }
2855
2936
  }
@@ -3816,6 +3897,107 @@ var applyUiDesignWorker = async (opts = {}) => {
3816
3897
  })();
3817
3898
  };
3818
3899
 
3900
+ // src/workers/scheduled-worker.ts
3901
+ var SCHEDULE_INTERVAL_HOURS = 24;
3902
+ var SCHEDULE_INTERVAL_MS = SCHEDULE_INTERVAL_HOURS * 60 * 60 * 1e3;
3903
+ var SCOPE_DAYS = SCHEDULE_INTERVAL_HOURS / 24;
3904
+ function createScheduledWorker(config) {
3905
+ return async () => {
3906
+ const { owner, name: repoName, defaultBranch } = await getRepoInfo();
3907
+ const { pollingIntervalSeconds } = getWorkerConfig(config.name);
3908
+ console.log(
3909
+ `[${config.name}] Checking every ${pollingIntervalSeconds} seconds (runs at most once per ${SCHEDULE_INTERVAL_HOURS}h for ${owner}/${repoName})`
3910
+ );
3911
+ let startedAt = 0;
3912
+ const tick = async () => {
3913
+ if (isShuttingDown()) return;
3914
+ if (config.enabled && !config.enabled()) return;
3915
+ if (isRunning(config.taskId)) return;
3916
+ const now = Date.now();
3917
+ const lastRunAt = Math.max(getLastRunAt(config.name) ?? 0, startedAt);
3918
+ if (lastRunAt > 0 && now - lastRunAt < SCHEDULE_INTERVAL_MS) return;
3919
+ const worktreeId = generateWorktreeName();
3920
+ startedAt = now;
3921
+ try {
3922
+ syncDefaultBranch(defaultBranch);
3923
+ const { model, effort, skill, advisorModel } = getWorkerConfig(config.name);
3924
+ const command = skill || config.command;
3925
+ const mode = getRunMode();
3926
+ const execution = buildClaudeExecution({
3927
+ mode,
3928
+ prompt: `${command} ${SCOPE_DAYS}`,
3929
+ model,
3930
+ effort,
3931
+ advisorModel: isAdvisorEnabled() ? advisorModel : "",
3932
+ permissionMode: getPermissionMode()
3933
+ });
3934
+ await createWorktreeFromBranch(worktreeId, defaultBranch);
3935
+ const cwd = getWorktreePath(worktreeId);
3936
+ writeLastRun(cwd, config.name, new Date(now));
3937
+ console.log(`[${config.name}] created worktree ${worktreeId} from ${defaultBranch}`);
3938
+ const repoUrl = `https://github.com/${owner}/${repoName}`;
3939
+ run(
3940
+ execution.command,
3941
+ execution.args,
3942
+ config.taskId,
3943
+ config.name,
3944
+ config.name,
3945
+ worktreeId,
3946
+ async (status, output) => {
3947
+ try {
3948
+ if (status === "completed") {
3949
+ await notifyTaskCompleted(config.name, repoName, config.taskId, config.name, repoUrl, output);
3950
+ } else {
3951
+ await notifyTaskFailed(config.name, repoName, config.taskId, config.name, repoUrl, output);
3952
+ }
3953
+ } catch (err) {
3954
+ console.error(`[${config.name}] post-task error: ${err}`);
3955
+ } finally {
3956
+ await removeWorktree(worktreeId).catch(
3957
+ (err) => console.error(`[${config.name}] removeWorktree failed: ${err}`)
3958
+ );
3959
+ }
3960
+ },
3961
+ cwd,
3962
+ buildClaudeEnv(mode)
3963
+ );
3964
+ } catch (err) {
3965
+ console.error(`[${config.name}] setup error: ${err}`);
3966
+ await removeWorktree(worktreeId).catch(() => {
3967
+ });
3968
+ await notifyError(config.name, repoName, err);
3969
+ }
3970
+ };
3971
+ await tick();
3972
+ setInterval(tick, pollingIntervalSeconds * 1e3);
3973
+ };
3974
+ }
3975
+
3976
+ // src/workers/update-coding-guidelines.ts
3977
+ var updateCodingGuidelinesWorker = createScheduledWorker({
3978
+ name: "update-coding-guidelines",
3979
+ command: "/claude-task-worker:update-coding-guidelines",
3980
+ taskId: -1
3981
+ });
3982
+
3983
+ // src/workers/update-requirement-rules.ts
3984
+ var updateRequirementRulesWorker = createScheduledWorker({
3985
+ name: "update-requirement-rules",
3986
+ command: "/claude-task-worker:update-requirement-rules",
3987
+ taskId: -2
3988
+ });
3989
+
3990
+ // src/workers/update-design-md.ts
3991
+ var updateDesignMdWorker = createScheduledWorker({
3992
+ name: "update-design-md",
3993
+ command: "/claude-task-worker:update-design-md",
3994
+ taskId: -3,
3995
+ // DESIGN.md の材料は `cc-ui-design` ラベル付きのマージ済みデザインPRで、それを作るのは
3996
+ // uiDesign.enabled のデザイン先行フローだけ。無効なリポジトリでは収集対象が原理的に
3997
+ // 存在しないため、空振りのセッションを毎日起動しないようここで止める。
3998
+ enabled: () => getUiDesignConfig().enabled
3999
+ });
4000
+
3819
4001
  // src/index.ts
3820
4002
  init_table();
3821
4003
 
@@ -4223,7 +4405,10 @@ var WORKERS = {
4223
4405
  "check-dependabot": checkDependabotWorker,
4224
4406
  "epic-issue": epicIssueWorker,
4225
4407
  "create-ui-design": createUiDesignWorker,
4226
- "apply-ui-design": applyUiDesignWorker
4408
+ "apply-ui-design": applyUiDesignWorker,
4409
+ "update-coding-guidelines": updateCodingGuidelinesWorker,
4410
+ "update-requirement-rules": updateRequirementRulesWorker,
4411
+ "update-design-md": updateDesignMdWorker
4227
4412
  };
4228
4413
  function printUsage() {
4229
4414
  console.log(`Usage: claude-task-worker <command> [--project <name>] [--epic <issue-number>] [--label <label-name>]
@@ -4248,6 +4433,9 @@ Workers:
4248
4433
  epic-issue Poll cc-epic-issue issues and create epic PR when all sub-issues are closed
4249
4434
  create-ui-design Poll cc-create-ui-design issues and create a Pencil design PR (requires uiDesign.enabled)
4250
4435
  apply-ui-design Poll cc-ui-design-pr-created issues and write the design reference back once the design PR is merged (requires uiDesign.enabled)
4436
+ update-coding-guidelines Run /update-coding-guidelines once every 24 hours over the last 24 hours
4437
+ update-requirement-rules Run /update-requirement-rules once every 24 hours over the last 24 hours
4438
+ update-design-md Run /update-design-md once every 24 hours over the last 24 hours (requires uiDesign.enabled)
4251
4439
  all Poll all workers except triage-created-issue, triage-pr, check-dependabot
4252
4440
  yolo Poll all workers including triage-created-issue, triage-pr, check-dependabot
4253
4441
 
@@ -4433,7 +4621,12 @@ if (hasProjectFilter()) {
4433
4621
  resolveConflictWorker(),
4434
4622
  epicIssueWorker({ epicFilters, labelFilters }),
4435
4623
  createUiDesignWorker({ epicFilters, labelFilters }),
4436
- applyUiDesignWorker({ epicFilters, labelFilters })
4624
+ applyUiDesignWorker({ epicFilters, labelFilters }),
4625
+ // 24時間おきの定期ワーカー。update-design-md は uiDesign.enabled が false のとき
4626
+ // 自身で no-op になる(create-ui-design / apply-ui-design と同じ扱い)。
4627
+ updateCodingGuidelinesWorker(),
4628
+ updateRequirementRulesWorker(),
4629
+ updateDesignMdWorker()
4437
4630
  ]);
4438
4631
  })();
4439
4632
  } else if (workerType === "yolo") {
@@ -4454,7 +4647,10 @@ if (hasProjectFilter()) {
4454
4647
  resolveConflictWorker(),
4455
4648
  epicIssueWorker({ epicFilters, labelFilters }),
4456
4649
  createUiDesignWorker({ epicFilters, labelFilters }),
4457
- applyUiDesignWorker({ epicFilters, labelFilters })
4650
+ applyUiDesignWorker({ epicFilters, labelFilters }),
4651
+ updateCodingGuidelinesWorker(),
4652
+ updateRequirementRulesWorker(),
4653
+ updateDesignMdWorker()
4458
4654
  ]);
4459
4655
  })();
4460
4656
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.79.0",
3
+ "version": "0.81.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",