@ozzylabs/feedradar 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +12 -6
- package/README.md +11 -6
- package/dist/agents/claude-code.d.ts +12 -1
- package/dist/agents/claude-code.d.ts.map +1 -1
- package/dist/agents/claude-code.js +9 -5
- package/dist/agents/claude-code.js.map +1 -1
- package/dist/agents/codex-cli.d.ts +7 -1
- package/dist/agents/codex-cli.d.ts.map +1 -1
- package/dist/agents/codex-cli.js +9 -5
- package/dist/agents/codex-cli.js.map +1 -1
- package/dist/agents/copilot.d.ts +7 -1
- package/dist/agents/copilot.d.ts.map +1 -1
- package/dist/agents/copilot.js +9 -5
- package/dist/agents/copilot.js.map +1 -1
- package/dist/agents/gemini-cli.d.ts +7 -1
- package/dist/agents/gemini-cli.d.ts.map +1 -1
- package/dist/agents/gemini-cli.js +9 -5
- package/dist/agents/gemini-cli.js.map +1 -1
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/types.d.ts +33 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/cli/_progress.d.ts +138 -0
- package/dist/cli/_progress.d.ts.map +1 -0
- package/dist/cli/_progress.js +176 -0
- package/dist/cli/_progress.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/research.d.ts +18 -20
- package/dist/cli/research.d.ts.map +1 -1
- package/dist/cli/research.js +318 -203
- package/dist/cli/research.js.map +1 -1
- package/dist/cli/review.d.ts +7 -0
- package/dist/cli/review.d.ts.map +1 -1
- package/dist/cli/review.js +46 -1
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/source.d.ts +23 -2
- package/dist/cli/source.d.ts.map +1 -1
- package/dist/cli/source.js +428 -7
- package/dist/cli/source.js.map +1 -1
- package/dist/cli/update.d.ts +7 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +41 -1
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/watch.d.ts.map +1 -1
- package/dist/cli/watch.js +67 -3
- package/dist/cli/watch.js.map +1 -1
- package/dist/cli/workflow/generate-combined.d.ts +100 -0
- package/dist/cli/workflow/generate-combined.d.ts.map +1 -0
- package/dist/cli/workflow/generate-combined.js +387 -0
- package/dist/cli/workflow/generate-combined.js.map +1 -0
- package/dist/cli/workflow/generate-watch.d.ts +142 -0
- package/dist/cli/workflow/generate-watch.d.ts.map +1 -0
- package/dist/cli/workflow/generate-watch.js +338 -0
- package/dist/cli/workflow/generate-watch.js.map +1 -0
- package/dist/cli/workflow.d.ts +29 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +66 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/core/feeds/_fetch.d.ts +10 -0
- package/dist/core/feeds/_fetch.d.ts.map +1 -1
- package/dist/core/feeds/_fetch.js +182 -0
- package/dist/core/feeds/_fetch.js.map +1 -1
- package/dist/core/feeds/_jsonpath.d.ts +57 -0
- package/dist/core/feeds/_jsonpath.d.ts.map +1 -0
- package/dist/core/feeds/_jsonpath.js +207 -0
- package/dist/core/feeds/_jsonpath.js.map +1 -0
- package/dist/core/feeds/html-js.d.ts +8 -0
- package/dist/core/feeds/html-js.d.ts.map +1 -1
- package/dist/core/feeds/html-js.js +47 -1
- package/dist/core/feeds/html-js.js.map +1 -1
- package/dist/core/feeds/index.d.ts +1 -1
- package/dist/core/feeds/index.d.ts.map +1 -1
- package/dist/core/feeds/index.js +4 -0
- package/dist/core/feeds/index.js.map +1 -1
- package/dist/core/feeds/json-api.d.ts +29 -0
- package/dist/core/feeds/json-api.d.ts.map +1 -0
- package/dist/core/feeds/json-api.js +860 -0
- package/dist/core/feeds/json-api.js.map +1 -0
- package/dist/core/feeds/json-feed.d.ts +11 -0
- package/dist/core/feeds/json-feed.d.ts.map +1 -0
- package/dist/core/feeds/json-feed.js +242 -0
- package/dist/core/feeds/json-feed.js.map +1 -0
- package/dist/core/feeds/types.d.ts +123 -0
- package/dist/core/feeds/types.d.ts.map +1 -1
- package/dist/core/progress.d.ts +101 -0
- package/dist/core/progress.d.ts.map +1 -0
- package/dist/core/progress.js +212 -0
- package/dist/core/progress.js.map +1 -0
- package/dist/core/recipes.d.ts +138 -0
- package/dist/core/recipes.d.ts.map +1 -0
- package/dist/core/recipes.js +242 -0
- package/dist/core/recipes.js.map +1 -0
- package/dist/core/watcher.d.ts +61 -1
- package/dist/core/watcher.d.ts.map +1 -1
- package/dist/core/watcher.js +99 -2
- package/dist/core/watcher.js.map +1 -1
- package/dist/recipes/aws-whats-new.yaml +87 -0
- package/dist/recipes/dev-to.yaml +40 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/recipe.d.ts +127 -0
- package/dist/schemas/recipe.d.ts.map +1 -0
- package/dist/schemas/recipe.js +57 -0
- package/dist/schemas/recipe.js.map +1 -0
- package/dist/schemas/source.d.ts +222 -0
- package/dist/schemas/source.d.ts.map +1 -1
- package/dist/schemas/source.js +234 -0
- package/dist/schemas/source.js.map +1 -1
- package/dist/templates/agents/AGENTS.md +33 -3
- package/dist/templates/feedradar.md +23 -8
- package/dist/templates/workflows/combined.template.yaml.tmpl +110 -0
- package/dist/templates/workflows/watch.template.yaml.tmpl +103 -0
- package/package.json +1 -2
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { RESEARCH_BATCH_DEFAULT_MAX_ITEMS } from "../research.js";
|
|
5
|
+
import { SUPPORTED_AGENTS } from "./generate-watch.js";
|
|
6
|
+
/**
|
|
7
|
+
* `radar workflow generate combined` (#189 / ADR-0014).
|
|
8
|
+
*
|
|
9
|
+
* Emits a GitHub Actions workflow that chains `radar watch run` -> "skip on
|
|
10
|
+
* no-new-items" guard -> `radar research --batch` in a single job so the
|
|
11
|
+
* detection-to-research delay collapses to one cron tick. The `--max-items`
|
|
12
|
+
* hard cap is rendered as a YAML literal AND re-enforced by the CLI (ADR-0014
|
|
13
|
+
* D3a "二重防御"), so a runaway detection or a hand-edited workflow cannot
|
|
14
|
+
* blow the cap inside one invocation.
|
|
15
|
+
*
|
|
16
|
+
* The four supported `--agent` values map to ADR-0014 D5's API-key-only
|
|
17
|
+
* secrets table (never OAuth). The generator emits the correct `env:` block
|
|
18
|
+
* in both the watch and research steps and prints the secrets the user must
|
|
19
|
+
* register so they do not have to grep the YAML.
|
|
20
|
+
*/
|
|
21
|
+
/** Default cron expression — daily 00:00 UTC, matching ADR-0004 / watch template. */
|
|
22
|
+
const DEFAULT_CRON = "0 0 * * *";
|
|
23
|
+
/** Default output path relative to the workspace root (ADR-0014 D6). */
|
|
24
|
+
const DEFAULT_OUTPUT = join(".github", "workflows", "feedradar-combined.yaml");
|
|
25
|
+
/**
|
|
26
|
+
* Agent-specific `env:` blocks emitted into the generated workflow.
|
|
27
|
+
*
|
|
28
|
+
* Each block sits under ` env:` and is two-space-indented from the
|
|
29
|
+
* `name:` step header (i.e. 10 leading spaces per line). All four agents
|
|
30
|
+
* stay on API-key auth — never OAuth — per ADR-0014 D5.
|
|
31
|
+
*
|
|
32
|
+
* `claude-code` / `codex-cli` / `gemini-cli` also expose `GITHUB_TOKEN`
|
|
33
|
+
* so the github-releases adapter gets the 5000 req/h ceiling instead of 60.
|
|
34
|
+
* `copilot` reuses `secrets.GITHUB_TOKEN` natively (the Copilot CLI
|
|
35
|
+
* authenticates via the GH token).
|
|
36
|
+
*/
|
|
37
|
+
const AGENT_SECRETS_BLOCKS = {
|
|
38
|
+
"claude-code": [
|
|
39
|
+
" ANTHROPIC_API_KEY: $" + "{{ secrets.ANTHROPIC_API_KEY }}",
|
|
40
|
+
" GITHUB_TOKEN: $" + "{{ secrets.GITHUB_TOKEN }}",
|
|
41
|
+
].join("\n"),
|
|
42
|
+
"codex-cli": [
|
|
43
|
+
" OPENAI_API_KEY: $" + "{{ secrets.OPENAI_API_KEY }}",
|
|
44
|
+
" GITHUB_TOKEN: $" + "{{ secrets.GITHUB_TOKEN }}",
|
|
45
|
+
].join("\n"),
|
|
46
|
+
"gemini-cli": [
|
|
47
|
+
" GEMINI_API_KEY: $" + "{{ secrets.GEMINI_API_KEY }}",
|
|
48
|
+
" GITHUB_TOKEN: $" + "{{ secrets.GITHUB_TOKEN }}",
|
|
49
|
+
].join("\n"),
|
|
50
|
+
copilot: [" GITHUB_TOKEN: $" + "{{ secrets.GITHUB_TOKEN }}"].join("\n"),
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Human-readable list of secrets each agent requires the user to register
|
|
54
|
+
* under Settings -> Secrets and variables -> Actions. Emitted after a
|
|
55
|
+
* successful generation so users do not have to grep the YAML to find what
|
|
56
|
+
* to register.
|
|
57
|
+
*/
|
|
58
|
+
const AGENT_SECRET_NAMES = {
|
|
59
|
+
"claude-code": ["ANTHROPIC_API_KEY", "GITHUB_TOKEN (auto-provisioned)"],
|
|
60
|
+
"codex-cli": ["OPENAI_API_KEY", "GITHUB_TOKEN (auto-provisioned)"],
|
|
61
|
+
"gemini-cli": ["GEMINI_API_KEY", "GITHUB_TOKEN (auto-provisioned)"],
|
|
62
|
+
copilot: ["GITHUB_TOKEN (auto-provisioned)"],
|
|
63
|
+
};
|
|
64
|
+
async function pathExists(p) {
|
|
65
|
+
try {
|
|
66
|
+
await access(p);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Mirrors `resolveTemplatesRoot` in `generate-watch.ts` so both generators
|
|
75
|
+
* find the bundled templates dir under both compiled (`dist/`) and source
|
|
76
|
+
* (`src/`) layouts.
|
|
77
|
+
*/
|
|
78
|
+
async function resolveTemplatesRoot() {
|
|
79
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
80
|
+
return resolve(here, "..", "..", "templates");
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate a 5-field POSIX cron expression. Borrowed verbatim from
|
|
84
|
+
* `generate-watch.ts` `isValidCron` so both type generators apply the same
|
|
85
|
+
* shape check (structural only — range validation is GitHub Actions's job).
|
|
86
|
+
*/
|
|
87
|
+
export function isValidCron(expr) {
|
|
88
|
+
const trimmed = expr.trim();
|
|
89
|
+
if (trimmed.length === 0)
|
|
90
|
+
return false;
|
|
91
|
+
const fields = trimmed.split(/\s+/);
|
|
92
|
+
if (fields.length !== 5)
|
|
93
|
+
return false;
|
|
94
|
+
const tokenPattern = /^(?:\*|\d+(?:-\d+)?)(?:\/\d+)?$/;
|
|
95
|
+
for (const field of fields) {
|
|
96
|
+
if (field.length === 0)
|
|
97
|
+
return false;
|
|
98
|
+
const tokens = field.split(",");
|
|
99
|
+
for (const token of tokens) {
|
|
100
|
+
if (token.length === 0)
|
|
101
|
+
return false;
|
|
102
|
+
if (!tokenPattern.test(token))
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Validate that the requested `--output` path lands under
|
|
110
|
+
* `.github/workflows/`. Mirrors `isSafeWorkflowPath` in `generate-watch.ts`.
|
|
111
|
+
*/
|
|
112
|
+
export function isSafeWorkflowPath(outputPath, cwd) {
|
|
113
|
+
if (isAbsolute(outputPath)) {
|
|
114
|
+
const allowedDir = resolve(cwd, ".github", "workflows");
|
|
115
|
+
const resolved = resolve(outputPath);
|
|
116
|
+
const rel = relative(allowedDir, resolved);
|
|
117
|
+
if (rel.startsWith("..") || isAbsolute(rel))
|
|
118
|
+
return false;
|
|
119
|
+
return /\.(ya?ml)$/i.test(resolved);
|
|
120
|
+
}
|
|
121
|
+
const normalized = normalize(outputPath);
|
|
122
|
+
if (normalized.split(/[\\/]/).includes(".."))
|
|
123
|
+
return false;
|
|
124
|
+
const required = `${join(".github", "workflows")}/`;
|
|
125
|
+
const unixified = normalized.replace(/\\/g, "/");
|
|
126
|
+
if (!unixified.startsWith(required.replace(/\\/g, "/")))
|
|
127
|
+
return false;
|
|
128
|
+
return /\.(ya?ml)$/i.test(unixified);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Validate `--max-items` as a positive integer. Mirrors `parseMaxItems` in
|
|
132
|
+
* `src/cli/research.ts` so the same input is accepted at both layers (CLI ->
|
|
133
|
+
* workflow YAML -> `radar research --batch`).
|
|
134
|
+
*/
|
|
135
|
+
export function isValidMaxItems(raw) {
|
|
136
|
+
if (!/^[0-9]+$/.test(raw))
|
|
137
|
+
return false;
|
|
138
|
+
const n = Number.parseInt(raw, 10);
|
|
139
|
+
return Number.isFinite(n) && n > 0;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Render the `--filter-tags` literal as it should appear on the generated
|
|
143
|
+
* `radar research --batch` line.
|
|
144
|
+
*
|
|
145
|
+
* When the user omits the flag, returns an empty string so the resulting
|
|
146
|
+
* line collapses to `radar research --batch --status detected --max-items N
|
|
147
|
+
* --agent <id>` without a stray `--filter-tags`. When present, each tag is
|
|
148
|
+
* trimmed, lower-cased, and deduped (matching the CLI parser) and rendered
|
|
149
|
+
* with a leading space so the template's `--max-items {{maxItems}}{{filterTags}}`
|
|
150
|
+
* concatenation reads naturally.
|
|
151
|
+
*/
|
|
152
|
+
export function renderFilterTagsLiteral(raw) {
|
|
153
|
+
if (raw === undefined || raw.trim() === "")
|
|
154
|
+
return "";
|
|
155
|
+
const tags = [
|
|
156
|
+
...new Set(raw
|
|
157
|
+
.split(",")
|
|
158
|
+
.map((s) => s.trim().toLowerCase())
|
|
159
|
+
.filter((s) => s.length > 0)),
|
|
160
|
+
];
|
|
161
|
+
if (tags.length === 0)
|
|
162
|
+
return "";
|
|
163
|
+
return ` --filter-tags ${tags.join(",")}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Render the bundled template by substituting `{{cron}}` / `{{maxItems}}` /
|
|
167
|
+
* `{{filterTags}}` / `{{agent}}` / `{{secretsBlock}}` placeholders.
|
|
168
|
+
*
|
|
169
|
+
* Substitution is a literal `replaceAll` so each placeholder lands
|
|
170
|
+
* atomically in every site it appears (the template intentionally uses
|
|
171
|
+
* `{{secretsBlock}}` twice, in the watch and research steps).
|
|
172
|
+
*/
|
|
173
|
+
export function renderCombinedTemplate(template, values) {
|
|
174
|
+
return template
|
|
175
|
+
.replaceAll("{{cron}}", values.cron)
|
|
176
|
+
.replaceAll("{{maxItems}}", String(values.maxItems))
|
|
177
|
+
.replaceAll("{{filterTags}}", values.filterTagsLiteral)
|
|
178
|
+
.replaceAll("{{agent}}", values.agent)
|
|
179
|
+
.replaceAll("{{secretsBlock}}", values.secretsBlock);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Core implementation of `radar workflow generate combined`.
|
|
183
|
+
*
|
|
184
|
+
* Validates inputs, reads the bundled `combined.template.yaml.tmpl`,
|
|
185
|
+
* substitutes placeholders, and writes the result. The completion stdout
|
|
186
|
+
* lines tell the user exactly which secrets to register so they do not have
|
|
187
|
+
* to hunt through ADR-0014 D5 by hand. The trailing `warn()` line surfaces
|
|
188
|
+
* the CLI-layer hard-cap double-defense so the user does not assume
|
|
189
|
+
* editing the YAML alone will raise `--max-items`.
|
|
190
|
+
*/
|
|
191
|
+
export async function generateCombined(options) {
|
|
192
|
+
const { cwd, watchCron, output, agent, maxItems, filterTags, force } = options;
|
|
193
|
+
const log = options.io?.log ?? ((m) => console.log(m));
|
|
194
|
+
const warn = options.io?.warn ?? ((m) => console.warn(m));
|
|
195
|
+
if (!isValidCron(watchCron)) {
|
|
196
|
+
throw new Error(`invalid --watch-cron expression '${watchCron}' (expected 5-field POSIX cron, e.g. "0 0 * * *")`);
|
|
197
|
+
}
|
|
198
|
+
if (!isSafeWorkflowPath(output, cwd)) {
|
|
199
|
+
throw new Error(`invalid --output '${output}' (must be a relative path under .github/workflows/ ending in .yaml or .yml)`);
|
|
200
|
+
}
|
|
201
|
+
if (!Number.isInteger(maxItems) || maxItems <= 0) {
|
|
202
|
+
throw new Error(`invalid --max-items '${maxItems}' (must be a positive integer)`);
|
|
203
|
+
}
|
|
204
|
+
const templatesRoot = options.templatesRoot ?? (await resolveTemplatesRoot());
|
|
205
|
+
const templatePath = join(templatesRoot, "workflows", "combined.template.yaml.tmpl");
|
|
206
|
+
if (!(await pathExists(templatePath))) {
|
|
207
|
+
throw new Error(`bundled template not found: ${templatePath}`);
|
|
208
|
+
}
|
|
209
|
+
const template = await readFile(templatePath, "utf8");
|
|
210
|
+
// `renderFilterTagsLiteral` accepts the raw CLI string; `generateCombined`
|
|
211
|
+
// accepts a pre-parsed array (so test code can build one without going
|
|
212
|
+
// through CLI parsing). Re-render here so both entrypoints agree on the
|
|
213
|
+
// literal shape.
|
|
214
|
+
const filterTagsLiteral = filterTags.length === 0 ? "" : ` --filter-tags ${filterTags.join(",")}`;
|
|
215
|
+
const rendered = renderCombinedTemplate(template, {
|
|
216
|
+
cron: watchCron,
|
|
217
|
+
maxItems,
|
|
218
|
+
filterTagsLiteral,
|
|
219
|
+
agent,
|
|
220
|
+
secretsBlock: AGENT_SECRETS_BLOCKS[agent],
|
|
221
|
+
});
|
|
222
|
+
const destAbs = isAbsolute(output) ? output : join(cwd, output);
|
|
223
|
+
const destRel = isAbsolute(output) ? relative(cwd, output) : output;
|
|
224
|
+
if ((await pathExists(destAbs)) && !force) {
|
|
225
|
+
throw new Error(`output file already exists: ${destRel} (use --force to overwrite)`);
|
|
226
|
+
}
|
|
227
|
+
if ((await pathExists(destAbs)) && force) {
|
|
228
|
+
warn(`workflow generate combined: overwriting existing file ${destRel}`);
|
|
229
|
+
}
|
|
230
|
+
await mkdir(dirname(destAbs), { recursive: true });
|
|
231
|
+
await writeFile(destAbs, rendered, "utf8");
|
|
232
|
+
log(`workflow generate combined: wrote ${destRel}`);
|
|
233
|
+
log(` agent: ${agent}`);
|
|
234
|
+
log(` cron: ${watchCron}`);
|
|
235
|
+
log(` max-items: ${maxItems}`);
|
|
236
|
+
log(` filter-tags: ${filterTags.length === 0 ? "(none)" : filterTags.join(",")}`);
|
|
237
|
+
log("");
|
|
238
|
+
log("Required GitHub Actions secrets (Settings → Secrets and variables → Actions):");
|
|
239
|
+
for (const s of AGENT_SECRET_NAMES[agent]) {
|
|
240
|
+
log(` ${s}`);
|
|
241
|
+
}
|
|
242
|
+
// Surface the hard-cap double-defense so the user knows editing the YAML
|
|
243
|
+
// alone will not lift the CLI cap (ADR-0014 D3a tail).
|
|
244
|
+
warn("workflow generate combined: the --max-items cap is also enforced by `radar research --batch`; editing the YAML alone will not raise it");
|
|
245
|
+
return { outputPath: destRel, requiredSecrets: AGENT_SECRET_NAMES[agent] };
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Parse `workflow generate combined` flags.
|
|
249
|
+
*
|
|
250
|
+
* Throws on missing values, unknown flags, unsupported `--agent` choices,
|
|
251
|
+
* and malformed `--max-items` so the caller can surface validation errors
|
|
252
|
+
* before any IO happens.
|
|
253
|
+
*/
|
|
254
|
+
export function parseGenerateCombinedArgs(args) {
|
|
255
|
+
let watchCron = DEFAULT_CRON;
|
|
256
|
+
let output = DEFAULT_OUTPUT;
|
|
257
|
+
let agent = "claude-code";
|
|
258
|
+
let maxItems = RESEARCH_BATCH_DEFAULT_MAX_ITEMS;
|
|
259
|
+
let filterTags = [];
|
|
260
|
+
let force = false;
|
|
261
|
+
let help = false;
|
|
262
|
+
for (let i = 0; i < args.length; i++) {
|
|
263
|
+
const a = args[i];
|
|
264
|
+
if (a === "-h" || a === "--help") {
|
|
265
|
+
help = true;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (a === "--watch-cron") {
|
|
269
|
+
const value = args[++i];
|
|
270
|
+
if (value === undefined)
|
|
271
|
+
throw new Error(`option ${a} requires a value`);
|
|
272
|
+
watchCron = value;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (a === "--output") {
|
|
276
|
+
const value = args[++i];
|
|
277
|
+
if (value === undefined)
|
|
278
|
+
throw new Error(`option ${a} requires a value`);
|
|
279
|
+
output = value;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (a === "--agent") {
|
|
283
|
+
const value = args[++i];
|
|
284
|
+
if (value === undefined)
|
|
285
|
+
throw new Error(`option ${a} requires a value`);
|
|
286
|
+
if (!SUPPORTED_AGENTS.includes(value)) {
|
|
287
|
+
throw new Error(`option --agent expects one of: ${SUPPORTED_AGENTS.join(" | ")}, got '${value}'`);
|
|
288
|
+
}
|
|
289
|
+
agent = value;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (a === "--max-items") {
|
|
293
|
+
const value = args[++i];
|
|
294
|
+
if (value === undefined)
|
|
295
|
+
throw new Error(`option ${a} requires a value`);
|
|
296
|
+
if (!isValidMaxItems(value)) {
|
|
297
|
+
throw new Error(`option --max-items expects a positive integer, got '${value}'`);
|
|
298
|
+
}
|
|
299
|
+
maxItems = Number.parseInt(value, 10);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (a === "--filter-tags") {
|
|
303
|
+
const value = args[++i];
|
|
304
|
+
if (value === undefined)
|
|
305
|
+
throw new Error(`option ${a} requires a value`);
|
|
306
|
+
filterTags = [
|
|
307
|
+
...new Set(value
|
|
308
|
+
.split(",")
|
|
309
|
+
.map((s) => s.trim().toLowerCase())
|
|
310
|
+
.filter((s) => s.length > 0)),
|
|
311
|
+
];
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (a === "--force" || a === "-f") {
|
|
315
|
+
force = true;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (a?.startsWith("--") || a?.startsWith("-")) {
|
|
319
|
+
throw new Error(`unknown option: ${a}`);
|
|
320
|
+
}
|
|
321
|
+
throw new Error(`unexpected positional argument: ${a}`);
|
|
322
|
+
}
|
|
323
|
+
return { watchCron, output, agent, maxItems, filterTags, force, help };
|
|
324
|
+
}
|
|
325
|
+
export function printGenerateCombinedHelp(log) {
|
|
326
|
+
log("Usage: radar workflow generate combined [options]");
|
|
327
|
+
log("");
|
|
328
|
+
log("Generates a GitHub Actions workflow that chains `radar watch run` ->");
|
|
329
|
+
log("a no-new-items guard -> `radar research --batch` with hard-capped cost");
|
|
330
|
+
log("controls (ADR-0014 D2 / D3 / D4 / D5).");
|
|
331
|
+
log("");
|
|
332
|
+
log("Options:");
|
|
333
|
+
log(' --watch-cron <expression> 5-field cron expression (default: "0 0 * * *")');
|
|
334
|
+
log(" --output <path> Output file under .github/workflows/");
|
|
335
|
+
log(" (default: .github/workflows/feedradar-combined.yaml)");
|
|
336
|
+
log(" --agent <name> claude-code | codex-cli | gemini-cli | copilot (default: claude-code)");
|
|
337
|
+
log(` --max-items N Hard cap on auto-research per run (default: ${RESEARCH_BATCH_DEFAULT_MAX_ITEMS})`);
|
|
338
|
+
log(" --filter-tags <list> Comma-separated allow-list of matchedKeywords");
|
|
339
|
+
log(" (default: unset, matches every detected item)");
|
|
340
|
+
log(" --force, -f Overwrite existing output file");
|
|
341
|
+
log("");
|
|
342
|
+
log("Required secrets (Settings → Secrets and variables → Actions):");
|
|
343
|
+
log(" ANTHROPIC_API_KEY when --agent claude-code (default)");
|
|
344
|
+
log(" OPENAI_API_KEY when --agent codex-cli");
|
|
345
|
+
log(" GEMINI_API_KEY when --agent gemini-cli");
|
|
346
|
+
log(" GITHUB_TOKEN auto-provisioned for --agent copilot (no setup needed)");
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Entry point invoked by `runWorkflow` (in `src/cli/workflow.ts`) when the
|
|
350
|
+
* user types `radar workflow generate combined`. Translates parsed flags
|
|
351
|
+
* into `generateCombined` arguments and surfaces validation errors with the
|
|
352
|
+
* `workflow generate combined:` prefix to match the rest of the CLI.
|
|
353
|
+
*/
|
|
354
|
+
export async function runGenerateCombined(args, io = {}, cwd = process.cwd()) {
|
|
355
|
+
const log = io.log ?? ((m) => console.log(m));
|
|
356
|
+
const error = io.error ?? ((m) => console.error(m));
|
|
357
|
+
let parsed;
|
|
358
|
+
try {
|
|
359
|
+
parsed = parseGenerateCombinedArgs(args);
|
|
360
|
+
}
|
|
361
|
+
catch (e) {
|
|
362
|
+
error(`workflow generate combined: ${e instanceof Error ? e.message : String(e)}`);
|
|
363
|
+
return 2;
|
|
364
|
+
}
|
|
365
|
+
if (parsed.help) {
|
|
366
|
+
printGenerateCombinedHelp(log);
|
|
367
|
+
return 0;
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
await generateCombined({
|
|
371
|
+
cwd,
|
|
372
|
+
watchCron: parsed.watchCron,
|
|
373
|
+
output: parsed.output,
|
|
374
|
+
agent: parsed.agent,
|
|
375
|
+
maxItems: parsed.maxItems,
|
|
376
|
+
filterTags: parsed.filterTags,
|
|
377
|
+
force: parsed.force,
|
|
378
|
+
io,
|
|
379
|
+
});
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
error(`workflow generate combined: ${e instanceof Error ? e.message : String(e)}`);
|
|
384
|
+
return 1;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
//# sourceMappingURL=generate-combined.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-combined.js","sourceRoot":"","sources":["../../../src/cli/workflow/generate-combined.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sinks for the `workflow generate watch` command's user-facing output.
|
|
3
|
+
*
|
|
4
|
+
* The CLI binds these to `console.*` by default; tests inject capturing
|
|
5
|
+
* sinks so they can assert against printed lines without poking at stdio.
|
|
6
|
+
*/
|
|
7
|
+
export interface WorkflowIO {
|
|
8
|
+
log?: (message: string) => void;
|
|
9
|
+
warn?: (message: string) => void;
|
|
10
|
+
error?: (message: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Agents supported by `--agent`. The literal list mirrors ADR-0014 D5's
|
|
14
|
+
* auth-policy table: each agent maps to a single repo secret name and the
|
|
15
|
+
* generator writes that name into the rendered workflow's `env:` block.
|
|
16
|
+
*
|
|
17
|
+
* `copilot` is included for completeness, but uses the auto-provisioned
|
|
18
|
+
* `GITHUB_TOKEN` rather than a user-managed secret — see `agentEnvKey` below.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SUPPORTED_AGENTS: readonly ["claude-code", "codex-cli", "gemini-cli", "copilot"];
|
|
21
|
+
export type SupportedAgent = (typeof SUPPORTED_AGENTS)[number];
|
|
22
|
+
/**
|
|
23
|
+
* Map an agent identifier to its required workflow secret name (ADR-0014 D5).
|
|
24
|
+
*
|
|
25
|
+
* The generator writes the result into the rendered workflow as both the
|
|
26
|
+
* `env:` key and the `${{ secrets.<NAME> }}` reference, so the user only
|
|
27
|
+
* needs to register a single secret named after the table below.
|
|
28
|
+
*
|
|
29
|
+
* `copilot` uses the auto-provisioned `GITHUB_TOKEN`; no user action needed.
|
|
30
|
+
* The completion message still surfaces this so the user can confirm
|
|
31
|
+
* `permissions: contents: write` is enough (which it is — Copilot CLI rides
|
|
32
|
+
* the `GITHUB_TOKEN` granted to the job).
|
|
33
|
+
*/
|
|
34
|
+
export declare function agentEnvKey(agent: SupportedAgent): string;
|
|
35
|
+
/**
|
|
36
|
+
* Validate a 5-field POSIX cron expression.
|
|
37
|
+
*
|
|
38
|
+
* GitHub Actions accepts the standard 5-field crontab format (minute, hour,
|
|
39
|
+
* day-of-month, month, day-of-week). We do not aim for byte-perfect parity
|
|
40
|
+
* with the upstream cron parser (e.g. `@daily` aliases) — those are
|
|
41
|
+
* actively documented as unsupported by GitHub Actions, so rejecting them
|
|
42
|
+
* here protects the user from generating a workflow that silently never
|
|
43
|
+
* fires.
|
|
44
|
+
*
|
|
45
|
+
* The validator accepts each field as one of:
|
|
46
|
+
* - `*`
|
|
47
|
+
* - `<n>` or `<n>-<m>` (with optional `/<step>`)
|
|
48
|
+
* - `<a>,<b>,...` (comma list, each token validated independently)
|
|
49
|
+
* - star slash step (every N units, e.g. `0 *_/6 * * *` reading the
|
|
50
|
+
* underscore as a placeholder for the slash that would close this comment)
|
|
51
|
+
*
|
|
52
|
+
* Range bounds (e.g. month 1-12) are NOT enforced here; GitHub Actions
|
|
53
|
+
* rejects out-of-range expressions on workflow load. Keeping our check
|
|
54
|
+
* structural keeps the dep surface zero (no cron-parser library) without
|
|
55
|
+
* generating workflows that pass our check but fail GitHub's.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isValidCron(expr: string): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Validate that the requested `--output` path lands under
|
|
60
|
+
* `.github/workflows/` relative to the workspace root.
|
|
61
|
+
*
|
|
62
|
+
* Two attack/footgun classes are blocked:
|
|
63
|
+
*
|
|
64
|
+
* 1. **Traversal:** `../../etc/passwd` or `.github/workflows/../../foo`
|
|
65
|
+
* — `..` segments anywhere in the relative path push the file out of
|
|
66
|
+
* the allowed directory.
|
|
67
|
+
* 2. **Absolute paths:** `/etc/foo` — the workflow is meant to live in
|
|
68
|
+
* the workspace; an absolute path is almost certainly user error or
|
|
69
|
+
* malicious input.
|
|
70
|
+
*
|
|
71
|
+
* The check operates on the relative form of the resolved path so that
|
|
72
|
+
* `cwd` differences (test temp dirs vs `process.cwd()`) do not affect the
|
|
73
|
+
* verdict. We also require the file to end with `.yaml` or `.yml` so the
|
|
74
|
+
* GitHub Actions loader picks it up.
|
|
75
|
+
*/
|
|
76
|
+
export declare function isSafeWorkflowPath(outputPath: string, cwd: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Render the bundled template by substituting `{{cron}}` / `{{output}}` /
|
|
79
|
+
* `{{agentEnvKey}}` placeholders with the user-supplied values.
|
|
80
|
+
*
|
|
81
|
+
* Substitution is intentionally a literal `replace`, not a templating
|
|
82
|
+
* engine: the placeholders are simple tokens, and we don't want to expand
|
|
83
|
+
* arbitrary GitHub Actions expressions (`${{ ... }}`) that already live in
|
|
84
|
+
* the template body.
|
|
85
|
+
*
|
|
86
|
+
* Exported for unit testing — tests can drive the renderer in isolation
|
|
87
|
+
* without touching the file system.
|
|
88
|
+
*/
|
|
89
|
+
export declare function renderWatchTemplate(template: string, values: {
|
|
90
|
+
cron: string;
|
|
91
|
+
agentEnvKey: string;
|
|
92
|
+
}): string;
|
|
93
|
+
export interface GenerateWatchOptions {
|
|
94
|
+
cwd: string;
|
|
95
|
+
cron: string;
|
|
96
|
+
output: string;
|
|
97
|
+
agent: SupportedAgent;
|
|
98
|
+
force: boolean;
|
|
99
|
+
/** Test seam: override the templates root location. */
|
|
100
|
+
templatesRoot?: string;
|
|
101
|
+
io?: WorkflowIO;
|
|
102
|
+
}
|
|
103
|
+
export interface GenerateWatchResult {
|
|
104
|
+
/** Relative path (from `cwd`) of the file that was written. */
|
|
105
|
+
outputPath: string;
|
|
106
|
+
/** Name of the GitHub Actions secret the user must register. */
|
|
107
|
+
requiredSecret: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Core implementation of `radar workflow generate watch`.
|
|
111
|
+
*
|
|
112
|
+
* Validates the cron + output path, renders the template with the
|
|
113
|
+
* agent-appropriate secret name, and writes the result. The completion
|
|
114
|
+
* stdout lines (printed via `io.log`) tell the user exactly which secrets
|
|
115
|
+
* to register so they don't have to hunt through ADR-0014 D5 by hand.
|
|
116
|
+
*/
|
|
117
|
+
export declare function generateWatch(options: GenerateWatchOptions): Promise<GenerateWatchResult>;
|
|
118
|
+
interface ParsedFlags {
|
|
119
|
+
cron: string;
|
|
120
|
+
output: string;
|
|
121
|
+
agent: SupportedAgent;
|
|
122
|
+
force: boolean;
|
|
123
|
+
help: boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Parse `workflow generate watch` flags.
|
|
127
|
+
*
|
|
128
|
+
* Throws on flags that require a value but receive none, unknown flags, or
|
|
129
|
+
* unsupported `--agent` choices. Returning a structured object keeps `run`
|
|
130
|
+
* free of argument-parsing branches.
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseGenerateWatchArgs(args: string[]): ParsedFlags;
|
|
133
|
+
export declare function printGenerateWatchHelp(log: (m: string) => void): void;
|
|
134
|
+
/**
|
|
135
|
+
* Entry point invoked by `runWorkflow` (in `src/cli/workflow.ts`) when the
|
|
136
|
+
* user types `radar workflow generate watch`. Translates parsed flags into
|
|
137
|
+
* `generateWatch` arguments and surfaces validation errors with the
|
|
138
|
+
* `workflow generate watch:` prefix to match the rest of the CLI.
|
|
139
|
+
*/
|
|
140
|
+
export declare function runGenerateWatch(args: string[], io?: WorkflowIO, cwd?: string): Promise<number>;
|
|
141
|
+
export {};
|
|
142
|
+
//# sourceMappingURL=generate-watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-watch.d.ts","sourceRoot":"","sources":["../../../src/cli/workflow/generate-watch.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,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;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,gEAAiE,CAAC;AAC/F,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AA4B/D;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAWzD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAkBjD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAsB3E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC5C,MAAM,CAIR;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,EAAE,CAAC,EAAE,UAAU,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA2D/F;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CA+ClE;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAsBrE;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EAAE,EACd,EAAE,GAAE,UAAe,EACnB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA8BjB"}
|