commitshow 0.3.26 → 0.3.28
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/dist/commands/audit.js +11 -0
- package/dist/lib/ci-prompt.js +93 -0
- package/dist/lib/render.js +17 -0
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -3,6 +3,7 @@ import { findProjectByGithubUrl, fetchLatestSnapshot, fetchStanding, runPreviewA
|
|
|
3
3
|
import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderStarCta, renderQuotaFooter, renderRateLimitDeny, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
|
|
4
4
|
import { c } from '../lib/colors.js';
|
|
5
5
|
import { Spinner } from '../lib/spinner.js';
|
|
6
|
+
import { maybeOfferCi } from '../lib/ci-prompt.js';
|
|
6
7
|
export async function audit(args) {
|
|
7
8
|
const asJson = args.includes('--json');
|
|
8
9
|
const force = args.includes('--refresh') || args.includes('--force');
|
|
@@ -123,6 +124,12 @@ export async function audit(args) {
|
|
|
123
124
|
if (jsonPath)
|
|
124
125
|
console.log(c.dim(` Saved → ${jsonPath}`));
|
|
125
126
|
}
|
|
127
|
+
// Post-audit CI nudge · only in interactive non-JSON mode.
|
|
128
|
+
// Skipped silently if not in a git repo / no GitHub remote /
|
|
129
|
+
// workflow already exists.
|
|
130
|
+
if (!asJson && target.localPath) {
|
|
131
|
+
await maybeOfferCi(target.localPath);
|
|
132
|
+
}
|
|
126
133
|
}
|
|
127
134
|
return 0;
|
|
128
135
|
}
|
|
@@ -248,6 +255,10 @@ export async function audit(args) {
|
|
|
248
255
|
if (jsonPath)
|
|
249
256
|
console.log(c.dim(` Saved → ${jsonPath}`));
|
|
250
257
|
}
|
|
258
|
+
// Post-audit CI nudge · only in interactive non-JSON mode.
|
|
259
|
+
if (!asJson && target.localPath) {
|
|
260
|
+
await maybeOfferCi(target.localPath);
|
|
261
|
+
}
|
|
251
262
|
}
|
|
252
263
|
return 0;
|
|
253
264
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Post-audit nudge · ask the user whether to wire the audit into CI
|
|
2
|
+
// by writing .github/workflows/audit.yml in their repo. Highest-intent
|
|
3
|
+
// moment (they just saw their score and concerns) and the cheapest
|
|
4
|
+
// possible install path (one file, one commit).
|
|
5
|
+
//
|
|
6
|
+
// Skipped silently when:
|
|
7
|
+
// · stdout/stdin isn't a TTY (CI, scripted use)
|
|
8
|
+
// · target isn't a git repo
|
|
9
|
+
// · the repo has no github.com remote
|
|
10
|
+
// · the workflow file already exists (don't overwrite)
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { createInterface } from 'node:readline/promises';
|
|
14
|
+
import { c } from './colors.js';
|
|
15
|
+
const WORKFLOW_REL_PATH = '.github/workflows/audit.yml';
|
|
16
|
+
const WORKFLOW_BODY = `name: audit
|
|
17
|
+
on:
|
|
18
|
+
pull_request:
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
pull-requests: write
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
audit:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: commitshow/audit-action@v1
|
|
28
|
+
`;
|
|
29
|
+
function isGitRepo(path) {
|
|
30
|
+
return existsSync(join(path, '.git'));
|
|
31
|
+
}
|
|
32
|
+
function hasGithubRemote(path) {
|
|
33
|
+
try {
|
|
34
|
+
const cfg = readFileSync(join(path, '.git', 'config'), 'utf8');
|
|
35
|
+
return /url\s*=\s*[^\n]*github\.com/i.test(cfg);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function existingWorkflow(path) {
|
|
42
|
+
return existsSync(join(path, WORKFLOW_REL_PATH));
|
|
43
|
+
}
|
|
44
|
+
async function ask(question, defaultYes = true) {
|
|
45
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
46
|
+
try {
|
|
47
|
+
const raw = await rl.question(question + ' ');
|
|
48
|
+
const ans = raw.trim().toLowerCase();
|
|
49
|
+
if (ans === '')
|
|
50
|
+
return defaultYes;
|
|
51
|
+
return ans === 'y' || ans === 'yes';
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
rl.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export async function maybeOfferCi(localPath) {
|
|
58
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
59
|
+
return;
|
|
60
|
+
if (!isGitRepo(localPath))
|
|
61
|
+
return;
|
|
62
|
+
if (!hasGithubRemote(localPath))
|
|
63
|
+
return;
|
|
64
|
+
if (existingWorkflow(localPath))
|
|
65
|
+
return;
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(c.muted(' Want this audit to run on every pull request?'));
|
|
68
|
+
console.log(c.muted(` Drops a single file at ${WORKFLOW_REL_PATH} that uses commitshow/audit-action@v1.`));
|
|
69
|
+
console.log(c.muted(' The action posts a sticky comment on each PR so regressions surface in review.'));
|
|
70
|
+
let yes;
|
|
71
|
+
try {
|
|
72
|
+
yes = await ask(c.gold(' Add the workflow? [Y/n]'), true);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Reading from stdin can throw on unusual terminal states (e.g. piped
|
|
76
|
+
// input that closes mid-read). Treat as "no" rather than crashing.
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!yes) {
|
|
80
|
+
console.log(c.dim(' Skipped. Run again later or copy the YAML from https://github.com/commitshow/audit-action.'));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const dir = join(localPath, '.github', 'workflows');
|
|
85
|
+
mkdirSync(dir, { recursive: true });
|
|
86
|
+
writeFileSync(join(localPath, WORKFLOW_REL_PATH), WORKFLOW_BODY, 'utf8');
|
|
87
|
+
console.log(c.gold(` ✓ Wrote ${WORKFLOW_REL_PATH}`));
|
|
88
|
+
console.log(c.muted(' Next: commit and push, then open a pull request — the score will post automatically.'));
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
console.log(c.scarlet(` Could not write ${WORKFLOW_REL_PATH}: ${e.message}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/lib/render.js
CHANGED
|
@@ -517,6 +517,14 @@ export function renderAudit(view) {
|
|
|
517
517
|
const name = p.project_name ?? 'untitled';
|
|
518
518
|
const slug = p.github_url?.replace(/^https?:\/\//, '') ?? '';
|
|
519
519
|
lines.push(' ' + c.bold(c.cream(name)) + ' ' + c.muted(slug));
|
|
520
|
+
// Scanned-scope transparency line · monorepo-aware. For supabase-style
|
|
521
|
+
// big monorepos, this disarms the "you said our service fails X" trap
|
|
522
|
+
// by stating what was actually traversed (apps/studio + apps/www, not
|
|
523
|
+
// postgres-meta or gotrue or storage which live in separate repos).
|
|
524
|
+
const scope = snapshot?.github_signals?.scanned_scope;
|
|
525
|
+
if (scope) {
|
|
526
|
+
lines.push(' ' + c.muted('Scanned · ') + c.cream(scope));
|
|
527
|
+
}
|
|
520
528
|
lines.push('');
|
|
521
529
|
// ── 3 strengths + 2 concerns box · errors-first reorder (2026-04-30) ──
|
|
522
530
|
// CONCERNS render before STRENGTHS · the value prop is "what your AI
|
|
@@ -929,6 +937,14 @@ export function renderMarkdown(view) {
|
|
|
929
937
|
lines.push(`**${p.project_name}**`);
|
|
930
938
|
if (p.github_url)
|
|
931
939
|
lines.push(`_${p.github_url}_`);
|
|
940
|
+
// Scope transparency · same string the CLI prints. Lets a downstream
|
|
941
|
+
// agent reading audit.md know whether the concerns came from the core
|
|
942
|
+
// service or from a dashboard / marketing sub-app of a monorepo.
|
|
943
|
+
const scope = snapshot?.github_signals?.scanned_scope;
|
|
944
|
+
if (scope) {
|
|
945
|
+
lines.push('');
|
|
946
|
+
lines.push(`Scanned · ${scope}`);
|
|
947
|
+
}
|
|
932
948
|
lines.push('');
|
|
933
949
|
// errors-first markdown order (2026-04-30) · concerns + strengths
|
|
934
950
|
// BEFORE the score so the AI agent reading audit.md picks up the
|
|
@@ -1024,6 +1040,7 @@ export function toAgentShape(view) {
|
|
|
1024
1040
|
status: p.status,
|
|
1025
1041
|
creator: { name: p.creator_name, grade: p.creator_grade },
|
|
1026
1042
|
url: `https://commit.show/projects/${p.id}`,
|
|
1043
|
+
scanned_scope: snapshot?.github_signals?.scanned_scope ?? null,
|
|
1027
1044
|
},
|
|
1028
1045
|
score: {
|
|
1029
1046
|
track: isWalkOn ? 'walk_on' : 'league',
|