@ozzylabs/feedradar 0.2.1 → 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.
Files changed (192) hide show
  1. package/README.ja.md +51 -13
  2. package/README.md +51 -13
  3. package/dist/agents/_boundary.d.ts +21 -0
  4. package/dist/agents/_boundary.d.ts.map +1 -1
  5. package/dist/agents/_boundary.js +34 -0
  6. package/dist/agents/_boundary.js.map +1 -1
  7. package/dist/agents/claude-code.d.ts.map +1 -1
  8. package/dist/agents/claude-code.js +14 -6
  9. package/dist/agents/claude-code.js.map +1 -1
  10. package/dist/agents/codex-cli.d.ts.map +1 -1
  11. package/dist/agents/codex-cli.js +13 -7
  12. package/dist/agents/codex-cli.js.map +1 -1
  13. package/dist/agents/copilot.d.ts.map +1 -1
  14. package/dist/agents/copilot.js +13 -6
  15. package/dist/agents/copilot.js.map +1 -1
  16. package/dist/agents/gemini-cli.d.ts.map +1 -1
  17. package/dist/agents/gemini-cli.js +13 -6
  18. package/dist/agents/gemini-cli.js.map +1 -1
  19. package/dist/agents/types.d.ts +26 -0
  20. package/dist/agents/types.d.ts.map +1 -1
  21. package/dist/claude-skills/dismiss/SKILL.md +4 -4
  22. package/dist/claude-skills/research/SKILL.md +2 -3
  23. package/dist/claude-skills/review/SKILL.md +2 -2
  24. package/dist/claude-skills/update/SKILL.md +7 -7
  25. package/dist/cli/_locale.d.ts +96 -0
  26. package/dist/cli/_locale.d.ts.map +1 -0
  27. package/dist/cli/_locale.js +130 -0
  28. package/dist/cli/_locale.js.map +1 -0
  29. package/dist/cli/_progress.d.ts +30 -1
  30. package/dist/cli/_progress.d.ts.map +1 -1
  31. package/dist/cli/_progress.js +9 -1
  32. package/dist/cli/_progress.js.map +1 -1
  33. package/dist/cli/dismiss.d.ts.map +1 -1
  34. package/dist/cli/dismiss.js +61 -54
  35. package/dist/cli/dismiss.js.map +1 -1
  36. package/dist/cli/doctor.d.ts +8 -0
  37. package/dist/cli/doctor.d.ts.map +1 -1
  38. package/dist/cli/doctor.js +91 -60
  39. package/dist/cli/doctor.js.map +1 -1
  40. package/dist/cli/index.d.ts +36 -1
  41. package/dist/cli/index.d.ts.map +1 -1
  42. package/dist/cli/index.js +81 -18
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/cli/init.d.ts +15 -0
  45. package/dist/cli/init.d.ts.map +1 -1
  46. package/dist/cli/init.js +149 -51
  47. package/dist/cli/init.js.map +1 -1
  48. package/dist/cli/items.d.ts.map +1 -1
  49. package/dist/cli/items.js +51 -30
  50. package/dist/cli/items.js.map +1 -1
  51. package/dist/cli/research.d.ts.map +1 -1
  52. package/dist/cli/research.js +138 -109
  53. package/dist/cli/research.js.map +1 -1
  54. package/dist/cli/review.d.ts.map +1 -1
  55. package/dist/cli/review.js +114 -92
  56. package/dist/cli/review.js.map +1 -1
  57. package/dist/cli/routine/fire.d.ts +3 -2
  58. package/dist/cli/routine/fire.d.ts.map +1 -1
  59. package/dist/cli/routine/fire.js +30 -25
  60. package/dist/cli/routine/fire.js.map +1 -1
  61. package/dist/cli/routine/generate-pipeline.d.ts +24 -10
  62. package/dist/cli/routine/generate-pipeline.d.ts.map +1 -1
  63. package/dist/cli/routine/generate-pipeline.js +158 -83
  64. package/dist/cli/routine/generate-pipeline.js.map +1 -1
  65. package/dist/cli/routine/generate-watch.d.ts +56 -1
  66. package/dist/cli/routine/generate-watch.d.ts.map +1 -1
  67. package/dist/cli/routine/generate-watch.js +116 -42
  68. package/dist/cli/routine/generate-watch.js.map +1 -1
  69. package/dist/cli/routine.d.ts.map +1 -1
  70. package/dist/cli/routine.js +28 -24
  71. package/dist/cli/routine.js.map +1 -1
  72. package/dist/cli/source.d.ts.map +1 -1
  73. package/dist/cli/source.js +212 -182
  74. package/dist/cli/source.js.map +1 -1
  75. package/dist/cli/state.d.ts +43 -0
  76. package/dist/cli/state.d.ts.map +1 -0
  77. package/dist/cli/state.js +177 -0
  78. package/dist/cli/state.js.map +1 -0
  79. package/dist/cli/triage.d.ts.map +1 -1
  80. package/dist/cli/triage.js +146 -130
  81. package/dist/cli/triage.js.map +1 -1
  82. package/dist/cli/undismiss.d.ts.map +1 -1
  83. package/dist/cli/undismiss.js +32 -25
  84. package/dist/cli/undismiss.js.map +1 -1
  85. package/dist/cli/update.d.ts.map +1 -1
  86. package/dist/cli/update.js +77 -61
  87. package/dist/cli/update.js.map +1 -1
  88. package/dist/cli/watch.d.ts.map +1 -1
  89. package/dist/cli/watch.js +71 -31
  90. package/dist/cli/watch.js.map +1 -1
  91. package/dist/cli/workflow/generate-combined-with-triage.d.ts +9 -2
  92. package/dist/cli/workflow/generate-combined-with-triage.d.ts.map +1 -1
  93. package/dist/cli/workflow/generate-combined-with-triage.js +120 -71
  94. package/dist/cli/workflow/generate-combined-with-triage.js.map +1 -1
  95. package/dist/cli/workflow/generate-combined.d.ts +8 -1
  96. package/dist/cli/workflow/generate-combined.d.ts.map +1 -1
  97. package/dist/cli/workflow/generate-combined.js +39 -33
  98. package/dist/cli/workflow/generate-combined.js.map +1 -1
  99. package/dist/cli/workflow/generate-watch.d.ts +10 -1
  100. package/dist/cli/workflow/generate-watch.d.ts.map +1 -1
  101. package/dist/cli/workflow/generate-watch.js +37 -30
  102. package/dist/cli/workflow/generate-watch.js.map +1 -1
  103. package/dist/cli/workflow.d.ts.map +1 -1
  104. package/dist/cli/workflow.js +28 -23
  105. package/dist/cli/workflow.js.map +1 -1
  106. package/dist/core/config.d.ts +2 -1
  107. package/dist/core/config.d.ts.map +1 -1
  108. package/dist/core/config.js +14 -4
  109. package/dist/core/config.js.map +1 -1
  110. package/dist/core/feeds/html-js.d.ts.map +1 -1
  111. package/dist/core/feeds/html-js.js +16 -9
  112. package/dist/core/feeds/html-js.js.map +1 -1
  113. package/dist/core/feeds/json-api.d.ts.map +1 -1
  114. package/dist/core/feeds/json-api.js +38 -21
  115. package/dist/core/feeds/json-api.js.map +1 -1
  116. package/dist/core/feeds/types.d.ts +9 -0
  117. package/dist/core/feeds/types.d.ts.map +1 -1
  118. package/dist/core/filter.d.ts +20 -12
  119. package/dist/core/filter.d.ts.map +1 -1
  120. package/dist/core/filter.js +87 -46
  121. package/dist/core/filter.js.map +1 -1
  122. package/dist/core/locale.d.ts +69 -0
  123. package/dist/core/locale.d.ts.map +1 -0
  124. package/dist/core/locale.js +74 -0
  125. package/dist/core/locale.js.map +1 -0
  126. package/dist/core/state.d.ts +20 -0
  127. package/dist/core/state.d.ts.map +1 -1
  128. package/dist/core/state.js +26 -0
  129. package/dist/core/state.js.map +1 -1
  130. package/dist/core/triage/prompt.d.ts.map +1 -1
  131. package/dist/core/triage/prompt.js +18 -4
  132. package/dist/core/triage/prompt.js.map +1 -1
  133. package/dist/core/watcher.d.ts +28 -0
  134. package/dist/core/watcher.d.ts.map +1 -1
  135. package/dist/core/watcher.js +77 -8
  136. package/dist/core/watcher.js.map +1 -1
  137. package/dist/i18n/index.d.ts +57 -0
  138. package/dist/i18n/index.d.ts.map +1 -0
  139. package/dist/i18n/index.js +49 -0
  140. package/dist/i18n/index.js.map +1 -0
  141. package/dist/i18n/messages/en.d.ts +1049 -0
  142. package/dist/i18n/messages/en.d.ts.map +1 -0
  143. package/dist/i18n/messages/en.js +1152 -0
  144. package/dist/i18n/messages/en.js.map +1 -0
  145. package/dist/i18n/messages/ja.d.ts +13 -0
  146. package/dist/i18n/messages/ja.d.ts.map +1 -0
  147. package/dist/i18n/messages/ja.js +1010 -0
  148. package/dist/i18n/messages/ja.js.map +1 -0
  149. package/dist/schemas/config.d.ts +7 -0
  150. package/dist/schemas/config.d.ts.map +1 -1
  151. package/dist/schemas/config.js +5 -0
  152. package/dist/schemas/config.js.map +1 -1
  153. package/dist/schemas/item.d.ts +1 -0
  154. package/dist/schemas/item.d.ts.map +1 -1
  155. package/dist/schemas/item.js +15 -0
  156. package/dist/schemas/item.js.map +1 -1
  157. package/dist/schemas/recipe.d.ts +7 -1
  158. package/dist/schemas/recipe.d.ts.map +1 -1
  159. package/dist/schemas/recipe.js +1 -0
  160. package/dist/schemas/recipe.js.map +1 -1
  161. package/dist/schemas/source.d.ts +40 -18
  162. package/dist/schemas/source.d.ts.map +1 -1
  163. package/dist/schemas/source.js +84 -23
  164. package/dist/schemas/source.js.map +1 -1
  165. package/dist/skills/research/SKILL.md +13 -12
  166. package/dist/skills/review/SKILL.md +13 -12
  167. package/dist/skills/update/SKILL.md +19 -19
  168. package/dist/templates/en/agents/AGENTS.md +284 -0
  169. package/dist/templates/en/claude/CLAUDE.md +5 -0
  170. package/dist/templates/en/default.md +16 -0
  171. package/dist/templates/en/digest.md +66 -0
  172. package/dist/templates/en/feedradar.md +235 -0
  173. package/dist/templates/{routines → en/routines}/pipeline.yaml.tmpl +93 -34
  174. package/dist/templates/{routines → en/routines}/watch-daily.yaml +12 -15
  175. package/dist/templates/{routines → en/routines}/watch.yaml.tmpl +11 -14
  176. package/dist/templates/{workflows → en/workflows}/combined-with-triage.template.yaml.tmpl +3 -3
  177. package/dist/templates/{workflows → en/workflows}/combined.template.yaml.tmpl +6 -6
  178. package/dist/templates/{workflows → en/workflows}/watch.template.yaml.tmpl +8 -8
  179. package/dist/templates/{workflows → en/workflows}/watch.yaml +3 -3
  180. package/dist/templates/{agents → ja/agents}/AGENTS.md +16 -16
  181. package/dist/templates/{digest.md → ja/digest.md} +5 -6
  182. package/dist/templates/{feedradar.md → ja/feedradar.md} +12 -12
  183. package/dist/templates/ja/routines/pipeline.yaml.tmpl +267 -0
  184. package/dist/templates/ja/routines/watch-daily.yaml +151 -0
  185. package/dist/templates/ja/routines/watch.yaml.tmpl +145 -0
  186. package/dist/templates/ja/workflows/combined-with-triage.template.yaml.tmpl +123 -0
  187. package/dist/templates/ja/workflows/combined.template.yaml.tmpl +109 -0
  188. package/dist/templates/ja/workflows/watch.template.yaml.tmpl +100 -0
  189. package/dist/templates/ja/workflows/watch.yaml +73 -0
  190. package/package.json +1 -1
  191. /package/dist/templates/{claude → ja/claude}/CLAUDE.md +0 -0
  192. /package/dist/templates/{default.md → ja/default.md} +0 -0
