clud-bug 0.6.34 → 0.7.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/clud-bug.js +10 -1353
- package/dist/cli/agents-md.d.ts +16 -0
- package/dist/cli/agents-md.d.ts.map +1 -0
- package/dist/cli/agents-md.js +226 -0
- package/dist/cli/agents-md.js.map +1 -0
- package/dist/cli/audit.d.ts +13 -0
- package/dist/cli/audit.d.ts.map +1 -0
- package/dist/cli/audit.js +90 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/branch-protection.d.ts +57 -0
- package/dist/cli/branch-protection.d.ts.map +1 -0
- package/dist/cli/branch-protection.js +118 -0
- package/dist/cli/branch-protection.js.map +1 -0
- package/dist/cli/edit-workflow.d.ts +18 -0
- package/dist/cli/edit-workflow.d.ts.map +1 -0
- package/dist/cli/edit-workflow.js +43 -0
- package/dist/cli/edit-workflow.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +1336 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/skill-usage.d.ts +109 -0
- package/dist/cli/skill-usage.d.ts.map +1 -0
- package/dist/cli/skill-usage.js +380 -0
- package/dist/cli/skill-usage.js.map +1 -0
- package/dist/cli/skills.d.ts +56 -0
- package/dist/cli/skills.d.ts.map +1 -0
- package/dist/cli/skills.js +292 -0
- package/dist/cli/skills.js.map +1 -0
- package/dist/cli/update.d.ts +29 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +186 -0
- package/dist/cli/update.js.map +1 -0
- package/dist/cli/usage.d.ts +142 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +348 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/core/audit.d.ts +8 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +47 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/detect.d.ts +77 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +262 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +14 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/prompts.d.ts +9 -0
- package/dist/core/prompts.d.ts.map +1 -0
- package/dist/core/prompts.js +401 -0
- package/dist/core/prompts.js.map +1 -0
- package/dist/core/render-review.d.ts +6 -0
- package/dist/core/render-review.d.ts.map +1 -0
- package/dist/core/render-review.js +219 -0
- package/dist/core/render-review.js.map +1 -0
- package/dist/core/render.d.ts +13 -0
- package/dist/core/render.d.ts.map +1 -0
- package/dist/core/render.js +80 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/review-schema.d.ts +42 -0
- package/dist/core/review-schema.d.ts.map +1 -0
- package/dist/core/review-schema.js +156 -0
- package/dist/core/review-schema.js.map +1 -0
- package/dist/core/skills.d.ts +80 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +510 -0
- package/dist/core/skills.js.map +1 -0
- package/package.json +27 -4
- package/{lib/agents-md.js → src/cli/agents-md.ts} +25 -14
- package/{lib/audit.js → src/cli/audit.ts} +37 -44
- package/{lib/branch-protection.js → src/cli/branch-protection.ts} +75 -11
- package/{lib/edit-workflow.js → src/cli/edit-workflow.ts} +32 -11
- package/src/cli/index.ts +101 -0
- package/src/cli/main.ts +1376 -0
- package/{lib/skill-usage.js → src/cli/skill-usage.ts} +168 -94
- package/src/cli/skills.ts +386 -0
- package/{lib/update.js → src/cli/update.ts} +68 -27
- package/{lib/usage.js → src/cli/usage.ts} +167 -76
- package/src/core/audit.ts +53 -0
- package/{lib/detect.js → src/core/detect.ts} +100 -47
- package/src/core/index.ts +70 -0
- package/{lib/prompts.js → src/core/prompts.ts} +16 -2
- package/{lib/render-review.js → src/core/render-review.ts} +57 -25
- package/{lib/render.js → src/core/render.ts} +36 -10
- package/{lib/review-schema.js → src/core/review-schema.ts} +68 -5
- package/{lib/skills.js → src/core/skills.ts} +172 -343
- package/templates/workflow-py.yml.tmpl +2 -2
- package/templates/workflow-ts.yml.tmpl +2 -2
- package/templates/workflow.yml.tmpl +17 -8
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface RenderBlockOptions {
|
|
2
|
+
version?: string | undefined;
|
|
3
|
+
strictMode?: boolean | undefined;
|
|
4
|
+
skillRelPath?: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function renderBlock({ version, strictMode, skillRelPath }?: RenderBlockOptions): string;
|
|
7
|
+
export declare function detectSkillRelPath(cwd: string): Promise<string>;
|
|
8
|
+
export declare function upsertBlock(content: string, block: string): string;
|
|
9
|
+
export declare function hasAgentsMdImport(content: unknown): boolean;
|
|
10
|
+
export declare function removeBlock(content: string): string;
|
|
11
|
+
export interface ApplyToRepoResult {
|
|
12
|
+
touched: string[];
|
|
13
|
+
created: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function applyToRepo(cwd: string, blockOpts?: RenderBlockOptions): Promise<ApplyToRepoResult>;
|
|
16
|
+
//# sourceMappingURL=agents-md.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../../src/cli/agents-md.ts"],"names":[],"mappings":"AAiCA,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAuBD,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,GAAE,kBAAuB,GAAG,MAAM,CAyBlG;AAQD,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrE;AAID,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAWlE;AAQD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAG3D;AASD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASnD;AAMD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAQD,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkD7G"}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { readFile, writeFile, stat, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
// Manages a clud-bug-owned section inside AGENTS.md / CLAUDE.md and adjacent
|
|
4
|
+
// agent-instruction files. Mirrors the well-established `<!-- logmind-start -->`
|
|
5
|
+
// pattern: a marked block that other agents can read for collaboration rules,
|
|
6
|
+
// idempotently rewritten on each `clud-bug init` so the content stays current.
|
|
7
|
+
const START_MARKER = '<!-- clud-bug-start -->';
|
|
8
|
+
const END_MARKER = '<!-- clud-bug-end -->';
|
|
9
|
+
// v2 (clud-bug v0.6.6+): trimmed from ~44 lines to ~10. Full collaboration
|
|
10
|
+
// rules moved to the bundled `clud-bug-collaboration` skill (always installed
|
|
11
|
+
// alongside clud-bug), which clud-bug-review loads at review time. AGENTS.md
|
|
12
|
+
// becomes a pointer + the strict-mode toggle (repo-specific, varies per
|
|
13
|
+
// consumer). Compounds across every agent session in every consuming repo.
|
|
14
|
+
const BLOCK_VERSION = 'v2';
|
|
15
|
+
// Files we'll touch when present, plus files we'll create if missing.
|
|
16
|
+
// AGENTS.md is the cross-tool canonical (logmind made it canonical too).
|
|
17
|
+
// CLAUDE.md, GEMINI.md, .cursorrules, .windsurfrules etc. are tool-specific
|
|
18
|
+
// stubs/instructions; we append the same block where they exist but don't
|
|
19
|
+
// create them (logmind already creates the ones it knows about).
|
|
20
|
+
const ALWAYS_TOUCH = ['AGENTS.md']; // create if missing
|
|
21
|
+
const TOUCH_IF_PRESENT = [
|
|
22
|
+
'CLAUDE.md',
|
|
23
|
+
'GEMINI.md',
|
|
24
|
+
'.github/copilot-instructions.md',
|
|
25
|
+
'.cursorrules',
|
|
26
|
+
'.windsurfrules',
|
|
27
|
+
'.clinerules',
|
|
28
|
+
'.continuerules',
|
|
29
|
+
];
|
|
30
|
+
// Render the clud-bug block. Bundled here rather than in a template file so
|
|
31
|
+
// updates ship with the CLI itself.
|
|
32
|
+
//
|
|
33
|
+
// `strictMode` MUST match the workflow's gate predicate exactly so the block
|
|
34
|
+
// can't lie about repo state. The workflow at `templates/workflow*.yml.tmpl`
|
|
35
|
+
// reads the manifest with `JSON.parse(s).strictMode === true` — meaning the
|
|
36
|
+
// gate fires ONLY on an explicit `true`, and anything else (missing field,
|
|
37
|
+
// `false`, `null`) is advisory. Mirror that here: render "on" only when the
|
|
38
|
+
// caller explicitly passes `true`. Anything else is "off".
|
|
39
|
+
//
|
|
40
|
+
// Why this matters: a v0.3-era install (no `strictMode` field, `lastUpdate`
|
|
41
|
+
// set) is a documented advisory upgrade path — `clud-bug init` deliberately
|
|
42
|
+
// preserves that state. If the block rendered "on" for that case, other
|
|
43
|
+
// agents reading AGENTS.md would get a wrong model of the gate.
|
|
44
|
+
//
|
|
45
|
+
// v0.6.25 (gotcha #2 fix): when the consuming repo IS the publisher of
|
|
46
|
+
// the clud-bug-collaboration skill (skill source lives at
|
|
47
|
+
// `skills/clud-bug-collaboration/SKILL.md` instead of the consumer-install
|
|
48
|
+
// path `.claude/skills/...`), render the LOCAL repo path. Otherwise the
|
|
49
|
+
// link is dead in the publisher repo (this used to require a manual fix
|
|
50
|
+
// every v0.6.* propagation cycle on agent-skills).
|
|
51
|
+
export function renderBlock({ version, strictMode, skillRelPath } = {}) {
|
|
52
|
+
const versionLine = version ? `_Installed at clud-bug v${version}._` : '';
|
|
53
|
+
const strictNote = strictMode === true
|
|
54
|
+
? '**on** in this repo (workflow check fails on critical findings)'
|
|
55
|
+
: '**off** in this repo (advisory only)';
|
|
56
|
+
const skillPath = skillRelPath || '.claude/skills/clud-bug-collaboration/SKILL.md';
|
|
57
|
+
return `${START_MARKER}
|
|
58
|
+
<!-- clud-bug-block-version: ${BLOCK_VERSION} -->
|
|
59
|
+
## clud-bug — Claude PR review
|
|
60
|
+
|
|
61
|
+
This repo uses [clud-bug](https://cludbug.dev) for automatic PR reviews.
|
|
62
|
+
Full collaboration rules — fix-push flow, skill structure, comment format,
|
|
63
|
+
strict-mode mechanics, workflow-edit constraint — live in the bundled
|
|
64
|
+
[\`clud-bug-collaboration\` skill](${skillPath}).
|
|
65
|
+
Read that skill before pushing fixes addressing prior review threads.
|
|
66
|
+
|
|
67
|
+
Strict mode is ${strictNote}. Toggle via \`.claude/skills/.clud-bug.json\`
|
|
68
|
+
(read from PR **base ref**, so PRs can't disable strict-mode on themselves).
|
|
69
|
+
|
|
70
|
+
For agent invocations of the \`clud-bug\` CLI, prefer \`CLUD_BUG_QUIET=1\`
|
|
71
|
+
(or pass \`--quiet\`) — suppresses progress chatter and emits a single
|
|
72
|
+
\`ok <key-value>\` summary line per command.
|
|
73
|
+
|
|
74
|
+
${versionLine}
|
|
75
|
+
${END_MARKER}`;
|
|
76
|
+
}
|
|
77
|
+
// v0.6.25 (gotcha #2): detect repos that PUBLISH the
|
|
78
|
+
// clud-bug-collaboration skill (agent-skills is the canonical case).
|
|
79
|
+
// When the skill source exists at `skills/clud-bug-collaboration/SKILL.md`
|
|
80
|
+
// in the working tree, the AGENTS.md link should point there, not at the
|
|
81
|
+
// consumer-install `.claude/skills/...` path that doesn't exist in the
|
|
82
|
+
// publisher repo.
|
|
83
|
+
export async function detectSkillRelPath(cwd) {
|
|
84
|
+
const publisherPath = 'skills/clud-bug-collaboration/SKILL.md';
|
|
85
|
+
const consumerPath = '.claude/skills/clud-bug-collaboration/SKILL.md';
|
|
86
|
+
if (await fileExists(join(cwd, publisherPath)))
|
|
87
|
+
return publisherPath;
|
|
88
|
+
return consumerPath;
|
|
89
|
+
}
|
|
90
|
+
// Replace an existing clud-bug block in `content`, OR append if absent.
|
|
91
|
+
// Idempotent: running multiple times leaves a single block.
|
|
92
|
+
export function upsertBlock(content, block) {
|
|
93
|
+
const startRe = new RegExp(escapeRe(START_MARKER));
|
|
94
|
+
const endRe = new RegExp(escapeRe(END_MARKER));
|
|
95
|
+
if (startRe.test(content) && endRe.test(content)) {
|
|
96
|
+
// Replace from START_MARKER through END_MARKER (greedy multi-line).
|
|
97
|
+
const re = new RegExp(`${escapeRe(START_MARKER)}[\\s\\S]*?${escapeRe(END_MARKER)}`);
|
|
98
|
+
return content.replace(re, block);
|
|
99
|
+
}
|
|
100
|
+
// Append with a separating blank line, no trailing newline duplication.
|
|
101
|
+
const sep = content.endsWith('\n') ? '\n' : '\n\n';
|
|
102
|
+
return `${content}${sep}${block}\n`;
|
|
103
|
+
}
|
|
104
|
+
// 0.0.I.1 (v0.6.X): true if `content` has Claude Code's `@AGENTS.md`
|
|
105
|
+
// @-import — the canonical AGENTS.md content gets eager-loaded.
|
|
106
|
+
//
|
|
107
|
+
// Matches at start-of-line (a literal `@AGENTS.md` mentioned in prose
|
|
108
|
+
// won't fire; only the import directive does). Allows trailing space
|
|
109
|
+
// (some editors trim it; some don't) and optional newline terminator.
|
|
110
|
+
export function hasAgentsMdImport(content) {
|
|
111
|
+
if (typeof content !== 'string')
|
|
112
|
+
return false;
|
|
113
|
+
return /^@AGENTS\.md\s*$/m.test(content);
|
|
114
|
+
}
|
|
115
|
+
// 0.0.I.1: strip a clud-bug block (markers + body) AND the blank line
|
|
116
|
+
// that precedes it, if any. Used when an @AGENTS.md import is detected
|
|
117
|
+
// — the AGENTS.md content covers the block, so keeping it in CLAUDE.md
|
|
118
|
+
// (or any tool stub) is duplication.
|
|
119
|
+
//
|
|
120
|
+
// Returns the cleaned content. If no block exists, returns content
|
|
121
|
+
// unchanged. Idempotent.
|
|
122
|
+
export function removeBlock(content) {
|
|
123
|
+
if (typeof content !== 'string')
|
|
124
|
+
return content;
|
|
125
|
+
// Strip the block. Match \n+ before the marker so we also eat the
|
|
126
|
+
// preceding blank line — otherwise we'd leave a "stub line + blank
|
|
127
|
+
// + block" structure as "stub line + blank line".
|
|
128
|
+
const re = new RegExp(`\\n*${escapeRe(START_MARKER)}[\\s\\S]*?${escapeRe(END_MARKER)}\\n?`);
|
|
129
|
+
return content.replace(re, '');
|
|
130
|
+
}
|
|
131
|
+
function escapeRe(s) {
|
|
132
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
+
}
|
|
134
|
+
// Touches all relevant agent-instruction files in `cwd`.
|
|
135
|
+
// Creates AGENTS.md if it doesn't exist (it's the canonical home).
|
|
136
|
+
// Updates other files only if they already exist (don't proliferate stubs;
|
|
137
|
+
// logmind or the user owns those creation decisions).
|
|
138
|
+
//
|
|
139
|
+
// Returns { touched: string[], created: string[] } for the caller to log.
|
|
140
|
+
export async function applyToRepo(cwd, blockOpts = {}) {
|
|
141
|
+
// v0.6.25 / gotcha #2: detect publisher repo + render local skill path.
|
|
142
|
+
// Pre-v0.6.25 always rendered the consumer install path → broke
|
|
143
|
+
// agent-skills' check-links every propagation cycle. Detection runs
|
|
144
|
+
// before block render so the path is correct from the first write.
|
|
145
|
+
const skillRelPath = blockOpts.skillRelPath ?? await detectSkillRelPath(cwd);
|
|
146
|
+
const block = renderBlock({ ...blockOpts, skillRelPath });
|
|
147
|
+
const touched = [];
|
|
148
|
+
const created = [];
|
|
149
|
+
for (const path of ALWAYS_TOUCH) {
|
|
150
|
+
const full = join(cwd, path);
|
|
151
|
+
const existed = await fileExists(full);
|
|
152
|
+
const prior = existed ? await readFile(full, 'utf8') : seedFile(path);
|
|
153
|
+
const next = upsertBlock(prior, block);
|
|
154
|
+
if (next !== prior) {
|
|
155
|
+
await writeFile(full, next);
|
|
156
|
+
(existed ? touched : created).push(path);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const path of TOUCH_IF_PRESENT) {
|
|
160
|
+
const full = join(cwd, path);
|
|
161
|
+
if (!(await fileExists(full)))
|
|
162
|
+
continue;
|
|
163
|
+
const prior = await readFile(full, 'utf8');
|
|
164
|
+
const next = nextContentFor(prior, block);
|
|
165
|
+
if (next !== prior) {
|
|
166
|
+
await writeFile(full, next);
|
|
167
|
+
touched.push(path);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// .cursor/rules/*.md — append to every file that exists.
|
|
171
|
+
const cursorRulesDir = join(cwd, '.cursor', 'rules');
|
|
172
|
+
if (await fileExists(cursorRulesDir)) {
|
|
173
|
+
let entries = [];
|
|
174
|
+
try {
|
|
175
|
+
entries = await readdir(cursorRulesDir);
|
|
176
|
+
}
|
|
177
|
+
catch { /* dir missing or unreadable */ }
|
|
178
|
+
for (const name of entries) {
|
|
179
|
+
if (!name.endsWith('.md'))
|
|
180
|
+
continue;
|
|
181
|
+
const full = join(cursorRulesDir, name);
|
|
182
|
+
const prior = await readFile(full, 'utf8');
|
|
183
|
+
const next = nextContentFor(prior, block);
|
|
184
|
+
if (next !== prior) {
|
|
185
|
+
await writeFile(full, next);
|
|
186
|
+
touched.push(`.cursor/rules/${name}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { touched, created };
|
|
191
|
+
}
|
|
192
|
+
// 0.0.I.1: decide what content a TOUCH_IF_PRESENT file should have.
|
|
193
|
+
// If it already imports AGENTS.md via Claude Code's @-syntax, skip the
|
|
194
|
+
// block entirely (and clean up any pre-existing block — the content is
|
|
195
|
+
// duplicated via @-import, so the AGENTS.md block is the only authority).
|
|
196
|
+
// Otherwise, upsert as before.
|
|
197
|
+
//
|
|
198
|
+
// AGENTS.md itself is NOT routed through here — it always gets the
|
|
199
|
+
// block (it's the canonical source).
|
|
200
|
+
function nextContentFor(prior, block) {
|
|
201
|
+
return hasAgentsMdImport(prior) ? removeBlock(prior) : upsertBlock(prior, block);
|
|
202
|
+
}
|
|
203
|
+
function seedFile(name) {
|
|
204
|
+
// When AGENTS.md doesn't exist (no logmind, no prior tooling), seed with a
|
|
205
|
+
// minimal canonical header so the clud-bug block has context.
|
|
206
|
+
if (name === 'AGENTS.md') {
|
|
207
|
+
return `# AGENTS.md
|
|
208
|
+
|
|
209
|
+
This file is the canonical instruction file for AI coding agents working in
|
|
210
|
+
this repository. Tools that understand AGENTS.md (Cursor, Codex, Windsurf,
|
|
211
|
+
Claude Code, Cline, Continue, Aider, ...) read it directly.
|
|
212
|
+
|
|
213
|
+
`;
|
|
214
|
+
}
|
|
215
|
+
return '';
|
|
216
|
+
}
|
|
217
|
+
async function fileExists(path) {
|
|
218
|
+
try {
|
|
219
|
+
await stat(path);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=agents-md.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents-md.js","sourceRoot":"","sources":["../../src/cli/agents-md.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface GitLinesOptions {
|
|
2
|
+
cwd?: string | undefined;
|
|
3
|
+
allowFail?: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
export declare function gitLines(args: string[], opts?: GitLinesOptions): string[];
|
|
6
|
+
export interface AuditFileSetOptions {
|
|
7
|
+
since?: string | null;
|
|
8
|
+
changedIn?: string | null;
|
|
9
|
+
scopes?: string[];
|
|
10
|
+
cwd?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function computeAuditFileSet({ since, changedIn, scopes, cwd }?: AuditFileSetOptions): string[];
|
|
13
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/cli/audit.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,eAAe;IAI9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAOD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,eAAoB,GAAG,MAAM,EAAE,CAY7E;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAKD,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,MAAW,EAAE,GAAG,EAAE,GAAE,mBAAwB,GAAG,MAAM,EAAE,CAyB9G"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// CLI audit helpers — these shell out to git and walk the working tree.
|
|
2
|
+
//
|
|
3
|
+
// Split from lib/audit.js during the v0.7.0 TS migration. Pure helpers
|
|
4
|
+
// (durationToGitSince, renderAuditHeader) live in src/core/audit.ts so the
|
|
5
|
+
// App-side (clud-bug-app) can consume them without dragging child_process
|
|
6
|
+
// in. computeAuditFileSet stays CLI-only — it is only meaningful when run
|
|
7
|
+
// in a checked-out repo.
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { durationToGitSince } from '../core/audit.js';
|
|
10
|
+
// Run a git command, return stdout lines split by \n. Throws on non-zero exit
|
|
11
|
+
// unless { allowFail: true }, in which case returns [].
|
|
12
|
+
// Note: under noUncheckedIndexedAccess: true, array element reads are typed
|
|
13
|
+
// `string | undefined`. The return type stays `string[]` (we filter falsy
|
|
14
|
+
// strings out), but callers indexing into the result must keep that in mind.
|
|
15
|
+
export function gitLines(args, opts = {}) {
|
|
16
|
+
const r = spawnSync('git', args, { encoding: 'utf8', cwd: opts.cwd || process.cwd() });
|
|
17
|
+
if (r.status !== 0) {
|
|
18
|
+
if (opts.allowFail)
|
|
19
|
+
return [];
|
|
20
|
+
// spawnSync with encoding:'utf8' makes stderr `string | null`; the null
|
|
21
|
+
// path only happens when the child can't be spawned at all (a different
|
|
22
|
+
// error path). When status is non-zero we have stderr.
|
|
23
|
+
const stderr = (r.stderr ?? '').toString().trim();
|
|
24
|
+
throw new Error(`git ${args.join(' ')} failed (${r.status}): ${stderr}`);
|
|
25
|
+
}
|
|
26
|
+
const stdout = (r.stdout ?? '').toString();
|
|
27
|
+
return stdout.split('\n').filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
// Returns the file set the audit should consider, in repo-relative paths.
|
|
30
|
+
// Filters: optional --since (git date), optional --changed-in (duration string),
|
|
31
|
+
// optional --scope globs (one or more, repeatable).
|
|
32
|
+
export function computeAuditFileSet({ since, changedIn, scopes = [], cwd } = {}) {
|
|
33
|
+
const sinceArg = since || (changedIn ? durationToGitSince(changedIn) : null);
|
|
34
|
+
let files;
|
|
35
|
+
if (sinceArg) {
|
|
36
|
+
// Files touched in any commit within the window.
|
|
37
|
+
files = [...new Set(gitLines(['log', `--since=${sinceArg}`, '--name-only', '--pretty=format:'], { cwd }))];
|
|
38
|
+
// --diff-filter at the log level only excludes the delete commit; a file
|
|
39
|
+
// that was modified (and emitted by --name-only) and *later* deleted will
|
|
40
|
+
// still appear here. Intersect with the current tracked-file set so the
|
|
41
|
+
// manifest only contains paths we can actually read.
|
|
42
|
+
const tracked = new Set(gitLines(['ls-files'], { cwd }));
|
|
43
|
+
files = files.filter((f) => tracked.has(f));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
files = gitLines(['ls-files'], { cwd });
|
|
47
|
+
}
|
|
48
|
+
if (scopes.length) {
|
|
49
|
+
const matchers = scopes.map(globToRegex);
|
|
50
|
+
files = files.filter((f) => matchers.some((rx) => rx.test(f)));
|
|
51
|
+
}
|
|
52
|
+
// Skip vendor / build artifacts that bloat audits without adding signal.
|
|
53
|
+
const skip = /(^|\/)(node_modules|dist|build|out|\.next|\.vercel|coverage|target|__pycache__)\//;
|
|
54
|
+
return files.filter((f) => !skip.test(f)).sort();
|
|
55
|
+
}
|
|
56
|
+
// Minimal glob → RegExp. Supports **, *, ?. Anchors at both ends so that
|
|
57
|
+
// 'src/**/*.ts' matches 'src/lib/foo.ts' but not 'app/src/lib/foo.ts'.
|
|
58
|
+
function globToRegex(glob) {
|
|
59
|
+
let rx = '';
|
|
60
|
+
let i = 0;
|
|
61
|
+
while (i < glob.length) {
|
|
62
|
+
const ch = glob[i];
|
|
63
|
+
if (ch === '*' && glob[i + 1] === '*') {
|
|
64
|
+
// ** = any depth (including zero) of path segments
|
|
65
|
+
rx += '.*';
|
|
66
|
+
i += 2;
|
|
67
|
+
// consume an optional trailing slash so 'src/**/*.ts' works cleanly
|
|
68
|
+
if (glob[i] === '/')
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
else if (ch === '*') {
|
|
72
|
+
rx += '[^/]*';
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
else if (ch === '?') {
|
|
76
|
+
rx += '[^/]';
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
else if (ch !== undefined && /[.+^$|()\[\]{}\\]/.test(ch)) {
|
|
80
|
+
rx += '\\' + ch;
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
rx += ch;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return new RegExp(`^${rx}$`);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/cli/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface GhResult {
|
|
2
|
+
code: number | null;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
export interface GhOptions {
|
|
7
|
+
stdin?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
export type GhInvoker = (args: string[], opts?: GhOptions) => Promise<GhResult>;
|
|
10
|
+
export interface DetectRepoOptions {
|
|
11
|
+
gh?: GhInvoker | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface DetectedRepo {
|
|
14
|
+
owner: string;
|
|
15
|
+
repo: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function detectRepo({ gh }?: DetectRepoOptions): Promise<DetectedRepo>;
|
|
18
|
+
export interface DetectDefaultBranchOptions {
|
|
19
|
+
owner: string;
|
|
20
|
+
repo: string;
|
|
21
|
+
gh?: GhInvoker | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare function detectDefaultBranch({ owner, repo, gh }: DetectDefaultBranchOptions): Promise<string>;
|
|
24
|
+
export type ProtectionState = {
|
|
25
|
+
state: 'enabled';
|
|
26
|
+
} | {
|
|
27
|
+
state: 'disabled';
|
|
28
|
+
} | {
|
|
29
|
+
state: 'no-protection';
|
|
30
|
+
} | {
|
|
31
|
+
state: 'forbidden';
|
|
32
|
+
} | {
|
|
33
|
+
state: 'unknown';
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
export interface GetProtectionStateOptions {
|
|
37
|
+
owner: string;
|
|
38
|
+
repo: string;
|
|
39
|
+
branch: string;
|
|
40
|
+
gh?: GhInvoker | undefined;
|
|
41
|
+
}
|
|
42
|
+
export declare function getProtectionState({ owner, repo, branch, gh }: GetProtectionStateOptions): Promise<ProtectionState>;
|
|
43
|
+
export interface EnableConversationResolutionOptions {
|
|
44
|
+
owner: string;
|
|
45
|
+
repo: string;
|
|
46
|
+
branch: string;
|
|
47
|
+
gh?: GhInvoker | undefined;
|
|
48
|
+
}
|
|
49
|
+
export type EnableResult = {
|
|
50
|
+
ok: true;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
state: 'no-protection' | 'forbidden' | 'unknown';
|
|
54
|
+
reason: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function enableConversationResolution({ owner, repo, branch, gh }: EnableConversationResolutionOptions): Promise<EnableResult>;
|
|
57
|
+
//# sourceMappingURL=branch-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-protection.d.ts","sourceRoot":"","sources":["../../src/cli/branch-protection.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAGD,MAAM,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,SAAS,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAuBhF,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAID,wBAAsB,UAAU,CAAC,EAAE,EAAc,EAAE,GAAE,iBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAOlG;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B;AAGD,wBAAsB,mBAAmB,CACvC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAc,EAAE,EAAE,0BAA0B,GAC1D,OAAO,CAAC,MAAM,CAAC,CAMjB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GACrB;IAAE,KAAK,EAAE,eAAe,CAAA;CAAE,GAC1B;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B;AAaD,wBAAsB,kBAAkB,CACtC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAc,EAAE,EAAE,yBAAyB,GACjE,OAAO,CAAC,eAAe,CAAC,CAmB1B;AAED,MAAM,WAAW,mCAAmC;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMpF,wBAAsB,4BAA4B,CAChD,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAc,EAAE,EAAE,mCAAmC,GAC3E,OAAO,CAAC,YAAY,CAAC,CAiBvB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Helpers for managing `required_conversation_resolution` on the default
|
|
2
|
+
// branch via the `gh` CLI. Factored out so `runInit` can call this without
|
|
3
|
+
// embedding `spawnSync` boilerplate, and so tests can swap a mock for the
|
|
4
|
+
// real `gh` invocation.
|
|
5
|
+
//
|
|
6
|
+
// Why `gh` rather than direct fetch(): clud-bug already depends on `gh`
|
|
7
|
+
// being installed and authenticated (workflows use `gh pr comment`, edit
|
|
8
|
+
// workflows use `gh pr create`). Reusing it inherits the user's auth
|
|
9
|
+
// instead of asking them to set up another token.
|
|
10
|
+
//
|
|
11
|
+
// API endpoints used:
|
|
12
|
+
// GET /repos/{owner}/{repo}
|
|
13
|
+
// → .default_branch
|
|
14
|
+
// GET /repos/{owner}/{repo}/branches/{branch}/protection
|
|
15
|
+
// → .required_conversation_resolution.enabled (true/false), OR 404 if
|
|
16
|
+
// the branch has no protection rule at all.
|
|
17
|
+
// POST /repos/{owner}/{repo}/branches/{branch}/protection/required_conversation_resolution
|
|
18
|
+
// → enables the single flag without touching other settings. This is
|
|
19
|
+
// a real single-flag endpoint; we don't have to GET-merge-PUT the
|
|
20
|
+
// full protection JSON.
|
|
21
|
+
import { spawn } from 'node:child_process';
|
|
22
|
+
// Default `gh` invoker: spawns `gh <args>` and resolves with
|
|
23
|
+
// { code, stdout, stderr }. Tests pass a function with the same shape.
|
|
24
|
+
function defaultGh(args, opts = {}) {
|
|
25
|
+
const { stdin } = opts;
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn('gh', args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
28
|
+
let stdout = '';
|
|
29
|
+
let stderr = '';
|
|
30
|
+
// Under strict typing, spawn() can return null for stdio pipes when
|
|
31
|
+
// stdio is configured to ignore the stream. We requested 'pipe' for
|
|
32
|
+
// all three so the streams are guaranteed; the `!` non-null asserts
|
|
33
|
+
// make this explicit and keep the runtime semantics identical.
|
|
34
|
+
child.stdout.on('data', (d) => { stdout += d; });
|
|
35
|
+
child.stderr.on('data', (d) => { stderr += d; });
|
|
36
|
+
child.on('error', reject);
|
|
37
|
+
child.on('close', (code) => resolve({ code, stdout, stderr }));
|
|
38
|
+
if (stdin)
|
|
39
|
+
child.stdin.end(stdin);
|
|
40
|
+
else
|
|
41
|
+
child.stdin.end();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Returns { owner, repo } from the local git remote. Uses
|
|
45
|
+
// `gh repo view --json owner,name` so it doesn't depend on parsing URLs.
|
|
46
|
+
export async function detectRepo({ gh = defaultGh } = {}) {
|
|
47
|
+
const { code, stdout, stderr } = await gh(['repo', 'view', '--json', 'owner,name']);
|
|
48
|
+
if (code !== 0) {
|
|
49
|
+
throw new Error(`gh repo view failed (${code}): ${stderr.trim() || '(no stderr)'}`);
|
|
50
|
+
}
|
|
51
|
+
const parsed = JSON.parse(stdout);
|
|
52
|
+
return { owner: parsed.owner.login, repo: parsed.name };
|
|
53
|
+
}
|
|
54
|
+
// Returns the default branch name (e.g. "main", "master", "trunk").
|
|
55
|
+
export async function detectDefaultBranch({ owner, repo, gh = defaultGh }) {
|
|
56
|
+
const { code, stdout, stderr } = await gh(['api', `repos/${owner}/${repo}`, '--jq', '.default_branch']);
|
|
57
|
+
if (code !== 0) {
|
|
58
|
+
throw new Error(`Could not read default_branch for ${owner}/${repo}: ${stderr.trim() || stdout.trim()}`);
|
|
59
|
+
}
|
|
60
|
+
return stdout.trim();
|
|
61
|
+
}
|
|
62
|
+
// Inspects the current required_conversation_resolution state. Returns
|
|
63
|
+
// one of:
|
|
64
|
+
// { state: 'enabled' }
|
|
65
|
+
// { state: 'disabled' }
|
|
66
|
+
// { state: 'no-protection' } // branch has no protection rule at all
|
|
67
|
+
// { state: 'forbidden' } // user lacks admin perms
|
|
68
|
+
// { state: 'unknown', reason } // any other failure mode
|
|
69
|
+
//
|
|
70
|
+
// The reason this returns a discriminated union rather than throwing is
|
|
71
|
+
// that runInit decides what to do based on the state: each value above
|
|
72
|
+
// has a different user-facing message and follow-up action.
|
|
73
|
+
export async function getProtectionState({ owner, repo, branch, gh = defaultGh }) {
|
|
74
|
+
const { code, stdout, stderr } = await gh([
|
|
75
|
+
'api',
|
|
76
|
+
`repos/${owner}/${repo}/branches/${branch}/protection`,
|
|
77
|
+
'--jq', '.required_conversation_resolution.enabled // false',
|
|
78
|
+
]);
|
|
79
|
+
if (code === 0) {
|
|
80
|
+
return { state: stdout.trim() === 'true' ? 'enabled' : 'disabled' };
|
|
81
|
+
}
|
|
82
|
+
// gh prints HTTP details to stderr. Look for the markers we recognize.
|
|
83
|
+
// We deliberately key on 403 / 'Forbidden' / 'Resource not accessible'
|
|
84
|
+
// rather than the bare word 'admin' — gh's error vocabulary can mention
|
|
85
|
+
// 'admin' in unrelated contexts (administrator@…, admin api endpoint,
|
|
86
|
+
// future error copy) and we don't want to misclassify those as
|
|
87
|
+
// permission failures.
|
|
88
|
+
const blob = `${stdout}\n${stderr}`;
|
|
89
|
+
if (/404|Branch not protected|Not Found/i.test(blob))
|
|
90
|
+
return { state: 'no-protection' };
|
|
91
|
+
if (/403|Forbidden|Resource not accessible/i.test(blob))
|
|
92
|
+
return { state: 'forbidden' };
|
|
93
|
+
return { state: 'unknown', reason: stderr.trim() || stdout.trim() || `gh exited ${code}` };
|
|
94
|
+
}
|
|
95
|
+
// Enables the single flag via the dedicated endpoint. Doesn't touch any
|
|
96
|
+
// other protection settings. Returns { ok: true } on success or
|
|
97
|
+
// { ok: false, state, reason } using the same state taxonomy as
|
|
98
|
+
// getProtectionState() so callers can produce a consistent message.
|
|
99
|
+
export async function enableConversationResolution({ owner, repo, branch, gh = defaultGh }) {
|
|
100
|
+
const { code, stdout, stderr } = await gh([
|
|
101
|
+
'api', '-X', 'POST',
|
|
102
|
+
`repos/${owner}/${repo}/branches/${branch}/protection/required_conversation_resolution`,
|
|
103
|
+
]);
|
|
104
|
+
if (code === 0)
|
|
105
|
+
return { ok: true };
|
|
106
|
+
// Match the same precise alternatives as getProtectionState — no bare
|
|
107
|
+
// 'admin' fallback to avoid misclassifying unrelated error messages
|
|
108
|
+
// that happen to contain the word.
|
|
109
|
+
const blob = `${stdout}\n${stderr}`;
|
|
110
|
+
if (/404|Branch not protected|Not Found/i.test(blob)) {
|
|
111
|
+
return { ok: false, state: 'no-protection', reason: 'Branch has no base protection rule; enable basic branch protection first.' };
|
|
112
|
+
}
|
|
113
|
+
if (/403|Forbidden|Resource not accessible/i.test(blob)) {
|
|
114
|
+
return { ok: false, state: 'forbidden', reason: 'You do not have admin permissions on this repository.' };
|
|
115
|
+
}
|
|
116
|
+
return { ok: false, state: 'unknown', reason: stderr.trim() || stdout.trim() || `gh exited ${code}` };
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=branch-protection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"branch-protection.js","sourceRoot":"","sources":["../../src/cli/branch-protection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PendingWorkflowEdits {
|
|
2
|
+
allWorkflow: boolean;
|
|
3
|
+
files: string[];
|
|
4
|
+
nonWorkflow: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function getPendingWorkflowEdits(cwd?: string): PendingWorkflowEdits;
|
|
7
|
+
export declare function isWorkflowFile(path: string): boolean;
|
|
8
|
+
export declare function makeBranchName(date?: Date): string;
|
|
9
|
+
export interface GitOptions {
|
|
10
|
+
allowFail?: boolean | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface GitResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function git(cwd: string, args: string[], opts?: GitOptions): GitResult;
|
|
18
|
+
//# sourceMappingURL=edit-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-workflow.d.ts","sourceRoot":"","sources":["../../src/cli/edit-workflow.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAgB,uBAAuB,CAAC,GAAG,GAAE,MAAsB,GAAG,oBAAoB,CAyBzF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG9D;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjC;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,UAAe,GAAG,SAAS,CAMjF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
export function getPendingWorkflowEdits(cwd = process.cwd()) {
|
|
3
|
+
// --untracked-files=all so new files in new directories are reported as
|
|
4
|
+
// individual paths instead of being collapsed to the parent dir (default
|
|
5
|
+
// 'normal' mode would emit '.github/' for a brand-new clud-bug-review.yml).
|
|
6
|
+
const r = spawnSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd, encoding: 'utf8' });
|
|
7
|
+
if (r.status !== 0) {
|
|
8
|
+
// r.stderr is `string | null` under strict; safe to coalesce to '' for the
|
|
9
|
+
// error message — null here means the child failed to spawn entirely.
|
|
10
|
+
throw new Error(`git status failed: ${(r.stderr ?? '').trim()}`);
|
|
11
|
+
}
|
|
12
|
+
const stdout = r.stdout ?? '';
|
|
13
|
+
const lines = stdout.split('\n').filter(Boolean);
|
|
14
|
+
if (lines.length === 0)
|
|
15
|
+
return { allWorkflow: true, files: [], nonWorkflow: [] };
|
|
16
|
+
const files = [];
|
|
17
|
+
const nonWorkflow = [];
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
// porcelain format: XY <space> <path> ; rename: XY old -> new
|
|
20
|
+
// .pop() on a non-empty split always yields a string here, but
|
|
21
|
+
// noUncheckedIndexedAccess makes TS pessimistic — coalesce + trim.
|
|
22
|
+
const path = (line.slice(3).split(' -> ').pop() ?? '').trim();
|
|
23
|
+
files.push(path);
|
|
24
|
+
if (!isWorkflowFile(path))
|
|
25
|
+
nonWorkflow.push(path);
|
|
26
|
+
}
|
|
27
|
+
return { allWorkflow: nonWorkflow.length === 0, files, nonWorkflow };
|
|
28
|
+
}
|
|
29
|
+
export function isWorkflowFile(path) {
|
|
30
|
+
return /^\.github\/workflows\/clud-bug-.*\.ya?ml$/.test(path);
|
|
31
|
+
}
|
|
32
|
+
export function makeBranchName(date = new Date()) {
|
|
33
|
+
const stamp = date.toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
34
|
+
return `clud-bug/edit-workflow-${stamp}`;
|
|
35
|
+
}
|
|
36
|
+
export function git(cwd, args, opts = {}) {
|
|
37
|
+
const r = spawnSync('git', args, { cwd, encoding: 'utf8' });
|
|
38
|
+
if (r.status !== 0 && !opts.allowFail) {
|
|
39
|
+
throw new Error(`git ${args.join(' ')} failed (${r.status}): ${(r.stderr ?? '').trim()}`);
|
|
40
|
+
}
|
|
41
|
+
return { ok: r.status === 0, stdout: (r.stdout ?? '').trim(), stderr: (r.stderr ?? '').trim() };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=edit-workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-workflow.js","sourceRoot":"","sources":["../../src/cli/edit-workflow.ts"],"names":[],"mappings":"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"}
|