arkgate 3.6.1 → 3.8.0

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 (83) hide show
  1. package/CHANGELOG.md +122 -1145
  2. package/README.md +59 -19
  3. package/bin/ark-check-runtime.mjs +1598 -0
  4. package/bin/ark-check.mjs +32 -1565
  5. package/bin/ark-layer-match.mjs +2 -1
  6. package/bin/ark-mcp-runtime.mjs +1976 -0
  7. package/bin/ark-mcp.mjs +84 -1495
  8. package/bin/ark-shared.mjs +34 -38
  9. package/bin/ark.mjs +33 -66
  10. package/bin/lib/adapter-contract.mjs +161 -9
  11. package/bin/lib/agent-gates.mjs +1 -0
  12. package/bin/lib/analysis-completeness.mjs +28 -0
  13. package/bin/lib/analysis-engine.mjs +8 -8
  14. package/bin/lib/analysis-policy.mjs +27 -0
  15. package/bin/lib/architecture-scan.mjs +70 -304
  16. package/bin/lib/auto-patch.mjs +76 -8
  17. package/bin/lib/ci-and-commands.mjs +1 -1
  18. package/bin/lib/codex-home.mjs +43 -16
  19. package/bin/lib/design-delta.mjs +4 -0
  20. package/bin/lib/design-smells.mjs +67 -14
  21. package/bin/lib/doctor-advisories.mjs +23 -7
  22. package/bin/lib/doctor-plan.mjs +44 -47
  23. package/bin/lib/enforcement-state.mjs +2 -0
  24. package/bin/lib/github-enforcement.mjs +443 -0
  25. package/bin/lib/hook-templates.mjs +12 -148
  26. package/bin/lib/html-report-advisories.mjs +59 -0
  27. package/bin/lib/html-report-depth.mjs +9 -0
  28. package/bin/lib/html-report.mjs +5 -5
  29. package/bin/lib/install-migrate.mjs +83 -79
  30. package/bin/lib/managed-upgrade.mjs +622 -0
  31. package/bin/lib/mcp-adoption.mjs +3 -1
  32. package/bin/lib/parse-health.mjs +75 -0
  33. package/bin/lib/port-proof.mjs +2 -2
  34. package/bin/lib/prepare-change.mjs +68 -38
  35. package/bin/lib/prepare-write.mjs +7 -1
  36. package/bin/lib/reshape-decisions.mjs +284 -0
  37. package/bin/lib/resident-doctor-client.mjs +55 -0
  38. package/bin/lib/resident-hook.mjs +247 -0
  39. package/bin/lib/resolved-candidate-facts.mjs +1160 -0
  40. package/bin/lib/scan-files.mjs +19 -6
  41. package/bin/lib/snippet-analysis.mjs +119 -0
  42. package/bin/lib/source-policy.mjs +24 -0
  43. package/bin/lib/typescript-host.mjs +15 -18
  44. package/bin/lib/unavailable-analysis.mjs +76 -0
  45. package/bin/lib/upgrade-command.mjs +115 -0
  46. package/bin/lib/weakest-link.mjs +21 -179
  47. package/bin/lib/write-path-capabilities.mjs +167 -16
  48. package/bin/lib/write-path-detect.mjs +3 -2
  49. package/dist/eslint/index.cjs +3 -3
  50. package/dist/eslint/index.d.ts +3 -0
  51. package/dist/eslint/index.js +3 -3
  52. package/dist/index.cjs +7 -7
  53. package/dist/index.d.ts +1073 -141
  54. package/dist/index.js +7 -7
  55. package/docs/agent-guide.md +127 -52
  56. package/docs/ai-gates.md +100 -18
  57. package/docs/configuration.md +6 -0
  58. package/docs/demos/01-write-gate-self-correction.md +2 -2
  59. package/docs/enthusiast/README.md +10 -10
  60. package/docs/enthusiast/how-to-gallery-starter.md +2 -2
  61. package/docs/enthusiast/reference-commands.md +18 -1
  62. package/docs/enthusiast/tutorial-first-project.md +2 -2
  63. package/docs/package-surface.md +101 -14
  64. package/docs/typescript-support.md +118 -37
  65. package/package.json +33 -4
  66. package/schemas/ark.analysis-result.schema.json +159 -2
  67. package/schemas/ark.design-delta.schema.json +1 -0
  68. package/schemas/ark.enforcement-state.schema.json +84 -0
  69. package/schemas/ark.resolved-candidate-facts.schema.json +1 -0
  70. package/server.json +2 -2
  71. package/templates/skills/ark-autopilot.md +12 -0
  72. package/templates/skills/ark-explore.md +12 -5
  73. package/templates/skills/ark-fix.md +12 -2
  74. package/templates/skills/ark-loop.md +14 -1
  75. package/templates/skills/ark-runtime.md +15 -8
  76. package/templates/skills/ark-upgrade.md +122 -182
  77. package/bin/lib/ai-velocity.mjs +0 -293
  78. package/bin/lib/graph-cycles.mjs +0 -6
  79. package/bin/lib/safety-diagnostics.mjs +0 -284
  80. package/bin/lib/ts-resolve.mjs +0 -227
  81. package/dist/configTypes-DAPvBqK6.d.cts +0 -61
  82. package/dist/eslint/index.d.cts +0 -146
  83. package/dist/index.d.cts +0 -986
