@opengsd/gsd-core 1.20.0 → 1.21.0

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.
Files changed (154) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +33 -2
  4. package/agents/gsd-executor.md +1 -1
  5. package/agents/gsd-plan-checker.md +1 -1
  6. package/agents/gsd-planner.md +3 -3
  7. package/agents/gsd-verifier.md +2 -2
  8. package/bin/install.js +265 -161
  9. package/commands/gsd/autonomous.md +1 -1
  10. package/commands/gsd/new-milestone.md +2 -2
  11. package/commands/gsd/new-project.md +2 -2
  12. package/gsd-core/bin/gsd-tools.cjs +554 -823
  13. package/gsd-core/bin/lib/capability-activation.cjs +8 -2
  14. package/gsd-core/bin/lib/capability-command-router.cjs +6 -2
  15. package/gsd-core/bin/lib/capability-ledger.cjs +1 -1
  16. package/gsd-core/bin/lib/capability-lock.cjs +19 -27
  17. package/gsd-core/bin/lib/capability-registry.cjs +74 -71
  18. package/gsd-core/bin/lib/capability-validator.cjs +1 -0
  19. package/gsd-core/bin/lib/capability-writer.cjs +7 -4
  20. package/gsd-core/bin/lib/claude-orchestration-command-router.cjs +142 -62
  21. package/gsd-core/bin/lib/claude-orchestration.cjs +214 -122
  22. package/gsd-core/bin/lib/cli-surface.cjs +545 -0
  23. package/gsd-core/bin/lib/command-arg-projection.cjs +356 -103
  24. package/gsd-core/bin/lib/commands.cjs +99 -2
  25. package/gsd-core/bin/lib/config-file-read.cjs +85 -0
  26. package/gsd-core/bin/lib/config-loader.cjs +23 -15
  27. package/gsd-core/bin/lib/config.cjs +139 -101
  28. package/gsd-core/bin/lib/configuration.cjs +41 -2
  29. package/gsd-core/bin/lib/criterion-baseline.cjs +346 -0
  30. package/gsd-core/bin/lib/criterion-replay.cjs +380 -0
  31. package/gsd-core/bin/lib/estimate-cli.cjs +5 -1
  32. package/gsd-core/bin/lib/gap-checker.cjs +12 -1
  33. package/gsd-core/bin/lib/gate-evaluation-scope.cjs +593 -27
  34. package/gsd-core/bin/lib/gate-phase-context.cjs +16 -4
  35. package/gsd-core/bin/lib/graphify.cjs +19 -2
  36. package/gsd-core/bin/lib/gsd-tool-dir.cjs +134 -0
  37. package/gsd-core/bin/lib/health-diagnostic.cjs +5 -1
  38. package/gsd-core/bin/lib/init.cjs +20 -18
  39. package/gsd-core/bin/lib/install-effort-resolver.cjs +24 -4
  40. package/gsd-core/bin/lib/install-engine.cjs +20 -28
  41. package/gsd-core/bin/lib/install-fs-adapter.cjs +5 -1
  42. package/gsd-core/bin/lib/install-manifest-record.cjs +86 -0
  43. package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -3
  44. package/gsd-core/bin/lib/install-profiles.cjs +4 -2
  45. package/gsd-core/bin/lib/io.cjs +6 -0
  46. package/gsd-core/bin/lib/markdown-sectionizer.cjs +62 -31
  47. package/gsd-core/bin/lib/markdown-table.cjs +39 -1
  48. package/gsd-core/bin/lib/milestone.cjs +8 -6
  49. package/gsd-core/bin/lib/model-resolver.cjs +8 -4
  50. package/gsd-core/bin/lib/phase-entry-grammar.cjs +14 -0
  51. package/gsd-core/bin/lib/phase-id.cjs +26 -9
  52. package/gsd-core/bin/lib/phase-locator.cjs +8 -3
  53. package/gsd-core/bin/lib/phase.cjs +170 -45
  54. package/gsd-core/bin/lib/plan-dependency-graph.cjs +46 -23
  55. package/gsd-core/bin/lib/planning-snapshot.cjs +10 -6
  56. package/gsd-core/bin/lib/planning-workspace.cjs +20 -10
  57. package/gsd-core/bin/lib/profile-output.cjs +9 -6
  58. package/gsd-core/bin/lib/project-context.cjs +439 -0
  59. package/gsd-core/bin/lib/project-root.cjs +9 -230
  60. package/gsd-core/bin/lib/quick-batch-dispatch.cjs +1 -1
  61. package/gsd-core/bin/lib/report-parser.cjs +16 -0
  62. package/gsd-core/bin/lib/roadmap-parser.cjs +357 -50
  63. package/gsd-core/bin/lib/roadmap-upgrade.cjs +24 -5
  64. package/gsd-core/bin/lib/roadmap.cjs +2 -2
  65. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +317 -134
  66. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +81 -3
  67. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +6 -0
  68. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +279 -8
  69. package/gsd-core/bin/lib/runtime-slash.cjs +7 -2
  70. package/gsd-core/bin/lib/shell-command-projection.cjs +1010 -10
  71. package/gsd-core/bin/lib/stale-bake-guard.cjs +4 -1
  72. package/gsd-core/bin/lib/state-transition.cjs +103 -46
  73. package/gsd-core/bin/lib/state.cjs +448 -63
  74. package/gsd-core/bin/lib/surface.cjs +4 -7
  75. package/gsd-core/bin/lib/tdd-red-evidence.cjs +10 -3
  76. package/gsd-core/bin/lib/verify-command-router.cjs +6 -1
  77. package/gsd-core/bin/lib/verify.cjs +49 -6
  78. package/gsd-core/bin/lib/workstream-inventory.cjs +4 -4
  79. package/gsd-core/bin/lib/worktree-base-ref.cjs +32 -2
  80. package/gsd-core/bin/lib/worktree-safety.cjs +8 -111
  81. package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
  82. package/gsd-core/references/autonomous-smart-discuss.md +99 -18
  83. package/gsd-core/references/checkpoints.md +1 -1
  84. package/gsd-core/references/planner-guidance.md +11 -0
  85. package/gsd-core/references/planner-human-verify-mode.md +1 -1
  86. package/gsd-core/references/planner-reversibility.md +2 -0
  87. package/gsd-core/references/planner-verify-command-grounding.md +22 -6
  88. package/gsd-core/workflows/autonomous.md +4 -4
  89. package/gsd-core/workflows/code-review-fix.md +5 -7
  90. package/gsd-core/workflows/execute-phase.md +21 -7
  91. package/gsd-core/workflows/plan-phase/steps/plan-pre-dispatch.md +101 -0
  92. package/gsd-core/workflows/plan-phase.md +18 -19
  93. package/hooks/dist/gsd-agent-isolation-guard.js +4 -3
  94. package/hooks/dist/gsd-config-reload.js +2 -2
  95. package/hooks/dist/gsd-context-monitor.js +2 -1
  96. package/hooks/dist/gsd-cursor-subagent-start.js +9 -6
  97. package/hooks/dist/gsd-read-injection-scanner.js +2 -2
  98. package/hooks/dist/gsd-secret-read-guard.js +141 -6
  99. package/hooks/dist/gsd-statusline.js +3 -2
  100. package/hooks/dist/gsd-workflow-guard.js +2 -1
  101. package/hooks/dist/lib/config-read.js +49 -0
  102. package/hooks/dist/lib/git-cmd.js +3 -1
  103. package/hooks/dist/lib/git-head-tree.js +474 -0
  104. package/hooks/dist/lib/isolation-sentinel.js +168 -13
  105. package/hooks/dist/lib/secret-read-exemptions.js +161 -0
  106. package/hooks/gsd-agent-isolation-guard.js +4 -3
  107. package/hooks/gsd-config-reload.js +2 -2
  108. package/hooks/gsd-context-monitor.js +2 -1
  109. package/hooks/gsd-cursor-subagent-start.js +9 -6
  110. package/hooks/gsd-read-injection-scanner.js +2 -2
  111. package/hooks/gsd-secret-read-guard.js +141 -6
  112. package/hooks/gsd-statusline.js +3 -2
  113. package/hooks/gsd-workflow-guard.js +2 -1
  114. package/hooks/lib/config-read.js +49 -0
  115. package/hooks/lib/git-cmd.js +3 -1
  116. package/hooks/lib/git-head-tree.js +474 -0
  117. package/hooks/lib/isolation-sentinel.js +168 -13
  118. package/hooks/lib/secret-read-exemptions.js +161 -0
  119. package/package.json +7 -2
  120. package/pi/package-agents/gsd-executor.md +1 -1
  121. package/pi/package-agents/gsd-plan-checker.md +1 -1
  122. package/pi/package-agents/gsd-planner.md +3 -3
  123. package/pi/package-agents/gsd-verifier.md +2 -2
  124. package/pi/package-agents/manifest.json +4 -4
  125. package/pi/package-skills/gsd-autonomous/SKILL.md +1 -1
  126. package/pi/package-skills/gsd-new-milestone/SKILL.md +1 -1
  127. package/pi/package-skills/gsd-new-project/SKILL.md +1 -1
  128. package/scripts/changeset/cli.cjs +2 -2
  129. package/scripts/changeset/lint.cjs +28 -3
  130. package/scripts/changeset/serialize.cjs +12 -2
  131. package/scripts/ci-compact-v8-coverage.cjs +204 -0
  132. package/scripts/ci-fix-next-rerun.cjs +149 -0
  133. package/scripts/ci-next-health.cjs +52 -9
  134. package/scripts/ci-test-scope.cjs +77 -71
  135. package/scripts/command-contract-helpers.cjs +56 -7
  136. package/scripts/docs-guard-registry.cjs +3 -0
  137. package/scripts/gen-cli-surface-manifest.cjs +287 -0
  138. package/scripts/gen-platform-conformance-tier.cjs +258 -19
  139. package/scripts/lib/macos-conformance-tier.generated.cjs +59 -47
  140. package/scripts/lib/platform-conformance-tier.generated.cjs +23 -64
  141. package/scripts/lib/shellcheck-fetch.cjs +71 -4
  142. package/scripts/lint-removed-but-needed.cjs +562 -50
  143. package/scripts/merge-commit-pr.cjs +186 -0
  144. package/scripts/release-ancestry.cjs +86 -0
  145. package/scripts/release-notes/changelog-release-notes.cjs +345 -0
  146. package/scripts/release-notes/discord-release-summary.cjs +82 -1
  147. package/scripts/release-pipeline-pr.cjs +92 -0
  148. package/scripts/require-issue-link-policy.cjs +18 -9
  149. package/scripts/sync-next-version.cjs +11 -2
  150. package/scripts/timeout-evidence-from-ci.cjs +964 -0
  151. package/skills/gsd-autonomous/SKILL.md +1 -1
  152. package/skills/gsd-new-milestone/SKILL.md +1 -1
  153. package/skills/gsd-new-project/SKILL.md +1 -1
  154. package/vscode/package.json +1 -1
