@kairyou/agent-tools 0.22.0 → 0.23.1

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.
@@ -78,6 +78,12 @@ node <skill-root>/scripts/zentao-cli.mjs hours task <id>
78
78
  `get` downloads token-gated inline images and attachments into a temporary
79
79
  directory by default and returns only local paths. Inspect those local files;
80
80
  never pass the original ZenTao URL to an image tool.
81
+ For Bugs and Tasks, a configured `zentao.commentPrompt` is returned as
82
+ `writeback.commentPrompt`. Use it only when drafting a write-back comment; it
83
+ is not item data or an existing comment. `get` also returns safe `comments`
84
+ from action history, keeping only id, actor, action, date, and non-empty
85
+ comment. An omitted `comments` means actions were unavailable; an empty array
86
+ means actions were available but contained no comments.
81
87
 
82
88
  Write commands require JSON on stdin and are allowed only after the explicit
83
89
  confirmation steps below:
@@ -98,11 +104,11 @@ Input shapes:
98
104
 
99
105
  ```json
100
106
  {"comment":"Root cause and result."}
101
- {"resolution":"fixed","resolvedBuild":"trunk","comment":"Root cause and result, commit abc1234."}
107
+ {"resolution":"fixed","resolvedBuild":"trunk","comment":"Root cause: stale session cache; Fix: refresh it during renewal; Commit: abc1234."}
102
108
  {"realStarted":"2026-08-11 09:00:00","comment":"Started implementation."}
103
109
  {"date":"2026-08-11","consumed":2,"left":14,"work":"Implemented the first part of the task."}
104
110
  {"work":"Corrected work description, commit abc1234."}
105
- {"currentConsumed":1.5,"realStarted":"2026-08-11 09:00:00","finishedDate":"2026-08-11 10:30:00"}
111
+ {"currentConsumed":1.5,"realStarted":"2026-08-11 09:00:00","finishedDate":"2026-08-11 10:30:00","comment":"Completed: implemented session renewal; Commit: abc1234."}
106
112
  ```
107
113
 
108
114
  For `duplicate`, also pass `"duplicateBug": <id>`. Send JSON through stdin,
@@ -114,7 +120,7 @@ the task's current hours when starting or resuming it.
114
120
  `log-hours` defaults `date` to today, requires positive remaining hours,
115
121
  and keeps the task open. `hours` is read-only. `edit-hours` preserves omitted
116
122
  fields from the existing record and updates it through ZenTao's native effort
117
- workflow. Use `finish` when the task is complete.
123
+ workflow. Use `finish` when the task is complete; its `comment` is optional.
118
124
 
119
125
  ## Usage
120
126
 
@@ -159,9 +165,11 @@ the current hours and never implies new consumed time.
159
165
 
160
166
  Follow these steps in order:
161
167
 
162
- 1. **Fetch details** — use the CLI `get` command. Read every downloaded image
163
- now so screenshots inform the fix. If no image-inspection tool is available,
164
- continue from text and state that screenshots were skipped.
168
+ 1. **Fetch details** — use the CLI `get` command. Review every returned comment,
169
+ including comments attached to resolution, activation, and lifecycle actions.
170
+ Read every downloaded image now so screenshots inform the fix. If no
171
+ image-inspection tool is available, continue from text and state that
172
+ screenshots were skipped.
165
173
  2. **Restate and confirm** — explain the problem and intended fix in your own
166
174
  words. Ask before editing when the item is ambiguous.
167
175
  3. **Locate the code** — search the current project and explain how the relevant
@@ -185,8 +193,20 @@ Follow these steps in order:
185
193
 
186
194
  Bug resolutions are `fixed`, `notrepro`, `duplicate`, `bydesign`, `external`,
187
195
  `postponed`, and `willnotfix`. Choose what matches the verified outcome. A
