sneakoscope 2.0.8 → 2.0.10

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 (76) hide show
  1. package/README.md +8 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +33 -8
  8. package/dist/cli/command-registry.js +1 -0
  9. package/dist/commands/doctor.js +18 -1
  10. package/dist/commands/zellij-slot-pane.js +26 -0
  11. package/dist/commands/zellij.js +144 -1
  12. package/dist/core/agents/agent-orchestrator.js +202 -9
  13. package/dist/core/agents/agent-role-config.js +92 -0
  14. package/dist/core/agents/native-cli-session-swarm.js +230 -48
  15. package/dist/core/commands/mad-sks-command.js +17 -26
  16. package/dist/core/commands/naruto-command.js +155 -37
  17. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/hooks-runtime.js +4 -0
  20. package/dist/core/init.js +1 -0
  21. package/dist/core/naruto/naruto-active-pool.js +141 -0
  22. package/dist/core/naruto/naruto-concurrency-governor.js +17 -2
  23. package/dist/core/naruto/naruto-real-worker-child.js +35 -0
  24. package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
  25. package/dist/core/naruto/naruto-work-graph.js +2 -1
  26. package/dist/core/release/release-gate-cache-v2.js +58 -4
  27. package/dist/core/release/release-gate-dag.js +36 -25
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
  30. package/dist/core/zellij/zellij-launcher.js +3 -3
  31. package/dist/core/zellij/zellij-layout-builder.js +1 -1
  32. package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
  33. package/dist/core/zellij/zellij-right-column-manager.js +304 -0
  34. package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
  35. package/dist/core/zellij/zellij-ui-mode.js +16 -0
  36. package/dist/core/zellij/zellij-worker-pane-manager.js +152 -17
  37. package/dist/scripts/agent-role-config-repair-check.js +33 -0
  38. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
  39. package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
  40. package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
  41. package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
  42. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  43. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
  44. package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
  45. package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
  46. package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
  47. package/dist/scripts/naruto-extreme-parallelism-real-check.js +42 -0
  48. package/dist/scripts/naruto-real-active-pool-check.js +39 -0
  49. package/dist/scripts/naruto-real-active-pool-runtime-check.js +53 -0
  50. package/dist/scripts/naruto-work-graph-check.js +1 -1
  51. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +48 -0
  52. package/dist/scripts/product-design-auto-install-check.js +3 -3
  53. package/dist/scripts/product-design-plugin-routing-check.js +3 -3
  54. package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
  55. package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
  56. package/dist/scripts/release-check-dynamic-execute.js +27 -1
  57. package/dist/scripts/release-check-dynamic.js +38 -11
  58. package/dist/scripts/release-check-stamp.js +7 -2
  59. package/dist/scripts/release-dag-full-coverage-check.js +35 -0
  60. package/dist/scripts/release-dynamic-performance-check.js +31 -1
  61. package/dist/scripts/release-gate-existence-audit.js +29 -33
  62. package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
  63. package/dist/scripts/release-readiness-report.js +14 -3
  64. package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
  65. package/dist/scripts/zellij-developer-controls-check.js +20 -0
  66. package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
  67. package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
  68. package/dist/scripts/zellij-right-column-geometry-proof.js +162 -0
  69. package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
  70. package/dist/scripts/zellij-right-column-manager-check.js +22 -0
  71. package/dist/scripts/zellij-slot-only-ui-check.js +22 -0
  72. package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
  73. package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
  74. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
  75. package/package.json +23 -5
  76. package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ const mod = await importDist('core/agents/agent-role-config.js');
