sneakoscope 4.8.7 → 5.3.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 (261) hide show
  1. package/README.md +51 -726
  2. package/config/bench-baseline.json +7 -0
  3. package/crates/sks-core/Cargo.lock +1 -1
  4. package/crates/sks-core/Cargo.toml +1 -1
  5. package/crates/sks-core/src/main.rs +1 -1
  6. package/dist/bin/install.js +35 -0
  7. package/dist/bin/sks.js +1 -1
  8. package/dist/cli/cli-theme.js +51 -0
  9. package/dist/cli/command-registry.js +15 -4
  10. package/dist/cli/help-fast.js +18 -8
  11. package/dist/cli/insane-search-command.js +36 -8
  12. package/dist/cli/install-helpers.js +32 -0
  13. package/dist/commands/codex-lb.js +2 -0
  14. package/dist/commands/doctor.js +94 -9
  15. package/dist/commands/proof.js +6 -3
  16. package/dist/config/skills-manifest.json +82 -54
  17. package/dist/core/agents/agent-conflict-graph.js +5 -0
  18. package/dist/core/agents/agent-lease.js +5 -0
  19. package/dist/core/agents/agent-ledger-schemas.js +1 -0
  20. package/dist/core/agents/agent-lifecycle.js +5 -3
  21. package/dist/core/agents/agent-orchestrator.js +268 -62
  22. package/dist/core/agents/agent-output-validator.js +14 -3
  23. package/dist/core/agents/agent-patch-queue-store.js +95 -2
  24. package/dist/core/agents/agent-patch-queue.js +6 -3
  25. package/dist/core/agents/agent-patch-schema.js +13 -0
  26. package/dist/core/agents/agent-proof-evidence.js +6 -1
  27. package/dist/core/agents/agent-runner-fake.js +40 -0
  28. package/dist/core/agents/agent-worker-pipeline.js +36 -2
  29. package/dist/core/agents/codex-exec-worker-adapter.js +2 -0
  30. package/dist/core/agents/native-cli-worker.js +0 -1
  31. package/dist/core/agents/native-worker-backend-router.js +58 -1
  32. package/dist/core/codex-app/sks-menubar.js +477 -132
  33. package/dist/core/codex-app.js +1 -0
  34. package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
  35. package/dist/core/codex-control/codex-sdk-env-policy.js +1 -1
  36. package/dist/core/codex-control/schemas/agent-worker-result.schema.js +12 -3
  37. package/dist/core/codex-hooks/codex-hook-managed-install.js +5 -0
  38. package/dist/core/codex-hooks/codex-hook-state-writer.js +70 -11
  39. package/dist/core/codex-hooks/codex-hook-trust-doctor.js +2 -2
  40. package/dist/core/codex-lb/codex-lb-env.js +20 -1
  41. package/dist/core/commands/basic-cli.js +18 -1
  42. package/dist/core/commands/check-command.js +37 -5
  43. package/dist/core/commands/computer-use-command.js +62 -7
  44. package/dist/core/commands/db-command.js +31 -5
  45. package/dist/core/commands/fast-mode-command.js +3 -0
  46. package/dist/core/commands/gate-result-contract.js +43 -0
  47. package/dist/core/commands/gates-command.js +6 -1
  48. package/dist/core/commands/gc-command.js +29 -2
  49. package/dist/core/commands/goal-command.js +9 -2
  50. package/dist/core/commands/gx-command.js +79 -7
  51. package/dist/core/commands/image-ux-review-command.js +188 -13
  52. package/dist/core/commands/mad-db-command.js +85 -176
  53. package/dist/core/commands/mad-sks-command.js +230 -29
  54. package/dist/core/commands/menubar-command.js +24 -2
  55. package/dist/core/commands/naruto-command.js +64 -10
  56. package/dist/core/commands/plan-command.js +76 -0
  57. package/dist/core/commands/ppt-command.js +159 -24
  58. package/dist/core/commands/qa-loop-command.js +6 -2
  59. package/dist/core/commands/release-command.js +55 -2
  60. package/dist/core/commands/research-command.js +1 -1
  61. package/dist/core/commands/review-command.js +217 -0
  62. package/dist/core/commands/route-success-helpers.js +59 -0
  63. package/dist/core/commands/run-command.js +19 -7
  64. package/dist/core/commands/seo-command.js +49 -1
  65. package/dist/core/commands/ui-command.js +161 -0
  66. package/dist/core/commands/uninstall-command.js +15 -1
  67. package/dist/core/db-safety.js +2 -2
  68. package/dist/core/doctor/doctor-dirty-planner.js +51 -7
  69. package/dist/core/doctor/doctor-transaction.js +8 -0
  70. package/dist/core/doctor/doctor-zellij-repair.js +1 -0
  71. package/dist/core/doctor/imagegen-repair.js +161 -0
  72. package/dist/core/feature-fixture-runner.js +2 -2
  73. package/dist/core/feature-fixtures.js +58 -37
  74. package/dist/core/feature-registry.js +102 -2
  75. package/dist/core/fsx.js +39 -6
  76. package/dist/core/image-ux-review/imagegen-adapter.js +48 -11
  77. package/dist/core/image-ux-review.js +16 -0
  78. package/dist/core/imagegen/imagegen-capability.js +11 -5
  79. package/dist/core/imagegen/require-imagegen.js +57 -0
  80. package/dist/core/init/skills.js +10 -5
  81. package/dist/core/mad-db/mad-db-coordinator.js +94 -18
  82. package/dist/core/mad-db/mad-db-policy.js +12 -10
  83. package/dist/core/mad-sks/executors/executor-base.js +8 -2
  84. package/dist/core/mad-sks/executors/index.js +4 -0
  85. package/dist/core/mad-sks/executors/sql-plane-executor.js +194 -0
  86. package/dist/core/naruto/naruto-active-pool.js +15 -4
  87. package/dist/core/naruto/naruto-real-worker-child.js +11 -1
  88. package/dist/core/naruto/naruto-role-policy.js +3 -0
  89. package/dist/core/naruto/naruto-task-hints.js +10 -0
  90. package/dist/core/naruto/naruto-work-graph.js +8 -2
  91. package/dist/core/naruto/naruto-work-item.js +6 -0
  92. package/dist/core/naruto/solution-tournament.js +101 -0
  93. package/dist/core/permission-gates.js +30 -0
  94. package/dist/core/pipeline-internals/runtime-core.js +11 -3
  95. package/dist/core/pipeline-internals/runtime-gates.js +27 -0
  96. package/dist/core/ppt-review/index.js +6 -1
  97. package/dist/core/ppt-review/slide-imagegen-review.js +75 -10
  98. package/dist/core/ppt.js +70 -2
  99. package/dist/core/proof/auto-finalize.js +70 -11
  100. package/dist/core/proof/proof-schema.js +2 -0
  101. package/dist/core/proof/route-adapter.js +5 -1
  102. package/dist/core/proof/route-finalizer.js +8 -5
  103. package/dist/core/proof/selftest-proof-fixtures.js +18 -5
  104. package/dist/core/proof/validation.js +2 -0
  105. package/dist/core/provider/model-router.js +53 -0
  106. package/dist/core/release/release-gate-node.js +3 -0
  107. package/dist/core/release/release-gate-resource-governor.js +1 -0
  108. package/dist/core/routes/constants.js +1 -1
  109. package/dist/core/routes.js +65 -18
  110. package/dist/core/safety/mutation-guard.js +5 -1
  111. package/dist/core/stop-gate/gate-evaluator.js +102 -0
  112. package/dist/core/stop-gate/stop-gate-check.js +16 -1
  113. package/dist/core/stop-gate/stop-gate-writer.js +4 -0
  114. package/dist/core/triwiki/agents-md-projector.js +184 -0
  115. package/dist/core/triwiki-wrongness/wrongness-schema.js +3 -1
  116. package/dist/core/trust-kernel/trust-kernel-schema.js +1 -0
  117. package/dist/core/trust-kernel/trust-report.js +1 -1
  118. package/dist/core/trust-kernel/trust-status.js +2 -0
  119. package/dist/core/ui/dashboard-html.js +111 -0
  120. package/dist/core/update/update-migration-state.js +386 -15
  121. package/dist/core/update-check.js +178 -81
  122. package/dist/core/verification/diff-quality.js +100 -0
  123. package/dist/core/verification/impact-scan.js +164 -0
  124. package/dist/core/verification/machine-feedback.js +146 -0
  125. package/dist/core/verification/mistake-rule-compiler.js +195 -0
  126. package/dist/core/version.js +1 -1
  127. package/dist/core/zellij/zellij-self-heal.js +9 -6
  128. package/dist/core/zellij/zellij-slot-telemetry.js +1 -1
  129. package/dist/scripts/check-feature-quality.js +2 -2
  130. package/dist/scripts/cli-output-consistency-check.js +57 -0
  131. package/dist/scripts/coding-bench-check.js +136 -0
  132. package/dist/scripts/doctor-imagegen-repair-check.js +62 -0
  133. package/dist/scripts/ensure-bin-executable.js +11 -3
  134. package/dist/scripts/harness-benchmark-check.js +104 -0
  135. package/dist/scripts/legacy-update-e2e-check.js +228 -0
  136. package/dist/scripts/mad-db-command-check.js +11 -8
  137. package/dist/scripts/mad-db-route-identity-check.js +8 -6
  138. package/dist/scripts/mad-db-safety-conflict-matrix-check.js +31 -8
  139. package/dist/scripts/mad-db-skill-policy-snapshot-check.js +4 -5
  140. package/dist/scripts/naruto-real-active-pool-runtime-check.js +26 -1
  141. package/dist/scripts/ppt-real-imagegen-wiring-check.js +5 -1
  142. package/dist/scripts/proof-root-cause-policy-check.js +2 -0
  143. package/dist/scripts/release-dag-full-coverage-check.js +13 -4
  144. package/dist/scripts/release-gate-dag-runner.js +14 -0
  145. package/dist/scripts/release-gate-existence-audit.js +2 -1
  146. package/dist/scripts/release-gate-planner.js +2 -1
  147. package/dist/scripts/release-gate-script-parity-check.js +45 -17
  148. package/dist/scripts/release-metadata-1-19-check.js +2 -1
  149. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +1 -1
  150. package/dist/scripts/sks-1-11-gate-lib.js +8 -6
  151. package/dist/scripts/sks-menubar-install-check.js +57 -20
  152. package/dist/scripts/ux-review-real-loop-fixture-check.js +9 -4
  153. package/dist/scripts/ux-review-run-wires-imagegen-check.js +4 -0
  154. package/dist/scripts/zellij-slot-telemetry-runtime-check.js +1 -0
  155. package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
  156. package/docs/demo.tape +28 -0
  157. package/package.json +31 -24
  158. package/schemas/codex/completion-proof.schema.json +3 -2
  159. package/schemas/release/release-gate-node.schema.json +2 -1
  160. package/dist/scripts/agent-backfill-route-blackbox.js +0 -5
  161. package/dist/scripts/agent-dynamic-pool-route-blackbox.js +0 -5
  162. package/dist/scripts/agent-parallel-write-blackbox.js +0 -56
  163. package/dist/scripts/agent-patch-swarm-route-blackbox.js +0 -10
  164. package/dist/scripts/agent-route-blackbox-lib.js +0 -132
  165. package/dist/scripts/blackbox-command-import-smoke.js +0 -143
  166. package/dist/scripts/blackbox-global-shim.js +0 -77
  167. package/dist/scripts/blackbox-matrix.js +0 -70
  168. package/dist/scripts/blackbox-npx-one-shot.js +0 -69
  169. package/dist/scripts/blackbox-pack-install.js +0 -174
  170. package/dist/scripts/brand-neutrality-zero-leakage-blackbox.js +0 -4
  171. package/dist/scripts/build-once-runner-blackbox.js +0 -34
  172. package/dist/scripts/codex-0140-integration-blackbox.js +0 -13
  173. package/dist/scripts/codex-agent-type-blackbox.js +0 -4
  174. package/dist/scripts/codex-app-harness-blackbox.js +0 -4
  175. package/dist/scripts/codex-app-skill-agent-blackbox.js +0 -4
  176. package/dist/scripts/codex-hook-approval-blackbox.js +0 -4
  177. package/dist/scripts/codex-init-deep-directory-local-blackbox.js +0 -4
  178. package/dist/scripts/codex-native-feature-broker-blackbox.js +0 -4
  179. package/dist/scripts/codex-native-pattern-analysis-blackbox.js +0 -4
  180. package/dist/scripts/codex-native-read-repair-split-blackbox.js +0 -55
  181. package/dist/scripts/codex-native-reference-cache-blackbox.js +0 -41
  182. package/dist/scripts/core-skill-integrity-blackbox.js +0 -32
  183. package/dist/scripts/dfix-fast-blackbox-check.js +0 -37
  184. package/dist/scripts/dfix-parallel-write-blackbox.js +0 -48
  185. package/dist/scripts/dfix-patch-swarm-route-blackbox.js +0 -10
  186. package/dist/scripts/doctor-dirty-repair-blackbox.js +0 -22
  187. package/dist/scripts/doctor-dirty-semantic-blackbox.js +0 -8
  188. package/dist/scripts/doctor-native-capability-repair-blackbox.js +0 -39
  189. package/dist/scripts/doctor-transaction-engine-blackbox.js +0 -28
  190. package/dist/scripts/doctor-zellij-fix-blackbox.js +0 -4
  191. package/dist/scripts/doctor-zellij-no-homebrew-blackbox.js +0 -4
  192. package/dist/scripts/doctor-zellij-upgrade-blackbox.js +0 -4
  193. package/dist/scripts/gate-pack-runner-blackbox.js +0 -27
  194. package/dist/scripts/gate-pack-v2-blackbox.js +0 -18
  195. package/dist/scripts/geo-cli-blackbox-check.js +0 -18
  196. package/dist/scripts/loop-collision-blackbox.js +0 -3
  197. package/dist/scripts/loop-concurrency-oversubscription-blackbox.js +0 -3
  198. package/dist/scripts/loop-fixture-production-misuse-blackbox.js +0 -3
  199. package/dist/scripts/loop-kill-interrupt-real-blackbox.js +0 -3
  200. package/dist/scripts/loop-merge-strategy-blackbox.js +0 -3
  201. package/dist/scripts/loop-mesh-production-e2e-blackbox.js +0 -3
  202. package/dist/scripts/loop-side-effect-blackbox.js +0 -3
  203. package/dist/scripts/mad-db-operation-lifecycle-blackbox.js +0 -29
  204. package/dist/scripts/mad-sks-actual-executor-blackbox.js +0 -5
  205. package/dist/scripts/mad-zellij-headless-fallback-blackbox.js +0 -4
  206. package/dist/scripts/mad-zellij-self-heal-blackbox.js +0 -4
  207. package/dist/scripts/naruto-loop-mesh-blackbox.js +0 -3
  208. package/dist/scripts/naruto-real-parallelism-blackbox.js +0 -307
  209. package/dist/scripts/naruto-worktree-coding-blackbox.js +0 -29
  210. package/dist/scripts/parallel-runtime-real-blackbox.js +0 -44
  211. package/dist/scripts/pipeline-codex-native-doctor-mad-routing-real-blackbox.js +0 -77
  212. package/dist/scripts/pipeline-codex-native-e2e-blackbox.js +0 -13
  213. package/dist/scripts/pipeline-codex-native-image-routing-real-blackbox.js +0 -50
  214. package/dist/scripts/pipeline-codex-native-loop-routing-real-blackbox.js +0 -74
  215. package/dist/scripts/pipeline-codex-native-qa-routing-real-blackbox.js +0 -51
  216. package/dist/scripts/pipeline-codex-native-research-routing-real-blackbox.js +0 -45
  217. package/dist/scripts/pipeline-execution-profile-routing-blackbox.js +0 -4
  218. package/dist/scripts/postinstall-global-doctor-blackbox.js +0 -12
  219. package/dist/scripts/ppt-full-e2e-blackbox-check.js +0 -109
  220. package/dist/scripts/ppt-imagegen-blackbox-check.js +0 -46
  221. package/dist/scripts/qa-backfill-route-blackbox.js +0 -5
  222. package/dist/scripts/qa-patch-swarm-route-blackbox.js +0 -10
  223. package/dist/scripts/release-full-parallelism-blackbox.js +0 -41
  224. package/dist/scripts/release-triwiki-first-runner-blackbox.js +0 -48
  225. package/dist/scripts/release-wiring-3110-blackbox.js +0 -27
  226. package/dist/scripts/release-wiring-3112-blackbox.js +0 -17
  227. package/dist/scripts/release-wiring-3113-blackbox.js +0 -17
  228. package/dist/scripts/research-backfill-route-blackbox.js +0 -5
  229. package/dist/scripts/research-final-reviewer-blackbox.js +0 -70
  230. package/dist/scripts/research-stage-cycle-runtime-blackbox.js +0 -40
  231. package/dist/scripts/research-synthesis-writer-blackbox.js +0 -24
  232. package/dist/scripts/route-blackbox-realism-check.js +0 -21
  233. package/dist/scripts/scheduler-resource-claim-blackbox.js +0 -24
  234. package/dist/scripts/seo-cli-blackbox-check.js +0 -18
  235. package/dist/scripts/sks-3110-all-feature-regression-blackbox.js +0 -116
  236. package/dist/scripts/sks-3112-all-feature-regression-blackbox.js +0 -29
  237. package/dist/scripts/sks-3113-all-feature-regression-blackbox.js +0 -17
  238. package/dist/scripts/sks-400-all-feature-regression-blackbox.js +0 -21
  239. package/dist/scripts/sks-400-extreme-parallel-blackbox.js +0 -8
  240. package/dist/scripts/sks-400-five-minute-blackbox.js +0 -9
  241. package/dist/scripts/sks-400-legacy-purge-blackbox.js +0 -8
  242. package/dist/scripts/sks-401-all-feature-regression-blackbox.js +0 -46
  243. package/dist/scripts/sks-401-five-minute-actual-blackbox.js +0 -23
  244. package/dist/scripts/sks-402-all-feature-regression-blackbox.js +0 -9
  245. package/dist/scripts/sks-402-five-minute-real-blackbox.js +0 -22
  246. package/dist/scripts/sksd-warm-cache-blackbox.js +0 -12
  247. package/dist/scripts/team-backfill-route-blackbox.js +0 -5
  248. package/dist/scripts/team-parallel-write-blackbox.js +0 -55
  249. package/dist/scripts/team-patch-swarm-route-blackbox.js +0 -10
  250. package/dist/scripts/triwiki-affected-graph-blackbox.js +0 -28
  251. package/dist/scripts/triwiki-proof-bank-blackbox.js +0 -30
  252. package/dist/scripts/triwiki-proof-bank-lock-blackbox.js +0 -7
  253. package/dist/scripts/ux-patch-swarm-route-blackbox.js +0 -10
  254. package/dist/scripts/ux-review-imagegen-blackbox-check.js +0 -67
  255. package/dist/scripts/zellij-initial-main-only-blackbox.js +0 -28
  256. package/dist/scripts/zellij-pane-lock-concurrency-blackbox.js +0 -80
  257. package/dist/scripts/zellij-pane-lock-open-worker-integration-blackbox.js +0 -137
  258. package/dist/scripts/zellij-self-heal-typed-blackbox.js +0 -4
  259. package/dist/scripts/zellij-slot-telemetry-real-blackbox.js +0 -20
  260. package/dist/scripts/zellij-stacked-fallback-integration-blackbox.js +0 -81
  261. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +0 -202
