clud-bug 0.6.34 → 0.7.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/bin/clud-bug.js +10 -1353
  2. package/dist/cli/agents-md.d.ts +16 -0
  3. package/dist/cli/agents-md.d.ts.map +1 -0
  4. package/dist/cli/agents-md.js +226 -0
  5. package/dist/cli/agents-md.js.map +1 -0
  6. package/dist/cli/audit.d.ts +13 -0
  7. package/dist/cli/audit.d.ts.map +1 -0
  8. package/dist/cli/audit.js +90 -0
  9. package/dist/cli/audit.js.map +1 -0
  10. package/dist/cli/branch-protection.d.ts +57 -0
  11. package/dist/cli/branch-protection.d.ts.map +1 -0
  12. package/dist/cli/branch-protection.js +118 -0
  13. package/dist/cli/branch-protection.js.map +1 -0
  14. package/dist/cli/edit-workflow.d.ts +18 -0
  15. package/dist/cli/edit-workflow.d.ts.map +1 -0
  16. package/dist/cli/edit-workflow.js +43 -0
  17. package/dist/cli/edit-workflow.js.map +1 -0
  18. package/dist/cli/index.d.ts +8 -0
  19. package/dist/cli/index.d.ts.map +1 -0
  20. package/dist/cli/index.js +18 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/main.d.ts +3 -0
  23. package/dist/cli/main.d.ts.map +1 -0
  24. package/dist/cli/main.js +1336 -0
  25. package/dist/cli/main.js.map +1 -0
  26. package/dist/cli/skill-usage.d.ts +109 -0
  27. package/dist/cli/skill-usage.d.ts.map +1 -0
  28. package/dist/cli/skill-usage.js +380 -0
  29. package/dist/cli/skill-usage.js.map +1 -0
  30. package/dist/cli/skills.d.ts +56 -0
  31. package/dist/cli/skills.d.ts.map +1 -0
  32. package/dist/cli/skills.js +292 -0
  33. package/dist/cli/skills.js.map +1 -0
  34. package/dist/cli/update.d.ts +29 -0
  35. package/dist/cli/update.d.ts.map +1 -0
  36. package/dist/cli/update.js +186 -0
  37. package/dist/cli/update.js.map +1 -0
  38. package/dist/cli/usage.d.ts +142 -0
  39. package/dist/cli/usage.d.ts.map +1 -0
  40. package/dist/cli/usage.js +348 -0
  41. package/dist/cli/usage.js.map +1 -0
  42. package/dist/core/audit.d.ts +8 -0
  43. package/dist/core/audit.d.ts.map +1 -0
  44. package/dist/core/audit.js +47 -0
  45. package/dist/core/audit.js.map +1 -0
  46. package/dist/core/detect.d.ts +77 -0
  47. package/dist/core/detect.d.ts.map +1 -0
  48. package/dist/core/detect.js +262 -0
  49. package/dist/core/detect.js.map +1 -0
  50. package/dist/core/index.d.ts +11 -0
  51. package/dist/core/index.d.ts.map +1 -0
  52. package/dist/core/index.js +31 -0
  53. package/dist/core/index.js.map +1 -0
  54. package/dist/core/prompt-builder.d.ts +164 -0
  55. package/dist/core/prompt-builder.d.ts.map +1 -0
  56. package/dist/core/prompt-builder.js +419 -0
  57. package/dist/core/prompt-builder.js.map +1 -0
  58. package/dist/core/prompts.d.ts +9 -0
  59. package/dist/core/prompts.d.ts.map +1 -0
  60. package/dist/core/prompts.js +401 -0
  61. package/dist/core/prompts.js.map +1 -0
  62. package/dist/core/render-review.d.ts +6 -0
  63. package/dist/core/render-review.d.ts.map +1 -0
  64. package/dist/core/render-review.js +219 -0
  65. package/dist/core/render-review.js.map +1 -0
  66. package/dist/core/render.d.ts +13 -0
  67. package/dist/core/render.d.ts.map +1 -0
  68. package/dist/core/render.js +80 -0
  69. package/dist/core/render.js.map +1 -0
  70. package/dist/core/review-schema-zod.d.ts +240 -0
  71. package/dist/core/review-schema-zod.d.ts.map +1 -0
  72. package/dist/core/review-schema-zod.js +218 -0
  73. package/dist/core/review-schema-zod.js.map +1 -0
  74. package/dist/core/review-schema.d.ts +42 -0
  75. package/dist/core/review-schema.d.ts.map +1 -0
  76. package/dist/core/review-schema.js +156 -0
  77. package/dist/core/review-schema.js.map +1 -0
  78. package/dist/core/review-writeback.d.ts +139 -0
  79. package/dist/core/review-writeback.d.ts.map +1 -0
  80. package/dist/core/review-writeback.js +313 -0
  81. package/dist/core/review-writeback.js.map +1 -0
  82. package/dist/core/skills.d.ts +122 -0
  83. package/dist/core/skills.d.ts.map +1 -0
  84. package/dist/core/skills.js +636 -0
  85. package/dist/core/skills.js.map +1 -0
  86. package/package.json +30 -4
  87. package/{lib/agents-md.js → src/cli/agents-md.ts} +25 -14
  88. package/{lib/audit.js → src/cli/audit.ts} +37 -44
  89. package/{lib/branch-protection.js → src/cli/branch-protection.ts} +75 -11
  90. package/{lib/edit-workflow.js → src/cli/edit-workflow.ts} +32 -11
  91. package/src/cli/index.ts +101 -0
  92. package/src/cli/main.ts +1376 -0
  93. package/{lib/skill-usage.js → src/cli/skill-usage.ts} +168 -94
  94. package/src/cli/skills.ts +386 -0
  95. package/{lib/update.js → src/cli/update.ts} +68 -27
  96. package/{lib/usage.js → src/cli/usage.ts} +167 -76
  97. package/src/core/audit.ts +53 -0
  98. package/{lib/detect.js → src/core/detect.ts} +100 -47
  99. package/src/core/index.ts +155 -0
  100. package/src/core/prompt-builder.ts +561 -0
  101. package/{lib/prompts.js → src/core/prompts.ts} +16 -2
  102. package/{lib/render-review.js → src/core/render-review.ts} +57 -25
  103. package/{lib/render.js → src/core/render.ts} +36 -10
  104. package/src/core/review-schema-zod.ts +262 -0
  105. package/{lib/review-schema.js → src/core/review-schema.ts} +68 -5
  106. package/src/core/review-writeback.ts +446 -0
  107. package/{lib/skills.js → src/core/skills.ts} +339 -342
  108. package/templates/workflow-py.yml.tmpl +2 -2
  109. package/templates/workflow-ts.yml.tmpl +2 -2
  110. 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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"}