dsh-gitnexus-plus 0.0.0-stage → 0.1.1

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 (78) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +483 -2
  3. package/cordis.patch.yml +18 -0
  4. package/lib/bin/gitnexus-coverage.d.ts +14 -0
  5. package/lib/bin/gitnexus-coverage.d.ts.map +1 -0
  6. package/lib/bin/gitnexus-coverage.js +171 -0
  7. package/lib/bin/gitnexus-coverage.js.map +1 -0
  8. package/lib/client.js +1642 -0
  9. package/lib/gitnexus/analyze.d.ts +148 -0
  10. package/lib/gitnexus/analyze.d.ts.map +1 -0
  11. package/lib/gitnexus/analyze.js +283 -0
  12. package/lib/gitnexus/analyze.js.map +1 -0
  13. package/lib/gitnexus/commands.d.ts +96 -0
  14. package/lib/gitnexus/commands.d.ts.map +1 -0
  15. package/lib/gitnexus/commands.js +132 -0
  16. package/lib/gitnexus/commands.js.map +1 -0
  17. package/lib/gitnexus/coverage.d.ts +340 -0
  18. package/lib/gitnexus/coverage.d.ts.map +1 -0
  19. package/lib/gitnexus/coverage.js +552 -0
  20. package/lib/gitnexus/coverage.js.map +1 -0
  21. package/lib/gitnexus/format.d.ts +16 -0
  22. package/lib/gitnexus/format.d.ts.map +1 -0
  23. package/lib/gitnexus/format.js +178 -0
  24. package/lib/gitnexus/format.js.map +1 -0
  25. package/lib/gitnexus/git-activity.d.ts +136 -0
  26. package/lib/gitnexus/git-activity.d.ts.map +1 -0
  27. package/lib/gitnexus/git-activity.js +345 -0
  28. package/lib/gitnexus/git-activity.js.map +1 -0
  29. package/lib/gitnexus/git.d.ts +70 -0
  30. package/lib/gitnexus/git.d.ts.map +1 -0
  31. package/lib/gitnexus/git.js +121 -0
  32. package/lib/gitnexus/git.js.map +1 -0
  33. package/lib/gitnexus/index-meta.d.ts +150 -0
  34. package/lib/gitnexus/index-meta.d.ts.map +1 -0
  35. package/lib/gitnexus/index-meta.js +156 -0
  36. package/lib/gitnexus/index-meta.js.map +1 -0
  37. package/lib/gitnexus/install.d.ts +97 -0
  38. package/lib/gitnexus/install.d.ts.map +1 -0
  39. package/lib/gitnexus/install.js +228 -0
  40. package/lib/gitnexus/install.js.map +1 -0
  41. package/lib/gitnexus/languages.d.ts +73 -0
  42. package/lib/gitnexus/languages.d.ts.map +1 -0
  43. package/lib/gitnexus/languages.js +217 -0
  44. package/lib/gitnexus/languages.js.map +1 -0
  45. package/lib/gitnexus/root.d.ts +68 -0
  46. package/lib/gitnexus/root.d.ts.map +1 -0
  47. package/lib/gitnexus/root.js +162 -0
  48. package/lib/gitnexus/root.js.map +1 -0
  49. package/lib/gitnexus/scan.d.ts +67 -0
  50. package/lib/gitnexus/scan.d.ts.map +1 -0
  51. package/lib/gitnexus/scan.js +247 -0
  52. package/lib/gitnexus/scan.js.map +1 -0
  53. package/lib/gitnexus/toolchain.d.ts +136 -0
  54. package/lib/gitnexus/toolchain.d.ts.map +1 -0
  55. package/lib/gitnexus/toolchain.js +251 -0
  56. package/lib/gitnexus/toolchain.js.map +1 -0
  57. package/lib/index.d.ts +79 -0
  58. package/lib/index.d.ts.map +1 -0
  59. package/lib/index.js +211 -0
  60. package/lib/index.js.map +1 -0
  61. package/lib/routes/analyze.d.ts +35 -0
  62. package/lib/routes/analyze.d.ts.map +1 -0
  63. package/lib/routes/analyze.js +71 -0
  64. package/lib/routes/analyze.js.map +1 -0
  65. package/lib/routes/coverage.d.ts +44 -0
  66. package/lib/routes/coverage.d.ts.map +1 -0
  67. package/lib/routes/coverage.js +68 -0
  68. package/lib/routes/coverage.js.map +1 -0
  69. package/lib/routes/guard.d.ts +92 -0
  70. package/lib/routes/guard.d.ts.map +1 -0
  71. package/lib/routes/guard.js +187 -0
  72. package/lib/routes/guard.js.map +1 -0
  73. package/lib/routes/install.d.ts +27 -0
  74. package/lib/routes/install.d.ts.map +1 -0
  75. package/lib/routes/install.js +46 -0
  76. package/lib/routes/install.js.map +1 -0
  77. package/package.json +87 -4
  78. package/scripts/link-runtime.mjs +146 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Run `gitnexus analyze --index-only` through the host's shell executor.