@@ -0,0 +1,217 @@
1
+ import path from 'node:path';
2
+ import { flag } from '../../cli/args.js';
3
+ import { printJson } from '../../cli/output.js';
4
+ import { projectRoot, readText, runProcess, writeJsonAtomic, nowIso } from '../fsx.js';
5
+ import { ui } from '../../cli/cli-theme.js';
6
+ import { runCompiledRules } from '../verification/mistake-rule-compiler.js';
7
+ const LENSES = [
8
+ 'review-correctness',
9
+ 'review-security',
10
+ 'review-lean',
11
+ 'review-regression'
12
+ ];
13
+ export async function reviewCommand(args = []) {
14
+ const root = path.resolve(String(readOption(args, '--root', '') || await projectRoot()));
15
+ const diff = await collectDiff(root, args);
16
+ if (!diff.files.length) {
17
+ const report = buildReport({ files: [], machine: { checks: [], findings: [] }, lenses: [], fix: null });
18
+ if (flag(args, '--json'))
19
+ return printJson(report);
20
+ ui.banner('review');
21
+ ui.ok('변경 없음');
22
+ return report;
23
+ }
24
+ const [machine, lenses] = await Promise.all([
25
+ runMachineChecks(root, diff),
26
+ runReadOnlyReviewLenses(diff)
27
+ ]);
28
+ let fix = null;
29
+ if (flag(args, '--fix'))
30
+ fix = await attemptMachineFix(root, machine.findings);
31
+ const report = buildReport({ files: diff.files, machine, lenses, fix });
32
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'review-report.json'), report);
33
+ if (flag(args, '--json'))
34
+ return printJson(report);
35
+ printReviewReport(report);
36
+ if (report.verdict === 'blocked')
37
+ process.exitCode = 1;
38
+ return report;
39
+ }
40
+ async function collectDiff(root, args) {
41
+ const staged = flag(args, '--staged');
42
+ const ref = String(readOption(args, '--diff', '') || '');
43
+ const nameArgs = staged ? ['diff', '--staged', '--name-only'] : ref ? ['diff', ref, '--name-only'] : ['diff', '--name-only'];
44
+ const diffArgs = staged ? ['diff', '--staged', '--no-ext-diff'] : ref ? ['diff', ref, '--no-ext-diff'] : ['diff', '--no-ext-diff'];
45
+ const names = await runProcess('git', nameArgs, { cwd: root, timeoutMs: 15000, maxOutputBytes: 256 * 1024 });
46
+ const body = await runProcess('git', diffArgs, { cwd: root, timeoutMs: 15000, maxOutputBytes: 2 * 1024 * 1024 });
47
+ const files = String(names.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
48
+ return {
49
+ mode: staged ? 'staged' : ref ? `diff:${ref}` : 'worktree',
50
+ files,
51
+ text: body.stdout || '',
52
+ command: ['git', ...diffArgs].join(' ')
53
+ };
54
+ }
55
+ async function runMachineChecks(root, diff) {
56
+ const findings = [];
57
+ const checks = [];
58
+ findings.push(...await conflictMarkerFindings(root, diff.files));
59
+ findings.push(...secretPatternFindings(diff.text));
60
+ const mistakeRules = await runCompiledRules(root, diff.files).catch((err) => ({
61
+ ok: false,
62
+ violations: [{ rule_id: 'mistake-rule-runner', severity: 'error', file: '', line: 0, description: err instanceof Error ? err.message : String(err), good_example: '' }],
63
+ rule_count: 0
64
+ }));
65
+ checks.push({ command: 'sks mistake rules', ok: mistakeRules.ok });
66
+ for (const violation of mistakeRules.violations || []) {
67
+ findings.push({
68
+ id: `machine:mistake-rule:${violation.rule_id}:${violation.file}:${violation.line}`,
69
+ severity: violation.severity === 'error' ? 'blocker' : 'medium',
70
+ evidence: 'machine',
71
+ source: 'mistake-rule',
72
+ ...(violation.file ? { file: violation.file } : {}),
73
+ ...(violation.line ? { line: violation.line } : {}),
74
+ message: `${violation.description}${violation.good_example ? ` | good: ${singleLine(violation.good_example)}` : ''}`
75
+ });
76
+ }
77
+ if (diff.files.some((file) => /\.(ts|tsx|mts|cts)$/.test(file))) {
78
+ const command = 'tsc -p tsconfig.json --noEmit';
79
+ const result = await runProcess('npx', ['tsc', '-p', 'tsconfig.json', '--noEmit'], {
80
+ cwd: root,
81
+ timeoutMs: 120000,
82
+ maxOutputBytes: 512 * 1024
83
+ });
84
+ checks.push({ command, ok: result.code === 0, code: result.code });
85
+ if (result.code !== 0) {
86
+ findings.push({
87
+ id: 'machine:typecheck',
88
+ severity: 'blocker',
89
+ evidence: 'machine',
90
+ source: 'typescript',
91
+ message: summarizeProcessOutput(result.stderr || result.stdout || 'TypeScript check failed.'),
92
+ command
93
+ });
94
+ }
95
+ }
96
+ return { checks, findings, summary: { check_count: checks.length, finding_count: findings.length } };
97
+ }
98
+ async function conflictMarkerFindings(root, files) {
99
+ const findings = [];
100
+ for (const file of files) {
101
+ const abs = path.join(root, file);
102
+ const text = await readText(abs, '').catch(() => '');
103
+ const lines = String(text || '').split(/\r?\n/);
104
+ for (let index = 0; index < lines.length; index += 1) {
105
+ if (/^(<<<<<<<|=======|>>>>>>>)(?:\s|$)/.test(lines[index] || '')) {
106
+ findings.push({
107
+ id: `machine:conflict:${file}:${index + 1}`,
108
+ severity: 'blocker',
109
+ evidence: 'machine',
110
+ source: 'conflict-marker-scan',
111
+ file,
112
+ line: index + 1,
113
+ message: 'Git conflict marker remains in a changed file.'
114
+ });
115
+ }
116
+ }
117
+ }
118
+ return findings;
119
+ }
120
+ function secretPatternFindings(diffText) {
121
+ const findings = [];
122
+ const patterns = [
123
+ /(?:api[_-]?key|secret|token|password)\s*[:=]\s*['"][^'"]{12,}['"]/i,
124
+ /sk-[A-Za-z0-9_-]{20,}/
125
+ ];
126
+ const lines = diffText.split(/\r?\n/);
127
+ for (let index = 0; index < lines.length; index += 1) {
128
+ const line = lines[index] || '';
129
+ if (!line.startsWith('+') || line.startsWith('+++'))
130
+ continue;
131
+ if (patterns.some((pattern) => pattern.test(line))) {
132
+ findings.push({
133
+ id: `machine:secret:${index + 1}`,
134
+ severity: 'blocker',
135
+ evidence: 'machine',
136
+ source: 'diff-secret-scan',
137
+ line: index + 1,
138
+ message: 'Added diff line looks like a secret or credential.'
139
+ });
140
+ }
141
+ }
142
+ return findings;
143
+ }
144
+ async function runReadOnlyReviewLenses(diff) {
145
+ return LENSES.map((role) => ({
146
+ role,
147
+ evidence: 'llm',
148
+ status: 'not_run',
149
+ findings: [],
150
+ unverified: [`${role} native read-only worker not run in this local review invocation`],
151
+ files: diff.files.length
152
+ }));
153
+ }
154
+ async function attemptMachineFix(root, findings) {
155
+ const machine = findings.filter((finding) => finding.evidence === 'machine');
156
+ if (!machine.length)
157
+ return { attempted: false, reason: 'no_machine_findings' };
158
+ const pkg = JSON.parse(await readText(path.join(root, 'package.json'), '{}'));
159
+ if (pkg?.scripts?.lint && String(pkg.scripts.lint).includes('--fix')) {
160
+ const result = await runProcess('npm', ['run', 'lint', '--', '--fix'], { cwd: root, timeoutMs: 120000, maxOutputBytes: 512 * 1024 });
161
+ return { attempted: true, command: 'npm run lint -- --fix', ok: result.code === 0, code: result.code };
162
+ }
163
+ return { attempted: false, reason: 'no_safe_machine_fixer_available', machine_findings: machine.length };
164
+ }
165
+ function buildReport(input) {
166
+ const findings = dedupeFindings([...(input.machine.findings || []), ...input.lenses.flatMap((lens) => lens.findings || [])]);
167
+ const verdict = findings.some((finding) => finding.severity === 'blocker') ? 'blocked' : findings.length ? 'needs_attention' : 'clean';
168
+ return {
169
+ schema: 'sks.review-report.v1',
170
+ generated_at: nowIso(),
171
+ files: input.files.length,
172
+ machine: input.machine.summary || { check_count: 0, finding_count: 0 },
173
+ lenses: input.lenses,
174
+ findings,
175
+ fix: input.fix,
176
+ verdict
177
+ };
178
+ }
179
+ function dedupeFindings(findings) {
180
+ const seen = new Set();
181
+ const severityRank = { blocker: 0, high: 1, medium: 2, low: 3 };
182
+ return findings
183
+ .filter((finding) => {
184
+ const key = `${finding.evidence}:${finding.source}:${finding.file || ''}:${finding.line || ''}:${finding.message}`;
185
+ if (seen.has(key))
186
+ return false;
187
+ seen.add(key);
188
+ return true;
189
+ })
190
+ .sort((a, b) => a.evidence === b.evidence ? severityRank[a.severity] - severityRank[b.severity] : a.evidence === 'machine' ? -1 : 1);
191
+ }
192
+ function printReviewReport(report) {
193
+ ui.banner('review');
194
+ const ok = report.verdict === 'clean';
195
+ if (ok)
196
+ ui.ok(`clean (${report.files} files)`);
197
+ else
198
+ ui.warn(`${report.verdict} (${report.findings.length} findings across ${report.files} files)`);
199
+ for (const finding of report.findings.slice(0, 20)) {
200
+ const where = finding.file ? `${finding.file}${finding.line ? `:${finding.line}` : ''}` : finding.command || finding.source;
201
+ ui.kv(`${finding.evidence}/${finding.severity}`, `${where} - ${finding.message}`);
202
+ }
203
+ }
204
+ function summarizeProcessOutput(text) {
205
+ return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 8).join(' | ').slice(0, 900);
206
+ }
207
+ function singleLine(value) {
208
+ return String(value || '').replace(/\s+/g, ' ').trim().slice(0, 240);
209
+ }
210
+ function readOption(args = [], name, fallback = null) {
211
+ const index = args.indexOf(name);
212
+ if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
213
+ return args[index + 1];
214
+ const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
215
+ return prefixed ? prefixed.slice(name.length + 1) : fallback;
216
+ }
217
+ //# sourceMappingURL=review-command.js.map
@@ -0,0 +1,59 @@
1
+ import path from 'node:path';
2
+ import { exists, readJson, readText } from '../fsx.js';
3
+ export async function context7EvidenceStatus(root, missionId = null) {
4
+ const candidates = [
5
+ missionId ? path.join(root, '.sneakoscope', 'missions', missionId, 'context7-evidence.jsonl') : null,
6
+ path.join(root, '.sneakoscope', 'state', 'context7-evidence.jsonl'),
7
+ path.join(root, '.sneakoscope', 'memory', 'q2_facts', 'stack-current-docs.md')
8
+ ].filter(Boolean);
9
+ for (const file of candidates) {
10
+ if (!(await exists(file)))
11
+ continue;
12
+ const text = await readText(file, '');
13
+ if (/\S/.test(text) && /(context7|resolve[-_]?library|query[-_]?docs|get[-_]?library|official docs|vendor docs)/i.test(text)) {
14
+ return { ok: true, policy: 'required_satisfied', evidence: path.relative(root, file).split(path.sep).join('/') };
15
+ }
16
+ }
17
+ return { ok: false, policy: 'required_not_satisfied', evidence: null, blocker: 'context7_policy_required_not_satisfied' };
18
+ }
19
+ export async function artifactPresence(root, dir, artifacts) {
20
+ const rows = await Promise.all(artifacts.map(async (artifact) => {
21
+ const file = path.isAbsolute(artifact) ? artifact : path.join(dir, artifact);
22
+ return { artifact, present: await exists(file) };
23
+ }));
24
+ const missing = rows.filter((row) => !row.present).map((row) => row.artifact);
25
+ return { ok: missing.length === 0, rows, missing };
26
+ }
27
+ export async function evaluateLocalGate(input) {
28
+ const missionDir = input.dir || (input.missionId ? path.join(input.root, '.sneakoscope', 'missions', input.missionId) : input.root);
29
+ const gatePath = path.isAbsolute(input.gateFile) ? input.gateFile : path.join(missionDir, input.gateFile);
30
+ const gate = await readJson(gatePath, null);
31
+ const blockers = [];
32
+ if (!gate)
33
+ blockers.push(`${path.basename(input.gateFile)}_missing`);
34
+ if (gate) {
35
+ if (!(gate.passed === true || gate.ok === true || gate.status === 'pass' || gate.status === 'passed'))
36
+ blockers.push('gate_not_passed');
37
+ if (gate.ok === false)
38
+ blockers.push('gate_ok_false');
39
+ if (Array.isArray(gate.blockers) && gate.blockers.length)
40
+ blockers.push(...gate.blockers.map(String));
41
+ if (Array.isArray(gate.missing_fields) && gate.missing_fields.length)
42
+ blockers.push(...gate.missing_fields.map((field) => `missing_field:${field}`));
43
+ for (const section of input.requiredSections || []) {
44
+ if (gate[section] == null)
45
+ blockers.push(`missing_gate_section:${section}`);
46
+ }
47
+ }
48
+ const artifactCheck = await artifactPresence(input.root, missionDir, input.requiredArtifacts || []);
49
+ blockers.push(...artifactCheck.missing.map((artifact) => `missing_artifact:${artifact}`));
50
+ const uniqueBlockers = [...new Set(blockers)];
51
+ return {
52
+ ok: uniqueBlockers.length === 0,
53
+ gate,
54
+ gate_path: gatePath,
55
+ blockers: uniqueBlockers,
56
+ artifacts: artifactCheck
57
+ };
58
+ }
59
+ //# sourceMappingURL=route-success-helpers.js.map
@@ -62,7 +62,15 @@ export async function runCommand(args = []) {
62
62
  return finalizeMockRun(root, { id, route, prompt, args, classification, mode });
63
63
  }
