commitshow 0.3.28 → 0.3.29

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.
@@ -23,10 +23,22 @@ export async function audit(args) {
23
23
  ?? (sourceIdx >= 0 ? args[sourceIdx + 1] : undefined)
24
24
  ?? process.env.COMMITSHOW_SOURCE
25
25
  ?? null;
26
- const positional = args.find(a => !a.startsWith('--') && a !== sourceFlag);
26
+ // --workspace <path> · explicit override of the server-side auto-pick
27
+ // for monorepo audits. Both --workspace=apps/web and --workspace apps/web
28
+ // forms accepted. Passes through to the Edge Function as `workspace`
29
+ // in the audit request body. Inline path inside the URL
30
+ // (`github.com/o/r/apps/web`) is the second-priority source — picked
31
+ // up by resolveTarget() if the flag isn't set.
32
+ const workspaceIdx = args.indexOf('--workspace');
33
+ const workspaceFlag = args.find(a => a.startsWith('--workspace='))?.split('=')[1]
34
+ ?? (workspaceIdx >= 0 ? args[workspaceIdx + 1] : undefined)
35
+ ?? null;
36
+ const positional = args.find(a => !a.startsWith('--') &&
37
+ a !== sourceFlag &&
38
+ a !== workspaceFlag);
27
39
  let target;
28
40
  try {
29
- target = resolveTarget(positional);
41
+ target = resolveTarget(positional, { workspace: workspaceFlag });
30
42
  }
31
43
  catch (err) {
32
44
  if (err instanceof TargetError) {
@@ -141,7 +153,11 @@ export async function audit(args) {
141
153
  else
142
154
  console.log(c.dim('First time on commit.show for this repo — running a preview audit…'));
143
155
  }
144
- const result = await runPreviewAudit(target.github_url, undefined, { force, source: sourceFlag });
156
+ const result = await runPreviewAudit(target.github_url, undefined, {
157
+ force,
158
+ source: sourceFlag,
159
+ workspace: target.workspace,
160
+ });
145
161
  // Error envelope
146
162
  if ('error' in result) {
147
163
  const err = result;
package/dist/index.js CHANGED
@@ -39,15 +39,21 @@ ${c.muted('COMMANDS')}
39
39
  ${c.gold('whoami')} who am I signed in as
40
40
 
41
41
  ${c.muted('FLAGS')}
42
- ${c.gold('--json')} stable machine-readable output (for agents · CI · jq pipes)
43
- ${c.gold('--refresh')} bypass the 7-day cache · re-run a fresh audit ${c.dim('(counts against IP cap)')}
42
+ ${c.gold('--json')} stable machine-readable output (for agents · CI · jq pipes)
43
+ ${c.gold('--refresh')} bypass the 7-day cache · re-run a fresh audit ${c.dim('(counts against IP cap)')}
44
+ ${c.gold('--workspace')} ${c.cream('<path>')} monorepo: audit only this sub-app (e.g. apps/web · packages/next)
44
45
 
45
46
  ${c.muted('TARGET FORMS')} ${c.dim('(default: cwd)')}
46
- ${c.cream('commitshow audit')} ${c.dim('# cwd · git remote origin')}
47
- ${c.cream('commitshow audit ./my-repo')} ${c.dim('# local path')}
48
- ${c.cream('commitshow audit github.com/owner/repo')} ${c.dim('# remote shorthand')}
49
- ${c.cream('commitshow audit https://github.com/o/r')} ${c.dim('# full URL')}
50
- ${c.cream('commitshow audit owner/repo')} ${c.dim('# last-ditch shorthand')}
47
+ ${c.cream('commitshow audit')} ${c.dim('# cwd · git remote origin')}
48
+ ${c.cream('commitshow audit ./my-repo')} ${c.dim('# local path')}
49
+ ${c.cream('commitshow audit github.com/owner/repo')} ${c.dim('# remote shorthand')}
50
+ ${c.cream('commitshow audit https://github.com/o/r')} ${c.dim('# full URL')}
51
+ ${c.cream('commitshow audit owner/repo')} ${c.dim('# last-ditch shorthand')}
52
+
53
+ ${c.muted('MONOREPO TARGETS')} ${c.dim('(all three forms produce the same audit)')}
54
+ ${c.cream('commitshow audit github.com/o/r --workspace apps/web')} ${c.dim('# explicit flag')}
55
+ ${c.cream('commitshow audit github.com/o/r/apps/web')} ${c.dim('# inline path')}
56
+ ${c.cream('commitshow audit https://github.com/o/r/tree/main/apps/web')} ${c.dim('# paste a GitHub browse URL')}
51
57
 
52
58
  ${c.muted('FOR AGENTS')}
53
59
  ${c.cream('commitshow audit github.com/owner/repo --json | jq .concerns')}
package/dist/lib/api.js CHANGED
@@ -98,6 +98,10 @@ export async function runPreviewAudit(githubUrl, liveUrl, opts = {}) {
98
98
  live_url: liveUrl,
99
99
  force: opts.force === true,
100
100
  source: opts.source ?? null,
101
+ // Workspace = explicit override of the server's monorepo auto-pick.
102
+ // null/undefined = let the server pick (default); a path like
103
+ // 'apps/web' = audit only that workspace.
104
+ workspace: opts.workspace ?? null,
101
105
  }),
102
106
  });
103
107
  const body = await res.json().catch(() => ({ error: 'invalid_json' }));
@@ -2,12 +2,19 @@
2
2
  // { kind: 'remote-url', github_url } or { kind: 'local', path, github_url? }.
3
3
  //
4
4
  // Accepted inputs (all resolve to a GitHub HTTPS URL):
5
- // · (omitted) → cwd · read `git remote get-url origin`
6
- // · ./my-repo · /abs/path → local dir · same remote inference
7
- // · github.com/owner/repo → bare host shorthand
8
- // · https://github.com/owner/repo → full URL
9
- // · git@github.com:owner/repo.git → ssh form (common in `git remote`)
10
- // · owner/repo → last-ditch shorthand (2 segments, no dot)
5
+ // · (omitted) → cwd · read `git remote get-url origin`
6
+ // · ./my-repo · /abs/path → local dir · same remote inference
7
+ // · github.com/owner/repo → bare host shorthand
8
+ // · https://github.com/owner/repo → full URL
9
+ // · git@github.com:owner/repo.git → ssh form (common in `git remote`)
10
+ // · owner/repo → last-ditch shorthand (2 segments, no dot)
11
+ // · github.com/owner/repo/apps/web → inline workspace (post-2026-05-06)
12
+ // · https://github.com/owner/repo/tree/main/apps/web → GitHub browse URL (paste-friendly)
13
+ //
14
+ // Workspace selection precedence:
15
+ // 1. --workspace <path> CLI flag (highest · explicit override)
16
+ // 2. Inline path in target URL (sub-path after <owner>/<repo>)
17
+ // 3. Auto-pick on the server (Edge Function priority-name → repo-name → file count)
11
18
  import { execSync } from 'node:child_process';
12
19
  import { existsSync, statSync } from 'node:fs';
13
20
  import { resolve } from 'node:path';
@@ -45,21 +52,65 @@ export async function verifyRemoteExists(githubUrl) {
45
52
  return { exists: true, ambiguous: true };
46
53
  }
47
54
  }
