arkgate 4.8.23 → 4.8.24

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 (131) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +24 -77
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +596 -112
  6. package/bin/ark-mcp-runtime.mjs +412 -138
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +39 -0
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/agent-projection-command.mjs +5 -1
  13. package/bin/lib/analysis-engine.mjs +15 -11
  14. package/bin/lib/architecture-scan.mjs +50 -6
  15. package/bin/lib/ark-order-doctor.mjs +1 -1
  16. package/bin/lib/ark-order-facts.mjs +410 -15
  17. package/bin/lib/ark-order-invariants.mjs +51 -9
  18. package/bin/lib/ark-order-sensors.mjs +28 -101
  19. package/bin/lib/ark-order-types.mjs +2 -0
  20. package/bin/lib/ark-run-doctor.mjs +14 -0
  21. package/bin/lib/ark-run-facts.mjs +434 -11
  22. package/bin/lib/ark-run-sensors.mjs +5 -3
  23. package/bin/lib/arkgate-pins.mjs +91 -0
  24. package/bin/lib/arkrules-sensors.mjs +50 -249
  25. package/bin/lib/check-args.mjs +23 -1
  26. package/bin/lib/ci-and-commands.mjs +16 -6
  27. package/bin/lib/class-source-scan.mjs +479 -0
  28. package/bin/lib/codex-home.mjs +4 -2
  29. package/bin/lib/config-contract-slices.mjs +701 -0
  30. package/bin/lib/config-contract.mjs +5 -493
  31. package/bin/lib/config-warnings.mjs +32 -0
  32. package/bin/lib/dashboard-cli.mjs +62 -0
  33. package/bin/lib/design-delta.mjs +1 -1
  34. package/bin/lib/diagnostic-catalog.mjs +16 -9
  35. package/bin/lib/doctor-advisories.mjs +25 -1
  36. package/bin/lib/doctor-next-actions.mjs +17 -2
  37. package/bin/lib/doctor-plan.mjs +6 -8
  38. package/bin/lib/effective-contract-load.mjs +121 -59
  39. package/bin/lib/enforcement-profiles.mjs +40 -4
  40. package/bin/lib/first-run-help.mjs +3 -2
  41. package/bin/lib/flat-parent-importers.mjs +137 -0
  42. package/bin/lib/flat-parent-pilot.mjs +40 -100
  43. package/bin/lib/gate-files.mjs +29 -2
  44. package/bin/lib/git-change-scope.mjs +109 -0
  45. package/bin/lib/github-enforcement.mjs +1 -1
  46. package/bin/lib/hook-templates.mjs +18 -16
  47. package/bin/lib/host-support-matrix.mjs +3 -1
  48. package/bin/lib/import-resolve.mjs +13 -1
  49. package/bin/lib/install-migrate.mjs +50 -73
  50. package/bin/lib/invariant-coverage.mjs +59 -11
  51. package/bin/lib/mcp-adoption.mjs +9 -1
  52. package/bin/lib/mcp-hook-payload.mjs +401 -61
  53. package/bin/lib/package-manager.mjs +59 -1
  54. package/bin/lib/passthrough-commands.mjs +83 -0
  55. package/bin/lib/persistence-write-hint.mjs +81 -0
  56. package/bin/lib/pin-dependent-gates.mjs +117 -0
  57. package/bin/lib/policy-delta-io.mjs +168 -10
  58. package/bin/lib/prepare-change.mjs +120 -6
  59. package/bin/lib/prepare-write.mjs +1 -0
  60. package/bin/lib/presets.mjs +3 -5
  61. package/bin/lib/remediation.mjs +23 -4
  62. package/bin/lib/resolved-candidate-facts.mjs +315 -55
  63. package/bin/lib/rules-inventory.mjs +22 -3
  64. package/bin/lib/rules-under-contract.mjs +179 -5
  65. package/bin/lib/scan-files.mjs +32 -0
  66. package/bin/lib/snippet-analysis.mjs +140 -6
  67. package/bin/lib/source-policy.mjs +34 -0
  68. package/bin/lib/stable-hash.mjs +90 -0
  69. package/bin/lib/start-install-recovery.mjs +71 -1
  70. package/bin/lib/start-preview.mjs +30 -0
  71. package/bin/lib/status-command.mjs +33 -4
  72. package/bin/lib/status-manifest.mjs +32 -4
  73. package/bin/lib/team-parliament-io.mjs +77 -75
  74. package/bin/lib/team-parliament.mjs +6 -1
  75. package/bin/lib/write-path-capabilities.mjs +4 -2
  76. package/dist/chunk-A5EIY3HI.cjs +5 -0
  77. package/dist/chunk-MBXCFLO4.js +16 -0
  78. package/dist/chunk-RNQHRDCQ.cjs +16 -0
  79. package/dist/chunk-SVSQVGFJ.js +5 -0
  80. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-BJbaeoj4.d.cts} +18 -3
  81. package/dist/configTypes-BJbaeoj4.d.ts +246 -0
  82. package/dist/eslint/index.cjs +17 -4
  83. package/dist/eslint/index.d.cts +359 -0
  84. package/dist/eslint/index.d.ts +130 -156
  85. package/dist/eslint/index.js +10 -4
  86. package/dist/index.cjs +35 -34
  87. package/dist/index.d.cts +5475 -0
  88. package/dist/index.d.ts +3312 -299
  89. package/dist/index.js +35 -34
  90. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  91. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  92. package/dist/nestjs/index.cjs +1 -18
  93. package/dist/nestjs/index.d.cts +22 -0
  94. package/dist/nestjs/index.d.ts +1 -3
  95. package/dist/nestjs/index.js +1 -18
  96. package/dist/order/index.cjs +1 -1
  97. package/dist/order/index.d.cts +155 -0
  98. package/dist/order/index.d.ts +15 -3
  99. package/dist/order/index.js +1 -1
  100. package/dist/runtime/index.cjs +13 -30
  101. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +804 -290
  102. package/dist/runtime/index.d.ts +2678 -108
  103. package/dist/runtime/index.js +13 -30
  104. package/dist/{types-Bdj32QwA.d.ts → types-DRinG-vT.d.cts} +576 -140
  105. package/dist/types-DRinG-vT.d.ts +1792 -0
  106. package/docs/README.md +2 -1
  107. package/docs/agent-guide.md +84 -15
  108. package/docs/ai-gates.md +160 -26
  109. package/docs/arkorder.md +32 -15
  110. package/docs/brownfield-adoption.md +5 -1
  111. package/docs/configuration.md +196 -26
  112. package/docs/develop.md +2 -1
  113. package/docs/diagnostics.md +96 -19
  114. package/docs/package-surface.md +31 -8
  115. package/docs/product-voice.md +1 -1
  116. package/package.json +59 -18
  117. package/schemas/ark.analysis-result.schema.json +15 -0
  118. package/schemas/ark.config.schema.json +47 -10
  119. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  120. package/schemas/ark.status-manifest.schema.json +22 -0
  121. package/server.json +3 -9
  122. package/templates/agent-skills/README.md +1 -1
  123. package/templates/agent-skills/ark-adopt/SKILL.md +4 -1
  124. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  125. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  126. package/templates/arkrules/PersistenceAdapters.json +1 -1
  127. package/templates/arkrules/PresentationAdapters.json +1 -1
  128. package/templates/skills/ark-adopt.md +4 -1
  129. package/templates/skills/ark-order.md +4 -2
  130. package/templates/skills/ark-runtime.md +6 -1
  131. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -0,0 +1,479 @@
1
+ /**
2
+ * GENERATED FILE — do not edit by hand.
3
+ *
4
+ * Canonical algorithm: src/domain/classSourceScan.ts
5
+ * Regenerate: node scripts/generate-cli-pure.mjs
6
+ * Drift check: node scripts/generate-cli-pure.mjs --check
7
+ *
8
+ * Pure CLI helper (bin/lib/class-source-scan.mjs). Zero Node I/O.
9
+ */
10
+
11
+ export const MEMBER_MODIFIERS = new Set([
12
+ 'public',
13
+ 'private',
14
+ 'protected',
15
+ 'static',
16
+ 'async',
17
+ 'readonly',
18
+ 'abstract',
19
+ 'override',
20
+ 'declare',
21
+ 'get',
22
+ 'set',
23
+ ]);
24
+ /** Characters after which a `/` starts a regular-expression literal, not a division. */
25
+ const REGEX_PRECEDING_CHARS = new Set([
26
+ '(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '+', '-', '*', '%', '<', '>', '~', '^',
27
+ ]);
28
+ /** Keywords after which a `/` starts a regular-expression literal. */
29
+ const REGEX_PRECEDING_KEYWORDS = new Set([
30
+ 'return', 'typeof', 'case', 'do', 'else', 'in', 'of', 'new', 'delete', 'void', 'throw',
31
+ 'instanceof', 'yield', 'await',
32
+ ]);
33
+ /**
34
+ * True when the `/` at `index` sits where an expression starts (so it opens a
35
+ * regex literal). Looks back to the previous significant character; identifiers,
36
+ * numbers, `)` and `]` mean division.
37
+ */
38
+ function slashStartsRegex(src, index) {
39
+ let j = index - 1;
40
+ while (j >= 0 && /\s/.test(src[j]))
41
+ j -= 1;
42
+ if (j < 0)
43
+ return true;
44
+ const prev = src[j];
45
+ if (/[A-Za-z0-9_$]/.test(prev)) {
46
+ let k = j;
47
+ while (k >= 0 && /[A-Za-z0-9_$]/.test(src[k]))
48
+ k -= 1;
49
+ return REGEX_PRECEDING_KEYWORDS.has(src.slice(k + 1, j + 1));
50
+ }
51
+ // `*/` ends a comment; what came before it is not examined (treated as division, rare).
52
+ if (prev === '/' && src[j - 1] === '*')
53
+ return false;
54
+ return REGEX_PRECEDING_CHARS.has(prev);
55
+ }
56
+ /**
57
+ * Index after the regex literal starting at `index` (`/…/flags`), or `index` when
58
+ * none starts there. Character classes (`[/]`) and escapes are honoured; a line
59
+ * break before the closing `/` means it was not a regex after all.
60
+ */
61
+ function skipRegexLiteral(src, index) {
62
+ if (!slashStartsRegex(src, index))
63
+ return index;
64
+ let j = index + 1;
65
+ let inClass = false;
66
+ while (j < src.length) {
67
+ const ch = src[j];
68
+ if (ch === '\n' || ch === '\r')
69
+ return index;
70
+ if (ch === '\\') {
71
+ j += 2;
72
+ continue;
73
+ }
74
+ if (inClass) {
75
+ if (ch === ']')
76
+ inClass = false;
77
+ }
78
+ else if (ch === '[') {
79
+ inClass = true;
80
+ }
81
+ else if (ch === '/') {
82
+ j += 1;
83
+ while (j < src.length && /[A-Za-z]/.test(src[j]))
84
+ j += 1;
85
+ return j;
86
+ }
87
+ j += 1;
88
+ }
89
+ return index;
90
+ }
91
+ /**
92
+ * Index after a string / comment / regex literal starting at `index`, or `index`
93
+ * when none starts there.
94
+ */
95
+ export function skipStringOrComment(src, index) {
96
+ const ch = src[index];
97
+ if (ch === '/' && src[index + 1] === '/') {
98
+ const nl = src.indexOf('\n', index);
99
+ return nl === -1 ? src.length : nl;
100
+ }
101
+ if (ch === '/' && src[index + 1] === '*') {
102
+ const end = src.indexOf('*/', index + 2);
103
+ return end === -1 ? src.length : end + 2;
104
+ }
105
+ if (ch === '/')
106
+ return skipRegexLiteral(src, index);
107
+ if (ch === "'" || ch === '"' || ch === '`') {
108
+ let j = index + 1;
109
+ while (j < src.length) {
110
+ if (src[j] === '\\') {
111
+ j += 2;
112
+ continue;
113
+ }
114
+ if (src[j] === ch)
115
+ return j + 1;
116
+ j += 1;
117
+ }
118
+ return src.length;
119
+ }
120
+ return index;
121
+ }
122
+ export function skipWsAndComments(src, index) {
123
+ let i = index;
124
+ while (i < src.length) {
125
+ if (/\s/.test(src[i])) {
126
+ i += 1;
127
+ continue;
128
+ }
129
+ if (src[i] === '/' && (src[i + 1] === '/' || src[i + 1] === '*')) {
130
+ i = skipStringOrComment(src, i);
131
+ continue;
132
+ }
133
+ break;
134
+ }
135
+ return i;
136
+ }
137
+ export function readIdent(src, index) {
138
+ const ch = src[index];
139
+ if (!ch || !/[A-Za-z_$]/.test(ch))
140
+ return null;
141
+ let j = index + 1;
142
+ while (j < src.length && /[A-Za-z0-9_$]/.test(src[j]))
143
+ j += 1;
144
+ return { ident: src.slice(index, j), end: j };
145
+ }
146
+ /**
147
+ * Index just after the delimiter closing `openCh` at `openIndex`, or null when it
148
+ * never closes. Strings and comments are skipped. For `<`/`>`, an arrow `=>` is not
149
+ * a closing angle.
150
+ */
151
+ export function skipBalanced(src, openIndex, openCh, closeCh) {
152
+ if (src[openIndex] !== openCh)
153
+ return null;
154
+ let depth = 1;
155
+ let i = openIndex + 1;
156
+ while (i < src.length && depth > 0) {
157
+ const skipped = skipStringOrComment(src, i);
158
+ if (skipped !== i) {
159
+ i = skipped;
160
+ continue;
161
+ }
162
+ const ch = src[i];
163
+ if (ch === openCh)
164
+ depth += 1;
165
+ else if (ch === closeCh && !(closeCh === '>' && src[i - 1] === '='))
166
+ depth -= 1;
167
+ i += 1;
168
+ }
169
+ return depth === 0 ? i : null;
170
+ }
171
+ export function scanClassMembers(body) {
172
+ const members = [];
173
+ let i = 0;
174
+ let truncatedAt;
175
+ while (i < body.length) {
176
+ i = skipWsAndComments(body, i);
177
+ if (i >= body.length)
178
+ break;
179
+ if (body[i] === ';') {
180
+ i += 1;
181
+ continue;
182
+ }
183
+ const modifiers = [];
184
+ let cursor = i;
185
+ while (true) {
186
+ const tok = readIdent(body, cursor);
187
+ if (!tok || !MEMBER_MODIFIERS.has(tok.ident))
188
+ break;
189
+ modifiers.push(tok.ident);
190
+ cursor = skipWsAndComments(body, tok.end);
191
+ }
192
+ if (modifiers.length === 1 && modifiers[0] === 'static' && body[cursor] === '{') {
193
+ // `static { … }` is a class static initialization block, not a member.
194
+ const afterBlock = skipBalanced(body, cursor, '{', '}');
195
+ if (afterBlock == null) {
196
+ truncatedAt = body.length;
197
+ break;
198
+ }
199
+ i = afterBlock;
200
+ continue;
201
+ }
202
+ const nameTok = readIdent(body, cursor);
203
+ if (!nameTok) {
204
+ i += 1;
205
+ continue;
206
+ }
207
+ cursor = skipWsAndComments(body, nameTok.end);
208
+ if (body[cursor] === '<') {
209
+ const afterGeneric = skipBalanced(body, cursor, '<', '>');
210
+ if (afterGeneric == null) {
211
+ truncatedAt = body.length;
212
+ break;
213
+ }
214
+ cursor = skipWsAndComments(body, afterGeneric);
215
+ }
216
+ if (body[cursor] === '(') {
217
+ const afterParen = skipBalanced(body, cursor, '(', ')');
218
+ if (afterParen == null) {
219
+ truncatedAt = body.length;
220
+ break;
221
+ }
222
+ cursor = skipWsAndComments(body, afterParen);
223
+ if (body[cursor] === ':') {
224
+ cursor += 1;
225
+ while (cursor < body.length && body[cursor] !== '{' && body[cursor] !== ';') {
226
+ const skipped = skipStringOrComment(body, cursor);
227
+ if (skipped !== cursor) {
228
+ cursor = skipped;
229
+ continue;
230
+ }
231
+ cursor += 1;
232
+ }
233
+ }
234
+ if (body[cursor] === '{') {
235
+ const afterBrace = skipBalanced(body, cursor, '{', '}');
236
+ if (afterBrace == null) {
237
+ truncatedAt = body.length;
238
+ break;
239
+ }
240
+ members.push({
241
+ name: nameTok.ident,
242
+ modifiers,
243
+ kind: 'method',
244
+ body: body.slice(cursor + 1, afterBrace - 1),
245
+ });
246
+ i = afterBrace;
247
+ continue;
248
+ }
249
+ if (body[cursor] === ';') {
250
+ i = cursor + 1;
251
+ continue;
252
+ }
253
+ i = cursor + 1;
254
+ continue;
255
+ }
256
+ let depthBrace = 0;
257
+ let depthParen = 0;
258
+ let depthBracket = 0;
259
+ while (cursor < body.length) {
260
+ const skipped = skipStringOrComment(body, cursor);
261
+ if (skipped !== cursor) {
262
+ cursor = skipped;
263
+ continue;
264
+ }
265
+ const ch = body[cursor];
266
+ if (ch === '{')
267
+ depthBrace += 1;
268
+ else if (ch === '}') {
269
+ if (depthBrace === 0)
270
+ break;
271
+ depthBrace -= 1;
272
+ }
273
+ else if (ch === '(')
274
+ depthParen += 1;
275
+ else if (ch === ')')
276
+ depthParen -= 1;
277
+ else if (ch === '[')
278
+ depthBracket += 1;
279
+ else if (ch === ']')
280
+ depthBracket -= 1;
281
+ else if (ch === ';' && depthBrace === 0 && depthParen === 0 && depthBracket === 0) {
282
+ cursor += 1;
283
+ break;
284
+ }
285
+ cursor += 1;
286
+ }
287
+ if (cursor >= body.length && (depthBrace > 0 || depthParen > 0 || depthBracket > 0)) {
288
+ // An initializer that never balances: later members are not visible.
289
+ truncatedAt = body.length;
290
+ }
291
+ members.push({
292
+ name: nameTok.ident,
293
+ modifiers,
294
+ kind: 'field',
295
+ body: '',
296
+ });
297
+ i = cursor;
298
+ }
299
+ return { members, truncatedAt };
300
+ }
301
+ /**
302
+ * Walk a class header from just after its name to the opening body brace.
303
+ * Type parameters, `extends Base<{ a: 1 }>`, `implements A, B<C>` and call
304
+ * expressions in `extends mixin(Base)` are skipped with balanced delimiters.
305
+ * Returns the index of `{`, or null when the header cannot be walked.
306
+ */
307
+ function findClassBodyBrace(content, from) {
308
+ let i = from;
309
+ while (i < content.length) {
310
+ const skipped = skipWsAndComments(content, i);
311
+ if (skipped !== i) {
312
+ i = skipped;
313
+ continue;
314
+ }
315
+ const ch = content[i];
316
+ if (ch === '{')
317
+ return i;
318
+ if (ch === ';')
319
+ return null;
320
+ if (ch === '<' || ch === '(' || ch === '[') {
321
+ const close = ch === '<' ? '>' : ch === '(' ? ')' : ']';
322
+ const after = skipBalanced(content, i, ch, close);
323
+ if (after == null)
324
+ return null;
325
+ i = after;
326
+ continue;
327
+ }
328
+ if (ch === '"' || ch === "'" || ch === '`') {
329
+ i = skipStringOrComment(content, i);
330
+ continue;
331
+ }
332
+ i += 1;
333
+ }
334
+ return null;
335
+ }
336
+ /**
337
+ * Locate class declarations. `exportedOnly` keeps the historic sensor scope:
338
+ * `export class` / `export abstract class` (not `export default class`).
339
+ * Otherwise any `class Name` in code (exported, default-exported, or local).
340
+ * Declarations inside comments and strings are ignored.
341
+ */
342
+ export function findClassDeclarations(content, options = {}) {
343
+ const out = [];
344
+ const headerRe = options.exportedOnly
345
+ ? /\bexport\s+(?:abstract\s+)?class\s+([A-Za-z_$][A-Za-z0-9_$]*)/g
346
+ : /(?:\b(export)\s+(?:default\s+)?)?(?:\babstract\s+)?\bclass\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
347
+ const code = maskStringsAndComments(content);
348
+ let match;
349
+ while ((match = headerRe.exec(code)) !== null) {
350
+ const name = (options.exportedOnly ? match[1] : match[2]);
351
+ const exported = options.exportedOnly === true || match[1] === 'export';
352
+ const brace = findClassBodyBrace(content, match.index + match[0].length);
353
+ if (brace == null) {
354
+ out.push({ name, start: match.index, exported, bodyStart: null, bodyEnd: null });
355
+ continue;
356
+ }
357
+ const after = skipBalanced(content, brace, '{', '}');
358
+ out.push({
359
+ name,
360
+ start: match.index,
361
+ exported,
362
+ bodyStart: brace + 1,
363
+ bodyEnd: after == null ? null : after - 1,
364
+ });
365
+ }
366
+ return out;
367
+ }
368
+ /** Same length as `content`; string and comment characters become spaces (newlines kept). */
369
+ function maskStringsAndComments(content) {
370
+ let out = '';
371
+ let i = 0;
372
+ while (i < content.length) {
373
+ const next = skipStringOrComment(content, i);
374
+ if (next !== i) {
375
+ out += content.slice(i, next).replace(/[^\n]/g, ' ');
376
+ i = next;
377
+ continue;
378
+ }
379
+ out += content[i];
380
+ i += 1;
381
+ }
382
+ return out;
383
+ }
384
+ /**
385
+ * Locate `namespace Name {` / `module Name {`, `const Name = {` (object literal) and
386
+ * `const Name = class … {` (class expression) declarations of `name`, so a
387
+ * `Name.member` symbol can be checked for membership in them as well as in
388
+ * `class Name`. Strings, comments, and regex literals are ignored.
389
+ */
390
+ export function findMemberContainers(content, name) {
391
+ const out = [];
392
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name))
393
+ return out;
394
+ const code = maskStringsAndComments(content);
395
+ const escaped = name.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
396
+ const re = new RegExp(`\\b(?:(namespace|module)\\s+${escaped}\\s*\\{|(?:const|let|var)\\s+${escaped}\\s*(?::[^=;]+?)?=(?!=|>)\\s*(class\\b)?)`, 'g');
397
+ let match;
398
+ while ((match = re.exec(code)) !== null) {
399
+ let brace;
400
+ let kind;
401
+ if (match[1]) {
402
+ kind = 'namespace';
403
+ brace = match.index + match[0].length - 1;
404
+ }
405
+ else if (match[2]) {
406
+ kind = 'class-expression';
407
+ brace = findClassBodyBrace(content, match.index + match[0].length);
408
+ }
409
+ else {
410
+ kind = 'object';
411
+ const at = skipWsAndComments(content, match.index + match[0].length);
412
+ brace = content[at] === '{' ? at : null;
413
+ }
414
+ if (brace == null)
415
+ continue;
416
+ const after = skipBalanced(content, brace, '{', '}');
417
+ out.push({ kind, bodyStart: brace + 1, bodyEnd: after == null ? content.length : after - 1 });
418
+ }
419
+ return out;
420
+ }
421
+ /**
422
+ * Top-level member names of an object-literal body: `name() {}`, `async name()`,
423
+ * `*name()`, `name: …`, and shorthand `name,`. Nested braces/parens/brackets are
424
+ * skipped so a key of an inner object is not a member of the outer one.
425
+ */
426
+ export function objectLiteralMemberNames(body) {
427
+ const names = [];
428
+ let i = 0;
429
+ let expectKey = true;
430
+ while (i < body.length) {
431
+ const skipped = skipStringOrComment(body, i);
432
+ if (skipped !== i) {
433
+ i = skipped;
434
+ expectKey = false;
435
+ continue;
436
+ }
437
+ const ch = body[i];
438
+ if (ch === '{' || ch === '(' || ch === '[') {
439
+ const close = ch === '{' ? '}' : ch === '(' ? ')' : ']';
440
+ i = skipBalanced(body, i, ch, close) ?? body.length;
441
+ expectKey = false;
442
+ continue;
443
+ }
444
+ if (ch === ',') {
445
+ expectKey = true;
446
+ i += 1;
447
+ continue;
448
+ }
449
+ if (expectKey && /[A-Za-z_$]/.test(ch)) {
450
+ let tok = readIdent(body, i);
451
+ // `async name()`, `get name()`, `set name(v)`, `async *name()`: the modifier is not the key.
452
+ while (tok && (tok.ident === 'async' || tok.ident === 'get' || tok.ident === 'set')) {
453
+ let next = skipWsAndComments(body, tok.end);
454
+ if (body[next] === '*')
455
+ next = skipWsAndComments(body, next + 1);
456
+ const peek = readIdent(body, next);
457
+ if (!peek)
458
+ break;
459
+ tok = peek;
460
+ }
461
+ if (tok) {
462
+ const after = skipWsAndComments(body, tok.end);
463
+ if ('(:,<'.includes(body[after] ?? '') || after >= body.length)
464
+ names.push(tok.ident);
465
+ i = tok.end;
466
+ expectKey = false;
467
+ continue;
468
+ }
469
+ }
470
+ if (expectKey && ch === '*') {
471
+ i += 1;
472
+ continue;
473
+ }
474
+ if (!/\s/.test(ch))
475
+ expectKey = false;
476
+ i += 1;
477
+ }
478
+ return names;
479
+ }
@@ -7,6 +7,7 @@ import fs from 'node:fs';
7
7
  import os from 'node:os';
8
8
  import path from 'node:path';
9
9
  import { execCommandParts } from '../ark-shared.mjs';
10
+ import { npxArkgatePrefixLength } from './package-manager.mjs';
10
11
 
11
12
  export const PREFERRED_CODEX_MCP_BIN = 'arkgate-mcp';
12
13
 
@@ -204,8 +205,9 @@ function codexArkMcpInvocation(command, args) {
204
205
  const argv = [command, ...args];
205
206
  if (isArkMcpToken(command)) return { binArgs: argv.slice(1) };
206
207
  const runner = executableName(command);
207
- if ((runner === 'npx' || runner === 'yarn') && isArkMcpToken(args[0])) {
208
- return { binArgs: argv.slice(2) };
208
+ const npxPrefix = runner === 'npx' ? npxArkgatePrefixLength(args) : 0;
209
+ if ((runner === 'npx' || runner === 'yarn') && isArkMcpToken(args[npxPrefix])) {
210
+ return { binArgs: argv.slice(npxPrefix + 2) };
209
211
  }
210
212
  if (runner === 'node') {
211
213
  const script = args[0]?.replace(/\\/g, '/');