@@ -10,10 +10,27 @@ import { createProgressReporter } from "../core/progress.js";
10
10
  import { statusForTriageDecision } from "../core/transitions.js";
11
11
  import { buildTriagePrompt, parseTriageResponse, TriageResponseParseError, triageItems, } from "../core/triage/index.js";
12
12
  import { loadSources } from "../core/watcher.js";
13
+ import { createTranslator } from "../i18n/index.js";
13
14
  import { TriageDecisionValueSchema } from "../schemas/item.js";
14
15
  import { AgentIdSchema } from "../schemas/research.js";
15
16
  import { SourceTriagePolicySchema } from "../schemas/source.js";
16
17
  import { resolveCommitPathInside } from "./_commit-path.js";
18
+ import { LangFlagError, parseLangFlag, resolveWorkspaceLocale } from "./_locale.js";
19
+ /**
20
+ * Validation error thrown by the sync `parseTriageRunArgs` that carries a
21
+ * message *key* instead of pre-rendered English (#336). The parser runs before
22
+ * the locale is resolved, so it tags the error with a catalog key and the
23
+ * caller renders it once the translator is in scope. Raw-token errors (unknown
24
+ * option / unexpected argument) stay plain `Error`s and echo verbatim.
25
+ */
26
+ class TriageArgError extends Error {
27
+ key;
28
+ constructor(key) {
29
+ super(key);
30
+ this.key = key;
31
+ this.name = "TriageArgError";
32
+ }
33
+ }
17
34
  function splitCsv(value) {
18
35
  return value
19
36
  .split(",")
@@ -39,19 +56,19 @@ function parseTriageRunArgs(args) {
39
56
  }
40
57
  if (a === "--dry-run") {
41
58
  if (out.mode)
42
- throw new Error("--dry-run / --apply / --interactive are mutually exclusive");
59
+ throw new TriageArgError("cli.triage.modesExclusive");
43
60
  out.mode = "dry-run";
44
61
  continue;
45
62
  }
46
63
  if (a === "--apply") {
47
64
  if (out.mode)
48
- throw new Error("--dry-run / --apply / --interactive are mutually exclusive");
65
+ throw new TriageArgError("cli.triage.modesExclusive");
49
66
  out.mode = "apply";
50
67
  continue;
51
68
  }
52
69
  if (a === "--interactive") {
53
70
  if (out.mode)
54
- throw new Error("--dry-run / --apply / --interactive are mutually exclusive");
71
+ throw new TriageArgError("cli.triage.modesExclusive");
55
72
  out.mode = "interactive";
56
73
  continue;
57
74
  }
@@ -119,7 +136,7 @@ function parseTriageRunArgs(args) {
119
136
  throw new Error(`unexpected positional argument: ${a}`);
120
137
  }
121
138
  if (out.verbose && out.quiet) {
122
- throw new Error("--verbose and --quiet are mutually exclusive");
139
+ throw new TriageArgError("cli.triage.verboseQuietExclusive");
123
140
  }
124
141
  return out;
125
142
  }
