@entrorove/skill 0.1.32 → 0.1.34

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.
@@ -14,6 +14,7 @@ import { array, binding, change, confirmation, enumeration, evidence, evidenceAr
14
14
  const schemas = {};
15
15
  const base = { repositoryRoot: text, home: text };
16
16
  const flow = { ...base, flowId: identifier };
17
+ const fixedRoundingPolicy = object({ decimalPlaces: { const: 2 }, mode: { const: 'half-away-from-zero' } }, ['decimalPlaces', 'mode']);
17
18
  const step = { ...flow, stepId: identifier };
18
19
  function add(names, properties, required = [], alternatives) {
19
20
  const schema = object(properties, required);
@@ -54,8 +55,8 @@ add('rollback', { ...step, snapshotId: identifier }, ['repositoryRoot', 'flowId'
54
55
  const check = { anyOf: [{ type: 'boolean' }, { type: 'object', properties: { status: text }, required: ['status'] }] };
55
56
  add('final-gate', { ...base, ...binding, audit: check, test: check, operations: check, evidence: array(evidence), preflight: record }, ['audit', 'test', 'operations', 'evidence']);
56
57
  schemas['final-gate'] = { oneOf: [schemas['final-gate'], FINAL_GATE16_INPUT_SCHEMA, FINAL_GATE16_READ_INPUT_SCHEMA] };
57
- add('formula-evaluate', { formula, values: decimalValues }, ['formula']);
58
- add('formula-graph', { formulas: array(formulaEntry), inputs: decimalValues }, ['formulas']);
58
+ add('formula-evaluate', { formula, values: decimalValues, roundingPolicy: fixedRoundingPolicy }, ['formula']);
59
+ add('formula-graph', { formulas: array(formulaEntry), inputs: decimalValues, roundingPolicy: fixedRoundingPolicy }, ['formulas']);
59
60
  add('formula-final-gate', { engine: record, flowId: identifier, stepId: identifier }, ['engine']);
60
61
  add('calculation-final-gate', { ...step, enginePath: relativePath }, ['repositoryRoot', 'enginePath']);
61
62
  add('run-approved-plan', { repositoryRoot: text, planPath: relativePath, approvalPath: text, signerConfigPath: nullable(text) }, ['repositoryRoot', 'planPath', 'approvalPath', 'signerConfigPath']);
@@ -114,9 +115,37 @@ Object.assign(schemas, ARCHGUARD_LOCAL_OPERATION_SCHEMAS, CALCTOOL_LOCAL_OPERATI
114
115
  export const LOCAL_OPERATION_SCHEMAS = Object.freeze(schemas);
115
116
  const ajv = new Ajv({ strict: true, strictRequired: false, allErrors: true });
116
117
  const validators = new Map(Object.entries(schemas).map(([operation, schema]) => [operation, ajv.compile(schema)]));
118
+ const FORMULA_ARITY = { add: [1, Infinity], mul: [1, Infinity], sub: [2, 2], div: [2, 2], safeDivide: [2, 2], percentOf: [2, 2], round: [1, 1], if: [3, 3] };
119
+ function formulaArityError(node, path) {
120
+ if (!node || typeof node !== 'object' || Array.isArray(node) || typeof node.op !== 'string') return undefined;
121
+ const bounds = FORMULA_ARITY[node.op];
122
+ if (!bounds) return undefined;
123
+ const count = Array.isArray(node.args) ? node.args.length : -1;
124
+ if (count < bounds[0] || count > bounds[1]) {
125
+ const expected = bounds[0] === bounds[1] ? `exactly ${bounds[0]}` : `at least ${bounds[0]}`;
126
+ return `${path}.args: operator "${node.op}" requires ${expected} arguments; received ${count < 0 ? 'non-array args' : count}`;
127
+ }
128
+ for (const [index, child] of node.args.entries()) {
129
+ const failure = formulaArityError(child, `${path}.args[${index}]`);
130
+ if (failure) return failure;
131
+ }
132
+ return undefined;
133
+ }
134
+ function formulaInputHint(operation, input) {
135
+ if (operation === 'formula-evaluate') return formulaArityError(input?.formula, 'formula');
136
+ if (operation === 'formula-graph' && Array.isArray(input?.formulas)) {
137
+ for (const [index, entry] of input.formulas.entries()) {
138
+ const failure = formulaArityError(entry?.expression, `formulas[${index}].expression`);
139
+ if (failure) return failure;
140
+ }
141
+ }
142
+ return undefined;
143
+ }
117
144
  export function validateLocalInput(operation, input) {
118
145
  const validate = validators.get(operation);
119
146
  if (!validate) throw Object.assign(new Error(`Unsupported local operation: ${operation}`), { code: 'ENTROROVE_OPERATION_UNKNOWN' });
147
+ const hint = formulaInputHint(operation, input);
148
+ if (hint) throw Object.assign(new Error(`Invalid ${operation} input: ${hint}`), { code: 'ENTROROVE_INPUT_INVALID' });
120
149
  if (!validate(input)) {
121
150
  const details = validate.errors.map(error => `${error.instancePath || '/'} ${error.message}`).join('; ');
122
151
  throw Object.assign(new Error(`Invalid ${operation} input: ${details}`), { code: 'ENTROROVE_INPUT_INVALID' });
package/local-error.mjs CHANGED
@@ -1,4 +1,14 @@
1
1
  export const LOCAL_ERROR_SCHEMA = 'entrorove.local-error/1.0';
2
+ export const LOCAL_ERROR_CODE_CATALOG = Object.freeze({
3
+ ENTROROVE_INPUT_INVALID: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
4
+ ENTROROVE_OPERATION_UNKNOWN: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
5
+ ENTROROVE_LOCAL_FAILURE: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
6
+ DIV_ZERO: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
7
+ ENTROROVE_MUTATE_NOT_ACCEPTED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
8
+ ENTROROVE_MUTATION_AUTHORITY_REQUIRED: Object.freeze({ disposition: 'needs-human', stability: 'stable' }),
9
+ ENTROROVE_BUDGET_LEASE_EXPIRED: Object.freeze({ disposition: 'retriable', stability: 'stable' }),
10
+ ENTROROVE_GIT_READER_UNSUPPORTED: Object.freeze({ disposition: 'terminal', stability: 'stable' }),
11
+ });
2
12
 
3
13
  const BLOCKED_CODES = new Set([
4
14
  'ENTROROVE_MUTATE_NOT_ACCEPTED', 'ENTROROVE_MUTATION_AUTHORITY_REQUIRED',
@@ -16,8 +26,16 @@ export function localErrorStatus(error) {
16
26
  ? 'blocked' : 'failed';
17
27
  }
18
28
 
29
+ export function localErrorDisposition(code) {
30
+ const catalogued = LOCAL_ERROR_CODE_CATALOG[code];
31
+ if (catalogued) return catalogued.disposition;
32
+ if (/_LEASE_EXPIRED$|_BUSY$|_RECONNECT/.test(code)) return 'retriable';
33
+ if (BLOCKED_CODES.has(code) || /(?:AUTHORITY_REQUIRED|NOT_ACCEPTED|REVOKED)$/.test(code)) return 'needs-human';
34
+ return 'terminal';
35
+ }
36
+
19
37
  export function localErrorEnvelope(error, fallbackCode = 'ENTROROVE_LOCAL_FAILURE') {
20
38
  const code = typeof error?.code === 'string' && error.code ? error.code : fallbackCode;
21
39
  const message = typeof error?.message === 'string' && error.message ? error.message : 'Local operation failed';
22
- return { schemaVersion: LOCAL_ERROR_SCHEMA, status: localErrorStatus({ ...error, code }), code, message };
40
+ return { schemaVersion: LOCAL_ERROR_SCHEMA, status: localErrorStatus({ ...error, code }), code, disposition: localErrorDisposition(code), message };
23
41
  }
@@ -275,7 +275,29 @@ export async function finalGate(input) {
275
275
  guarantee: 'Original engine, frozen role plans and actual three-party checks are required' };
276
276
  }
277
277
 
278
- export const LOCAL_CAPABILITIES = Object.freeze({ schemaVersion: LOCAL_SCHEMA, operations: Object.freeze(Object.keys(LOCAL_OPERATION_SCHEMAS)), operationSchemas: LOCAL_OPERATION_SCHEMAS, stateDirectory: '~/.entrorove', forbidGitBranch: true, forbidWorktree: true, evidenceSchema: EVIDENCE_SCHEMA, budgets: READ_BUDGETS, validatorRunner: RUNNER_CAPABILITIES, workflowRunner: WORKFLOW_LOCAL_CAPABILITIES });
278
+ const LOCAL_OPERATION_GROUPS = Object.freeze(['core', 'budget', 'scope', 'merge', 'chain', 'handoff', 'archguard', 'calctool', 'swarm', 'validator', 'formula', 'evidence']);
279
+ const PASS_REQUIRED_OPERATIONS = new Set(['mutation-snapshot-attest', 'mutate-gate', 'gate-issue', 'gate-verify', 'guarded-write',
280
+ 'merge-verified', 'rollback', 'archguard-contract-create', 'archguard-contract-update', 'archguard-checkpoint', 'archguard-scope-request',
281
+ 'budget-handoff', 'budget-transition', 'spawn-bind', 'peer-complete', 'complete']);
282
+ function operationGroup(operation) {
283
+ if (operation.startsWith('archguard-')) return 'archguard';
284
+ if (operation.startsWith('calctool-')) return 'calctool';
285
+ if (operation.includes('handoff')) return 'handoff';
286
+ if (operation.startsWith('budget-')) return 'budget';
287
+ if (operation.startsWith('chain-')) return 'chain';
288
+ if (operation.startsWith('formula-')) return 'formula';
289
+ if (operation.startsWith('merge-') || ['branch-create', 'branch-create-virtual', 'branch-list', 'branch-switch', 'diff-report', 'preflight', 'ledger-query'].includes(operation)) return 'merge';
290
+ if (operation.startsWith('peer-') || ['coordinate', 'spawn-bind', 'complete', 'lease-verify'].includes(operation)) return 'swarm';
291
+ if (operation.startsWith('evidence-')) return 'evidence';
292
+ if (operation.startsWith('probe') || operation === 'reassess' || operation.startsWith('snapshot') || operation.includes('scope')) return 'scope';
293
+ if (operation === 'final-gate' || operation.includes('verify') || operation.includes('validate')) return 'validator';
294
+ return 'core';
295
+ }
296
+ const LOCAL_OPERATION_METADATA = Object.freeze(Object.fromEntries(Object.keys(LOCAL_OPERATION_SCHEMAS).map(operation => [operation, Object.freeze({
297
+ group: operationGroup(operation), requiresPass: PASS_REQUIRED_OPERATIONS.has(operation),
298
+ })])));
299
+ export const LOCAL_CAPABILITIES = Object.freeze({ schemaVersion: LOCAL_SCHEMA, operations: Object.freeze(Object.keys(LOCAL_OPERATION_SCHEMAS)), operationSchemas: LOCAL_OPERATION_SCHEMAS,
300
+ operationMetadata: LOCAL_OPERATION_METADATA, groups: LOCAL_OPERATION_GROUPS, stateDirectory: '~/.entrorove', forbidGitBranch: true, forbidWorktree: true, evidenceSchema: EVIDENCE_SCHEMA, budgets: READ_BUDGETS, validatorRunner: RUNNER_CAPABILITIES, workflowRunner: WORKFLOW_LOCAL_CAPABILITIES });
279
301
  export function capabilities() { return LOCAL_CAPABILITIES; }
280
302
  export const guardedWriteFile = guardedWrite;
281
303
  export const verifyMutationPassFile = verifyMutationPass;
@@ -0,0 +1,192 @@
1
+ import { createHash } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import ts from 'typescript';
4
+ const missing = (error) => ['ENOENT', 'ENOTDIR'].includes(error?.code ?? '');
5
+ function literal(value, field) {
6
+ if (typeof value !== 'string' || !value || value !== value.trim() || value !== value.normalize('NFC')
7
+ || /[\\:\u0000-\u001f\u007f]/.test(value) || value.startsWith('/') || value.startsWith('~')) {
8
+ throw new Error(`Scope tsconfig ${field} requires a supported repository-relative literal path`);
9
+ }
10
+ return value;
11
+ }
12
+ function within(directory, value, allowRoot = false) {
13
+ const resolved = path.posix.normalize(path.posix.join(directory, literal(value, 'path'))).replace(/\/+$/, '');
14
+ if (resolved === '..' || resolved.startsWith('../') || resolved.startsWith('/')
15
+ || resolved.split('/').some(part => ['node_modules', '.git', '.entrorove', '.aimlock', '.archguard'].includes(part))) {
16
+ throw new Error('Scope tsconfig path escapes the authorized repository');
17
+ }
18
+ if (resolved === '.') {
19
+ if (!allowRoot)
20
+ throw new Error('Scope tsconfig mapping requires a literal candidate within the repository');
21
+ return '';
22
+ }
23
+ return resolved;
24
+ }
25
+ function pathsOption(value) {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value))
27
+ throw new Error('Scope tsconfig paths requires an object of literal mappings');
28
+ const result = Object.create(null);
29
+ const entries = Object.entries(value);
30
+ if (entries.length > 512)
31
+ throw new Error('Scope tsconfig paths exceeds 512 mappings');
32
+ for (const [key, targets] of entries) {
33
+ if (!key || key !== key.trim() || key !== key.normalize('NFC') || /[\\:\u0000-\u001f\u007f]/.test(key)
34
+ || key.startsWith('/') || key.split('/').some(part => part === '.' || part === '..') || key.split('*').length > 2) {
35
+ throw new Error('Scope tsconfig paths supports only exact or single-wildcard mappings');
36
+ }
37
+ if (!Array.isArray(targets) || !targets.length || targets.length > 128 || targets.some(target => typeof target !== 'string')) {
38
+ throw new Error('Scope tsconfig paths targets must be a nonempty bounded string array');
39
+ }
40
+ result[key] = targets.map(target => {
41
+ literal(target, 'mapping');
42
+ if (target.split('*').length > 2 || (target.includes('*') && !key.includes('*'))) {
43
+ throw new Error('Scope tsconfig paths target has an unsupported wildcard mapping');
44
+ }
45
+ return target;
46
+ });
47
+ }
48
+ return result;
49
+ }
50
+ function parsedConfig(relative, source) {
51
+ const parsed = ts.parseConfigFileTextToJson(relative, source);
52
+ if (parsed.error)
53
+ throw new Error(`Scope tsconfig JSONC syntax is invalid (diagnostic ${parsed.error.code})`);
54
+ if (!parsed.config || typeof parsed.config !== 'object' || Array.isArray(parsed.config))
55
+ throw new Error('Scope tsconfig must contain a configuration object');
56
+ return parsed.config;
57
+ }
58
+ function pathMatch(paths, specifier) {
59
+ if (!paths)
60
+ return null;
61
+ if (Object.hasOwn(paths, specifier))
62
+ return { targets: paths[specifier], wildcard: '' };
63
+ let selected = null;
64
+ let prefixLength = -1;
65
+ for (const [key, targets] of Object.entries(paths)) {
66
+ const star = key.indexOf('*');
67
+ if (star < 0)
68
+ continue;
69
+ const prefix = key.slice(0, star);
70
+ const suffix = key.slice(star + 1);
71
+ if (prefix.length > prefixLength && specifier.startsWith(prefix) && specifier.endsWith(suffix)
72
+ && specifier.length >= prefix.length + suffix.length) {
73
+ selected = { targets, wildcard: specifier.slice(prefix.length, specifier.length - suffix.length) };
74
+ prefixLength = prefix.length;
75
+ }
76
+ }
77
+ return selected;
78
+ }
79
+ function importCandidates(resolution, specifier) {
80
+ if (typeof specifier !== 'string' || !specifier)
81
+ throw new Error('Scope tsconfig import specifier must be nonempty');
82
+ if (specifier.startsWith('node:') || specifier.startsWith('./') || specifier.startsWith('../'))
83
+ return [];
84
+ if (specifier !== specifier.trim() || /[\\:\u0000-\u001f\u007f*]/.test(specifier)
85
+ || specifier.startsWith('/') || specifier.split('/').some(part => part === '.' || part === '..')) {
86
+ throw new Error('Scope tsconfig import alias or candidate uses an unsupported path');
87
+ }
88
+ const match = pathMatch(resolution?.paths, specifier);
89
+ if (match) {
90
+ const base = resolution.baseUrl ?? resolution.pathsOrigin;
91
+ return [...new Set(match.targets.map(target => within(base, target.replace('*', match.wildcard))))];
92
+ }
93
+ if (specifier.startsWith('@/') || specifier.startsWith('#') || specifier.startsWith('~/')) {
94
+ throw new Error('Scope local import alias requires an actual tsconfig or package-import mapping');
95
+ }
96
+ return resolution?.baseUrl !== undefined ? [within(resolution.baseUrl, specifier)] : [];
97
+ }
98
+ export async function scopeTsconfigImports(read, fromFile, configPath) {
99
+ const canonical = within('', fromFile);
100
+ if (canonical !== fromFile)
101
+ throw new Error('Scope tsconfig source path must be canonical within the repository');
102
+ const observed = new Map();
103
+ const absent = new Set();
104
+ let bytes = 0;
105
+ const metadata = async (relative) => {
106
+ if (absent.has(relative))
107
+ return null;
108
+ if (observed.has(relative))
109
+ return Buffer.from(observed.get(relative).contentBase64, 'base64').toString('utf8');
110
+ let item;
111
+ try {
112
+ item = await read(relative);
113
+ }
114
+ catch (error) {
115
+ if (missing(error)) {
116
+ absent.add(relative);
117
+ return null;
118
+ }
119
+ ;
120
+ throw error;
121
+ }
122
+ const data = Buffer.from(item.contentBase64, 'base64');
123
+ if (item.path !== relative || data.toString('base64') !== item.contentBase64
124
+ || createHash('sha256').update(data).digest('hex') !== item.sha256 || !Buffer.from(data.toString('utf8')).equals(data)) {
125
+ throw new Error('Scope tsconfig metadata lost its actual path, UTF-8 content or hash');
126
+ }
127
+ bytes += data.length;
128
+ if (data.length > 1024 * 1024 || bytes > 16 * 1024 * 1024)
129
+ throw new Error('Scope tsconfig metadata exceeds its bounded read size');
130
+ observed.set(relative, item);
131
+ return data.toString('utf8');
132
+ };
133
+ const load = async (relative, ancestors, depth = 0) => {
134
+ if (depth >= 10)
135
+ throw new Error('Scope tsconfig extends exceeds the 10-configuration depth bound');
136
+ let source = await metadata(relative);
137
+ if (source === null && !relative.endsWith('.json')) {
138
+ relative += '.json';
139
+ source = await metadata(relative);
140
+ }
141
+ if (source === null)
142
+ throw new Error('Scope tsconfig extends references a missing actual configuration');
143
+ if (ancestors.has(relative))
144
+ throw new Error('Scope tsconfig extends contains a configuration cycle');
145
+ const config = parsedConfig(relative, source);
146
+ const directory = path.posix.dirname(relative) === '.' ? '' : path.posix.dirname(relative);
147
+ let inherited = { pathsOrigin: directory };
148
+ if (config.extends !== undefined) {
149
+ const extended = literal(config.extends, 'extends');
150
+ if (!extended.startsWith('./') && !extended.startsWith('../'))
151
+ throw new Error('Scope tsconfig npm extends is unsupported; use an actual repository-relative extends');
152
+ inherited = await load(within(directory, extended), new Set([...ancestors, relative]), depth + 1);
153
+ }
154
+ const options = config.compilerOptions === undefined ? {} : config.compilerOptions;
155
+ if (!options || typeof options !== 'object' || Array.isArray(options))
156
+ throw new Error('Scope tsconfig compilerOptions must be an object');
157
+ const own = options;
158
+ return { ...inherited,
159
+ ...(own.baseUrl === undefined ? {} : { baseUrl: within(directory, literal(own.baseUrl, 'baseUrl'), true) }),
160
+ ...(own.paths === undefined ? {} : { paths: pathsOption(own.paths), pathsOrigin: directory }) };
161
+ };
162
+ let directory = path.posix.dirname(canonical);
163
+ let resolution = null;
164
+ if (configPath !== undefined) {
165
+ if (within('', configPath) !== configPath || !/(?:^|\/)(?:ts|js)config(?:\.[^/]+)?\.json$/.test(configPath))
166
+ throw new Error('Scope import context requires an actual repository-relative tsconfig or jsconfig');
167
+ resolution = await load(configPath, new Set());
168
+ }
169
+ while (configPath === undefined) {
170
+ const prefix = directory === '.' ? '' : `${directory}/`;
171
+ const relative = await metadata(`${prefix}tsconfig.json`) !== null ? `${prefix}tsconfig.json`
172
+ : await metadata(`${prefix}jsconfig.json`) !== null ? `${prefix}jsconfig.json` : null;
173
+ if (relative !== null) {
174
+ resolution = await load(relative, new Set());
175
+ break;
176
+ }
177
+ if (directory === '.')
178
+ break;
179
+ directory = path.posix.dirname(directory);
180
+ }
181
+ if (resolution?.paths)
182
+ for (const targets of Object.values(resolution.paths))
183
+ for (const target of targets) {
184
+ within(resolution.baseUrl ?? resolution.pathsOrigin, target.replace('*', '__scope_wildcard__'));
185
+ }
186
+ return { candidates: specifier => importCandidates(resolution, specifier), mapped: specifier => {
187
+ if (typeof specifier !== 'string' || !specifier)
188
+ throw new Error('Scope tsconfig mapping lookup requires a nonempty specifier');
189
+ return pathMatch(resolution?.paths, specifier) !== null;
190
+ }, observations: [...observed.keys()].sort()
191
+ .map(relative => ({ path: relative, sha256: observed.get(relative).sha256 })) };
192
+ }
@@ -11,6 +11,7 @@ import { workspaceImport, workspaceIndex } from './local-scope-workspaces.mjs';
11
11
  import { assertBudgetRunnerAllowed } from './local-budget-reads.mjs';
12
12
  import { observeBudgetScopeGit, scopeGitBudgetEnforced } from './local-scope-git.mjs';
13
13
  import { scopeGitMetadata } from './local-scope-git-reads.mjs';
14
+ import { scopeTsconfigImports } from './local-scope-config-imports.mjs';
14
15
 
15
16
  const execute = promisify(execFile);
16
17
  async function git(root, args, allowed = [], input = { repositoryRoot: root }) {
@@ -179,48 +180,83 @@ function importSpecifiers(content, filename) {
179
180
  }
180
181
  }
181
182
  const sourceExtension = /\.(?:[cm]?[jt]sx?|vue)$/i;
182
- async function resolveImport(root, from, specifier, workspace, input) {
183
+ function declarationModules(content, from) {
184
+ if (!/\.d\.[cm]?ts$/i.test(from)) return new Set();
185
+ const parsed = ts.createSourceFile(from, content.toString('utf8'), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
186
+ return new Set(parsed.statements.filter(node => ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)
187
+ && node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DeclareKeyword)).map(node => node.name.text));
188
+ }
189
+ async function resolveImport(root, from, specifier, workspace, input, aliases, configPath, declarations) {
183
190
  if (/^(?:https?:|file:|data:)/i.test(specifier)) scopeFail('ENTROROVE_IMPORT_UNSUPPORTED', 'remote source imports cannot be inspected as local facts');
191
+ if (declarations.has(specifier)) return null;
192
+ const label = /^[A-Za-z0-9@._/~+-]{1,256}$/.test(specifier) ? specifier : '[unsupported specifier]';
193
+ let candidates;
194
+ try { candidates = aliases.candidates(specifier); }
195
+ catch (error) { scopeFail('ENTROROVE_IMPORT_UNRESOLVED', `${from}: ${error.message}; specify its actual build config with importContexts`); }
196
+ for (const candidate of candidates) {
197
+ const resolved = await resolveLocalImport(root, candidate, false);
198
+ if (resolved) return resolved;
199
+ }
200
+ if (aliases.mapped(specifier)) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', `${from}: ${label} has no actual resolvable source file in its build config`);
184
201
  const localPackage = !specifier.startsWith('.') ? workspaceImport(workspace, specifier) : null;
185
202
  if (!specifier.startsWith('.') && !localPackage) {
186
203
  if (builtinModules.includes(specifier) || (specifier.startsWith('node:') && builtinModules.includes(specifier.slice(5)))) return null;
187
- const boundary = await packageBoundary(root, from, input);
204
+ const boundary = await packageBoundary(root, configPath ?? from, input);
188
205
  if (boundary.manifestPath) {
189
206
  const manifest = JSON.parse((await scopeRead(root, boundary.manifestPath, input)).content.toString('utf8'));
190
207
  const name = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0];
191
208
  if (['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].some(field => Object.hasOwn(manifest[field] ?? {}, name))) return null;
192
209
  }
193
- scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'bare imports require a builtin, declared dependency, or actual workspace export; unresolved aliases cannot be assumed external');
210
+ scopeFail('ENTROROVE_IMPORT_UNRESOLVED', `${from}: ${label} requires a builtin, declared dependency, or actual workspace export`);
194
211
  }
195
212
  const relative = localPackage ?? path.posix.normalize(path.posix.join(path.posix.dirname(from), specifier));
213
+ return resolveLocalImport(root, relative);
214
+ }
215
+ async function resolveLocalImport(root, relative, required = true) {
196
216
  if (relative.startsWith('../') || relative === '..') scopeFail('ENTROROVE_PATH_UNSAFE', 'local import escapes the repository');
197
217
  const replacements = /\.js$/.test(relative) ? [relative.replace(/\.js$/, '.ts'), relative.replace(/\.js$/, '.tsx')] : [];
198
- for (const candidate of [relative, ...replacements, ...['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '/index.js', '/index.ts'].map(ext => relative + ext)]) {
218
+ for (const candidate of [relative, ...replacements, ...['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue', '.json', '.d.ts', '.d.mts', '.d.cts', '/index.js', '/index.ts', '/index.d.ts'].map(ext => relative + ext)]) {
199
219
  const item = await scopeTarget(root, candidate, true);
200
220
  if (item.exists && item.stat.isFile()) return candidate;
201
221
  }
202
- scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
222
+ if (required) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
223
+ return null;
203
224
  }
204
225
  export async function importGraph(root, originals, input = { repositoryRoot: root }) {
205
226
  const pending = originals.filter(file => file.exists !== false).map(file => file.path); const visited = new Map(); const edges = []; let dynamic = false;
227
+ const dynamicSources = [];
206
228
  const workspace = await workspaceIndex(root, input);
229
+ const metadata = new Map(workspace.metadata.map(item => [item.path, item]));
207
230
  while (pending.length) {
208
231
  const relative = pending.shift(); if (visited.has(relative)) continue;
209
232
  if (visited.size >= 1000) scopeFail('ENTROROVE_SCOPE_LIMIT', 'import graph exceeds the 1000-file inspection bound');
210
233
  const read = await scopeRead(root, relative, input); const boundary = await packageBoundary(root, relative, input);
211
234
  visited.set(relative, { path: relative, sha256: read.sha256, module: boundary.path, boundary });
212
235
  if (!sourceExtension.test(relative)) continue;
236
+ const contexts = (input.importContexts ?? []).filter(context => relative === context.sourcePrefix || relative.startsWith(`${context.sourcePrefix}/`))
237
+ .sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length);
238
+ const configPath = contexts[0]?.configPath;
239
+ const aliases = await scopeTsconfigImports(async target => {
240
+ const item = await scopeRead(root, target, input);
241
+ return { path: target, sha256: item.sha256, contentBase64: item.content.toString('base64') };
242
+ }, relative, configPath);
243
+ for (const observed of aliases.observations) metadata.set(observed.path, observed);
244
+ const declarations = declarationModules(read.content, relative);
213
245
  const imports = importSpecifiers(read.content, relative); dynamic ||= imports.dynamic;
246
+ if (imports.dynamic) dynamicSources.push({ path: relative, sha256: read.sha256 });
214
247
  for (const specifier of imports.specifiers.sort()) {
215
- const target = await resolveImport(root, relative, specifier, workspace, input);
216
- edges.push({ from: relative, to: target, external: target === null, specifier });
248
+ const target = await resolveImport(root, relative, specifier, workspace, input, aliases, configPath, declarations);
249
+ edges.push({ from: relative, to: target, external: target === null, specifier,
250
+ ...(declarations.has(specifier) ? { declarationOnly: true, declarationPath: relative } : {}) });
217
251
  if (target !== null && !visited.has(target)) pending.push(target);
218
252
  }
219
253
  }
220
254
  const nodes = [...visited.values()].sort((a, b) => a.path.localeCompare(b.path));
221
255
  await stableScope(root, nodes, input);
222
- await stableScope(root, workspace.metadata, input);
223
- return { nodes, edges: edges.sort((a, b) => `${a.from}:${a.specifier}`.localeCompare(`${b.from}:${b.specifier}`)), dynamic, complete: !dynamic, workspaceMetadata: workspace.metadata };
256
+ const workspaceMetadata = [...metadata.values()].sort((a, b) => a.path.localeCompare(b.path));
257
+ await stableScope(root, workspaceMetadata, input);
258
+ return { nodes, edges: edges.sort((a, b) => `${a.from}:${a.specifier}`.localeCompare(`${b.from}:${b.specifier}`)),
259
+ dynamic, complete: !dynamic, dynamicSources: dynamicSources.sort((a, b) => a.path.localeCompare(b.path)), workspaceMetadata };
224
260
  }
225
261
  export async function collectScopeFacts(root, requested, baselineRef, knownFiles = [], input = { repositoryRoot: root }) {
226
262
  const selected = await scopeFiles(root, requested, knownFiles); const files = [];
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.skill-manifest/1.0",
3
3
  "name": "entrorove",
4
- "version": "0.1.32",
4
+ "version": "0.1.34",
5
5
  "endpoint": "https://entrorove.com",
6
6
  "requestSchemaVersion": "entrorove.skill.request/2.0",
7
7
  "supportedRequestSchemaVersions": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entrorove/skill",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -76,7 +76,12 @@
76
76
  "local-task-handoff-write-archive.mjs",
77
77
  "local-task-handoff-write-receipts.mjs",
78
78
  "remote-client.mjs",
79
+ "scope-runner-client.mjs",
79
80
  "cloud-task-client.mjs",
81
+ "cloud-watch-client.mjs",
82
+ "cloud-event-stream.mjs",
83
+ "ide-presentation.mjs",
84
+ "automatic-feedback-client.mjs",
80
85
  "remote-transport.mjs",
81
86
  "local-credential.mjs",
82
87
  "local-invocations.mjs",
@@ -122,6 +127,7 @@
122
127
  "local-scope-files.mjs",
123
128
  "local-scope-repository.mjs",
124
129
  "local-scope-signature.mjs",
130
+ "local-scope-config-imports.mjs",
125
131
  "local-scope-snapshot.mjs",
126
132
  "local-scope-state.mjs",
127
133
  "local-scope-workspaces.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.32",
4
- "digest": "615bc63383071e3a1356d129b2c072e1641744f6b82def3ec0c2f56fa68a39a4",
3
+ "version": "0.1.34",
4
+ "digest": "88b67ef8c4be3ddf86777218a7324b095e37b2e6df30a002ac267a48548e2a31",
5
5
  "files": {
6
6
  "local-final-gate16.mjs": "b30caab08441449ad556a6e9a3cefbf91190f0b3f7370f46d2a21156375aa8e3",
7
7
  "local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
@@ -44,23 +44,23 @@
44
44
  "local-autocoord17-waits.mjs": "2b0a713987822548a94e1ed3a9a891532db45383797dfc5d5bc8d1e35593cf25",
45
45
  "local-validator-isolation17.mjs": "48e94604e28b8fddd567dfb35ff60b73fd094a5803bd49f076388b7f310f92aa",
46
46
  "local-calctool-composition17.mjs": "ac89fcb41ab0d0d9b47c1b5696945281b0894eb064820775e36a011c76314e6a",
47
- "cli.mjs": "6d455d8e22fccd24332726de6821a8a99f628d057648c9aae7c6262d89745722",
47
+ "cli.mjs": "b890c2173195edc9ccbaa1f9d1b45a845cad0f1838c300d90a77b1f776c8f178",
48
48
  "review-client.mjs": "3491eef68ed533174b31db3c53febb6519b8f088f71a9964644714a9a4c03c77",
49
49
  "version.mjs": "1f392147d72c40af4b03b307ea607075fb815af5174fa07e254b95fe12dbf3e7",
50
- "SKILL.md": "20c09094ffd094fa59f73a26b8cbb987cf40a8247368bb5b6417695831ed2a70",
51
- "CAPABILITY-MAP.md": "128ecc8d90b2c17846ce6f0c650eada9e7fa7240f5d3c03b31e3ee01135557b8",
50
+ "SKILL.md": "11469bd5f51e008c26492d2e01401959451c259e04699dd4b8c833701c27d0b2",
51
+ "CAPABILITY-MAP.md": "f1dd06d99f191daa27f7b1520a3d6d26b1ceba139d30cd287d2b9bfd79459923",
52
52
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
53
- "package.json": "77e29f7740cc2a2284490a992a9945fbb4a3ad3ac044f4ff9a59b1aa83e44ae0",
53
+ "package.json": "559c14f546a781839ed5e741602d26f888a9c98399a397b788c0fc17da1c4083",
54
54
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
55
- "release.mjs": "1e6155d10b5e6384414233847815742e13f0945101c5f53253ad9ca827766beb",
56
- "local-cli.mjs": "ecc85bc5f36a5663fef004e59525374dc7883d3889f5a0cfe219ae5e84b2d3ea",
55
+ "release.mjs": "1b1925df27dd26164874d291c2dbac9a13010fca7892a9d5b529a3e6e99c0139",
56
+ "local-cli.mjs": "a79037d7383a29822b465e6db1c862a43d47cc344f05b6651c849633c49d19f7",
57
57
  "local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
58
- "local-execution.mjs": "97dfad5e889e679e9cc34434005342526502795abba21078fa40673788073153",
58
+ "local-execution.mjs": "cdada6df6dc8831cebb58a629b56c4b3f8e1bf9aae4e3fdc121521ba20839383",
59
59
  "local-integrity.mjs": "4f325376e536999c3b00245f66aac9ef1ae762c33012b363c231622008f72805",
60
60
  "local-evidence-verification.mjs": "9b7dea31777505272f63616fd62af7603338f703bb1285fc565d6451715c7ce0",
61
61
  "local-formula.mjs": "706d5ec0cb2caf95a547531a81ca746fe246d4c53d902e03595a3b360e8c85af",
62
62
  "local-ledger.mjs": "2009fb68aadcd7292b72f1d0d99f378d03644be1c5b4b4150d9f6a0b3f7c15ce",
63
- "local-error.mjs": "66484e2e27c9870a17dc0f1cf64d108ea384ad9fb41b7adc3aacb243ae65fe2e",
63
+ "local-error.mjs": "9dc42763ca7013cde607543308e029796d68f3b020a5a3b4ad9e68815e38d0f5",
64
64
  "blueprint-criterion-wire.mjs": "fb6b7dcce0b97a699e3e8968372767a3fddd83fedc750998573f789177243072",
65
65
  "blueprint-criterion-runner.mjs": "761c0e670b4b20c6bed53e5ca5d0178759505c3977ea2ed1b7223383c534bc4c",
66
66
  "local-chain-bindings.mjs": "b8eff999ccd17b53c614a3c9f5376da78fcaa8632bb3e07bc1adcceda2e75f6c",
@@ -89,7 +89,7 @@
89
89
  "local-scope-candidate.mjs": "8f0bd9606e70d4a0ebacdd3cd600ec3afed85e2bb3e1760aa1144cf55d4e028a",
90
90
  "local-scope-context.mjs": "79753abfc6c76089d17f003a1be537dfaea10d6731c542f7fc53f2a9996ed164",
91
91
  "local-scope-files.mjs": "0671ac4baef610a55ea6c1db6ebca7a4b6cfda45edb8aeaa828019e2a26f5a76",
92
- "local-scope-repository.mjs": "f3fa673fdb4b37bb085ce0caab6a24be2b9f056e12d59bd81aac798d85dd507c",
92
+ "local-scope-repository.mjs": "af7fe24408509dcebe39bdfc6fcef6adcf66f6ad09473149f920d5e921a48d1a",
93
93
  "local-scope-git.mjs": "53bbbb6c4795db711b5bc164591a6ad79a561427da046d6bbe997dae6eb82829",
94
94
  "local-scope-git-reads.mjs": "d18d4441664aa59ad8360ee16a69fc0282e7530705d5ae7950944a8158448bb4",
95
95
  "local-scope-git-comparison.mjs": "d86e559843087ae851795c9fb16b844dc46cc461fb5da37dbca0f6ba0d06450b",
@@ -113,12 +113,18 @@
113
113
  "runtime/git-native-linux-v1.mjs": "cf5cb91426a105e62c0f07a8a230dec592dba46f85a3f708b4c4e8cfcd1126c6",
114
114
  "runtime/git-native-darwin-v1.mjs": "f88aab72dc86f94eb1115784872957cb8ed0f74205ae70e1ecd1939e7790e310",
115
115
  "local-scope-signature.mjs": "bd05e8cf9e23f9511a16844974362297b9e9d11930ff7b8ad2a490e8d7660fe9",
116
+ "local-scope-config-imports.mjs": "16053142410dd647f13360f29fb3680c3e3a3ebc38019e43af6db05896d92a33",
116
117
  "local-scope-snapshot.mjs": "c0f851470b98863a1d187825ed61ee6072d1c057b178fdc85ae29af02a3b250f",
117
118
  "local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
118
119
  "local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
119
120
  "local-task-routing.mjs": "e26d6f1ba134ffe1da2a49b4fb6bb6dfa5617671f774a08e719d04f202cf5eca",
120
- "remote-client.mjs": "ce16258faffb09c9db19609d47a0af6f63fcbc256feb35ba7a14bbb89e69cc6e",
121
- "cloud-task-client.mjs": "ca6c5af7f7c2507680fcce3b8ab739a50ebed88f5a202cffbecea962861bbebb",
121
+ "remote-client.mjs": "3c95a81bcad3e53f63071a87929dae4b7c71e6a45034263269c731d9d494d529",
122
+ "cloud-task-client.mjs": "ecb6124ea5e6249f31fb95cb68d13d821d2daea8768fe47ddf6dcf82fc3392cc",
123
+ "scope-runner-client.mjs": "940c841e2c56e92793e5bf65e713bf4087151f721d0ecb659e0a75d6fdefeab9",
124
+ "cloud-watch-client.mjs": "bfbe636c10af467855776f9d29008d9ce13420b66bcc91b5bf8f6ef79fb2aefe",
125
+ "cloud-event-stream.mjs": "e0eec9fc3e0613094523929634f25030df5ca5c5b5fe2c1e1a4586ab10574808",
126
+ "ide-presentation.mjs": "026bf425d06276300ca748a8e22f66596ee52ff268fe0e6ffeff230885533d9a",
127
+ "automatic-feedback-client.mjs": "817e76b7ca4b5a282b9408ac9cf3daf7e09085db5aacbb49d74d8b6bcc4bb611",
122
128
  "remote-transport.mjs": "5e5a8c0b490371f7f5a052cffbeee6202d4cf713dff1c43b6d910fe944f8429c",
123
129
  "local-credential.mjs": "2a302a7f67b5ea8dc73453fc96ab33db349b59ca11fa9456e48ca1d9669d877c",
124
130
  "local-invocations.mjs": "f6ac4c41c1a5ec34de358c4cca210ca8e403b3a913150a43bb55ec9be55d9998",
@@ -139,8 +145,8 @@
139
145
  "workflow-local-request.mjs": "4e24c543ce386292a87dc9cd41f90d23375f12079cacffc515c8cd01fd571780",
140
146
  "task-message-entry.mjs": "31d10056cb905237cd5649e8eb067009ff1612ec5dc1061e0373209fbc0b646a",
141
147
  "task-message-cli.mjs": "3ca65e96c28129b3bd5cac962d106e0859a1f17df1f6a66ee543c6efc22f918d",
142
- "local-contracts.mjs": "43715cd73460ccea734501a3dd5667184da0f6a5b0189e038319a439ba52fc76",
143
- "local-contract-types.mjs": "5d244237fc69052b2fa83098022761bae929e8b75a0683883b620711bc132050",
148
+ "local-contracts.mjs": "e6c6b9b8b8b89e991c5689d935e26ccd92d398405ebced298817d673095bca1e",
149
+ "local-contract-types.mjs": "85e71fa4dd67e351a4b67573c740bcdb1661cede53f097189f11837771e75e91",
144
150
  "local-archguard-ast.mjs": "cefd835312a623e83bf48e4d8e1f76ac6a8adc56c35843705c16967d16bec4e6",
145
151
  "local-archguard-checkpoint.mjs": "37896902520cc995720c96288e4a5ed3585952b55c9c35be281f91fa754c71ec",
146
152
  "local-archguard-components.mjs": "a8fb3d0fd787f9160905013fd333cc0b9e8cf624cce797461f36f4ab31d170ad",
package/release.mjs CHANGED
@@ -86,10 +86,13 @@ const sourceFiles = [
86
86
  'runtime/git-native-linux-v1.mjs',
87
87
  'runtime/git-native-darwin-v1.mjs',
88
88
  'local-scope-signature.mjs',
89
+ 'local-scope-config-imports.mjs',
89
90
  'local-scope-snapshot.mjs',
90
91
  'local-scope-state.mjs',
91
92
  'local-scope-workspaces.mjs',
92
93
  'local-task-routing.mjs', 'remote-client.mjs', 'cloud-task-client.mjs',
94
+ 'scope-runner-client.mjs',
95
+ 'cloud-watch-client.mjs', 'cloud-event-stream.mjs', 'ide-presentation.mjs', 'automatic-feedback-client.mjs',
93
96
  'remote-transport.mjs', 'local-credential.mjs', 'local-invocations.mjs',
94
97
  'local-task-handoff-fence.mjs',
95
98
  'local-task-handoff-storage.mjs', 'local-task-handoff-overlay-wire.mjs', 'local-task-handoff-completion-wire.mjs',
@@ -257,7 +260,15 @@ function releaseNote(changed) {
257
260
  if (suppliedZh && suppliedEn) return {
258
261
  zh: suppliedZh.replaceAll(/\s+/g, ' '), en: suppliedEn.replaceAll(/\s+/g, ' '),
259
262
  }
260
- const labels = withFinalGate16ReleaseLabels({ ...calctoolReleaseLabels, ...mutationReleaseLabels, ...swarmReleaseLabels, ...coordination17ReleaseLabels, ...nativeReadReleaseLabels, ...feedbackReleaseLabels, ...invocationReleaseLabels, ...chainReleaseLabels,
263
+ if (changed.includes('scope-runner-client.mjs')) return {
264
+ zh: '补齐账号与任务隔离的本地签名器和真实快照接入,解析实际别名及类型声明;动态依赖仅进入受限规划,保留独立写入核验。',
265
+ en: 'Connected account-bound scope signers and real snapshots, resolved actual aliases and declarations, and retained restricted planning for unresolved dynamic imports with separate mutation verification.',
266
+ }
267
+ const distinct = distinctReleaseLabels(changed, releaseLabels())
268
+ return { zh: `更新了${distinct.map(label => label.zh).join('、')}。`, en: `Updated ${distinct.map(label => label.en).join(', ')}.` }
269
+ }
270
+ function releaseLabels() {
271
+ return withFinalGate16ReleaseLabels({ ...calctoolReleaseLabels, ...mutationReleaseLabels, ...swarmReleaseLabels, ...coordination17ReleaseLabels, ...nativeReadReleaseLabels, ...feedbackReleaseLabels, ...invocationReleaseLabels, ...chainReleaseLabels,
261
272
  'blueprint-criterion-wire.mjs': { zh: '蓝图验收标准执行授权', en: 'Blueprint criterion execution authority' },
262
273
  'blueprint-criterion-runner.mjs': { zh: '蓝图验收标准实际执行器', en: 'Blueprint criterion process runner' },
263
274
  'local-archguard-ast.mjs': { zh: 'Archguard 架构合同与执行守卫', en: 'Archguard architecture contract and execution guard' },
@@ -344,12 +355,11 @@ function releaseNote(changed) {
344
355
  'task-message-entry.mjs': { zh: '任务消息实际投递入口', en: 'task message host delivery entry' },
345
356
  'task-message-cli.mjs': { zh: '任务消息命令与适配器', en: 'task message command and adapter' },
346
357
  'cloud-task-client.mjs': { zh: '云端沙箱任务路由与逐任务选择', en: 'cloud sandbox task routing and per-task choice' },
358
+ 'cloud-watch-client.mjs': { zh: 'IDE 实时云端进度与推广展示', en: 'live IDE cloud progress and promotion display' },
359
+ 'cloud-event-stream.mjs': { zh: 'IDE 实时云端进度与推广展示', en: 'live IDE cloud progress and promotion display' },
360
+ 'ide-presentation.mjs': { zh: 'IDE 实时云端进度与推广展示', en: 'live IDE cloud progress and promotion display' },
361
+ 'automatic-feedback-client.mjs': { zh: '原调用自动评价回执核验', en: 'original invocation automatic evaluation receipt verification' },
347
362
  })
348
- const distinct = distinctReleaseLabels(changed, labels)
349
- return {
350
- zh: `更新了${distinct.map(label => label.zh).join('、')}。`,
351
- en: `Updated ${distinct.map(label => label.en).join(', ')}.`,
352
- }
353
363
  }
354
364
 
355
365
  async function writeAll(changes) {
package/releases.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.1.34",
4
+ "date": "2026-10-12",
5
+ "zh": "补回旧版八技能的 IDE 可见云端选择、真实事件进度与独立推广提示;服务端自动评价绑定原调用并返回可验证回执,支持只读恢复;修复旧式 UUID 调用漏计用量,保持账号隔离及沙箱/调用计数分离。",
6
+ "en": "Restore visible IDE cloud choice, authoritative live progress and separate promotion notices from the original eight-skill workflow. Persist account-bound automatic invocation evaluations with verifiable receipts and read-only recovery; meter legacy UUID calls without mixing skill and sandbox counters."
7
+ },
8
+ {
9
+ "version": "0.1.33",
10
+ "date": "2026-10-12",
11
+ "zh": "补齐账号与任务隔离的本地签名器和真实快照接入,解析实际别名及类型声明;动态依赖仅进入受限规划,保留独立写入核验。",
12
+ "en": "Connected account-bound scope signers and real snapshots, resolved actual aliases and declarations, and retained restricted planning for unresolved dynamic imports with separate mutation verification."
13
+ },
2
14
  {
3
15
  "version": "0.1.32",
4
16
  "date": "2026-10-11",