@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.
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/dist/agents/_boundary.d.ts +44 -0
- package/dist/agents/_boundary.d.ts.map +1 -0
- package/dist/agents/_boundary.js +59 -0
- package/dist/agents/_boundary.js.map +1 -0
- package/dist/agents/claude-code.d.ts +32 -0
- package/dist/agents/claude-code.d.ts.map +1 -0
- package/dist/agents/claude-code.js +256 -0
- package/dist/agents/claude-code.js.map +1 -0
- package/dist/agents/codex-cli.d.ts +31 -0
- package/dist/agents/codex-cli.d.ts.map +1 -0
- package/dist/agents/codex-cli.js +303 -0
- package/dist/agents/codex-cli.js.map +1 -0
- package/dist/agents/copilot.d.ts +29 -0
- package/dist/agents/copilot.d.ts.map +1 -0
- package/dist/agents/copilot.js +282 -0
- package/dist/agents/copilot.js.map +1 -0
- package/dist/agents/gemini-cli.d.ts +30 -0
- package/dist/agents/gemini-cli.d.ts.map +1 -0
- package/dist/agents/gemini-cli.js +316 -0
- package/dist/agents/gemini-cli.js.map +1 -0
- package/dist/agents/index.d.ts +12 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +33 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/types.d.ts +103 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/claude-skills/dismiss/SKILL.md +41 -0
- package/dist/claude-skills/research/SKILL.md +45 -0
- package/dist/claude-skills/review/SKILL.md +45 -0
- package/dist/claude-skills/update/SKILL.md +49 -0
- package/dist/cli/dismiss.d.ts +28 -0
- package/dist/cli/dismiss.d.ts.map +1 -0
- package/dist/cli/dismiss.js +122 -0
- package/dist/cli/dismiss.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +148 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +578 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/research.d.ts +30 -0
- package/dist/cli/research.d.ts.map +1 -0
- package/dist/cli/research.js +313 -0
- package/dist/cli/research.js.map +1 -0
- package/dist/cli/review.d.ts +34 -0
- package/dist/cli/review.d.ts.map +1 -0
- package/dist/cli/review.js +418 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/source.d.ts +57 -0
- package/dist/cli/source.d.ts.map +1 -0
- package/dist/cli/source.js +511 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/cli/update.d.ts +43 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +429 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/watch.d.ts +22 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +101 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/core/config.d.ts +60 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +101 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/feeds/derive-id.d.ts +43 -0
- package/dist/core/feeds/derive-id.d.ts.map +1 -0
- package/dist/core/feeds/derive-id.js +66 -0
- package/dist/core/feeds/derive-id.js.map +1 -0
- package/dist/core/feeds/github-api.d.ts +69 -0
- package/dist/core/feeds/github-api.d.ts.map +1 -0
- package/dist/core/feeds/github-api.js +161 -0
- package/dist/core/feeds/github-api.js.map +1 -0
- package/dist/core/feeds/github-releases.d.ts +3 -0
- package/dist/core/feeds/github-releases.d.ts.map +1 -0
- package/dist/core/feeds/github-releases.js +85 -0
- package/dist/core/feeds/github-releases.js.map +1 -0
- package/dist/core/feeds/html.d.ts +10 -0
- package/dist/core/feeds/html.d.ts.map +1 -0
- package/dist/core/feeds/html.js +263 -0
- package/dist/core/feeds/html.js.map +1 -0
- package/dist/core/feeds/index.d.ts +5 -0
- package/dist/core/feeds/index.d.ts.map +1 -0
- package/dist/core/feeds/index.js +18 -0
- package/dist/core/feeds/index.js.map +1 -0
- package/dist/core/feeds/npm-registry.d.ts +36 -0
- package/dist/core/feeds/npm-registry.d.ts.map +1 -0
- package/dist/core/feeds/npm-registry.js +200 -0
- package/dist/core/feeds/npm-registry.js.map +1 -0
- package/dist/core/feeds/rss.d.ts +12 -0
- package/dist/core/feeds/rss.d.ts.map +1 -0
- package/dist/core/feeds/rss.js +222 -0
- package/dist/core/feeds/rss.js.map +1 -0
- package/dist/core/feeds/types.d.ts +45 -0
- package/dist/core/feeds/types.d.ts.map +1 -0
- package/dist/core/feeds/types.js +2 -0
- package/dist/core/feeds/types.js.map +1 -0
- package/dist/core/filter.d.ts +25 -0
- package/dist/core/filter.d.ts.map +1 -0
- package/dist/core/filter.js +123 -0
- package/dist/core/filter.js.map +1 -0
- package/dist/core/injection-detector.d.ts +57 -0
- package/dist/core/injection-detector.d.ts.map +1 -0
- package/dist/core/injection-detector.js +109 -0
- package/dist/core/injection-detector.js.map +1 -0
- package/dist/core/items.d.ts +20 -0
- package/dist/core/items.d.ts.map +1 -0
- package/dist/core/items.js +105 -0
- package/dist/core/items.js.map +1 -0
- package/dist/core/state.d.ts +12 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +42 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/templates.d.ts +21 -0
- package/dist/core/templates.d.ts.map +1 -0
- package/dist/core/templates.js +52 -0
- package/dist/core/templates.js.map +1 -0
- package/dist/core/watcher.d.ts +72 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +240 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/gemini-commands/dismiss.toml +2 -0
- package/dist/gemini-commands/research.toml +2 -0
- package/dist/gemini-commands/review.toml +2 -0
- package/dist/gemini-commands/update.toml +2 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas/config.d.ts +39 -0
- package/dist/schemas/config.d.ts.map +1 -0
- package/dist/schemas/config.js +23 -0
- package/dist/schemas/config.js.map +1 -0
- package/dist/schemas/index.d.ts +6 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +6 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/item.d.ts +38 -0
- package/dist/schemas/item.d.ts.map +1 -0
- package/dist/schemas/item.js +34 -0
- package/dist/schemas/item.js.map +1 -0
- package/dist/schemas/research.d.ts +82 -0
- package/dist/schemas/research.d.ts.map +1 -0
- package/dist/schemas/research.js +45 -0
- package/dist/schemas/research.js.map +1 -0
- package/dist/schemas/source.d.ts +139 -0
- package/dist/schemas/source.d.ts.map +1 -0
- package/dist/schemas/source.js +127 -0
- package/dist/schemas/source.js.map +1 -0
- package/dist/schemas/state.d.ts +19 -0
- package/dist/schemas/state.d.ts.map +1 -0
- package/dist/schemas/state.js +12 -0
- package/dist/schemas/state.js.map +1 -0
- package/dist/skills/research/SKILL.md +156 -0
- package/dist/skills/review/SKILL.md +173 -0
- package/dist/skills/update/SKILL.md +200 -0
- package/dist/templates/agents/AGENTS.md +161 -0
- package/dist/templates/claude/CLAUDE.md +5 -0
- package/dist/templates/default.md +16 -0
- package/dist/templates/feedradar.md +165 -0
- package/dist/templates/routines/watch-daily.md +42 -0
- package/dist/templates/workflows/watch.yaml +70 -0
- 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":"AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AAEtE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAG/E;;;;;GAKG;AACH,MAAM,aAAa,GAAG;IACpB,OAAO,EAAE;QACP,IAAI,EAAE;YACJ,KAAK,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAW;YAC5C,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;SACjD;KACF;CACF,CAAC;AAqBF,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YACjC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,YAAY,EAAE,CAAC;YACvB,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;YACpB,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,SAAS,CAAC,GAAwB;IACzC,GAAG,CAAC,mFAAmF,CAAC,CAAC;IACzF,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,GAAG,CAAC,YAAY,CAAC,CAAC;IAClB,GAAG,CAAC,gFAAgF,CAAC,CAAC;IACtF,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,GAAG,CAAC,UAAU,CAAC,CAAC;IAChB,GAAG,CACD,+FAA+F,CAChG,CAAC;IACF,GAAG,CAAC,yEAAyE,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,GAAG,CAAC,iEAAiE,CAAC,CAAC;IACvE,GAAG,CAAC,qEAAqE,CAAC,CAAC;IAC3E,GAAG,CAAC,wEAAwE,CAAC,CAAC;IAC9E,GAAG,CAAC,qEAAqE,CAAC,CAAC;AAC7E,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,CAAS;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,mBAAmB,CAAC,GAAW,EAAE,UAAkB;IAC1D,MAAM,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC7E,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,KAAK,CAAC,2BAA2B,UAAU,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,oBAAoB,CAAC,GAAW,EAAE,OAAiB;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAW,EAAE,CAAC;IACvB,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,IAAI,KAAK;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAiBD;;;;GAIG;AACH,KAAK,UAAU,eAAe,CAAC,QAAwB;IACrD,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACxE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAc,EACd,UAAgC,EAAE;IAElC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,IAAI,MAAkB,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,WAAW,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,SAAS,CAAC,GAAG,CAAC,CAAC;QACf,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QACvB,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACvC,SAAS,CAAC,KAAK,CAAC,CAAC;QACjB,OAAO,CAAC,CAAC;IACX,CAAC;IAED,iDAAiD;IACjD,4EAA4E;IAC5E,yEAAyE;IACzE,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,aAAkC,CAAC;IACvC,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACzB,KAAK,CACH,4BAA4B,MAAM,CAAC,KAAK,8DAA8D,CACvG,CAAC;YACF,OAAO,CAAC,CAAC;QACX,CAAC;QACD,aAAa,GAAG,WAAW,CAAC,IAAI,CAAC;IACnC,CAAC;IACD,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,KAAK,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;YAClC,KAAK,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;IACD,yEAAyE;IACzE,wEAAwE;IACxE,6BAA6B;IAE7B,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,IAAI,SAAS,CAAC;IAEhD,yBAAyB;IACzB,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,WAAW,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QACtC,KAAK,CAAC,oCAAoC,YAAY,EAAE,CAAC,CAAC;QAC1D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,iDAAiD;IACjD,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,yCAAyC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7F,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,cAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,cAAc,GAAG,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC;IAC5D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,wCAAwC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5F,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,SAAS,GAAG,yBAAyB,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACtE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAChF,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC3C,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC;IAE7B,2EAA2E;IAC3E,wEAAwE;IACxE,sCAAsC;IACtC,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC3D,KAAK,CACH,qBAAqB,KAAK,CAAC,EAAE,qCAAqC,KAAK,CAAC,UAAU,gBAAgB,KAAK,CAAC,UAAU,GAAG,CACtH,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,wBAAwB;IACxB,MAAM,WAAW,GAAG,MAAM,oBAAoB,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACnE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,KAAK,CACH,iDAAiD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,EAAE,EAAE,CACvG,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,KAAK,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7D,KAAK,CACH,WAAW,OAAO,CAAC,MAAM,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,EAAE,GAAG,CACxG,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC;IAC1E,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,KAAK,CACH,iFAAiF,YAAY;aAC1F,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;aACjC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChB,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAC1E,uEAAuE;IACvE,mDAAmD;IACnD,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;QACjC,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,IAAI,CACF,iBAAiB,MAAM,CAAC,EAAE,SAAS,MAAM,CAAC,cAAc,CAAC,MAAM,uBAAuB,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,gFAAgF,CACvM,CAAC;QACJ,CAAC;IACH,CAAC;IAED,iBAAiB;IACjB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC5C,IAAI,QAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,WAAW,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,gCAAgC;IAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,QAAQ,GAAmB;QAC/B,YAAY;QACZ,YAAY;QACZ,QAAQ;QACR,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;KAC1C,CAAC;IAEF,GAAG,CAAC,oBAAoB,KAAK,0BAA0B,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;IAEpE,kBAAkB;IAClB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,MAAM,CAAC;YACnB,KAAK;YACL,UAAU;YACV,YAAY,EAAE,QAAQ,CAAC,IAAI;YAC3B,YAAY;YACZ,mBAAmB,EAAE,KAAK;YAC1B,YAAY;YACZ,GAAG;SACJ,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,2BAA2B,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/E,uEAAuE;QACvE,0DAA0D;QAC1D,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;gBAC/B,KAAK,CAAC,sCAAsC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,KAAK,CACH,+DAA+D,YAAY,aAAa,CACzF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,uEAAuE,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,mDAAmD;IACnD,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CACH,uDAAuD,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACpG,CAAC;QACF,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,eAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,eAAe,GAAG,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC;IACzD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CACH,sDAAsD,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnG,CAAC;QACF,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,UAAU,GAAG,yBAAyB,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACxE,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACxB,KAAK,CAAC,4EAA4E,CAAC,CAAC;QACpF,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;YAC5C,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC;IAC/B,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC/B,KAAK,CAAC,yEAAyE,CAAC,CAAC;QACjF,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;QAChC,KAAK,CAAC,uCAAuC,MAAM,CAAC,UAAU,gBAAgB,KAAK,GAAG,CAAC,CAAC;QACxF,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,uEAAuE;IACvE,4EAA4E;IAC5E,mEAAmE;IACnE,yEAAyE;IACzE,iDAAiD;IACjD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,MAAM,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,OAAO,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3E,IAAI,MAAM,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,KAAK,OAAO,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;IAC1F,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU;QACxC,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,UAAU,OAAO,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS;QACtC,KAAK,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,SAAS,OAAO,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;IACtE,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACpF,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU;QACxC,KAAK,CAAC,IAAI,CAAC,eAAe,KAAK,CAAC,UAAU,OAAO,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;IACzE,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC,yDAAyD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnF,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,4EAA4E;IAC5E,sEAAsE;IACtE,0BAA0B;IAC1B,MAAM,YAAY,GAAW,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACpF,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,yCAAyC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7F,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;gBAC/B,KAAK,CAAC,sCAAsC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,KAAK,CACH,+DAA+D,YAAY,aAAa,CACzF,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,+DAA+D,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,GAAG,CAAC,mBAAmB,YAAY,eAAe,MAAM,CAAC,UAAU,eAAe,KAAK,EAAE,CAAC,CAAC;IAC3F,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,GAAG,CAAC,iBAAiB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,EAAE,0BAA0B,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAY;IACpC,IAAI,EAAE,QAAQ;IACd,OAAO,EAAE,mEAAmE;IAC5E,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC;CAC/B,CAAC"}
|
|
@@ -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"}
|