8
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-role-repair-'));
9
+ fs.mkdirSync(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
10
+ const plan = await mod.repairAgentRoleConfigs({ root, apply: false, codexHome: path.join(root, 'codex-home') });
11
+ const repair = await mod.repairAgentRoleConfigs({ root, apply: true, codexHome: path.join(root, 'codex-home'), reportPath: path.join(root, '.sneakoscope', 'reports', 'agent-role-config-repair.json') });
12
+ const analysisScout = path.join(root, '.codex', 'agents', 'analysis-scout.toml');
13
+ const staleRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-role-stale-'));
14
+ fs.mkdirSync(path.join(staleRoot, '.codex', 'agents'), { recursive: true });
15
+ fs.writeFileSync(path.join(staleRoot, '.codex', 'agents', 'analysis-scout.toml'), 'model = "gpt-5-codex"\nsandbox_mode = "read-only"\n');
16
+ const stalePlan = await mod.repairAgentRoleConfigs({ root: staleRoot, apply: false, codexHome: path.join(staleRoot, 'codex-home') });
17
+ const staleRepair = await mod.repairAgentRoleConfigs({ root: staleRoot, apply: true, codexHome: path.join(staleRoot, 'codex-home') });
18
+ const repairedText = fs.readFileSync(path.join(staleRoot, '.codex', 'agents', 'analysis-scout.toml'), 'utf8');
19
+ const report = {
20
+ schema: 'sks.agent-role-config-repair-check.v1',
21
+ plan_ok: plan.ok === true && plan.missing.includes('analysis-scout.toml'),
22
+ repair_ok: repair.ok === true,
23
+ analysis_scout_created: fs.existsSync(analysisScout),
24
+ created_matches_model: fs.readFileSync(analysisScout, 'utf8').includes('model = "gpt-5.5"'),
25
+ stale_detected: stalePlan.stale.includes('analysis-scout.toml'),
26
+ stale_repaired: staleRepair.repaired.includes('.codex/agents/analysis-scout.toml') && repairedText.includes('name = "analysis_scout"') && repairedText.includes('model = "gpt-5.5"'),
27
+ warnings_suppressed: repair.warnings_suppressed === true,
28
+ artifact_written: fs.existsSync(path.join(root, '.sneakoscope', 'reports', 'agent-role-config-repair.json'))
29
+ };
30
+ const ok = report.plan_ok && report.repair_ok && report.analysis_scout_created && report.created_matches_model && report.stale_detected && report.stale_repaired && report.warnings_suppressed && report.artifact_written;
31
+ assertGate(ok, 'doctor --fix must repair missing SKS-owned agent role configs', report);
32
+ emitGate('agent:role-config-repair', report);
33
+ //# sourceMappingURL=agent-role-config-repair-check.js.map
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
- import { assertGate, emitGate, packageScripts, readText } from './lib/codex-sdk-gate-lib.js';
3
+ import { assertGate, emitGate, packageScripts, readText, releaseGateIds } from './lib/codex-sdk-gate-lib.js';
4
4
  const scripts = packageScripts();
5
- const releaseCheck = String(scripts['release:check'] || '');
6
5
  const releaseRealCheck = String(scripts['release:real-check'] || '');
7
- assertGate(releaseCheck.includes('codex-sdk:capability'), 'release:check must include Codex SDK capability gate');
8
- assertGate(releaseCheck.includes('codex-sdk:all-pipelines'), 'release:check must include Codex SDK all-pipelines gate');
6
+ const releaseGates = releaseGateIds();
7
+ assertGate(releaseGates.has('codex-sdk:capability'), 'release gate DAG must include Codex SDK capability gate');
8
+ assertGate(releaseGates.has('codex-sdk:all-pipelines'), 'release gate DAG must include Codex SDK all-pipelines gate');
9
9
  assertGate(releaseRealCheck.includes('codex-sdk:real-smoke'), 'release:real-check must include Codex SDK real smoke');
10
10
  assertGate(readText('src/core/agents/agent-orchestrator.ts').includes('legacy_codex_exec_runtime_removed'), 'orchestrator must block legacy codex-exec requests');
11
- emitGate('codex-sdk:release-review-pipeline', { release_check_contains_sdk: true });
11
+ emitGate('codex-sdk:release-review-pipeline', { release_gate_dag_contains_sdk: true });
12
12
  //# sourceMappingURL=codex-sdk-release-review-pipeline-check.js.map
@@ -25,19 +25,40 @@ const run = spawnSync(process.execPath, [
25
25
  SKS_DISABLE_UPDATE_CHECK: '1'
26
26
  },
27
27
  encoding: 'utf8',
28
- timeout: 60000
28
+ timeout: 180000
29
29
  });
30
30
  const parsed = parseLastJson(run.stdout || '{}');
31
31
  const ok = run.status !== 0
32
32
  && parsed.ready?.ready === false
33
33
  && parsed.ready?.blockers?.includes('codex_cli_config_eperm')
34
34
  && parsed.ready?.next_actions?.length > 0;
35
- console.log(JSON.stringify({ schema: 'sks.doctor-fix-proves-codex-read-check.v1', ok, status: run.status, parsed }, null, 2));
35
+ console.log(JSON.stringify({
36
+ schema: 'sks.doctor-fix-proves-codex-read-check.v1',
37
+ ok,
38
+ status: run.status,
39
+ signal: run.signal,
40
+ error: run.error ? String(run.error.message || run.error) : null,
41
+ parsed,
42
+ stdout_tail: String(run.stdout || '').slice(-1000),
43
+ stderr_tail: String(run.stderr || '').slice(-1000)
44
+ }, null, 2));
36
45
  if (!ok)
37
46
  process.exitCode = 1;
38
47
  function parseLastJson(text) {
39
- const index = String(text).lastIndexOf('\n{');
40
- const jsonText = index >= 0 ? String(text).slice(index + 1) : String(text).slice(String(text).indexOf('{'));
41
- return JSON.parse(jsonText || '{}');
48
+ const source = String(text || '').trim();
49
+ if (!source)
50
+ return {};
51
+ const starts = [];
52
+ for (let index = source.indexOf('{'); index >= 0; index = source.indexOf('{', index + 1))
53
+ starts.push(index);
54
+ for (let i = starts.length - 1; i >= 0; i -= 1) {
55
+ try {
56
+ return JSON.parse(source.slice(starts[i]));
57
+ }
58
+ catch {
59
+ // Continue searching for the outer JSON object; pretty JSON may contain nested objects.
60
+ }
61
+ }
62
+ return {};
42
63
  }
43
64
  //# sourceMappingURL=doctor-fix-proves-codex-read-check.js.map
@@ -17,6 +17,8 @@ const integrationPath = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-integration-'
17
17
  fs.rmSync(integrationPath, { recursive: true, force: true });
18
18
  const integration = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-integrate', workerId: 'integration', slotId: 'integration' });
19
19
  const report = await mergeMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: integration.worktree_path, diffs: [diff] });
20
- assertGate(report.ok === true && report.applied_count === 1, 'git-worktree-diff must apply through merge queue', report);
21
- emitGate('git:worktree-integration-primary', { applied_count: report.applied_count });
20
+ const mainReport = await mergeMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: repo, diffs: [diff] });
21
+ const mainContent = fs.readFileSync(path.join(repo, 'a.txt'), 'utf8');
22
+ assertGate(report.ok === true && report.applied_count === 1 && mainReport.ok === true && mainReport.applied_count === 1 && mainContent.includes('integrated'), 'git-worktree-diff must prevalidate in integration worktree and apply to main repo', { report, mainReport, mainContent });
23
+ emitGate('git:worktree-integration-primary', { applied_count: report.applied_count, main_applied_count: mainReport.applied_count });
22
24
  //# sourceMappingURL=git-worktree-integration-primary-check.js.map
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
6
+ const orchestrator = fs.readFileSync(path.join(root, 'src/core/agents/agent-orchestrator.ts'), 'utf8');
7
+ const report = {
8
+ schema: 'sks.git-worktree-integration-primary-runtime-check.v1',
9
+ splits_worktree_entries: orchestrator.includes("entry.envelope?.source === 'git-worktree-diff'"),
10
+ uses_integration_worktree: orchestrator.includes('createGitIntegrationWorktree'),
11
+ uses_merge_queue: orchestrator.includes('applyGitWorktreeMergeQueue'),
12
+ applies_to_main_repo: orchestrator.includes('main_repo_apply') && orchestrator.includes('integrationWorktreePath: repoRoot'),
13
+ rollback_evidence: orchestrator.includes('rollback_evidence') && orchestrator.includes('captureGitWorktreeRollbackPlan') && orchestrator.includes('completeGitWorktreeRollbackPlan'),
14
+ bypasses_normal_apply: orchestrator.includes('const normalEntries') && orchestrator.includes('applyAgentPatchQueueEntry(root, entry') && orchestrator.includes('parallelEntries = disjointEntries'),
15
+ writes_report: orchestrator.includes('git-worktree-merge-queue-report.json')
16
+ };
17
+ const ok = report.splits_worktree_entries && report.uses_integration_worktree && report.uses_merge_queue && report.applies_to_main_repo && report.rollback_evidence && report.bypasses_normal_apply && report.writes_report;
18
+ assertGate(ok, 'git-worktree-diff entries must use integration merge queue primary path', report);
19
+ emitGate('git:worktree-integration-primary-runtime', report);
20
+ //# sourceMappingURL=git-worktree-integration-primary-runtime-check.js.map
@@ -59,6 +59,10 @@ export async function runFakeCodexSdkTaskFixture(label = 'fixture', extra = {})
59
59
  export function packageScripts() {
60
60
  return readJson('package.json').scripts || {};
61
61
  }
