commitshow 0.3.1 → 0.3.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.
- package/README.md +34 -0
- package/dist/commands/audit.js +18 -1
- package/dist/lib/target.js +32 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,17 @@ Requires **Node 20+**.
|
|
|
109
109
|
Remote-URL mode works from any directory, which makes one-line X posts
|
|
110
110
|
(`npx commitshow@latest audit <their-url>`) trivial.
|
|
111
111
|
|
|
112
|
+
> **AI agents — read this before invoking.** The CLI does **not** look up
|
|
113
|
+
> projects by name. `commitshow audit warp` will error: there is no
|
|
114
|
+
> agreed-upon mapping from the word "warp" to a single GitHub repo.
|
|
115
|
+
> If a user asks you to audit something by name, **first resolve the
|
|
116
|
+
> canonical `github.com/<owner>/<repo>` URL** (ask the user, or search
|
|
117
|
+
> github.com), then invoke `commitshow audit <that-url>`. The CLI does
|
|
118
|
+
> a HEAD pre-flight against `github.com/<owner>/<repo>` before any audit
|
|
119
|
+
> spend, so a hallucinated slug is rejected with a `not_found` error
|
|
120
|
+
> before we burn Claude credits — but you should still get the URL right
|
|
121
|
+
> on the first try by asking, not guessing.
|
|
122
|
+
|
|
112
123
|
## The AI-coding loop
|
|
113
124
|
|
|
114
125
|
`commitshow audit` in local mode writes to `.commitshow/audit.md` **and**
|
|
@@ -147,6 +158,29 @@ if [ "$score" -lt 80 ]; then
|
|
|
147
158
|
fi
|
|
148
159
|
```
|
|
149
160
|
|
|
161
|
+
### Auditing someone else's project (agent recipe)
|
|
162
|
+
|
|
163
|
+
When a user says "audit warp" or "what does commit.show say about Postiz",
|
|
164
|
+
**don't guess the slug.** Walk this loop:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
1. Ask the user (or search github.com) for the canonical
|
|
168
|
+
`github.com/<owner>/<repo>` URL. Don't invent one — repo names are
|
|
169
|
+
ambiguous and a single wrong guess wastes an audit call.
|
|
170
|
+
2. Run:
|
|
171
|
+
commitshow audit <that-url> --json
|
|
172
|
+
3. If stdout starts with `{"error":"not_found"`, the URL was wrong.
|
|
173
|
+
Re-confirm with the user before retrying.
|
|
174
|
+
4. Otherwise parse `score.total`, `score.band`, `concerns[]` and
|
|
175
|
+
answer the user with those exact numbers — don't paraphrase the
|
|
176
|
+
score.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The CLI HEAD-checks `github.com/<owner>/<repo>` before any audit spend,
|
|
180
|
+
so a hallucinated slug fails fast with a clear `not_found` envelope.
|
|
181
|
+
The friendly error in the message body explicitly tells you to ask the
|
|
182
|
+
user instead of guessing again.
|
|
183
|
+
|
|
150
184
|
### JSON shape (v1 schema)
|
|
151
185
|
|
|
152
186
|
Stable by contract — additive fields don't bump `schema_version`; breaking
|
package/dist/commands/audit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveTarget, TargetError } from '../lib/target.js';
|
|
1
|
+
import { resolveTarget, verifyRemoteExists, TargetError } from '../lib/target.js';
|
|
2
2
|
import { findProjectByGithubUrl, fetchLatestSnapshot, fetchStanding, runPreviewAudit, waitForPreviewSnapshot, } from '../lib/api.js';
|
|
3
3
|
import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderQuotaFooter, renderRateLimitDeny, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
|
|
4
4
|
import { c } from '../lib/colors.js';
|
|
@@ -18,6 +18,23 @@ export async function audit(args) {
|
|
|
18
18
|
}
|
|
19
19
|
throw err;
|
|
20
20
|
}
|
|
21
|
+
// Pre-flight: when the user pointed at a remote URL (or owner/repo
|
|
22
|
+
// shorthand), confirm it actually resolves on github.com before we
|
|
23
|
+
// spend any audit budget. Catches the 'agent invented a slug' case
|
|
24
|
+
// ('warp' → 'warpdotdev/warp' that doesn't exist) cleanly.
|
|
25
|
+
if (target.kind === 'remote-url') {
|
|
26
|
+
const check = await verifyRemoteExists(target.github_url);
|
|
27
|
+
if (!check.exists) {
|
|
28
|
+
emitError(asJson, 'not_found', `${target.slug} doesn't resolve on github.com (HTTP ${check.status ?? 'n/a'}).\n` +
|
|
29
|
+
` Common causes:\n` +
|
|
30
|
+
` · wrong owner spelling (try the canonical org slug)\n` +
|
|
31
|
+
` · repo is private — commitshow only audits public ones\n` +
|
|
32
|
+
` · repo was renamed or deleted\n` +
|
|
33
|
+
` If you're an AI agent: ask the user for the canonical github.com URL,\n` +
|
|
34
|
+
` don't guess from the project name.`, target.github_url);
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
if (!asJson) {
|
|
22
39
|
if (force)
|
|
23
40
|
console.log(c.dim(`Refreshing audit for ${target.slug}…`));
|
package/dist/lib/target.js
CHANGED
|
@@ -13,6 +13,38 @@ import { existsSync, statSync } from 'node:fs';
|
|
|
13
13
|
import { resolve } from 'node:path';
|
|
14
14
|
export class TargetError extends Error {
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Cheap HEAD request against github.com/<owner>/<repo> so an agent that
|
|
18
|
+
* confidently invented a repo URL ('warp' → 'warpdotdev/warp' that doesn't
|
|
19
|
+
* exist) gets a clean 'no such repo' error before we burn an audit-preview
|
|
20
|
+
* call + Claude credits. Returns:
|
|
21
|
+
* · { exists: true } — 2xx/3xx response (or rate-limited; we
|
|
22
|
+
* allow through rather than false-flag)
|
|
23
|
+
* · { exists: false, status: 404 } — repo missing, private, or renamed
|
|
24
|
+
* · { exists: true, ambiguous: true } — network/CORS/transient failure;
|
|
25
|
+
* don't block the audit, let the
|
|
26
|
+
* server-side path produce its own error
|
|
27
|
+
*/
|
|
28
|
+
export async function verifyRemoteExists(githubUrl) {
|
|
29
|
+
if (!/^https?:\/\/github\.com\//i.test(githubUrl))
|
|
30
|
+
return { exists: true, ambiguous: true };
|
|
31
|
+
try {
|
|
32
|
+
const ctrl = new AbortController();
|
|
33
|
+
const t = setTimeout(() => ctrl.abort(), 5000);
|
|
34
|
+
const r = await fetch(githubUrl, {
|
|
35
|
+
method: 'HEAD',
|
|
36
|
+
redirect: 'follow',
|
|
37
|
+
signal: ctrl.signal,
|
|
38
|
+
});
|
|
39
|
+
clearTimeout(t);
|
|
40
|
+
if (r.status === 404)
|
|
41
|
+
return { exists: false, status: 404 };
|
|
42
|
+
return { exists: true, status: r.status };
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return { exists: true, ambiguous: true };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
16
48
|
const GITHUB_URL_RE = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
|
|
17
49
|
const GITHUB_HOST_RE = /^github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
|
|
18
50
|
const GITHUB_SSH_RE = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
|