@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.
Files changed (185) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +3 -3
  3. package/.next-prod/build-manifest.json +2 -2
  4. package/.next-prod/prerender-manifest.json +3 -3
  5. package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
  6. package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next-prod/server/app/_global-error.html +1 -1
  8. package/.next-prod/server/app/_global-error.rsc +1 -1
  9. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next-prod/server/app/_not-found.html +1 -1
  18. package/.next-prod/server/app/_not-found.rsc +3 -3
  19. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
  22. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next-prod/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
  26. package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
  27. package/.next-prod/server/app/api/chat/codex/route.js +2 -2
  28. package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
  29. package/.next-prod/server/app/api/chat/deepseek/route.js +2 -2
  30. package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
  31. package/.next-prod/server/app/api/chat/kimi/route.js +2 -2
  32. package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
  33. package/.next-prod/server/app/api/chat/ollama/route.js +2 -2
  34. package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
  35. package/.next-prod/server/app/api/chat/route.js +2 -2
  36. package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
  37. package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
  38. package/.next-prod/server/app/api/commands/route.js +1 -1
  39. package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
  40. package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
  41. package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
  42. package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
  43. package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
  44. package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
  45. package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
  46. package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
  47. package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
  48. package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
  49. package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
  50. package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
  51. package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
  52. package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
  53. package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
  54. package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
  55. package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
  56. package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
  57. package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
  58. package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
  59. package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
  60. package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
  61. package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
  62. package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
  63. package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
  64. package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
  65. package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
  66. package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
  67. package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
  68. package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
  69. package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
  70. package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
  71. package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
  72. package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
  73. package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
  74. package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
  75. package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
  76. package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
  77. package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
  78. package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
  79. package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
  80. package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
  81. package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
  82. package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
  83. package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
  84. package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
  85. package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
  86. package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
  87. package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
  88. package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
  89. package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
  90. package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
  91. package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
  92. package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
  93. package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
  94. package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
  95. package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
  96. package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
  97. package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
  98. package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
  99. package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
  100. package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
  101. package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
  102. package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
  103. package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
  104. package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
  105. package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
  106. package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
  107. package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
  108. package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
  109. package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
  110. package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
  111. package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
  112. package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
  113. package/.next-prod/server/app/api/projectGraph/search/route.js +1 -1
  114. package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
  115. package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
  116. package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
  117. package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
  118. package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
  119. package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
  120. package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
  121. package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
  122. package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
  123. package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
  124. package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
  125. package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
  126. package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
  127. package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
  128. package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
  129. package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
  130. package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
  131. package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
  132. package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
  133. package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
  134. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
  135. package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
  136. package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
  137. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
  138. package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
  139. package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
  140. package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
  141. package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
  142. package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
  143. package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
  144. package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
  145. package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
  146. package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
  147. package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
  148. package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
  149. package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
  150. package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
  151. package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  152. package/.next-prod/server/app/page.js.nft.json +1 -1
  153. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  154. package/.next-prod/server/app/project/page.js.nft.json +1 -1
  155. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  156. package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
  157. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  158. package/.next-prod/server/app-paths-manifest.json +3 -3
  159. package/.next-prod/server/chunks/2939.js +1 -1
  160. package/.next-prod/server/chunks/8916.js +1 -1
  161. package/.next-prod/server/chunks/9658.js +3 -3
  162. package/.next-prod/server/chunks/{7828.js → 9877.js} +11 -2
  163. package/.next-prod/server/middleware-build-manifest.js +1 -1
  164. package/.next-prod/server/pages/404.html +1 -1
  165. package/.next-prod/server/pages/500.html +1 -1
  166. package/.next-prod/server/server-reference-manifest.json +1 -1
  167. package/.next-prod/static/chunks/{5188-38f55b21ae1eeb28.js → 5188-415582403ef0e29c.js} +1 -1
  168. package/.next-prod/static/chunks/6345-e5ceeb2aeb698eb6.js +14 -0
  169. package/.next-prod/static/css/{f016b445331fc5a2.css → cc6d733cdf607b30.css} +1 -1
  170. package/.next-prod/trace +13 -13
  171. package/.next-prod/trace-build +1 -1
  172. package/README.md +8 -7
  173. package/README.zh.md +8 -7
  174. package/bin/cock-browser.mjs +93 -3
  175. package/bin/cock-codegraph.mjs +907 -0
  176. package/bin/cock-terminal.mjs +5 -0
  177. package/bin/cock.mjs +4 -4
  178. package/bin/cockpit-dev.mjs +9 -0
  179. package/bin/setup-dev.mjs +92 -0
  180. package/package.json +3 -2
  181. package/.next-prod/static/chunks/6345-fc2d45a72316c5f8.js +0 -14
  182. package/bin/cock-affected.mjs +0 -190
  183. package/bin/cock-dev.mjs +0 -6
  184. /package/.next-prod/static/{ekMRRxcvpy2AIjMrVn4lK → GAYKr2BmQpFqJgRJfvQ3D}/_buildManifest.js +0 -0
  185. /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();