@@ -139,11 +139,62 @@ const MACOS_GENERATED_HEADER =
139
139
  '// Windows-oriented tier in platform-conformance-tier.generated.cjs — see\n' +
140
140
  '// docs/adr/4593-macos-conformance-tier-architecture.md for the full rationale.\n';
141
141
 
142
+ // #6002 child_process call-site shapes. First arguments that are node itself:
143
+ // `process.execPath` or the literal 'node' / "node".
144
+ const NODE_FIRST_ARG = "(?:process\\.execPath\\s*[,)]|(['\"])node\\1\\s*[,)])";
145
+ const NOT_NODE_FIRST_ARG = '(?!' + NODE_FIRST_ARG + ')';
146
+ // One source of truth for the call names every shape below matches.
147
+ const CHILD_SYNC_NAMES = 'spawnSync|execSync|execFileSync';
148
+ const CHILD_ASYNC_NAMES = 'spawn|execFile|exec';
149
+ const CHILD_CALL_NAMES = CHILD_SYNC_NAMES + '|' + CHILD_ASYNC_NAMES;
150
+ const CHILD_NON_NODE_CALL = new RegExp(
151
+ '(?:\\b(?:' + CHILD_SYNC_NAMES + ')|(?<![.\\w$])(?:' + CHILD_ASYNC_NAMES + '))\\(\\s*' + NOT_NODE_FIRST_ARG,
152
+ );
153
+ // Member form `<ident>.spawn(...)` (e.g. `cp.spawn('bash')`, `child_process.execFile('git')`).
154
+ // Heuristic: any `<ident>.(spawn|spawnSync|execFile|execFileSync|exec|execSync)(` whose first
155
+ // argument is not node counts as a non-node call, EXCEPT a regex `.exec(` — the receiver is a
156
+ // regex literal (preceded by `/`) or an identifier ending in re/Re/RE/regex/Regex/REGEX.
157
+ const CHILD_MEMBER_CALL = new RegExp(
158
+ '([\\w$]+)\\.(?:' + CHILD_CALL_NAMES + ')\\(\\s*' + NOT_NODE_FIRST_ARG,
159
+ 'g',
160
+ );
161
+ const REGEX_RECEIVER_NAME = /(?:re|Re|RE|regex|Regex|REGEX)$/;
162
+ function hasMemberNonNodeCall(content) {
163
+ CHILD_MEMBER_CALL.lastIndex = 0;
164
+ let m;
165
+ while ((m = CHILD_MEMBER_CALL.exec(content)) !== null) {
166
+ if (content[m.index - 1] === '/') continue; // regex-literal receiver: /x/g.exec(
167
+ if (/\.exec\(/.test(m[0]) && REGEX_RECEIVER_NAME.test(m[1])) continue;
168
+ return true;
169
+ }
170
+ return false;
171
+ }
172
+ // `promisify(cp.execFile)` hides the call sites from the call-shape scan: an unidentifiable,
173
+ // counted site.
174
+ const CHILD_PROMISIFIED = /\bpromisify\(\s*(?:[\w$]+\.)?(?:execFile|exec|spawn)\b/;
175
+ const CHILD_NODE_ONLY_CALL = new RegExp(
176
+ '\\b(?:' + CHILD_CALL_NAMES + ')\\(\\s*' + NODE_FIRST_ARG + '|(?<![.\\w$])fork\\(',
177
+ );
178
+ // Code constructs only: a bare `lock` word matches prose, so lock-ness is a lockfile/lock-helper
179
+ // identifier, a `.lock` path suffix, a rename/unlink call, EBUSY/EPERM, the installer or --config-dir.
180
+ const FS_LOCK_OR_INSTALL =
181
+ /\b(?:lockfile|acquireLock|releaseLock|withLock|lockSync|proper-lockfile)\b|\.lock\b|\b(?:rename|unlink)(?:Sync)?\(|\bE(?:BUSY|PERM)\b|install\.js|--config-dir/;
182
+ const CHILD_SHELL_TRUE = /\bshell\s*:\s*true\b/;
183
+ // Pre-#6002 raw-child-process test, kept for source-file reachability
184
+ // (scripts/ci-test-scope.cjs), whose behavior must not change.
185
+ function preIssue6002RawChildProcess(content) {
186
+ if (/require\((['"])(?:node:)?child_process\1\)/.test(content)) return true;
187
+ if (!content.includes('child_process')) return false;
188
+ return /\bspawnSync\(|\bexecSync\(|\bexecFileSync\(/.test(content);
189
+ }
190
+
142
191
  /**
143
192
  * Detection categories (#4591 design doc). Each entry's `test` receives the
144
193
  * raw file content string and returns true when that category's signal is
145
194
  * present. Order matches the design doc's enumeration; `signals` in
146
- * `classifyContent`'s return value preserves this order.
195
+ * `classifyContent`'s return value preserves this order. Optional fields
196
+ * (the pre-#6002 view used by `classifyUnstrippedContent`): `raw: <fn>`
197
+ * replaces `test` there; `rawSkip: true` omits the category there.
147
198
  */
148
199
  const CATEGORIES = [
149
200
  {
@@ -172,16 +223,43 @@ const CATEGORIES = [
172
223
  },
173
224
  {
174
225
  name: 'raw-child-process',
175
- // Requires BOTH the child_process import/reference token AND one of the
176
- // three call names in the same file — this is what keeps a same-named
177
- // local identifier (e.g. a variable called `spawnResult`) from false-
178
- // positiving: `spawnResult` never forms the substring `spawnSync(`.
226
+ raw: preIssue6002RawChildProcess, // pre-#6002 test used by classifyUnstrippedContent
227
+ // #6002: matches a call that can spawn a platform-dependent binary, not
228
+ // the mere presence of the module. Requires the child_process token AND a
229
+ // sync or async spawn call (spawnSync/execSync/execFileSync/spawn/execFile/
230
+ // exec) whose first argument is NOT node itself (`process.execPath` or the
231
+ // literal 'node' / "node"): a child that only re-runs node behaves
232
+ // identically on every OS and is no real-OS signal. `fork(` always runs
233
+ // node, so it is node-only by construction and never a signal on its own.
234
+ // Any other first argument (another literal, an identifier, a template
235
+ // literal) matches (over-inclusion, the safe direction), as does
236
+ // `shell: true` anywhere in a child_process-using file. A file that
237
+ // requires child_process but whose call sites cannot be identified at all
238
+ // (aliased or destructured-and-renamed usage) also counts. A same-named
239
+ // local identifier (e.g. `spawnResult`) never forms the substring
240
+ // `spawnSync(`; bare `.exec(` member calls (e.g. `re.exec(`) are not
241
+ // spawn call sites.
179
242
  test: (content) => {
180
- if (/require\((['"])(?:node:)?child_process\1\)/.test(content)) return true;
181
243
  if (!content.includes('child_process')) return false;
182
- return /\bspawnSync\(|\bexecSync\(|\bexecFileSync\(/.test(content);
244
+ if (CHILD_SHELL_TRUE.test(content)) return true;
245
+ if (CHILD_NON_NODE_CALL.test(content)) return true;
246
+ if (hasMemberNonNodeCall(content)) return true;
247
+ if (CHILD_PROMISIFIED.test(content)) return true;
248
+ return !CHILD_NODE_ONLY_CALL.test(content);
183
249
  },
184
250
  },
251
+ {
252
+ name: 'fs-lock-or-install-child',
253
+ rawSkip: true, // did not exist pre-#6002; classifyUnstrippedContent omits it
254
+ // #6002 decision: a node-only child still counts when the file also
255
+ // exercises Windows-sensitive filesystem behavior (locking, rename/unlink
256
+ // semantics, EBUSY/EPERM, the installer, --config-dir), because the child
257
+ // is then driving that behavior in a real process.
258
+ test: (content) =>
259
+ content.includes('child_process') &&
260
+ CHILD_NODE_ONLY_CALL.test(content) &&
261
+ FS_LOCK_OR_INSTALL.test(content),
262
+ },
185
263
  {
186
264
  name: 'shell-interpreter-spawn',
187
265
  // Added after an adversarial review (#4641) caught a REAL false negative
@@ -259,15 +337,8 @@ const NOISY_FOR_SOURCE_REACHABILITY = new Set(['symlink-keyword']);
259
337
  * expressible as content; reach for this map only when it structurally is
260
338
  * not.
261
339
  *
262
- * Current entries:
263
- * - tests/external-descriptor-confinement.test.cjs: exercises `isPathConfined`
264
- * (src/external-descriptor-trust.cts:41-49), which calls the AMBIENT
265
- * `path` module directly — `path.resolve(root, target)` and `path.sep` —
266
- * with no platform/path injection seam. Its win32 semantics (drive
267
- * letters, UNC paths, `\` separator) are therefore only reachable by
268
- * actually running on Windows; the win32 branch is unreachable on Linux.
269
- * This is a security-relevant write-confinement gate, so a silent gap
270
- * here is a security regression, not a coverage nit (#4641).
340
+ * Entries and their reasons live in the Map below (the single source); each
341
+ * reason is a required value, so none can be added without one.
271
342
  */
272
343
  const ALWAYS_REAL_OS = new Map([
273
344
  [
@@ -276,6 +347,13 @@ const ALWAYS_REAL_OS = new Map([
276
347
  'path module (path.resolve/path.sep) with no platform injection; its win32 branch (drive ' +
277
348
  'letters, UNC paths, \\ separator) is unreachable on Linux. Security-relevant write-confinement gate.',
278
349
  ],
350
+ [
351
+ 'tests/state-todos-render.test.cjs',
352
+ 'Exercises renderPendingTodosMarkdown (src/init.cts:2646-2650), which uses the ambient path module ' +
353
+ '(path.isAbsolute/path.relative) with no platform injection to build repo-relative todo links ' +
354
+ '(#4384/#4421); its win32 branch (drive letters, \\ separator) is unreachable on Linux. The test ' +
355
+ "file's only content signal was a node-only spawnSync, which #6002 deliberately ignores.",
356
+ ],
279
357
  ]);
280
358
 
281
359
  /**
@@ -301,18 +379,157 @@ const MACOS_CATEGORIES = [
301
379
  { name: 'case-sensitivity', test: (content) => /case.?insensitiv|case.?sensitiv/i.test(content) },
302
380
  { name: 'chmod-mode-bit', test: (content) => /chmodSync|chmod\(/.test(content) || /0o[0-7]{3,4}\b/.test(content) },
303
381
  { name: 'symlink-keyword', test: (content) => /\bsymlink/i.test(content) },
382
+ // #6002: realpath calls normalize the macOS /var -> /private/var tmpdir symlink.
383
+ { name: 'realpath', test: (content) => /\brealpath(Sync)?\b/.test(content) },
304
384
  ];
305
385
 
386
+ /** Keywords after which a `/` begins a regex literal rather than a division. */
387
+ const REGEX_AFTER_KEYWORD = new Set([
388
+ 'return', 'typeof', 'case', 'in', 'of', 'delete', 'void', 'throw', 'new', 'else', 'do', 'instanceof', 'yield', 'await',
389
+ ]);
390
+ const REGEX_AFTER_CHAR = '(,=:[!&|?{};+-*%<>~^';
391
+
392
+ function regexAllowedAfter(out) {
393
+ let j = out.length - 1;
394
+ while (j >= 0 && /\s/.test(out[j])) j--;
395
+ if (j < 0) return true;
396
+ if (REGEX_AFTER_CHAR.includes(out[j])) return true;
397
+ if (/[A-Za-z0-9_$]/.test(out[j])) {
398
+ let k = j;
399
+ while (k >= 0 && /[A-Za-z0-9_$]/.test(out[k])) k--;
400
+ return REGEX_AFTER_KEYWORD.has(out.slice(k + 1, j + 1));
401
+ }
402
+ return false;
403
+ }
404
+
405
+ /**
406
+ * #6002 — remove `//` line comments and block comments from source text so
407
+ * the CATEGORIES / MACOS_CATEGORIES tests match platform BEHAVIOR in code, not
408
+ * words in prose. A single-pass scanner tracks whether it is inside a '…', "…"
409
+ * or `…` string literal (honoring backslash escapes), so a `//` inside a
410
+ * string (e.g. a URL) does not start a comment. A `/` is read as the start of
411
+ * a regex literal (scanned to its closing unescaped `/`, honoring `[...]`
412
+ * classes and escapes) when the previous significant character is one of
413
+ * `( , = : [ ! & | ? { } ; + - * % < > ~ ^`, the start of input, or a keyword
414
+ * such as return/typeof; the regex body is kept verbatim so quotes or `//`
415
+ * inside it cannot corrupt string or comment tracking. An unterminated regex
416
+ * (newline first) falls back to a plain `/`. '…' and "…" strings end at a
417
+ * newline so an unbalanced quote cannot swallow the rest of the file. A line
418
+ * comment is dropped up to (not including) its newline; a block comment
419
+ * becomes one space (its newlines are kept) so adjacent tokens never fuse.
420
+ *
421
+ * @param {string} content
422
+ * @returns {string}
423
+ */
424
+ function stripComments(content) {
425
+ const src = typeof content === 'string' ? content : '';
426
+ const n = src.length;
427
+ let out = '';
428
+ let i = 0;
429
+
430
+ // Scans code from `i`. When `inExpr` (inside a template `${ … }`), returns at the
431
+ // matching unnested `}` (not consumed); otherwise runs to end of input.
432
+ function scanCode(inExpr) {
433
+ let depth = 0;
434
+ while (i < n) {
435
+ const ch = src[i];
436
+ const next = src[i + 1];
437
+ if (inExpr && ch === '}' && depth === 0) return;
438
+ if (inExpr && ch === '{') depth++;
439
+ else if (inExpr && ch === '}') depth--;
440
+ if (ch === '/' && next === '/') {
441
+ while (i < n && src[i] !== '\n') i++;
442
+ } else if (ch === '/' && next === '*') {
443
+ i += 2;
444
+ let newlines = '';
445
+ while (i < n && !(src[i] === '*' && src[i + 1] === '/')) {
446
+ if (src[i] === '\n') newlines += '\n';
447
+ i++;
448
+ }
449
+ i += 2;
450
+ out += ' ' + newlines;
451
+ } else if (ch === '/' && regexAllowedAfter(out)) {
452
+ let j = i + 1;
453
+ let inClass = false;
454
+ let closed = false;
455
+ while (j < n && src[j] !== '\n') {
456
+ const c = src[j];
457
+ if (c === '\\') {
458
+ j += 2;
459
+ continue;
460
+ }
461
+ if (c === '[') inClass = true;
462
+ else if (c === ']') inClass = false;
463
+ else if (c === '/' && !inClass) {
464
+ closed = true;
465
+ break;
466
+ }
467
+ j++;
468
+ }
469
+ if (closed) {
470
+ out += src.slice(i, j + 1);
471
+ i = j + 1;
472
+ } else {
473
+ out += ch;
474
+ i++;
475
+ }
476
+ } else if (ch === '`') {
477
+ out += ch;
478
+ i++;
479
+ while (i < n) {
480
+ const c = src[i];
481
+ if (c === '\\' && i + 1 < n) {
482
+ out += c + src[i + 1];
483
+ i += 2;
484
+ continue;
485
+ }
486
+ if (c === '$' && src[i + 1] === '{') {
487
+ out += '${';
488
+ i += 2;
489
+ scanCode(true);
490
+ continue;
491
+ }
492
+ out += c;
493
+ i++;
494
+ if (c === '`') break;
495
+ }
496
+ } else if (ch === "'" || ch === '"') {
497
+ out += ch;
498
+ i++;
499
+ while (i < n) {
500
+ const c = src[i];
501
+ if (c === '\\' && i + 1 < n) {
502
+ out += c + src[i + 1];
503
+ i += 2;
504
+ continue;
505
+ }
506
+ if (c === '\n') break;
507
+ out += c;
508
+ i++;
509
+ if (c === ch) break;
510
+ }
511
+ } else {
512
+ out += ch;
513
+ i++;
514
+ }
515
+ }
516
+ }
517
+
518
+ scanCode(false);
519
+ return out;
520
+ }
521
+
306
522
  /**
307
523
  * Pure classifier: given a test file's raw string content, returns which
308
524
  * platform-conformance categories matched and whether the file needs real-OS
309
- * coverage (true iff at least one category matched).
525
+ * coverage (true iff at least one category matched). Comments are stripped
526
+ * first (#6002).
310
527
  *
311
528
  * @param {string} content
312
529
  * @returns {{needsRealOs: boolean, signals: string[]}}
313
530
  */
314
531
  function classifyContent(content) {
315
- const text = typeof content === 'string' ? content : '';
532
+ const text = stripComments(content);
316
533
  const signals = [];
317
534
  for (const category of CATEGORIES) {
318
535
  if (category.test(text)) signals.push(category.name);
@@ -320,6 +537,26 @@ function classifyContent(content) {
320
537
  return { needsRealOs: signals.length > 0, signals };
321
538
  }
322
539
 
540
+ /**
541
+ * Pre-#6002 classification: no comment stripping and the legacy
542
+ * raw-child-process test (any child_process require counts); the
543
+ * fs-lock-or-install-child category does not exist here. Used by
544
+ * scripts/ci-test-scope.cjs for src/ reachability, whose behavior #6002 must
545
+ * not change.
546
+ *
547
+ * @param {string} content
548
+ * @returns {{needsRealOs: boolean, signals: string[]}}
549
+ */
550
+ function classifyUnstrippedContent(content) {
551
+ const text = typeof content === 'string' ? content : '';
552
+ const signals = [];
553
+ for (const category of CATEGORIES) {
554
+ if (category.rawSkip) continue;
555
+ if ((category.raw || category.test)(text)) signals.push(category.name);
556
+ }
557
+ return { needsRealOs: signals.length > 0, signals };
558
+ }
559
+
323
560
  /**
324
561
  * Pure classifier, macOS-specific signal set (#4593). Same shape as
325
562
  * classifyContent, against MACOS_CATEGORIES instead of CATEGORIES.
@@ -328,7 +565,7 @@ function classifyContent(content) {
328
565
  * @returns {{needsRealOs: boolean, signals: string[]}}
329
566
  */
330
567
  function classifyMacosContent(content) {
331
- const text = typeof content === 'string' ? content : '';
568
+ const text = stripComments(content); // #6002
332
569
  const signals = [];
333
570
  for (const category of MACOS_CATEGORIES) {
334
571
  if (category.test(text)) signals.push(category.name);
@@ -728,6 +965,8 @@ if (require.main === module) {
728
965
 
729
966
  module.exports = {
730
967
  classifyContent,
968
+ classifyUnstrippedContent,
969
+ stripComments,
731
970
  CATEGORIES,
732
971
  NOISY_FOR_SOURCE_REACHABILITY,
733
972
  ALWAYS_REAL_OS,
@@ -8,20 +8,17 @@ module.exports = {
8
8
  MACOS_CONFORMANCE_TIER_FILES: [
9
9
  "tests/5100-win32-bare-bash-sentinel.property.test.cjs",
10
10
  "tests/active-workstream-store.unit.test.cjs",
11
- "tests/adr-612-bracket-phase-counting.test.cjs",
12
11
  "tests/adr-612-bracket-phase-remove.test.cjs",
13
12
  "tests/adr-612-bracket-write-path.test.cjs",
14
13
  "tests/adr-index-gate.test.cjs",
15
14
  "tests/adr-parser.property.test.cjs",
16
15
  "tests/adr-parser.unit.test.cjs",
17
- "tests/agent-classification-parity.test.cjs",
18
16
  "tests/agent-install-check.test.cjs",
17
+ "tests/agent-install-validation.test.cjs",
19
18
  "tests/agent-skills.test.cjs",
20
- "tests/api-coverage-gate-e2e.test.cjs",
21
19
  "tests/api-coverage.test.cjs",
22
20
  "tests/args-terminator-nonce.test.cjs",
23
- "tests/ask-user-questions-fallback.test.cjs",
24
- "tests/audit-command-cutover.test.cjs",
21
+ "tests/blocking-guard-budget-parity.test.cjs",
25
22
  "tests/broken-windows.test.cjs",
26
23
  "tests/capability-cli.test.cjs",
27
24
  "tests/capability-command-dispatch.test.cjs",
@@ -29,11 +26,12 @@ module.exports = {
29
26
  "tests/capability-ledger.test.cjs",
30
27
  "tests/capability-lifecycle.test.cjs",
31
28
  "tests/capability-loader.test.cjs",
32
- "tests/capability-lock-mkdir-failure-3987.test.cjs",
33
29
  "tests/capability-registry.test.cjs",
34
30
  "tests/capability-source.test.cjs",
35
31
  "tests/capability-state.test.cjs",
36
32
  "tests/capability-trust.test.cjs",
33
+ "tests/changelog-release-notes.test.cjs",
34
+ "tests/changeset-lint.test.cjs",
37
35
  "tests/changeset-parse.test.cjs",
38
36
  "tests/check-contract-drift.test.cjs",
39
37
  "tests/check-predicate.test.cjs",
@@ -42,81 +40,90 @@ module.exports = {
42
40
  "tests/check-ui-safety-gate.test.cjs",
43
41
  "tests/check-update-config-dir.test.cjs",
44
42
  "tests/chunked-planning-parallel.test.cjs",
43
+ "tests/ci-compact-v8-coverage.test.cjs",
45
44
  "tests/ci-conformance-pr-gating.test.cjs",
46
- "tests/ci-docs-guard-registry.test.cjs",
47
45
  "tests/ci-test-scope.test.cjs",
46
+ "tests/claude-md-path.test.cjs",
47
+ "tests/claude-md.test.cjs",
48
+ "tests/claude-orchestration-4747.test.cjs",
48
49
  "tests/cline-install.test.cjs",
49
50
  "tests/code-review-pipeline-regression.test.cjs",
51
+ "tests/code-review-tier3-files-override-scoping.test.cjs",
52
+ "tests/code-review.test.cjs",
50
53
  "tests/codex-config-agents.test.cjs",
51
54
  "tests/codex-config-hooks.test.cjs",
52
55
  "tests/codex-config-install.test.cjs",
53
- "tests/codex-config.test.cjs",
54
56
  "tests/command-arguments-block.test.cjs",
55
57
  "tests/commands.platform.test.cjs",
56
- "tests/commit-docs-bypass.test.cjs",
57
58
  "tests/commit-files-deletion.test.cjs",
58
59
  "tests/commit-files-pathspec.test.cjs",
59
60
  "tests/commonjs-marker.test.cjs",
60
- "tests/completion-ratio-scope-withholding.test.cjs",
61
- "tests/config-loader.test.cjs",
61
+ "tests/complete-milestone-release-map-routing.test.cjs",
62
+ "tests/config-loader-bounded-read.platform.test.cjs",
62
63
  "tests/config-schema.property.test.cjs",
63
64
  "tests/config.test.cjs",
64
- "tests/configured-entrypoint-validation.test.cjs",
65
65
  "tests/contributor-standards.test.cjs",
66
66
  "tests/core-utils.test.cjs",
67
+ "tests/criterion-replay.test.cjs",
67
68
  "tests/cursor-subagent-isolation.test.cjs",
68
69
  "tests/custom-phase-readers.test.cjs",
70
+ "tests/custom-phase-review-round7.test.cjs",
69
71
  "tests/debugger-semantic-recall.test.cjs",
70
72
  "tests/default-flip-documentation-lint.test.cjs",
71
- "tests/discuss-phase-power.test.cjs",
72
- "tests/docs-parity-live-registry.test.cjs",
73
+ "tests/docs-update.test.cjs",
73
74
  "tests/drift-whole-change-set.test.cjs",
74
75
  "tests/effort-surface-axis.test.cjs",
75
76
  "tests/effort-sync-installed-runtime.test.cjs",
76
77
  "tests/emitted-attribution.test.cjs",
77
- "tests/ensure-runtime-build.test.cjs",
78
- "tests/execute-phase-decimal-arithmetic.test.cjs",
78
+ "tests/eslint-no-verification-status-literal.test.cjs",
79
+ "tests/execute-plan-update-codebase-map-diff-base.test.cjs",
79
80
  "tests/executed-plan.test.cjs",
80
81
  "tests/executor-mvp-tdd-section.test.cjs",
81
82
  "tests/executor-sequential-linked-worktree.test.cjs",
82
83
  "tests/external-job.test.cjs",
83
84
  "tests/failing-direction.test.cjs",
84
- "tests/fallow-runner.test.cjs",
85
85
  "tests/feat-2483-review-claude-mds-guard.test.cjs",
86
86
  "tests/features-index-gate.test.cjs",
87
87
  "tests/frontmatter.unit.test.cjs",
88
- "tests/gap-checker.property.test.cjs",
89
- "tests/gate-api-coverage-verify-pre-escape.test.cjs",
88
+ "tests/gate-api-coverage-verify-pre.unit.test.cjs",
90
89
  "tests/gate-codebase-drift.unit.test.cjs",
90
+ "tests/gate-contained-path-symlinked-planning.test.cjs",
91
+ "tests/gate-decision-coverage-plan.unit.test.cjs",
92
+ "tests/gate-decision-coverage-verify-stderr.test.cjs",
93
+ "tests/gate-decision-coverage-verify.unit.test.cjs",
91
94
  "tests/gate-evaluation-scope.test.cjs",
95
+ "tests/gate-gap-analysis-plan-post.unit.test.cjs",
92
96
  "tests/gate-predicate.unit.test.cjs",
93
97
  "tests/gate-tdd-red-evidence-containment.test.cjs",
98
+ "tests/gate-tdd-red-evidence.unit.test.cjs",
99
+ "tests/gate-tdd-review-checkpoint.unit.test.cjs",
94
100
  "tests/gate-ui-plan.unit.test.cjs",
101
+ "tests/gate-ui-safety.unit.test.cjs",
95
102
  "tests/gate-verify-command-paths.unit.test.cjs",
96
- "tests/gemini-runtime-removed.test.cjs",
97
- "tests/gen-context-index.test.cjs",
98
- "tests/gen-section-manifest.test.cjs",
103
+ "tests/gate-verify-failure-directions.unit.test.cjs",
99
104
  "tests/git-base-branch.test.cjs",
105
+ "tests/git-head-tree.test.cjs",
100
106
  "tests/git-location-env-scrub.test.cjs",
101
107
  "tests/graphify-query.test.cjs",
102
108
  "tests/graphify-visualization.test.cjs",
103
109
  "tests/gsd-agent-isolation-guard.test.cjs",
104
110
  "tests/gsd-path-export-hook.test.cjs",
105
111
  "tests/gsd-secret-read-guard.test.cjs",
106
- "tests/gsd-settings-advanced.test.cjs",
107
112
  "tests/gsd-statusline.test.cjs",
113
+ "tests/gsd-tool-dir.platform.test.cjs",
114
+ "tests/gsd-tool-dir.test.cjs",
108
115
  "tests/gsd-tools-path-refs.test.cjs",
109
116
  "tests/gsd-validate-commit-crash-policy.test.cjs",
110
117
  "tests/gsd-validate-commit-sigpipe.test.cjs",
111
118
  "tests/gsd-write-guard.test.cjs",
112
119
  "tests/health-diagnostic-rules/worktree-health.test.cjs",
113
- "tests/health-diagnostic.test.cjs",
114
120
  "tests/helpers-cleanup.test.cjs",
115
121
  "tests/helpers-process-isolation.test.cjs",
116
122
  "tests/hooks-crash-policy.test.cjs",
117
123
  "tests/hooks-opt-in.test.cjs",
118
- "tests/host-integration.test.cjs",
119
124
  "tests/init-manager.test.cjs",
125
+ "tests/init.test.cjs",
126
+ "tests/install-fs-adapter-seam.test.cjs",
120
127
  "tests/install-minimal-hooks.test.cjs",
121
128
  "tests/install-path-detection.test.cjs",
122
129
  "tests/install-regressions.test.cjs",
@@ -131,44 +138,46 @@ module.exports = {
131
138
  "tests/installer-migrations.test.cjs",
132
139
  "tests/intel.test.cjs",
133
140
  "tests/inventory-nested-families.test.cjs",
141
+ "tests/isolated-env.test.cjs",
142
+ "tests/isolation-sentinel-read.platform.test.cjs",
134
143
  "tests/isolation-sentinel.test.cjs",
135
144
  "tests/issue-version-gate.test.cjs",
145
+ "tests/kilo-upgrades.test.cjs",
136
146
  "tests/kimi-upgrades.test.cjs",
147
+ "tests/launcher-homes-derivation.test.cjs",
137
148
  "tests/lint-docs-command-form.test.cjs",
138
149
  "tests/lint-frontmatter-scalar-broad-grep.test.cjs",
139
150
  "tests/lint-retired-runtime-name.test.cjs",
140
151
  "tests/lint-workflow-shellcheck-fetch.test.cjs",
141
152
  "tests/list-seeds.test.cjs",
142
- "tests/markdown-sectionizer-xml-tags.property.test.cjs",
143
153
  "tests/markdown-sectionizer.test.cjs",
144
154
  "tests/mcp-catalog.test.cjs",
155
+ "tests/merge-commit-pr.test.cjs",
145
156
  "tests/milestone-archive.test.cjs",
146
157
  "tests/milestone-release-tags.test.cjs",
147
158
  "tests/milestone-window-single-owner.test.cjs",
148
- "tests/model-catalog.unit.test.cjs",
149
159
  "tests/model-resolver.test.cjs",
150
160
  "tests/no-exact-case-env-access.rule.test.cjs",
151
- "tests/no-pending-3212-markers.test.cjs",
152
161
  "tests/no-phantom-issue-refs.test.cjs",
153
162
  "tests/no-posix-mode-bit-assert.rule.test.cjs",
154
163
  "tests/no-private-binary-resolution.rule.test.cjs",
155
164
  "tests/no-unguarded-nonportable-exec.rule.test.cjs",
156
- "tests/nsegment-phase-grammar.test.cjs",
165
+ "tests/node-compile-cache.test.cjs",
157
166
  "tests/observability/event.test.cjs",
158
167
  "tests/onboard-command.test.cjs",
168
+ "tests/opencode-advisory-stderr.test.cjs",
159
169
  "tests/opencode-generations.smoke.test.cjs",
160
170
  "tests/opencode-install-notices.test.cjs",
171
+ "tests/opencode-install-state.test.cjs",
161
172
  "tests/opencode-parity-hooks.test.cjs",
162
173
  "tests/opencode-plugin-adapter.test.cjs",
174
+ "tests/opencode-statusline-plugin.test.cjs",
163
175
  "tests/opencode-uninstall-roundtrip.test.cjs",
164
- "tests/pause-work-context-detection.test.cjs",
176
+ "tests/path-replacement.test.cjs",
165
177
  "tests/pause-work-improvements.test.cjs",
166
178
  "tests/perf-317-context-monitor-fs.test.cjs",
167
- "tests/phase-completion-single-owner.test.cjs",
168
- "tests/phase-estimation.test.cjs",
169
179
  "tests/phase-id-drift-guard.test.cjs",
170
180
  "tests/phase-id.test.cjs",
171
- "tests/phase-locator.test.cjs",
172
181
  "tests/phase-status.test.cjs",
173
182
  "tests/phase.platform.test.cjs",
174
183
  "tests/phase.test.cjs",
@@ -181,26 +190,27 @@ module.exports = {
181
190
  "tests/plan-review-convergence.test.cjs",
182
191
  "tests/planning-inspect.test.cjs",
183
192
  "tests/planning-inspect.unit.test.cjs",
184
- "tests/planning-lock-mkdir-failure-1884.test.cjs",
185
- "tests/planning-snapshot.test.cjs",
186
- "tests/planning-workspace.test.cjs",
187
193
  "tests/platform-conformance-tier.test.cjs",
188
194
  "tests/plugin-manifest.test.cjs",
189
195
  "tests/policy-shell-pinning.test.cjs",
190
- "tests/portability-rule-disable-ban.test.cjs",
191
196
  "tests/precommit-alias-drift-hook.test.cjs",
192
197
  "tests/prepush-enterprise-email-hook.test.cjs",
193
198
  "tests/process-seam.test.cjs",
194
- "tests/profile-output.test.cjs",
195
199
  "tests/profile-pipeline.test.cjs",
200
+ "tests/project-context-cli.test.cjs",
201
+ "tests/project-context.test.cjs",
196
202
  "tests/project-root.test.cjs",
203
+ "tests/prune-orphaned-worktrees.test.cjs",
197
204
  "tests/quick-batch-command-router.stdin.test.cjs",
198
205
  "tests/quick-batch.test.cjs",
199
206
  "tests/quick-branching.test.cjs",
200
207
  "tests/quick-commit-boundary.test.cjs",
201
208
  "tests/quick-research.test.cjs",
209
+ "tests/quick-review-scope-tip-bound.test.cjs",
202
210
  "tests/reapply-verify-hunks.test.cjs",
203
211
  "tests/refactor-1390-t3-characterization.test.cjs",
212
+ "tests/release-notes-real-os-5922.test.cjs",
213
+ "tests/release-pipeline-pr.test.cjs",
204
214
  "tests/require-issue-link-policy.test.cjs",
205
215
  "tests/response-language-coverage.test.cjs",
206
216
  "tests/retired-artifact-cleanup.test.cjs",
@@ -219,13 +229,13 @@ module.exports = {
219
229
  "tests/roadmap-parser.test.cjs",
220
230
  "tests/roadmap-phase-fallback.test.cjs",
221
231
  "tests/roadmap-upgrade-bracket.test.cjs",
222
- "tests/roadmap.test.cjs",
223
232
  "tests/run-tests-accounting.test.cjs",
224
233
  "tests/run-tests-harness.test.cjs",
234
+ "tests/run-tests-temp-root.test.cjs",
225
235
  "tests/runtime-artifact-layout.test.cjs",
226
236
  "tests/runtime-identity.test.cjs",
227
237
  "tests/runtime-launcher-parity.test.cjs",
228
- "tests/runtime-name-policy.test.cjs",
238
+ "tests/secret-read-exemptions.test.cjs",
229
239
  "tests/security.test.cjs",
230
240
  "tests/self-test-report-policy.test.cjs",
231
241
  "tests/settings-jsonc.test.cjs",
@@ -235,37 +245,39 @@ module.exports = {
235
245
  "tests/shipped-reference-cites.test.cjs",
236
246
  "tests/skill-manifest.test.cjs",
237
247
  "tests/smart-entry.unit.test.cjs",
238
- "tests/spawn-liveness-banner.test.cjs",
239
248
  "tests/state-document.test.cjs",
240
249
  "tests/state-transition.test.cjs",
241
250
  "tests/state.platform.test.cjs",
242
251
  "tests/state.test.cjs",
243
- "tests/tdd-mode.platform.test.cjs",
252
+ "tests/sub-repos-evaluation-scope-5867.test.cjs",
253
+ "tests/timeout-evidence-from-ci.test.cjs",
254
+ "tests/timeout-evidence.test.cjs",
244
255
  "tests/todos-workstream-scope.test.cjs",
256
+ "tests/traceability-row-existence-5844.test.cjs",
245
257
  "tests/tracer-bullet.test.cjs",
246
258
  "tests/uat-complete-session.test.cjs",
247
259
  "tests/uat-predicate.test.cjs",
248
- "tests/uat.test.cjs",
249
260
  "tests/ui-auditor-screenshot-block.test.cjs",
250
261
  "tests/ui-interaction-capture.test.cjs",
251
262
  "tests/ui-safety-gate.test.cjs",
252
- "tests/ui-spec-inventory-provenance.test.cjs",
263
+ "tests/undo-commit-selection-4465.test.cjs",
253
264
  "tests/unreachable-guard-drift.test.cjs",
254
265
  "tests/unreachable-shell-guard.test.cjs",
255
- "tests/unusable-input.test.cjs",
256
266
  "tests/update-custom-backup.test.cjs",
257
267
  "tests/user-artifact-staging.test.cjs",
258
268
  "tests/verification-append-audit.test.cjs",
259
269
  "tests/verification-overrides.test.cjs",
260
270
  "tests/verification-status.test.cjs",
261
- "tests/verify-archive-dirs-live-path.test.cjs",
262
271
  "tests/verify-command-grounding.test.cjs",
263
272
  "tests/verify.test.cjs",
273
+ "tests/windows-powershell-hook-runner.test.cjs",
264
274
  "tests/workflow-fragments.test.cjs",
265
- "tests/workflow-shell-pinning.test.cjs",
266
275
  "tests/workstream-inventory.test.cjs",
267
276
  "tests/workstream.test.cjs",
277
+ "tests/worktree-cleanup.test.cjs",
278
+ "tests/worktree-safety-reap.test.cjs",
268
279
  "tests/worktree-safety.test.cjs",
280
+ "tests/worktree-subdir-effective-root-4885.test.cjs",
269
281
  "tests/worktree.test.cjs",
270
282
  ],
271
283
  };