@surething/cockpit 1.0.214 → 1.0.216
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/.next-prod/BUILD_ID +1 -1
- package/.next-prod/app-path-routes-manifest.json +3 -3
- package/.next-prod/build-manifest.json +2 -2
- package/.next-prod/prerender-manifest.json +3 -3
- package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
- package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_global-error.html +1 -1
- package/.next-prod/server/app/_global-error.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
- package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/_not-found.html +1 -1
- package/.next-prod/server/app/_not-found.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/codex/route.js +2 -2
- package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/deepseek/route.js +2 -2
- package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/kimi/route.js +2 -2
- package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/ollama/route.js +2 -2
- package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/chat/route.js +2 -2
- package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/commands/route.js +1 -1
- package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projectGraph/search/route.js +1 -1
- package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
- package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
- package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next-prod/server/app/page.js.nft.json +1 -1
- package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/project/page.js.nft.json +1 -1
- package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
- package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
- package/.next-prod/server/app-paths-manifest.json +3 -3
- package/.next-prod/server/chunks/2939.js +1 -1
- package/.next-prod/server/chunks/8916.js +1 -1
- package/.next-prod/server/chunks/9658.js +3 -3
- package/.next-prod/server/chunks/{7828.js → 9877.js} +11 -2
- package/.next-prod/server/middleware-build-manifest.js +1 -1
- package/.next-prod/server/pages/404.html +1 -1
- package/.next-prod/server/pages/500.html +1 -1
- package/.next-prod/server/server-reference-manifest.json +1 -1
- package/.next-prod/static/chunks/{5188-38f55b21ae1eeb28.js → 5188-415582403ef0e29c.js} +1 -1
- package/.next-prod/static/chunks/6345-e5ceeb2aeb698eb6.js +14 -0
- package/.next-prod/static/css/{f016b445331fc5a2.css → cc6d733cdf607b30.css} +1 -1
- package/.next-prod/trace +13 -13
- package/.next-prod/trace-build +1 -1
- package/README.md +8 -7
- package/README.zh.md +8 -7
- package/bin/cock-browser.mjs +93 -3
- package/bin/cock-codegraph.mjs +907 -0
- package/bin/cock-terminal.mjs +5 -0
- package/bin/cock.mjs +4 -4
- package/bin/cockpit-dev.mjs +9 -0
- package/bin/setup-dev.mjs +92 -0
- package/package.json +3 -2
- package/.next-prod/static/chunks/6345-fc2d45a72316c5f8.js +0 -14
- package/bin/cock-affected.mjs +0 -190
- package/bin/cock-dev.mjs +0 -6
- /package/.next-prod/static/{ekMRRxcvpy2AIjMrVn4lK → GAYKr2BmQpFqJgRJfvQ3D}/_buildManifest.js +0 -0
- /package/.next-prod/static/{ekMRRxcvpy2AIjMrVn4lK → GAYKr2BmQpFqJgRJfvQ3D}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cock codegraph — unified CLI for all 10 /api/projectGraph/* endpoints.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* cock codegraph List subcommands
|
|
7
|
+
* cock codegraph <sub> --help Per-subcommand help
|
|
8
|
+
* cock codegraph <sub> ... [--json] All subcommands accept --json
|
|
9
|
+
*
|
|
10
|
+
* Subcommands map 1:1 to HTTP endpoints; the CLI is a thin shim that
|
|
11
|
+
* shares the running server's live CodeIndex (no per-invocation parse).
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 output produced
|
|
15
|
+
* 1 empty result (e.g. no callers; lets shell pipelines short-circuit)
|
|
16
|
+
* 2 argument / usage / 4xx server error
|
|
17
|
+
* 3 Cockpit server not reachable on COCKPIT_PORT (default 3457)
|
|
18
|
+
*/
|
|
19
|
+
import { argv, cwd, exit, stderr, stdin, stdout, env } from 'node:process';
|
|
20
|
+
|
|
21
|
+
const PORT = env.COCKPIT_PORT || '3457';
|
|
22
|
+
const HOST = env.COCKPIT_HOST || 'localhost';
|
|
23
|
+
const BASE = `http://${HOST}:${PORT}`;
|
|
24
|
+
|
|
25
|
+
const SUBCMDS = [
|
|
26
|
+
'search', 'callers', 'callees', 'impact', 'file', 'coedit',
|
|
27
|
+
'context', 'related', 'risk', 'affected',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const sub = argv[2];
|
|
31
|
+
|
|
32
|
+
if (!sub || sub === '-h' || sub === '--help') {
|
|
33
|
+
printTopHelp();
|
|
34
|
+
exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!SUBCMDS.includes(sub)) {
|
|
38
|
+
stderr.write(`cock codegraph: unknown subcommand "${sub}"\n`);
|
|
39
|
+
stderr.write(`Available: ${SUBCMDS.join(', ')}\n`);
|
|
40
|
+
exit(2);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Per-subcommand args are everything after the subcommand name.
|
|
44
|
+
const subArgs = argv.slice(3);
|
|
45
|
+
|
|
46
|
+
// Common flag: --json / --help.
|
|
47
|
+
const wantJson = subArgs.includes('--json');
|
|
48
|
+
const wantHelp = subArgs.includes('--help') || subArgs.includes('-h');
|
|
49
|
+
|
|
50
|
+
if (wantHelp) {
|
|
51
|
+
printSubHelp(sub);
|
|
52
|
+
exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Dispatch.
|
|
56
|
+
try {
|
|
57
|
+
switch (sub) {
|
|
58
|
+
case 'search': await cmdSearch(); break;
|
|
59
|
+
case 'callers': await cmdCallers(); break;
|
|
60
|
+
case 'callees': await cmdCallees(); break;
|
|
61
|
+
case 'impact': await cmdImpact(); break;
|
|
62
|
+
case 'file': await cmdFile(); break;
|
|
63
|
+
case 'coedit': await cmdCoedit(); break;
|
|
64
|
+
case 'context': await cmdContext(); break;
|
|
65
|
+
case 'related': await cmdRelated(); break;
|
|
66
|
+
case 'risk': await cmdRisk(); break;
|
|
67
|
+
case 'affected': await cmdAffected(); break;
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
stderr.write(`codegraph ${sub}: ${err?.message || err}\n`);
|
|
71
|
+
exit(2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// HTTP helpers
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
async function get(path, params) {
|
|
79
|
+
const url = new URL(`${BASE}${path}`);
|
|
80
|
+
url.searchParams.set('cwd', cwd());
|
|
81
|
+
for (const [k, v] of Object.entries(params)) {
|
|
82
|
+
if (v === undefined || v === null || v === '') continue;
|
|
83
|
+
url.searchParams.set(k, String(v));
|
|
84
|
+
}
|
|
85
|
+
let resp;
|
|
86
|
+
try {
|
|
87
|
+
resp = await fetch(url);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
stderr.write(
|
|
90
|
+
`codegraph: cannot reach Cockpit at ${BASE}\n` +
|
|
91
|
+
` (${err?.code ?? err?.name ?? 'fetch failed'})\n` +
|
|
92
|
+
` Start it with: cock\n`,
|
|
93
|
+
);
|
|
94
|
+
exit(3);
|
|
95
|
+
}
|
|
96
|
+
if (!resp.ok) {
|
|
97
|
+
const text = await resp.text();
|
|
98
|
+
stderr.write(`codegraph: server returned ${resp.status}\n${text}\n`);
|
|
99
|
+
exit(2);
|
|
100
|
+
}
|
|
101
|
+
return resp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function post(path, body) {
|
|
105
|
+
let resp;
|
|
106
|
+
try {
|
|
107
|
+
resp = await fetch(`${BASE}${path}`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
});
|
|
112
|
+
} catch (err) {
|
|
113
|
+
stderr.write(
|
|
114
|
+
`codegraph: cannot reach Cockpit at ${BASE}\n` +
|
|
115
|
+
` (${err?.code ?? err?.name ?? 'fetch failed'})\n` +
|
|
116
|
+
` Start it with: cock\n`,
|
|
117
|
+
);
|
|
118
|
+
exit(3);
|
|
119
|
+
}
|
|
120
|
+
if (!resp.ok) {
|
|
121
|
+
const text = await resp.text();
|
|
122
|
+
stderr.write(`codegraph: server returned ${resp.status}\n${text}\n`);
|
|
123
|
+
exit(2);
|
|
124
|
+
}
|
|
125
|
+
return resp;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Tiny arg parser — pulls --flag values without depending on yargs etc.
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
function getFlag(args, name) {
|
|
133
|
+
const i = args.indexOf(name);
|
|
134
|
+
if (i < 0) return undefined;
|
|
135
|
+
const v = args[i + 1];
|
|
136
|
+
if (v === undefined || v.startsWith('-')) return undefined;
|
|
137
|
+
return v;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getInt(args, name) {
|
|
141
|
+
const v = getFlag(args, name);
|
|
142
|
+
if (v === undefined) return undefined;
|
|
143
|
+
const n = parseInt(v, 10);
|
|
144
|
+
if (!Number.isFinite(n)) {
|
|
145
|
+
stderr.write(`${name} requires a number, got "${v}"\n`);
|
|
146
|
+
exit(2);
|
|
147
|
+
}
|
|
148
|
+
return n;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function positional(args) {
|
|
152
|
+
return args.filter((a, i) => {
|
|
153
|
+
if (a.startsWith('-')) return false;
|
|
154
|
+
const prev = args[i - 1];
|
|
155
|
+
// Skip values that follow a known flag-with-value.
|
|
156
|
+
const flagsWithValue = new Set([
|
|
157
|
+
'--file', '--depth', '--top', '--limit', '--commits',
|
|
158
|
+
'--query', '--cursor', '--open', '--filter', '--include',
|
|
159
|
+
]);
|
|
160
|
+
if (prev && flagsWithValue.has(prev)) return false;
|
|
161
|
+
return true;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function readStdinLines() {
|
|
166
|
+
let buf = '';
|
|
167
|
+
stdin.setEncoding('utf8');
|
|
168
|
+
for await (const chunk of stdin) buf += chunk;
|
|
169
|
+
return buf.split('\n').map((s) => s.trim()).filter(Boolean);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function emitJson(obj) {
|
|
173
|
+
stdout.write(JSON.stringify(obj, null, 2) + '\n');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// search
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
async function cmdSearch() {
|
|
181
|
+
const pos = positional(subArgs);
|
|
182
|
+
const q = pos[0];
|
|
183
|
+
const limit = getInt(subArgs, '--limit') ?? 15;
|
|
184
|
+
if (!q) {
|
|
185
|
+
stderr.write('codegraph search: missing <query>\n');
|
|
186
|
+
exit(2);
|
|
187
|
+
}
|
|
188
|
+
const resp = await get('/api/projectGraph/search', { q, limit });
|
|
189
|
+
const data = await resp.json();
|
|
190
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
191
|
+
|
|
192
|
+
const files = data.files || [];
|
|
193
|
+
const syms = data.symbols || [];
|
|
194
|
+
if (files.length === 0 && syms.length === 0) exit(1);
|
|
195
|
+
|
|
196
|
+
for (const f of files) {
|
|
197
|
+
stdout.write(`file\t${f.target?.filePath ?? f.label}\n`);
|
|
198
|
+
}
|
|
199
|
+
for (const s of syms) {
|
|
200
|
+
const t = s.target;
|
|
201
|
+
stdout.write(`sym \t${t.filePath}:${t.line}\t${t.symbolKind}\t${t.qualifiedName}\n`);
|
|
202
|
+
}
|
|
203
|
+
exit(0);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// callers / callees (shared shape)
|
|
208
|
+
// ============================================================================
|
|
209
|
+
|
|
210
|
+
async function cmdCallers() { await runCallSide('callers'); }
|
|
211
|
+
async function cmdCallees() { await runCallSide('callees'); }
|
|
212
|
+
|
|
213
|
+
async function runCallSide(kind) {
|
|
214
|
+
const pos = positional(subArgs);
|
|
215
|
+
const qname = pos[0];
|
|
216
|
+
const filePath = getFlag(subArgs, '--file');
|
|
217
|
+
if (!qname) {
|
|
218
|
+
stderr.write(`codegraph ${kind}: missing <qname>\n`);
|
|
219
|
+
exit(2);
|
|
220
|
+
}
|
|
221
|
+
const resp = await get(`/api/projectGraph/${kind}`, { qname, filePath });
|
|
222
|
+
const data = await resp.json();
|
|
223
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
224
|
+
|
|
225
|
+
const list = data[kind] || [];
|
|
226
|
+
if (data.ambiguousIn) {
|
|
227
|
+
stderr.write(`# ambiguousIn: ${data.ambiguousIn.join(', ')} — pass --file to disambiguate\n`);
|
|
228
|
+
}
|
|
229
|
+
if (list.length === 0) exit(1);
|
|
230
|
+
for (const item of list) {
|
|
231
|
+
const node = item.caller || item.callee;
|
|
232
|
+
const lines = (item.callLines || []).join(',');
|
|
233
|
+
stdout.write(`${node.filePath}:${node.startLine}\t${node.qualifiedName}\t[${lines}]\n`);
|
|
234
|
+
}
|
|
235
|
+
exit(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// impact
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
async function cmdImpact() {
|
|
243
|
+
const pos = positional(subArgs);
|
|
244
|
+
const qname = pos[0];
|
|
245
|
+
const filePath = getFlag(subArgs, '--file');
|
|
246
|
+
const depth = getInt(subArgs, '--depth') ?? 2;
|
|
247
|
+
if (!qname) {
|
|
248
|
+
stderr.write('codegraph impact: missing <qname>\n');
|
|
249
|
+
exit(2);
|
|
250
|
+
}
|
|
251
|
+
const resp = await get('/api/projectGraph/impact', { qname, filePath, depth });
|
|
252
|
+
const data = await resp.json();
|
|
253
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
254
|
+
|
|
255
|
+
const nodes = data.nodes || [];
|
|
256
|
+
if (data.truncated) {
|
|
257
|
+
stderr.write(`# impact truncated at server cap (500). Use 'risk' for ranked + bounded output.\n`);
|
|
258
|
+
}
|
|
259
|
+
if (nodes.length === 0) exit(1);
|
|
260
|
+
for (const n of nodes) {
|
|
261
|
+
const s = n.symbol;
|
|
262
|
+
stdout.write(`d=${n.depth}\t${s.filePath}:${s.startLine}\t${s.qualifiedName}\n`);
|
|
263
|
+
}
|
|
264
|
+
exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// file
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
async function cmdFile() {
|
|
272
|
+
const pos = positional(subArgs);
|
|
273
|
+
const path = pos[0];
|
|
274
|
+
if (!path) {
|
|
275
|
+
stderr.write('codegraph file: missing <path>\n');
|
|
276
|
+
exit(2);
|
|
277
|
+
}
|
|
278
|
+
const resp = await get('/api/projectGraph/file', { path });
|
|
279
|
+
const data = await resp.json();
|
|
280
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
281
|
+
|
|
282
|
+
const syms = data.symbols || [];
|
|
283
|
+
if (syms.length === 0) exit(1);
|
|
284
|
+
for (const s of syms) {
|
|
285
|
+
stdout.write(`${s.kind}\t${s.startLine}-${s.endLine}\t${s.qualifiedName}\n`);
|
|
286
|
+
}
|
|
287
|
+
exit(0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// coedit
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
async function cmdCoedit() {
|
|
295
|
+
const pos = positional(subArgs);
|
|
296
|
+
const filePath = pos[0];
|
|
297
|
+
const commits = getInt(subArgs, '--commits') ?? 100;
|
|
298
|
+
if (!filePath) {
|
|
299
|
+
stderr.write('codegraph coedit: missing <path>\n');
|
|
300
|
+
exit(2);
|
|
301
|
+
}
|
|
302
|
+
const resp = await get('/api/projectGraph/coedit', { filePath, commits });
|
|
303
|
+
const data = await resp.json();
|
|
304
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
305
|
+
|
|
306
|
+
const history = data.history || [];
|
|
307
|
+
const uncommitted = data.uncommitted || [];
|
|
308
|
+
if (history.length === 0 && uncommitted.length === 0) {
|
|
309
|
+
stderr.write(`# no cooccurrence (totalCommits=${data.totalCommits})\n`);
|
|
310
|
+
exit(1);
|
|
311
|
+
}
|
|
312
|
+
if (history.length > 0) {
|
|
313
|
+
stdout.write(`# history (cooccurrence/total)\n`);
|
|
314
|
+
for (const h of history) {
|
|
315
|
+
stdout.write(`${h.cooccurrence}/${data.totalCommits}\t${h.file}\n`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (uncommitted.length > 0) {
|
|
319
|
+
stdout.write(`# uncommitted (working tree)\n`);
|
|
320
|
+
for (const f of uncommitted) stdout.write(`-\t${f}\n`);
|
|
321
|
+
}
|
|
322
|
+
exit(0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// context
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
async function cmdContext() {
|
|
330
|
+
const query = getFlag(subArgs, '--query');
|
|
331
|
+
const cursor = getFlag(subArgs, '--cursor');
|
|
332
|
+
const openFiles = getFlag(subArgs, '--open');
|
|
333
|
+
const top = getInt(subArgs, '--top') ?? 15;
|
|
334
|
+
if (!query && !cursor && !openFiles) {
|
|
335
|
+
stderr.write('codegraph context: need at least one of --query / --cursor / --open\n');
|
|
336
|
+
exit(2);
|
|
337
|
+
}
|
|
338
|
+
const resp = await get('/api/projectGraph/context', {
|
|
339
|
+
query, cursor, openFiles, topK: top,
|
|
340
|
+
});
|
|
341
|
+
const data = await resp.json();
|
|
342
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
343
|
+
|
|
344
|
+
if (data.cursorResolution && !data.cursorResolution.matched) {
|
|
345
|
+
stderr.write(`# cursor not matched: ${data.cursorResolution.notes || '(no note)'}\n`);
|
|
346
|
+
} else if (data.cursorResolution?.notes) {
|
|
347
|
+
stderr.write(`# cursor: ${data.cursorResolution.notes}\n`);
|
|
348
|
+
}
|
|
349
|
+
if (data.degraded) {
|
|
350
|
+
stderr.write(`# degraded: ${data.degradedReason}\n`);
|
|
351
|
+
}
|
|
352
|
+
const results = data.results || [];
|
|
353
|
+
if (results.length === 0) exit(1);
|
|
354
|
+
for (const r of results) {
|
|
355
|
+
const sigs = (r.signals || []).map((s) => s.type).join(',');
|
|
356
|
+
stdout.write(`${r.score.toFixed(2)}\t${r.filePath}:${r.startLine}\t${r.qualifiedName}\t[${sigs}]\n`);
|
|
357
|
+
}
|
|
358
|
+
exit(0);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ============================================================================
|
|
362
|
+
// related
|
|
363
|
+
// ============================================================================
|
|
364
|
+
|
|
365
|
+
async function cmdRelated() {
|
|
366
|
+
const pos = positional(subArgs);
|
|
367
|
+
const qname = pos[0];
|
|
368
|
+
const filePath = getFlag(subArgs, '--file');
|
|
369
|
+
const top = getInt(subArgs, '--top') ?? 10;
|
|
370
|
+
const include = getFlag(subArgs, '--include') ?? 'all';
|
|
371
|
+
if (!qname) {
|
|
372
|
+
stderr.write('codegraph related: missing <qname>\n');
|
|
373
|
+
exit(2);
|
|
374
|
+
}
|
|
375
|
+
const resp = await get('/api/projectGraph/related', {
|
|
376
|
+
qname, filePath, topK: top, include,
|
|
377
|
+
});
|
|
378
|
+
const data = await resp.json();
|
|
379
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
380
|
+
|
|
381
|
+
if (data.ambiguousIn) {
|
|
382
|
+
stderr.write(`# ambiguousIn: ${data.ambiguousIn.join(', ')} — pass --file to disambiguate\n`);
|
|
383
|
+
}
|
|
384
|
+
if (data.degraded) stderr.write(`# degraded: ${data.degradedReason}\n`);
|
|
385
|
+
const results = data.results || [];
|
|
386
|
+
if (results.length === 0) exit(1);
|
|
387
|
+
for (const r of results) {
|
|
388
|
+
const rels = (r.relations || []).map((x) => x.type).join(',');
|
|
389
|
+
stdout.write(`${r.score.toFixed(2)}\t${r.filePath}:${r.startLine}\t${r.qualifiedName}\t<${rels}>\n`);
|
|
390
|
+
}
|
|
391
|
+
exit(0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// risk
|
|
396
|
+
// ============================================================================
|
|
397
|
+
|
|
398
|
+
async function cmdRisk() {
|
|
399
|
+
const pos = positional(subArgs);
|
|
400
|
+
const qname = pos[0];
|
|
401
|
+
const filePath = getFlag(subArgs, '--file');
|
|
402
|
+
const depth = getInt(subArgs, '--depth') ?? 2;
|
|
403
|
+
const top = getInt(subArgs, '--top') ?? 20;
|
|
404
|
+
if (!qname) {
|
|
405
|
+
stderr.write('codegraph risk: missing <qname>\n');
|
|
406
|
+
exit(2);
|
|
407
|
+
}
|
|
408
|
+
const resp = await get('/api/projectGraph/risk', {
|
|
409
|
+
qname, filePath, depth, topK: top,
|
|
410
|
+
});
|
|
411
|
+
const data = await resp.json();
|
|
412
|
+
if (wantJson) { emitJson(data); exit(0); }
|
|
413
|
+
|
|
414
|
+
if (data.degraded) stderr.write(`# degraded: ${data.degradedReason}\n`);
|
|
415
|
+
const highRisk = data.highRisk || [];
|
|
416
|
+
if (highRisk.length === 0 && !data.target) exit(1);
|
|
417
|
+
|
|
418
|
+
stdout.write(`# total impacted: ${data.totalImpactedNodes}\n`);
|
|
419
|
+
for (const n of highRisk) {
|
|
420
|
+
const tags = (n.tags || []).join(',') || '-';
|
|
421
|
+
stdout.write(`${n.risk.score.toFixed(3)}\td=${n.depth}\t${n.filePath}:${n.startLine}\t${n.qualifiedName}\t[${tags}]\n`);
|
|
422
|
+
}
|
|
423
|
+
const tests = data.suggestedTests || [];
|
|
424
|
+
if (tests.length > 0) {
|
|
425
|
+
stdout.write(`\n# suggestedTests\n`);
|
|
426
|
+
for (const t of tests) {
|
|
427
|
+
const covered = (t.coveredNodes || []).slice(0, 3).join(', ');
|
|
428
|
+
stdout.write(`${t.reason}\t${t.filePath}\t(${covered}${t.coveredNodes?.length > 3 ? ',…' : ''})\n`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
exit(0);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// affected — inherits the original cock-affected.mjs behaviour
|
|
436
|
+
// ============================================================================
|
|
437
|
+
|
|
438
|
+
async function cmdAffected() {
|
|
439
|
+
// Affected is special: accepts file list via positional or --stdin, and
|
|
440
|
+
// has dedicated text modes (--as-cmd / --json / plain).
|
|
441
|
+
//
|
|
442
|
+
// --as-cmd is intentionally generic (not --as-jest etc.) because:
|
|
443
|
+
// - test runners differ across project ecosystems (jest, vitest, bun
|
|
444
|
+
// test, playwright test, pytest, go test, cargo test, ...)
|
|
445
|
+
// - our isTestFile() already detects test files for JS/TS/Python/Go/Rust
|
|
446
|
+
// - hardcoding `jest` here would mis-format outputs in any non-Jest
|
|
447
|
+
// project, while still claiming the files are runnable.
|
|
448
|
+
// The user passes whatever shell prefix matches their setup.
|
|
449
|
+
let useStdin = false;
|
|
450
|
+
let asCmd; // string runner prefix (e.g. "jest", "vitest run", "pytest -v")
|
|
451
|
+
let includeAll = false;
|
|
452
|
+
const files = [];
|
|
453
|
+
let depth = 10;
|
|
454
|
+
let filter;
|
|
455
|
+
for (let i = 0; i < subArgs.length; i++) {
|
|
456
|
+
const a = subArgs[i];
|
|
457
|
+
if (a === '--stdin') useStdin = true;
|
|
458
|
+
else if (a === '--as-cmd') {
|
|
459
|
+
asCmd = subArgs[++i];
|
|
460
|
+
if (!asCmd) {
|
|
461
|
+
stderr.write('codegraph affected: --as-cmd requires a runner string (e.g. "jest", "vitest run", "pytest -v")\n');
|
|
462
|
+
exit(2);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else if (a === '--include-all') includeAll = true;
|
|
466
|
+
else if (a === '--json') { /* already handled */ }
|
|
467
|
+
else if (a === '--depth') { depth = parseInt(subArgs[++i], 10) || depth; }
|
|
468
|
+
else if (a === '--filter') { filter = subArgs[++i]; }
|
|
469
|
+
else if (a.startsWith('-')) {
|
|
470
|
+
stderr.write(`codegraph affected: unknown flag "${a}"\n`);
|
|
471
|
+
exit(2);
|
|
472
|
+
} else {
|
|
473
|
+
files.push(a);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (useStdin) {
|
|
478
|
+
const lines = await readStdinLines();
|
|
479
|
+
files.push(...lines);
|
|
480
|
+
}
|
|
481
|
+
if (files.length === 0) {
|
|
482
|
+
stderr.write('codegraph affected: no files provided (pass file args or use --stdin)\n');
|
|
483
|
+
exit(2);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const payload = {
|
|
487
|
+
cwd: cwd(),
|
|
488
|
+
files,
|
|
489
|
+
depth,
|
|
490
|
+
filter,
|
|
491
|
+
includeAll,
|
|
492
|
+
format: wantJson ? 'json' : 'plain',
|
|
493
|
+
};
|
|
494
|
+
const resp = await post('/api/projectGraph/affected', payload);
|
|
495
|
+
|
|
496
|
+
if (wantJson) {
|
|
497
|
+
const data = await resp.json();
|
|
498
|
+
stdout.write(JSON.stringify(data, null, 2) + '\n');
|
|
499
|
+
exit(data?.testFiles?.length > 0 ? 0 : 1);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const unresolvedHdr = parseInt(resp.headers.get('x-unresolved-count') ?? '0', 10) || 0;
|
|
503
|
+
const truncated = resp.headers.get('x-truncated') === 'true';
|
|
504
|
+
const text = await resp.text();
|
|
505
|
+
const paths = text.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
506
|
+
|
|
507
|
+
if (unresolvedHdr > 0) {
|
|
508
|
+
stderr.write(
|
|
509
|
+
`codegraph affected: ${unresolvedHdr} input file(s) not found in CodeIndex (deleted, generated, or unsupported language)\n`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (truncated) {
|
|
513
|
+
stderr.write(
|
|
514
|
+
`codegraph affected: BFS hit node cap — results may miss deeper tests\n`,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (asCmd) {
|
|
519
|
+
if (paths.length === 0) {
|
|
520
|
+
stderr.write('codegraph affected: no test files — nothing to run\n');
|
|
521
|
+
exit(1);
|
|
522
|
+
}
|
|
523
|
+
stdout.write(`${asCmd} ${paths.map((p) => JSON.stringify(p)).join(' ')}\n`);
|
|
524
|
+
exit(0);
|
|
525
|
+
}
|
|
526
|
+
if (paths.length === 0) exit(1);
|
|
527
|
+
stdout.write(paths.join('\n') + '\n');
|
|
528
|
+
exit(0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ============================================================================
|
|
532
|
+
// Help text
|
|
533
|
+
// ============================================================================
|
|
534
|
+
|
|
535
|
+
function printTopHelp() {
|
|
536
|
+
stdout.write(`Usage: cock codegraph <subcommand> [options]
|
|
537
|
+
|
|
538
|
+
Lookups (mirror existing /api/projectGraph/* endpoints — coordinates only):
|
|
539
|
+
search <query> Find symbols by name (file + qname hits)
|
|
540
|
+
callers <qname> [--file PATH] Direct callers of a symbol
|
|
541
|
+
callees <qname> [--file PATH] What a symbol calls
|
|
542
|
+
impact <qname> [--depth N=2] Transitive callers BFS (use 'risk' for ranked output)
|
|
543
|
+
file <path> Symbol tree of a file
|
|
544
|
+
coedit <path> [--commits N=100] Files co-edited in git history
|
|
545
|
+
|
|
546
|
+
Analytics (PPR / TF-IDF / Louvain / coedit blended):
|
|
547
|
+
context --query Q [--cursor C] [--open F1,F2,...]
|
|
548
|
+
[--top N=15] Top-K semantically relevant coordinates
|
|
549
|
+
related <qname> [--file PATH]
|
|
550
|
+
[--top N=10] Broader neighbours: caller/callee + ppr + coedit + community
|
|
551
|
+
risk <qname> [--depth N=2] [--top N=20]
|
|
552
|
+
Risk-scored impact + suggestedTests
|
|
553
|
+
affected <files…|--stdin>
|
|
554
|
+
[--depth N=10] [--filter G]
|
|
555
|
+
[--as-cmd RUNNER] Test files transitively affected (CI / xargs)
|
|
556
|
+
[--include-all]
|
|
557
|
+
|
|
558
|
+
Common flags (all subcommands):
|
|
559
|
+
--json Emit raw JSON response (full schema; see per-cmd --help)
|
|
560
|
+
--help, -h Subcommand-specific help (output format + exit codes + examples)
|
|
561
|
+
|
|
562
|
+
Plain output format (TAB-separated, one row per result):
|
|
563
|
+
search sym\\t<file>:<line>\\t<kind>\\t<qname> or file\\t<file>
|
|
564
|
+
callers <file>:<line>\\t<qname>\\t[<callLines>]
|
|
565
|
+
callees <file>:<line>\\t<qname>\\t[<callLines>]
|
|
566
|
+
impact d=<depth>\\t<file>:<line>\\t<qname>
|
|
567
|
+
file <kind>\\t<startLine>-<endLine>\\t<qname>
|
|
568
|
+
coedit <cooccur>/<total>\\t<file> (after '# history' comment)
|
|
569
|
+
context <score>\\t<file>:<line>\\t<qname>\\t[<signals>]
|
|
570
|
+
related <score>\\t<file>:<line>\\t<qname>\\t<<relations>>
|
|
571
|
+
risk <score>\\td=<depth>\\t<file>:<line>\\t<qname>\\t[<tags>]
|
|
572
|
+
affected <file> (one test path per line)
|
|
573
|
+
|
|
574
|
+
Diagnostics on stderr (don't break shell pipelines):
|
|
575
|
+
# ambiguousIn: <files…> Same qname in multiple files — pass --file
|
|
576
|
+
# cursor: <note> Cursor format auto-corrected ('.' → '::', etc.)
|
|
577
|
+
# degraded: <reason> analytics-warming / coedit-unavailable / truncated
|
|
578
|
+
|
|
579
|
+
Exit codes:
|
|
580
|
+
0 output produced
|
|
581
|
+
1 empty result (no callers / no tests / no hits) — short-circuit shell pipelines
|
|
582
|
+
2 argument or 4xx server error
|
|
583
|
+
3 Cockpit server not reachable (start it: cock <project-path>)
|
|
584
|
+
|
|
585
|
+
Prerequisites:
|
|
586
|
+
Cockpit server running at ${BASE}
|
|
587
|
+
(override host/port: COCKPIT_HOST, COCKPIT_PORT)
|
|
588
|
+
|
|
589
|
+
Examples:
|
|
590
|
+
cock codegraph search getCodeIndex
|
|
591
|
+
cock codegraph related getCodeIndex --top 5
|
|
592
|
+
cock codegraph risk searchIndex --depth 2
|
|
593
|
+
git diff --name-only | cock codegraph affected --stdin # → newline test paths
|
|
594
|
+
git diff --name-only | cock codegraph affected --stdin --as-cmd jest # → jest "a" "b" …
|
|
595
|
+
git diff --name-only | cock codegraph affected --stdin --as-cmd "vitest run" # → vitest run "a" "b" …
|
|
596
|
+
`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Per-subcommand help. Each entry follows the SAME shape so an LLM agent
|
|
601
|
+
* scanning the output knows exactly where to find what it needs:
|
|
602
|
+
*
|
|
603
|
+
* Usage: <command line>
|
|
604
|
+
* Purpose: <one-paragraph what + when>
|
|
605
|
+
* Flags: <flag table>
|
|
606
|
+
* Output: <TAB-separated row schema for plain mode>
|
|
607
|
+
* --json: <top-level JSON keys to expect>
|
|
608
|
+
* Stderr: <diagnostic prefixes you might see>
|
|
609
|
+
* Exit codes: <ints + meaning>
|
|
610
|
+
* Examples: <2-4 worked invocations>
|
|
611
|
+
*
|
|
612
|
+
* Self-documenting like `cock terminal <id>` — agents are stateless between
|
|
613
|
+
* tool calls, so the help has to carry everything they need.
|
|
614
|
+
*/
|
|
615
|
+
// Lazy — exposed via getSubHelp() so the top-level `printSubHelp(sub)`
|
|
616
|
+
// call (which runs before this declaration is reached in module order)
|
|
617
|
+
// doesn't hit a const TDZ.
|
|
618
|
+
function getSubHelp() { return {
|
|
619
|
+
search: `Usage: cock codegraph search <query> [--limit N=15] [--json]
|
|
620
|
+
|
|
621
|
+
Purpose: Find symbols (and files) matching a name fragment. Tokenised
|
|
622
|
+
match across name + qualifiedName + filePath. Use this when
|
|
623
|
+
you know the symbol's name but not its location.
|
|
624
|
+
|
|
625
|
+
Flags:
|
|
626
|
+
--limit N Max symbol hits to return (default 15)
|
|
627
|
+
--json Emit raw JSON {files: [...], symbols: [...]}
|
|
628
|
+
|
|
629
|
+
Output (plain, TAB-separated):
|
|
630
|
+
sym <TAB> <file>:<line> <TAB> <kind> <TAB> <qname>
|
|
631
|
+
file <TAB> <file>
|
|
632
|
+
|
|
633
|
+
JSON keys: files[].target, symbols[].target.{filePath,line,symbolKind,qualifiedName}
|
|
634
|
+
|
|
635
|
+
Exit: 0=hits, 1=no hits, 2=usage, 3=server unreachable
|
|
636
|
+
|
|
637
|
+
Examples:
|
|
638
|
+
cock codegraph search getCodeIndex
|
|
639
|
+
cock codegraph search useChatStore --limit 5
|
|
640
|
+
cock codegraph search authenticate --json | jq '.symbols[].target'`,
|
|
641
|
+
|
|
642
|
+
callers: `Usage: cock codegraph callers <qname> [--file PATH] [--json]
|
|
643
|
+
|
|
644
|
+
Purpose: Direct callers of <qname> (1-hop). For transitive use 'impact'
|
|
645
|
+
or 'risk'. Pass --file when the qname exists in multiple files
|
|
646
|
+
(response will warn via 'ambiguousIn').
|
|
647
|
+
|
|
648
|
+
Flags:
|
|
649
|
+
--file PATH Disambiguate when qname appears in multiple files
|
|
650
|
+
--json Emit raw JSON {qname, target, callers: [...]}
|
|
651
|
+
|
|
652
|
+
Output (plain, TAB-separated):
|
|
653
|
+
<file>:<startLine> <TAB> <callerQname> <TAB> [<callLine1>,<callLine2>,...]
|
|
654
|
+
|
|
655
|
+
Stderr:
|
|
656
|
+
# ambiguousIn: <files…> Pass --file to pick the right target
|
|
657
|
+
|
|
658
|
+
Exit: 0=callers found, 1=no callers, 2=usage/qname missing, 3=server
|
|
659
|
+
|
|
660
|
+
Examples:
|
|
661
|
+
cock codegraph callers getCodeIndex
|
|
662
|
+
cock codegraph callers GET --file packages/feature/explorer/src/server/api/projectGraph/risk.ts
|
|
663
|
+
cock codegraph callers searchIndex --json`,
|
|
664
|
+
|
|
665
|
+
callees: `Usage: cock codegraph callees <qname> [--file PATH] [--json]
|
|
666
|
+
|
|
667
|
+
Purpose: What <qname> calls directly (1-hop outgoing). Mirror of
|
|
668
|
+
'callers'. Same shape, opposite direction.
|
|
669
|
+
|
|
670
|
+
Flags: same as 'callers'
|
|
671
|
+
|
|
672
|
+
Output (plain, TAB-separated):
|
|
673
|
+
<file>:<startLine> <TAB> <calleeQname> <TAB> [<callLine1>,<callLine2>,...]
|
|
674
|
+
|
|
675
|
+
Exit: 0=callees found, 1=none, 2=usage, 3=server
|
|
676
|
+
|
|
677
|
+
Examples:
|
|
678
|
+
cock codegraph callees runDialogue
|
|
679
|
+
cock codegraph callees buildCodeIndex --file packages/feature/explorer/src/server/codeMap/projectGraph/codeIndex.ts`,
|
|
680
|
+
|
|
681
|
+
impact: `Usage: cock codegraph impact <qname> [--file PATH] [--depth N=2] [--json]
|
|
682
|
+
|
|
683
|
+
Purpose: Transitive callers via BFS, up to <depth> hops. Returns a
|
|
684
|
+
flat node list capped at 500 server-side. For ranked + bounded
|
|
685
|
+
output prefer 'risk' (which wraps this and overlays scoring).
|
|
686
|
+
|
|
687
|
+
Flags:
|
|
688
|
+
--file PATH Disambiguate qname across files
|
|
689
|
+
--depth N BFS depth (1-5, default 2)
|
|
690
|
+
--json Raw JSON {qname, target, nodes: [...], truncated, ambiguousIn?}
|
|
691
|
+
|
|
692
|
+
Output (plain, TAB-separated):
|
|
693
|
+
d=<depth> <TAB> <file>:<startLine> <TAB> <qname>
|
|
694
|
+
|
|
695
|
+
Stderr:
|
|
696
|
+
# impact truncated at server cap (500). Use 'risk' for ranked + bounded output.
|
|
697
|
+
|
|
698
|
+
Exit: 0=impacted nodes, 1=target only / no impact, 2=usage, 3=server
|
|
699
|
+
|
|
700
|
+
Examples:
|
|
701
|
+
cock codegraph impact getCodeIndex
|
|
702
|
+
cock codegraph impact validateCwd --depth 3
|
|
703
|
+
cock codegraph impact searchIndex --json | jq '.nodes | length'`,
|
|
704
|
+
|
|
705
|
+
file: `Usage: cock codegraph file <path> [--json]
|
|
706
|
+
|
|
707
|
+
Purpose: Symbol tree of one file. Useful for "what's in this file" or
|
|
708
|
+
for picking a qname to feed to callers/related/risk.
|
|
709
|
+
|
|
710
|
+
Flags:
|
|
711
|
+
--json Raw JSON {filePath, language, symbols: [...]} (hierarchical)
|
|
712
|
+
|
|
713
|
+
Output (plain, TAB-separated):
|
|
714
|
+
<kind> <TAB> <startLine>-<endLine> <TAB> <qname>
|
|
715
|
+
|
|
716
|
+
Exit: 0=symbols found, 1=empty file / not indexed, 2=usage, 3=server
|
|
717
|
+
|
|
718
|
+
Examples:
|
|
719
|
+
cock codegraph file packages/feature/explorer/src/server/codeMap/types.ts
|
|
720
|
+
cock codegraph file packages/feature/explorer/src/server/codeMap/projectGraph/codeIndex.ts --json`,
|
|
721
|
+
|
|
722
|
+
coedit: `Usage: cock codegraph coedit <path> [--commits N=100] [--json]
|
|
723
|
+
|
|
724
|
+
Purpose: Files frequently edited together with <path> in git history.
|
|
725
|
+
Captures "convention coupling" (parallel registries, double-
|
|
726
|
+
writes, sibling docs) that the call graph can't see. Auto-falls
|
|
727
|
+
back to merge-granularity for squash-style projects.
|
|
728
|
+
|
|
729
|
+
Flags:
|
|
730
|
+
--commits N Git log scan window (default 100, max 1000)
|
|
731
|
+
--json Raw JSON {target, totalCommits, history: [...], uncommitted: [...]}
|
|
732
|
+
|
|
733
|
+
Output (plain, TAB-separated):
|
|
734
|
+
# history (cooccurrence/total)
|
|
735
|
+
<cooccur>/<total> <TAB> <coeditFile>
|
|
736
|
+
# uncommitted (working tree)
|
|
737
|
+
- <TAB> <file>
|
|
738
|
+
|
|
739
|
+
Exit: 0=signal found, 1=no cooccurrence (totalCommits may be 0), 2=usage, 3=server
|
|
740
|
+
|
|
741
|
+
Examples:
|
|
742
|
+
cock codegraph coedit packages/feature/explorer/src/server/codeMap/projectGraph/codeIndex.ts
|
|
743
|
+
cock codegraph coedit packages/feature/agent/src/server/lib/cgPrompt.ts --commits 500`,
|
|
744
|
+
|
|
745
|
+
context: `Usage: cock codegraph context [--query Q] [--cursor C] [--open F1,F2,...]
|
|
746
|
+
[--top N=15] [--json]
|
|
747
|
+
|
|
748
|
+
Purpose: Semantic retrieval. Combine free-text query, a cursor anchor,
|
|
749
|
+
and currently-open files; returns Top-K relevant coordinates
|
|
750
|
+
ranked by PPR + TF-IDF + PageRank. Use when you DON'T know
|
|
751
|
+
the symbol's name, only intent.
|
|
752
|
+
|
|
753
|
+
At least one of --query / --cursor / --open is required.
|
|
754
|
+
|
|
755
|
+
Flags:
|
|
756
|
+
--query Q Free-text seed (TF-IDF tokenised)
|
|
757
|
+
--cursor C Anchor at "<file>::<qname>" OR "<file>:<line>" — accepts
|
|
758
|
+
'.' as separator, bare qname, case-insensitive, ±5 line fuzzy
|
|
759
|
+
--open F1,F2,... Comma-separated paths of currently-open files (weak seeds)
|
|
760
|
+
--top N Result cap (1-50, default 15)
|
|
761
|
+
--json Raw JSON {results, seeds, cursorResolution, degraded, degradedReason?}
|
|
762
|
+
|
|
763
|
+
Output (plain, TAB-separated, score descending):
|
|
764
|
+
<score> <TAB> <file>:<startLine> <TAB> <qname> <TAB> [<sig1>,<sig2>,...]
|
|
765
|
+
signals: query-match | ppr | pagerank | open
|
|
766
|
+
|
|
767
|
+
Stderr:
|
|
768
|
+
# cursor: <note> Cursor format auto-corrected
|
|
769
|
+
# cursor not matched: ... Cursor failed; results may still come from query/open
|
|
770
|
+
# degraded: <reason> analytics-warming = index not ready yet
|
|
771
|
+
|
|
772
|
+
Exit: 0=results, 1=no results, 2=usage/no seeds, 3=server
|
|
773
|
+
|
|
774
|
+
Examples:
|
|
775
|
+
cock codegraph context --query "spawn language server shutdown"
|
|
776
|
+
cock codegraph context --query "auth flow" --cursor src/auth.ts::login
|
|
777
|
+
cock codegraph context --cursor src/api.ts:42 --top 5`,
|
|
778
|
+
|
|
779
|
+
related: `Usage: cock codegraph related <qname> [--file PATH] [--top N=10]
|
|
780
|
+
[--include all|structural|coedit] [--json]
|
|
781
|
+
|
|
782
|
+
Purpose: Broader 1-hop relatedness than callers/callees alone. Combines
|
|
783
|
+
direct callers + callees + PPR neighbours + Louvain community
|
|
784
|
+
siblings + frequent coedit partners into one ranked list.
|
|
785
|
+
|
|
786
|
+
Flags:
|
|
787
|
+
--file PATH Disambiguate qname across files
|
|
788
|
+
--top N Result cap (1-30, default 10)
|
|
789
|
+
--include K Limit relation kinds (default: all)
|
|
790
|
+
structural = callers/callees + PPR + community
|
|
791
|
+
coedit = coedit only
|
|
792
|
+
--json Raw JSON {target, results, ambiguousIn?, coedit, degraded}
|
|
793
|
+
|
|
794
|
+
Output (plain, TAB-separated, score descending):
|
|
795
|
+
<score> <TAB> <file>:<startLine> <TAB> <qname> <TAB> <<rel1>,<rel2>,...>
|
|
796
|
+
relations: caller | callee | ppr-neighbor | frequent-coedit | sibling-in-community
|
|
797
|
+
|
|
798
|
+
Stderr:
|
|
799
|
+
# ambiguousIn: <files…> Same qname in multiple files
|
|
800
|
+
# degraded: <reason> coedit-unavailable / analytics-warming
|
|
801
|
+
|
|
802
|
+
Exit: 0=results, 1=no relatives, 2=usage, 3=server
|
|
803
|
+
|
|
804
|
+
Examples:
|
|
805
|
+
cock codegraph related getCodeIndex --top 5
|
|
806
|
+
cock codegraph related NewRoutineModal --include structural
|
|
807
|
+
cock codegraph related GET --file packages/feature/explorer/src/server/api/projectGraph/risk.ts`,
|
|
808
|
+
|
|
809
|
+
risk: `Usage: cock codegraph risk <qname> [--file PATH] [--depth N=2]
|
|
810
|
+
[--top N=20] [--json]
|
|
811
|
+
|
|
812
|
+
Purpose: Risk-scored impact. Wraps 'impact' BFS and overlays
|
|
813
|
+
callFreq + coeditProb + hasTest + pagerank per node. Returns
|
|
814
|
+
the top-K highest-risk nodes + suggestedTests + the target
|
|
815
|
+
file's coedit history (don't re-call /coedit on the same file).
|
|
816
|
+
Use for "I'm about to change X — what should I worry about?"
|
|
817
|
+
|
|
818
|
+
Flags:
|
|
819
|
+
--file PATH Disambiguate qname
|
|
820
|
+
--depth N BFS depth (1-5, default 2)
|
|
821
|
+
--top N High-risk cap (1-50, default 20)
|
|
822
|
+
--json Raw JSON {target, totalImpactedNodes, highRisk, suggestedTests, coedit, degraded}
|
|
823
|
+
|
|
824
|
+
Output (plain, TAB-separated, risk.score descending):
|
|
825
|
+
# total impacted: <N>
|
|
826
|
+
<score> <TAB> d=<depth> <TAB> <file>:<startLine> <TAB> <qname> <TAB> [<tag1>,<tag2>,...]
|
|
827
|
+
tags: high-risk | untested | frequent-coedit | core | leaf
|
|
828
|
+
|
|
829
|
+
Followed by (if non-empty):
|
|
830
|
+
# suggestedTests
|
|
831
|
+
<reason> <TAB> <testFile> <TAB> (<covered qname1>, <qname2>, ...)
|
|
832
|
+
reason: direct-test | coedit-history
|
|
833
|
+
|
|
834
|
+
Stderr:
|
|
835
|
+
# degraded: <reason> coedit-unavailable / analytics-warming
|
|
836
|
+
|
|
837
|
+
Exit: 0=high-risk nodes returned, 1=qname not found, 2=usage, 3=server
|
|
838
|
+
|
|
839
|
+
Examples:
|
|
840
|
+
cock codegraph risk searchIndex
|
|
841
|
+
cock codegraph risk getCodeIndex --depth 3 --top 10
|
|
842
|
+
cock codegraph risk validateCwd --json | jq '.suggestedTests[].filePath'`,
|
|
843
|
+
|
|
844
|
+
affected: `Usage: cock codegraph affected <files…|--stdin>
|
|
845
|
+
[--depth N=10] [--filter GLOB]
|
|
846
|
+
[--as-cmd RUNNER] [--include-all] [--json]
|
|
847
|
+
|
|
848
|
+
Purpose: Test files transitively affected by changing the input files.
|
|
849
|
+
File-level reverse-import closure. Recall-oriented (catch every
|
|
850
|
+
relevant test, may over-include) where 'risk' is precision-
|
|
851
|
+
oriented (highlight a few high-impact symbols). Use this for
|
|
852
|
+
CI selective-test pipelines.
|
|
853
|
+
|
|
854
|
+
Detects test files by convention: *.test.* / *.spec.* / test_*.py /
|
|
855
|
+
*_test.go / tests/*.rs / __tests__/* / *.e2e.*
|
|
856
|
+
|
|
857
|
+
Flags:
|
|
858
|
+
--stdin Read file list from stdin (one path per line; pairs
|
|
859
|
+
with 'git diff --name-only')
|
|
860
|
+
--depth N BFS depth (1-20, default 10)
|
|
861
|
+
--filter GLOB Restrict tests matching this glob (e.g. "**/*.e2e.ts")
|
|
862
|
+
--as-cmd RUNNER Emit a single shell command with all paths quoted, e.g.
|
|
863
|
+
--as-cmd jest → jest "a" "b" …
|
|
864
|
+
--as-cmd "vitest run" → vitest run "a" "b" …
|
|
865
|
+
--as-cmd "pytest -v" → pytest -v "a" "b" …
|
|
866
|
+
Pick whatever your project's runner is — codegraph
|
|
867
|
+
doesn't assume jest. NOTE: filter your input set to
|
|
868
|
+
the matching language (e.g. --filter "**/*.test.ts")
|
|
869
|
+
if you mix JS+Python+Go test files in one repo.
|
|
870
|
+
--include-all Also report non-test affected files (JSON only)
|
|
871
|
+
--json Raw JSON {testFiles, byInput, unresolved, stats, degraded}
|
|
872
|
+
|
|
873
|
+
Output (default plain):
|
|
874
|
+
<test/file/path>
|
|
875
|
+
<test/file/path>
|
|
876
|
+
... (one path per line, alphabetically sorted)
|
|
877
|
+
|
|
878
|
+
With --as-cmd:
|
|
879
|
+
<RUNNER> "path1" "path2" "..."
|
|
880
|
+
(single quoted shell command)
|
|
881
|
+
|
|
882
|
+
Stderr:
|
|
883
|
+
codegraph affected: N input file(s) not found in CodeIndex
|
|
884
|
+
codegraph affected: BFS hit node cap — results may miss deeper tests
|
|
885
|
+
|
|
886
|
+
Exit: 0=tests printed, 1=no tests affected (short-circuit in Makefile),
|
|
887
|
+
2=usage, 3=server
|
|
888
|
+
|
|
889
|
+
Examples:
|
|
890
|
+
git diff --name-only main | cock codegraph affected --stdin
|
|
891
|
+
git diff --name-only | cock codegraph affected --stdin --as-cmd jest
|
|
892
|
+
git diff --name-only | cock codegraph affected --stdin --as-cmd "vitest run"
|
|
893
|
+
cock codegraph affected src/auth.ts --filter "**/*.e2e.ts"
|
|
894
|
+
cock codegraph affected --stdin --json | jq '.byInput[] | {file, tests: .reachableTests | length}'`,
|
|
895
|
+
}; }
|
|
896
|
+
|
|
897
|
+
function printSubHelp(name) {
|
|
898
|
+
const h = getSubHelp()[name];
|
|
899
|
+
if (!h) {
|
|
900
|
+
printTopHelp();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
stdout.write(h + '\n');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Exported for cock.mjs `await mod.done` pattern.
|
|
907
|
+
export const done = Promise.resolve();
|