62
+ export function releaseGateIds() {
63
+ const manifest = readJson('release-gates.v2.json');
64
+ return new Set((manifest.gates || []).map((gate) => gate.id));
65
+ }
62
66
  export function assertSourceIncludes(file, tokens) {
63
67
  const text = readText(file);
64
68
  for (const token of tokens)
@@ -17,10 +17,10 @@ const checks = {
17
17
  zellij_session_before_swarm: launchIndex >= 0 && swarmIndex >= 0 && launchIndex < swarmIndex,
18
18
  main_only_session: mad.includes('slotCount: 0'),
19
19
  zellij_default_backend: /return 'zellij'/.test(mad) && mad.includes("list.includes('--json')") && mad.includes("list.includes('--no-attach')"),
20
- worker_command_real_zellij: mad.includes("command.push('--real')") && mad.includes("command.push('--zellij-session-name'") && mad.includes("command.push('--zellij-pane-worker')"),
20
+ worker_command_real_zellij: mad.includes("command.push('--real')") && mad.includes("command.push('--zellij-session-name'") && mad.includes("command.push('--zellij-pane-worker')") && mad.includes("command.push('--worker-placement'"),
21
21
  parser_accepts_worker_flags: parser.includes('--zellij-session-name') && parser.includes('--zellij-pane-worker') && parser.includes('--no-zellij-pane-worker'),
22
22
  native_worker_pane_path: swarm.includes("this.input.backend === 'zellij'") && swarm.includes('ctx.opts.zellijPaneWorker !== false') && swarm.includes('openWorkerPane({'),
23
- right_pane_requested: manager.includes("'--direction', 'right'")
23
+ right_pane_requested: manager.includes("'--direction', directionRequested") && manager.includes("'--near-current-pane'")
24
24
  };
25
25
  const ok = Object.values(checks).every(Boolean);
26
26
  emit({
@@ -17,6 +17,7 @@ const uncovered = [];
17
17
  const GUARD_CALL = /\bguarded(WriteFile|Rm|Rename|Chmod|Xattr|Chflags|GlobalCodexConfigWrite|ProcessKill|PackageInstall|SkillSnapshotPromotion|Apply)\(/;
18
18
  const RISKY = [
19
19
  { kind: 'write_file', token: 'fs.writeFile', re: /\bfs\.writeFile\(/ },
20
+ { kind: 'write_file', token: 'fs.promises.writeFile', re: /\bfs\.promises\.writeFile\(/ },
20
21
  { kind: 'write_file', token: 'fsp.writeFile', re: /\bfsp\.writeFile\(/ },
21
22
  { kind: 'write_file', token: 'writeFileSync', re: /\b(?:fs\.)?writeFileSync\(/ },
22
23
  { kind: 'rm', token: 'fs.rm', re: /\bfs\.rm\(/ },
@@ -167,7 +168,7 @@ function processKillIsLivenessProbe(line) {
167
168
  return /\bprocess\.kill\([^,\n]+,\s*0\s*\)/.test(line);
168
169
  }
169
170
  function codexHomeMutationOnLine(line) {
170
- return /\b(?:writeTextAtomic|writeJsonAtomic|writeFileSync|fs\.writeFile|fsp\.writeFile|fs\.rm|fsp\.rm|fs\.rename|fsp\.rename|fs\.chmod|fsp\.chmod|copyFile|open)\b/.test(line)
171
+ return /\b(?:writeTextAtomic|writeJsonAtomic|writeFileSync|fs\.writeFile|fs\.promises\.writeFile|fsp\.writeFile|fs\.rm|fsp\.rm|fs\.rename|fsp\.rename|fs\.chmod|fsp\.chmod|copyFile|open)\b/.test(line)
171
172
  && /(?:~\/\.codex|CODEX_HOME|codexHome|codexLbHome|auth\.json|config\.toml)/.test(line);
172
173
  }
173
174
  function snippet(line) {
@@ -10,6 +10,7 @@ const normal = governorMod.decideNarutoConcurrency({
10
10
  zellijVisiblePaneCap: 12,
11
11
  hardware: {
12
12
  cores: 32,
13
+ loadAverage: [0, 0, 0],
13
14
  freeMemoryBytes: 48 * 1024 * 1024 * 1024,
14
15
  totalMemoryBytes: 64 * 1024 * 1024 * 1024,
15
16
  fileDescriptorLimit: 4096,
@@ -37,7 +38,7 @@ const pressure = governorMod.decideNarutoConcurrency({
37
38
  diskIoPressure: 0.9
38
39
  }
39
40
  });
40
- assertGate(normal.safe_active_workers <= 32, 'requested_clones=200 fixture must cap active workers safely', { normal });
41
+ assertGate(normal.safe_active_workers >= 32 && normal.safe_active_workers <= 100, 'requested_clones=200 fixture must cap active workers safely while allowing aggressive process-pool fanout', { normal });
41
42
  assertGate(normal.safe_zellij_visible_panes === 12 && normal.headless_workers === normal.safe_active_workers - 12, 'zellij visible panes must stay within UI cap', { normal });
42
43
  assertGate(normal.local_llm_parallel <= 4, 'local LLM active requests must respect max_parallel_requests=4', { normal });
43
44
  assertGate(pressure.safe_active_workers < normal.safe_active_workers, 'memory/load pressure fixture must decrease active workers', { normal: normal.safe_active_workers, pressure: pressure.safe_active_workers });
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
4
+ import { buildNarutoWorkGraph } from '../core/naruto/naruto-work-graph.js';
5
+ import { decideNarutoConcurrency } from '../core/naruto/naruto-concurrency-governor.js';
6
+ const graph = buildNarutoWorkGraph({ requestedClones: 100, writeCapable: true, maxActiveWorkers: 32 });
7
+ const governor = decideNarutoConcurrency({
8
+ requestedClones: 100,
9
+ totalWorkItems: graph.total_work_items,
10
+ pendingWorkQueueSize: graph.total_work_items,
11
+ backend: 'codex-sdk',
12
+ hardware: { cores: 16, loadAverage: [1, 1, 1], freeMemoryBytes: 64 * 1024 * 1024 * 1024, totalMemoryBytes: 128 * 1024 * 1024 * 1024, fileDescriptorLimit: 8192, processCount: 100, terminalRows: 48, remoteApiRateLimitBudget: 32, localLlmMaxParallelRequests: 8 }
13
+ });
14
+ const report = {
15
+ schema: 'sks.naruto-extreme-parallelism-check.v1',
16
+ ok: graph.total_work_items >= 200 && governor.safe_active_workers >= 16 && governor.safe_zellij_visible_panes <= governor.safe_active_workers && governor.safe_zellij_visible_panes <= 8 && graph.mixed_work_kinds.length >= 6,
17
+ graph: { total_work_items: graph.total_work_items, mixed_work_kinds: graph.mixed_work_kinds, write_allowed_count: graph.write_allowed_count },
18
+ governor
19
+ };
20
+ assertGate(report.ok, 'Naruto extreme parallelism must fan out >=2x clones and keep a high safe active pool', report);
21
+ emitGate('naruto:extreme-parallelism', report);
22
+ //# sourceMappingURL=naruto-extreme-parallelism-check.js.map
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
7
+ import { buildNarutoWorkGraph } from '../core/naruto/naruto-work-graph.js';
8
+ import { decideNarutoConcurrency } from '../core/naruto/naruto-concurrency-governor.js';
9
+ import { runNarutoRealActivePool } from '../core/naruto/naruto-active-pool.js';
10
+ import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../core/naruto/naruto-real-worker-runtime.js';
11
+ const graph = buildNarutoWorkGraph({ requestedClones: 100, totalWorkItems: 200, writeCapable: true, maxActiveWorkers: 32 });
12
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-extreme-real-'));
13
+ const missionId = `M-naruto-extreme-real-${process.pid}`;
14
+ const governor = decideNarutoConcurrency({
15
+ requestedClones: 100,
16
+ totalWorkItems: graph.total_work_items,
17
+ pendingWorkQueueSize: graph.total_work_items,
18
+ backend: 'codex-sdk',
19
+ hardware: { cores: 16, loadAverage: [1, 1, 1], freeMemoryBytes: 64 * 1024 * 1024 * 1024, totalMemoryBytes: 128 * 1024 * 1024 * 1024, fileDescriptorLimit: 8192, processCount: 100, terminalRows: 48, remoteApiRateLimitBudget: 32, localLlmMaxParallelRequests: 8 }
20
+ });
21
+ const target = { ...governor, safe_active_workers: Math.min(32, governor.safe_active_workers), safe_zellij_visible_panes: Math.min(8, governor.safe_zellij_visible_panes) };
22
+ const report = await runNarutoRealActivePool({
23
+ graph,
24
+ governor: target,
25
+ spawnWorker: async (item, placement) => spawnActualNarutoWorker({
26
+ root: tempRoot,
27
+ missionId,
28
+ item,
29
+ placement,
30
+ backend: 'codex-sdk',
31
+ visiblePaneCap: target.safe_zellij_visible_panes,
32
+ zellijSessionName: `sks-${missionId}`
33
+ }),
34
+ collectWorker: async (handle) => collectActualNarutoWorker(handle),
35
+ enqueueVerification: async () => undefined,
36
+ updateDashboard: async () => undefined
37
+ });
38
+ const actualArtifacts = report.worker_lifecycle.every((row) => row.pid && row.worker_artifact_dir && fs.existsSync(path.join(row.worker_artifact_dir, 'worker-result.json')));
39
+ const ok = report.ok && report.max_observed_active_workers >= Math.ceil(target.safe_active_workers * 0.8) && report.active_pool_utilization >= 0.8 && report.headless_workers > 0 && report.visible_workers <= graph.total_work_items && actualArtifacts;
40
+ assertGate(ok, 'Naruto extreme parallelism must use actual child process active-pool lifecycle near cap with headless overflow', { report, target, actualArtifacts, tempRoot });
41
+ emitGate('naruto:extreme-parallelism-real', report);
42
+ //# sourceMappingURL=naruto-extreme-parallelism-real-check.js.map
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
4
+ import { buildNarutoWorkGraph } from '../core/naruto/naruto-work-graph.js';
5
+ import { decideNarutoConcurrency } from '../core/naruto/naruto-concurrency-governor.js';
6
+ import { runNarutoRealActivePool } from '../core/naruto/naruto-active-pool.js';
7
+ const graph = buildNarutoWorkGraph({ requestedClones: 12, totalWorkItems: 24, writeCapable: true, maxActiveWorkers: 6 });
8
+ const governor = decideNarutoConcurrency({
9
+ requestedClones: 12,
10
+ totalWorkItems: graph.total_work_items,
11
+ pendingWorkQueueSize: graph.total_work_items,
12
+ backend: 'codex-sdk',
13
+ hardware: { cpuCoreCount: 8, freeMemoryBytes: 32 * 1024 * 1024 * 1024, totalMemoryBytes: 64 * 1024 * 1024 * 1024, fileDescriptorLimit: 4096, processCount: 100, terminalRows: 40, remoteApiRateLimitBudget: 8 }
14
+ });
15
+ const target = { ...governor, safe_active_workers: Math.min(6, governor.safe_active_workers), safe_zellij_visible_panes: 3 };
16
+ let spawned = 0;
17
+ let collected = 0;
18
+ let dashboardEvents = 0;
19
+ const report = await runNarutoRealActivePool({
20
+ graph,
21
+ governor: target,
22
+ spawnWorker: async (item, placement) => {
23
+ spawned += 1;
24
+ return { id: item.id, item, placement, started_at: Date.now() };
25
+ },
26
+ collectWorker: async (handle) => {
27
+ collected += 1;
28
+ return { id: handle.id, ok: true, item: handle.item, placement: handle.placement, completed_at: Date.now() };
29
+ },
30
+ enqueueVerification: async () => undefined,
31
+ updateDashboard: async () => {
32
+ dashboardEvents += 1;
33
+ }
34
+ });
35
+ const processEvidence = report.worker_lifecycle.every((row) => row.pid == null || row.worker_artifact_dir != null);
36
+ const ok = report.ok && spawned === graph.total_work_items && collected === graph.total_work_items && report.max_observed_active_workers >= target.safe_active_workers && dashboardEvents > graph.total_work_items && report.active_cap === target.safe_active_workers && processEvidence;
37
+ assertGate(ok, 'Naruto real active pool must run spawn/collect lifecycle and refill to cap', { report, spawned, collected, dashboardEvents, target });
38
+ emitGate('naruto:real-active-pool', { spawned, collected, active_cap: report.active_cap, max_observed_active_workers: report.max_observed_active_workers, refill_latency_ms_p95: report.refill_latency_ms_p95, active_pool_utilization: report.active_pool_utilization });
39
+ //# sourceMappingURL=naruto-real-active-pool-check.js.map
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
7
+ import { buildNarutoWorkGraph } from '../core/naruto/naruto-work-graph.js';
8
+ import { decideNarutoConcurrency } from '../core/naruto/naruto-concurrency-governor.js';
9
+ import { runNarutoRealActivePool } from '../core/naruto/naruto-active-pool.js';
10
+ import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../core/naruto/naruto-real-worker-runtime.js';
11
+ const graph = buildNarutoWorkGraph({ requestedClones: 12, totalWorkItems: 24, writeCapable: false, maxActiveWorkers: 6 });
12
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-real-runtime-'));
13
+ const missionId = `M-naruto-real-runtime-${process.pid}`;
14
+ const governor = decideNarutoConcurrency({
15
+ requestedClones: 12,
16
+ totalWorkItems: graph.total_work_items,
17
+ pendingWorkQueueSize: graph.total_work_items,
18
+ backend: 'codex-sdk',
19
+ hardware: { cores: 8, freeMemoryBytes: 32 * 1024 * 1024 * 1024, totalMemoryBytes: 64 * 1024 * 1024 * 1024, fileDescriptorLimit: 4096, processCount: 100, terminalRows: 40, remoteApiRateLimitBudget: 8 }
20
+ });
21
+ let spawned = 0;
22
+ let collected = 0;
23
+ const target = { ...governor, safe_active_workers: Math.min(6, governor.safe_active_workers), safe_zellij_visible_panes: 3 };
24
+ const report = await runNarutoRealActivePool({
25
+ graph,
26
+ governor: target,
27
+ spawnWorker: async (item, placement) => {
28
+ spawned += 1;
29
+ return spawnActualNarutoWorker({
30
+ root: tempRoot,
31
+ missionId,
32
+ item,
33
+ placement,
34
+ backend: 'codex-sdk',
35
+ visiblePaneCap: target.safe_zellij_visible_panes,
36
+ zellijSessionName: `sks-${missionId}`
37
+ });
38
+ },
39
+ collectWorker: async (handle) => {
40
+ collected += 1;
41
+ return collectActualNarutoWorker(handle);
42
+ },
43
+ enqueueVerification: async () => undefined,
44
+ updateDashboard: async () => undefined
45
+ });
46
+ const processEvidence = report.worker_lifecycle.every((row) => row.pid && row.worker_artifact_dir);
47
+ const artifactEvidence = report.worker_lifecycle.every((row) => row.worker_artifact_dir
48
+ && fs.existsSync(path.join(row.worker_artifact_dir, 'worker-heartbeat.jsonl'))
49
+ && fs.existsSync(path.join(row.worker_artifact_dir, 'worker-result.json')));
50
+ const ok = report.ok && spawned === graph.total_work_items && collected === graph.total_work_items && report.max_observed_active_workers >= target.safe_active_workers && report.active_pool_utilization >= 0.8 && processEvidence && artifactEvidence;
51
+ assertGate(ok, 'Naruto real active pool runtime must include actual child process, heartbeat, and result evidence', { report, spawned, collected, processEvidence, artifactEvidence, tempRoot });
52
+ emitGate('naruto:real-active-pool-runtime', report);
53
+ //# sourceMappingURL=naruto-real-active-pool-runtime-check.js.map
@@ -11,7 +11,7 @@ const graph = workGraph.buildNarutoWorkGraph({
11
11
  });
12
12
  const validation = workGraph.validateNarutoWorkGraph(graph);
13
13
  assertGate(graph.ok === true && validation.ok === true, 'Naruto work graph must validate', { graph_blockers: graph.blockers, validation });
14
- assertGate(graph.total_work_items >= graph.requested_clones, 'work graph must create at least requested clone count', { total: graph.total_work_items, requested: graph.requested_clones });
14
+ assertGate(graph.total_work_items >= graph.requested_clones * 2, 'write-capable work graph must create at least 2x requested clone count', { total: graph.total_work_items, requested: graph.requested_clones });
15
15
  assertGate(graph.mixed_work_kinds.length > 4, 'work graph must contain mixed work kinds, not only verification', { kinds: graph.mixed_work_kinds });
16
16
  assertGate(graph.write_allowed_count > 0, 'write-capable Naruto graph must include write_allowed work items', { write_allowed_count: graph.write_allowed_count });
17
17
  assertGate(graph.active_waves.every((wave) => wave.conflict_count === 0), 'active waves must not overlap write leases', { waves: graph.active_waves });
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
7
+ const requireReal = process.argv.includes('--require-real') || process.env.SKS_REQUIRE_ZELLIJ === '1';
8
+ const naruto = fs.readFileSync(path.join(root, 'src/core/commands/naruto-command.ts'), 'utf8');
9
+ const worker = fs.readFileSync(path.join(root, 'src/core/zellij/zellij-worker-pane-manager.ts'), 'utf8');
10
+ const swarm = fs.readFileSync(path.join(root, 'src/core/agents/native-cli-session-swarm.ts'), 'utf8');
11
+ const realGeometryProof = requireReal ? runRealGeometryProof() : null;
12
+ const report = {
13
+ schema: 'sks.naruto-zellij-dynamic-right-column-check.v1',
14
+ ok: true,
15
+ require_real: requireReal,
16
+ initial_main_only: /slotCount:\s*0/.test(naruto),
17
+ passes_session_name: naruto.includes('zellijSessionName: liveZellij?.session_name'),
18
+ passes_worker_placement: naruto.includes("workerPlacement: parsed.json || parsed.noOpenZellij ? 'process' : 'zellij-pane'"),
19
+ passes_visible_cap: naruto.includes('zellijVisiblePaneCap: zellijVisiblePanes'),
20
+ worker_uses_right_column: worker.includes("rightColumnMode: 'spawn-on-first-worker'") || swarm.includes("rightColumnMode: 'spawn-on-first-worker'"),
21
+ real_geometry_proof: realGeometryProof
22
+ };
23
+ report.ok = report.initial_main_only
24
+ && report.passes_session_name
25
+ && report.passes_worker_placement
26
+ && report.passes_visible_cap
27
+ && report.worker_uses_right_column
28
+ && (!requireReal || realGeometryProof?.ok === true);
29
+ assertGate(report.ok, 'Naruto must use dynamic right-column worker panes in interactive mode', report);
30
+ emitGate('naruto:zellij-dynamic-right-column', report);
31
+ function runRealGeometryProof() {
32
+ const res = spawnSync('npm', ['run', 'zellij:right-column-real-geometry', '--silent', '--', '--require-real'], {
33
+ cwd: root,
34
+ env: { ...process.env, SKS_REQUIRE_ZELLIJ: '1' },
35
+ encoding: 'utf8',
36
+ maxBuffer: 20 * 1024 * 1024
37
+ });
38
+ return {
39
+ ok: res.status === 0,
40
+ exit_code: res.status,
41
+ stdout_tail: tail(res.stdout),
42
+ stderr_tail: tail(res.stderr)
43
+ };
44
+ }
45
+ function tail(text) {
46
+ return String(text || '').split('\n').slice(-30).join('\n');
47
+ }
48
+ //# sourceMappingURL=naruto-zellij-dynamic-right-column-check.js.map
@@ -2,7 +2,7 @@
2
2
  // @ts-nocheck
3
3
  import { ensureProductDesignPluginInstalledWithRequest, findProductDesignPluginSummaryFromMarketplaces, productDesignAutoInstallRequested } from '../core/product-design-app-server.js';
4
4
  import { PRODUCT_DESIGN_PLUGIN, PRODUCT_DESIGN_REQUIRED_SKILLS } from '../core/product-design-plugin.js';
5
- import { assertGate, emitGate, readText } from './lib/codex-sdk-gate-lib.js';
5
+ import { assertGate, emitGate, readText, releaseGateIds } from './lib/codex-sdk-gate-lib.js';
6
6
  function pluginReadResponse({ installed, enabled }) {
7
7
  return {
8
8
  plugin: {
@@ -92,7 +92,7 @@ const codexAppSource = readText('src/core/codex-app.ts');
92
92
  const commandSource = readText('src/commands/codex-app.ts');
93
93
  const appServerSource = readText('src/core/product-design-app-server.ts');
94
94
  const pkg = JSON.parse(readText('package.json'));
95
- const releaseCheck = String(pkg.scripts?.['release:check'] || '');
95
+ const releaseGates = releaseGateIds();
96
96
  for (const token of [
97
97
  'ensureProductDesignPluginInstalled',
98
98
  'PRODUCT_DESIGN_AUTO_INSTALL_ENV',
@@ -109,7 +109,7 @@ for (const token of [
109
109
  assertGate(commandSource.includes(token), `sks codex-app command missing Product Design token: ${token}`);
110
110
  }
111
111
  assertGate(Boolean(pkg.scripts?.['codex:product-design-auto-install']), 'package script missing codex:product-design-auto-install');
112
- assertGate(releaseCheck.includes('codex:product-design-auto-install'), 'release:check must include Product Design auto-install gate');
112
+ assertGate(releaseGates.has('codex:product-design-auto-install'), 'release gate DAG must include Product Design auto-install gate');
113
113
  emitGate('codex:product-design-auto-install', {
114
114
  install_calls: installCalls.map((call) => call.method),
115
115
  dry_calls: dryCalls.map((call) => call.method),
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-nocheck
3
3
  import { PRODUCT_DESIGN_PIPELINE_STAGES, PRODUCT_DESIGN_PLUGIN, PRODUCT_DESIGN_REQUIRED_SKILLS, normalizeProductDesignPluginEvidence, productDesignPluginVisibilityFromCodexPluginList } from '../core/product-design-plugin.js';
4
- import { assertGate, emitGate, readText } from './lib/codex-sdk-gate-lib.js';
4
+ import { assertGate, emitGate, readText, releaseGateIds } from './lib/codex-sdk-gate-lib.js';
5
5
  const routesSource = readText('src/core/routes.ts');
6
6
  const productDesignSource = readText('src/core/product-design-plugin.ts');
7
7
  const runtimeSource = readText('src/core/pipeline-internals/runtime-core.ts');
@@ -9,7 +9,7 @@ const pptSource = readText('src/core/ppt.ts');
9
9
  const codexAppSource = readText('src/core/codex-app.ts');
10
10
  const initSource = readText('src/core/init.ts');
11
11
  const pkg = JSON.parse(readText('package.json'));
12
- const releaseCheck = String(pkg.scripts?.['release:check'] || '');
12
+ const releaseGates = releaseGateIds();
13
13
  assertGate(PRODUCT_DESIGN_PLUGIN.id === 'product-design@openai-curated-remote', 'Product Design plugin id must use the remote marketplace');
14
14
  assertGate(PRODUCT_DESIGN_PLUGIN.marketplace === 'openai-curated-remote', 'Product Design marketplace must be openai-curated-remote');
15
15
  assertGate(PRODUCT_DESIGN_PLUGIN.marketplace_kind === 'vertical', 'Product Design marketplace kind must be vertical');
@@ -90,7 +90,7 @@ for (const token of [
90
90
  assertGate(codexAppSource.includes(token), `codex-app.ts missing Product Design readiness token: ${token}`);
91
91
  }
92
92
  assertGate(Boolean(pkg.scripts?.['codex:product-design-plugin-routing']), 'package script missing codex:product-design-plugin-routing');
93
- assertGate(releaseCheck.includes('codex:product-design-plugin-routing'), 'release:check must include Product Design routing gate');
93
+ assertGate(releaseGates.has('codex:product-design-plugin-routing'), 'release gate DAG must include Product Design routing gate');
94
94
  emitGate('codex:product-design-plugin-routing', {
95
95
  plugin_id: PRODUCT_DESIGN_PLUGIN.id,
96
96
  remote_plugin_id: PRODUCT_DESIGN_PLUGIN.remote_plugin_id,
@@ -5,6 +5,7 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { execFileSync } from 'node:child_process';
8
+ import { writeTextAtomic } from '../core/fsx.js';
8
9
  const root = process.cwd();
9
10
  const args = process.argv.slice(2);
10
11
  const reportPath = path.join(root, '.sneakoscope', 'reports', 'readme-architecture-imagegen-attempt-1.18.8.json');
@@ -139,12 +140,12 @@ else {
139
140
  }
140
141
  async function ensurePromptContract() {
141
142
  if (!promptExistedBeforeRun) {
142
- await fs.promises.writeFile(promptPath, prompt);
143
+ await writeTextAtomic(promptPath, prompt);
143
144
  return { changed: true, reason: 'created' };
144
145
  }
145
146
  const existing = await fs.promises.readFile(promptPath, 'utf8').catch(() => null);
146
147
  if (existing !== prompt) {
147
- await fs.promises.writeFile(promptPath, prompt);
148
+ await writeTextAtomic(promptPath, prompt);
148
149
  return { changed: true, reason: 'refreshed_prompt_changed' };
149
150
  }
150
151
  return { changed: false, reason: 'unchanged' };
@@ -334,7 +335,7 @@ async function textArtifactMeta(file, promptWrite = null) {
334
335
  };
335
336
  }
336
337
  async function writeReport(report) {
337
- await fs.promises.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`);
338
+ await writeTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}\n`);
338
339
  }
339
340
  async function sha256File(file) {
340
341
  return new Promise((resolve, reject) => {
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate } from './sks-1-18-gate-lib.js';
7
+ import { expandGlob, releaseGateCacheKey } from '../core/release/release-gate-cache-v2.js';
8
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-cache-glob-'));
9
+ fs.mkdirSync(path.join(tmp, 'src/core/release'), { recursive: true });
10
+ fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({ version: '0.0.0' }));
11
+ fs.writeFileSync(path.join(tmp, 'release-gates.v2.json'), JSON.stringify({ schema: 'sks.release-gates.v2', gates: [] }));
12
+ fs.writeFileSync(path.join(tmp, 'src/core/release/a.ts'), 'a');
13
+ const gate = {
14
+ id: 'release:cache-glob-hashing-fixture',
15
+ command: 'node fixture',
16
+ deps: [],
17
+ resource: ['cpu-light'],
18
+ side_effect: 'hermetic',
19
+ timeout_ms: 1000,
20
+ cache: { enabled: true, inputs: ['src/core/release/**'] },
21
+ isolation: { home: 'temp', codex_home: 'temp', report_dir: 'per-gate' },
22
+ preset: ['release']
23
+ };
24
+ const before = releaseGateCacheKey(tmp, gate);
25
+ const expandedBefore = expandGlob(tmp, 'src/core/release/**');
26
+ fs.writeFileSync(path.join(tmp, 'src/core/release/b.ts'), 'b');
27
+ const afterAdd = releaseGateCacheKey(tmp, gate);
28
+ fs.writeFileSync(path.join(tmp, 'src/core/release/a.ts'), 'changed');
29
+ const afterChange = releaseGateCacheKey(tmp, gate);
30
+ const expandedAfter = expandGlob(tmp, 'src/core/release/**');
31
+ const report = {
32
+ schema: 'sks.release-cache-glob-hashing-check.v1',
33
+ ok: expandedBefore.length === 1 && expandedAfter.length === 2 && before !== afterAdd && afterAdd !== afterChange,
34
+ expanded_before: expandedBefore.map((file) => path.basename(file)),
35
+ expanded_after: expandedAfter.map((file) => path.basename(file)),
36
+ before,
37
+ after_add: afterAdd,
38
+ after_change: afterChange
39
+ };
40
+ assertGate(report.ok, 'release cache key must hash recursive glob file paths and contents', report);
41
+ emitGate('release:cache-glob-hashing', report);
42
+ //# sourceMappingURL=release-cache-glob-hashing-check.js.map
@@ -40,7 +40,7 @@ const invariants = {
40
40
  };
41
41
  const distHash = distHashValue();
42
42
  const gitCommit = gitHead();
43
- const manifestHash = fileHash('release-gates.json');
43
+ const manifestHash = fileHash(fs.existsSync(path.join(root, 'release-gates.v2.json')) ? 'release-gates.v2.json' : 'release-gates.json');
44
44
  const packageScriptsHash = sha256(JSON.stringify(pkg.scripts || {}));
45
45
  const nodeVersion = process.version;
46
46
  const npmVersion = npmVersionValue();
@@ -134,6 +134,25 @@ emitGate('release:check:dynamic:execute', {
134
134
  });
135
135
  // ---- helpers ----------------------------------------------------------------
136
136
  function loadManifest() {
137
+ const v2Path = path.join(root, 'release-gates.v2.json');
138
+ if (fs.existsSync(v2Path)) {
139
+ const parsed = JSON.parse(fs.readFileSync(v2Path, 'utf8'));
140
+ const releaseNodes = (Array.isArray(parsed.gates) ? parsed.gates : []).filter((gate) => Array.isArray(gate.preset) && gate.preset.includes('release'));
141
+ const byId = new Map(releaseNodes.map((gate) => [gate.id, gate]));
142
+ const dynamic = buildGateManifest(releaseNodes.map((gate) => gate.id));
143
+ return {
144
+ schema: 'sks.release-gate-manifest.v1.from-v2',
145
+ gates: dynamic.gates.map((entry) => {
146
+ const node = byId.get(entry.id);
147
+ const resource = Array.isArray(node?.resource) ? node.resource.join(',') : '';
148
+ return {
149
+ ...entry,
150
+ affected_by: usefulCacheInputs(node?.cache?.inputs, entry.affected_by),
151
+ cost: node?.side_effect === 'real-env' || resource.includes('real') ? 'real' : entry.cost
152
+ };
153
+ })
154
+ };
155
+ }
137
156
  const p = path.join(root, 'release-gates.json');
138
157
  if (fs.existsSync(p))
139
158
  return JSON.parse(fs.readFileSync(p, 'utf8'));
@@ -143,6 +162,13 @@ function loadManifest() {
143
162
  const ids = [...new Set([...dagIds, ...releaseCheckIds])].filter((id) => id && id !== 'build' && id !== 'release:check:parallel');
144
163
  return buildGateManifest(ids);
145
164
  }
165
+ function usefulCacheInputs(inputs, fallback) {
166
+ if (!Array.isArray(inputs) || !inputs.length)
167
+ return fallback;
168
+ if (inputs.some((input) => ['src/**', 'package.json', 'release-gates.v2.json', 'schemas/**'].includes(input)))
169
+ return fallback;
170
+ return inputs;
171
+ }
146
172
  function hashAffectedFiles(globs) {
147
173
  const regexes = (globs || []).map(globToRegExp);
148
174
  const hashes = [];