@ozzylabs/feedradar 0.1.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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/dist/agents/_boundary.d.ts +44 -0
  4. package/dist/agents/_boundary.d.ts.map +1 -0
  5. package/dist/agents/_boundary.js +59 -0
  6. package/dist/agents/_boundary.js.map +1 -0
  7. package/dist/agents/claude-code.d.ts +32 -0
  8. package/dist/agents/claude-code.d.ts.map +1 -0
  9. package/dist/agents/claude-code.js +256 -0
  10. package/dist/agents/claude-code.js.map +1 -0
  11. package/dist/agents/codex-cli.d.ts +31 -0
  12. package/dist/agents/codex-cli.d.ts.map +1 -0
  13. package/dist/agents/codex-cli.js +303 -0
  14. package/dist/agents/codex-cli.js.map +1 -0
  15. package/dist/agents/copilot.d.ts +29 -0
  16. package/dist/agents/copilot.d.ts.map +1 -0
  17. package/dist/agents/copilot.js +282 -0
  18. package/dist/agents/copilot.js.map +1 -0
  19. package/dist/agents/gemini-cli.d.ts +30 -0
  20. package/dist/agents/gemini-cli.d.ts.map +1 -0
  21. package/dist/agents/gemini-cli.js +316 -0
  22. package/dist/agents/gemini-cli.js.map +1 -0
  23. package/dist/agents/index.d.ts +12 -0
  24. package/dist/agents/index.d.ts.map +1 -0
  25. package/dist/agents/index.js +33 -0
  26. package/dist/agents/index.js.map +1 -0
  27. package/dist/agents/types.d.ts +103 -0
  28. package/dist/agents/types.d.ts.map +1 -0
  29. package/dist/agents/types.js +2 -0
  30. package/dist/agents/types.js.map +1 -0
  31. package/dist/claude-skills/dismiss/SKILL.md +41 -0
  32. package/dist/claude-skills/research/SKILL.md +45 -0
  33. package/dist/claude-skills/review/SKILL.md +45 -0
  34. package/dist/claude-skills/update/SKILL.md +49 -0
  35. package/dist/cli/dismiss.d.ts +28 -0
  36. package/dist/cli/dismiss.d.ts.map +1 -0
  37. package/dist/cli/dismiss.js +122 -0
  38. package/dist/cli/dismiss.js.map +1 -0
  39. package/dist/cli/index.d.ts +7 -0
  40. package/dist/cli/index.d.ts.map +1 -0
  41. package/dist/cli/index.js +64 -0
  42. package/dist/cli/index.js.map +1 -0
  43. package/dist/cli/init.d.ts +148 -0
  44. package/dist/cli/init.d.ts.map +1 -0
  45. package/dist/cli/init.js +578 -0
  46. package/dist/cli/init.js.map +1 -0
  47. package/dist/cli/research.d.ts +30 -0
  48. package/dist/cli/research.d.ts.map +1 -0
  49. package/dist/cli/research.js +313 -0
  50. package/dist/cli/research.js.map +1 -0
  51. package/dist/cli/review.d.ts +34 -0
  52. package/dist/cli/review.d.ts.map +1 -0
  53. package/dist/cli/review.js +418 -0
  54. package/dist/cli/review.js.map +1 -0
  55. package/dist/cli/source.d.ts +57 -0
  56. package/dist/cli/source.d.ts.map +1 -0
  57. package/dist/cli/source.js +511 -0
  58. package/dist/cli/source.js.map +1 -0
  59. package/dist/cli/update.d.ts +43 -0
  60. package/dist/cli/update.d.ts.map +1 -0
  61. package/dist/cli/update.js +429 -0
  62. package/dist/cli/update.js.map +1 -0
  63. package/dist/cli/watch.d.ts +22 -0
  64. package/dist/cli/watch.d.ts.map +1 -0
  65. package/dist/cli/watch.js +101 -0
  66. package/dist/cli/watch.js.map +1 -0
  67. package/dist/core/config.d.ts +60 -0
  68. package/dist/core/config.d.ts.map +1 -0
  69. package/dist/core/config.js +101 -0
  70. package/dist/core/config.js.map +1 -0
  71. package/dist/core/feeds/derive-id.d.ts +43 -0
  72. package/dist/core/feeds/derive-id.d.ts.map +1 -0
  73. package/dist/core/feeds/derive-id.js +66 -0
  74. package/dist/core/feeds/derive-id.js.map +1 -0
  75. package/dist/core/feeds/github-api.d.ts +69 -0
  76. package/dist/core/feeds/github-api.d.ts.map +1 -0
  77. package/dist/core/feeds/github-api.js +161 -0
  78. package/dist/core/feeds/github-api.js.map +1 -0
  79. package/dist/core/feeds/github-releases.d.ts +3 -0
  80. package/dist/core/feeds/github-releases.d.ts.map +1 -0
  81. package/dist/core/feeds/github-releases.js +85 -0
  82. package/dist/core/feeds/github-releases.js.map +1 -0
  83. package/dist/core/feeds/html.d.ts +10 -0
  84. package/dist/core/feeds/html.d.ts.map +1 -0
  85. package/dist/core/feeds/html.js +263 -0
  86. package/dist/core/feeds/html.js.map +1 -0
  87. package/dist/core/feeds/index.d.ts +5 -0
  88. package/dist/core/feeds/index.d.ts.map +1 -0
  89. package/dist/core/feeds/index.js +18 -0
  90. package/dist/core/feeds/index.js.map +1 -0
  91. package/dist/core/feeds/npm-registry.d.ts +36 -0
  92. package/dist/core/feeds/npm-registry.d.ts.map +1 -0
  93. package/dist/core/feeds/npm-registry.js +200 -0
  94. package/dist/core/feeds/npm-registry.js.map +1 -0
  95. package/dist/core/feeds/rss.d.ts +12 -0
  96. package/dist/core/feeds/rss.d.ts.map +1 -0
  97. package/dist/core/feeds/rss.js +222 -0
  98. package/dist/core/feeds/rss.js.map +1 -0
  99. package/dist/core/feeds/types.d.ts +45 -0
  100. package/dist/core/feeds/types.d.ts.map +1 -0
  101. package/dist/core/feeds/types.js +2 -0
  102. package/dist/core/feeds/types.js.map +1 -0
  103. package/dist/core/filter.d.ts +25 -0
  104. package/dist/core/filter.d.ts.map +1 -0
  105. package/dist/core/filter.js +123 -0
  106. package/dist/core/filter.js.map +1 -0
  107. package/dist/core/injection-detector.d.ts +57 -0
  108. package/dist/core/injection-detector.d.ts.map +1 -0
  109. package/dist/core/injection-detector.js +109 -0
  110. package/dist/core/injection-detector.js.map +1 -0
  111. package/dist/core/items.d.ts +20 -0
  112. package/dist/core/items.d.ts.map +1 -0
  113. package/dist/core/items.js +105 -0
  114. package/dist/core/items.js.map +1 -0
  115. package/dist/core/state.d.ts +12 -0
  116. package/dist/core/state.d.ts.map +1 -0
  117. package/dist/core/state.js +42 -0
  118. package/dist/core/state.js.map +1 -0
  119. package/dist/core/templates.d.ts +21 -0
  120. package/dist/core/templates.d.ts.map +1 -0
  121. package/dist/core/templates.js +52 -0
  122. package/dist/core/templates.js.map +1 -0
  123. package/dist/core/watcher.d.ts +72 -0
  124. package/dist/core/watcher.d.ts.map +1 -0
  125. package/dist/core/watcher.js +240 -0
  126. package/dist/core/watcher.js.map +1 -0
  127. package/dist/gemini-commands/dismiss.toml +2 -0
  128. package/dist/gemini-commands/research.toml +2 -0
  129. package/dist/gemini-commands/review.toml +2 -0
  130. package/dist/gemini-commands/update.toml +2 -0
  131. package/dist/index.d.ts +3 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +8 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/schemas/config.d.ts +39 -0
  136. package/dist/schemas/config.d.ts.map +1 -0
  137. package/dist/schemas/config.js +23 -0
  138. package/dist/schemas/config.js.map +1 -0
  139. package/dist/schemas/index.d.ts +6 -0
  140. package/dist/schemas/index.d.ts.map +1 -0
  141. package/dist/schemas/index.js +6 -0
  142. package/dist/schemas/index.js.map +1 -0
  143. package/dist/schemas/item.d.ts +38 -0
  144. package/dist/schemas/item.d.ts.map +1 -0
  145. package/dist/schemas/item.js +34 -0
  146. package/dist/schemas/item.js.map +1 -0
  147. package/dist/schemas/research.d.ts +82 -0
  148. package/dist/schemas/research.d.ts.map +1 -0
  149. package/dist/schemas/research.js +45 -0
  150. package/dist/schemas/research.js.map +1 -0
  151. package/dist/schemas/source.d.ts +139 -0
  152. package/dist/schemas/source.d.ts.map +1 -0
  153. package/dist/schemas/source.js +127 -0
  154. package/dist/schemas/source.js.map +1 -0
  155. package/dist/schemas/state.d.ts +19 -0
  156. package/dist/schemas/state.d.ts.map +1 -0
  157. package/dist/schemas/state.js +12 -0
  158. package/dist/schemas/state.js.map +1 -0
  159. package/dist/skills/research/SKILL.md +156 -0
  160. package/dist/skills/review/SKILL.md +173 -0
  161. package/dist/skills/update/SKILL.md +200 -0
  162. package/dist/templates/agents/AGENTS.md +161 -0
  163. package/dist/templates/claude/CLAUDE.md +5 -0
  164. package/dist/templates/default.md +16 -0
  165. package/dist/templates/feedradar.md +165 -0
  166. package/dist/templates/routines/watch-daily.md +42 -0
  167. package/dist/templates/workflows/watch.yaml +70 -0
  168. package/package.json +73 -0