@@ -0,0 +1,247 @@
1
+ /** Z07 transport only; ark-mcp-runtime owns every hook decision. */
2
+ import { createHash } from 'node:crypto';
3
+ import fs from 'node:fs';
4
+ import net from 'node:net';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+
8
+ export const RESIDENT_HOOK_PROTOCOL_VERSION = 1;
9
+ const RESIDENT_DOCTOR_ENV = [
10
+ 'ARK_ACTIVE_HOST',
11
+ 'ARK_DOCTOR_GITHUB',
12
+ 'CODEX_HOME',
13
+ 'CODEX_CI',
14
+ 'CODEX_SANDBOX',
15
+ 'CODEX_SESSION_ID',
16
+ 'CODEX_THREAD_ID',
17
+ 'CLAUDE_CODE',
18
+ 'CLAUDE_CODE_ENTRYPOINT',
19
+ 'CLAUDE_PROJECT_DIR',
20
+ 'CLAUDECODE',
21
+ 'CURSOR_AGENT',
22
+ 'CURSOR_AGENT_CLI',
23
+ 'CURSOR_TRACE_ID',
24
+ 'GH_HOST',
25
+ 'GH_TOKEN',
26
+ 'GITHUB_TOKEN',
27
+ 'GROK_BUILD',
28
+ 'GROK_AGENT',
29
+ 'GROK_SESSION_ID',
30
+ 'GROK_WORKSPACE_ROOT',
31
+ 'HOME',
32
+ 'HOMEDRIVE',
33
+ 'HOMEPATH',
34
+ 'PATH',
35
+ 'USERPROFILE',
36
+ 'XAI_GROK',
37
+ ];
38
+
39
+ export function residentDoctorEnvironment(env = process.env) {
40
+ const hash = createHash('sha256');
41
+ for (const name of RESIDENT_DOCTOR_ENV) {
42
+ hash.update(name).update('\0').update(env[name] ?? '').update('\0');
43
+ }
44
+ return { digest: `sha256:${hash.digest('hex')}` };
45
+ }
46
+
47
+ function residentRuntimeIdentity(launcher) {
48
+ const hash = createHash('sha256');
49
+ const packageRoot = path.dirname(path.dirname(launcher));
50
+ const files = [path.join(packageRoot, 'package.json')];
51
+ const visit = (directory) => {
52
+ try {
53
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
54
+ const absolute = path.join(directory, entry.name);
55
+ if (entry.isDirectory()) visit(absolute);
56
+ else if (/\.(?:[cm]?js|json)$/.test(entry.name)) files.push(absolute);
57
+ }
58
+ } catch { files.push(directory); }
59
+ };
60
+ visit(path.join(packageRoot, 'bin'));
61
+ visit(path.join(packageRoot, 'dist'));
62
+ for (const file of [...new Set(files)].sort()) {
63
+ hash.update(path.relative(packageRoot, file)).update('\0');
64
+ try { hash.update(fs.readFileSync(file)); } catch { hash.update('missing'); }
65
+ hash.update('\0');
66
+ }
67
+ return hash.digest('hex');
68
+ }
69
+
70
+ export function residentHookEndpoint({ root, config, manifest, tsconfig, launcher }) {
71
+ let realRoot;
72
+ try { realRoot = fs.realpathSync(root); } catch { realRoot = path.resolve(root); }
73
+ const uid = typeof process.getuid === 'function' ? process.getuid() : 'user';
74
+ const directory = path.join(os.tmpdir(), `arkgate-${uid}`);
75
+ const digest = createHash('sha256').update(JSON.stringify({
76
+ root: realRoot,
77
+ config: path.resolve(root, config),
78
+ manifest: manifest ? path.resolve(root, manifest) : null,
79
+ tsconfig: tsconfig ? path.resolve(root, tsconfig) : null,
80
+ launcher: path.resolve(launcher),
81
+ executable: process.execPath,
82
+ protocolVersion: RESIDENT_HOOK_PROTOCOL_VERSION,
83
+ runtimeIdentity: residentRuntimeIdentity(path.resolve(launcher)),
84
+ })).digest('hex').slice(0, 24);
85
+ return {
86
+ directory,
87
+ socket: process.platform === 'win32'
88
+ ? `\\\\.\\pipe\\arkgate-z07-${uid}-${digest}`
89
+ : path.join(directory, `${digest}.sock`),
90
+ };
91
+ }
92
+
93
+ export function residentEnvironmentIdentity(paths, tokens = []) {
94
+ const hash = createHash('sha256');
95
+ for (const token of [...tokens].map(String).sort()) hash.update('token\0').update(token).update('\0');
96
+ for (const file of [...new Set(paths.map((entry) => path.resolve(entry)))].sort()) {
97
+ hash.update('path\0').update(file).update('\0');
98
+ try {
99
+ const stat = fs.lstatSync(file);
100
+ if (stat.isSymbolicLink()) {
101
+ hash.update('link\0').update(fs.readlinkSync(file)).update('\0');
102
+ hash.update(fs.realpathSync(file)).update('\0');
103
+ const target = fs.statSync(file);
104
+ if (target.isFile()) hash.update('target-file\0').update(fs.readFileSync(file));
105
+ else hash.update('target-other\0');
106
+ }
107
+ else if (stat.isFile()) hash.update('file\0').update(fs.readFileSync(file));
108
+ else hash.update('other\0');
109
+ } catch { hash.update('missing\0'); }
110
+ hash.update('\0');
111
+ }
112
+ return `sha256:${hash.digest('hex')}`;
113
+ }
114
+
115
+ function inputState(file) { // Stryker disable StringLiteral: opaque state tags have no public meaning
116
+ const stat = fs.lstatSync(file, { bigint: true, throwIfNoEntry: false });
117
+ if (!stat) return 'missing';
118
+ const metadata = [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeNs, stat.ctimeNs]
119
+ .map(String)
120
+ .join(':');
121
+ if (stat.isSymbolicLink()) {
122
+ const target = fs.realpathSync(file);
123
+ const targetStat = fs.statSync(file, { bigint: true });
124
+ return `link:${metadata}:${fs.readlinkSync(file)}:${target}:${[
125
+ targetStat.dev,
126
+ targetStat.ino,
127
+ targetStat.mode,
128
+ targetStat.size,
129
+ targetStat.mtimeNs,
130
+ targetStat.ctimeNs,
131
+ ].map(String).join(':')}`;
132
+ }
133
+ if (stat.isDirectory()) {
134
+ const entries = fs.readdirSync(file, { withFileTypes: true })
135
+ .map((entry) => `${entry.name}:${entry.isDirectory() ? 'd' : entry.isFile() ? 'f' : 'o'}`)
136
+ .sort();
137
+ return `dir:${metadata}:${entries.join('\0')}`;
138
+ }
139
+ return `${stat.isFile() ? 'file' : 'other'}:${metadata}`;
140
+ } // Stryker restore StringLiteral
141
+
142
+ /** Process-local, metadata-based invalidation ledger; never a persisted result cache. */
143
+ export function createResidentInputLedger(paths) {
144
+ const normalized = [...new Set(paths.map((entry) => path.resolve(entry)))].sort();
145
+ const states = new Map(normalized.map((file) => [file, inputState(file)]));
146
+ return {
147
+ paths: normalized,
148
+ matches(candidatePaths = normalized) {
149
+ try {
150
+ const candidate = [...new Set(candidatePaths.map((entry) => path.resolve(entry)))].sort();
151
+ return (
152
+ candidate.length === normalized.length &&
153
+ candidate.every((file, index) =>
154
+ file === normalized[index] && inputState(file) === states.get(file)
155
+ )
156
+ );
157
+ } catch {
158
+ return false;
159
+ }
160
+ },
161
+ };
162
+ }
163
+
164
+ export function requestResidentHook({ socket, request, timeoutMs = 75 }) {
165
+ return new Promise((resolve) => {
166
+ let done = false;
167
+ let buffered = '';
168
+ const client = net.createConnection(socket);
169
+ const finish = (value) => {
170
+ if (done) return;
171
+ done = true;
172
+ clearTimeout(timer);
173
+ client.destroy();
174
+ resolve(value);
175
+ };
176
+ const timer = setTimeout(() => finish(null), timeoutMs);
177
+ client.setEncoding('utf8');
178
+ client.once('connect', () => client.write(`${JSON.stringify(request)}\n`));
179
+ client.on('data', (chunk) => {
180
+ buffered += chunk;
181
+ if (buffered.length > 24 * 1024 * 1024) return finish(null);
182
+ const newline = buffered.indexOf('\n');
183
+ if (newline < 0) return;
184
+ try {
185
+ const parsed = JSON.parse(buffered.slice(0, newline));
186
+ finish(parsed?.protocolVersion === RESIDENT_HOOK_PROTOCOL_VERSION ? parsed : null);
187
+ } catch { finish(null); }
188
+ });
189
+ client.once('error', () => finish(null));
190
+ client.once('end', () => finish(null));
191
+ });
192
+ }
193
+
194
+ function secureDirectory(directory) {
195
+ if (process.platform === 'win32') return true;
196
+ try {
197
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
198
+ const stat = fs.lstatSync(directory);
199
+ if (!stat.isDirectory() || stat.isSymbolicLink()) return false;
200
+ if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) return false;
201
+ fs.chmodSync(directory, 0o700);
202
+ return true;
203
+ } catch { return false; }
204
+ }
205
+
206
+ export async function startResidentHookServer({ endpoint, handle }) {
207
+ if (!secureDirectory(endpoint.directory)) return null;
208
+ const server = net.createServer((client) => {
209
+ client.setEncoding('utf8');
210
+ let buffered = '';
211
+ let handled = false;
212
+ client.on('data', async (chunk) => {
213
+ if (handled) return;
214
+ buffered += chunk;
215
+ if (buffered.length > 12 * 1024 * 1024) return client.destroy();
216
+ const newline = buffered.indexOf('\n');
217
+ if (newline < 0) return;
218
+ handled = true;
219
+ let response;
220
+ try { response = await handle(JSON.parse(buffered.slice(0, newline))); }
221
+ catch { response = { protocolVersion: RESIDENT_HOOK_PROTOCOL_VERSION, fallback: true }; }
222
+ if (!client.destroyed) client.end(`${JSON.stringify(response)}\n`);
223
+ });
224
+ });
225
+ const listening = await new Promise((resolve) => {
226
+ const failed = () => resolve(false);
227
+ server.once('error', failed);
228
+ server.listen(endpoint.socket, () => {
229
+ server.off('error', failed);
230
+ resolve(true);
231
+ });
232
+ });
233
+ if (!listening) {
234
+ try { server.close(); } catch { /* The endpoint is already owned. */ }
235
+ return null;
236
+ }
237
+ let cleaned = false;
238
+ const cleanup = () => {
239
+ if (cleaned) return;
240
+ cleaned = true;
241
+ try { server.close(); } catch { /* Already closed. */ }
242
+ if (process.platform !== 'win32') {
243
+ try { fs.unlinkSync(endpoint.socket); } catch { /* Exact endpoint already absent. */ }
244
+ }
245
+ };
246
+ return { cleanup };
247
+ }