188
- write-back comment is one sentence containing root cause, change summary, and
189
- the commit hash when committed.
196
+ write-back comment follows the current user request, then
197
+ `writeback.commentPrompt`. Otherwise use the item or user's language and these
198
+ defaults:
199
+
200
+ - For a fixed Bug, use `Root cause`, `Fix`, optional `Verification`, and the
201
+ real `Commit`.
202
+ - For a finished Task, use `Completed`, optional `Verification`, and `Commit`
203
+ only when the work has a related commit.
204
+ - For other Bug resolutions, use `Conclusion` and applicable `Reason` or
205
+ `Evidence`.
206
+ - Keep standalone and lifecycle comments free-form but concise.
207
+
208
+ Formatting controls wording and layout only. Never invent missing facts;
209
+ surface required gaps and put `Commit: <hash>` last.
190
210
 
191
211
  Immediately before confirming any ZenTao write that cites the latest commit,
192
212
  run `git rev-parse HEAD` and `git log -1 --format=%h`. Do not reuse a hash from
@@ -200,9 +220,10 @@ current entry and make that estimate editable in the confirmation; ask only
200
220
  when no reliable suggestion is possible. Draft `work` from the verified result
201
221
  when context is available. It is optional, so mention the omission without
202
222
  blocking the write when there is nothing useful to add. For a completed task,
203
- collect `currentConsumed` and draft a `finish` write. Never infer consumed
204
- hours. Show all submitted values and require the same explicit ZenTao
205
- confirmation before either write.
223
+ collect `currentConsumed`, draft the completion comment from established facts,
224
+ and include both in the `finish` write. Never infer consumed hours or a
225
+ verification result. Show all submitted values and require the same explicit
226
+ ZenTao confirmation before either write.
206
227
 
207
228
  To correct an existing time entry, use `hours` to select its effort id. Show
208
229
  the current and proposed `date`, `consumed`, `left`, and `work`, then obtain
@@ -9,6 +9,8 @@ import { fileURLToPath } from "node:url";
9
9
  const JSON_LIMIT = 4 * 1024 * 1024;
10
10
  const BINARY_LIMIT = 20 * 1024 * 1024;
11
11
  const INPUT_LIMIT = 1024 * 1024;
12
+ // Keep write-back guidance compact enough to remain useful in an agent response.
13
+ const COMMENT_PROMPT_LIMIT = 1000;
12
14
  const SECRET_KEYS = /^(?:password|token|authorization|cookie|set-cookie)$/i;
