@entrorove/skill 0.1.50 → 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 CHANGED
@@ -1,5 +1,10 @@
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
+
3
8
  ## 0.1.50 — 2026-10-16
4
9
 
5
10
  - 修复 tsconfig 的 npm 配置继承,支持包与子路径、exports/typesVersions 及数组继承;按真实 pnpm 来源解析并记录配置哈希。依赖保持只读,越界及无法确定规范来源的引用报告具体路径和原因,保留原任务重试。
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 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.
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.50.
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 = [], pattern = /\s*(?:'([^']*)'|"([^"\\]*)"|([^\s'"\\]+))/gu;
18
- let match, end = 0;
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 = [...parsed.words];
30
- if (words[0]?.startsWith('ENTROROVE_IDE=')) {
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
  }
@@ -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) scopeFail('ENTROROVE_FILE_INVALID', 'scope refuses symlink or non-directory ancestors');
52
- await assertDependencyReference(root, current, relative);
53
- stat = await fs.stat(current);
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()) scopeFail('ENTROROVE_FILE_INVALID', 'scope refuses symlink or non-directory ancestors');
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()) scopeFail('ENTROROVE_FILE_INVALID', 'scope source must be a regular file');
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()) scopeFail('ENTROROVE_FILE_INVALID', 'scope target must be a regular file or directory');
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';
@@ -228,7 +228,7 @@ async function externalConfigReference(root, relative, resolved) {
228
228
  return boundary;
229
229
  }
230
230
  async function configMetadata(root, relative, input) {
231
- const target = await scopeTarget(root, relative, true, dependencyReference(relative));
231
+ const target = await scopeProbeTarget(root, relative, dependencyReference(relative));
232
232
  if (!target.exists || !target.stat.isFile()) throw Object.assign(new Error(`Missing configuration file: ${relative}`), { code: 'ENOENT' });
233
233
  const read = await scopeRead(root, relative, input), resolved = await fs.realpath(target.target);
234
234
  const physical = path.relative(root, resolved).split(path.sep).join('/');
@@ -241,7 +241,7 @@ async function resolveLocalImport(root, relative, required = true) {
241
241
  if (relative.startsWith('../') || relative === '..') scopeFail('ENTROROVE_PATH_UNSAFE', 'local import escapes the repository');
242
242
  const replacements = /\.js$/.test(relative) ? [relative.replace(/\.js$/, '.ts'), relative.replace(/\.js$/, '.tsx')] : [];
243
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)]) {
244
- const item = await scopeTarget(root, candidate, true, dependencyReference(candidate));
244
+ const item = await scopeProbeTarget(root, candidate, dependencyReference(candidate));
245
245
  if (item.exists && item.stat.isFile()) return candidate;
246
246
  }
247
247
  if (required) scopeFail('ENTROROVE_IMPORT_UNRESOLVED', 'a local import has no resolvable source file');
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.50",
4
+ "version": "0.1.51",
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.50",
3
+ "version": "0.1.51",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "entrorove": "cli.mjs"
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schemaVersion": "entrorove.release-state/1.0",
3
- "version": "0.1.50",
4
- "digest": "c749866399a99cf122f71c00dca31cf49706a76f0998b7f3197d0763968468b5",
3
+ "version": "0.1.51",
4
+ "digest": "337302626679e547236186da4653bd37c5ed8aec25bd24999920488334426587",
5
5
  "files": {
6
6
  "ide-code-mode.mjs": "f3f53357ba09727b3b597851976a24de93287c0faca5052448277d0904bb61f7",
7
- "ide-command.mjs": "dcd4b48305cc4f106e5e4509e794e4b46ef02de29afa9cccbbc2774308c81ff3",
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": "4e76abb3b2e357de07665d7422204ce710af3cafc0ef946816de73bf1a8998e6",
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",
@@ -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": "96c301f2a45e5fa22f0b11fb15e5e564f118e53187fc7749263c78d5ed87b056",
125
- "local-scope-repository.mjs": "56d7874326de2f90e47cf13d6b9839372044ea70ab15fbb7f7125bd13a87a087",
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",
package/releases.json CHANGED
@@ -1,4 +1,10 @@
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
+ },
2
8
  {
3
9
  "version": "0.1.50",
4
10
  "date": "2026-10-16",