@@ -157,66 +174,14 @@ function parseTriageFeedbackArgs(args) {
157
174
  }
158
175
  return out;
159
176
  }
160
- function printRunHelp(log) {
161
- log("Usage: radar triage [--dry-run | --apply | --interactive] [options]");
162
- log(" radar triage --emit-payload [--source <id>] [options]");
163
- log(" radar triage --commit <path>");
164
- log("");
165
- log("Classify `detected` items using the configured per-source triage policy.");
166
- log("");
167
- log("Modes (mutually exclusive; default: --dry-run):");
168
- log(" --dry-run print proposed decisions to stdout (no disk writes)");
169
- log(" --apply write decisions to items/<id>.yaml + transition status");
170
- log(" --interactive --dry-run output → $EDITOR → confirm → apply");
171
- log("");
172
- log("Options:");
173
- log(" --source <id> limit triage to a single source");
174
- log(" --filter-tags <a,b> matchedKeywords allow-list (comma-separated)");
175
- log(" --triage-agent <id> override policy.agent for this run");
176
- log(" --policy <path> override per-source policy with a YAML file");
177
- log(" --max-items N hard cap on items triaged in this run");
178
- log(" --audit-log <path> append JSONL audit records of every triage call");
179
- log(" --emit-payload Host-agent mode (ADR-0019): print the triage payload to");
180
- log(" stdout and DO NOT spawn an agent. The interactive host");
181
- log(" session classifies the items itself, writes a decisions");
182
- log(" JSON, then finalizes with `radar triage --commit <path>`.");
183
- log(" Requires a single source group: pass --source unless only");
184
- log(" one source has detected items. Interactive/opt-in only —");
185
- log(" CI/headless must use the default spawn path.");
186
- log(" --commit <path> Host-agent mode (ADR-0019): validate a host-written");
187
- log(" decisions JSON (under <cwd>/triage/) against the source's");
188
- log(" policy + detected items and apply the status transitions.");
189
- log(" -v, --verbose verbose progress output");
190
- log(" -q, --quiet suppress progress output entirely");
191
- log("");
192
- log("Sources missing a `triagePolicy:` block are skipped with a warning. See");
193
- log("ADR-0018 for the policy schema reference.");
177
+ function printRunHelp(t, log) {
178
+ log(t("cli.triage.runHelp"));
194
179
  }
195
- function printFeedbackHelp(log) {
196
- log("Usage: radar triage feedback <item-id> --correct | --wrong [--reason <text>]");
197
- log("");
198
- log("Record human feedback on a prior triage decision (ADR-0018 §W5).");
199
- log("Feedback is appended to items/<id>.yaml > triage.feedback, used by");
200
- log("`radar triage stats` (#242) for policy tuning.");
201
- log("");
202
- log("Options:");
203
- log(" --correct mark the prior triage decision as correct");
204
- log(" --wrong mark the prior triage decision as wrong");
205
- log(" --reason <text> free-form rationale (recommended for --wrong)");
180
+ function printFeedbackHelp(t, log) {
181
+ log(t("cli.triage.feedbackHelp"));
206
182
  }
207
- function printTriageHelp(log) {
208
- log("Usage: radar triage <subcommand|--apply|--dry-run|--interactive> [...]");
209
- log("");
210
- log("Subcommands:");
211
- log(" feedback <item-id> --correct | --wrong [--reason <text>]");
212
- log(" stats [--since <duration>] [--source <id>] [--json]");
213
- log("");
214
- log("Run modes (when no subcommand given):");
215
- log(" --dry-run print proposed decisions");
216
- log(" --apply write decisions to items/<id>.yaml");
217
- log(" --interactive edit decisions in $EDITOR before applying");
218
- log("");
219
- log("Run `radar triage --help` for the full option list.");
183
+ function printTriageHelp(t, log) {
184
+ log(t("cli.triage.help"));
220
185
  }
221
186
  async function pathExists(p) {
222
187
  try {
@@ -376,16 +341,16 @@ async function promptConfirm(message) {
376
341
  * resolved groups plus the full `detected` set (for the decision table) and the
377
342
  * items dir.
378
343
  */
379
- async function prepareTriageGroups(parsed, cwd, io) {
344
+ async function prepareTriageGroups(parsed, cwd, io, t) {
380
345
  const { log, warn, error } = io;
381
346
  const sourcesDir = join(cwd, "sources");
382
347
  if (!(await pathExists(sourcesDir))) {
383
- error("triage: no sources/ directory (run `radar init` first)");
348
+ error(t("cli.triage.noSourcesDir"));
384
349
  return { exitCode: 1 };
385
350
  }
386
351
  const sources = await loadSources(sourcesDir, error);
387
352
  if (sources.length === 0) {
388
- log("triage: no sources defined; nothing to triage");
353
+ log(t("cli.triage.noSourcesDefined"));
389
354
  return { exitCode: 0 };
390
355
  }
391
356
  let policyOverride = null;
@@ -396,7 +361,7 @@ async function prepareTriageGroups(parsed, cwd, io) {
396
361
  }
397
362
  const itemsDir = join(cwd, "items");
398
363
  if (!(await pathExists(itemsDir))) {
399
- log("triage: no items/ directory; nothing to triage");
364
+ log(t("cli.triage.noItemsDir"));
400
365
  return { exitCode: 0 };
401
366
  }
402
367
  let allItems;
@@ -415,11 +380,11 @@ async function prepareTriageGroups(parsed, cwd, io) {
415
380
  detected = detected.filter((i) => i.matchedKeywords.some((k) => tags.has(k)));
416
381
  }
417
382
  if (detected.length === 0) {
418
- log("triage: no detected items match the filter (nothing to do)");
383
+ log(t("cli.triage.noDetectedMatch"));
419
384
  return { exitCode: 0 };
420
385
  }
421
386
  if (parsed.maxItems !== undefined && detected.length > parsed.maxItems) {
422
- warn(`triage: ${detected.length} detected item(s) exceed --max-items ${parsed.maxItems}; processing the first ${parsed.maxItems} only`);
387
+ warn(t("cli.triage.maxItemsExceeded", { detected: detected.length, maxItems: parsed.maxItems }));
423
388
  detected = detected.slice(0, parsed.maxItems);
424
389
  }
425
390
  const sourcesById = new Map(sources.map((s) => [s.id, s]));
@@ -436,7 +401,7 @@ async function prepareTriageGroups(parsed, cwd, io) {
436
401
  const source = sourcesById.get(sourceId);
437
402
  const policy = policyOverride ?? source?.triagePolicy;
438
403
  if (!policy) {
439
- warn(`triage: skipping ${groupItems.length} item(s) from source '${sourceId}' (no triagePolicy configured)`);
404
+ warn(t("cli.triage.skippingNoPolicy", { count: groupItems.length, sourceId }));
440
405
  continue;
441
406
  }
442
407
  const triageAgent = parsed.triageAgent ?? policy.agent;
@@ -444,7 +409,7 @@ async function prepareTriageGroups(parsed, cwd, io) {
444
409
  // agent call (or emitting a payload) — typos like `gemnini-cli` fail fast.
445
410
  const validated = AgentIdSchema.safeParse(triageAgent);
446
411
  if (!validated.success) {
447
- error(`triage: --triage-agent '${triageAgent}' is not a valid agent id (claude-code | codex-cli | gemini-cli | copilot)`);
412
+ error(t("cli.triage.invalidTriageAgent", { agent: triageAgent }));
448
413
  return { exitCode: 2 };
449
414
  }
450
415
  groups.push({ sourceId, items: groupItems, policy, triageAgent });
@@ -487,20 +452,21 @@ const TriageDecisionsFileSchema = z.object({
487
452
  * detected items the user must narrow with `--source` (mirrors the ADR-0020
488
453
  * "one item set at a time" host-mode posture).
489
454
  */
490
- async function runTriageEmitPayload(parsed, cwd, io) {
455
+ async function runTriageEmitPayload(parsed, cwd, io, t) {
491
456
  const { log, error } = io;
492
- const prepared = await prepareTriageGroups(parsed, cwd, io);
457
+ const prepared = await prepareTriageGroups(parsed, cwd, io, t);
493
458
  if ("exitCode" in prepared)
494
459
  return prepared.exitCode;
495
460
  const { groups } = prepared;
496
461
  if (groups.length === 0) {
497
- log("triage: no items were triaged (all sources skipped)");
462
+ log(t("cli.triage.noItemsTriaged"));
498
463
  return 0;
499
464
  }
500
465
  if (groups.length > 1) {
501
- error(`triage: --emit-payload requires a single source group, but ${groups.length} sources have detected items (${groups
502
- .map((g) => g.sourceId)
503
- .join(", ")}). Narrow with --source <id>.`);
466
+ error(t("cli.triage.emitPayloadSingleSource", {
467
+ count: groups.length,
468
+ sources: groups.map((g) => g.sourceId).join(", "),
469
+ }));
504
470
  return 2;
505
471
  }
506
472
  const group = groups[0];
@@ -528,7 +494,7 @@ async function runTriageEmitPayload(parsed, cwd, io) {
528
494
  * host misled by injected content into committing an arbitrary path is rejected
529
495
  * at the CLI boundary.
530
496
  */
531
- async function runTriageCommit(parsed, commitPath, cwd, options, io) {
497
+ async function runTriageCommit(parsed, commitPath, cwd, options, io, t) {
532
498
  const { log, warn, error } = io;
533
499
  const now = options.now ?? defaultNow;
534
500
  const guard = await resolveCommitPathInside(cwd, "triage", commitPath);
@@ -538,7 +504,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
538
504
  }
539
505
  const resolved = guard.resolved;
540
506
  if (!(await pathExists(resolved))) {
541
- error(`triage: decisions file not found: ${resolved}`);
507
+ error(t("cli.triage.decisionsFileNotFound", { path: resolved }));
542
508
  return 1;
543
509
  }
544
510
  let raw;
@@ -570,7 +536,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
570
536
  // `triage.agent`). Reject upfront rather than persisting a bogus origin.
571
537
  const agentValid = AgentIdSchema.safeParse(file.agent);
572
538
  if (!agentValid.success) {
573
- error(`triage: decisions file agent '${file.agent}' is not a valid agent id (claude-code | codex-cli | gemini-cli | copilot)`);
539
+ error(t("cli.triage.invalidDecisionsAgent", { agent: file.agent }));
574
540
  return 1;
575
541
  }
576
542
  const triageAgent = file.agent;
@@ -586,7 +552,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
586
552
  }
587
553
  const sourcesDir = join(cwd, "sources");
588
554
  if (!policyOverride && !(await pathExists(sourcesDir))) {
589
- error("triage: no sources/ directory (run `radar init` first)");
555
+ error(t("cli.triage.noSourcesDir"));
590
556
  return 1;
591
557
  }
592
558
  let policy = policyOverride;
@@ -594,11 +560,11 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
594
560
  const sources = await loadSources(sourcesDir, error);
595
561
  const source = sources.find((s) => s.id === file.sourceId);
596
562
  if (!source) {
597
- error(`triage: decisions file references unknown source '${file.sourceId}'`);
563
+ error(t("cli.triage.unknownSource", { sourceId: file.sourceId }));
598
564
  return 1;
599
565
  }
600
566
  if (!source.triagePolicy) {
601
- error(`triage: source '${file.sourceId}' has no triagePolicy (cannot validate decisions; pass --policy <path>)`);
567
+ error(t("cli.triage.sourceNoPolicy", { sourceId: file.sourceId }));
602
568
  return 1;
603
569
  }
604
570
  policy = source.triagePolicy;
@@ -610,7 +576,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
610
576
  // spawn path's behavior.
611
577
  const itemsDir = join(cwd, "items");
612
578
  if (!(await pathExists(itemsDir))) {
613
- error("triage: no items/ directory; nothing to commit");
579
+ error(t("cli.triage.noItemsDirCommit"));
614
580
  return 1;
615
581
  }
616
582
  let allItems;
@@ -623,7 +589,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
623
589
  }
624
590
  const detected = allItems.filter((i) => i.status === "detected");
625
591
  if (detected.length === 0) {
626
- error(`triage: no detected items remain for source '${file.sourceId}' (already triaged, or wrong source?)`);
592
+ error(t("cli.triage.noDetectedForSource", { sourceId: file.sourceId }));
627
593
  return 1;
628
594
  }
629
595
  // Re-validate through the SAME parser the spawn path runs. We feed the raw
@@ -682,7 +648,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
682
648
  error(`triage: failed to write items: ${e instanceof Error ? e.message : String(e)}`);
683
649
  return 1;
684
650
  }
685
- log(`triage: committed ${updated.length} decision(s) for source '${file.sourceId}'`);
651
+ log(t("cli.triage.committed", { count: updated.length, sourceId: file.sourceId }));
686
652
  for (const row of formatDecisionTable(detected, decisions))
687
653
  log(row);
688
654
  return 0;
@@ -692,6 +658,7 @@ async function runTriageCommit(parsed, commitPath, cwd, options, io) {
692
658
  * when the first positional is `feedback`, otherwise runs the triage flow.
693
659
  */
694
660
  export async function runTriage(args, options = {}) {
661
+ const cwd = options.cwd ?? process.cwd();
695
662
  const log = options.io?.log ?? ((m) => console.log(m));
696
663
  const warn = options.io?.warn ?? ((m) => console.warn(m));
697
664
  const error = options.io?.error ?? ((m) => console.error(m));
@@ -703,7 +670,18 @@ export async function runTriage(args, options = {}) {
703
670
  return runTriageStats(rest, options, { log, warn, error });
704
671
  }
705
672
  if (first === "help") {
706
- printTriageHelp(log);
673
+ // `help` subcommand: resolve a translator for the localized overview. A
674
+ // leading `--lang` is read-only here (no further args to forward).
675
+ const langFlag = (() => {
676
+ try {
677
+ return parseLangFlag(args).flag;
678
+ }
679
+ catch {
680
+ return undefined;
681
+ }
682
+ })();
683
+ const locale = await resolveWorkspaceLocale({ flag: langFlag, cwd, warn: error });
684
+ printTriageHelp(createTranslator(locale), log);
707
685
  return 0;
708
686
  }
709
687
  // `--help` / `-h` flow through to the run-mode parser so the user sees
@@ -723,16 +701,34 @@ async function runTriageRun(args, options, io) {
723
701
  const { log, warn, error } = io;
724
702
  const cwd = options.cwd ?? process.cwd();
725
703
  const now = options.now ?? defaultNow;
704
+ // Strip `--lang <en|ja>` before `parseTriageRunArgs` (which rejects unknown
705
+ // flags), then resolve the UI locale for the help text.
706
+ let langState;
707
+ try {
708
+ langState = parseLangFlag(args);
709
+ }
710
+ catch (e) {
711
+ if (e instanceof LangFlagError) {
712
+ error(`triage: ${e.message}`);
713
+ return 2;
714
+ }
715
+ throw e;
716
+ }
717
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
718
+ const t = createTranslator(locale);
726
719
  let parsed;
727
720
  try {
728
- parsed = parseTriageRunArgs(args);
721
+ parsed = parseTriageRunArgs(langState.rest);
729
722
  }
730
723
  catch (e) {
731
- error(`triage: ${e instanceof Error ? e.message : String(e)}`);
724
+ // Keyed validation errors are translated now that the locale is resolved;
725
+ // raw-token errors (unknown option / unexpected argument) echo verbatim.
726
+ const message = e instanceof TriageArgError ? t(e.key) : e instanceof Error ? e.message : String(e);
727
+ error(`triage: ${message}`);
732
728
  return 2;
733
729
  }
734
730
  if (parsed.help) {
735
- printRunHelp(log);
731
+ printRunHelp(t, log);
736
732
  return 0;
737
733
  }
738
734
  // Host-agent commit (#279 / ADR-0019). Independent of the run modes: it takes
@@ -740,23 +736,23 @@ async function runTriageRun(args, options, io) {
740
736
  // it must not be confused with `--dry-run` / `--apply` / `--interactive`.
741
737
  if (parsed.commit !== undefined) {
742
738
  if (parsed.mode) {
743
- error("triage: --commit is incompatible with --dry-run / --apply / --interactive");
739
+ error(t("cli.triage.commitIncompatibleModes"));
744
740
  return 2;
745
741
  }
746
742
  if (parsed.emitPayload) {
747
- error("triage: --commit is incompatible with --emit-payload");
743
+ error(t("cli.triage.commitIncompatibleEmitPayload"));
748
744
  return 2;
749
745
  }
750
- return runTriageCommit(parsed, parsed.commit, cwd, options, io);
746
+ return runTriageCommit(parsed, parsed.commit, cwd, options, io, t);
751
747
  }
752
748
  // Host-agent emit (#279 / ADR-0019). Mutually exclusive with the apply / dry
753
749
  // / interactive run modes — it prints a payload instead of running an agent.
754
750
  if (parsed.emitPayload) {
755
751
  if (parsed.mode) {
756
- error("triage: --emit-payload is incompatible with --dry-run / --apply / --interactive");
752
+ error(t("cli.triage.emitPayloadIncompatibleModes"));
757
753
  return 2;
758
754
  }
759
- return runTriageEmitPayload(parsed, cwd, io);
755
+ return runTriageEmitPayload(parsed, cwd, io, t);
760
756
  }
761
757
  const mode = parsed.mode ?? "dry-run";
762
758
  // Progress reporter (#197 / ADR-0015). The triage CLI uses the spinner
@@ -768,7 +764,7 @@ async function runTriageRun(args, options, io) {
768
764
  const reporter = createProgressReporter({ level });
769
765
  // Shared PRE block: load + filter + group detected items and resolve each
770
766
  // group's policy / agent (also used by `--emit-payload`).
771
- const prepared = await prepareTriageGroups(parsed, cwd, io);
767
+ const prepared = await prepareTriageGroups(parsed, cwd, io, t);
772
768
  if ("exitCode" in prepared)
773
769
  return prepared.exitCode;
774
770
  const { groups, detected, itemsDir } = prepared;
@@ -779,7 +775,11 @@ async function runTriageRun(args, options, io) {
779
775
  const allDecisions = new Map();
780
776
  const allErrors = [];
781
777
  for (const { sourceId, items: groupItems, policy, triageAgent } of groups) {
782
- reporter.phase(`Triaging ${groupItems.length} item(s) from source '${sourceId}' via ${triageAgent}`);
778
+ reporter.phase(t("cli.triage.progressTriaging", {
779
+ count: groupItems.length,
780
+ sourceId,
781
+ agent: triageAgent,
782
+ }));
783
783
  let result;
784
784
  try {
785
785
  result = await triageItems(groupItems, {
@@ -807,13 +807,13 @@ async function runTriageRun(args, options, io) {
807
807
  warn(`triage: ${e}`);
808
808
  }
809
809
  if (allDecisions.size === 0) {
810
- log("triage: no items were triaged (all sources skipped)");
810
+ log(t("cli.triage.noItemsTriaged"));
811
811
  return 0;
812
812
  }
813
813
  // Render the decision table. Used by every mode.
814
814
  const rows = formatDecisionTable(detected, allDecisions);
815
815
  if (mode === "dry-run") {
816
- log("triage: dry-run — no changes written");
816
+ log(t("cli.triage.dryRunNoChanges"));
817
817
  for (const row of rows)
818
818
  log(row);
819
819
  return 0;
@@ -841,9 +841,9 @@ async function runTriageRun(args, options, io) {
841
841
  return 1;
842
842
  }
843
843
  const confirm = options.confirm ?? promptConfirm;
844
- const confirmed = await confirm("Apply these decisions? [y/N]");
844
+ const confirmed = await confirm(t("cli.triage.confirmApply"));
845
845
  if (!confirmed) {
846
- log("triage: aborted by user");
846
+ log(t("cli.triage.abortedByUser"));
847
847
  return 0;
848
848
  }
849
849
  }
@@ -855,7 +855,7 @@ async function runTriageRun(args, options, io) {
855
855
  error(`triage: failed to write items: ${e instanceof Error ? e.message : String(e)}`);
856
856
  return 1;
857
857
  }
858
- log(`triage: applied ${allUpdated.length} decision(s)`);
858
+ log(t("cli.triage.applied", { count: allUpdated.length }));
859
859
  for (const row of rows)
860
860
  log(row);
861
861
  return 0;
@@ -874,34 +874,47 @@ async function runTriageFeedback(args, options) {
874
874
  const log = options.io?.log ?? ((m) => console.log(m));
875
875
  const error = options.io?.error ?? ((m) => console.error(m));
876
876
  const now = options.now ?? defaultNow;
877
+ let langState;
878
+ try {
879
+ langState = parseLangFlag(args);
880
+ }
881
+ catch (e) {
882
+ if (e instanceof LangFlagError) {
883
+ error(`triage feedback: ${e.message}`);
884
+ return 2;
885
+ }
886
+ throw e;
887
+ }
888
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
889
+ const t = createTranslator(locale);
877
890
  let parsed;
878
891
  try {
879
- parsed = parseTriageFeedbackArgs(args);
892
+ parsed = parseTriageFeedbackArgs(langState.rest);
880
893
  }
881
894
  catch (e) {
882
895
  error(`triage feedback: ${e instanceof Error ? e.message : String(e)}`);
883
896
  return 2;
884
897
  }
885
898
  if (parsed.help) {
886
- printFeedbackHelp(log);
899
+ printFeedbackHelp(t, log);
887
900
  return 0;
888
901
  }
889
902
  if (!parsed.itemId) {
890
- error("triage feedback: missing <item-id>");
891
- printFeedbackHelp(error);
903
+ error(t("cli.triage.feedbackMissingItemId"));
904
+ printFeedbackHelp(t, error);
892
905
  return 2;
893
906
  }
894
907
  if (parsed.correct && parsed.wrong) {
895
- error("triage feedback: --correct and --wrong are mutually exclusive");
908
+ error(t("cli.triage.feedbackModesExclusive"));
896
909
  return 2;
897
910
  }
898
911
  if (!parsed.correct && !parsed.wrong) {
899
- error("triage feedback: one of --correct | --wrong is required");
912
+ error(t("cli.triage.feedbackModeRequired"));
900
913
  return 2;
901
914
  }
902
915
  const itemsDir = join(cwd, "items");
903
916
  if (!(await pathExists(itemsDir))) {
904
- error(`triage feedback: items/ not found (run \`radar init\`)`);
917
+ error(t("cli.triage.feedbackItemsDirNotFound"));
905
918
  return 1;
906
919
  }
907
920
  let items;
@@ -914,11 +927,11 @@ async function runTriageFeedback(args, options) {
914
927
  }
915
928
  const item = items.find((i) => i.id === parsed.itemId);
916
929
  if (!item) {
917
- error(`triage feedback: item '${parsed.itemId}' not found under items/`);
930
+ error(t("cli.triage.feedbackItemNotFound", { id: parsed.itemId }));
918
931
  return 1;
919
932
  }
920
933
  if (!item.triage) {
921
- error(`triage feedback: item '${item.id}' has no prior triage decision to give feedback on`);
934
+ error(t("cli.triage.feedbackNoPriorDecision", { id: item.id }));
922
935
  return 1;
923
936
  }
924
937
  // Overwrite-semantic: replace the existing feedback array with a single
@@ -947,7 +960,7 @@ async function runTriageFeedback(args, options) {
947
960
  return 1;
948
961
  }
949
962
  const verdict = parsed.correct ? "correct" : "wrong";
950
- log(`triage feedback: items/${item.sourceId}/${item.id}.yaml feedback -> ${verdict}`);
963
+ log(t("cli.triage.feedbackRecorded", { sourceId: item.sourceId, id: item.id, verdict }));
951
964
  return 0;
952
965
  }
953
966
  function parseTriageStatsArgs(args) {
@@ -983,19 +996,8 @@ function parseTriageStatsArgs(args) {
983
996
  }
984
997
  return out;
985
998
  }
986
- function printStatsHelp(log) {
987
- log("Usage: radar triage stats [--since <duration>] [--source <id>] [--json]");
988
- log("");
989
- log("Aggregate triage decisions and human feedback (ADR-0018 §W5, #242).");
990
- log("Use after running `radar triage --apply` for some weeks; the output");
991
- log("highlights precision / recall drift and suggests `triagePolicy.rules:`");
992
- log("tweaks. See docs/user-guide.md `policy tuning workflow` for the");
993
- log("recommended monthly loop.");
994
- log("");
995
- log("Options:");
996
- log(" --since <duration> only count items triaged within the cutoff (e.g. 30d, 24h)");
997
- log(" --source <id> limit stats to a single source (default: all sources)");
998
- log(" --json emit machine-readable JSON instead of the text report");
999
+ function printStatsHelp(t, log) {
1000
+ log(t("cli.triage.statsHelp"));
999
1001
  }
1000
1002
  /**
1001
1003
  * Parse `Nd | Nh | Nm | Ns` into a `Date` cutoff. Returns `null` when the
@@ -1298,23 +1300,36 @@ async function runTriageStats(args, options, io) {
1298
1300
  const cwd = options.cwd ?? process.cwd();
1299
1301
  const nowFn = options.now ?? defaultNow;
1300
1302
  const now = new Date(nowFn());
1303
+ let langState;
1304
+ try {
1305
+ langState = parseLangFlag(args);
1306
+ }
1307
+ catch (e) {
1308
+ if (e instanceof LangFlagError) {
1309
+ error(`triage stats: ${e.message}`);
1310
+ return 2;
1311
+ }
1312
+ throw e;
1313
+ }
1314
+ const locale = await resolveWorkspaceLocale({ flag: langState.flag, cwd, warn: error });
1315
+ const t = createTranslator(locale);
1301
1316
  let parsed;
1302
1317
  try {
1303
- parsed = parseTriageStatsArgs(args);
1318
+ parsed = parseTriageStatsArgs(langState.rest);
1304
1319
  }
1305
1320
  catch (e) {
1306
1321
  error(`triage stats: ${e instanceof Error ? e.message : String(e)}`);
1307
1322
  return 2;
1308
1323
  }
1309
1324
  if (parsed.help) {
1310
- printStatsHelp(log);
1325
+ printStatsHelp(t, log);
1311
1326
  return 0;
1312
1327
  }
1313
1328
  let sinceCutoff = null;
1314
1329
  if (parsed.since) {
1315
1330
  sinceCutoff = parseSinceCutoffForStats(parsed.since, now);
1316
1331
  if (!sinceCutoff) {
1317
- error(`triage stats: invalid --since '${parsed.since}' (expected Ns | Nm | Nh | Nd)`);
1332
+ error(t("cli.triage.statsInvalidSince", { since: parsed.since }));
1318
1333
  return 2;
1319
1334
  }
1320
1335
  }
@@ -1328,7 +1343,7 @@ async function runTriageStats(args, options, io) {
1328
1343
  }, null, 2));
1329
1344
  }
1330
1345
  else {
1331
- log("triage stats: no items/ directory (run `radar init` first)");
1346
+ log(t("cli.triage.statsNoItemsDir"));
1332
1347
  }
1333
1348
  return 0;
1334
1349
  }
@@ -1382,7 +1397,7 @@ async function runTriageStats(args, options, io) {
1382
1397
  return 0;
1383
1398
  }
1384
1399
  if (perSource.length === 0) {
1385
- log("triage stats: no triaged items match the filter (nothing to report)");
1400
+ log(t("cli.triage.statsNoMatch"));
1386
1401
  return 0;
1387
1402
  }
1388
1403
  let first = true;
@@ -1409,7 +1424,8 @@ export const __test__ = {
1409
1424
  };
1410
1425
  export const triageCommand = {
1411
1426
  name: "triage",
1412
- summary: "LLM-based triage of detected items (ADR-0018)",
1427
+ summary: "LLM-based triage of detected items",
1428
+ summaryKey: "cli.summary.triage",
1413
1429
  run: (args) => runTriage(args),
1414
1430
  };
1415
1431
  //# sourceMappingURL=triage.js.map