@entrorove/skill 0.1.48 → 0.1.50

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.50 — 2026-10-16
4
+
5
+ - 修复 tsconfig 的 npm 配置继承,支持包与子路径、exports/typesVersions 及数组继承;按真实 pnpm 来源解析并记录配置哈希。依赖保持只读,越界及无法确定规范来源的引用报告具体路径和原因,保留原任务重试。
6
+ - Fixed tsconfig inheritance from npm packages, including package subpaths, exports/typesVersions and ordered parent arrays. Resolve pnpm configurations from their actual locations and retain metadata hashes. Dependencies remain read-only; escaping or unrepresentable canonical references report their paths and reasons while preserving the original task for retry.
7
+
8
+ ## 0.1.49 — 2026-10-16
9
+
10
+ - 修复本地签名快照扫描大量源码时的内存耗尽:优先分析当前授权目标,为源码与语法树设置内存边界,并及时释放 Vue 解析缓存。未完整分析的引用明确保持未知,全仓文件哈希与变更核验继续保留;超限目标返回具体路径和限制,原 flow、范围与队列可保留后重试。
11
+ - Fixed memory exhaustion during local signed snapshots of large source repositories: inspect authorized targets first, bound source and syntax-tree retention, and release the Vue parser cache. Unresolved references remain explicitly unknown while whole-repository hashes and change verification are retained. Oversized targets report their exact path and limit so the original flow, scope, and queue can be preserved for retry.
12
+
3
13
  ## 0.1.48 — 2026-10-16
4
14
 
5
15
  - 新增标准命令 `install --host codex`,仅配置 Codex 并保留 Claude 的配置与技能副本;后续自动更新沿用已配置的宿主范围。安装命令守卫和旧版清理也按目标宿主限界。
package/IDE-EXECUTION.md CHANGED
@@ -64,7 +64,7 @@ The v2 progress sequence is durable and ordered: the first runtime step reports
64
64
 
65
65
  When intake returns `workflow-gate`, connect the current repository before invoking that gate: run `npx @entrorove/skill scope-connect <absolute repositoryRoot>` with JSON `{"flowId":"<original flowId>","paths":["<existing source file>"]}` on stdin. Use the concrete existing source files in the task's scope; new files belong in the later plan's declared artifacts. The adapter enrolls an OS-private Ed25519 scope signer for this accepted account and task, answers a server challenge, copies and verifies real source files, and submits the signed facts. It sends hashes and scope metadata rather than source contents or the private key. Enrollment grants scope inspection only; execution evidence and write passes retain their separate authority checks. An operator-configured signer cannot be replaced. After a successful connection, use the returned `nextInput` for `continue`, or explicitly send `payload: {"targetFiles":["<same existing source file>"]}` to the original `workflow-gate`. The connector uses `paths`; the gate uses `targetFiles`. Refresh an expired snapshot by reconnecting the same flow and path set. Show an explicit blocked result; query the original request ID after an unknown transport outcome before considering another attempt.
66
66
 
67
- Scope inspection resolves aliases from actual `tsconfig.json` or `jsconfig.json`, including relative `extends`. For plugin source compiled by a host project whose config is outside the source's ancestors, include `importContexts: [{"sourcePrefix":"<plugin source directory>","configPath":"<actual host tsconfig.json>"}]` in `scope-connect` input. Inspect the real build configuration first; never invent an alias or mark an unresolved import as external. These context paths remain inside the repository, their hashes enter the signed facts, and the local task binding freezes them.
67
+ Scope inspection resolves aliases from actual `tsconfig.json` or `jsconfig.json`, including relative and npm `extends` and ordered inheritance arrays. Npm configuration lookup uses the pinned TypeScript JSON-config resolver over controlled metadata reads, including scoped packages, subpaths, package `tsconfig`, `exports` and `typesVersions`. Configurations and package metadata enter the signed hashes; dependencies are read only, never executed. Repository-local pnpm links use their actual configuration location. Read-only external dependency packages support parents and mappings inside their reference boundary; references requiring an unrepresentable external canonical ancestor fail explicitly rather than selecting a different same-name repository package. For plugin source compiled by a host project whose config is outside the source's ancestors, include `importContexts: [{"sourcePrefix":"<plugin source directory>","configPath":"<actual host tsconfig.json>"}]` in `scope-connect` input. Inspect the real build configuration first; never invent an alias or mark an unresolved import as external. These context paths remain inside the repository, their hashes enter the signed facts, and the local task binding freezes them.
68
68
 
69
69
  Keep snapshot before-images distinct from a frozen execution plan's expected final file hashes. Controlled writes consume the original approved authority; the runner then verifies the actual planned final bytes. Never edit a signed plan or silently substitute a newer snapshot after an unknown or failed gate. Existing changes outside that authority require explicit revalidation of the same original flow.
70
70
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  Apply these rules when adding to or changing an existing system. Reuse is assessed inside the current accepted repository; external projects, databases, accounts, sessions and keys are outside scope.
4
4
 
5
+ Snapshot scans stream whole-repository integrity hashes and bound semantic work to 1 MiB per source, 8 MiB of retained source, 512 files and 200,000 syntax nodes. Original target paths and proposed files receive capacity first. A resource-limited non-target source stays in the integrity comparison and is recorded as unresolved; `scanComplete` is false and absent references cannot prove a symbol unused. A target that exceeds a bound returns `ENTROROVE_DISCIPLINE_ANALYSIS_LIMIT` with its path and the exceeded limit. These are execution memory limits, never size-based review exemptions or approval thresholds. Preserve the original flow, signer, scope and queue during recovery. Upgrade with the standard managed installer, then retry the original `scope-connect` input; do not create a replacement task or bypass the host guard with an arbitrary Node environment wrapper.
6
+
5
7
  - D-1: Before another business mapping or fact definition, search actual definitions and reuse them. Snapshot planning records the whole repository comparison and AST usage review in `discipline-review.json`; its digest is covered by the signed original snapshot. Typed source-entity field mappings use the existing import/alias resolver to identify the same entity, then compare field/value sets. A conflicting second mapping is refused with the existing file and line. An unresolved semantic relation remains unknown. No overlap threshold is invented.
6
8
  - D-2: A rewrite must cite a concrete existing defect at file:line. “Clearer”, “more maintainable” and “more general” are not evidence. A necessary sensitive exception is submitted through `continue` → `workflow-discipline-review` on the original planning phase. Display the whole concrete proposal. Approval must be the original account's stored Confirm Protocol answer and audit, never a caller's `accepted:true`.
