@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.
- package/dist/sbom.cdx.json +6504 -6410
- package/docs/install/installer-diagnostics.md +52 -0
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +27 -4
- package/packages/client/dist/agent/refresh-stubs.d.ts +7 -0
- package/packages/client/dist/agent/refresh-stubs.js +2 -2
- package/packages/client/dist/cache/encrypted-cache.d.ts +3 -0
- package/packages/client/dist/cache/encrypted-cache.js +1 -1
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -2
- package/packages/client/dist/cli.js +3 -3
- package/packages/client/dist/commands/activate.js +7 -7
- package/packages/client/dist/commands/config.js +1 -1
- package/packages/client/dist/commands/invoke.d.ts +64 -0
- package/packages/client/dist/commands/invoke.js +54 -47
- package/packages/client/dist/config/secure-config.d.ts +9 -3
- package/packages/client/dist/config/secure-config.js +8 -8
- package/packages/client/dist/config.d.ts +1 -1
- package/packages/client/dist/config.js +1 -1
- package/packages/client/dist/continuity/continuity-client-state-store.d.ts +1 -0
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1
- package/packages/client/dist/continuity/invoke-hooks.d.ts +2 -1
- package/packages/client/dist/continuity/invoke-hooks.js +1 -1
- package/packages/client/dist/continuity/sqlite-store.d.ts +21 -0
- package/packages/client/dist/continuity/sqlite-store.js +49 -49
- package/packages/client/dist/effects/invoke-effect-gateways.d.ts +22 -0
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -0
- package/packages/client/dist/errors/frontmatter-diagnostics.d.ts +20 -0
- package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -0
- package/packages/client/dist/generated/command-registry.generated.d.ts +2836 -0
- package/packages/client/dist/generated/command-registry.generated.js +1 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.d.ts +2 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -1
- package/packages/client/dist/index.d.ts +1 -0
- package/packages/client/dist/index.js +1 -1
- package/packages/client/dist/license/license-client.d.ts +3 -0
- package/packages/client/dist/license/license-client.js +1 -1
- package/packages/client/dist/machine/fingerprint.d.ts +3 -1
- package/packages/client/dist/machine/fingerprint.js +2 -2
- package/packages/client/dist/memory/project-memory-writer.js +19 -15
- package/packages/client/dist/{subagent-approval-policy.d.ts → policy/subagent-approval-policy.d.ts} +8 -3
- package/packages/client/dist/policy/subagent-approval-policy.js +1 -0
- package/packages/client/dist/resolvers/local-resolver.d.ts +3 -0
- package/packages/client/dist/resolvers/local-resolver.js +4 -4
- package/packages/client/dist/runner/cli.d.ts +1 -0
- package/packages/client/dist/runner/cli.js +1 -1
- package/packages/client/dist/runner/headless/prepared-story-bundle.d.ts +20 -0
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -0
- package/packages/client/dist/runner/headless/run-yoloop.d.ts +2 -0
- package/packages/client/dist/runner/headless/run-yoloop.js +1 -1
- package/packages/client/dist/runner/portal-sync-worker.js +1 -1
- package/packages/client/dist/runner/scheduler.d.ts +3 -0
- package/packages/client/dist/runner/scheduler.js +7 -7
- package/packages/client/dist/state/project-state-snapshot.js +1 -1
- package/packages/client/dist/tier/tier-aware-client.js +1 -1
- package/packages/client/dist/version.d.ts +3 -0
- package/packages/client/dist/version.js +1 -0
- package/packages/client/dist/yoloop/discovery-hook.js +2 -2
- package/packages/client/dist/yoloop/invoke-hooks.js +4 -4
- package/packages/client/dist/yoloop/shared-discover-epics.d.ts +9 -3
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1
- package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +24 -0
- package/packages/client/dist/yoloop/target-normalization.d.ts +24 -0
- package/packages/client/dist/yoloop/target-normalization.js +1 -0
- package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +4 -0
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1
- package/packages/shared/dist/command-registry-contract.js +382 -0
- package/packages/shared/dist/generated/command-registry.generated.js +4216 -0
- package/packages/shared/dist/generated/invocation-schema.generated.js +1850 -0
- package/packages/shared/dist/invocation/index.d.ts +175 -0
- package/packages/shared/dist/invocation/index.js +587 -0
- package/packages/shared/dist/nx-invoke-signature.js +233 -0
- package/packages/shared/dist/strict-readonly.js +381 -0
- package/packages/shared/dist/yoloop/wave-decision-receipt.js +77 -0
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +5 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +7 -2
- package/targets-stubs/codex/AGENTS.md +4 -1
- package/targets-stubs/codex/neocortex.toml +4 -0
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +4 -1
- package/targets-stubs/opencode/neocortex.md +3 -0
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
- 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
|
+
}
|