@ran-sh/dsh-crew 2.0.14 → 2.0.15

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-crew",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Dispatch subtasks to DeepSeek Harness (DSH) agents as native subagents with live progress",
5
5
  "author": {
6
6
  "name": "ZSeven-W"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ran-sh/dsh-crew",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "type": "module",
5
5
  "main": "./src/hub/entry.mjs",
6
6
  "bin": {
@@ -36,6 +36,79 @@ const CLAUDE_SNAPSHOT_POLL_MS = 5_000;
36
36
  // record stand in for a missing user-scope snapshot, so a failed install read as
37
37
  // a current one.
38
38
  const CLAUDE_PLUGIN_SCOPE = 'user';
39
+ // What a module specifier can look like: a relative or absolute path, a package
40
+ // name, or a scoped one. Anything else the scan produces is the source text
41
+ // between two quotes, not a dependency.
42
+ const SPECIFIER_SHAPE = /^(?:(?:\.{1,2}\/|\/|[A-Za-z]:[\\/])[^\s"']*|(?:@[^/\s]+\/[^/\s]+|[A-Za-z][\w.-]*)(?:\/[^\s"']*)?)$/;
43
+
44
+ // A `/` starts a regular expression, not a division, when the last significant
45
+ // token could not end an expression. Getting this wrong desynchronises the scan:
46
+ // the quotes inside a regex were read as string delimiters, and everything after
47
+ // them — including real imports — was swallowed into a phantom literal.
48
+ const REGEX_MAY_FOLLOW = new Set([
49
+ '(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '=>',
50
+ 'return', 'typeof', 'case', 'in', 'of', 'new', 'delete', 'void', 'instanceof', 'do', 'else', 'yield', 'await',
51
+ ]);
52
+
53
+ /**
54
+ * Split module source into tokens: comments dropped, string and template literals
55
+ * kept whole, regular-expression literals kept whole, identifiers single, and
56
+ * everything else one character. Specifiers are then read from tokens rather than
57
+ * from the raw text, so a keyword inside a comment or a string is not a
58
+ * declaration and a quote inside a regex is not a string.
59
+ */
60
+ export function tokenizeModuleSource(source) {
61
+ const tokens = [];
62
+ const text = String(source ?? '');
63
+ let i = 0;
64
+ let previous = null;
65
+ const push = (token) => { tokens.push(token); previous = token; };
66
+ const scanQuoted = (quote) => {
67
+ let j = i + 1;
68
+ while (j < text.length) {
69
+ if (text[j] === '\\') { j += 2; continue; }
70
+ if (text[j] === quote) { j += 1; break; }
71
+ if (quote !== '`' && text[j] === '\n') break;
72
+ j += 1;
73
+ }
74
+ push(text.slice(i, j));
75
+ i = j;
76
+ };
77
+ const scanRegex = () => {
78
+ let j = i + 1;
79
+ let inClass = false;
80
+ while (j < text.length) {
81
+ const c = text[j];
82
+ if (c === '\\') { j += 2; continue; }
83
+ if (c === '\n') break;
84
+ if (inClass) { if (c === ']') inClass = false; }
85
+ else if (c === '[') inClass = true;
86
+ else if (c === '/') { j += 1; break; }
87
+ j += 1;
88
+ }
89
+ while (j < text.length && /[a-z]/i.test(text[j])) j += 1;
90
+ push(text.slice(i, j));
91
+ i = j;
92
+ };
93
+ while (i < text.length) {
94
+ const ch = text[i];
95
+ if (/\s/.test(ch)) { i += 1; continue; }
96
+ if (ch === '/' && text[i + 1] === '/') { const end = text.indexOf('\n', i); i = end === -1 ? text.length : end; continue; }
97
+ if (ch === '/' && text[i + 1] === '*') { const end = text.indexOf('*/', i + 2); i = end === -1 ? text.length : end + 2; continue; }
98
+ if (ch === '"' || ch === "'" || ch === '`') { scanQuoted(ch); continue; }
99
+ if (ch === '/' && (previous === null || REGEX_MAY_FOLLOW.has(previous))) { scanRegex(); continue; }
100
+ if (/[A-Za-z_$]/.test(ch)) {
101
+ let j = i + 1;
102
+ while (j < text.length && /[\w$]/.test(text[j])) j += 1;
103
+ push(text.slice(i, j));
104
+ i = j;
105
+ continue;
106
+ }
107
+ push(ch);
108
+ i += 1;
109
+ }
110
+ return tokens;
111
+ }
39
112
  const POLICY_START = '<!-- DSH CREW MANAGED POLICY:START -->';
40
113
  const POLICY_END = '<!-- DSH CREW MANAGED POLICY:END -->';
41
114
 
@@ -616,7 +689,7 @@ function claudeSnapshotResolvable(snapshotRoot) {
616
689
  const source = readFileSync(entry, 'utf8');
617
690
  const fromEntry = createRequire(entry);
618
691
  // Consume comments and literals as complete tokens before recognizing declarations.
619
- const tokens = (source.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`|[A-Za-z_$][\w$]*|[^\s]/g) ?? []).filter((token) => !token.startsWith('/*') && !token.startsWith('//'));
692
+ const tokens = tokenizeModuleSource(source);
620
693
  const specifiers = [];
621
694
  const literal = (token) => token && /^['"]/.test(token);
622
695
  for (let i = 0; i < tokens.length; i += 1) {
@@ -636,7 +709,14 @@ function claudeSnapshotResolvable(snapshotRoot) {
636
709
  if (tokens[j] === 'from' && literal(tokens[j + 1])) { specifiers.push(tokens[j + 1].slice(1, -1)); break; }
637
710
  }
638
711
  }
639
- return specifiers.every((specifier) => {
712
+ // The scan is lexical, and a regex literal holding a quote — `/^['"]/`, or
713
+ // the TOML matchers above — desynchronises it: the phantom string swallows
714
+ // text up to the next quote in the file, which can expose a keyword as a
715
+ // bare token and hand back the source between two of them as a "specifier".
716
+ // Those are not module specifiers, and resolving them read a working install
717
+ // as broken, so anything not shaped like a path or a package name is dropped.
718
+ const shaped = specifiers.filter((specifier) => isBuiltin(specifier) || SPECIFIER_SHAPE.test(specifier));
719
+ return shaped.every((specifier) => {
640
720
  if (isBuiltin(specifier)) return true;
641
721
  const target = fromEntry.resolve(specifier);
642
722
  if (!existsSync(target)) return false;
@@ -36,7 +36,7 @@ export {
36
36
  // included in the identity contract.
37
37
  const RUNTIME_ID = randomUUID();
38
38
 
39
- export const RUNTIME_VERSION = '2.0.14';
39
+ export const RUNTIME_VERSION = '2.0.15';
40
40
  export const HUB_PROTOCOL_VERSION = 1;
41
41
 
42
42
  export const HUB_CAPABILITIES = Object.freeze([