@@ -0,0 +1,418 @@
1
+ import { access, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import matter from "gray-matter";
4
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
5
+ import { getAgentAdapter } from "../agents/index.js";
6
+ import { getDefaultAgent, loadRadarConfig, RadarConfigError } from "../core/config.js";
7
+ import { loadItems, saveItems } from "../core/items.js";
8
+ import { loadTemplate } from "../core/templates.js";
9
+ import { AgentIdSchema, ResearchFrontmatterSchema } from "../schemas/index.js";
10
+ /**
11
+ * gray-matter defaults to js-yaml which auto-converts ISO 8601 strings to
12
+ * `Date` objects. That breaks `ResearchFrontmatterSchema`, which expects
13
+ * timestamps as strings. Swap in the `yaml` v2 engine for parity with the
14
+ * `research` command (see src/cli/research.ts for the same rationale).
15
+ */
16
+ const matterOptions = {
17
+ engines: {
18
+ yaml: {
19
+ parse: (s) => parseYaml(s),
20
+ stringify: (data) => stringifyYaml(data),
21
+ },
22
+ },
23
+ };
24
+ function parseArgs(args) {
25
+ const out = {};
26
+ for (let i = 0; i < args.length; i++) {
27
+ const a = args[i];
28
+ if (a === "-h" || a === "--help") {
29
+ out.help = true;
30
+ continue;
31
+ }
32
+ if (a === "--agent") {
33
+ out.agent = args[++i];
34
+ continue;
35
+ }
36
+ if (a === "--template") {
37
+ out.template = args[++i];
38
+ continue;
39
+ }
40
+ if (a?.startsWith("--")) {
41
+ throw new Error(`unknown option: ${a}`);
42
+ }
43
+ if (!out.researchId) {
44
+ out.researchId = a;
45
+ continue;
46
+ }
47
+ throw new Error(`unexpected positional argument: ${a}`);
48
+ }
49
+ return out;
50
+ }
51
+ function printHelp(log) {
52
+ log("Usage: radar review <research-id> [--agent <agent-id>] [--template <template-id>]");
53
+ log("");
54
+ log("Arguments:");
55
+ log(" <research-id> Research id (basename of research/<id>.md without .md)");
56
+ log("");
57
+ log("Options:");
58
+ log(" --agent <agent-id> claude-code | codex-cli | gemini-cli | copilot (default: claude-code)");
59
+ log(" --template <id> Template id under templates/ (default: default)");
60
+ log("");
61
+ log("Appends a review block to research/<research-id>.md, stamps the");
62
+ log("frontmatter `reviewedAt` / `reviewedBy`, and transitions the linked");
63
+ log("items/<id>.yaml `status` from `researched` to `reviewed`. Both updates");
64
+ log("happen atomically — a partial failure rolls back the research file.");
65
+ }
66
+ async function pathExists(p) {
67
+ try {
68
+ await access(p);
69
+ return true;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * Resolve `<research-id>` to the actual file on disk.
77
+ *
78
+ * The CLI accepts both `<id>` and `<id>.md` for ergonomics. We validate the
79
+ * resulting path stays inside `research/` so a malicious id like
80
+ * `../etc/passwd` cannot escape the workspace.
81
+ */
82
+ function resolveResearchPath(cwd, researchId) {
83
+ const id = researchId.endsWith(".md") ? researchId.slice(0, -3) : researchId;
84
+ if (id.includes("/") || id.includes("\\") || id.includes("..")) {
85
+ throw new Error(`invalid <research-id>: '${researchId}'`);
86
+ }
87
+ return join(cwd, "research", `${id}.md`);
88
+ }
89
+ /**
90
+ * Locate the items/<sourceId>/<itemId>.yaml files for a research file's
91
+ * `itemIds`. The research frontmatter records `itemIds` (zero or more) so we
92
+ * need to walk the items dir to find each one — items are stored under the
93
+ * source dir, and the CLI does not carry sourceId in the research frontmatter
94
+ * (ADR-0003 keeps the schema minimal).
95
+ */
96
+ async function findItemsForResearch(cwd, itemIds) {
97
+ const itemsDir = join(cwd, "items");
98
+ if (!(await pathExists(itemsDir)))
99
+ return [];
100
+ const allItems = await loadItems(itemsDir);
101
+ const out = [];
102
+ for (const id of itemIds) {
103
+ const match = allItems.find((i) => i.id === id);
104
+ if (match)
105
+ out.push(match);
106
+ }
107
+ return out;
108
+ }
109
+ /**
110
+ * Restore the snapshot. Best-effort — if a restore step itself fails, we
111
+ * surface the error to the caller so they can warn the user that manual
112
+ * recovery is required, but we still attempt the remaining restores.
113
+ */
114
+ async function restoreSnapshot(snapshot) {
115
+ const errors = [];
116
+ try {
117
+ await writeFile(snapshot.researchPath, snapshot.researchBody, "utf8");
118
+ }
119
+ catch (e) {
120
+ errors.push(e instanceof Error ? e : new Error(String(e)));
121
+ }
122
+ try {
123
+ await saveItems(snapshot.itemsDir, snapshot.items);
124
+ }
125
+ catch (e) {
126
+ errors.push(e instanceof Error ? e : new Error(String(e)));
127
+ }
128
+ return errors;
129
+ }
130
+ /**
131
+ * Implementation of `radar review <research-id>`.
132
+ *
133
+ * High-level flow (Phase 2):
134
+ * 1. Parse + validate args (agent defaults to `claude-code`, template to `default`).
135
+ * 2. Resolve `research/<research-id>.md` and parse its frontmatter.
136
+ * 3. Refuse to re-review (frontmatter already has `reviewedAt`).
137
+ * 4. Locate the linked items and verify their status is `researched`.
138
+ * 5. Load `templates/<template-id>.md` (empty body when default is absent).
139
+ * 6. Snapshot the research file body and item payloads (rollback target).
140
+ * 7. Invoke the adapter; the agent rewrites the research file in place
141
+ * (appends review block + stamps `reviewedAt` / `reviewedBy`).
142
+ * 8. Re-read the research file, validate the updated frontmatter.
143
+ * 9. Transition each linked item: `status` → `reviewed` and persist.
144
+ * If this step fails, restore the snapshot from (6) and exit non-zero.
145
+ *
146
+ * The snapshot/restore strategy gives us atomicity at the workspace level
147
+ * (ADR-0003 / ADR-0008): either both the research file and the linked items
148
+ * reflect the new review, or neither does.
149
+ */
150
+ export async function runReview(args, options = {}) {
151
+ const cwd = options.cwd ?? process.cwd();
152
+ const log = options.io?.log ?? ((m) => console.log(m));
153
+ const warn = options.io?.warn ?? ((m) => console.warn(m));
154
+ const error = options.io?.error ?? ((m) => console.error(m));
155
+ let parsed;
156
+ try {
157
+ parsed = parseArgs(args);
158
+ }
159
+ catch (e) {
160
+ error(`review: ${e instanceof Error ? e.message : String(e)}`);
161
+ return 2;
162
+ }
163
+ if (parsed.help) {
164
+ printHelp(log);
165
+ return 0;
166
+ }
167
+ if (!parsed.researchId) {
168
+ error("review: missing <research-id>");
169
+ printHelp(error);
170
+ return 2;
171
+ }
172
+ // Resolve the agent honoring the priority chain:
173
+ // explicit --agent > radar.config.yaml defaultReviewAgent > "claude-code"
174
+ // The explicit value is validated against AgentIdSchema first so a bogus
175
+ // --agent never reaches the config / fallback path. Mirrors runResearch's
176
+ // resolver so #29 (review) and #30 (radar.config) stay aligned.
177
+ let explicitAgent;
178
+ if (parsed.agent !== undefined) {
179
+ const agentResult = AgentIdSchema.safeParse(parsed.agent);
180
+ if (!agentResult.success) {
181
+ error(`review: invalid --agent '${parsed.agent}' (expected: claude-code | codex-cli | gemini-cli | copilot)`);
182
+ return 2;
183
+ }
184
+ explicitAgent = agentResult.data;
185
+ }
186
+ let agent;
187
+ try {
188
+ const config = await loadRadarConfig(cwd);
189
+ agent = await getDefaultAgent("review", { explicit: explicitAgent, configOverride: config });
190
+ }
191
+ catch (e) {
192
+ if (e instanceof RadarConfigError) {
193
+ error(`review: ${e.message}`);
194
+ return 2;
195
+ }
196
+ throw e;
197
+ }
198
+ // Phase 2 sub-issues B / C / D / E ship all four adapters. AgentIdSchema
199
+ // already rejected invalid `--agent` values upstream, so any value that
200
+ // reaches here is supported.
201
+ const templateId = parsed.template ?? "default";
202
+ // Resolve research path.
203
+ let researchPath;
204
+ try {
205
+ researchPath = resolveResearchPath(cwd, parsed.researchId);
206
+ }
207
+ catch (e) {
208
+ error(`review: ${e instanceof Error ? e.message : String(e)}`);
209
+ return 2;
210
+ }
211
+ if (!(await pathExists(researchPath))) {
212
+ error(`review: research file not found: ${researchPath}`);
213
+ return 1;
214
+ }
215
+ // Parse research frontmatter (pre-review state).
216
+ let researchBody;
217
+ try {
218
+ researchBody = await readFile(researchPath, "utf8");
219
+ }
220
+ catch (e) {
221
+ error(`review: failed to read research file: ${e instanceof Error ? e.message : String(e)}`);
222
+ return 1;
223
+ }
224
+ let preFrontmatter;
225
+ try {
226
+ preFrontmatter = matter(researchBody, matterOptions).data;
227
+ }
228
+ catch (e) {
229
+ error(`review: failed to parse frontmatter: ${e instanceof Error ? e.message : String(e)}`);
230
+ return 1;
231
+ }
232
+ const preResult = ResearchFrontmatterSchema.safeParse(preFrontmatter);
233
+ if (!preResult.success) {
234
+ error(`review: research frontmatter does not match ResearchFrontmatterSchema:`);
235
+ for (const issue of preResult.error.issues) {
236
+ error(` - ${issue.path.join(".") || "<root>"}: ${issue.message}`);
237
+ }
238
+ return 1;
239
+ }
240
+ const preFm = preResult.data;
241
+ // Refuse re-review. ADR-0008 leaves "re-review after update" undefined and
242
+ // we explicitly block the idempotent case here so users do not silently
243
+ // overwrite an existing review stamp.
244
+ if (preFm.reviewedAt !== null || preFm.reviewedBy !== null) {
245
+ error(`review: research '${preFm.id}' is already reviewed (reviewedAt=${preFm.reviewedAt}, reviewedBy=${preFm.reviewedBy})`);
246
+ return 1;
247
+ }
248
+ // Resolve linked items.
249
+ const linkedItems = await findItemsForResearch(cwd, preFm.itemIds);
250
+ if (linkedItems.length === 0) {
251
+ error(`review: no items/<id>.yaml found for itemIds=[${preFm.itemIds.join(", ")}] referenced by ${preFm.id}`);
252
+ return 1;
253
+ }
254
+ if (linkedItems.length !== preFm.itemIds.length) {
255
+ const found = new Set(linkedItems.map((i) => i.id));
256
+ const missing = preFm.itemIds.filter((id) => !found.has(id));
257
+ error(`review: ${missing.length} linked item(s) not found: ${missing.join(", ")} (referenced by ${preFm.id})`);
258
+ return 1;
259
+ }
260
+ const unresearched = linkedItems.filter((i) => i.status !== "researched");
261
+ if (unresearched.length > 0) {
262
+ error(`review: linked items must be in status 'researched' before review. Offenders: ${unresearched
263
+ .map((i) => `${i.id}=${i.status}`)
264
+ .join(", ")}`);
265
+ return 1;
266
+ }
267
+ // Surface any prompt-injection pre-filter hits recorded by the watcher
268
+ // (ADR-0009 M1a / M5a — Adopt). Audit-only: review still proceeds against
269
+ // the original research file, but the user sees the audit trail before
270
+ // tokens are spent on the cross-agent review pass.
271
+ for (const linked of linkedItems) {
272
+ if (linked.injectionFlags.length > 0) {
273
+ warn(`review: item '${linked.id}' has ${linked.injectionFlags.length} injection flag(s): ${linked.injectionFlags.join(", ")} (audit-only; the linked research was generated from possibly tainted content)`);
274
+ }
275
+ }
276
+ // Load template.
277
+ const templatesDir = join(cwd, "templates");
278
+ let template;
279
+ try {
280
+ template = await loadTemplate(templateId, templatesDir);
281
+ }
282
+ catch (e) {
283
+ error(`review: ${e instanceof Error ? e.message : String(e)}`);
284
+ return 1;
285
+ }
286
+ // Snapshot for atomic rollback.
287
+ const itemsDir = join(cwd, "items");
288
+ const snapshot = {
289
+ researchPath,
290
+ researchBody,
291
+ itemsDir,
292
+ items: linkedItems.map((i) => ({ ...i })),
293
+ };
294
+ log(`review: invoking ${agent} adapter for research '${preFm.id}'`);
295
+ // Invoke adapter.
296
+ const adapter = getAgentAdapter(agent);
297
+ try {
298
+ await adapter.review({
299
+ agent,
300
+ templateId,
301
+ templateBody: template.body,
302
+ researchPath,
303
+ researchFrontmatter: preFm,
304
+ researchBody,
305
+ cwd,
306
+ });
307
+ }
308
+ catch (e) {
309
+ error(`review: adapter failed: ${e instanceof Error ? e.message : String(e)}`);
310
+ // The agent may have partially written to researchPath before failing.
311
+ // Restore the snapshot so the workspace stays consistent.
312
+ const restoreErrors = await restoreSnapshot(snapshot);
313
+ if (restoreErrors.length > 0) {
314
+ for (const re of restoreErrors) {
315
+ error(`review: rollback partially failed: ${re.message}`);
316
+ }
317
+ error(`review: workspace may be in an inconsistent state — inspect ${researchPath} and items/`);
318
+ }
319
+ else {
320
+ warn("review: rolled back research file and item status to pre-review state");
321
+ }
322
+ return 1;
323
+ }
324
+ // Re-read and validate the modified research file.
325
+ let postBody;
326
+ try {
327
+ postBody = await readFile(researchPath, "utf8");
328
+ }
329
+ catch (e) {
330
+ error(`review: failed to read research file after adapter: ${e instanceof Error ? e.message : String(e)}`);
331
+ await restoreSnapshot(snapshot);
332
+ return 1;
333
+ }
334
+ let postFrontmatter;
335
+ try {
336
+ postFrontmatter = matter(postBody, matterOptions).data;
337
+ }
338
+ catch (e) {
339
+ error(`review: failed to parse frontmatter after adapter: ${e instanceof Error ? e.message : String(e)}`);
340
+ await restoreSnapshot(snapshot);
341
+ return 1;
342
+ }
343
+ const postResult = ResearchFrontmatterSchema.safeParse(postFrontmatter);
344
+ if (!postResult.success) {
345
+ error(`review: post-adapter frontmatter does not match ResearchFrontmatterSchema:`);
346
+ for (const issue of postResult.error.issues) {
347
+ error(` - ${issue.path.join(".") || "<root>"}: ${issue.message}`);
348
+ }
349
+ await restoreSnapshot(snapshot);
350
+ return 1;
351
+ }
352
+ const postFm = postResult.data;
353
+ if (postFm.reviewedAt === null) {
354
+ error("review: adapter completed but did not stamp `reviewedAt` in frontmatter");
355
+ await restoreSnapshot(snapshot);
356
+ return 1;
357
+ }
358
+ if (postFm.reviewedBy !== agent) {
359
+ error(`review: adapter stamped reviewedBy='${postFm.reviewedBy}', expected '${agent}'`);
360
+ await restoreSnapshot(snapshot);
361
+ return 1;
362
+ }
363
+ // Other immutable frontmatter fields must remain unchanged. Catching a
364
+ // misbehaving agent that rewrote `id` / `itemIds` / `agent` / `createdAt` /
365
+ // `supersedes` here is cheaper than letting the inconsistency leak
366
+ // downstream. `supersedes` is owned by `research` (v1=null) and `update`
367
+ // (v+1=<prev id>); `review` must never touch it.
368
+ const drift = [];
369
+ if (postFm.id !== preFm.id)
370
+ drift.push(`id (${preFm.id} -> ${postFm.id})`);
371
+ if (postFm.agent !== preFm.agent)
372
+ drift.push(`agent (${preFm.agent} -> ${postFm.agent})`);
373
+ if (postFm.templateId !== preFm.templateId)
374
+ drift.push(`templateId (${preFm.templateId} -> ${postFm.templateId})`);
375
+ if (postFm.createdAt !== preFm.createdAt)
376
+ drift.push(`createdAt (${preFm.createdAt} -> ${postFm.createdAt})`);
377
+ if (JSON.stringify(postFm.itemIds) !== JSON.stringify(preFm.itemIds))
378
+ drift.push(`itemIds (${preFm.itemIds.join(",")} -> ${postFm.itemIds.join(",")})`);
379
+ if (postFm.supersedes !== preFm.supersedes)
380
+ drift.push(`supersedes (${preFm.supersedes} -> ${postFm.supersedes})`);
381
+ if (drift.length > 0) {
382
+ error(`review: adapter mutated immutable frontmatter fields: ${drift.join("; ")}`);
383
+ await restoreSnapshot(snapshot);
384
+ return 1;
385
+ }
386
+ // Now transition item status. If this fails (filesystem error, permissions,
387
+ // etc.), restore both the research file and the items snapshot so the
388
+ // workspace stays atomic.
389
+ const updatedItems = linkedItems.map((i) => ({ ...i, status: "reviewed" }));
390
+ try {
391
+ await saveItems(itemsDir, updatedItems);
392
+ }
393
+ catch (e) {
394
+ error(`review: failed to update item status: ${e instanceof Error ? e.message : String(e)}`);
395
+ const restoreErrors = await restoreSnapshot(snapshot);
396
+ if (restoreErrors.length > 0) {
397
+ for (const re of restoreErrors) {
398
+ error(`review: rollback partially failed: ${re.message}`);
399
+ }
400
+ error(`review: workspace may be in an inconsistent state — inspect ${researchPath} and items/`);
401
+ }
402
+ else {
403
+ warn("review: rolled back research file (item status update failed)");
404
+ }
405
+ return 1;
406
+ }
407
+ log(`review: stamped ${researchPath} reviewedAt=${postFm.reviewedAt} reviewedBy=${agent}`);
408
+ for (const item of updatedItems) {
409
+ log(`review: items/${item.sourceId}/${item.id}.yaml status -> reviewed`);
410
+ }
411
+ return 0;
412
+ }
413
+ export const reviewCommand = {
414
+ name: "review",
415
+ summary: "Cross-review existing research reports using a different AI agent",
416
+ run: (args) => runReview(args),
417
+ };
418
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/cli/review.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Command } from "./index.js";
2
+ /**
3
+ * Sinks for the source command's user-facing output.
4
+ *
5
+ * The CLI binds these to `console.*` by default; tests inject capturing sinks
6
+ * to assert against printed lines without poking at stdio.
7
+ *
8
+ * `warn` is separate from `error` so non-fatal hints (e.g. "no keywords
9
+ * configured — items will be filtered out") still surface on stderr without
10
+ * affecting the exit code or being treated as a hard failure by callers.
11
+ */
12
+ export interface SourceIO {
13
+ log?: (message: string) => void;
14
+ warn?: (message: string) => void;
15
+ error?: (message: string) => void;
16
+ }
17
+ /** Options accepted across all `source` subcommands. */
18
+ export interface SourceCommandOptions {
19
+ /** Workspace root (defaults to `process.cwd()` for the real CLI). */
20
+ cwd?: string;
21
+ io?: SourceIO;
22
+ }
23
+ /**
24
+ * Implementation of `source add`.
25
+ *
26
+ * Builds a Source object from CLI flags, validates it with the Zod schema, and
27
+ * writes `sources/<id>.yaml`. Refuses to overwrite an existing file (issue #12
28
+ * explicitly forbids `--force`-style escape hatches; YAML edits remain the
29
+ * supported workflow for mutation).
30
+ */
31
+ export declare function addSource(args: string[], options?: SourceCommandOptions): Promise<number>;
32
+ /**
33
+ * Implementation of `source list`.
34
+ *
35
+ * Renders a fixed-width table (id / kind / url / tags). When no sources exist
36
+ * we print a guidance message instead of an empty table so first-time users
37
+ * are nudged toward `source add`.
38
+ */
39
+ export declare function listSources(args: string[], options?: SourceCommandOptions): Promise<number>;
40
+ /**
41
+ * Implementation of `source remove`.
42
+ *
43
+ * Deletes `sources/<id>.yaml` only. Per user-guide.md and issue #12, this
44
+ * intentionally preserves `state/<id>.yaml` and any `items/` history so users
45
+ * never lose recorded research by toggling a source off.
46
+ */
47
+ export declare function removeSource(args: string[], options?: SourceCommandOptions): Promise<number>;
48
+ /**
49
+ * Top-level dispatcher for `radar source <subcommand>`.
50
+ *
51
+ * Sub-commands are kept as named functions (addSource/listSources/removeSource)
52
+ * so tests can call them directly with injected IO sinks without spawning the
53
+ * full CLI.
54
+ */
55
+ export declare function runSource(args: string[], options?: SourceCommandOptions): Promise<number>;
56
+ export declare const sourceCommand: Command;
57
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/cli/source.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,QAAQ,CAAC;CACf;AA4ND;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAoHjB;AA6CD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CA2FjB;AA4BD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAmCjB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC,CAqBjB;AAED,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC"}