7
9
  - D-3: A parallel path requires frozen `policy.parallelPaths` and actual same-input, fieldwise equivalence evidence. Use the existing GoldenBaseline to pin baseline bytes and source hash. The approved runner reads the baseline and actual process output; the original output cannot be supplied anew by the implementation. Missing evidence yields incomplete, mismatch yields blocked with field names. Baselines come from the existing path's actual independent samples, not the new implementation's self-consistency tests.
package/SKILL.md CHANGED
@@ -5,7 +5,7 @@ description: Execute an engineering task through Entrorove, preserving its origi
5
5
 
6
6
  # entrorove · 熵巡
7
7
 
8
- Version: 0.1.48.
8
+ Version: 0.1.50.
9
9
 
10
10
  Entrorove applies to the whole task once selected. Reading this document or calling an entry once does not execute the task. On every later turn, including after compaction, continue the original task and its required next operation before dependent work. Keep the original goal, scope, account, flow, cloud task and failure records. Never describe planning, a reported step or model output as verified delivery.
11
11
 
@@ -2,6 +2,21 @@ import ts from 'typescript';
2
2
  import { archguardScriptSource } from './local-archguard-ast.mjs';
3
3
  import { canonical, sha256 } from './local-integrity.mjs';
4
4
 
5
+ export const disciplineAnalysisLimits = Object.freeze({ fileBytes: 1_048_576, sourceBytes: 8_388_608, files: 512, nodes: 200_000 });
6
+ function analysisLimit(file, reason, limit, required) {
7
+ if (required.has(file.path)) throw Object.assign(new Error(`Semantic inspection of "${file.path}" exceeds ${reason} (${limit}); preserve the original flow and split its approved source inspection before retrying`),
8
+ { code: 'ENTROROVE_DISCIPLINE_ANALYSIS_LIMIT' });
9
+ return { path: file.path, reason, limit };
10
+ }
11
+ function syntaxNodes(source, limit) {
12
+ const pending = [source]; let count = 0;
13
+ while (pending.length) {
14
+ if (++count > limit) return count;
15
+ ts.forEachChild(pending.pop(), child => { pending.push(child); });
16
+ }
17
+ return count;
18
+ }
19
+
5
20
  const location = (source, node) => ({ path: source.fileName, line: source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1 });
6
21
  const unwrap = node => {
7
22
  while (node && (ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isParenthesizedExpression(node))) node = node.expression;
@@ -58,7 +73,7 @@ function definition(source, node) {
58
73
  while (ancestor && !ts.isSourceFile(ancestor) && !publicDefinition(ancestor)) ancestor = ancestor.parent;
59
74
  const exported = Boolean(ancestor && !ts.isSourceFile(ancestor));
60
75
  const contract = exported && (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isFunctionDeclaration(node));
61
- const signature = ts.isFunctionDeclaration(node) ? canonical({ parameters: node.parameters.map(item => item.getText(source)), type: node.type?.getText(source) ?? null }) : node.getText(source);
76
+ const signature = !contract ? null : ts.isFunctionDeclaration(node) ? canonical({ parameters: node.parameters.map(item => item.getText(source)), type: node.type?.getText(source) ?? null }) : node.getText(source);
62
77
  return { ...location(source, node), symbol, name, exported: Boolean(exported), contract: Boolean(contract),
63
78
  signatureDigest: contract ? sha256(signature) : null };
64
79
  }
@@ -110,11 +125,24 @@ function programFor(sources, edges) {
110
125
  });
111
126
  return ts.createProgram([...sources.keys()], { noLib: true, allowJs: true, checkJs: false }, host);
112
127
  }