13
15
  const RESOLUTIONS = new Set([
14
16
  "fixed",
@@ -148,6 +150,7 @@ export function loadConfig({ env = process.env, file = configFile(env) } = {}) {
148
150
  const rawAccount = env.ZENTAO_ACCOUNT || section.account;
149
151
  const rawPassword = env.ZENTAO_PASSWORD || section.password;
150
152
  const rawToken = env.ZENTAO_TOKEN;
153
+ const rawCommentPrompt = section.commentPrompt;
151
154
  const urlText = resolveValue(rawUrl, env, "zentao.url");
152
155
  let parsedUrl;
153
156
  try {
@@ -171,12 +174,23 @@ export function loadConfig({ env = process.env, file = configFile(env) } = {}) {
171
174
  const token = resolveValue(rawToken, env, "zentao.token", { required: false });
172
175
  const account = resolveValue(rawAccount, env, "zentao.account", { required: !token });
173
176
  const password = resolveValue(rawPassword, env, "zentao.password", { required: !token });
177
+ if (rawCommentPrompt !== undefined && typeof rawCommentPrompt !== "string") {
178
+ throw new CliError("config_error", "zentao.commentPrompt must be a string");
179
+ }
180
+ const commentPrompt = rawCommentPrompt?.trim() || null;
181
+ if (commentPrompt && commentPrompt.length > COMMENT_PROMPT_LIMIT) {
182
+ throw new CliError(
183
+ "config_error",
184
+ `zentao.commentPrompt must not exceed ${COMMENT_PROMPT_LIMIT} characters`
185
+ );
186
+ }
174
187
  return {
175
188
  url: parsedUrl.href.replace(/\/$/, ""),
176
189
  account,
177
190
  password,
178
191
  token,
179
192
  tokenOnly: Boolean(token),
193
+ commentPrompt,
180
194
  secrets: [account, password, token].filter(Boolean),
181
195
  };
182
196
  }
@@ -385,6 +399,23 @@ function pick(source, keys) {
385
399
  return output;
386
400
  }
387
401
 
402
+ function normalizeComments(actions) {
403
+ const entries = Array.isArray(actions) ? actions : Object.values(actions || {});
404
+ const comments = [];
405
+ for (const entry of entries) {
406
+ if (!entry || typeof entry !== "object" || typeof entry.comment !== "string") continue;
407
+ const comment = entry.comment.trim();
408
+ if (!comment) continue;
409
+ const output = {};
410
+ for (const key of ["id", "actor", "action", "date"]) {
411
+ if (typeof entry[key] === "string" || typeof entry[key] === "number") output[key] = entry[key];
412
+ }
413
+ output.comment = comment;
414
+ comments.push(output);
415
+ }
416
+ return comments;
417
+ }
418
+
388
419
  function normalizeDetail(kind, response) {
389
420
  const container = response?.data && typeof response.data === "object" ? response.data : response;
390
421
  const detail = container?.[kind] || container;
@@ -414,7 +445,12 @@ function normalizeDetail(kind, response) {
414
445
  } else if (kind === "story") {
415
446
  fields.push("stage", "category", "plan", "estimate", "spec", "verify", "source", "sourceNote");
416
447
  }
417
- return { raw: detail, safe: pick(detail, fields) };
448
+ const safe = pick(detail, fields);
449
+ const actions = detail.actions ?? container?.actions;
450
+ if ((kind === "bug" || kind === "task") && actions && typeof actions === "object") {
451
+ safe.comments = normalizeComments(actions);
452
+ }
453
+ return { raw: detail, safe };
418
454
  }
419
455
 
420
456
  function attachmentUrls(detail) {
@@ -682,7 +718,13 @@ export async function run(argv, { env = process.env } = {}) {
682
718
  const resource = kind === "story" ? "stories" : `${kind}s`;
683
719
  const detail = normalizeDetail(kind, await client.json(`api.php/v1/${resource}/${id}`));
684
720
  const attachments = await downloadAttachments(client, detail.raw, directory);
685
- return { item: detail.safe, attachments };
721
+ return {
722
+ item: detail.safe,
723
+ attachments,
724
+ ...(kind !== "story" && config.commentPrompt
725
+ ? { writeback: { commentPrompt: config.commentPrompt } }
726
+ : {}),
727
+ };
686
728
  }
687
729
 
688
730
  if (command === "comment") {
@@ -842,6 +884,9 @@ export async function run(argv, { env = process.env } = {}) {
842
884
  const input = await readInput();
843
885
  const current = Number(input.currentConsumed);
844
886
  if (!Number.isFinite(current) || current <= 0) throw new CliError("usage_error", "currentConsumed must be positive");
887
+ if (input.comment !== undefined && typeof input.comment !== "string") {
888
+ throw new CliError("usage_error", "comment must be a string when provided");
889
+ }
845
890
  const form = decodeLegacy(await client.json(`task-finish-${id}.json`));
846
891
  const task = form?.task || {};
847
892
  const previous = Number(task.consumed || 0);
@@ -855,6 +900,7 @@ export async function run(argv, { env = process.env } = {}) {
855
900
  consumed: previous + current,
856
901
  realStarted,
857
902
  finishedDate: input.finishedDate || localDateTime(),
903
+ comment: input.comment?.trim(),
858
904
  }),
859
905
  });
860
906
  return legacyResult(response);