48
- const GITHUB_URL_RE = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
49
- const GITHUB_HOST_RE = /^github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
55
+ // `github_url` patterns. Each captures (owner, repo, optional sub-path).
56
+ // The sub-path lets users inline a workspace inside the URL itself instead
57
+ // of using the --workspace flag (`github.com/o/r/apps/web` or paste of a
58
+ // GitHub browse URL `https://github.com/o/r/tree/main/apps/web`).
59
+ const GITHUB_URL_RE = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/(.+))?\/?$/i;
60
+ const GITHUB_HOST_RE = /^github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/(.+))?\/?$/i;
50
61
  const GITHUB_SSH_RE = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
51
62
  const SLUG_RE = /^([A-Za-z0-9][\w.-]*)\/([A-Za-z0-9][\w.-]*)$/;
52
63
  function canonical(owner, repo) {
53
64
  return `https://github.com/${owner}/${repo.replace(/\.git$/, '')}`;
54
65
  }
66
+ /**
67
+ * Normalize the sub-path portion of a GitHub URL into a workspace path.
68
+ * Handles the GitHub browse URL prefixes (`tree/<branch>/...` ·
69
+ * `blob/<branch>/...`) so users can paste any URL they're looking at.
70
+ *
71
+ * Rejects non-workspace prefixes (issues · pulls · actions · settings ·
72
+ * releases · wiki · etc.) — those aren't workspaces, returning null
73
+ * means "no inline workspace" rather than a confusing error.
74
+ */
75
+ function normalizeSubpath(raw) {
76
+ if (!raw)
77
+ return null;
78
+ let s = raw.replace(/^\/+/, '').replace(/\/+$/, '');
79
+ if (!s)
80
+ return null;
81
+ // GitHub browse URL prefixes — strip and keep the path part
82
+ const browseMatch = s.match(/^(?:tree|blob)\/[^/]+\/(.+)$/i);
83
+ if (browseMatch)
84
+ s = browseMatch[1];
85
+ // Non-workspace GitHub URL prefixes — ignore the sub-path entirely
86
+ const NON_WORKSPACE_PREFIXES = [
87
+ 'issues', 'pull', 'pulls', 'actions', 'settings', 'releases',
88
+ 'wiki', 'security', 'pulse', 'graphs', 'network', 'commits',
89
+ 'commit', 'compare', 'tags', 'branches', 'discussions', 'projects',
90
+ ];
91
+ const head = s.split('/')[0].toLowerCase();
92
+ if (NON_WORKSPACE_PREFIXES.includes(head))
93
+ return null;
94
+ return s;
95
+ }
55
96
  function matchUrl(raw) {
56
97
  const s = raw.trim();
57
- const urlMatch = s.match(GITHUB_URL_RE) ?? s.match(GITHUB_HOST_RE) ?? s.match(GITHUB_SSH_RE);
58
- if (urlMatch)
59
- return { owner: urlMatch[1], repo: urlMatch[2] };
98
+ const urlMatch = s.match(GITHUB_URL_RE) ?? s.match(GITHUB_HOST_RE);
99
+ if (urlMatch) {
100
+ return {
101
+ owner: urlMatch[1],
102
+ repo: urlMatch[2],
103
+ workspace: normalizeSubpath(urlMatch[3]),
104
+ };
105
+ }
106
+ const sshMatch = s.match(GITHUB_SSH_RE);
107
+ if (sshMatch) {
108
+ return { owner: sshMatch[1], repo: sshMatch[2], workspace: null };
109
+ }
60
110
  const slug = s.match(SLUG_RE);
61
- if (slug && !slug[2].includes('.'))
62
- return { owner: slug[1], repo: slug[2] };
111
+ if (slug && !slug[2].includes('.')) {
112
+ return { owner: slug[1], repo: slug[2], workspace: null };
113
+ }
63
114
  return null;
64
115
  }
