@kairyou/agent-tools 0.14.0 → 0.16.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.
package/README.md CHANGED
@@ -32,7 +32,7 @@ Usage:
32
32
 
33
33
  ### at-review
34
34
 
35
- Review changes for correctness bugs, regressions, convention violations, and high-value cleanup findings.
35
+ Review local changes or a hosted PR/MR for correctness bugs, regressions, convention violations, and high-value cleanup findings.
36
36
 
37
37
  ```bash
38
38
  npx -y skills@latest add kairyou/agent-tools --skill at-review -g -y
@@ -40,7 +40,7 @@ npx -y skills@latest add kairyou/agent-tools --skill at-review -g -y
40
40
 
41
41
  Usage:
42
42
 
43
- - `/at-review [--fix] [<pr|branch|path>]` — reports review findings; `--fix` also applies them
43
+ - `/at-review [--fix] [<pr-or-mr-url|branch|path>]` — reports review findings; private hosted targets require locally available read access, and `--fix` applies fixes only to a matching working tree
44
44
 
45
45
  ### at-simplify
46
46
 
@@ -69,7 +69,7 @@ Usage:
69
69
  - `/at-zentao bug <id>` — work a specific bug
70
70
  - `/at-zentao task <id>` — work a specific task
71
71
 
72
- Config: `~/.agent-tools/config.jsonc` → `"zentao": { "url", "account", "password" }`. First run guides you; fill `password` in the file yourself (or env `ZENTAO_PASSWORD`), never in chat.
72
+ Config: `~/.agent-tools/config.jsonc` → `"zentao": { "url", "account", "password" }`. The password can be entered directly or referenced with `"password": { "env": "ZENTAO_PASSWORD" }`; never paste credentials or tokens into chat.
73
73
 
74
74
  ## Integrations
75
75
 
package/README.zh-CN.md CHANGED
@@ -32,7 +32,7 @@ npx -y skills@latest add kairyou/agent-tools --skill at-commit -g -y
32
32
 
33
33
  ### at-review
34
34
 
35
- 审查改动中的正确性 bug, 回归风险, 约定违规和高价值清理项.
35
+ 审查本地改动或托管的 PR/MR 中的正确性 bug, 回归风险, 约定违规和高价值清理项.
36
36
 
37
37
  ```bash
38
38
  npx -y skills@latest add kairyou/agent-tools --skill at-review -g -y
@@ -40,7 +40,7 @@ npx -y skills@latest add kairyou/agent-tools --skill at-review -g -y
40
40
 
41
41
  用法:
42
42
 
43
- - `/at-review [--fix] [<pr|分支|路径>]` — 输出审查结果; `--fix` 则同时应用修复
43
+ - `/at-review [--fix] [<PR/MR-URL|分支|路径>]` — 输出审查结果; 私有托管目标需要本地已有只读访问权限, `--fix` 仅对匹配的工作树应用修复
44
44
 
45
45
  ### at-simplify
46
46
 
@@ -69,7 +69,7 @@ npx -y skills@latest add kairyou/agent-tools --skill at-zentao -g -y
69
69
  - `/at-zentao bug <id>` — 直接处理指定 bug
70
70
  - `/at-zentao task <id>` — 直接处理指定 task
71
71
 
72
- 配置: `~/.agent-tools/config.jsonc` → `"zentao": { "url", "account", "password" }`. 首次使用会引导; `password` 自己填进文件(或设环境变量 `ZENTAO_PASSWORD`), 不要发在对话里.
72
+ 配置: `~/.agent-tools/config.jsonc` → `"zentao": { "url", "account", "password" }`. 密码可以直接填写, 也可以使用 `"password": { "env": "ZENTAO_PASSWORD" }` 引用环境变量; 不要在对话中粘贴凭据或 token.
73
73
 
74
74
  ## Integrations
75
75
 
@@ -19,9 +19,9 @@
19
19
  // log capability: AI session work log; see the extras doc for details.
20
20
  "log": {
21
21
  "enabled": true, // false: pause recording without uninstalling
22
- "output": "~/.agent-tools/logs/ai-log.md", // daily: one file; detailed: a directory
22
+ "output": "~/.agent-tools/logs/ai-log", // detailed: one <date>.md file per day; daily: one file
23
23
  "language": "zh", // zh | en (detailed report headings)
24
- "format": "daily", // daily | detailed
24
+ "format": "detailed", // detailed | daily
25
25
  "projects": [ // record only these; empty: record everything
26
26
  // "C:\\projects\\project-a"
27
27
  ]
package/dist/log/hook.mjs CHANGED
@@ -868,7 +868,8 @@ var ParseErrorCode;
868
868
  // integrations/log/hook.mjs
869
869
  var MAX_SNAPSHOT_BYTES = 512 * 1024;
870
870
  var MIN_RESULT_SUMMARY_LENGTH = 24;
871
- var DAILY_ITEM_MAX_CHARS = 160;
871
+ var DAILY_ITEM_TARGET_CHARS = 160;
872
+ var DAILY_ITEM_HARD_MAX_CHARS = 320;
872
873
  var INSTALL_ROOT = process.env.AGENT_TOOLS_HOME || path.join(os.homedir(), ".agent-tools");
873
874
  var CACHE_ROOT = path.join(INSTALL_ROOT, "cache", "log");
874
875
  async function main() {
@@ -980,7 +981,8 @@ async function loadLogConfig() {
980
981
  }
981
982
  const section = isPlainObject(parsed.log) ? parsed.log : {};
982
983
  const enabled = section.enabled !== false;
983
- const format2 = pickFormat(section.format, "daily");
984
+ const inferredFormat = section.format === void 0 && typeof section.output === "string" && section.output.trim().toLowerCase().endsWith(".md") ? "daily" : "detailed";
985
+ const format2 = pickFormat(section.format, inferredFormat);
984
986
  const language = pickLanguage(section.language, "zh");
985
987
  const output = pickOutput(section.output, format2, defaultOutput(format2));
986
988
  const projects = [];
@@ -1239,11 +1241,23 @@ function buildDailyItems(state, scopeKey) {
1239
1241
  function dailyItemText(turn) {
1240
1242
  const request = String(turn.request_text || "");
1241
1243
  const outcome = String(turn.result_summary || "");
1242
- if (!hasSubstantiveTurn(request, outcome) || isTrivialTurn(request, outcome)) return "";
1243
- const source = outcome || request;
1244
+ if (!outcome || !hasSubstantiveTurn(request, outcome) || isTrivialTurn(request, outcome)) return "";
1245
+ const source = outcome;
1244
1246
  const flattened = source.split("\n").map((line) => line.replace(/^[#>*\-\s`|]+/, "").trim()).filter(Boolean).join(" ");
1245
1247
  if (!flattened) return "";
1246
- return flattened.length > DAILY_ITEM_MAX_CHARS ? `${flattened.slice(0, DAILY_ITEM_MAX_CHARS - 3)}...` : flattened;
1248
+ return truncateDailyItem(flattened);
1249
+ }
1250
+ function truncateDailyItem(text) {
1251
+ if (text.length <= DAILY_ITEM_HARD_MAX_CHARS) return text;
1252
+ const limit = DAILY_ITEM_HARD_MAX_CHARS - 3;
1253
+ const prefix = text.slice(0, limit);
1254
+ const boundaries = [...prefix.matchAll(/[。!?;;]|[.!?](?=\s|$)/g)];
1255
+ const boundary = boundaries.at(-1)?.index;
1256
+ if (boundary !== void 0 && boundary + 1 >= Math.floor(DAILY_ITEM_TARGET_CHARS * 0.6)) {
1257
+ return `${prefix.slice(0, boundary + 1)}...`;
1258
+ }
1259
+ const targetPrefix = text.slice(0, DAILY_ITEM_TARGET_CHARS - 3);
1260
+ return `${targetPrefix}...`;
1247
1261
  }
1248
1262
  async function updateDailyFile(outputFile, day, items) {
1249
1263
  if (items.length === 0) return;
package/docs/en/extras.md CHANGED
@@ -69,8 +69,8 @@ Independent of and complementary to `at-daily-log` above; use them together or a
69
69
  npx -y @kairyou/agent-tools@latest log -a claude codex opencode
70
70
  ```
71
71
 
72
- - `format: "daily"` (default): a single markdown file, one dated entry per day, each turn summarized into one line
73
- - `format: "detailed"`: one full report per day with each turn's request and outcome, the files changed, and approximate lines added/removed (measured against the current file when several turns touch one)
72
+ - `format: "detailed"` (default): one detailed report per day with each turn's request and outcome, the files changed, and estimated lines added and removed
73
+ - `format: "daily"`: writes to a single Markdown file grouped by date; each completed answer with substantive results is recorded as one line, while pending prompts are omitted; content may be truncated by the length limit, so use it only as a lightweight activity index
74
74
  - Codex: run `/hooks` once after installing to approve it; opencode: restart after installing or updating
75
75
 
76
76
  `daily` output example:
@@ -123,9 +123,9 @@ All in `~/.agent-tools/config.jsonc`:
123
123
  // log capability: AI session log
124
124
  "log": {
125
125
  "enabled": true, // false: pause recording without uninstalling
126
- "output": "C:\\logs\\ai-log.md", // daily: one file; detailed: a directory
126
+ "output": "C:\\logs\\ai-log", // detailed: one <date>.md per day; daily: one file
127
127
  "language": "zh", // zh | en
128
- "format": "daily", // daily | detailed
128
+ "format": "detailed", // detailed | daily
129
129
  "projects": [ // optional: record only these; entries may override the keys above
130
130
  "C:\\projects\\project-a",
131
131
  { "path": "C:\\projects\\project-b", "format": "detailed", "output": "C:\\logs\\project-b" }
@@ -11,10 +11,10 @@ agent-tools/
11
11
  ├── skills/ # Reusable Agent Skills.
12
12
  │ ├── workflow/ # Workflow-oriented skills.
13
13
  │ │ ├── at-commit/ # Conventional Commit message skill.
14
- │ │ ├── at-review/ # Review changes for bugs and regressions.
14
+ │ │ ├── at-review/ # Review local or hosted changes for bugs and regressions.
15
15
  │ │ └── at-simplify/ # Reduce complexity and duplication in changes.
16
16
  │ └── integrations/ # Skills that integrate external systems.
17
- │ └── at-zentao/ # ZenTao bug/task fixing workflow.
17
+ │ └── at-zentao/ # Self-contained ZenTao workflow and secure API script.
18
18
  ├── docs/ # Advanced guides and contributor reference.
19
19
  ├── tools/ # Maintainer-only upstream sync and repository tooling.
20
20
  └── scripts/ # Install, sync, validation, and maintenance scripts.
@@ -67,8 +67,8 @@ npx -y skills@latest add kairyou/agent-tools --skill at-daily-log -g -y
67
67
  npx -y @kairyou/agent-tools@latest log -a claude codex opencode
68
68
  ```
69
69
 
70
- - `format: "daily"` (默认): 单一 md 文件, 每天一个日期条目, 每轮对话总结成一行
71
- - `format: "detailed"`: 每天一份详细报告, 含每轮的请求与结果, 改动的文件和近似的增删行数(多轮改同一文件时按当前文件计算)
70
+ - `format: "detailed"` (默认): 每天生成一份详细报告, 记录每轮请求与结果, 修改的文件, 以及新增/删除代码行数的估算
71
+ - `format: "daily"`: 写入单个 Markdown 文件, 并按日期归档; 每个已完成且有实质结果的回答记录为一行, 不记录未完成的提问; 内容可能因长度限制被截断, 因此仅适合作为轻量活动索引
72
72
  - Codex 安装后运行 `/hooks` 批准一次; opencode 安装或更新后需要重启
73
73
 
74
74
  `daily` 输出示例:
@@ -120,9 +120,9 @@ Changes
120
120
  // log capability: AI 会话日志
121
121
  "log": {
122
122
  "enabled": true, // false: 临时停止记录, 不用卸载
123
- "output": "C:\\logs\\ai-log.md", // daily: 单一文件; detailed: 目录
123
+ "output": "C:\\logs\\ai-log", // detailed: 每天一个 <date>.md; daily: 单一文件
124
124
  "language": "zh", // zh | en
125
- "format": "daily", // daily | detailed
125
+ "format": "detailed", // detailed | daily
126
126
  "projects": [ // 可选: 只记录这些目录, 条目可覆盖上面的键
127
127
  "C:\\projects\\project-a",
128
128
  { "path": "C:\\projects\\project-b", "format": "detailed", "output": "C:\\logs\\project-b" }
@@ -11,10 +11,10 @@ agent-tools/
11
11
  ├── skills/ # 可复用的 Agent Skills.
12
12
  │ ├── workflow/ # 工作流类 skills.
13
13
  │ │ ├── at-commit/ # 生成 Conventional Commits message.
14
- │ │ ├── at-review/ # 审查改动中的 bug 与回归风险.
14
+ │ │ ├── at-review/ # 审查本地或托管改动中的 bug 与回归风险.
15
15
  │ │ └── at-simplify/ # 减少改动中的冗余和复杂度.
16
16
  │ └── integrations/ # 对接外部系统的 skills.
17
- │ └── at-zentao/ # 禅道 bug/task 修复工作流.
17
+ │ └── at-zentao/ # 自包含的禅道工作流和安全 API 脚本.
18
18
  ├── docs/ # 高级指南和贡献者参考.
19
19
  ├── tools/ # 仅供维护者使用的上游同步和仓库工具.
20
20
  └── scripts/ # 安装, 同步, 校验和仓库维护脚本.
@@ -26,7 +26,8 @@ import { parse as parseJsonc } from "jsonc-parser";
26
26
 
27
27
  const MAX_SNAPSHOT_BYTES = 512 * 1024;
28
28
  const MIN_RESULT_SUMMARY_LENGTH = 24;
29
- const DAILY_ITEM_MAX_CHARS = 160;
29
+ const DAILY_ITEM_TARGET_CHARS = 160;
30
+ const DAILY_ITEM_HARD_MAX_CHARS = 320;
30
31
 
31
32
  const INSTALL_ROOT = process.env.AGENT_TOOLS_HOME || path.join(os.homedir(), ".agent-tools");
32
33
  const CACHE_ROOT = path.join(INSTALL_ROOT, "cache", "log");
@@ -167,7 +168,15 @@ async function loadLogConfig() {
167
168
  }
168
169
  const section = isPlainObject(parsed.log) ? parsed.log : {};
169
170
  const enabled = section.enabled !== false;
170
- const format = pickFormat(section.format, "daily");
171
+ // Keep existing file-based configurations on daily while new installs use
172
+ // detailed by default. Explicit format always wins.
173
+ const inferredFormat =
174
+ section.format === undefined &&
175
+ typeof section.output === "string" &&
176
+ section.output.trim().toLowerCase().endsWith(".md")
177
+ ? "daily"
178
+ : "detailed";
179
+ const format = pickFormat(section.format, inferredFormat);
171
180
  const language = pickLanguage(section.language, "zh");
172
181
  const output = pickOutput(section.output, format, defaultOutput(format));
173
182
 
@@ -497,8 +506,11 @@ function buildDailyItems(state, scopeKey) {
497
506
  function dailyItemText(turn) {
498
507
  const request = String(turn.request_text || "");
499
508
  const outcome = String(turn.result_summary || "");
500
- if (!hasSubstantiveTurn(request, outcome) || isTrivialTurn(request, outcome)) return "";
501
- const source = outcome || request;
509
+ // Daily is an outcome index, not a prompt inbox. Keep request-only turns in
510
+ // detailed reports, but do not present an unfinished question as completed
511
+ // work in the compact daily file.
512
+ if (!outcome || !hasSubstantiveTurn(request, outcome) || isTrivialTurn(request, outcome)) return "";
513
+ const source = outcome;
502
514
  // Flattened rather than first-line: a structured summary often opens with a
503
515
  // preamble line, and the substance sits in the lines after it.
504
516
  const flattened = source
@@ -507,9 +519,23 @@ function dailyItemText(turn) {
507
519
  .filter(Boolean)
508
520
  .join(" ");
509
521
  if (!flattened) return "";
510
- return flattened.length > DAILY_ITEM_MAX_CHARS
511
- ? `${flattened.slice(0, DAILY_ITEM_MAX_CHARS - 3)}...`
512
- : flattened;
522
+ return truncateDailyItem(flattened);
523
+ }
524
+
525
+ function truncateDailyItem(text) {
526
+ if (text.length <= DAILY_ITEM_HARD_MAX_CHARS) return text;
527
+ const limit = DAILY_ITEM_HARD_MAX_CHARS - 3;
528
+ const prefix = text.slice(0, limit);
529
+ // Prefer the longest complete sentence within the hard cap. The target is a
530
+ // soft guide: retaining more complete context is better when the next
531
+ // sentence ends before the hard limit.
532
+ const boundaries = [...prefix.matchAll(/[。!?;;]|[.!?](?=\s|$)/g)];
533
+ const boundary = boundaries.at(-1)?.index;
534
+ if (boundary !== undefined && boundary + 1 >= Math.floor(DAILY_ITEM_TARGET_CHARS * 0.6)) {
535
+ return `${prefix.slice(0, boundary + 1)}...`;
536
+ }
537
+ const targetPrefix = text.slice(0, DAILY_ITEM_TARGET_CHARS - 3);
538
+ return `${targetPrefix}...`;
513
539
  }
514
540
 
515
541
  async function updateDailyFile(outputFile, day, items) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kairyou/agent-tools",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Reusable Agent Skills, plus integrations (statusline, provider usage, vision) that install into Codex, Claude Code, and opencode.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -6,143 +6,179 @@ argument-hint: "bug <id> | task <id> | bugs | tasks | export bug|task <id>"
6
6
 
7
7
  # ZenTao Bug/Task Workflow
8
8
 
9
+ ## Secure CLI boundary
10
+
11
+ This Skill bundles `scripts/zentao-cli.mjs`. Resolve the Skill root as the
12
+ directory containing this `SKILL.md`, then run the script with Node using its
13
+ resolved path from any working directory:
14
+
15
+ ```text
16
+ node <skill-root>/scripts/zentao-cli.mjs <command>
17
+ ```
18
+
19
+ The script is the only component allowed to read ZenTao configuration,
20
+ exchange credentials for a token, send authenticated requests, or inspect raw
21
+ error responses. Run it directly without reading or copying its source into
22
+ the conversation. Never read `~/.agent-tools/config.jsonc`, print ZenTao env
23
+ vars, call the token endpoint, or construct a ZenTao `Token` header yourself.
24
+
25
+ The CLI emits only sanitized JSON. Treat a nonzero exit as a stopped ZenTao
26
+ operation and report its safe `error`, `message`, and optional HTTP `status`.
27
+ Do not work around the CLI with `curl` when authentication or an endpoint
28
+ fails.
29
+
9
30
  ## Configuration
10
31
 
11
- Primary config lives under a `zentao` key in `~/.agent-tools/config.jsonc` or `$AGENT_TOOLS_HOME/config.jsonc` when `AGENT_TOOLS_HOME` is set, matching the rest of agent-tools. Every `~/.agent-tools/config.jsonc` below means this resolved path:
32
+ The CLI reads the global `~/.agent-tools/config.jsonc`, or
33
+ `$AGENT_TOOLS_HOME/config.jsonc` when `AGENT_TOOLS_HOME` is set. It never reads
34
+ repository-level configuration. Basic configuration:
12
35
 
13
36
  ```jsonc
14
37
  {
15
38
  "zentao": {
16
- "url": "http://zentao.example.com",
17
- "account": "...",
18
- "password": "..."
39
+ "url": "https://zentao.example.com",
40
+ "account": "user",
41
+ "password": "your-password"
19
42
  }
20
43
  }
21
44
  ```
22
45
 
23
- Resolution order (first match wins):
24
-
25
- - URL: env `ZENTAO_URL` `zentao.url` in `~/.agent-tools/config.jsonc`.
26
- - Credentials: env `ZENTAO_ACCOUNT`/`ZENTAO_PASSWORD` `zentao.account`/`zentao.password` in `~/.agent-tools/config.jsonc`.
27
-
28
- ZenTao config comes ONLY from env vars and the global `~/.agent-tools/config.jsonc`, never a repository-level file — so an untrusted repo can't redirect the endpoint to capture your credentials.
46
+ To avoid storing the password in the file, use
47
+ `"password": { "env": "ZENTAO_PASSWORD" }` and set that environment variable.
48
+ Direct env overrides are `ZENTAO_URL`, `ZENTAO_ACCOUNT`, and
49
+ `ZENTAO_PASSWORD`. `ZENTAO_TOKEN` may be used instead of password exchange; it
50
+ is accepted only from the environment.
29
51
 
30
- **First-run setup** (when config is missing or incomplete):
52
+ When configuration is missing, tell the user which file to edit and show the
53
+ template above. Never ask them to paste a password or token into chat, and do
54
+ not edit or inspect the file after they add credentials. Ask them to say
55
+ "done", then validate with:
31
56
 
32
- 1. If `~/.agent-tools/config.jsonc` has no `zentao` block, insert one INTO the root object (not appended after the closing `}`) — it is JSONC, so preserve existing keys and comments, and mind the trailing comma:
33
- `"zentao": { "url": "", "account": "", "password": "" }`
34
- 2. Ask for the URL and account as a PLAIN chat question and wait for the reply — never collect free-form values via a multiple-choice prompt (its fixed option labels would be submitted as the answer). Write the reply into the config — these are not secrets.
35
- 3. NEVER ask the user to paste the password into the chat (it would persist in transcripts). Tell them — in the same message as step 2 — to fill `zentao.password` in the file themselves or set env `ZENTAO_PASSWORD`, and to say "done" when finished.
36
- 4. Then validate immediately: exchange a token and call `GET /api.php/v1/user`. Report the result before doing any real work.
37
-
38
- ## Authentication (once per session)
39
-
40
- ZenTao tokens expire, so exchange credentials for a fresh token at the start of each session:
41
-
42
- ```
43
- POST $ZENTAO_URL/api.php/v1/tokens
44
- Content-Type: application/json
45
- {"account":"...","password":"..."}
57
+ ```text
58
+ node <skill-root>/scripts/zentao-cli.mjs doctor
46
59
  ```
47
60
 
48
- The response's `token` field is used as a `Token: <token>` header on every subsequent API request. Keep it in memory for the session only; never write it to a file and never echo it in full. If any later call returns 401, exchange for a new token once and retry; if it still fails, stop and report.
61
+ Report only whether the connection succeeded and whether authentication used
62
+ `token` or `account-password`.
49
63
 
50
- If env `ZENTAO_TOKEN` is set, use it directly and skip the exchange (re-exchange is unavailable then — on 401, tell the user the token expired).
51
-
52
- **Connectivity check**: after obtaining the token, `GET $ZENTAO_URL/api.php/v1/user` must succeed before any other work. On failure, stop and tell the user to check URL/credentials.
53
-
54
- ## Usage
64
+ ## CLI commands
55
65
 
56
- - `/at-zentao bug <id>` — handle a single bug
57
- - `/at-zentao task <id>` — handle a single task
58
- - `/at-zentao bugs` — list bugs assigned to the configured account; the user picks one or several (multiple = batch mode)
59
- - `/at-zentao tasks` — same for tasks
60
- - `/at-zentao export bug <id>` (or `export task <id>`) — export to a self-contained Markdown bundle for handoff; does NOT fix or write back (see Export mode)
66
+ Read-only commands:
61
67
 
62
- ## API endpoints (verified on ZenTao open source 18.12)
63
-
64
- All requests send the `Token: <token>` header — it works for both endpoint families below.
65
-
66
- **My work lists** (legacy `.json` pages; the entry point for picking what to fix):
67
-
68
- - `GET /my-work-bug.json` — bugs assigned to the configured account
69
- - `GET /my-work-task.json` — tasks assigned to the configured account
70
- - Response shape: `{"status":"success","data":"<JSON-encoded string>"}` — the `data` field is a STRING containing JSON (with `\uXXXX` escapes), so decode it a second time. Bugs are in `.bugs[]` (fields: `id`, `title`, `severity`, `pri`, `status`, `project`, `product`), tasks in `.tasks[]`. The first page usually suffices, but read the pager info inside `data` for the total — if there are more pages, tell the user (e.g. "showing 20 of 45; say more to load the rest") instead of silently truncating, and fetch further pages only on request.
71
-
72
- **Details and write-back** (REST v1):
73
-
74
- - `GET /api.php/v1/bugs/{id}` — bug details (title, steps, severity, module)
75
- - `GET /api.php/v1/tasks/{id}` — task details
76
-
77
- **Attachments / inline images** (legacy, same Token header; binary — save with `curl -o`, never read as text):
78
-
79
- - `GET /file-read-{fileID}.{ext}` — view/inline. Observed on 18.12: bug screenshots are embedded in the `steps` HTML as `<img src=".../file-read-{id}.png">` while the `files` list is empty — so scan `steps`, don't rely on `files`.
80
- - `GET /file-download-{fileID}.html` — download an attachment (when `files` is populated).
81
-
82
- **Resolving a bug** (the REST `PUT /bugs/{id}` does NOT perform a real resolve — do not use it for status changes; use the legacy action, which mirrors the web form and triggers the full workflow):
83
-
84
- 1. `POST /bug-resolve-{id}.json` with a form body (`Content-Type: application/x-www-form-urlencoded`, same Token header):
85
- `resolution=fixed&resolvedBuild=trunk&responsibleBy=<account>&comment=<...>`
86
- where `responsibleBy` is the authenticated account (from config) — this instance requires it.
87
- Encoding: NEVER pass non-ASCII (Chinese) text as a command-line argument — Windows curl.exe converts argv through the ANSI codepage and mangles it regardless of terminal. Feed such text via stdin: `--data-urlencode "comment@-"` plus a herestring/pipe/heredoc. ASCII fields may go inline in `-d`; all data flags merge into one form body. Verified one-liner:
88
- `curl -s -X POST -H "Token: $TOKEN" -d "resolution=fixed&resolvedBuild=trunk&responsibleBy=<account>" --data-urlencode "comment@-" "$ZENTAO_URL/bug-resolve-{id}.json" <<< $'<comment line 1>\n<line 2>'`
89
- 2. To add a comment WITHOUT changing status: `POST /action-comment-bug-{id}.json`, comment fed via stdin the same way (`--data-urlencode "comment@-"`).
90
- 3. Check the DECODED response: legacy endpoints return HTTP 200 with `{"status":"success","data":"..."}` even on failure — the real outcome is inside `data` (`result: "fail"` + per-field `message`). Surface those validation messages to the user verbatim; if a required field is missing, discover the form's fields and defaults via `GET /bug-resolve-{id}.json`, fill it, and re-confirm with the user before retrying.
91
-
92
- **Finishing a task**:
68
+ ```text
69
+ node <skill-root>/scripts/zentao-cli.mjs list bugs
70
+ node <skill-root>/scripts/zentao-cli.mjs list tasks
71
+ node <skill-root>/scripts/zentao-cli.mjs get bug <id>
72
+ node <skill-root>/scripts/zentao-cli.mjs get task <id>
73
+ node <skill-root>/scripts/zentao-cli.mjs get bug <id> --download-dir <path>
74
+ ```
93
75
 
94
- 1. `GET /task-finish-{id}.json` the decoded `data.task` object holds current values (`realStarted`, `consumed`, `openedBy`, ...).
95
- 2. `POST /task-finish-{id}.json` (same Token header and stdin-encoding rules as bug resolve) with:
96
- - `currentConsumed` — hours spent; a value only the user knows. ALWAYS ask the user for it; never invent or estimate it on their behalf.
97
- - `consumed` — TOTAL consumed, must exceed the previous total: compute as `data.task.consumed + currentConsumed`.
98
- - `realStarted` — required; reuse `data.task.realStarted` if set, otherwise use a sensible date confirmed with the user (format `YYYY-MM-DD HH:MM:SS`; a space, so either `--data-urlencode` it or write the space as `+` inside `-d`).
99
- - `finishedDate` — now, same format.
100
- 3. To comment on a task without finishing it: `POST /action-comment-task-{id}.json`.
76
+ `get` downloads token-gated inline images and attachments into a temporary
77
+ directory by default and returns only local paths. Inspect those local files;
78
+ never pass the original ZenTao URL to an image tool.
101
79
 
102
- If a call fails, report the actual HTTP status and response body to the user instead of failing silently.
80
+ Write commands require JSON on stdin and are allowed only after the explicit
81
+ confirmation steps below:
103
82
 
104
- Do NOT browse via products/projects — always start from the my-work lists or an explicit id the user gives.
83
+ ```text
84
+ node <skill-root>/scripts/zentao-cli.mjs comment bug <id>
85
+ node <skill-root>/scripts/zentao-cli.mjs comment task <id>
86
+ node <skill-root>/scripts/zentao-cli.mjs resolve bug <id>
87
+ node <skill-root>/scripts/zentao-cli.mjs finish task <id>
88
+ ```
105
89
 
106
- ## Per-item workflow (follow strictly, in order)
90
+ Input shapes:
107
91
 
108
- 1. **Fetch details** — pull title, reproduction steps, severity, and module via the API. Images are usually inline in the `steps` HTML (`<img src=".../file-read-{id}.png">`; the `files` list is often empty) — download each with the Token header to a temp file and Read it now, so the screenshot informs the fix. If you cannot see images, try an image-inspection tool with the downloaded file path — e.g. `inspect_image` (MCP server `agent-tools-vision`) — never the token-gated ZenTao URL; if none is available, work from the text details and note that screenshots were skipped.
109
- 2. **Restate and confirm** — restate the problem and the intended fix in your own words. If the description is unclear or ambiguous, ask the user before touching code.
110
- 3. **Locate the code** — search the current project for the relevant code and explain how it was identified.
111
- 4. **Fix** — change only what this bug/task requires; no unrelated cleanups.
112
- 5. **Verify** — proportionate to the change: run the narrowest check that exercises it (the affected tests, a targeted build/typecheck of the touched module — not a full build for a one-line fix). If the bug is reproducible from code, reproduce it before the fix and confirm it is gone after. Use the project's verify skill if one exists. For changes machines can't judge (UI/visual/interaction), say so honestly — state what WAS checked (compiles, tests pass) and that the visual result needs the user's eyes; the user verifies via the "not yet" path at the commit step. Never present an unverifiable change as verified. A failed check must not proceed to the next step.
113
- 6. **git add** — first run `git diff --staged --name-only`; if the index already holds unrelated changes, STOP and ask the user (commit those separately / unstage them / proceed anyway) so the `bug#<id>` commit isn't polluted. Then stage only the files changed for THIS item, listing them explicitly (never `git add -A`).
114
- 7. **Ask whether to commit** (never commit automatically):
115
- - 1) Commit — generate and show a Conventional Commits message, following all at-commit conventions (language policy, ≤74-char single-line title). Right after `type(scope):`, add the ZenTao link token — `bug#<id>` or `task#<id>` — e.g. `fix(<scope>): bug#30887 <desc>` (scope optional). Rewrite the description from the diff rather than copying the title, keeping the title's domain terms.
116
- - 2) Not yet — keep the changes staged and continue
117
- - 3) Needs adjustment — take the feedback and return to step 4
118
- 8. **Ask whether to update ZenTao** (never change status automatically). First DRAFT the write-back, then show it in the confirmation question. If the item was NOT committed (you chose "Not yet" at step 7), do not draft a `fixed` resolution or a commit hash — at most a comment with the status left unchanged, since a `fixed` write-back must reference a real commit:
119
- - **Resolution** — pick the value that matches what actually happened (ZenTao's enum): `fixed` 已解决 (default after a code fix), `notrepro` 无法重现, `duplicate` 重复Bug (needs the duplicate bug id), `bydesign` 设计如此, `external` 外部原因, `postponed` 延期处理, `willnotfix` 不予解决. If investigation showed the bug needs no code fix, propose the fitting non-`fixed` resolution instead.
120
- - **Comment** — one sentence: root cause + change summary, plus the commit hash if committed. Don't list files or expand into narrative.
121
- - Options (reply with a number): 1) Submit 2) Edit first 3) Comment only (no status change).
122
- - **For tasks**, default to adding a comment only (drafted the same way). Offer "finish" ONLY for simple tasks completable in one sitting — it asks the user for hours (`currentConsumed`) and submits once. For multi-day tasks or teams that log per-day workhours, do NOT attempt finish via API; post the comment and point the user to the web UI's 记录工时/完成 forms, which handle per-day entries properly.
92
+ ```json
93
+ {"comment":"Root cause and result."}
94
+ {"resolution":"fixed","resolvedBuild":"trunk","comment":"Root cause and result, commit abc1234."}
95
+ {"currentConsumed":1.5,"realStarted":"2026-08-11 09:00:00","finishedDate":"2026-08-11 10:30:00"}
96
+ ```
123
97
 
124
- ## Export mode (`export bug <id>` / `export task <id>`)
98
+ For `duplicate`, also pass `"duplicateBug": <id>`. Send JSON through stdin,
99
+ not as a command-line argument. The CLI handles UTF-8 form encoding and
100
+ computes a task's total consumed hours from its current ZenTao value.
125
101
 
126
- Produce a self-contained handoff for someone (or another agent) WITHOUT ZenTao access. Read-only: do NOT fix, commit, or write status back.
102
+ ## Usage
127
103
 
128
- 1. Fetch details as in step 1, including downloading every inline/attached image they are token-gated, so the recipient cannot fetch them; the export must carry them.
129
- 2. Ask the user where to save (a free-form value ask in plain chat, never via a multiple-choice prompt); default to the Desktop, never the code repo (an export artifact doesn't belong in project source).
130
- 3. Write the Markdown: a header (id, title, status, severity/pri, module/product, opened/assigned), the `steps` converted from HTML to Markdown, and comments/history when useful. Strip anything auth-bound — never include the token, credentials, or login-gated URLs.
131
- 4. Layout by content:
132
- - No images a single file `<dest>/zentao-<bug|task>-<id>.md`.
133
- - With images a folder `<dest>/zentao-<bug|task>-<id>/` holding that `.md` plus the downloaded images; rewrite each `<img src=".../file-read-...">` to a relative `![](./file-read-<id>.png)` link. Keep images as real files (never base64) so another agent can Read/see them and every viewer renders them; to hand the folder over as one item, zip it.
104
+ - `/at-zentao bug <id>`handle a single bug.
105
+ - `/at-zentao task <id>`handle a single task.
106
+ - `/at-zentao bugs` list bugs assigned to the configured account; let the
107
+ user select one or more.
108
+ - `/at-zentao tasks` list assigned tasks and let the user select.
109
+ - `/at-zentao export bug <id>` or `export task <id>` create a read-only,
110
+ self-contained handoff bundle.
111
+
112
+ If a list response includes pager data showing more items than returned, tell
113
+ the user the shown and total counts. Do not silently imply the list is complete.
114
+ Do not browse through products/projects; start from assigned lists or an
115
+ explicit item id.
116
+
117
+ ## Per-item workflow
118
+
119
+ Follow these steps in order:
120
+
121
+ 1. **Fetch details** — use the CLI `get` command. Read every downloaded image
122
+ now so screenshots inform the fix. If no image-inspection tool is available,
123
+ continue from text and state that screenshots were skipped.
124
+ 2. **Restate and confirm** — explain the problem and intended fix in your own
125
+ words. Ask before editing when the item is ambiguous.
126
+ 3. **Locate the code** — search the current project and explain how the relevant
127
+ code was identified.
128
+ 4. **Fix** — change only what this item requires.
129
+ 5. **Verify** — run the narrowest meaningful test, build, or typecheck. Reproduce
130
+ a code-observable bug before and after when practical. For visual changes,
131
+ state what was checked and what still needs the user's eyes. Stop on a failed
132
+ check.
133
+ 6. **Stage** — first inspect `git diff --staged --name-only`. If unrelated files
134
+ are staged, stop and ask how to handle them. Otherwise stage only files for
135
+ this item by explicit path; never use `git add -A`.
136
+ 7. **Ask whether to commit** — offer Commit, Not yet, or Needs adjustment. For a
137
+ commit, show a single-line Conventional Commit message before committing.
138
+ Put `bug#<id>` or `task#<id>` immediately after `type(scope):`, for example
139
+ `fix(auth): bug#30887 reject expired sessions`. Never commit automatically.
140
+ 8. **Ask whether to update ZenTao** — draft the exact resolution/comment and
141
+ offer Submit, Edit first, or Comment only. Never invoke a write CLI command
142
+ before confirmation. A `fixed` resolution must cite a real commit; without a
143
+ commit, offer at most a comment with status unchanged.
144
+
145
+ Bug resolutions are `fixed`, `notrepro`, `duplicate`, `bydesign`, `external`,
146
+ `postponed`, and `willnotfix`. Choose what matches the verified outcome. A
147
+ write-back comment is one sentence containing root cause, change summary, and
148
+ the commit hash when committed.
149
+
150
+ For tasks, default to comment only. Offer `finish` only for a simple task
151
+ completed in one sitting, and ask the user for `currentConsumed`; never invent
152
+ hours. For multi-day work or per-day time records, comment and direct the user
153
+ to ZenTao's web UI.
154
+
155
+ ## Export mode
156
+
157
+ Export is read-only and never fixes code, commits, or writes back:
158
+
159
+ 1. Fetch the item with `get`, including every downloaded image/attachment.
160
+ 2. Ask where to save; default to the Desktop and never the code repository.
161
+ 3. Write a Markdown handoff with id, title, status, severity/priority,
162
+ module/product, description/steps, and useful history available in the safe
163
+ CLI response. Do not include credentials or login-gated URLs.
164
+ 4. With no images, write `<dest>/zentao-<bug|task>-<id>.md`. With images, create
165
+ a same-named directory containing the Markdown and real image files, rewrite
166
+ image references to relative paths, and zip only when a single artifact is
167
+ needed.
134
168
 
135
169
  ## Batch mode
136
170
 
137
- - Strictly sequential — one item at a time, each with its own stage/commit. Never mix changes from different bugs.
138
- - Continue to the next item ONLY after the current item's changes are committed. If the user chose "not yet" at the commit step, do not start the next item — its `git add`/commit would sweep up the still-staged changes (and same-file edits can't be untangled later). Instead ask: commit now / stash this item's changes and continue / stop the batch here.
139
- - After each item, ask: continue to the next / stop (summarize progress so far). The user can also name a specific pending item to skip.
140
- - Before starting, show the pending list and let the user confirm the order.
171
+ - Process one item at a time, each with its own stage and commit.
172
+ - Show the pending order before starting.
173
+ - Continue only after the current item is committed. If it remains staged,
174
+ offer commit, stash and continue, or stop.
175
+ - After each item, ask whether to continue or stop and summarize progress.
141
176
 
142
177
  ## Hard rules
143
178
 
144
- - Never commit and never change ZenTao status without asking first.
145
- - The commit subject MUST carry the `bug#<id>` / `task#<id>` token right after `type(scope):` — ZenTao's repo integration parses it to auto-link the commit, and it keeps IDs aligned in `git log --oneline`.
146
- - Confirmations may use a multiple-choice prompt if the agent has one (e.g. Claude Code's AskUserQuestion), but ONLY for enumerable decisions (commit? write back? which resolution?). Free-form values URL, account, hours — are collected by asking in plain chat and waiting for the reply.
147
- - Never echo the password or token in full, and never write them anywhere except the `zentao` block of the user's global config (the password there is filled in by the user, not by you).
148
- - On API failures, report the HTTP status and response body verbatim; do not guess and continue.
179
+ - Never expose account, password, token, cookies, or authorization headers.
180
+ - Never bypass the bundled CLI for ZenTao authentication or API access.
181
+ - Never commit or change ZenTao state without separate explicit confirmation.
182
+ - Keep the `bug#<id>` or `task#<id>` token in every related commit subject.
183
+ - Collect free-form values such as output paths and hours in plain chat, not a
184
+ fixed-choice prompt. Passwords and tokens are never collected in chat.