113
- export function analyzeDisciplineSources(files, { edges = [], unresolved = [] } = {}) {
128
+ export function analyzeDisciplineSources(files, { edges = [], unresolved = [], requiredPaths = [] } = {}) {
114
129
  const sources = new Map(), unsupported = [];
130
+ const required = new Set(requiredPaths); let bytes = 0, nodes = 0;
115
131
  for (const file of files) {
132
+ const size = Buffer.byteLength(file.content);
133
+ const reason = size > disciplineAnalysisLimits.fileBytes ? 'source-file-byte-limit'
134
+ : bytes + size > disciplineAnalysisLimits.sourceBytes ? 'source-total-byte-limit'
135
+ : sources.size >= disciplineAnalysisLimits.files ? 'source-file-count-limit' : null;
136
+ if (reason) { unsupported.push(analysisLimit(file, reason, reason === 'source-file-byte-limit' ? disciplineAnalysisLimits.fileBytes
137
+ : reason === 'source-total-byte-limit' ? disciplineAnalysisLimits.sourceBytes : disciplineAnalysisLimits.files, required)); continue; }
138
+ bytes += size;
116
139
  const parsed = archguardScriptSource(file);
117
140
  if (!parsed || parsed.parseDiagnostics.length) { unsupported.push({ path: file.path, reason: parsed ? 'syntax-unresolved' : 'language-unresolved' }); continue; }
141
+ const count = syntaxNodes(parsed, disciplineAnalysisLimits.nodes - nodes);
142
+ if (nodes + count > disciplineAnalysisLimits.nodes) {
143
+ unsupported.push(analysisLimit(file, 'syntax-node-limit', disciplineAnalysisLimits.nodes, required)); continue;
144
+ }
145
+ nodes += count;
118
146
  sources.set(file.path, parsed);
119
147
  }
120
148
  const program = programFor(sources, edges), checker = program.getTypeChecker(), definitions = [], mappings = [], references = new Map();
@@ -5,7 +5,7 @@ import { canonical, sha256 } from './local-integrity.mjs';
5
5
  import { mutationRead14, mutationPath14, mutationTarget14, mutationFail14 } from './local-mutation-files.mjs';
6
6
  import { budgetBindingForRead } from './local-budget-reads.mjs';
7
7
  import { budgetStatus } from './local-budget.mjs';
8
- import { analyzeDisciplineSources } from './local-discipline-analysis.mjs';
8
+ import { analyzeDisciplineSources, disciplineAnalysisLimits } from './local-discipline-analysis.mjs';
9
9
  import { importGraph, scopeCandidateImports } from './local-scope-repository.mjs';
10
10
  import { nativeDisciplineDefinitions } from './local-discipline-native.mjs';
11
11
 
@@ -19,6 +19,14 @@ function withinBudgetScope(relative, scope) {
19
19
  && !scope.forbiddenPaths?.some(forbidden => within(relative, forbidden));
20
20
  }
21
21
  const sameFile = (left, right) => ['dev', 'ino', 'size', 'mtimeMs', 'ctimeMs', 'mode'].every(key => left[key] === right[key]);
22
+ function sourceLimit(relative, bytes, sources, used, required) {
23
+ const reason = bytes > disciplineAnalysisLimits.fileBytes ? 'source-file-byte-limit'
24
+ : used + bytes > disciplineAnalysisLimits.sourceBytes ? 'source-total-byte-limit'
25
+ : sources.length >= disciplineAnalysisLimits.files ? 'source-file-count-limit' : null;
26
+ if (reason && required.has(relative)) mutationFail14('ENTROROVE_DISCIPLINE_ANALYSIS_LIMIT',
27
+ `Semantic inspection of "${relative}" (${bytes} bytes) exceeds ${reason}; limits are ${disciplineAnalysisLimits.fileBytes} bytes per file, ${disciplineAnalysisLimits.sourceBytes} total bytes and ${disciplineAnalysisLimits.files} files. Preserve the original flow and split its approved inspection before retrying`);
28
+ return reason;
29
+ }
22
30
  async function integrityOnlyFile(root, relative) {
23
31
  // This path returns only an integrity digest. It never exposes source bytes or
24
32
  // contributes semantic facts outside the signed source-read scope.
@@ -47,12 +55,19 @@ async function inventory(root, prefix = '') {
47
55
  }
48
56
  export async function disciplineRepositoryObservation(input, root, { analyze = false, overrides = new Map() } = {}) {
49
57
  const paths = await inventory(root), files = [], sources = [], unresolved = [];
58
+ const required = new Set([...(input.paths ?? []), ...(input.targetPath ? [input.targetPath] : []), ...overrides.keys()]);
59
+ const ordered = [...paths].sort((a, b) => Number(required.has(b)) - Number(required.has(a)) || (a < b ? -1 : a > b ? 1 : 0));
60
+ let sourceBytes = 0;
50
61
  const binding = await budgetBindingForRead(input), budget = binding ? await budgetStatus(binding) : null;
51
62
  const restricted = budget?.enforced ? budget.scope : null;
52
63
  if (restricted && [...overrides.keys()].some(relative => !withinBudgetScope(relative, restricted))) {
53
64
  mutationFail14('ENTROROVE_BUDGET_SCOPE', 'candidate analysis is outside the original cloud task scope');
54
65
  }
55
- for (const relative of paths) {
66
+ for (const [relative, content] of overrides) if (analyze && !paths.includes(relative) && semanticSource(relative)) {
67
+ const bytes = Buffer.byteLength(content); sourceLimit(relative, bytes, sources, sourceBytes, required);
68
+ sources.push({ path: relative, content }); sourceBytes += bytes;
69
+ }
70
+ for (const relative of ordered) {
56
71
  const stat = await fs.lstat(path.join(root, relative));
57
72
  if (stat.isSymbolicLink()) {
58
73
  files.push({ path: relative, sha256: sha256(await fs.readlink(path.join(root, relative))), mode: stat.mode & 0o777, kind: 'symlink' });
@@ -60,24 +75,27 @@ export async function disciplineRepositoryObservation(input, root, { analyze = f
60
75
  }
61
76
  const semantic = analyze && semanticSource(relative);
62
77
  const allowed = !restricted || withinBudgetScope(relative, restricted);
63
- const read = semantic && allowed ? await mutationRead14(input, root, relative) : await integrityOnlyFile(root, relative);
78
+ const bytes = overrides.has(relative) ? Buffer.byteLength(overrides.get(relative)) : stat.size;
79
+ const limited = semantic && allowed ? sourceLimit(relative, bytes, sources, sourceBytes, required) : null;
80
+ const read = semantic && allowed && !limited ? await mutationRead14(input, root, relative) : await integrityOnlyFile(root, relative);
64
81
  files.push({ path: relative, sha256: read.sha256, mode: read.mode, kind: 'file' });
65
- if (semantic && allowed) sources.push({ path: relative, content: overrides.get(relative) ?? read.content.toString('utf8') });
82
+ if (semantic && allowed && !limited) { sources.push({ path: relative, content: overrides.get(relative) ?? read.content.toString('utf8') }); sourceBytes += bytes; }
83
+ else if (limited) unresolved.push({ path: relative, reason: limited, bytes });
66
84
  else if (semantic && !allowed) unresolved.push({ path: relative, reason: 'outside-signed-source-scope' });
67
85
  }
68
86
  if (canonical(paths) !== canonical(await inventory(root))) mutationFail14('ENTROROVE_DISCIPLINE_SCAN_CHANGED', 'Repository inventory changed while observing its original baseline');
87
+ files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
69
88
  const comparison = { schemaVersion: 'entrorove.repository-comparison/1.0', files,
70
89
  scope: 'whole-repository-including-ignored-files', excludedDirectoryNames: [...excluded].sort() };
71
90
  if (!analyze) return { comparison };
72
91
  // The existing resolver supplies actual aliases, package exports and import edges.
73
92
  // Unsupported/dynamic references stay unknown; they are never converted to unused.
74
- const roots = sources.filter(file => /\.[cm]?[jt]sx?$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
75
- for (const [relative, content] of overrides) if (!paths.includes(relative) && semanticSource(relative)) sources.push({ path: relative, content });
93
+ const roots = sources.filter(file => paths.includes(file.path) && /\.[cm]?[jt]sx?$/i.test(file.path)).map(file => ({ path: file.path, exists: true }));
76
94
  let graph;
77
- try { graph = roots.length ? await importGraph(root, roots, input) : { edges: [], dynamicSources: [], complete: true }; }
95
+ try { graph = roots.length ? await importGraph(root, roots, input, disciplineAnalysisLimits) : { edges: [], dynamicSources: [], complete: true }; }
78
96
  catch (error) {
79
97
  if (!['ENTROROVE_IMPORT_UNRESOLVED', 'ENTROROVE_IMPORT_INVALID', 'ENTROROVE_IMPORT_UNSUPPORTED', 'ENTROROVE_SCOPE_LIMIT', 'ENTROROVE_BUDGET_SCOPE'].includes(error.code)) throw error;
80
- graph = { edges: [], dynamicSources: [], complete: false }; unresolved.push({ reason: error.code });
98
+ graph = { edges: [], dynamicSources: [], complete: false }; unresolved.push({ reason: error.code, message: error.message });
81
99
  }
82
100
  for (const [relative, content] of overrides) if (semanticSource(relative) && /\.[cm]?[jt]sx?$/i.test(relative)) {
83
101
  try {
@@ -89,7 +107,7 @@ export async function disciplineRepositoryObservation(input, root, { analyze = f
89
107
  unresolved.push({ path: relative, reason: error.code });
90
108
  }
91
109
  }
92
- const analysis = analyzeDisciplineSources(sources, { edges: graph.edges,
110
+ const analysis = analyzeDisciplineSources(sources, { edges: graph.edges, requiredPaths: [...required],
93
111
  unresolved: [...unresolved, ...graph.dynamicSources.map(item => ({ path: item.path, reason: 'dynamic-import' }))] });
94
112
  analysis.definitions.push(...await nativeDisciplineDefinitions(sources));
95
113
  return { comparison, analysis, scanComplete: graph.complete && analysis.unresolved.length === 0 };
@@ -0,0 +1,71 @@
1
+ import path from 'node:path';
2
+ import ts from 'typescript';
3
+
4
+ const virtualRoot = '/entrorove-repository/';
5
+ export function assertScopeConfigReference(from, resolved, boundary, field) {
6
+ if (boundary && resolved !== boundary.path && !resolved.startsWith(`${boundary.path}/`)) {
7
+ throw new Error(`Scope tsconfig ${field} from "${from}" (canonical "${boundary.resolved}") resolves to "${resolved}" outside read-only dependency reference "${boundary.path}"; its external canonical base cannot be represented inside the authorized repository`);
8
+ }
9
+ return resolved;
10
+ }
11
+ function repositoryPath(filename, within) {
12
+ const normalized = path.posix.normalize(filename);
13
+ if (!normalized.startsWith(virtualRoot)) return null;
14
+ return within('', normalized.slice(virtualRoot.length), false, 'extends resolution');
15
+ }
16
+ function validatePackageConfig(relative, source, within, referenceRoot) {
17
+ if (!relative.endsWith('/package.json')) return;
18
+ let manifest;
19
+ try { manifest = JSON.parse(source); }
20
+ catch { throw new Error(`Scope npm extends package metadata is invalid JSON: ${relative}`); }
21
+ if (typeof manifest?.tsconfig === 'string') {
22
+ const resolved = within(path.posix.dirname(relative), manifest.tsconfig, false, `package tsconfig in ${relative}`);
23
+ assertScopeConfigReference(relative, resolved, referenceRoot(relative), `package tsconfig ${JSON.stringify(manifest.tsconfig)}`);
24
+ }
25
+ }
26
+
27
+ /** Replay the pinned TypeScript resolver over asynchronously authorized metadata only. */
28
+ export async function scopeNpmConfig(metadata, fromConfig, specifier, within, canonicalPath, referenceRoot) {
29
+ if (specifier.split('/').some(part => !part || part === '.' || part === '..')) {
30
+ throw new Error(`Scope tsconfig extends has an invalid npm specifier: ${JSON.stringify(specifier)} in ${fromConfig}`);
31
+ }
32
+ if (referenceRoot(fromConfig)) {
33
+ const boundary = referenceRoot(fromConfig);
34
+ throw new Error(`Scope tsconfig npm extends ${JSON.stringify(specifier)} from "${fromConfig}" resolves through external canonical configuration "${boundary.resolved}"; ancestor package lookup outside dependency reference "${boundary.path}" cannot be represented inside the authorized repository`);
35
+ }
36
+ const sources = new Map(), pending = Symbol('authorized-config-read');
37
+ const lookup = filename => {
38
+ const relative = repositoryPath(filename, within);
39
+ if (relative === null) return null;
40
+ if (!sources.has(relative)) throw { pending, relative };
41
+ return sources.get(relative);
42
+ };
43
+ const host = {
44
+ fileExists: filename => lookup(filename) !== null,
45
+ readFile: filename => lookup(filename) ?? undefined,
46
+ getCurrentDirectory: () => virtualRoot.slice(0, -1),
47
+ getGlobalTypingsCacheLocation: () => virtualRoot.slice(0, -1),
48
+ useCaseSensitiveFileNames: true,
49
+ realpath: filename => {
50
+ const relative = repositoryPath(filename, within);
51
+ return relative === null ? filename : virtualRoot + within('', canonicalPath(relative), false, 'dependency realpath');
52
+ },
53
+ };
54
+ for (let probes = 0; probes < 1024; probes++) {
55
+ try {
56
+ const result = ts.nodeNextJsonConfigResolver(specifier, virtualRoot + fromConfig, host);
57
+ if (!result.resolvedModule) {
58
+ throw new Error(`Scope tsconfig npm extends ${JSON.stringify(specifier)} in ${fromConfig} references a missing or unexported actual configuration within the authorized repository`);
59
+ }
60
+ const relative = repositoryPath(result.resolvedModule.resolvedFileName, within);
61
+ if (relative === null) throw new Error(`Scope tsconfig npm extends ${JSON.stringify(specifier)} resolved outside the authorized repository`);
62
+ return relative;
63
+ } catch (error) {
64
+ if (error?.pending !== pending) throw error;
65
+ const source = await metadata(error.relative);
66
+ if (source !== null) validatePackageConfig(error.relative, source, within, referenceRoot);
67
+ sources.set(error.relative, source);
68
+ }
69
+ }
70
+ throw new Error(`Scope tsconfig npm extends ${JSON.stringify(specifier)} in ${fromConfig} exceeds the 1024-metadata-probe bound`);
71
+ }
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import path from 'node:path';
3
3
  import ts from 'typescript';
4
+ import { scopeNpmConfig, assertScopeConfigReference } from './local-scope-config-extends.mjs';
4
5
  const missing = (error) => ['ENOENT', 'ENOTDIR'].includes(error?.code ?? '');
5
6
  function literal(value, field) {
6
7
  if (typeof value !== 'string' || !value || value !== value.trim() || value !== value.normalize('NFC')
@@ -81,6 +82,11 @@ function pathMatch(paths, specifier) {
81
82
  }
82
83
  return selected;
83
84
  }
85
+ function configCandidate(resolution, target) {
86
+ const base = resolution.baseUrl ?? resolution.pathsOrigin;
87
+ const boundary = resolution.baseUrl === undefined ? resolution.pathsReferenceRoot : resolution.baseUrlReferenceRoot;
88
+ return assertScopeConfigReference(base || '.', within(base, target), boundary, `mapping ${JSON.stringify(target)}`);
89
+ }
84
90
  function importCandidates(resolution, specifier) {
85
91
  if (typeof specifier !== 'string' || !specifier)
86
92
  throw new Error('Scope tsconfig import specifier must be nonempty');
@@ -92,18 +98,14 @@ function importCandidates(resolution, specifier) {
92
98
  }
93
99
  const match = pathMatch(resolution?.paths, specifier);
94
100
  if (match) {
95
- const base = resolution.baseUrl ?? resolution.pathsOrigin;
96
- return [...new Set(match.targets.map(target => within(base, target.replace('*', match.wildcard))))];
101
+ return [...new Set(match.targets.map(target => configCandidate(resolution, target.replace('*', match.wildcard))))];
97
102
  }
98
103
  if (specifier.startsWith('@/') || specifier.startsWith('#') || specifier.startsWith('~/')) {
99
104
  throw new Error('Scope local import alias requires an actual tsconfig or package-import mapping');
100
105
  }
101
- return resolution?.baseUrl !== undefined ? [within(resolution.baseUrl, specifier)] : [];
106
+ return resolution?.baseUrl !== undefined ? [configCandidate(resolution, specifier)] : [];
102
107
  }
103
- export async function scopeTsconfigImports(read, fromFile, configPath) {
104
- const canonical = within('', fromFile);
105
- if (canonical !== fromFile)
106
- throw new Error('Scope tsconfig source path must be canonical within the repository');
108
+ function configObservations(read) {
107
109
  const observed = new Map();
108
110
  const absent = new Set();
109
111
  let bytes = 0;
@@ -135,34 +137,50 @@ export async function scopeTsconfigImports(read, fromFile, configPath) {
135
137
  observed.set(relative, item);
136
138
  return data.toString('utf8');
137
139
  };
140
+ return { metadata, canonicalPath: relative => observed.get(relative)?.realPath ?? relative,
141
+ referenceRoot: relative => observed.get(relative)?.referenceRoot,
142
+ observations: () => [...observed.keys()].sort().map(relative => ({ path: relative, sha256: observed.get(relative).sha256 })) };
143
+ }
144
+ export async function scopeTsconfigImports(read, fromFile, configPath) {
145
+ const canonical = within('', fromFile);
146
+ if (canonical !== fromFile)
147
+ throw new Error('Scope tsconfig source path must be canonical within the repository');
148
+ const { metadata, canonicalPath, referenceRoot, observations } = configObservations(read);
149
+ let configurations = 0;
138
150
  const load = async (relative, ancestors, depth = 0) => {
139
- if (depth >= 10)
140
- throw new Error('Scope tsconfig extends exceeds the 10-configuration depth bound');
151
+ if (depth >= 10 || ++configurations > 512)
152
+ throw new Error('Scope tsconfig extends exceeds its 10-configuration depth or 512-configuration inspection bound');
141
153
  let source = await metadata(relative);
142
154
  if (source === null && !relative.endsWith('.json')) {
143
155
  relative += '.json';
144
156
  source = await metadata(relative);
145
157
  }
146
158
  if (source === null)
147
- throw new Error('Scope tsconfig extends references a missing actual configuration');
159
+ throw new Error(`Scope tsconfig extends references a missing actual configuration: ${relative}`);
148
160
  if (ancestors.has(relative))
149
161
  throw new Error('Scope tsconfig extends contains a configuration cycle');
150
162
  const config = parsedConfig(relative, source);
151
163
  const directory = path.posix.dirname(relative) === '.' ? '' : path.posix.dirname(relative);
152
- let inherited = { pathsOrigin: directory };
164
+ let inherited = {};
153
165
  if (config.extends !== undefined) {
154
- const extended = literal(config.extends, 'extends');
155
- if (!extended.startsWith('./') && !extended.startsWith('../'))
156
- throw new Error('Scope tsconfig npm extends is unsupported; use an actual repository-relative extends');
157
- inherited = await load(within(directory, extended, false, 'extends'), new Set([...ancestors, relative]), depth + 1);
166
+ const parents = Array.isArray(config.extends) ? config.extends : [config.extends];
167
+ if (parents.length > 128) throw new Error(`Scope tsconfig extends in ${relative} exceeds the 128-parent bound`);
168
+ for (const parent of parents) {
169
+ const extended = literal(parent, 'extends');
170
+ const target = extended.startsWith('./') || extended.startsWith('../')
171
+ ? assertScopeConfigReference(relative, within(directory, extended, false, 'extends'), referenceRoot(relative), `extends ${JSON.stringify(extended)}`)
172
+ : await scopeNpmConfig(metadata, relative, extended, within, canonicalPath, referenceRoot);
173
+ inherited = { ...inherited, ...await load(target, new Set([...ancestors, relative]), depth + 1) };
174
+ }
158
175
  }
159
176
  const options = config.compilerOptions === undefined ? {} : config.compilerOptions;
160
177
  if (!options || typeof options !== 'object' || Array.isArray(options))
161
178
  throw new Error('Scope tsconfig compilerOptions must be an object');
162
179
  const own = options;
163
180
  return { ...inherited,
164
- ...(own.baseUrl === undefined ? {} : { baseUrl: within(directory, own.baseUrl, true, 'baseUrl') }),
165
- ...(own.paths === undefined ? {} : { paths: pathsOption(own.paths), pathsOrigin: directory }) };
181
+ ...(own.baseUrl === undefined ? {} : { baseUrl: assertScopeConfigReference(relative, within(directory, own.baseUrl, true, 'baseUrl'), referenceRoot(relative), `baseUrl ${JSON.stringify(own.baseUrl)}`),
182
+ baseUrlReferenceRoot: referenceRoot(relative) }),
183
+ ...(own.paths === undefined ? {} : { paths: pathsOption(own.paths), pathsOrigin: directory, pathsReferenceRoot: referenceRoot(relative) }) };
166
184
  };
167
185
  let directory = path.posix.dirname(canonical);
168
186
  let resolution = null;
@@ -186,12 +204,11 @@ export async function scopeTsconfigImports(read, fromFile, configPath) {
186
204
  if (resolution?.paths)
187
205
  for (const targets of Object.values(resolution.paths))
188
206
  for (const target of targets) {
189
- within(resolution.baseUrl ?? resolution.pathsOrigin, target.replace('*', '__scope_wildcard__'), false, 'mapping');
207
+ configCandidate(resolution, target.replace('*', '__scope_wildcard__'));
190
208
  }
191
209
  return { candidates: specifier => importCandidates(resolution, specifier), mapped: specifier => {
192
210
  if (typeof specifier !== 'string' || !specifier)
193
211
  throw new Error('Scope tsconfig mapping lookup requires a nonempty specifier');
194
212
  return pathMatch(resolution?.paths, specifier) !== null;
195
- }, observations: [...observed.keys()].sort()
196
- .map(relative => ({ path: relative, sha256: observed.get(relative).sha256 })) };
213
+ }, observations: observations() };
197
214
  }
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { promisify } from 'node:util';
5
5
  import { builtinModules } from 'node:module';
6
6
  import ts from 'typescript';
7
- import { parse as parseVue } from '@vue/compiler-sfc';
7
+ import { parse as parseVue, parseCache as vueParseCache } from '@vue/compiler-sfc';
8
8
  import { lineCount, observedScopeRisk, packageBoundary, scopeFail, scopeFiles, scopeRead, scopeTarget, stableScope } from './local-scope-files.mjs';
9
9
  import { sha256 } from './local-integrity.mjs';
10
10
  import { workspaceImport, workspaceIndex } from './local-scope-workspaces.mjs';
@@ -142,6 +142,7 @@ function scriptImports(source, filename) {
142
142
  function vueScripts(source, filename) {
143
143
  const { descriptor, errors } = parseVue(source, { filename, sourceMap: false,
144
144
  templateParseOptions: { expressionPlugins: ['typescript'] } });
145
+ vueParseCache.clear();
145
146
  if (errors.length) scopeFail('ENTROROVE_IMPORT_INVALID', 'component has invalid or unterminated syntax');
146
147
  if (descriptor.customBlocks.length || descriptor.template?.lang || descriptor.template?.src) {
147
148
  scopeFail('ENTROROVE_IMPORT_UNSUPPORTED', 'component dependency inspection requires supported inline template syntax');
@@ -215,6 +216,27 @@ async function resolveImport(root, from, specifier, workspace, input, aliases, c
215
216
  return resolveLocalImport(root, relative);
216
217
  }
217
218
  const dependencyReference = relative => relative.split('/').includes('node_modules');
219
+ async function externalConfigReference(root, relative, resolved) {
220
+ const parts = relative.split('/'); let previous = root, boundary;
221
+ for (let index = 1; index <= parts.length; index++) {
222
+ const prefix = parts.slice(0, index).join('/'), current = await fs.realpath(path.join(root, prefix));
223
+ if (current !== root && !current.startsWith(`${root}${path.sep}`) && current !== path.join(previous, parts[index - 1])) {
224
+ boundary = { path: prefix, resolved };
225
+ }
226
+ previous = current;
227
+ }
228
+ return boundary;
229
+ }
230
+ async function configMetadata(root, relative, input) {
231
+ const target = await scopeTarget(root, relative, true, dependencyReference(relative));
232
+ if (!target.exists || !target.stat.isFile()) throw Object.assign(new Error(`Missing configuration file: ${relative}`), { code: 'ENOENT' });
233
+ const read = await scopeRead(root, relative, input), resolved = await fs.realpath(target.target);
234
+ const physical = path.relative(root, resolved).split(path.sep).join('/');
235
+ const outside = physical === '..' || physical.startsWith('../') || path.isAbsolute(physical);
236
+ const realPath = outside ? relative : physical;
237
+ const referenceRoot = outside ? await externalConfigReference(root, relative, resolved) : undefined;
238
+ return { path: relative, sha256: read.sha256, contentBase64: read.content.toString('base64'), realPath, referenceRoot };
239
+ }
218
240
  async function resolveLocalImport(root, relative, required = true) {
219
241
  if (relative.startsWith('../') || relative === '..') scopeFail('ENTROROVE_PATH_UNSAFE', 'local import escapes the repository');
220
242
  const replacements = /\.js$/.test(relative) ? [relative.replace(/\.js$/, '.ts'), relative.replace(/\.js$/, '.tsx')] : [];
@@ -225,24 +247,29 @@ async function resolveLocalImport(root, relative, required = true) {
225
247
  if (required) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
226
248
  return null;
227
249
  }
228
- export async function importGraph(root, originals, input = { repositoryRoot: root }) {
250
+ export async function importGraph(root, originals, input = { repositoryRoot: root }, limits = {}) {
229
251
  const pending = originals.filter(file => file.exists !== false).map(file => file.path); const visited = new Map(); const edges = []; let dynamic = false;
252
+ let sourceBytes = 0;
230
253
  const dynamicSources = [];
231
254
  const workspace = await workspaceIndex(root, input);
232
255
  const metadata = new Map(workspace.metadata.map(item => [item.path, item]));
233
256
  while (pending.length) {
234
257
  const relative = pending.shift(); if (visited.has(relative)) continue;
235
258
  if (visited.size >= 1000) scopeFail('ENTROROVE_SCOPE_LIMIT', 'import graph exceeds the 1000-file inspection bound');
259
+ if (limits.fileBytes || limits.sourceBytes) {
260
+ const target = await scopeTarget(root, relative, false, dependencyReference(relative));
261
+ if (target.stat.size > (limits.fileBytes ?? Infinity) || sourceBytes + target.stat.size > (limits.sourceBytes ?? Infinity)) {
262
+ scopeFail('ENTROROVE_SCOPE_LIMIT', `Import inspection of "${relative}" (${target.stat.size} bytes) exceeds its bounded semantic scan (${limits.fileBytes} bytes per file, ${limits.sourceBytes} total bytes)`);
263
+ }
264
+ sourceBytes += target.stat.size;
265
+ }
236
266
  const read = await scopeRead(root, relative, input); const boundary = await packageBoundary(root, relative, input);
237
267
  visited.set(relative, { path: relative, sha256: read.sha256, module: boundary.path, boundary });
238
268
  if (!sourceExtension.test(relative)) continue;
239
269
  const contexts = (input.importContexts ?? []).filter(context => relative === context.sourcePrefix || relative.startsWith(`${context.sourcePrefix}/`))
240
270
  .sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length);
241
271
  const configPath = contexts[0]?.configPath;
242
- const aliases = await scopeTsconfigImports(async target => {
243
- const item = await scopeRead(root, target, input);
244
- return { path: target, sha256: item.sha256, contentBase64: item.content.toString('base64') };
245
- }, relative, configPath);
272
+ const aliases = await scopeTsconfigImports(target => configMetadata(root, target, input), relative, configPath);
246
273
  for (const observed of aliases.observations) metadata.set(observed.path, observed);
247
274
  const declarations = declarationModules(read.content, relative);
248
275
  const imports = importSpecifiers(read.content, relative); dynamic ||= imports.dynamic;
@@ -266,10 +293,7 @@ export async function scopeCandidateImports(root, file, input = { repositoryRoot
266
293
  const workspace = await workspaceIndex(root, input);
267
294
  const context = (input.importContexts ?? []).filter(item => file.path === item.sourcePrefix || file.path.startsWith(`${item.sourcePrefix}/`))
268
295
  .sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length)[0];
269
- const aliases = await scopeTsconfigImports(async relative => {
270
- const read = await scopeRead(root, relative, input);
271
- return { path: relative, sha256: read.sha256, contentBase64: read.content.toString('base64') };
272
- }, file.path, context?.configPath);
296
+ const aliases = await scopeTsconfigImports(relative => configMetadata(root, relative, input), file.path, context?.configPath);
273
297
  const declarations = declarationModules(Buffer.from(file.content), file.path), edges = [];
274
298
  for (const specifier of parsed.specifiers) {
275
299
  const target = await resolveImport(root, file.path, specifier, workspace, input, aliases, context?.configPath, declarations);
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.48",
4
+ "version": "0.1.50",
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.48",
3
+ "version": "0.1.50",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -135,6 +135,7 @@
135
135
  "local-scope-repository.mjs",
136
136
  "local-scope-signature.mjs",
137
137
  "local-scope-config-imports.mjs",
138
+ "local-scope-config-extends.mjs",
138
139
  "local-scope-snapshot.mjs",
139
140
  "local-scope-state.mjs",
140
141
  "local-scope-workspaces.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.48",
4
- "digest": "6f79c0e2c12d4e8766801bf117bcf73070156ba89fb87e4b1137c9706b8f7df3",
3
+ "version": "0.1.50",
4
+ "digest": "c749866399a99cf122f71c00dca31cf49706a76f0998b7f3197d0763968468b5",
5
5
  "files": {
6
6
  "ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
7
7
  "ide-command.mjs": "dcd4b48305cc4f106e5e4509e794e4b46ef02de29afa9cccbbc2774308c81ff3",
@@ -12,14 +12,14 @@
12
12
  "ide-results.mjs": "dfe0d3f773c65a9fc21056538d7005012c982ed4509bc0410d7f62813d0863fd",
13
13
  "ide-queue.mjs": "806e716c775fd0079417027b135977c8516f47dddb0bc5ca19e32a355dab0e9e",
14
14
  "local-discipline-actions.mjs": "2379c08d0f49c44068f8239db7e514d75885f4606b1b88a7a36e4ff19167caca",
15
- "local-discipline-analysis.mjs": "ebea932438a53a157d5d07a5d68366496eee121a2c67cecfb4fdb2d5aa9763eb",
15
+ "local-discipline-analysis.mjs": "f49e67a2eb2f19f8eb7c4226d481842b38c0f19b495ad0149cbf649baae16bb9",
16
16
  "local-discipline-batch.mjs": "2cc90dbae55efdee0c445da399c0a9acafa92e915344c25208ebca554fc4c9e9",
17
17
  "local-discipline-contracts.mjs": "6cf8a3d132fdb9074eb1441600afe7aa199df33e88a71a660d9db534c14f19e4",
18
18
  "local-discipline-contracts.d.mts": "defc684c78dc88afa1c54340ab4a01ae36db7aa11257cbe1f5cd323f608c78c4",
19
19
  "local-discipline-gate.mjs": "04b1ba092d7f4342a0e77cfbeb44d9634dbce9fec0c79e6a1e4b11a662d904ac",
20
20
  "local-discipline-native.mjs": "8b99b5ce951cab3c1426428ad807650c61c0ffb2c346d747b73b99057b382049",
21
21
  "local-discipline-operations.mjs": "9fcb7b8889b7718d42e1d37936485d8198d5017d405e0cdbbabdbbd12fcc9d48",
22
- "local-discipline-repository.mjs": "d818aa6137459ef200892479cc8c47fe82c884dc0e76dc7e6d45a6b41a13c6ec",
22
+ "local-discipline-repository.mjs": "ffd31e9b2946a9784d4ab59cee5345318d2152816ce995ed5dac11efa4096bc4",
23
23
  "local-discipline-snapshot.mjs": "8fd35b2cfbece8f64579f144f589a66dec9b6022d50fe931c4c1f886cb83134d",
24
24
  "local-runner-discipline.mjs": "86f451de2f1e12a24f5596a29828891c1eba13cd4714ed0c18a77d66cecef934",
25
25
  "local-integrity.d.mts": "21357f306234bd841c0d3d90bf2d09f7663f63746f897b9f38190428ae8198b3",
@@ -29,8 +29,8 @@
29
29
  "runtime/validator-runtime-v7.0.44.d.mts": "7797edbfe2f7de24da370204dce4dfd22822a1bd80df93514fff900b98afe36f",
30
30
  "snapshot-retention.mjs": "4f8919fe8ec49fb4c23e080a8ad2fa3a9282bca3fffe7cf55c64e89ef316697e",
31
31
  "snapshot-cleanup-journal.mjs": "76a9684cc95b4eb076e1298d78a9dff37948b00d9607b8f546c599072d3ed331",
32
- "IDE-EXECUTION.md": "6c8b9abe17cc8f01aa3da2ab19ed34b2b9be3e3b38c1739fa6a7d763622c4864",
33
- "REUSE-DISCIPLINE.md": "295ecd41c931b00c21d009ab65ac160f0e2ab81b4000ed498d2e8e97c2dc6c63",
32
+ "IDE-EXECUTION.md": "4e76abb3b2e357de07665d7422204ce710af3cafc0ef946816de73bf1a8998e6",
33
+ "REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
34
34
  "reuse-discipline-release.mjs": "20ded9c21bd71b8779c454f5eba4c7c0a943ef99d94117b9bbef891d2ab6e3de",
35
35
  "local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
36
36
  "local-final-gate16-checks.mjs": "1dd30e9666d458d8b12ca6e4975f6f0ac8450c3ea9cbecc7401ae037f66b589a",
@@ -81,9 +81,9 @@
81
81
  "CAPABILITY-MAP.md": "a0f0468a098725ced52a96dd8e22bfc7d1ce7681a8446ebf7dae3696c76c3385",
82
82
  "AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
83
83
  "DELIVERY-BOUNDARIES.md": "d1bb2895e23fc14bf1c76707bd1f6f03b39695501a78a0b7289dc37b099889dc",
84
- "package.json": "8be823b5756da9b393af966a4152bee301cb49f988823aaf757e8c51bf867085",
84
+ "package.json": "cdb1f686304d5d29d1feff1b1ec5b75e4e3dcff1f1da841dfb59d0502ab261e8",
85
85
  "manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
86
- "release.mjs": "34a3da868e8b5b586b318f2e8275f2f238d48a20afceb7d51c0f783a120f6973",
86
+ "release.mjs": "e206cb6cabbb02542317bce283f7ba674621e67765a3eacef5741f2d6b1c58b2",
87
87
  "local-cli.mjs": "11461e4cf4f6a8612ff275bf8296ec57ff22281156cb2c72834d3c764ba422a1",
88
88
  "local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
89
89
  "local-execution.mjs": "46fddd78cd954fb080c660affe90cdaa716695580427336acb820089441a8025",
@@ -122,7 +122,7 @@
122
122
  "local-scope-candidate.mjs": "8f0bd9606e70d4a0ebacdd3cd600ec3afed85e2bb3e1760aa1144cf55d4e028a",
123
123
  "local-scope-context.mjs": "79753abfc6c76089d17f003a1be537dfaea10d6731c542f7fc53f2a9996ed164",
124
124
  "local-scope-files.mjs": "96c301f2a45e5fa22f0b11fb15e5e564f118e53187fc7749263c78d5ed87b056",
125
- "local-scope-repository.mjs": "0c955612d2f737e8661af84c1efb6e9e7fe6bb9ec1dfadd7776c61be177261f2",
125
+ "local-scope-repository.mjs": "56d7874326de2f90e47cf13d6b9839372044ea70ab15fbb7f7125bd13a87a087",
126
126
  "local-scope-git.mjs": "53bbbb6c4795db711b5bc164591a6ad79a561427da046d6bbe997dae6eb82829",
127
127
  "local-scope-git-reads.mjs": "5d8a2636bab955cb325cb693db7423e69512355887ae5b5af3b8cb615f5ddad0",
128
128
  "local-git-executable.mjs": "9391d716529025b1de3ee006df658a9d765b6ef29ad24ef9b7436829318e651e",
@@ -147,7 +147,8 @@
147
147
  "runtime/git-native-linux-v1.mjs": "cf5cb91426a105e62c0f07a8a230dec592dba46f85a3f708b4c4e8cfcd1126c6",
148
148
  "runtime/git-native-darwin-v1.mjs": "f88aab72dc86f94eb1115784872957cb8ed0f74205ae70e1ecd1939e7790e310",
149
149
  "local-scope-signature.mjs": "bd05e8cf9e23f9511a16844974362297b9e9d11930ff7b8ad2a490e8d7660fe9",
150
- "local-scope-config-imports.mjs": "53e510609066355318d886cd96f73a0fc8005a28857f6ab4eb6cb81c4857945b",
150
+ "local-scope-config-imports.mjs": "a399407227bbdd14468633bb6d25586df7c6a30a1ef0d1a80ab7c7b499d3fadf",
151
+ "local-scope-config-extends.mjs": "3ab4150b846b247e9e0851221123f220c5574699af651830e751f026ab33659e",
151
152
  "local-scope-snapshot.mjs": "c0f851470b98863a1d187825ed61ee6072d1c057b178fdc85ae29af02a3b250f",
152
153
  "local-scope-state.mjs": "8b600a91fc0068d7a8c0bb752ddba7ed30ada93daafb118d6cba2368fd94cb27",
153
154
  "local-scope-workspaces.mjs": "a44c34537e5ed2dcaf193a985e5bc05957989fbb160a650f9e37ba7d66eb2de8",
package/release.mjs CHANGED
@@ -90,6 +90,7 @@ const sourceFiles = [
90
90
  'runtime/git-native-darwin-v1.mjs',
91
91
  'local-scope-signature.mjs',
92
92
  'local-scope-config-imports.mjs',
93
+ 'local-scope-config-extends.mjs',
93
94
  'local-scope-snapshot.mjs',
94
95
  'local-scope-state.mjs',
95
96
  'local-scope-workspaces.mjs',
@@ -340,6 +341,7 @@ function releaseLabels() {
340
341
  'local-scope-files.mjs': { zh: '范围文件与安全读取', en: 'scope files and guarded reads' },
341
342
  'local-scope-repository.mjs': { zh: '真实仓库范围事实', en: 'actual repository scope facts' },
342
343
  'local-scope-config-imports.mjs': { zh: 'tsconfig 范围路径与依赖只读引用', en: 'tsconfig scope paths and read-only dependency references' },
344
+ 'local-scope-config-extends.mjs': { zh: 'tsconfig npm 配置继承解析', en: 'tsconfig npm configuration inheritance resolution' },
343
345
  'local-scope-git.mjs': { zh: '实际Git范围与变更事实', en: 'actual Git scope and change facts' },
344
346
  'local-scope-git-reads.mjs': { zh: '固定Git元数据与源码读取', en: 'fixed Git metadata and source reads' },
345
347
  'local-scope-git-comparison.mjs': { zh: 'Git范围比较接线', en: 'Git scope comparison wiring' },
package/releases.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.1.50",
4
+ "date": "2026-10-16",
5
+ "zh": "修复 tsconfig 的 npm 配置继承,支持包与子路径、exports/typesVersions 及数组继承;按真实 pnpm 来源解析并记录配置哈希。依赖保持只读,越界及无法确定规范来源的引用报告具体路径和原因,保留原任务重试。",
6
+ "en": "Fixed tsconfig inheritance from npm packages, including package subpaths, exports/typesVersions and ordered parent arrays. Resolve pnpm configurations from their actual locations and retain metadata hashes. Dependencies remain read-only; escaping or unrepresentable canonical references report their paths and reasons while preserving the original task for retry."
7
+ },
8
+ {
9
+ "version": "0.1.49",
10
+ "date": "2026-10-16",
11
+ "zh": "修复本地签名快照扫描大量源码时的内存耗尽:优先分析当前授权目标,为源码与语法树设置内存边界,并及时释放 Vue 解析缓存。未完整分析的引用明确保持未知,全仓文件哈希与变更核验继续保留;超限目标返回具体路径和限制,原 flow、范围与队列可保留后重试。",
12
+ "en": "Fixed memory exhaustion during local signed snapshots of large source repositories: inspect authorized targets first, bound source and syntax-tree retention, and release the Vue parser cache. Unresolved references remain explicitly unknown while whole-repository hashes and change verification are retained. Oversized targets report their exact path and limit so the original flow, scope, and queue can be preserved for retry."
13
+ },
2
14
  {
3
15
  "version": "0.1.48",
4
16
  "date": "2026-10-16",