commitshow 0.3.1 → 0.3.3

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 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
@@ -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}…`));
@@ -758,7 +758,11 @@ export function renderUpsell(githubUrl) {
758
758
  : 'https://commit.show/submit';
759
759
  const titleVisible = 'Walk-on · drop-in audit, no audition yet';
760
760
  const headVisible = 'Audition to unlock:';
761
- const ctaVisible = `→ ${submitUrl}`;
761
+ // CTA URL renders OUTSIDE the box (after boxBottom()) — the encoded
762
+ // submit URL is 60-70 chars on most repos, which busts the 54-char
763
+ // inside-box content width and visually shears the right border off.
764
+ // Pulling it below the box also lets terminals auto-link the full
765
+ // https:// URL without truncation.
762
766
  lines.push(' ' + boxTop());
763
767
  lines.push(' ' + boxRow(titleVisible.length, c.bold(c.gold('Walk-on')) + c.muted(' · ') + c.cream('drop-in audit, no audition yet')));
764
768
  lines.push(' ' + boxBlank());
@@ -782,9 +786,10 @@ export function renderUpsell(githubUrl) {
782
786
  const visible = `→ ${it.tag}${it.rest}`;
783
787
  lines.push(' ' + boxRow(visible.length, it.tone('→ ') + c.cream(it.tag) + c.muted(it.rest)));
784
788
  }
785
- lines.push(' ' + boxBlank());
786
- lines.push(' ' + boxRow(ctaVisible.length, c.gold(ctaVisible)));
787
789
  lines.push(' ' + boxBottom());
790
+ // CTA outside the box · full URL stays clickable in modern terminals,
791
+ // and we don't have to truncate or shorten the slug.
792
+ lines.push(' ' + c.gold('→ ') + c.gold(submitUrl));
788
793
  return lines.join('\n');
789
794
  }
790
795
  export function writeAuditJson(dir, json) {
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commitshow",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "commit.show CLI — audit any vibe-coded project from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {