@ornexus/neocortex 4.60.18 → 4.60.19

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 (88) hide show
  1. package/dist/sbom.cdx.json +6504 -6410
  2. package/docs/install/installer-diagnostics.md +52 -0
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +27 -4
  6. package/packages/client/dist/agent/refresh-stubs.d.ts +7 -0
  7. package/packages/client/dist/agent/refresh-stubs.js +2 -2
  8. package/packages/client/dist/cache/encrypted-cache.d.ts +3 -0
  9. package/packages/client/dist/cache/encrypted-cache.js +1 -1
  10. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -2
  11. package/packages/client/dist/cli.js +3 -3
  12. package/packages/client/dist/commands/activate.js +7 -7
  13. package/packages/client/dist/commands/config.js +1 -1
  14. package/packages/client/dist/commands/invoke.d.ts +64 -0
  15. package/packages/client/dist/commands/invoke.js +54 -47
  16. package/packages/client/dist/config/secure-config.d.ts +9 -3
  17. package/packages/client/dist/config/secure-config.js +8 -8
  18. package/packages/client/dist/config.d.ts +1 -1
  19. package/packages/client/dist/config.js +1 -1
  20. package/packages/client/dist/continuity/continuity-client-state-store.d.ts +1 -0
  21. package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1
  22. package/packages/client/dist/continuity/invoke-hooks.d.ts +2 -1
  23. package/packages/client/dist/continuity/invoke-hooks.js +1 -1
  24. package/packages/client/dist/continuity/sqlite-store.d.ts +21 -0
  25. package/packages/client/dist/continuity/sqlite-store.js +49 -49
  26. package/packages/client/dist/effects/invoke-effect-gateways.d.ts +22 -0
  27. package/packages/client/dist/effects/invoke-effect-gateways.js +1 -0
  28. package/packages/client/dist/errors/frontmatter-diagnostics.d.ts +20 -0
  29. package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -0
  30. package/packages/client/dist/generated/command-registry.generated.d.ts +2836 -0
  31. package/packages/client/dist/generated/command-registry.generated.js +1 -0
  32. package/packages/client/dist/graph-retrieval/pre-command-hook.d.ts +2 -0
  33. package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -1
  34. package/packages/client/dist/index.d.ts +1 -0
  35. package/packages/client/dist/index.js +1 -1
  36. package/packages/client/dist/license/license-client.d.ts +3 -0
  37. package/packages/client/dist/license/license-client.js +1 -1
  38. package/packages/client/dist/machine/fingerprint.d.ts +3 -1
  39. package/packages/client/dist/machine/fingerprint.js +2 -2
  40. package/packages/client/dist/memory/project-memory-writer.js +19 -15
  41. package/packages/client/dist/{subagent-approval-policy.d.ts → policy/subagent-approval-policy.d.ts} +8 -3
  42. package/packages/client/dist/policy/subagent-approval-policy.js +1 -0
  43. package/packages/client/dist/resolvers/local-resolver.d.ts +3 -0
  44. package/packages/client/dist/resolvers/local-resolver.js +4 -4
  45. package/packages/client/dist/runner/cli.d.ts +1 -0
  46. package/packages/client/dist/runner/cli.js +1 -1
  47. package/packages/client/dist/runner/headless/prepared-story-bundle.d.ts +20 -0
  48. package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -0
  49. package/packages/client/dist/runner/headless/run-yoloop.d.ts +2 -0
  50. package/packages/client/dist/runner/headless/run-yoloop.js +1 -1
  51. package/packages/client/dist/runner/portal-sync-worker.js +1 -1
  52. package/packages/client/dist/runner/scheduler.d.ts +3 -0
  53. package/packages/client/dist/runner/scheduler.js +7 -7
  54. package/packages/client/dist/state/project-state-snapshot.js +1 -1
  55. package/packages/client/dist/tier/tier-aware-client.js +1 -1
  56. package/packages/client/dist/version.d.ts +3 -0
  57. package/packages/client/dist/version.js +1 -0
  58. package/packages/client/dist/yoloop/discovery-hook.js +2 -2
  59. package/packages/client/dist/yoloop/invoke-hooks.js +4 -4
  60. package/packages/client/dist/yoloop/shared-discover-epics.d.ts +9 -3
  61. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1
  62. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +24 -0
  63. package/packages/client/dist/yoloop/target-normalization.d.ts +24 -0
  64. package/packages/client/dist/yoloop/target-normalization.js +1 -0
  65. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +4 -0
  66. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
  67. package/packages/shared/dist/command-registry-contract.js +382 -0
  68. package/packages/shared/dist/generated/command-registry.generated.js +4216 -0
  69. package/packages/shared/dist/generated/invocation-schema.generated.js +1850 -0
  70. package/packages/shared/dist/invocation/index.d.ts +175 -0
  71. package/packages/shared/dist/invocation/index.js +587 -0
  72. package/packages/shared/dist/nx-invoke-signature.js +233 -0
  73. package/packages/shared/dist/strict-readonly.js +381 -0
  74. package/packages/shared/dist/yoloop/wave-decision-receipt.js +77 -0
  75. package/targets-stubs/antigravity/gemini.md +1 -1
  76. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  77. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  78. package/targets-stubs/claude-code/neocortex-root.md +5 -2
  79. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  80. package/targets-stubs/claude-code/neocortex.md +7 -2
  81. package/targets-stubs/codex/AGENTS.md +4 -1
  82. package/targets-stubs/codex/neocortex.toml +4 -0
  83. package/targets-stubs/cursor/agent.md +2 -2
  84. package/targets-stubs/gemini-cli/agent.md +2 -2
  85. package/targets-stubs/opencode/neocortex-root.md +4 -1
  86. package/targets-stubs/opencode/neocortex.md +3 -0
  87. package/targets-stubs/vscode/neocortex.agent.md +2 -2
  88. package/packages/client/dist/subagent-approval-policy.js +0 -1
@@ -0,0 +1,587 @@
1
+ /**
2
+ * P205.07 -- sole executable invocation-admission authority.
3
+ *
4
+ * This parser deliberately implements a small finite grammar. It is not an
5
+ * argv or shell parser and never expands, evaluates, interpolates or repairs
6
+ * user input.
7
+ */
8
+ import { createHash } from 'node:crypto';
9
+ import { COMMAND_REGISTRY_CHECKSUM, COMMAND_REGISTRY_VERSION, GENERATED_SHARED_COMMANDS, } from '../generated/command-registry.generated.js';
10
+ import { GENERATED_INVOCATION_SCHEMA_MANIFEST, GENERATED_INVOCATION_SCHEMAS, } from '../generated/invocation-schema.generated.js';
11
+ import { canonicalizeCommandRegistryJcs } from '../command-registry-contract.js';
12
+ export const EXACT_INVOCATION_POLICY = 'p205-exact-invocation-v1';
13
+ export const INVOCATION_SCHEMA_VERSION = 'p205-invocation-schema-v1';
14
+ export const INVOCATION_LIMITS = Object.freeze({
15
+ inputBytes: 64 * 1024,
16
+ tokens: 512,
17
+ tokenBytes: 16 * 1024,
18
+ briefBytes: 16 * 1024,
19
+ receiptBytes: 2048,
20
+ suggestions: 5,
21
+ });
22
+ const freeze = (value) => {
23
+ if (value && typeof value === 'object' && !Object.isFrozen(value)) {
24
+ for (const child of Object.values(value))
25
+ freeze(child);
26
+ Object.freeze(value);
27
+ }
28
+ return value;
29
+ };
30
+ const commands = [...GENERATED_SHARED_COMMANDS]
31
+ .filter((entry) => entry.visibility === 'public')
32
+ .sort((a, b) => a.id.localeCompare(b.id, 'en'));
33
+ const byId = new Map(commands.map((entry) => [entry.id, entry]));
34
+ const byAlias = new Map(commands.flatMap((entry) => entry.aliases.map((alias) => [alias, entry])));
35
+ const byNumber = new Map(commands.map((entry) => [String(entry.number), entry]));
36
+ const invocationSchemas = GENERATED_INVOCATION_SCHEMAS;
37
+ const schemaByCommand = new Map(invocationSchemas.map((schema) => [schema.commandId, schema]));
38
+ export const INVOCATION_SCHEMA_CHECKSUM = GENERATED_INVOCATION_SCHEMA_MANIFEST.schemaChecksum;
39
+ export const INVOCATION_SCHEMA_MANIFEST = freeze(GENERATED_INVOCATION_SCHEMA_MANIFEST);
40
+ export function assertInvocationSchemaAdmission(value) {
41
+ if (!value || typeof value !== 'object' || Array.isArray(value))
42
+ throw new Error('INVOCATION_SCHEMA_INVALID');
43
+ const candidate = value;
44
+ const exactKeys = ['commandCount', 'commands', 'operationCount', 'optionCount', 'positionalCount', 'projectionSha256', 'registryChecksum', 'registryVersion', 'schemaChecksum', 'schemaVersion'];
45
+ if (Object.keys(candidate).sort().join('\0') !== exactKeys.join('\0'))
46
+ throw new Error('INVOCATION_SCHEMA_INVALID');
47
+ if (candidate.schemaVersion !== INVOCATION_SCHEMA_VERSION
48
+ || candidate.registryVersion !== COMMAND_REGISTRY_VERSION
49
+ || candidate.registryChecksum !== COMMAND_REGISTRY_CHECKSUM
50
+ || candidate.schemaChecksum !== INVOCATION_SCHEMA_CHECKSUM
51
+ || !Array.isArray(candidate.commands))
52
+ throw new Error('INVOCATION_SCHEMA_DRIFT');
53
+ const schemaProjection = {
54
+ schemaVersion: candidate.schemaVersion,
55
+ registryVersion: candidate.registryVersion,
56
+ registryChecksum: candidate.registryChecksum,
57
+ commands: candidate.commands,
58
+ };
59
+ let schemaChecksum = '';
60
+ let projectionSha256 = '';
61
+ try {
62
+ schemaChecksum = createHash('sha256').update(canonicalizeCommandRegistryJcs(schemaProjection)).digest('hex');
63
+ projectionSha256 = createHash('sha256').update(canonicalizeCommandRegistryJcs(candidate.commands)).digest('hex');
64
+ }
65
+ catch {
66
+ throw new Error('INVOCATION_SCHEMA_INVALID');
67
+ }
68
+ if (schemaChecksum !== candidate.schemaChecksum || projectionSha256 !== candidate.projectionSha256
69
+ || JSON.stringify(candidate) !== JSON.stringify(GENERATED_INVOCATION_SCHEMA_MANIFEST))
70
+ throw new Error('INVOCATION_SCHEMA_DRIFT');
71
+ const ids = invocationSchemas.map((schema) => schema.commandId);
72
+ if (new Set(ids).size !== ids.length || JSON.stringify(ids) !== JSON.stringify(commands.map((command) => command.id))
73
+ || candidate.commandCount !== invocationSchemas.length
74
+ || candidate.optionCount !== invocationSchemas.reduce((sum, schema) => sum + schema.options.length, 0)
75
+ || candidate.positionalCount !== invocationSchemas.reduce((sum, schema) => sum + schema.positionals.length, 0)
76
+ || candidate.operationCount !== invocationSchemas.reduce((sum, schema) => sum + schema.operation.operations.length, 0)
77
+ || invocationSchemas.some((schema) => {
78
+ const command = byId.get(schema.commandId);
79
+ return !command || command.inputShape !== schema.inputShape
80
+ || JSON.stringify(schema.operation.operations) !== JSON.stringify(command.subcommands.map(({ id, aliases }) => ({ id, aliases })));
81
+ })) {
82
+ throw new Error('INVOCATION_SCHEMA_COVERAGE');
83
+ }
84
+ }
85
+ assertInvocationSchemaAdmission(INVOCATION_SCHEMA_MANIFEST);
86
+ function byteLength(value) {
87
+ return Buffer.byteLength(value, 'utf8');
88
+ }
89
+ function bucket(bytes) {
90
+ if (bytes === 0)
91
+ return '0';
92
+ if (bytes <= 64)
93
+ return '1-64';
94
+ if (bytes <= 256)
95
+ return '65-256';
96
+ if (bytes <= 1024)
97
+ return '257-1024';
98
+ if (bytes <= 4096)
99
+ return '1025-4096';
100
+ if (bytes <= 16384)
101
+ return '4097-16384';
102
+ return '16385-65536';
103
+ }
104
+ export function lexInvocationEnvelope(input) {
105
+ const inputBytes = byteLength(input);
106
+ if (inputBytes > INVOCATION_LIMITS.inputBytes)
107
+ return { ok: false, reason: 'input_limit', tokenCount: 0 };
108
+ for (let i = 0; i < input.length; i += 1) {
109
+ const code = input.charCodeAt(i);
110
+ if (code === 0 || (code < 0x20 && code !== 0x09 && code !== 0x0a && code !== 0x0d) || code === 0x7f) {
111
+ return { ok: false, reason: 'invalid_control', tokenCount: 0 };
112
+ }
113
+ if (code >= 0xd800 && code <= 0xdbff) {
114
+ const next = input.charCodeAt(i + 1);
115
+ if (!(next >= 0xdc00 && next <= 0xdfff))
116
+ return { ok: false, reason: 'invalid_surrogate', tokenCount: 0 };
117
+ i += 1;
118
+ }
119
+ else if (code >= 0xdc00 && code <= 0xdfff) {
120
+ return { ok: false, reason: 'invalid_surrogate', tokenCount: 0 };
121
+ }
122
+ }
123
+ const tokens = [];
124
+ let value = '';
125
+ let tokenStart = -1;
126
+ let quote = false;
127
+ let segmentQuote = false;
128
+ let segmentStart = -1;
129
+ let escaped = false;
130
+ let touched = false;
131
+ let whollyUnquoted = true;
132
+ let segments = [];
133
+ const closeSegment = (end) => {
134
+ if (segmentStart >= 0)
135
+ segments.push({ quoted: segmentQuote, start: segmentStart, end });
136
+ segmentStart = -1;
137
+ };
138
+ const startSegment = (at, quoted) => {
139
+ if (segmentStart >= 0 && segmentQuote !== quoted)
140
+ closeSegment(at);
141
+ if (segmentStart < 0) {
142
+ segmentStart = at;
143
+ segmentQuote = quoted;
144
+ }
145
+ };
146
+ const emit = (end) => {
147
+ if (!touched)
148
+ return null;
149
+ closeSegment(end);
150
+ if (byteLength(value) > INVOCATION_LIMITS.tokenBytes)
151
+ return { ok: false, reason: 'token_size_limit', tokenCount: tokens.length };
152
+ tokens.push(freeze({ value, start: tokenStart, end, whollyUnquoted, segments }));
153
+ if (tokens.length > INVOCATION_LIMITS.tokens)
154
+ return { ok: false, reason: 'token_limit', tokenCount: tokens.length };
155
+ value = '';
156
+ tokenStart = -1;
157
+ touched = false;
158
+ whollyUnquoted = true;
159
+ segments = [];
160
+ return null;
161
+ };
162
+ for (let i = 0; i < input.length; i += 1) {
163
+ const char = input[i];
164
+ if (escaped) {
165
+ const allowed = char === '\\' || char === ' ' || char === '\t' || char === '\r' || char === '\n'
166
+ || (quote === 'single' && char === "'") || (quote === 'double' && char === '"');
167
+ if (!allowed)
168
+ return { ok: false, reason: 'unsupported_escape', tokenCount: tokens.length };
169
+ startSegment(i - 1, quote);
170
+ value += char;
171
+ escaped = false;
172
+ continue;
173
+ }
174
+ if (char === '\\') {
175
+ if (!touched) {
176
+ touched = true;
177
+ tokenStart = i;
178
+ }
179
+ startSegment(i, quote);
180
+ escaped = true;
181
+ continue;
182
+ }
183
+ if (quote) {
184
+ if ((quote === 'single' && char === "'") || (quote === 'double' && char === '"')) {
185
+ closeSegment(i + 1);
186
+ quote = false;
187
+ }
188
+ else {
189
+ startSegment(i, quote);
190
+ value += char;
191
+ }
192
+ continue;
193
+ }
194
+ if (char === "'" || char === '"') {
195
+ if (!touched) {
196
+ touched = true;
197
+ tokenStart = i;
198
+ }
199
+ whollyUnquoted = false;
200
+ closeSegment(i);
201
+ quote = char === "'" ? 'single' : 'double';
202
+ startSegment(i, quote);
203
+ continue;
204
+ }
205
+ if (char === ' ' || char === '\t' || char === '\r' || char === '\n') {
206
+ const emitted = emit(i);
207
+ if (emitted)
208
+ return emitted;
209
+ continue;
210
+ }
211
+ if (!touched) {
212
+ touched = true;
213
+ tokenStart = i;
214
+ }
215
+ startSegment(i, false);
216
+ value += char;
217
+ }
218
+ if (escaped)
219
+ return { ok: false, reason: 'trailing_escape', tokenCount: tokens.length };
220
+ if (quote)
221
+ return { ok: false, reason: 'unterminated_quote', tokenCount: tokens.length };
222
+ const emitted = emit(input.length);
223
+ return emitted ?? { ok: true, tokens: freeze(tokens) };
224
+ }
225
+ function editDistance(left, right) {
226
+ const row = Array.from({ length: right.length + 1 }, (_, index) => index);
227
+ for (let i = 1; i <= left.length; i += 1) {
228
+ let diagonal = row[0];
229
+ row[0] = i;
230
+ for (let j = 1; j <= right.length; j += 1) {
231
+ const above = row[j];
232
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, diagonal + (left[i - 1] === right[j - 1] ? 0 : 1));
233
+ diagonal = above;
234
+ }
235
+ }
236
+ return row[right.length];
237
+ }
238
+ function suggestionsFor(token) {
239
+ const normalized = token.replace(/^\*/, '').toLowerCase();
240
+ if (!/^[a-z0-9?-]{2,64}$/.test(normalized))
241
+ return [];
242
+ if (/^\d+$/.test(normalized))
243
+ return [];
244
+ return commands
245
+ .map((command) => {
246
+ const identities = [command.id, ...command.aliases];
247
+ const score = Math.min(...identities.map((identity) => identity.startsWith(normalized) || normalized.startsWith(identity) ? 0 : editDistance(normalized, identity)));
248
+ return { id: command.id, score };
249
+ })
250
+ .filter((candidate) => candidate.score <= Math.max(2, Math.floor(normalized.length / 3)))
251
+ .sort((a, b) => a.score - b.score || a.id.localeCompare(b.id, 'en'))
252
+ .slice(0, INVOCATION_LIMITS.suggestions)
253
+ .map((candidate) => candidate.id);
254
+ }
255
+ function base(reason, tokenCount, bytes) {
256
+ return {
257
+ policy: EXACT_INVOCATION_POLICY,
258
+ schemaVersion: INVOCATION_SCHEMA_VERSION,
259
+ registryVersion: COMMAND_REGISTRY_VERSION,
260
+ registryChecksum: COMMAND_REGISTRY_CHECKSUM,
261
+ schemaChecksum: INVOCATION_SCHEMA_CHECKSUM,
262
+ reason,
263
+ tokenCount,
264
+ inputBytesBucket: bucket(bytes),
265
+ };
266
+ }
267
+ function reject(outcome, reason, tokenCount, bytes, candidates = []) {
268
+ return freeze({
269
+ ...base(reason, tokenCount, bytes),
270
+ outcome,
271
+ executable: false,
272
+ mutationAttempts: 0,
273
+ candidates: [...candidates].slice(0, INVOCATION_LIMITS.suggestions),
274
+ });
275
+ }
276
+ function resolveIdentity(token) {
277
+ if (!token.whollyUnquoted)
278
+ return null;
279
+ const normalized = token.value.replace(/^\*/, '').toLowerCase();
280
+ if (normalized !== '?' && !/^(?:[a-z][a-z0-9?-]*|[1-9][0-9]*)$/.test(normalized))
281
+ return null;
282
+ return byId.get(normalized) ?? byAlias.get(normalized) ?? byNumber.get(normalized) ?? null;
283
+ }
284
+ function validUrl(value) {
285
+ // URL consumers decode percent escapes after admission. Reject encoded C0
286
+ // controls and DEL here as well as literal controls so a value cannot cross
287
+ // this boundary clean and become control-bearing downstream.
288
+ if (/[\u0000-\u001f\u007f]/u.test(value) || /%(?:0[0-9a-f]|1[0-9a-f]|7f)/iu.test(value))
289
+ return false;
290
+ try {
291
+ const url = new URL(value);
292
+ return (url.protocol === 'http:' || url.protocol === 'https:') && !url.username && !url.password;
293
+ }
294
+ catch {
295
+ return false;
296
+ }
297
+ }
298
+ function validPath(value) {
299
+ return value.length > 0 && value.length <= 256 && !value.startsWith('/') && !value.startsWith('~')
300
+ && !value.includes('\\') && !value.includes('//')
301
+ && value.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..')
302
+ && /^[A-Za-z0-9._/-]+$/.test(value);
303
+ }
304
+ function canonicalUiUx(value) {
305
+ return JSON.stringify({
306
+ architectureDir: value.architectureDir ?? null,
307
+ url: value.url ?? null,
308
+ surface: value.surface ?? null,
309
+ brand: value.brand ?? null,
310
+ designSystem: value.designSystem ?? null,
311
+ evidenceOut: value.evidenceOut ?? null,
312
+ reviewBrief: value.reviewBrief ?? null,
313
+ json: value.json,
314
+ screenshots: value.screenshots,
315
+ projectEvidence: value.projectEvidence,
316
+ });
317
+ }
318
+ function normalizeUiUx(positionals, options) {
319
+ let architectureDir = typeof options['architecture-dir'] === 'string' ? options['architecture-dir'] : undefined;
320
+ let url = typeof options.url === 'string' ? options.url : undefined;
321
+ const briefParts = [];
322
+ for (const positional of positionals) {
323
+ if (/^https?:\/\//i.test(positional)) {
324
+ if (url)
325
+ return 'argument_conflict';
326
+ url = positional;
327
+ continue;
328
+ }
329
+ if (/^(?:\.\/)?docs\//.test(positional) || positional.endsWith('.md')) {
330
+ if (architectureDir)
331
+ return 'argument_conflict';
332
+ architectureDir = positional;
333
+ continue;
334
+ }
335
+ briefParts.push(positional);
336
+ }
337
+ if (url && !validUrl(url))
338
+ return 'invalid_option_value';
339
+ if (architectureDir && !validPath(architectureDir))
340
+ return 'invalid_option_value';
341
+ const reviewBrief = briefParts.length ? briefParts.join(' ') : undefined;
342
+ if (reviewBrief && byteLength(reviewBrief) > INVOCATION_LIMITS.briefBytes)
343
+ return 'brief_limit';
344
+ const raw = {
345
+ ...(architectureDir ? { architectureDir } : {}),
346
+ ...(url ? { url } : {}),
347
+ ...(Array.isArray(options.surface) ? { surface: options.surface } : {}),
348
+ ...(typeof options.brand === 'string' ? { brand: options.brand } : {}),
349
+ ...(typeof options['design-system'] === 'string' ? { designSystem: options['design-system'] } : {}),
350
+ ...(typeof options['evidence-out'] === 'string' ? { evidenceOut: options['evidence-out'] } : {}),
351
+ ...(reviewBrief ? { reviewBrief } : {}),
352
+ json: options.json === true,
353
+ screenshots: options.screenshots === true,
354
+ projectEvidence: options['project-evidence'] === true,
355
+ };
356
+ const fingerprint = createHash('sha256').update(`p205-uiux-brief-v1\0${canonicalUiUx(raw)}`).digest('hex');
357
+ return freeze({ ...raw, fingerprint });
358
+ }
359
+ function validPositional(kind, value) {
360
+ if (!value.length || byteLength(value) > INVOCATION_LIMITS.tokenBytes)
361
+ return false;
362
+ if (kind === 'story-ref') {
363
+ return /^@?(?:docs\/stories\/[A-Za-z0-9._/-]+\.story\.md|P\d+\.\d+|story-P\d+\.\d+)$/.test(value);
364
+ }
365
+ if (kind === 'epic-ref') {
366
+ return /^@?(?:docs\/epics\/epic-P\d+\.md|P\d+|epic-P\d+(?:\.md)?)$/i.test(value);
367
+ }
368
+ if (kind === 'target-ref') {
369
+ const target = value.startsWith('@') ? value.slice(1) : value;
370
+ // Directory mode is a canonical yoloop target. Keep general path
371
+ // admission strict while explicitly accepting its single trailing slash.
372
+ return /^(?:P\d+|EPIC-\d+)$/i.test(target)
373
+ || /^docs\/epics\/?$/.test(target)
374
+ || validPath(target);
375
+ }
376
+ return true;
377
+ }
378
+ function admitPositionals(values, schemas) {
379
+ let offset = 0;
380
+ for (const schema of schemas) {
381
+ const count = schema.cardinality === 'many' ? values.length - offset : Math.min(1, values.length - offset);
382
+ if (schema.required && count === 0)
383
+ return 'positional_required';
384
+ const selected = values.slice(offset, offset + count);
385
+ if (selected.some((value) => !validPositional(schema.kind, value)))
386
+ return 'invalid_positional_value';
387
+ offset += count;
388
+ }
389
+ return offset === values.length ? undefined : 'positional_overflow';
390
+ }
391
+ function admitArguments(schema, tokens) {
392
+ const rules = new Map();
393
+ for (const rule of schema.options) {
394
+ rules.set(rule.id, rule);
395
+ for (const alias of rule.aliases)
396
+ rules.set(alias, rule);
397
+ }
398
+ const options = {};
399
+ const positionals = [];
400
+ let terminated = false;
401
+ for (let index = 0; index < tokens.length; index += 1) {
402
+ const token = tokens[index];
403
+ if (!terminated && token.whollyUnquoted && token.value === '--') {
404
+ terminated = true;
405
+ continue;
406
+ }
407
+ if (!terminated && token.whollyUnquoted && token.value.startsWith('--')) {
408
+ const separator = token.value.indexOf('=');
409
+ const name = token.value.slice(2, separator < 0 ? undefined : separator);
410
+ const inline = separator < 0 ? undefined : token.value.slice(separator + 1);
411
+ const rule = rules.get(name);
412
+ if (!rule)
413
+ return 'unknown_option';
414
+ if (Object.hasOwn(options, rule.id) && rule.cardinality === 'one')
415
+ return 'duplicate_option';
416
+ if (rule.kind === 'boolean') {
417
+ if (inline !== undefined || !rule.syntax.includes('bare'))
418
+ return 'unexpected_option_value';
419
+ options[rule.id] = true;
420
+ continue;
421
+ }
422
+ if (inline !== undefined && !rule.syntax.includes('inline'))
423
+ return 'invalid_option_value';
424
+ if (inline === undefined && !rule.syntax.includes('split'))
425
+ return 'invalid_option_value';
426
+ const next = inline ?? tokens[index + 1]?.value;
427
+ if (next === undefined || (inline === undefined && tokens[index + 1]?.whollyUnquoted && next.startsWith('--')))
428
+ return 'missing_option_value';
429
+ if (inline === undefined)
430
+ index += 1;
431
+ if (rule.valuePattern && !new RegExp(rule.valuePattern, 'u').test(next))
432
+ return 'invalid_option_value';
433
+ if (rule.kind === 'url' && !validUrl(next))
434
+ return 'invalid_option_value';
435
+ if (rule.kind === 'path' && !validPath(next))
436
+ return 'invalid_option_value';
437
+ if (rule.kind === 'string-list') {
438
+ const list = next.split(',').map((item) => item.trim()).filter(Boolean);
439
+ if (!list.length || list.some((item) => item.length > 128))
440
+ return 'invalid_option_value';
441
+ options[rule.id] = freeze(list);
442
+ }
443
+ else {
444
+ if (!next.length || byteLength(next) > 4096)
445
+ return 'invalid_option_value';
446
+ if (rule.cardinality === 'many' && Object.hasOwn(options, rule.id)) {
447
+ const previous = options[rule.id];
448
+ options[rule.id] = freeze([...(Array.isArray(previous) ? previous : [String(previous)]), next]);
449
+ }
450
+ else
451
+ options[rule.id] = next;
452
+ }
453
+ continue;
454
+ }
455
+ positionals.push(token.value);
456
+ }
457
+ for (const rule of schema.options)
458
+ if (rule.required && !Object.hasOwn(options, rule.id))
459
+ return 'missing_option_value';
460
+ const positionalFailure = admitPositionals(positionals, schema.positionals);
461
+ if (positionalFailure)
462
+ return positionalFailure;
463
+ return freeze({ options, positionals });
464
+ }
465
+ function admitOperation(schema, tokens) {
466
+ if (!schema.operation.operations.length)
467
+ return { rest: tokens };
468
+ if (!tokens.length || (tokens[0].whollyUnquoted && tokens[0].value.startsWith('--'))) {
469
+ return schema.operation.required ? 'operation_required' : { rest: tokens };
470
+ }
471
+ const candidate = tokens[0];
472
+ const normalized = candidate.value.toLowerCase();
473
+ const operation = schema.operation.operations.find((entry) => entry.id === normalized || entry.aliases.includes(normalized));
474
+ if (operation && !candidate.whollyUnquoted)
475
+ return 'operation_quoted';
476
+ if (!operation) {
477
+ const resemblesOperation = schema.operation.operations.some((entry) => entry.id.startsWith(normalized) || normalized.startsWith(entry.id)
478
+ || entry.aliases.some((alias) => alias.startsWith(normalized) || normalized.startsWith(alias)));
479
+ if (resemblesOperation)
480
+ return candidate.whollyUnquoted ? 'operation_unknown' : 'operation_quoted';
481
+ if (!schema.operation.unknownAsPositional || schema.operation.required)
482
+ return candidate.whollyUnquoted ? 'operation_unknown' : 'operation_quoted';
483
+ return { rest: tokens }; // Schema explicitly classifies this slot as parent positional data.
484
+ }
485
+ return { operationId: operation.id, rest: tokens.slice(1) };
486
+ }
487
+ export function admitInvocation(input, options = {}) {
488
+ const bytes = byteLength(input);
489
+ if (options.policy !== undefined && options.policy !== EXACT_INVOCATION_POLICY)
490
+ return reject('malformed_input', 'policy_mismatch', 0, bytes);
491
+ if (options.registryVersion !== undefined && options.registryVersion !== COMMAND_REGISTRY_VERSION)
492
+ return reject('malformed_input', 'registry_mismatch', 0, bytes);
493
+ if (options.registryChecksum !== undefined && options.registryChecksum !== COMMAND_REGISTRY_CHECKSUM)
494
+ return reject('malformed_input', 'registry_mismatch', 0, bytes);
495
+ if (options.schemaChecksum !== undefined && options.schemaChecksum !== INVOCATION_SCHEMA_CHECKSUM)
496
+ return reject('malformed_input', 'schema_mismatch', 0, bytes);
497
+ const lexed = lexInvocationEnvelope(input);
498
+ if (!lexed.ok)
499
+ return reject('malformed_input', lexed.reason, lexed.tokenCount, bytes);
500
+ if (!lexed.tokens.length) {
501
+ const menu = byId.get('menu');
502
+ if (!menu)
503
+ return reject('unknown_command', 'identity_unknown', 0, bytes);
504
+ return resolved(menu, undefined, { options: {}, positionals: [] }, undefined, 0, bytes);
505
+ }
506
+ const command = resolveIdentity(lexed.tokens[0]);
507
+ if (!command) {
508
+ const candidates = suggestionsFor(lexed.tokens[0].value);
509
+ return reject(candidates.length ? 'ambiguous_command' : 'unknown_command', candidates.length ? 'suggestions_available' : 'identity_unknown', lexed.tokens.length, bytes, candidates);
510
+ }
511
+ const schema = schemaByCommand.get(command.id);
512
+ if (!schema)
513
+ return reject('malformed_input', 'schema_mismatch', lexed.tokens.length, bytes);
514
+ const operation = admitOperation(schema, lexed.tokens.slice(1));
515
+ if (typeof operation === 'string')
516
+ return reject('invalid_arguments', operation, lexed.tokens.length, bytes);
517
+ const parsed = admitArguments(schema, operation.rest);
518
+ if (typeof parsed === 'string')
519
+ return reject('invalid_arguments', parsed, lexed.tokens.length, bytes);
520
+ const uiUxReview = command.id === 'ui-ux-review' ? normalizeUiUx(parsed.positionals, parsed.options) : undefined;
521
+ if (typeof uiUxReview === 'string')
522
+ return reject('invalid_arguments', uiUxReview, lexed.tokens.length, bytes);
523
+ return resolved(command, operation.operationId, parsed, uiUxReview, lexed.tokens.length, bytes);
524
+ }
525
+ function resolved(command, operationId, parsed, uiUxReview, tokenCount, bytes) {
526
+ const selection = freeze({
527
+ commandId: command.id,
528
+ commandNumber: command.number,
529
+ ...(operationId ? { operationId } : {}),
530
+ inputShape: command.inputShape,
531
+ positionals: parsed.positionals,
532
+ options: parsed.options,
533
+ ...(uiUxReview ? { uiUxReview } : {}),
534
+ capability: {
535
+ policy: EXACT_INVOCATION_POLICY,
536
+ registryChecksum: COMMAND_REGISTRY_CHECKSUM,
537
+ commandId: command.id,
538
+ ...(operationId ? { operationId } : {}),
539
+ },
540
+ });
541
+ return freeze({ ...base('accepted', tokenCount, bytes), outcome: 'resolved', executable: true, candidates: [], selection });
542
+ }
543
+ export function createInvocationAdmissionReceipt(result, driver) {
544
+ const receipt = freeze({
545
+ schemaVersion: 1,
546
+ policy: EXACT_INVOCATION_POLICY,
547
+ registryVersion: result.registryVersion,
548
+ registryChecksumPrefix: result.registryChecksum.slice(0, 12),
549
+ schemaChecksumPrefix: result.schemaChecksum.slice(0, 12),
550
+ outcome: result.outcome,
551
+ reason: result.reason,
552
+ ...(result.outcome === 'resolved' ? {
553
+ commandId: result.selection.commandId,
554
+ ...(result.selection.operationId ? { operationId: result.selection.operationId } : {}),
555
+ ...(result.selection.uiUxReview ? { fingerprintPrefix: result.selection.uiUxReview.fingerprint.slice(0, 12) } : {}),
556
+ } : {}),
557
+ candidates: result.candidates.slice(0, INVOCATION_LIMITS.suggestions),
558
+ tokenCount: Math.min(result.tokenCount, INVOCATION_LIMITS.tokens + 1),
559
+ inputBytesBucket: result.inputBytesBucket,
560
+ driver,
561
+ mutationAttempts: result.outcome === 'resolved' ? 0 : result.mutationAttempts,
562
+ publicSafe: true,
563
+ });
564
+ if (byteLength(JSON.stringify(receipt)) > INVOCATION_LIMITS.receiptBytes)
565
+ throw new Error('INVOCATION_RECEIPT_BOUND_EXCEEDED');
566
+ return receipt;
567
+ }
568
+ export function serializeInvocationAdmission(result) {
569
+ return JSON.stringify(result);
570
+ }
571
+ export function strictRollbackAdmitInvocation(input) {
572
+ // Rollback may narrow optional normalized syntax but never bypasses the exact
573
+ // parser or its fail-before-effect decision.
574
+ return admitInvocation(input);
575
+ }
576
+ /** Count-only migration telemetry; raw input is intentionally not accepted. */
577
+ export function createInvocationDeprecationCount(result) {
578
+ return Object.freeze({
579
+ schemaVersion: 1,
580
+ policy: EXACT_INVOCATION_POLICY,
581
+ outcome: result.outcome,
582
+ reason: result.reason,
583
+ count: 1,
584
+ expiresAfter: 'p205-exact-invocation-v1-rollout',
585
+ publicSafe: true,
586
+ });
587
+ }