@entrorove/skill 0.1.49 → 0.1.51
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 +10 -0
- package/IDE-EXECUTION.md +3 -1
- package/SKILL.md +1 -1
- package/ide-command.mjs +48 -16
- package/local-scope-config-extends.mjs +71 -0
- package/local-scope-config-imports.mjs +37 -20
- package/local-scope-files.mjs +41 -6
- package/local-scope-repository.mjs +25 -10
- package/manifest.json +1 -1
- package/package.json +2 -1
- package/release-state.json +10 -9
- package/release.mjs +2 -0
- package/releases.json +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.51 — 2026-10-16
|
|
4
|
+
|
|
5
|
+
- 修复入口阶段将自身生成的带引号 capabilities/unlock 命令误判为原生 Bash;正常 TypeScript 候选探测遇到普通文件祖先时继续回退,真实访问仍严格校验。路径拒绝会报告请求路径、阻断祖先、解析地址和原因。
|
|
6
|
+
- Fixed entry guards misclassifying their own quoted capabilities/unlock commands as native shell. Read-only TypeScript probes skip nonexistent children beneath ordinary files and continue valid fallbacks; actual access remains strict. Rejections identify the requested path, blocking ancestor, resolved target and reason.
|
|
7
|
+
|
|
8
|
+
## 0.1.50 — 2026-10-16
|
|
9
|
+
|
|
10
|
+
- 修复 tsconfig 的 npm 配置继承,支持包与子路径、exports/typesVersions 及数组继承;按真实 pnpm 来源解析并记录配置哈希。依赖保持只读,越界及无法确定规范来源的引用报告具体路径和原因,保留原任务重试。
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
3
13
|
## 0.1.49 — 2026-10-16
|
|
4
14
|
|
|
5
15
|
- 修复本地签名快照扫描大量源码时的内存耗尽:优先分析当前授权目标,为源码与语法树设置内存边界,并及时释放 Vue 解析缓存。未完整分析的引用明确保持未知,全仓文件哈希与变更核验继续保留;超限目标返回具体路径和限制,原 flow、范围与队列可保留后重试。
|
package/IDE-EXECUTION.md
CHANGED
|
@@ -4,6 +4,8 @@ Register or sign in by email at https://entrorove.com. Enter the account key onc
|
|
|
4
4
|
|
|
5
5
|
If the IDE guard itself is the blocked step, run `entrorove unlock` directly from the affected IDE terminal. The command is offline and records a 15-minute lease bound to that IDE session; it does not call a hook, read a credential, accept the agreement, inspect the protocol or authorize project writes. The host session ID must be present in the environment or supplied as `ENTROROVE_SESSION`. Under the lease, `flow-cancel` and `flow-abandon` recover the original flow; successful `flow-abandon` releases its task binding in both the flow state and the IDE session record, even without a hook callback. Queued requests remain, and other IDE sessions stay guarded.
|
|
6
6
|
|
|
7
|
+
Capability discovery and offline unlock remain administrative calls during `entry`, before a flow exists. The guard recognizes its own rendered commands with quoted identity assignments, their order changed, or a literal `env` prefix containing only supported host/session identities; it rewrites approved calls to the installed runtime and current session. This does not authorize native scripts or unbound project writes.
|
|
8
|
+
|
|
7
9
|
To stop using Entrorove on this computer, run `entrorove uninstall` directly. It works offline, removes only the recorded Entrorove host hooks and pristine global skill, and leaves other host settings, credentials and task records in place. A hand-edited file is reported as preserved rather than deleted. This command does not inspect or change another project.
|
|
8
10
|
|
|
9
11
|
Every invocation rereads that same credential file, including clients already running. After an authenticated account page supplies the current key and `configure` synchronizes it once, all tasks under the same OS user automatically use the new value. A revoked or expired key cannot obtain new permissions for itself: return to the authenticated account page and synchronize once through JSON stdin. Status recovery only queries the original request; it never rotates credentials or repeats the operation.
|
|
@@ -64,7 +66,7 @@ The v2 progress sequence is durable and ordered: the first runtime step reports
|
|
|
64
66
|
|
|
65
67
|
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
68
|
|
|
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.
|
|
69
|
+
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`. Read-only candidate probes treat a child beneath an ordinary file as absent so normal TypeScript fallbacks continue; actual sources, control files and mutation targets retain strict validation. Rejected paths report the requested path, blocking ancestor, resolved target and reason, including broken or cyclic dependency links. 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
70
|
|
|
69
71
|
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
72
|
|
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.
|
|
8
|
+
Version: 0.1.51.
|
|
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
|
|
package/ide-command.mjs
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
|
|
3
|
+
function literalWords(header) {
|
|
4
|
+
const words = [];
|
|
5
|
+
let word = '', quote = null, started = false;
|
|
6
|
+
for (let index = 0; index < header.length; index++) {
|
|
7
|
+
const character = header[index];
|
|
8
|
+
if (quote) {
|
|
9
|
+
if (character === quote) quote = null;
|
|
10
|
+
else if (quote === '"' && character === '\\') {
|
|
11
|
+
if (!['"', '\\'].includes(header[index + 1])) return null;
|
|
12
|
+
word += header[++index];
|
|
13
|
+
} else word += character;
|
|
14
|
+
} else if (character === "'" || character === '"') {
|
|
15
|
+
quote = character; started = true;
|
|
16
|
+
} else if (character === '\\') {
|
|
17
|
+
if (!["'", '"', '\\', ' ', '\t'].includes(header[index + 1])) return null;
|
|
18
|
+
word += header[++index]; started = true;
|
|
19
|
+
} else if (/\s/u.test(character)) {
|
|
20
|
+
if (started) { words.push(word); word = ''; started = false; }
|
|
21
|
+
} else { word += character; started = true; }
|
|
22
|
+
}
|
|
23
|
+
if (quote) return null;
|
|
24
|
+
if (started) words.push(word);
|
|
25
|
+
return words.length ? words : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
// Deliberately accepts a small literal shell grammar. Unknown commands are not
|
|
4
29
|
// classified as reads: the host must reject them or use the approved runner.
|
|
5
30
|
export function literalCommand(command) {
|
|
@@ -14,30 +39,37 @@ export function literalCommand(command) {
|
|
|
14
39
|
header = here[1];
|
|
15
40
|
}
|
|
16
41
|
if (/[\n\r\0`$<>;&|]/u.test(header)) return null;
|
|
17
|
-
const words =
|
|
18
|
-
|
|
19
|
-
while ((match = pattern.exec(header))) {
|
|
20
|
-
if (match.index !== end) return null;
|
|
21
|
-
words.push(match[1] ?? match[2] ?? match[3]); end = pattern.lastIndex;
|
|
22
|
-
}
|
|
23
|
-
if (end !== header.length || !words.length) return null;
|
|
42
|
+
const words = literalWords(header);
|
|
43
|
+
if (!words) return null;
|
|
24
44
|
return { words, ...(input ? { input } : {}) };
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
function sdkWords(parsed) {
|
|
48
|
+
const words = [...parsed.words], seen = new Set();
|
|
49
|
+
const env = ['env', '/usr/bin/env', '/bin/env'].includes(words[0]);
|
|
50
|
+
if (env) words.shift();
|
|
51
|
+
while (words[0]?.includes('=')) {
|
|
52
|
+
const assignment = words.shift(), separator = assignment.indexOf('=');
|
|
53
|
+
const key = assignment.slice(0, separator), value = assignment.slice(separator + 1);
|
|
54
|
+
if (seen.has(key)) return null;
|
|
55
|
+
seen.add(key);
|
|
56
|
+
if (key === 'ENTROROVE_IDE') {
|
|
57
|
+
if (!/^[\p{L}\p{N} .+_-]{2,48}$/u.test(value)) return null;
|
|
58
|
+
} else if (!['ENTROROVE_SESSION', 'CODEX_THREAD_ID', 'CLAUDE_SESSION_ID'].includes(key)
|
|
59
|
+
|| !/^[A-Za-z0-9_-]{1,200}$/u.test(value)) return null;
|
|
60
|
+
}
|
|
61
|
+
return words.length && (!env || seen.size) ? words : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
27
64
|
export function sdkCommand(parsed, runtime) {
|
|
28
65
|
if (!parsed) return null;
|
|
29
|
-
const words =
|
|
30
|
-
if (words
|
|
31
|
-
if (!/^ENTROROVE_IDE=[\p{L}\p{N} .+_-]{2,48}$/u.test(words.shift())) return null;
|
|
32
|
-
while (['ENTROROVE_SESSION=', 'CODEX_THREAD_ID='].some(prefix => words[0]?.startsWith(prefix))) {
|
|
33
|
-
if (!/^[A-Z_]+=[A-Za-z0-9_-]{1,200}$/u.test(words.shift())) return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
66
|
+
const words = sdkWords(parsed);
|
|
67
|
+
if (!words) return null;
|
|
36
68
|
let args;
|
|
37
69
|
if ([runtime.node, 'node'].includes(words[0]) && words[1] === runtime.cli) args = words.slice(2);
|
|
38
70
|
else if (['npx', 'npx.cmd'].includes(path.basename(words[0]))) {
|
|
39
71
|
words.shift();
|
|
40
|
-
while (['--yes', '--prefer-online'].includes(words[0])) words.shift();
|
|
72
|
+
while (['--yes', '--prefer-online', '--registry=https://registry.npmjs.org'].includes(words[0])) words.shift();
|
|
41
73
|
if (!['@entrorove/skill', '@entrorove/skill@latest', `@entrorove/skill@${runtime.version}`].includes(words.shift())) return null;
|
|
42
74
|
args = words;
|
|
43
75
|
} else if (path.basename(words[0]) === 'entrorove') {
|
|
@@ -83,6 +115,6 @@ export function readCommand(parsed) {
|
|
|
83
115
|
|
|
84
116
|
export function renderSdkCommand(call, runtime, sessionId, host = process.env.ENTROROVE_IDE ?? 'Codex') {
|
|
85
117
|
const quote = value => `'${String(value).replace(/'/gu, "'\\''")}'`;
|
|
86
|
-
const header = `ENTROROVE_IDE=${host} ENTROROVE_SESSION=${quote(sessionId)} CODEX_THREAD_ID=${quote(sessionId)} ${[runtime.node, runtime.cli, ...call.args].map(quote).join(' ')}`;
|
|
118
|
+
const header = `ENTROROVE_IDE=${quote(host)} ENTROROVE_SESSION=${quote(sessionId)} CODEX_THREAD_ID=${quote(sessionId)} ${[runtime.node, runtime.cli, ...call.args].map(quote).join(' ')}`;
|
|
87
119
|
return call.input ? `${header} <<'ENTROROVE_INPUT'\n${JSON.stringify(call.input)}\nENTROROVE_INPUT` : header;
|
|
88
120
|
}
|
|
@@ -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
|
-
|
|
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 ? [
|
|
106
|
+
return resolution?.baseUrl !== undefined ? [configCandidate(resolution, specifier)] : [];
|
|
102
107
|
}
|
|
103
|
-
|
|
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
|
|
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(
|
|
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 = {
|
|
164
|
+
let inherited = {};
|
|
153
165
|
if (config.extends !== undefined) {
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
196
|
-
.map(relative => ({ path: relative, sha256: observed.get(relative).sha256 })) };
|
|
213
|
+
}, observations: observations() };
|
|
197
214
|
}
|
package/local-scope-files.mjs
CHANGED
|
@@ -36,6 +36,26 @@ async function assertDependencyReference(root, target, relative) {
|
|
|
36
36
|
}
|
|
37
37
|
return resolved;
|
|
38
38
|
}
|
|
39
|
+
async function invalidScopeTarget(root, relative, current, reason, cause) {
|
|
40
|
+
let resolved = current, resolutionError;
|
|
41
|
+
try { resolved = await fs.realpath(current); }
|
|
42
|
+
catch (error) {
|
|
43
|
+
resolutionError = error.code;
|
|
44
|
+
try { resolved = path.resolve(path.dirname(current), await fs.readlink(current)); }
|
|
45
|
+
catch { /* A non-directory ancestor is already its own blocking target. */ }
|
|
46
|
+
}
|
|
47
|
+
const blockingPath = path.relative(root, current).split(path.sep).join('/');
|
|
48
|
+
const explanation = {
|
|
49
|
+
'non-directory-ancestor': 'the ancestor is not a directory',
|
|
50
|
+
'symlink-not-allowed': 'strict scope access does not allow symlinks',
|
|
51
|
+
'unresolvable-symlink': 'the symlink cannot be resolved to an actual dependency target',
|
|
52
|
+
'not-regular-file': 'the source must be a regular file',
|
|
53
|
+
'unsupported-target-type': 'the target must be a regular file or directory',
|
|
54
|
+
}[reason];
|
|
55
|
+
const unavailable = cause || resolutionError;
|
|
56
|
+
throw Object.assign(new Error(`Scope path ${JSON.stringify(relative)} is blocked at ${JSON.stringify(blockingPath)} (${JSON.stringify(current)}), resolved to ${JSON.stringify(resolved)} because ${explanation}${unavailable ? ` (${unavailable})` : ''}`),
|
|
57
|
+
{ code: 'ENTROROVE_FILE_INVALID', reason, path: relative, blockingPath, resolvedPath: resolved });
|
|
58
|
+
}
|
|
39
59
|
export async function scopeTarget(root, relative, allowMissing = false, allowDependency = false) {
|
|
40
60
|
let current = root;
|
|
41
61
|
const parts = scopePath(relative).split('/');
|
|
@@ -48,17 +68,32 @@ export async function scopeTarget(root, relative, allowMissing = false, allowDep
|
|
|
48
68
|
throw error;
|
|
49
69
|
}
|
|
50
70
|
if (stat.isSymbolicLink()) {
|
|
51
|
-
if (!followDependency)
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
if (!followDependency) await invalidScopeTarget(root, relative, current, 'symlink-not-allowed');
|
|
72
|
+
try {
|
|
73
|
+
await assertDependencyReference(root, current, relative);
|
|
74
|
+
stat = await fs.stat(current);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (['ENOENT', 'ENOTDIR', 'ELOOP'].includes(error.code)) {
|
|
77
|
+
await invalidScopeTarget(root, relative, current, 'unresolvable-symlink', error.code);
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
54
81
|
}
|
|
55
|
-
if (index < parts.length - 1 && !stat.isDirectory())
|
|
82
|
+
if (index < parts.length - 1 && !stat.isDirectory()) await invalidScopeTarget(root, relative, current, 'non-directory-ancestor');
|
|
56
83
|
if (index === parts.length - 1) return { target: current, exists: true, stat, dependency: followDependency };
|
|
57
84
|
}
|
|
58
85
|
}
|
|
86
|
+
// Only speculative, read-only resolver candidates may treat a non-directory ancestor as absent.
|
|
87
|
+
export async function scopeProbeTarget(root, relative, allowDependency = false) {
|
|
88
|
+
try { return await scopeTarget(root, relative, true, allowDependency); }
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error.code !== 'ENTROROVE_FILE_INVALID' || error.reason !== 'non-directory-ancestor') throw error;
|
|
91
|
+
return { target: path.join(root, relative), exists: false, stat: null };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
59
94
|
async function safeScopeRead(root, relative, input, allowDependency = true) {
|
|
60
95
|
const dependency = allowDependency && dependencyReference(relative), target = await scopeTarget(root, relative, false, dependency);
|
|
61
|
-
if (!target.stat.isFile())
|
|
96
|
+
if (!target.stat.isFile()) await invalidScopeTarget(root, relative, target.target, 'not-regular-file');
|
|
62
97
|
if (target.stat.size > 16_777_216) scopeFail('ENTROROVE_SCOPE_LIMIT', 'scope source exceeds the 16 MiB inspection bound');
|
|
63
98
|
const handle = await fs.open(target.target, constants.O_RDONLY | (dependency ? 0 : (constants.O_NOFOLLOW ?? 0)));
|
|
64
99
|
try {
|
|
@@ -87,7 +122,7 @@ export async function scopeFiles(root, requested, knownFiles = []) {
|
|
|
87
122
|
const item = await scopeTarget(root, relative, known.has(relative));
|
|
88
123
|
if (!item.exists) { found.add(relative); return; }
|
|
89
124
|
if (item.stat.isFile()) { found.add(relative); return; }
|
|
90
|
-
if (!item.stat.isDirectory())
|
|
125
|
+
if (!item.stat.isDirectory()) await invalidScopeTarget(root, relative, item.target, 'unsupported-target-type');
|
|
91
126
|
for (const name of (await fs.readdir(item.target)).sort()) if (!excluded.has(name)) await visit(`${relative}/${name}`);
|
|
92
127
|
}
|
|
93
128
|
for (const relative of known) if (requested.some(target => target === '.' || target === relative || relative.startsWith(`${target}/`))) found.add(relative);
|
|
@@ -5,7 +5,7 @@ import { promisify } from 'node:util';
|
|
|
5
5
|
import { builtinModules } from 'node:module';
|
|
6
6
|
import ts from 'typescript';
|
|
7
7
|
import { parse as parseVue, parseCache as vueParseCache } from '@vue/compiler-sfc';
|
|
8
|
-
import { lineCount, observedScopeRisk, packageBoundary, scopeFail, scopeFiles, scopeRead, scopeTarget, stableScope } from './local-scope-files.mjs';
|
|
8
|
+
import { lineCount, observedScopeRisk, packageBoundary, scopeFail, scopeFiles, scopeProbeTarget, 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';
|
|
11
11
|
import { assertBudgetRunnerAllowed } from './local-budget-reads.mjs';
|
|
@@ -216,11 +216,32 @@ async function resolveImport(root, from, specifier, workspace, input, aliases, c
|
|
|
216
216
|
return resolveLocalImport(root, relative);
|
|
217
217
|
}
|
|
218
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 scopeProbeTarget(root, relative, 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
|
+
}
|
|
219
240
|
async function resolveLocalImport(root, relative, required = true) {
|
|
220
241
|
if (relative.startsWith('../') || relative === '..') scopeFail('ENTROROVE_PATH_UNSAFE', 'local import escapes the repository');
|
|
221
242
|
const replacements = /\.js$/.test(relative) ? [relative.replace(/\.js$/, '.ts'), relative.replace(/\.js$/, '.tsx')] : [];
|
|
222
243
|
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)]) {
|
|
223
|
-
const item = await
|
|
244
|
+
const item = await scopeProbeTarget(root, candidate, dependencyReference(candidate));
|
|
224
245
|
if (item.exists && item.stat.isFile()) return candidate;
|
|
225
246
|
}
|
|
226
247
|
if (required) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
|
|
@@ -248,10 +269,7 @@ export async function importGraph(root, originals, input = { repositoryRoot: roo
|
|
|
248
269
|
const contexts = (input.importContexts ?? []).filter(context => relative === context.sourcePrefix || relative.startsWith(`${context.sourcePrefix}/`))
|
|
249
270
|
.sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length);
|
|
250
271
|
const configPath = contexts[0]?.configPath;
|
|
251
|
-
const aliases = await scopeTsconfigImports(
|
|
252
|
-
const item = await scopeRead(root, target, input);
|
|
253
|
-
return { path: target, sha256: item.sha256, contentBase64: item.content.toString('base64') };
|
|
254
|
-
}, relative, configPath);
|
|
272
|
+
const aliases = await scopeTsconfigImports(target => configMetadata(root, target, input), relative, configPath);
|
|
255
273
|
for (const observed of aliases.observations) metadata.set(observed.path, observed);
|
|
256
274
|
const declarations = declarationModules(read.content, relative);
|
|
257
275
|
const imports = importSpecifiers(read.content, relative); dynamic ||= imports.dynamic;
|
|
@@ -275,10 +293,7 @@ export async function scopeCandidateImports(root, file, input = { repositoryRoot
|
|
|
275
293
|
const workspace = await workspaceIndex(root, input);
|
|
276
294
|
const context = (input.importContexts ?? []).filter(item => file.path === item.sourcePrefix || file.path.startsWith(`${item.sourcePrefix}/`))
|
|
277
295
|
.sort((a, b) => b.sourcePrefix.length - a.sourcePrefix.length)[0];
|
|
278
|
-
const aliases = await scopeTsconfigImports(
|
|
279
|
-
const read = await scopeRead(root, relative, input);
|
|
280
|
-
return { path: relative, sha256: read.sha256, contentBase64: read.content.toString('base64') };
|
|
281
|
-
}, file.path, context?.configPath);
|
|
296
|
+
const aliases = await scopeTsconfigImports(relative => configMetadata(root, relative, input), file.path, context?.configPath);
|
|
282
297
|
const declarations = declarationModules(Buffer.from(file.content), file.path), edges = [];
|
|
283
298
|
for (const specifier of parsed.specifiers) {
|
|
284
299
|
const target = await resolveImport(root, file.path, specifier, workspace, input, aliases, context?.configPath, declarations);
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entrorove/skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
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",
|
package/release-state.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "entrorove.release-state/1.0",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"digest": "
|
|
3
|
+
"version": "0.1.51",
|
|
4
|
+
"digest": "337302626679e547236186da4653bd37c5ed8aec25bd24999920488334426587",
|
|
5
5
|
"files": {
|
|
6
6
|
"ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
|
|
7
|
-
"ide-command.mjs": "
|
|
7
|
+
"ide-command.mjs": "86472c2f89c17e13a6fb0e41794add983400aac3e626891dbb3812e34320e229",
|
|
8
8
|
"ide-hook.mjs": "2f00e56de1e1149f117714d5dd334196f1a3f206c3bfab541393bc0f625e0108",
|
|
9
9
|
"ide-install.mjs": "10247efadcfeef5da001022d12080bd0f789e67fdf7862bcc65db73dda3bbe57",
|
|
10
10
|
"ide-session.mjs": "dc256e77b03303932d354768c0e79384340a3137e7421b4ae9c869f7e58e1a3a",
|
|
@@ -29,7 +29,7 @@
|
|
|
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": "
|
|
32
|
+
"IDE-EXECUTION.md": "2cb952bb919261e9e420c9d83659eba013e5b1b175d80da03b50b660edfe4f31",
|
|
33
33
|
"REUSE-DISCIPLINE.md": "5b214cf313e7f715f52d54e28093a2d038f0f196ca64b15d1c49587b9d8eecf8",
|
|
34
34
|
"reuse-discipline-release.mjs": "20ded9c21bd71b8779c454f5eba4c7c0a943ef99d94117b9bbef891d2ab6e3de",
|
|
35
35
|
"local-final-gate16.mjs": "083aec470670e6cdb7ad2ab27b7a51b53a9ec3a8b4121cb9cbfa17d9940892c2",
|
|
@@ -81,9 +81,9 @@
|
|
|
81
81
|
"CAPABILITY-MAP.md": "a0f0468a098725ced52a96dd8e22bfc7d1ce7681a8446ebf7dae3696c76c3385",
|
|
82
82
|
"AGREEMENT.md": "f93a68e92c457200dd024610e65f74e478b8f60860f951da818b70561f1b9bf8",
|
|
83
83
|
"DELIVERY-BOUNDARIES.md": "d1bb2895e23fc14bf1c76707bd1f6f03b39695501a78a0b7289dc37b099889dc",
|
|
84
|
-
"package.json": "
|
|
84
|
+
"package.json": "cdb1f686304d5d29d1feff1b1ec5b75e4e3dcff1f1da841dfb59d0502ab261e8",
|
|
85
85
|
"manifest.json": "ab06e307795bc9da5add926752b6c4dbdaf18625090261a035aa356a679c504f",
|
|
86
|
-
"release.mjs": "
|
|
86
|
+
"release.mjs": "e206cb6cabbb02542317bce283f7ba674621e67765a3eacef5741f2d6b1c58b2",
|
|
87
87
|
"local-cli.mjs": "11461e4cf4f6a8612ff275bf8296ec57ff22281156cb2c72834d3c764ba422a1",
|
|
88
88
|
"local-state-lock.mjs": "1dbf64264642a0851faf043e07ee9f66d2ad0508e9f799b0626246e42fae893e",
|
|
89
89
|
"local-execution.mjs": "46fddd78cd954fb080c660affe90cdaa716695580427336acb820089441a8025",
|
|
@@ -121,8 +121,8 @@
|
|
|
121
121
|
"local-stage-boundary.mjs": "84c3bb5beab29306274c5ca8a78f19a83ed664ae1b93a8372bb493158b304fe3",
|
|
122
122
|
"local-scope-candidate.mjs": "8f0bd9606e70d4a0ebacdd3cd600ec3afed85e2bb3e1760aa1144cf55d4e028a",
|
|
123
123
|
"local-scope-context.mjs": "79753abfc6c76089d17f003a1be537dfaea10d6731c542f7fc53f2a9996ed164",
|
|
124
|
-
"local-scope-files.mjs": "
|
|
125
|
-
"local-scope-repository.mjs": "
|
|
124
|
+
"local-scope-files.mjs": "4c14a5e226dd5518aeb37f199b1aa896943a66d41e5a1757a3b3fd8a79446d76",
|
|
125
|
+
"local-scope-repository.mjs": "5bdd92729faec9b30f70d57891d0779b3cbff78b8a6a21c98240cdb62f2109d1",
|
|
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": "
|
|
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.51",
|
|
4
|
+
"date": "2026-10-16",
|
|
5
|
+
"zh": "修复入口阶段将自身生成的带引号 capabilities/unlock 命令误判为原生 Bash;正常 TypeScript 候选探测遇到普通文件祖先时继续回退,真实访问仍严格校验。路径拒绝会报告请求路径、阻断祖先、解析地址和原因。",
|
|
6
|
+
"en": "Fixed entry guards misclassifying their own quoted capabilities/unlock commands as native shell. Read-only TypeScript probes skip nonexistent children beneath ordinary files and continue valid fallbacks; actual access remains strict. Rejections identify the requested path, blocking ancestor, resolved target and reason."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.1.50",
|
|
10
|
+
"date": "2026-10-16",
|
|
11
|
+
"zh": "修复 tsconfig 的 npm 配置继承,支持包与子路径、exports/typesVersions 及数组继承;按真实 pnpm 来源解析并记录配置哈希。依赖保持只读,越界及无法确定规范来源的引用报告具体路径和原因,保留原任务重试。",
|
|
12
|
+
"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."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.1.49",
|
|
4
16
|
"date": "2026-10-16",
|