65
116
  function gitRemoteOrigin(cwd) {
@@ -79,7 +130,11 @@ function gitRemoteOrigin(cwd) {
79
130
  return null;
80
131
  }
81
132
  }
82
- export function resolveTarget(rawArg) {
133
+ export function resolveTarget(rawArg, opts = {}) {
134
+ // Workspace precedence: --workspace flag wins over inline URL path.
135
+ // Both normalized through the same path so the server receives a
136
+ // single shape.
137
+ const flagWorkspace = normalizeSubpath(opts.workspace ?? null);
83
138
  // 1 · Explicit URL forms
84
139
  if (rawArg) {
85
140
  const m = matchUrl(rawArg);
@@ -88,6 +143,7 @@ export function resolveTarget(rawArg) {
88
143
  kind: 'remote-url',
89
144
  github_url: canonical(m.owner, m.repo),
90
145
  slug: `${m.owner}/${m.repo.replace(/\.git$/, '')}`,
146
+ workspace: flagWorkspace ?? m.workspace ?? null,
91
147
  };
92
148
  }
93
149
  }
@@ -115,5 +171,6 @@ export function resolveTarget(rawArg) {
115
171
  github_url: canonical(m.owner, m.repo),
116
172
  localPath: path,
117
173
  slug: `${m.owner}/${m.repo.replace(/\.git$/, '')}`,
174
+ workspace: flagWorkspace, // local repos don't carry an inline URL workspace
118
175
  };
119
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commitshow",
3
- "version": "0.3.28",
3
+ "version": "0.3.29",
4
4
  "description": "commit.show CLI \u2014 audit any vibe-coded project from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {