Files
tea-sdlc/scripts/timer.js
T
jiantw83andClaude Opus 5 dbce8dbd3c fix(claim): 被碼錶擋下時明說停錶不會動到既有的工作樹
議題 #38 的使用者故事第 30 條:使用者常以為停錶等於放棄那顆工作包,於是寧可
不停——工時就記到別顆議題去了。碼錶只管時間、工作樹只管檔案,兩者互不相干,
這件事要在擋下來的當下就講,不能指望使用者自己推論。

領取與起錶會撞到同一個擋路理由,訊息收進 lib 只寫一份。順手收掉 review 指出的
三處:planWorktree 沒用到的 repo 參數、與 path.resolve 同名而誤導的區域函式、
以及只有 lib 自己用得到卻對外 export 的兩支路徑函式。

回滾補上最後一道:git 清不掉時把目錄本身也刪掉。那條路徑在這次執行之前不存在
(不存在正是建立的前提),裡面不可能有使用者的東西,而留著它下一次重跑會直接
撞上 WORKTREE_PATH_TAKEN——一次失敗的建立不該讓人從此開不了工。

議題 #40

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 15:59:48 +08:00

72 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
/**
* 起錶。
*
* 錶與領取鎖是兩件事:鎖用 assignee 加標籤(見 claim.js),錶只管工時。分開的理由是
* 時機不同——鎖要在開工之前就上好,錶則要等到**工作樹真的建好之後**才起。工作樹建立
* 失敗會中止整個領取,錶要是先起了,使用者就被計了一段什麼都沒做的時間,
* 而工時要準正是工時報表的立足點。
*
* 自己的錶跑在別顆議題上時擋下,不代勞停錶:那一段時間該記在哪顆議題上只有人知道,
* 腳本自作主張會把工時記錯地方。錶已經跑在本議題上則什麼都不做——重新起錶會把已經
* 累積的時間切成兩段,而中斷後重跑正是這支腳本最常見的處境。
*
* 用法:
* node scripts/timer.js --repo owner/name --index 40 [--host <網址>] [--dry-run]
*/
import {
expectOk,
fetchIssue,
giteaRequest,
listStopwatches,
main,
parseFlags,
parseIndex,
parseRepo,
preflight,
resolveLogin,
stopwatchElsewhere,
stopwatchOnIssue,
} from './lib.js';
main(async () => {
const flags = parseFlags(process.argv.slice(2), {
required: ['repo', 'index'],
optional: ['host'],
booleans: ['dry-run'],
});
const repo = parseRepo(flags.repo);
const index = parseIndex(flags.index);
const issuePath = `/repos/${repo}/issues/${index}`;
const dryRun = flags['dry-run'] === true;
const login = resolveLogin({ host: flags.host });
// 試跑照樣讀現況:手寫一份固定的清單會跟實作走鐘,也說不出「這顆已經在計時了」
if (!dryRun) await preflight(login, repo);
const issue = await fetchIssue(login, repo, index);
const watches = await listStopwatches(login);
const 已在計時 = stopwatchOnIssue(watches, repo, index) !== null;
if (!已在計時 && watches.length > 0) throw stopwatchElsewhere(watches[0], '起錶');
// 已經在跑就不重起:重新起錶會把已經累積的時間切成兩段
const planned = 已在計時 ? [] : [{ method: 'POST', path: `${issuePath}/stopwatch/start`, body: {} }];
if (dryRun) {
return { dryRun: true, repo, index, title: issue.title, requests: planned, 已在計時 };
}
for (const { method, path, body } of planned) {
expectOk(await giteaRequest(login, method, path, { body }), `${method} ${path}`);
}
return {
repo,
index: issue.number,
title: issue.title,
url: issue.html_url,
碼錶中: true,
已在計時,
};
});