64
64
  async function finalizeMockRun(root, { id, route, prompt, args, classification, mode, }) {
65
- const gate = { schema: 'sks.run-gate.v1', ok: true, passed: true, route: route.command, mock: true };
65
+ const gate = {
66
+ schema: 'sks.run-gate.v1',
67
+ ok: false,
68
+ passed: false,
69
+ route: route.command,
70
+ mock: true,
71
+ execution_class: 'mock_fixture',
72
+ blockers: ['run_mock_fixture_cannot_claim_real_completion']
73
+ };
66
74
  await writeJsonAtomic(path.join(missionDir(root, id), 'run-gate.json'), gate);
67
75
  const proof = await finalizeRoute(root, {
68
76
  missionId: id,
@@ -76,19 +84,20 @@ async function finalizeMockRun(root, { id, route, prompt, args, classification,
76
84
  command: { cmd: `sks run "${prompt}" --mock`, status: 0 },
77
85
  });
78
86
  const trust = await loadTrustReport(root, id);
87
+ const completionOk = proof.ok && proof.proof?.status !== 'mock_only' && gate.passed === true;
79
88
  await setCurrent(root, {
80
89
  mission_id: id,
81
90
  mode: 'RUN',
82
91
  route: route.id,
83
92
  route_command: route.command,
84
- phase: proof.ok ? 'RUN_MOCK_FINALIZED' : 'RUN_MOCK_BLOCKED',
93
+ phase: completionOk ? 'RUN_MOCK_FINALIZED' : 'RUN_MOCK_BLOCKED',
85
94
  implementation_allowed: true,
86
95
  completion_proof: 'completion-proof.json',
87
96
  trust_report: 'trust-report.json',
88
97
  });
89
98
  const result = {
90
99
  schema: 'sks.run.v2',
91
- ok: proof.ok,
100
+ ok: completionOk,
92
101
  mission_id: id,
93
102
  route: route.command,
94
103
  mode,
@@ -98,6 +107,8 @@ async function finalizeMockRun(root, { id, route, prompt, args, classification,
98
107
  completion_proof: { ok: proof.ok, validation: proof.validation },
99
108
  trust_report: trust,
100
109
  };
110
+ if (!result.ok)
111
+ process.exitCode = 1;
101
112
  if (flag(args, '--json')) {
102
113
  console.log(JSON.stringify(result, null, 2));
103
114
  return result;
@@ -235,7 +246,7 @@ async function executeRouteCommand(root, route, prompt, { auto = false } = {}) {
235
246
  return routeExecutionResult(route, ['sks', ...commandArgs].join(' '), result, {
236
247
  okStatus: 'completed',
237
248
  trustStatus: 'verified_partial',
238
- executionKind: route.command === '$DB' || route.command === '$Wiki' || route.command === '$Fast-Mode' || route.command === '$with-local-llm-on' || route.command === '$Commit' || route.command === '$Commit-And-Push' || route.command === '$Ultra-Search' || route.command === '$SEO-GEO-OPTIMIZER' ? 'safe_deterministic' : 'mock_safe',
249
+ executionKind: 'live_route',
239
250
  });
240
251
  }
241
252
  async function runAutoVerification(root, missionId) {
@@ -304,7 +315,7 @@ function routeExecutionResult(route, command, result, options = {}) {
304
315
  schema: 'sks.run-route-execution.v1',
305
316
  ok,
306
317
  status: ok ? (options.okStatus || 'completed') : 'blocked',
307
- execution_kind: ok ? (options.executionKind || 'safe_deterministic') : 'blocked',
318
+ execution_kind: ok ? (options.executionKind || 'live_route') : 'blocked',
308
319
  route: route.command,
309
320
  command,
310
321
  exit_code: result.code,
@@ -314,7 +325,7 @@ function routeExecutionResult(route, command, result, options = {}) {
314
325
  trust_status: ok ? options.trustStatus || 'verified_partial' : 'blocked',
315
326
  blockers: ok ? [] : ['route_command_failed'],
316
327
  unverified: ok
317
- ? options.unverified || ['sks run --execute used the deterministic safe route command path; real external dependencies remain route-specific.']
328
+ ? options.unverified || ['sks run --execute ran the selected route command; route-specific gates remain authoritative for final trust.']
318
329
  : [],
319
330
  next_action: ok ? 'review completion proof and trust report' : 'inspect run-route-execution.json stderr_tail',
320
331
  };
@@ -538,7 +549,7 @@ async function writeLightweightTrustReport(root, missionId, route, statusHint, p
538
549
  const report = {
539
550
  schema: TRUST_REPORT_SCHEMA,
540
551
  ...trustKernelMetadata(),
541
- ok: proofOk && !['blocked', 'failed', 'not_verified'].includes(status),
552
+ ok: proofOk && !['blocked', 'failed', 'not_verified', 'mock_only'].includes(status),
542
553
  mission_id: missionId,
543
554
  route,
544
555
  status,
@@ -551,6 +562,7 @@ async function writeLightweightTrustReport(root, missionId, route, statusHint, p
551
562
  lightweight: true,
552
563
  reason: 'safe_deterministic_run_wrapper'
553
564
  },
565
+ trust_basis: 'lightweight',
554
566
  evidence: {
555
567
  completion_proof: `.sneakoscope/missions/${missionId}/completion-proof.json`,
556
568
  route_contract: null,
@@ -2,7 +2,8 @@ import path from 'node:path';
2
2
  import { printJson } from '../../cli/output.js';
3
3
  import { projectRoot } from '../fsx.js';
4
4
  import { flag, readOption } from './command-utils.js';
5
- import { runSearchVisibilityApply, runSearchVisibilityAudit, runSearchVisibilityDoctor, runSearchVisibilityFixture, runSearchVisibilityPlan, runSearchVisibilityRollback, runSearchVisibilityStatus, runSearchVisibilityVerify, } from '../search-visibility/index.js';
5
+ import { runSearchVisibilityApply, runSearchVisibilityAudit, runSearchVisibilityDoctor, runSearchVisibilityFixture, runSearchVisibilityPlan, runSearchVisibilityRollback, runSearchVisibilityStatus, runSearchVisibilityVerify, resolveSearchVisibilityMission, } from '../search-visibility/index.js';
6
+ import { evaluateLocalGate } from './route-success-helpers.js';
6
7
  export async function seoCommand(args = []) {
7
8
  return runSearchVisibilityCommand('seo', args, 'seo');
8
9
  }
@@ -15,6 +16,26 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
15
16
  const rest = args.slice(1);
16
17
  const options = await parseOptions(rest);
17
18
  let result;
19
+ if (action === 'apply' && options.apply) {
20
+ const mission = await resolveSearchVisibilityMission(options.root, firstPositional(rest) || 'latest');
21
+ const blockers = [];
22
+ if (!mission)
23
+ blockers.push('mission_required_for_apply');
24
+ else {
25
+ const planGate = await evaluateLocalGate({ root: mission.root, dir: mission.dir, gateFile: mode === 'seo' ? 'seo-gate.json' : 'geo-gate.json', requiredArtifacts: ['search-visibility/mutation-plan.json'] });
26
+ blockers.push(...planGate.blockers.filter((blocker) => blocker !== 'gate_not_passed' && blocker !== 'gate_ok_false'));
27
+ }
28
+ if (blockers.length) {
29
+ result = { schema: 'sks.search-visibility.apply-command.v1', ok: false, route: mode === 'seo' ? '$SEO-GEO-OPTIMIZER' : '$SEO-GEO-OPTIMIZER', status: 'blocked', blockers };
30
+ process.exitCode = 1;
31
+ if (options.json) {
32
+ printJson(result);
33
+ return result;
34
+ }
35
+ printHuman(mode, action, result);
36
+ return result;
37
+ }
38
+ }
18
39
  if (action === 'doctor')
19
40
  result = await runSearchVisibilityDoctor(mode, options);
20
41
  else if (action === 'audit')
@@ -33,6 +54,7 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
33
54
  result = await runSearchVisibilityFixture(mode, options);
34
55
  else
35
56
  return usage(mode, 2, displayCommand);
57
+ result = await applySearchVisibilityGateResult(mode, action, result, options);
36
58
  if (isBlocked(result))
37
59
  process.exitCode = 1;
38
60
  if (options.json) {
@@ -42,6 +64,32 @@ export async function runSearchVisibilityCommand(mode, args = [], displayCommand
42
64
  printHuman(mode, action, result);
43
65
  return result;
44
66
  }
67
+ async function applySearchVisibilityGateResult(mode, action, result, options) {
68
+ if (!result || typeof result !== 'object' || !result.mission_id)
69
+ return result;
70
+ const mission = await resolveSearchVisibilityMission(options.root, result.mission_id);
71
+ if (!mission)
72
+ return { ...result, ok: false, blockers: [...(result.blockers || []), 'mission_missing_for_gate_evaluation'] };
73
+ const requiredArtifacts = action === 'apply'
74
+ ? ['search-visibility/mutation-plan.json', 'search-visibility/rollback-manifest.json', 'search-visibility/verification-report.json']
75
+ : action === 'status'
76
+ ? []
77
+ : ['search-visibility/verification-report.json'];
78
+ const evaluated = await evaluateLocalGate({
79
+ root: mission.root,
80
+ dir: mission.dir,
81
+ gateFile: mode === 'seo' ? 'seo-gate.json' : 'geo-gate.json',
82
+ requiredArtifacts
83
+ });
84
+ const blockers = [...new Set([...(result.blockers || []), ...evaluated.blockers])];
85
+ return {
86
+ ...result,
87
+ ok: result.ok === true && evaluated.ok === true,
88
+ status: result.ok === true && evaluated.ok === true ? result.status : 'blocked',
89
+ gate_evaluation: evaluated,
90
+ blockers
91
+ };
92
+ }
45
93
  function normalizeOptimizerArgs(args) {
46
94
  const out = [...args];
47
95
  const first = String(out[0] || '').toLowerCase();
@@ -0,0 +1,161 @@
1
+ import * as http from 'node:http';
2
+ import path from 'node:path';
3
+ import { flag } from '../../cli/args.js';
4
+ import { printJson } from '../../cli/output.js';
5
+ import { ui } from '../../cli/cli-theme.js';
6
+ import { DASHBOARD_HTML } from '../ui/dashboard-html.js';
7
+ import { findLatestMission, missionDir } from '../mission.js';
8
+ import { nowIso, projectRoot, readJson, readText, runProcess } from '../fsx.js';
9
+ import { readZellijSlotTelemetrySnapshot } from '../zellij/zellij-slot-telemetry.js';
10
+ export async function uiCommand(args = []) {
11
+ const root = path.resolve(String(readOption(args, '--root', '') || await projectRoot()));
12
+ if (flag(args, '--once')) {
13
+ const state = await collectUiState(root, String(readOption(args, '--mission', 'latest') || 'latest'));
14
+ if (flag(args, '--json'))
15
+ return printJson(state);
16
+ ui.banner('ui');
17
+ ui.ok(`state ${state.mission_id || 'latest'} ${state.gates.length} gates`);
18
+ return state;
19
+ }
20
+ const requestedPort = Number(readOption(args, '--port', '4477'));
21
+ const port = Number.isFinite(requestedPort) && requestedPort > 0 ? Math.floor(requestedPort) : 4477;
22
+ const mission = String(readOption(args, '--mission', 'latest') || 'latest');
23
+ const server = http.createServer(async (req, res) => {
24
+ try {
25
+ const url = new URL(req.url || '/', 'http://127.0.0.1');
26
+ if (url.pathname === '/') {
27
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
28
+ res.end(DASHBOARD_HTML);
29
+ return;
30
+ }
31
+ if (url.pathname === '/events')
32
+ return sseStream(res, root, String(url.searchParams.get('mission') || mission));
33
+ if (url.pathname === '/api/state') {
34
+ res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
35
+ res.end(JSON.stringify(await collectUiState(root, String(url.searchParams.get('mission') || mission))));
36
+ return;
37
+ }
38
+ res.writeHead(404);
39
+ res.end('not found');
40
+ }
41
+ catch (err) {
42
+ res.writeHead(500, { 'content-type': 'application/json; charset=utf-8' });
43
+ res.end(JSON.stringify({ ok: false, error: sanitizeText(err?.message || String(err)) }));
44
+ }
45
+ });
46
+ const actualPort = await listenLocal(server, port);
47
+ const url = `http://127.0.0.1:${actualPort}`;
48
+ ui.banner('ui');
49
+ ui.ok(`dashboard ${url}`);
50
+ void runProcess('/usr/bin/open', [url], { timeoutMs: 5000, maxOutputBytes: 4096 }).catch(() => undefined);
51
+ return new Promise(() => undefined);
52
+ }
53
+ export async function collectUiState(root, missionInput = 'latest') {
54
+ const missionId = missionInput === 'latest' ? await findLatestMission(root) : missionInput;
55
+ const snapshot = missionId ? await readZellijSlotTelemetrySnapshot(root, missionId).catch(() => null) : null;
56
+ const gates = missionId ? await readLatestGateSummaries(root, missionId) : [];
57
+ const events = missionId ? await tailJsonl(path.join(missionDir(root, missionId), 'events.jsonl'), 30) : [];
58
+ const mission = missionId ? await readJson(path.join(missionDir(root, missionId), 'mission.json'), null).catch(() => null) : null;
59
+ return sanitizeForUi({
60
+ schema: 'sks.ui-state.v1',
61
+ ok: true,
62
+ ts: nowIso(),
63
+ mission_id: missionId,
64
+ route: mission?.mode || mission?.route || null,
65
+ snapshot,
66
+ gates,
67
+ events
68
+ });
69
+ }
70
+ async function sseStream(res, root, mission) {
71
+ res.writeHead(200, {
72
+ 'content-type': 'text/event-stream',
73
+ 'cache-control': 'no-cache, no-store',
74
+ connection: 'keep-alive'
75
+ });
76
+ const tick = async () => {
77
+ const state = await collectUiState(root, mission).catch((err) => ({ ok: false, ts: nowIso(), error: sanitizeText(err?.message || String(err)) }));
78
+ res.write(`data: ${JSON.stringify(state)}\n\n`);
79
+ };
80
+ await tick();
81
+ const timer = setInterval(tick, 1000);
82
+ res.on('close', () => clearInterval(timer));
83
+ }
84
+ async function readLatestGateSummaries(root, missionId) {
85
+ const dir = missionDir(root, missionId);
86
+ const files = ['stop-gate.json', 'naruto-gate.json', 'team-gate.json', 'qa-gate.json', 'reflection-gate.json', 'completion-proof.json'];
87
+ const rows = [];
88
+ for (const file of files) {
89
+ const gate = await readJson(path.join(dir, file), null).catch(() => null);
90
+ if (!gate)
91
+ continue;
92
+ rows.push({
93
+ id: file,
94
+ ok: gate.passed === true || gate.ok === true || gate.status === 'passed',
95
+ missing: Array.isArray(gate.missing) ? gate.missing : Array.isArray(gate.blockers) ? gate.blockers : [],
96
+ source: `.sneakoscope/missions/${missionId}/${file}`
97
+ });
98
+ }
99
+ return rows;
100
+ }
101
+ async function tailJsonl(file, limit) {
102
+ const text = await readText(file, '').catch(() => '');
103
+ return String(text || '').split(/\r?\n/).filter(Boolean).slice(-limit).map((line) => {
104
+ try {
105
+ return JSON.parse(line);
106
+ }
107
+ catch {
108
+ return { ts: null, type: 'raw', text: line.slice(0, 500) };
109
+ }
110
+ });
111
+ }
112
+ async function listenLocal(server, startPort) {
113
+ for (let offset = 0; offset < 20; offset += 1) {
114
+ const port = startPort + offset;
115
+ const result = await new Promise((resolve) => {
116
+ const onError = (err) => {
117
+ server.off('listening', onListen);
118
+ resolve({ ok: false, code: err.code || 'listen_failed' });
119
+ };
120
+ const onListen = () => {
121
+ server.off('error', onError);
122
+ resolve({ ok: true });
123
+ };
124
+ server.once('error', onError);
125
+ server.once('listening', onListen);
126
+ server.listen(port, '127.0.0.1');
127
+ });
128
+ if (result.ok)
129
+ return port;
130
+ if (result.code !== 'EADDRINUSE')
131
+ throw new Error(`dashboard listen failed: ${result.code}`);
132
+ }
133
+ throw new Error('dashboard listen failed: no available localhost port');
134
+ }
135
+ function sanitizeForUi(value) {
136
+ if (Array.isArray(value))
137
+ return value.map((item) => sanitizeForUi(item));
138
+ if (!value || typeof value !== 'object')
139
+ return typeof value === 'string' ? sanitizeText(value) : value;
140
+ const out = {};
141
+ for (const [key, child] of Object.entries(value)) {
142
+ if (/(?:key|token|secret|password|credential|cookie|auth)/i.test(key))
143
+ continue;
144
+ out[key] = sanitizeForUi(child);
145
+ }
146
+ return out;
147
+ }
148
+ function sanitizeText(value) {
149
+ return String(value || '')
150
+ .replace(/sk-[A-Za-z0-9_-]{12,}/g, '[redacted]')
151
+ .replace(/(?:api[_-]?key|secret|token|password|credential)/ig, '[redacted-field]')
152
+ .slice(0, 2000);
153
+ }
154
+ function readOption(args = [], name, fallback = null) {
155
+ const index = args.indexOf(name);
156
+ if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
157
+ return args[index + 1];
158
+ const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
159
+ return prefixed ? prefixed.slice(name.length + 1) : fallback;
160
+ }
161
+ //# sourceMappingURL=ui-command.js.map
@@ -4,10 +4,12 @@ import fsp from 'node:fs/promises';
4
4
  import readline from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import { ensureDir, exists, readJson, readText, runProcess, writeJsonAtomic } from '../fsx.js';
7
+ import { ui as cliUi } from '../../cli/cli-theme.js';
7
8
  import { uninstallSksMenuBar } from '../codex-app/sks-menubar.js';
8
9
  import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
9
10
  import { sweepSksTempDirs } from '../retention.js';
10
11
  import { reconcileSkills } from '../init/skills.js';
12
+ import { removeTriwikiAgentsMdBlocks } from '../triwiki/agents-md-projector.js';
11
13
  export async function uninstallCommand(args = []) {
12
14
  const opts = {
13
15
  yes: hasFlag(args, '--yes') || hasFlag(args, '-y'),
@@ -24,8 +26,11 @@ export async function uninstallCommand(args = []) {
24
26
  const result = { schema: 'sks.uninstall.v1', ok: true, dry_run: true, inventory };
25
27
  if (opts.json)
26
28
  console.log(JSON.stringify(result, null, 2));
27
- else
29
+ else {
30
+ cliUi.banner('uninstall');
31
+ cliUi.warn('dry run only');
28
32
  printInventoryTable(inventory);
33
+ }
29
34
  return result;
30
35
  }
31
36
  if (!opts.yes && !(await confirmUninstall())) {
@@ -66,6 +71,8 @@ export async function uninstallCommand(args = []) {
66
71
  if (opts.json)
67
72
  console.log(JSON.stringify(report, null, 2));
68
73
  else {
74
+ cliUi.banner('uninstall');
75
+ report.ok ? cliUi.ok('removed configured SKS surfaces') : cliUi.fail('uninstall completed with errors');
69
76
  console.log('SKS uninstall complete.');
70
77
  console.log('Final package removal command: npm uninstall -g sneakoscope');
71
78
  }
@@ -84,6 +91,7 @@ async function collectSksInventory(opts) {
84
91
  ['hooks-json', path.join(home, '.codex', 'hooks.json'), 'strip SKS-managed hooks', '--keep-config'],
85
92
  ['sks-home', path.join(home, '.sneakoscope'), 'remove', '--keep-data'],
86
93
  ['sks-global-home', path.join(home, '.sneakoscope-global'), 'remove', '--keep-data'],
94
+ ['agents-md-memory-block', path.join(opts.root, 'AGENTS.md'), 'strip SKS Project Memory block', '--purge-projects'],
87
95
  ['tmp', os.tmpdir(), 'sweep sks temp dirs', 'default']
88
96
  ];
89
97
  const out = [];
@@ -263,6 +271,12 @@ async function purgeProjectArtifacts(report, opts) {
263
271
  report.errors.push({ id: 'project-skills', root, error: err?.message || String(err) });
264
272
  });
265
273
  await fsp.rm(path.join(root, '.codex'), { recursive: true, force: true }).catch(() => null);
274
+ const removedBlocks = await removeTriwikiAgentsMdBlocks(root).catch((err) => {
275
+ report.errors.push({ id: 'agents-md-memory-block', root, error: err?.message || String(err) });
276
+ return [];
277
+ });
278
+ if (removedBlocks.length)
279
+ report.removed.push({ id: 'agents-md-memory-block', root, result: { ok: true, files: removedBlocks.length } });
266
280
  await fsp.rm(path.join(root, 'SNEAKOSCOPE.md'), { force: true }).catch(() => null);
267
281
  if (!opts.keepData)
268
282
  await fsp.rm(path.join(root, '.sneakoscope'), { recursive: true, force: true }).catch(() => null);