3
+ *
4
+ * WHY THIS REPLACED HANDING OVER A COMMAND
5
+ * ----------------------------------------
6
+ * The plugin used to print a command and let the operator paste it. That was
7
+ * justified as "a plugin that spawns processes bypasses the permission system", and
8
+ * the justification was wrong — not in spirit, but in what it assumed permission
9
+ * meant here.
10
+ *
11
+ * DSH's approval service governs **sandbox escalation**: widening the scope a session
12
+ * was granted. Its audit pair must be turn-enclosed, and `ApprovalService.request()`
13
+ * throws outside an open turn. A panel route has no turn, so it cannot ask — but it
14
+ * also does not need to, because this command does not escalate anything. It writes
15
+ * `.gitnexus/` **inside the workspace**, which is already within the granted scope.
16
+ *
17
+ * The confinement is not something a caller opts into. It is what `ctx.shell` *is* in
18
+ * a given deployment: `dsh-pwsh-local` exposes the argv seam that `dsh-pwsh-sandbox`
19
+ * wraps through `ctx.sandbox.confine`. So routing through the executor gives this
20
+ * plugin exactly the isolation the agent's own shell tool gets.
21
+ *
22
+ * THE ONE RULE THAT MATTERS
23
+ * -------------------------
24
+ * `sandboxPolicy` is never set here, and must never be. Passing it would be this
25
+ * plugin choosing its own confinement — the escalation the approval service exists to
26
+ * gate, requested through a door that cannot ask. Leaving it undefined takes the
27
+ * deployment's decision. A denial is then reported as a denial, not retried wider:
28
+ * the same rule the subagent prompt states, for the same reason.
29
+ */
30
+ import { type ToolchainOptions } from './toolchain.js';
31
+ /** Where a run got to. `unavailable` is decided before anything is spawned. */
32
+ export type AnalyzeState =
33
+ /** No `gitnexus` executable was found — there is nothing to run. */
34
+ 'unavailable' | 'running' | 'done'
35
+ /** The command ran and exited non-zero. */
36
+ | 'failed' | 'timed-out' | 'cancelled'
37
+ /** The sandbox refused a write. Reported, never retried wider. */
38
+ | 'denied';
39
+ export interface AnalyzeStatus {
40
+ repoPath: string;
41
+ state: AnalyzeState;
42
+ /** The command as run, so a failure can be reproduced by hand. */
43
+ command: string;
44
+ startedAt: number;
45
+ finishedAt: number | null;
46
+ exitCode: number | null;
47
+ /** A short operator-facing explanation, or null when the state speaks for itself. */
48
+ message: string | null;
49
+ /** The tail of combined output, capped. */
50
+ output: string;
51
+ }
52
+ /**
53
+ * The slice of `ctx.shell` this module uses.
54
+ *
55
+ * Structural rather than an import: the plugin declares no runtime dependency on the
56
+ * DSH packages, and a test can supply a stub without a shell on the machine.
57
+ */
58
+ export interface ShellRequestLike {
59
+ command: string;
60
+ workdir?: string;
61
+ timeoutMs?: number;
62
+ onExpiry?: 'kill' | 'none';
63
+ stdoutMaxBytes?: number;
64
+ signal?: AbortSignal;
65
+ }
66
+ export interface ShellResultLike {
67
+ exitCode?: number | null;
68
+ signal?: string | null;
69
+ timedOut?: boolean;
70
+ aborted?: boolean;
71
+ stdout?: {
72
+ text?: string;
73
+ } | null;
74
+ stderr?: {
75
+ text?: string;
76
+ } | null;
77
+ }
78
+ export interface ShellExecutionLike {
79
+ result(): Promise<ShellResultLike>;
80
+ kill?(): boolean | Promise<boolean>;
81
+ }
82
+ export interface ShellLike {
83
+ /** Applies the executor's defaults and caps. `execute` only accepts a resolved spec. */
84
+ resolve(request: ShellRequestLike): unknown;
85
+ execute(spec: unknown): Promise<ShellExecutionLike>;
86
+ }
87
+ export interface AnalyzeRunnerOptions {
88
+ /**
89
+ * The host's shell executor, or `null` when the deployment has none.
90
+ *
91
+ * `null` is a real composition rather than a test convenience: `ctx.shell` is mounted
92
+ * per profile, and a headless one need not have it. A runner that reported "failed"
93
+ * for a missing service would send the operator looking for a GitNexus problem that
94
+ * is really a deployment shape.
95
+ */
96
+ shell: ShellLike | null;
97
+ /** Wall-clock ceiling for one run. Default 10 minutes — a full rebuild is minutes. */
98
+ timeoutMs?: number;
99
+ /** Captured-output ceiling handed to the executor. */
100
+ outputLimit?: number;
101
+ /** How much output this module keeps for the panel. */
102
+ tailLimit?: number;
103
+ toolchain?: ToolchainOptions;
104
+ /**
105
+ * Pass `GITNEXUS_MEMORY=off` and the heap override, exactly as the handed-over
106
+ * commands do. On by default: the CLI re-spawns itself over piped stdio without it,
107
+ * which a confined process denies.
108
+ */
109
+ memoryOff?: boolean;
110
+ heapMb?: number;
111
+ now?: () => number;
112
+ }
113
+ export interface AnalyzeRunner {
114
+ /** Begin a run, or report the one already in flight for this repository. */
115
+ start(repoPath: string): AnalyzeStatus;
116
+ status(repoPath: string): AnalyzeStatus | null;
117
+ cancel(repoPath: string): boolean;
118
+ /** Abort every in-flight run. Called on plugin teardown. */
119
+ dispose(): void;
120
+ }
121
+ /**
122
+ * Build the command line, as the shell will read it.
123
+ *
124
+ * TWO THINGS HERE WERE WRONG ON THE FIRST ATTEMPT, AND TOGETHER THEY MADE THE BUTTON
125
+ * DO NOTHING VISIBLE — a run started, finished, and the index never changed.
126
+ *
127
+ * 1. PowerShell needs the call operator before a quoted path.
128
+ * `'C:\...\gitnexus.cmd' analyze` is not a command: the single-quoted string is a
129
+ * string *literal*, and the parser dies on the next token. Measured:
130
+ * "表达式或语句中包含意外的标记" for the whole statement. The paste-able text in
131
+ * `commands.ts` never hit this because it names a bare `gitnexus` and lets PATH
132
+ * resolve it — a difference that exists only because this path is absolute.
133
+ *
134
+ * 2. The environment prefix is not optional. `GITNEXUS_MEMORY=off` stops the CLI
135
+ * re-spawning itself over piped stdio, which a confined process denies. Dropping it
136
+ * because the command "looked simpler" reintroduces exactly the failure the
137
+ * maintenance text was written to avoid.
138
+ *
139
+ * Exported so the shape can be asserted without a shell.
140
+ */
141
+ export declare function analyzeCommandLine(cliPath: string, repoPath: string, options?: {
142
+ flavor?: 'win32' | 'posix';
143
+ memoryOff?: boolean;
144
+ heapMb?: number;
145
+ viaNode?: boolean;
146
+ }): string;
147
+ export declare function createAnalyzeRunner(options: AnalyzeRunnerOptions): AnalyzeRunner;
148
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/gitnexus/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Run `gitnexus analyze --index-only` through the host's shell executor.
3
+ *
4
+ * WHY THIS REPLACED HANDING OVER A COMMAND
5
+ * ----------------------------------------
6
+ * The plugin used to print a command and let the operator paste it. That was
7
+ * justified as "a plugin that spawns processes bypasses the permission system", and
8
+ * the justification was wrong — not in spirit, but in what it assumed permission
9
+ * meant here.
10
+ *
11
+ * DSH's approval service governs **sandbox escalation**: widening the scope a session
12
+ * was granted. Its audit pair must be turn-enclosed, and `ApprovalService.request()`
13
+ * throws outside an open turn. A panel route has no turn, so it cannot ask — but it
14
+ * also does not need to, because this command does not escalate anything. It writes
15
+ * `.gitnexus/` **inside the workspace**, which is already within the granted scope.
16
+ *
17
+ * The confinement is not something a caller opts into. It is what `ctx.shell` *is* in
18
+ * a given deployment: `dsh-pwsh-local` exposes the argv seam that `dsh-pwsh-sandbox`
19
+ * wraps through `ctx.sandbox.confine`. So routing through the executor gives this
20
+ * plugin exactly the isolation the agent's own shell tool gets.
21
+ *
22
+ * THE ONE RULE THAT MATTERS
23
+ * -------------------------
24
+ * `sandboxPolicy` is never set here, and must never be. Passing it would be this
25
+ * plugin choosing its own confinement — the escalation the approval service exists to
26
+ * gate, requested through a door that cannot ask. Leaving it undefined takes the
27
+ * deployment's decision. A denial is then reported as a denial, not retried wider:
28
+ * the same rule the subagent prompt states, for the same reason.
29
+ */
30
+ import path from 'node:path';
31
+ import { findGitnexusCli } from './toolchain.js';
32
+ const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
33
+ const DEFAULT_OUTPUT_LIMIT = 256 * 1024;
34
+ const DEFAULT_TAIL_LIMIT = 4 * 1024;
35
+ /**
36
+ * Quote one argument for the shell the executor will spawn.
37
+ *
38
+ * Windows and POSIX disagree, and `commands.ts` already carries the same pair — kept
39
+ * separate here because these two modules are allowed to have different lifetimes:
40
+ * one produces text for a human to paste, the other produces argv for a spawn, and
41
+ * collapsing them would make a change to the paste-able text silently alter what
42
+ * actually runs.
43
+ */
44
+ function quoteFor(shell, value) {
45
+ return shell === 'win32' ? `'${value.replace(/'/g, "''")}'` : `'${value.replace(/'/g, "'\\''")}'`;
46
+ }
47
+ /**
48
+ * Build the command line, as the shell will read it.
49
+ *
50
+ * TWO THINGS HERE WERE WRONG ON THE FIRST ATTEMPT, AND TOGETHER THEY MADE THE BUTTON
51
+ * DO NOTHING VISIBLE — a run started, finished, and the index never changed.
52
+ *
53
+ * 1. PowerShell needs the call operator before a quoted path.
54
+ * `'C:\...\gitnexus.cmd' analyze` is not a command: the single-quoted string is a
55
+ * string *literal*, and the parser dies on the next token. Measured:
56
+ * "表达式或语句中包含意外的标记" for the whole statement. The paste-able text in
57
+ * `commands.ts` never hit this because it names a bare `gitnexus` and lets PATH
58
+ * resolve it — a difference that exists only because this path is absolute.
59
+ *
60
+ * 2. The environment prefix is not optional. `GITNEXUS_MEMORY=off` stops the CLI
61
+ * re-spawning itself over piped stdio, which a confined process denies. Dropping it
62
+ * because the command "looked simpler" reintroduces exactly the failure the
63
+ * maintenance text was written to avoid.
64
+ *
65
+ * Exported so the shape can be asserted without a shell.
66
+ */
67
+ export function analyzeCommandLine(cliPath, repoPath, options = {}) {
68
+ const flavor = options.flavor ?? (process.platform === 'win32' ? 'win32' : 'posix');
69
+ const memoryOff = options.memoryOff !== false;
70
+ const heapMb = options.heapMb ?? 8192;
71
+ // A CLI taken from the declared dependency is the package's `dist/cli/index.js`, so it
72
+ // is spawned through `node`. This is the better shape when available: no shim, no
73
+ // `.cmd`/`.ps1` selection, and therefore no reliance on the shell's call operator.
74
+ const viaNode = options.viaNode === true;
75
+ const args = `analyze --index-only ${quoteFor(flavor, repoPath)}`;
76
+ const head = viaNode ? `node ${quoteFor(flavor, cliPath)}` : quoteFor(flavor, cliPath);
77
+ // On Windows a `.cmd` or `.ps1` reached by absolute path needs `&`. POSIX needs none,
78
+ // because an absolute path in command position is already a command.
79
+ const invocation = viaNode || flavor !== 'win32' ? `${head} ${args}` : `& ${head} ${args}`;
80
+ const heap = quoteFor(flavor, `--max-old-space-size=${heapMb}`);
81
+ if (flavor === 'win32') {
82
+ const prefix = memoryOff ? `$env:GITNEXUS_MEMORY='off'; ` : '';
83
+ return `${prefix}$env:NODE_OPTIONS=${heap}; ${invocation}`;
84
+ }
85
+ const prefix = memoryOff ? `GITNEXUS_MEMORY='off' ` : '';
86
+ return `${prefix}NODE_OPTIONS=${heap} ${invocation}`;
87
+ }
88
+ /**
89
+ * A sandbox refusal, recognised from the marker DSH writes into the output.
90
+ *
91
+ * Tested before the generic non-zero path because a denial is not a failure of
92
+ * GitNexus — retrying it, or reporting it as "analyze failed", both mislead.
93
+ */
94
+ function looksDenied(text) {
95
+ return /\[sandbox:[^\]]*denied/i.test(text) || /access is denied|permission denied|EPERM/i.test(text);
96
+ }
97
+ /** Keep the last `limit` characters, on a line boundary when one is available. */
98
+ function tail(text, limit) {
99
+ if (text.length <= limit)
100
+ return text;
101
+ const cut = text.slice(text.length - limit);
102
+ const firstBreak = cut.indexOf('\n');
103
+ return firstBreak === -1 ? cut : cut.slice(firstBreak + 1);
104
+ }
105
+ export function createAnalyzeRunner(options) {
106
+ const { shell } = options;
107
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
108
+ const outputLimit = options.outputLimit ?? DEFAULT_OUTPUT_LIMIT;
109
+ const tailLimit = options.tailLimit ?? DEFAULT_TAIL_LIMIT;
110
+ const now = options.now ?? (() => Date.now());
111
+ const shellFlavor = process.platform === 'win32' ? 'win32' : 'posix';
112
+ const runs = new Map();
113
+ const inFlight = new Map();
114
+ /** The shape a status starts from, before any run has touched it. */
115
+ const BLANK = {
116
+ state: 'failed',
117
+ command: '',
118
+ startedAt: 0,
119
+ finishedAt: null,
120
+ exitCode: null,
121
+ message: null,
122
+ output: '',
123
+ };
124
+ const finish = (repoPath, patch) => {
125
+ const next = {
126
+ repoPath,
127
+ ...BLANK,
128
+ ...runs.get(repoPath),
129
+ ...patch,
130
+ // Set last and unconditionally: every path that reaches `finish` is finishing, so
131
+ // a caller passing `finishedAt: null` is asking for "not finished", not for a
132
+ // silent no-op. The spread above cannot express that.
133
+ finishedAt: patch.finishedAt ?? now(),
134
+ };
135
+ runs.set(repoPath, next);
136
+ inFlight.delete(repoPath);
137
+ return next;
138
+ };
139
+ const start = (repoPath) => {
140
+ const key = path.resolve(repoPath);
141
+ // One run per repository. Two concurrent `analyze` processes would contend for the
142
+ // same store, and the second would report a failure that says nothing about the
143
+ // repository — so a second request reports the first rather than starting one.
144
+ const running = runs.get(key);
145
+ if (running !== undefined && running.state === 'running')
146
+ return running;
147
+ if (shell === null) {
148
+ return finish(key, {
149
+ state: 'unavailable',
150
+ command: 'gitnexus analyze --index-only',
151
+ startedAt: now(),
152
+ output: '',
153
+ message: '当前部署没有提供 shell 执行器(ctx.shell 未挂载),无法运行命令。' +
154
+ '请复制下面的命令在终端中执行。',
155
+ });
156
+ }
157
+ // `moduleUrl` is what lets the probe find the `gitnexus` that npm installed beside
158
+ // this plugin. Omitting it would make the dependency branch unreachable and silently
159
+ // fall back to PATH forever — the dependency would be installed and never used.
160
+ const cli = findGitnexusCli({ moduleUrl: import.meta.url, ...(options.toolchain ?? {}) });
161
+ if (!cli.present) {
162
+ return finish(key, {
163
+ state: 'unavailable',
164
+ command: 'gitnexus analyze --index-only',
165
+ startedAt: now(),
166
+ output: '',
167
+ message: '本机没有找到 gitnexus 命令,无法执行。安装后重试:`npm i -g gitnexus`。' +
168
+ '扫描与覆盖数据仍然可用 —— 那部分不需要 CLI。',
169
+ });
170
+ }
171
+ // `--index-only` is not negotiable here: without it `analyze` rewrites AGENTS.md,
172
+ // CLAUDE.md and the agent skill files inside the repository, which is a much larger
173
+ // change than "refresh the index" and not what the button says it does.
174
+ const command = analyzeCommandLine(cli.cliPath, key, {
175
+ flavor: shellFlavor,
176
+ memoryOff: options.memoryOff,
177
+ heapMb: options.heapMb,
178
+ viaNode: cli.viaNode,
179
+ });
180
+ const controller = new AbortController();
181
+ inFlight.set(key, controller);
182
+ const started = {
183
+ repoPath: key,
184
+ state: 'running',
185
+ command,
186
+ startedAt: now(),
187
+ finishedAt: null,
188
+ exitCode: null,
189
+ message: null,
190
+ output: '',
191
+ };
192
+ runs.set(key, started);
193
+ const spec = shell.resolve({
194
+ command,
195
+ workdir: key,
196
+ timeoutMs,
197
+ onExpiry: 'kill',
198
+ stdoutMaxBytes: outputLimit,
199
+ signal: controller.signal,
200
+ // `sandboxPolicy` is deliberately absent. See the module comment.
201
+ });
202
+ void (async () => {
203
+ let execution;
204
+ try {
205
+ execution = await shell.execute(spec);
206
+ }
207
+ catch (cause) {
208
+ // `execute` rejects only for infrastructure failures — an unusable workdir, no
209
+ // shell. A cancellation rejects here too, before a handle exists.
210
+ const aborted = controller.signal.aborted;
211
+ finish(key, {
212
+ state: aborted ? 'cancelled' : 'failed',
213
+ message: aborted ? '已取消。' : `无法启动命令:${String(cause?.message ?? cause)}`,
214
+ });
215
+ return;
216
+ }
217
+ let result;
218
+ try {
219
+ result = await execution.result();
220
+ }
221
+ catch (cause) {
222
+ finish(key, { state: 'failed', message: `执行失败:${String(cause?.message ?? cause)}` });
223
+ return;
224
+ }
225
+ const combined = `${result.stdout?.text ?? ''}${result.stderr?.text ?? ''}`;
226
+ const body = tail(combined, tailLimit);
227
+ const exitCode = typeof result.exitCode === 'number' ? result.exitCode : null;
228
+ // Order matters. A denial can also be a non-zero exit, and reporting it as a
229
+ // generic failure sends the operator looking for a GitNexus bug that is not
230
+ // there. Cancellation is checked before timeout because a kill from `cancel()`
231
+ // and a kill from the deadline both surface as a killed process.
232
+ if (controller.signal.aborted) {
233
+ finish(key, { state: 'cancelled', exitCode, output: body, message: '已取消。' });
234
+ }
235
+ else if (result.timedOut === true) {
236
+ finish(key, {
237
+ state: 'timed-out',
238
+ exitCode,
239
+ output: body,
240
+ message: `超过 ${Math.round(timeoutMs / 1000)} 秒上限已终止。大仓库的全量重建可能需要更长时间。`,
241
+ });
242
+ }
243
+ else if (looksDenied(combined)) {
244
+ finish(key, {
245
+ state: 'denied',
246
+ exitCode,
247
+ output: body,
248
+ message: '沙箱拒绝了这次写入。本插件不会申请扩权 —— 需要在终端里手动执行这条命令。',
249
+ });
250
+ }
251
+ else if (exitCode === 0) {
252
+ finish(key, { state: 'done', exitCode, output: body, message: null });
253
+ }
254
+ else {
255
+ finish(key, {
256
+ state: 'failed',
257
+ exitCode,
258
+ output: body,
259
+ message: `gitnexus 以退出码 ${exitCode ?? '未知'} 结束。`,
260
+ });
261
+ }
262
+ })();
263
+ return started;
264
+ };
265
+ return {
266
+ start,
267
+ status: (repoPath) => runs.get(path.resolve(repoPath)) ?? null,
268
+ cancel: (repoPath) => {
269
+ const key = path.resolve(repoPath);
270
+ const controller = inFlight.get(key);
271
+ if (controller === undefined)
272
+ return false;
273
+ controller.abort();
274
+ return true;
275
+ },
276
+ dispose: () => {
277
+ for (const controller of inFlight.values())
278
+ controller.abort();
279
+ inFlight.clear();
280
+ },
281
+ };
282
+ }
283
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/gitnexus/analyze.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The maintenance commands this plugin hands to the operator.
3
+ *
4
+ * WHY THE PLUGIN DOES NOT RUN THESE ITSELF
5
+ * ---------------------------------------
6
+ * A first revision spawned `gitnexus analyze` directly from the plugin and exposed
7
+ * it as a button. That was wrong, for a reason that has nothing to do with
8
+ * convenience: a plugin that spawns processes bypasses the host's permission
9
+ * system. Nothing would ask the operator whether a thirty-minute write against
10
+ * their working tree was intended, and nothing would leave an audit trail of the
11
+ * escalation. Presenting the command and letting it run through the normal tool
12
+ * path keeps the decision — and the approval — where the product put it.
13
+ *
14
+ * WHAT MAKES THE COMMANDS NON-OBVIOUS
15
+ * -----------------------------------
16
+ * A bare `gitnexus analyze` does **not** work in every deployment, and the failures
17
+ * are silent-ish. Measured on this machine, under a confined DSH process:
18
+ *
19
+ * - `ensureHeap` re-spawns the CLI with a larger heap over piped stdio, which a
20
+ * confined sandbox denies (`spawn EPERM`). `GITNEXUS_MEMORY=off` suppresses the
21
+ * respawn; `NODE_OPTIONS` supplies the heap it was trying to obtain.
22
+ * - The FTS extension installs itself out-of-process and fails the same way.
23
+ * `--skip-fts` builds the index without it.
24
+ * - The global registry write (`~/.gitnexus/registry.json`) is outside the
25
+ * workspace and may be denied. `GITNEXUS_HOME` redirects it.
26
+ *
27
+ * `--index-only` on both commands is deliberate and not a sandbox workaround:
28
+ * the default `analyze` also rewrites AGENTS.md, CLAUDE.md and agent skill files
29
+ * inside the repository. A maintenance button must not edit the working tree.
30
+ */
31
+ export type MaintenanceMode = 'init' | 'full';
32
+ export interface MaintenanceCommand {
33
+ mode: MaintenanceMode;
34
+ label: string;
35
+ /** When the operator should reach for this one. */
36
+ when: string;
37
+ /** The command alone, for reference. */
38
+ argv: string;
39
+ /** The command with the environment it needs, ready to paste. */
40
+ shell: string;
41
+ /** Whether it discards existing index work. */
42
+ destructive: boolean;
43
+ caveat: string | null;
44
+ }
45
+ export interface MaintenanceOptions {
46
+ platform?: NodeJS.Platform;
47
+ /**
48
+ * Add `--skip-fts`. Off by default, and that default is load-bearing — see the
49
+ * measurement in {@link maintenanceCommands}.
50
+ */
51
+ skipFts?: boolean;
52
+ memoryOff?: boolean;
53
+ gitnexusHome?: string | null;
54
+ }
55
+ /** The resolved form of {@link MaintenanceOptions}, after defaults are applied. */
56
+ interface ResolvedOptions {
57
+ platform: NodeJS.Platform;
58
+ skipFts: boolean;
59
+ memoryOff: boolean;
60
+ gitnexusHome: string | null;
61
+ }
62
+ /**
63
+ * Build the two commands for a repository.
64
+ *
65
+ * Pure, because the exact text is the deliverable — a reviewer should be able to
66
+ * assert on it without spawning anything.
67
+ *
68
+ * WHY `--skip-fts` IS NOT A DEFAULT
69
+ * ---------------------------------
70
+ * It was one, on the reasoning that the FTS extension installs out-of-process and a
71
+ * confined process denies that (`spawn EPERM`). Measured on a 39-file repository,
72
+ * that reasoning is wrong in the case that matters:
73
+ *
74
+ * index has FTS + `--skip-fts` 179.22 s full DB wipe + bulk COPY
75
+ * index has FTS, no flag 8.57 s FTS fails to load, degrades cleanly
76
+ * index has no FTS + `--skip-fts` 8.25 s changed=0, 39 rows preserved
77
+ * same, warm 6.56 s
78
+ *
79
+ * Disabling a search index the store already has forces GitNexus to rebuild the
80
+ * whole graph store, which is the exact opposite of incremental — a 21x penalty on
81
+ * the one transition an operator is most likely to hit. Letting FTS fail on its own
82
+ * costs nothing: it prints a warning and indexes anyway.
83
+ *
84
+ * The flag remains available for the case it was written for, where the extension
85
+ * file is *absent* and the install itself is what fails.
86
+ */
87
+ export declare function maintenanceCommands(repoPath: string, options?: MaintenanceOptions): MaintenanceCommand[];
88
+ /** The options {@link commandRationale} needs — a subset of the command options. */
89
+ export type RationaleOptions = Pick<ResolvedOptions, 'skipFts' | 'memoryOff' | 'gitnexusHome'>;
90
+ /**
91
+ * A one-line explanation of why the environment prefix is there at all, for the
92
+ * panel to show under the commands rather than leaving them looking arbitrary.
93
+ */
94
+ export declare function commandRationale(options: RationaleOptions): string[];
95
+ export {};
96
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/gitnexus/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,mFAAmF;AACnF,UAAU,eAAe;IACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,kBAAkB,EAAE,CA0D5G;AAED,oFAAoF;AACpF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,EAAE,SAAS,GAAG,WAAW,GAAG,cAAc,CAAC,CAAC;AAE/F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,EAAE,CAcpE"}