soturail 0.2.1

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 (149) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +205 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.js +54 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/bench.d.ts +31 -0
  7. package/dist/commands/bench.js +387 -0
  8. package/dist/commands/bench.js.map +1 -0
  9. package/dist/commands/dedupe.d.ts +2 -0
  10. package/dist/commands/dedupe.js +16 -0
  11. package/dist/commands/dedupe.js.map +1 -0
  12. package/dist/commands/doctor.d.ts +14 -0
  13. package/dist/commands/doctor.js +122 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/expand.d.ts +3 -0
  16. package/dist/commands/expand.js +23 -0
  17. package/dist/commands/expand.js.map +1 -0
  18. package/dist/commands/format.d.ts +6 -0
  19. package/dist/commands/format.js +48 -0
  20. package/dist/commands/format.js.map +1 -0
  21. package/dist/commands/hooks.d.ts +11 -0
  22. package/dist/commands/hooks.js +248 -0
  23. package/dist/commands/hooks.js.map +1 -0
  24. package/dist/commands/index.d.ts +3 -0
  25. package/dist/commands/index.js +28 -0
  26. package/dist/commands/index.js.map +1 -0
  27. package/dist/commands/ingest.d.ts +6 -0
  28. package/dist/commands/ingest.js +52 -0
  29. package/dist/commands/ingest.js.map +1 -0
  30. package/dist/commands/init.d.ts +7 -0
  31. package/dist/commands/init.js +179 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/memory.d.ts +39 -0
  34. package/dist/commands/memory.js +184 -0
  35. package/dist/commands/memory.js.map +1 -0
  36. package/dist/commands/native.d.ts +2 -0
  37. package/dist/commands/native.js +17 -0
  38. package/dist/commands/native.js.map +1 -0
  39. package/dist/commands/read.d.ts +8 -0
  40. package/dist/commands/read.js +87 -0
  41. package/dist/commands/read.js.map +1 -0
  42. package/dist/commands/rules.d.ts +5 -0
  43. package/dist/commands/rules.js +62 -0
  44. package/dist/commands/rules.js.map +1 -0
  45. package/dist/commands/run.d.ts +22 -0
  46. package/dist/commands/run.js +200 -0
  47. package/dist/commands/run.js.map +1 -0
  48. package/dist/commands/spec.d.ts +11 -0
  49. package/dist/commands/spec.js +217 -0
  50. package/dist/commands/spec.js.map +1 -0
  51. package/dist/commands/stats.d.ts +15 -0
  52. package/dist/commands/stats.js +82 -0
  53. package/dist/commands/stats.js.map +1 -0
  54. package/dist/compressors/agent-response-reducer.d.ts +13 -0
  55. package/dist/compressors/agent-response-reducer.js +160 -0
  56. package/dist/compressors/agent-response-reducer.js.map +1 -0
  57. package/dist/compressors/generic-stream.d.ts +6 -0
  58. package/dist/compressors/generic-stream.js +63 -0
  59. package/dist/compressors/generic-stream.js.map +1 -0
  60. package/dist/compressors/git-reducer.d.ts +2 -0
  61. package/dist/compressors/git-reducer.js +78 -0
  62. package/dist/compressors/git-reducer.js.map +1 -0
  63. package/dist/compressors/index.d.ts +10 -0
  64. package/dist/compressors/index.js +84 -0
  65. package/dist/compressors/index.js.map +1 -0
  66. package/dist/compressors/json-toon.d.ts +14 -0
  67. package/dist/compressors/json-toon.js +149 -0
  68. package/dist/compressors/json-toon.js.map +1 -0
  69. package/dist/compressors/test-reducer.d.ts +2 -0
  70. package/dist/compressors/test-reducer.js +50 -0
  71. package/dist/compressors/test-reducer.js.map +1 -0
  72. package/dist/core/cache-normalizer.d.ts +23 -0
  73. package/dist/core/cache-normalizer.js +170 -0
  74. package/dist/core/cache-normalizer.js.map +1 -0
  75. package/dist/core/config.d.ts +104 -0
  76. package/dist/core/config.js +186 -0
  77. package/dist/core/config.js.map +1 -0
  78. package/dist/core/dedupe-store.d.ts +17 -0
  79. package/dist/core/dedupe-store.js +44 -0
  80. package/dist/core/dedupe-store.js.map +1 -0
  81. package/dist/core/document-ingest.d.ts +15 -0
  82. package/dist/core/document-ingest.js +45 -0
  83. package/dist/core/document-ingest.js.map +1 -0
  84. package/dist/core/file-scanner.d.ts +47 -0
  85. package/dist/core/file-scanner.js +248 -0
  86. package/dist/core/file-scanner.js.map +1 -0
  87. package/dist/core/git.d.ts +2 -0
  88. package/dist/core/git.js +29 -0
  89. package/dist/core/git.js.map +1 -0
  90. package/dist/core/metrics-store.d.ts +20 -0
  91. package/dist/core/metrics-store.js +19 -0
  92. package/dist/core/metrics-store.js.map +1 -0
  93. package/dist/core/native-engine.d.ts +25 -0
  94. package/dist/core/native-engine.js +137 -0
  95. package/dist/core/native-engine.js.map +1 -0
  96. package/dist/core/native-runner-adapter.d.ts +18 -0
  97. package/dist/core/native-runner-adapter.js +60 -0
  98. package/dist/core/native-runner-adapter.js.map +1 -0
  99. package/dist/core/raw-store.d.ts +28 -0
  100. package/dist/core/raw-store.js +57 -0
  101. package/dist/core/raw-store.js.map +1 -0
  102. package/dist/core/response-policy.d.ts +11 -0
  103. package/dist/core/response-policy.js +14 -0
  104. package/dist/core/response-policy.js.map +1 -0
  105. package/dist/core/rule-extractor.d.ts +18 -0
  106. package/dist/core/rule-extractor.js +116 -0
  107. package/dist/core/rule-extractor.js.map +1 -0
  108. package/dist/core/rule-validator.d.ts +9 -0
  109. package/dist/core/rule-validator.js +85 -0
  110. package/dist/core/rule-validator.js.map +1 -0
  111. package/dist/core/safety-policy.d.ts +10 -0
  112. package/dist/core/safety-policy.js +69 -0
  113. package/dist/core/safety-policy.js.map +1 -0
  114. package/dist/core/token-estimator.d.ts +2 -0
  115. package/dist/core/token-estimator.js +7 -0
  116. package/dist/core/token-estimator.js.map +1 -0
  117. package/dist/types/index.d.ts +4 -0
  118. package/dist/types/index.js +2 -0
  119. package/dist/types/index.js.map +1 -0
  120. package/docs/architecture.md +47 -0
  121. package/docs/assets/screenshots/README.md +18 -0
  122. package/docs/assets/screenshots/init.svg +13 -0
  123. package/docs/assets/screenshots/run-expand.svg +13 -0
  124. package/docs/assets/screenshots/stats.svg +11 -0
  125. package/docs/assets/soturail-fox.svg +17 -0
  126. package/docs/assets/soturail-icon.svg +10 -0
  127. package/docs/benchmarking.md +34 -0
  128. package/docs/branding.md +14 -0
  129. package/docs/hooks/claude.md +24 -0
  130. package/docs/hooks/codex.md +5 -0
  131. package/docs/hooks/cursor.md +5 -0
  132. package/docs/hooks/gemini.md +5 -0
  133. package/docs/hooks/host-capabilities.json +22 -0
  134. package/docs/hooks.md +15 -0
  135. package/docs/knowledge-to-rules.md +23 -0
  136. package/docs/metrics.md +35 -0
  137. package/docs/mvp.md +21 -0
  138. package/docs/native-runner.md +52 -0
  139. package/docs/prompt-caching.md +27 -0
  140. package/docs/pt-BR/visao-geral.md +26 -0
  141. package/docs/public-roadmap.md +20 -0
  142. package/docs/reducers.md +19 -0
  143. package/docs/release-checklist.md +13 -0
  144. package/docs/response-compression.md +15 -0
  145. package/docs/rules.md +23 -0
  146. package/docs/security-model.md +32 -0
  147. package/docs/spec-driven-workflow.md +22 -0
  148. package/docs/usage.md +64 -0
  149. package/package.json +71 -0
@@ -0,0 +1,387 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
5
+ import { performance } from "node:perf_hooks";
6
+ import { promisify } from "node:util";
7
+ import { reduceAgentResponse } from "../compressors/agent-response-reducer.js";
8
+ import { compressOutputWithEngine } from "../compressors/index.js";
9
+ import { ensureWorkspace, writeJson } from "../core/config.js";
10
+ import { ingestDocument } from "../core/document-ingest.js";
11
+ import { MetricsStore } from "../core/metrics-store.js";
12
+ import { detectNativeEngine } from "../core/native-engine.js";
13
+ import { extractRules } from "../core/rule-extractor.js";
14
+ import { estimateTokens } from "../core/token-estimator.js";
15
+ import { validateRules } from "../core/rule-validator.js";
16
+ const execFileAsync = promisify(execFile);
17
+ function sha256(text) {
18
+ return createHash("sha256").update(text).digest("hex");
19
+ }
20
+ function benchDirs(root) {
21
+ return {
22
+ fixtures: path.resolve(root, "benchmarks", "fixtures"),
23
+ results: path.resolve(root, "benchmarks", "results"),
24
+ reports: path.resolve(root, "benchmarks", "reports")
25
+ };
26
+ }
27
+ async function writeFixture(root, name, content) {
28
+ const dirs = benchDirs(root);
29
+ await fs.mkdir(dirs.fixtures, { recursive: true });
30
+ await fs.writeFile(path.resolve(dirs.fixtures, name), content, "utf8");
31
+ }
32
+ export async function prepareBenchmarks(root = process.cwd()) {
33
+ const gitDiff = [
34
+ "On branch main",
35
+ "Changes not staged for commit:",
36
+ " modified: src/app.ts",
37
+ " deleted: src/old.ts",
38
+ "diff --git a/src/app.ts b/src/app.ts",
39
+ "index 1111111..2222222 100644",
40
+ "--- a/src/app.ts",
41
+ "+++ b/src/app.ts",
42
+ "@@ -1,3 +1,5 @@",
43
+ " export function app() {",
44
+ "- return 1;",
45
+ "+ return 2;",
46
+ "+ console.log('changed');",
47
+ " }",
48
+ ...Array.from({ length: 300 }, (_, index) => `+ repeated diff noise ${index}`)
49
+ ].join("\n");
50
+ const testOutput = [
51
+ "FAIL tests/app.test.ts > app > returns expected value",
52
+ "AssertionError: expected 1 to equal 2",
53
+ "Expected: 2",
54
+ "Received: 1",
55
+ " at tests/app.test.ts:12:10",
56
+ "Traceback (most recent call last):",
57
+ " File \"tests/test_app.py\", line 7, in test_app",
58
+ "AssertionError: boom",
59
+ ...Array.from({ length: 250 }, () => "PASS tests/noise.test.ts")
60
+ ].join("\n");
61
+ const noisyJson = JSON.stringify({
62
+ status: "error",
63
+ message: "Permission denied",
64
+ path: "src/app.ts",
65
+ items: Array.from({ length: 120 }, (_, index) => ({ id: index, ok: index !== 77, error: index === 77 ? "timeout" : null }))
66
+ }, null, 2);
67
+ const noisyLog = [
68
+ ...Array.from({ length: 150 }, () => "server ok healthcheck passed"),
69
+ "WARN permission refused for src/secret.ts:4",
70
+ "ERROR timeout while reading src/app.ts:22",
71
+ ...Array.from({ length: 150 }, () => "server ok healthcheck passed")
72
+ ].join("\n");
73
+ const largeCode = [
74
+ ...Array.from({ length: 80 }, (_, index) => `export function helper${index}() { return ${index}; }`),
75
+ "export class CriticalService { run() { return 'ok'; } }",
76
+ ...Array.from({ length: 80 }, (_, index) => `def py_helper_${index}(): return ${index}`),
77
+ "public class App { public void run() {} }"
78
+ ].join("\n");
79
+ const verboseAi = [
80
+ "I think the main issue is probably that tests are failing because src/app.ts:12 returns the wrong value.",
81
+ "Security warning: do not run rm -rf or git push automatically.",
82
+ "Run npm test after changing src/app.ts.",
83
+ "```ts",
84
+ "export function app() {",
85
+ " return 2;",
86
+ "}",
87
+ "```",
88
+ ...Array.from({ length: 40 }, () => "Basically, this is just extra explanation that can be shorter.")
89
+ ].join("\n");
90
+ const rulesDoc = [
91
+ "# Runtime",
92
+ "- Project must run on Node.js 20 or newer.",
93
+ "- README.md must include a section named Quick start.",
94
+ "- LICENSE must be present.",
95
+ "- .github/workflows/ci.yml is required for CI workflow.",
96
+ "# Security",
97
+ "- git push must never be run automatically through soturail run."
98
+ ].join("\n");
99
+ await writeFixture(root, "noisy-git-diff.txt", gitDiff);
100
+ await writeFixture(root, "noisy-test-output.txt", testOutput);
101
+ await writeFixture(root, "noisy-json.json", noisyJson);
102
+ await writeFixture(root, "noisy-log.txt", noisyLog);
103
+ await writeFixture(root, "large-code-file.txt", largeCode);
104
+ await writeFixture(root, "verbose-ai-answer.md", verboseAi);
105
+ await writeFixture(root, "rules-doc.md", rulesDoc);
106
+ return "Benchmark fixtures prepared in benchmarks/fixtures.\n";
107
+ }
108
+ async function readFixture(root, name) {
109
+ return fs.readFile(path.resolve(benchDirs(root).fixtures, name), "utf8");
110
+ }
111
+ function compressionPercent(rawTokens, reducedTokens) {
112
+ return rawTokens === 0 ? 0 : Number((((rawTokens - reducedTokens) / rawTokens) * 100).toFixed(2));
113
+ }
114
+ async function terminalResult(root, fixture, command, engine) {
115
+ const raw = await readFixture(root, fixture);
116
+ const start = performance.now();
117
+ const reduced = await compressOutputWithEngine(command, raw, `bench-${fixture}`, engine, root);
118
+ const wall = performance.now() - start;
119
+ const text = reduced.summary;
120
+ const rawTokens = estimateTokens(raw);
121
+ const reducedTokens = estimateTokens(text);
122
+ const quality = qualityCheck(fixture, text);
123
+ if (!quality.ok) {
124
+ throw new Error(`Benchmark quality check failed for ${fixture}: ${quality.reason}`);
125
+ }
126
+ return {
127
+ name: fixture,
128
+ category: "terminal_compression",
129
+ engine: reduced.engine,
130
+ raw_bytes: Buffer.byteLength(raw),
131
+ reduced_bytes: Buffer.byteLength(text),
132
+ estimated_raw_tokens: rawTokens,
133
+ estimated_reduced_tokens: reducedTokens,
134
+ compression_ratio_percent: compressionPercent(rawTokens, reducedTokens),
135
+ wall_time_ms: Number(wall.toFixed(3)),
136
+ preserved_error_lines_count: (text.match(/\b(error|warn|fail|assertion|timeout|denied|refused)\b/gi) ?? []).length,
137
+ preserved_file_paths_count: (text.match(/(?:src|tests)[\\/][\w.-]+(?::\d+(?::\d+)?)?/g) ?? []).length,
138
+ raw_id: null,
139
+ raw_sha256: sha256(raw),
140
+ reduced_sha256: sha256(text),
141
+ quality_passed: true,
142
+ details: { compressor: reduced.compressor }
143
+ };
144
+ }
145
+ async function jsonResult(root, engine) {
146
+ const raw = await readFixture(root, "noisy-json.json");
147
+ const start = performance.now();
148
+ const reduced = await compressOutputWithEngine("cat output.json", raw, "bench-noisy-json", engine, root);
149
+ const wall = performance.now() - start;
150
+ const text = reduced.summary;
151
+ const rawTokens = estimateTokens(raw);
152
+ const reducedTokens = estimateTokens(text);
153
+ const quality = qualityCheck("noisy-json.json", text);
154
+ if (!quality.ok) {
155
+ throw new Error(`Benchmark quality check failed for noisy-json.json: ${quality.reason}`);
156
+ }
157
+ return {
158
+ name: "noisy-json.json",
159
+ category: "json_tool_payload_compression",
160
+ engine: reduced.engine,
161
+ raw_bytes: Buffer.byteLength(raw),
162
+ reduced_bytes: Buffer.byteLength(text),
163
+ estimated_raw_tokens: rawTokens,
164
+ estimated_reduced_tokens: reducedTokens,
165
+ compression_ratio_percent: compressionPercent(rawTokens, reducedTokens),
166
+ wall_time_ms: Number(wall.toFixed(3)),
167
+ preserved_error_lines_count: (text.match(/\b(error|denied|timeout|permission)\b/gi) ?? []).length,
168
+ preserved_file_paths_count: (text.match(/src[\\/][\w.-]+(?::\d+(?::\d+)?)?/g) ?? []).length,
169
+ raw_id: null,
170
+ raw_sha256: sha256(raw),
171
+ reduced_sha256: sha256(text),
172
+ quality_passed: true,
173
+ details: reduced.details ?? {}
174
+ };
175
+ }
176
+ function qualityCheck(fixture, text) {
177
+ if (fixture.includes("test") && (!text.includes("AssertionError") || !text.includes("tests/app.test.ts"))) {
178
+ return { ok: false, reason: "test assertion or path missing" };
179
+ }
180
+ if (fixture.includes("git") && (!text.includes("src/app.ts") || !text.includes("@@ -1,3 +1,5 @@"))) {
181
+ return { ok: false, reason: "git path or hunk missing" };
182
+ }
183
+ if (fixture.includes("json") && (!text.includes("Permission denied") || !text.includes("timeout"))) {
184
+ return { ok: false, reason: "JSON error primitives missing" };
185
+ }
186
+ if (fixture.includes("log") && (!text.includes("permission refused") || !text.includes("src/app.ts:22"))) {
187
+ return { ok: false, reason: "log signal missing" };
188
+ }
189
+ return { ok: true };
190
+ }
191
+ async function responseResult(root, mode) {
192
+ const raw = await readFixture(root, "verbose-ai-answer.md");
193
+ const start = performance.now();
194
+ const reduced = reduceAgentResponse(raw, mode);
195
+ const wall = performance.now() - start;
196
+ return {
197
+ name: `verbose-ai-answer-${mode}`,
198
+ category: "agent_response_compression",
199
+ engine: "ts",
200
+ raw_bytes: Buffer.byteLength(raw),
201
+ reduced_bytes: Buffer.byteLength(reduced.output),
202
+ estimated_raw_tokens: reduced.raw_tokens,
203
+ estimated_reduced_tokens: reduced.compressed_tokens,
204
+ compression_ratio_percent: reduced.reduction_percent,
205
+ wall_time_ms: Number(wall.toFixed(3)),
206
+ preserved_error_lines_count: 0,
207
+ preserved_file_paths_count: reduced.preserved_paths_count,
208
+ raw_id: null,
209
+ raw_sha256: sha256(raw),
210
+ reduced_sha256: sha256(reduced.output),
211
+ quality_passed: reduced.output.includes("npm test") && reduced.output.includes("Security warning"),
212
+ details: {
213
+ mode,
214
+ preserved_commands_count: reduced.preserved_commands_count,
215
+ preserved_code_blocks_count: reduced.preserved_code_blocks_count,
216
+ preserved_security_warnings_count: reduced.preserved_security_warnings_count
217
+ }
218
+ };
219
+ }
220
+ async function rulesResult(root) {
221
+ const raw = await readFixture(root, "rules-doc.md");
222
+ const fixturePath = path.resolve(benchDirs(root).fixtures, "rules-doc.md");
223
+ const start = performance.now();
224
+ const document = await ingestDocument(fixturePath, "requirements", root);
225
+ const rules = extractRules(document);
226
+ const checks = await validateRules(rules, root);
227
+ const output = JSON.stringify({ rules, checks }, null, 2);
228
+ const wall = performance.now() - start;
229
+ return {
230
+ name: "rules-doc-extraction",
231
+ category: "knowledge_structuring",
232
+ engine: "ts",
233
+ raw_bytes: Buffer.byteLength(raw),
234
+ reduced_bytes: Buffer.byteLength(output),
235
+ estimated_raw_tokens: estimateTokens(raw),
236
+ estimated_reduced_tokens: estimateTokens(output),
237
+ compression_ratio_percent: null,
238
+ wall_time_ms: Number(wall.toFixed(3)),
239
+ preserved_error_lines_count: 0,
240
+ preserved_file_paths_count: (output.match(/\bREADME\.md|LICENSE|ci\.yml/g) ?? []).length,
241
+ raw_id: null,
242
+ raw_sha256: sha256(raw),
243
+ reduced_sha256: sha256(output),
244
+ quality_passed: rules.length >= 3,
245
+ details: {
246
+ extracted_rules_count: rules.length,
247
+ raw_document_tokens: estimateTokens(raw),
248
+ structured_rule_tokens: estimateTokens(output),
249
+ reuse_value_note: "Structured rules may be larger than the seed document but are reusable, citable and validator-friendly.",
250
+ validator_success_count: checks.filter((check) => check.ok).length,
251
+ validator_failure_count: checks.filter((check) => !check.ok).length,
252
+ citations_count: rules.length,
253
+ source_sections_preserved_count: new Set(rules.map((rule) => rule.source_section)).size,
254
+ manual_expansion_count: 0
255
+ }
256
+ };
257
+ }
258
+ export async function runBenchmarks(options = {}, root = process.cwd()) {
259
+ await ensureWorkspace(root);
260
+ await prepareBenchmarks(root);
261
+ const engine = options.engine ?? "ts";
262
+ if (engine === "native" && !(await detectNativeEngine(root)).available) {
263
+ const dirs = benchDirs(root);
264
+ await fs.mkdir(dirs.results, { recursive: true });
265
+ await fs.mkdir(dirs.reports, { recursive: true });
266
+ const report = [
267
+ "# SotuRail Benchmark Report",
268
+ "",
269
+ "Native engine was requested but `soturail-native` is not available. Build it with `npm run build:native`.",
270
+ ""
271
+ ].join("\n");
272
+ await writeJson(path.resolve(dirs.results, "latest.json"), { generated_at: new Date().toISOString(), engine, native_available: false, results: [] });
273
+ await fs.writeFile(path.resolve(dirs.reports, "latest.md"), report, "utf8");
274
+ return [];
275
+ }
276
+ const results = [
277
+ await terminalResult(root, "noisy-git-diff.txt", "git diff", engine),
278
+ await terminalResult(root, "noisy-test-output.txt", "npm test", engine),
279
+ await terminalResult(root, "noisy-log.txt", "docker logs app", engine),
280
+ await jsonResult(root, engine),
281
+ await responseResult(root, "concise"),
282
+ await responseResult(root, "review"),
283
+ await responseResult(root, "debug"),
284
+ await responseResult(root, "commit"),
285
+ await responseResult(root, "docs"),
286
+ await rulesResult(root)
287
+ ];
288
+ const dirs = benchDirs(root);
289
+ await fs.mkdir(dirs.results, { recursive: true });
290
+ await fs.mkdir(dirs.reports, { recursive: true });
291
+ await writeJson(path.resolve(dirs.results, "latest.json"), { generated_at: new Date().toISOString(), engine, results });
292
+ await fs.writeFile(path.resolve(dirs.reports, "latest.md"), renderReport(results), "utf8");
293
+ await new MetricsStore(root).append({ type: "bench_run", details: { engine, results_count: results.length } });
294
+ return results;
295
+ }
296
+ export async function compareEngines(root = process.cwd()) {
297
+ const native = await detectNativeEngine(root);
298
+ if (!native.available) {
299
+ return "Native engine not available. Build it with npm run build:native before comparing engines.\n";
300
+ }
301
+ const tsResults = await runBenchmarks({ engine: "ts" }, root);
302
+ const nativeResults = await runBenchmarks({ engine: "native" }, root);
303
+ const rows = tsResults.map((tsResult) => {
304
+ const nativeResult = nativeResults.find((item) => item.name === tsResult.name && item.category === tsResult.category);
305
+ if (!nativeResult) {
306
+ return `| ${tsResult.name} | ${tsResult.category} | n/a | n/a | n/a |`;
307
+ }
308
+ const speedup = nativeResult.wall_time_ms === 0
309
+ ? 0
310
+ : Number((((tsResult.wall_time_ms - nativeResult.wall_time_ms) / tsResult.wall_time_ms) * 100).toFixed(2));
311
+ return `| ${tsResult.name} | ${tsResult.category} | ${tsResult.wall_time_ms} | ${nativeResult.wall_time_ms} | ${speedup}% |`;
312
+ });
313
+ const report = [
314
+ "# SotuRail Engine Comparison",
315
+ "",
316
+ "| Fixture | Category | TypeScript wall_time_ms | Native wall_time_ms | speedup_percent |",
317
+ "|---|---|---:|---:|---:|",
318
+ ...rows,
319
+ ""
320
+ ].join("\n");
321
+ const reportPath = path.resolve(benchDirs(root).reports, "engine-comparison.md");
322
+ await fs.writeFile(reportPath, report, "utf8");
323
+ return `${reportPath}\n`;
324
+ }
325
+ export async function reportBenchmarks(root = process.cwd()) {
326
+ const reportPath = path.resolve(benchDirs(root).reports, "latest.md");
327
+ return fs.readFile(reportPath, "utf8").catch(() => "No benchmark report found. Run soturail bench run first.\n");
328
+ }
329
+ function renderReport(results) {
330
+ const grouped = groupByCategory(results);
331
+ const rows = results.map((result) => `| ${result.name} | ${result.category} | ${result.engine} | ${result.estimated_raw_tokens} | ${result.estimated_reduced_tokens} | ${result.compression_ratio_percent === null ? "n/a" : `${result.compression_ratio_percent}%`} | ${result.wall_time_ms} | ${result.quality_passed ? "pass" : "fail"} |`);
332
+ return [
333
+ "# SotuRail Benchmark Report",
334
+ "",
335
+ "Generated from deterministic local fixtures. No external RTK/Squeez comparison numbers are included unless a user runs optional comparison locally.",
336
+ "",
337
+ "Categories:",
338
+ ...Object.entries(grouped).map(([category, count]) => `- ${category}: ${count} case(s)`),
339
+ "",
340
+ "| Fixture | Category | Engine | Raw tokens | Reduced/structured tokens | compression_ratio_percent | wall_time_ms | Quality |",
341
+ "|---|---|---:|---:|---:|---:|---:|---|",
342
+ ...rows,
343
+ "",
344
+ "Includes terminal compression, agent response compression, JSON/tool payload compression, knowledge structuring and native performance readiness cases.",
345
+ "",
346
+ "Knowledge-to-Rules is reported as reusable structuring, not pure compression."
347
+ ].join("\n") + "\n";
348
+ }
349
+ function groupByCategory(results) {
350
+ return results.reduce((acc, result) => {
351
+ acc[result.category] = (acc[result.category] ?? 0) + 1;
352
+ return acc;
353
+ }, {});
354
+ }
355
+ export async function compareOptional(tool, root = process.cwd()) {
356
+ if (!["rtk", "squeez"].includes(tool)) {
357
+ throw new Error("Optional comparison tool must be rtk or squeez.");
358
+ }
359
+ const locator = process.platform === "win32" ? "where" : "which";
360
+ try {
361
+ const { stdout } = await execFileAsync(locator, [tool], { cwd: root, windowsHide: true, timeout: 3000 });
362
+ return `Optional tool detected: ${tool}\nPath: ${stdout.trim().split(/\r?\n/)[0]}\nRun comparison manually with your chosen fixture; SotuRail does not install or depend on ${tool}.\n`;
363
+ }
364
+ catch {
365
+ return `Optional tool not found on PATH: ${tool}. No comparison was run.\n`;
366
+ }
367
+ }
368
+ export function registerBenchCommand(program) {
369
+ const bench = program.command("bench").description("Run reproducible local SotuRail benchmarks.");
370
+ bench.command("prepare").description("Generate deterministic benchmark fixtures.").action(async () => {
371
+ process.stdout.write(await prepareBenchmarks());
372
+ });
373
+ bench.command("run").description("Run benchmarks and write latest JSON/Markdown reports.").option("--engine <engine>", "ts, native, or auto", "ts").action(async (options) => {
374
+ const results = await runBenchmarks(options);
375
+ process.stdout.write(`Benchmark results written: ${results.length} cases\nbenchmarks/results/latest.json\nbenchmarks/reports/latest.md\n`);
376
+ });
377
+ bench.command("report").description("Print the latest benchmark report.").action(async () => {
378
+ process.stdout.write(await reportBenchmarks());
379
+ });
380
+ bench.command("compare-optional").description("Detect optional external comparison tool without installing it.").option("--tool <tool>", "rtk or squeez").action(async (options) => {
381
+ process.stdout.write(await compareOptional(options.tool ?? ""));
382
+ });
383
+ bench.command("compare-engines").description("Compare TypeScript and native engines when native is available.").action(async () => {
384
+ process.stdout.write(await compareEngines());
385
+ });
386
+ }
387
+ //# sourceMappingURL=bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench.js","sourceRoot":"","sources":["../../src/commands/bench.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDedupeCommand(program: Command): void;
@@ -0,0 +1,16 @@
1
+ import { DedupeStore } from "../core/dedupe-store.js";
2
+ export function registerDedupeCommand(program) {
3
+ const dedupe = program.command("dedupe").description("Inspect cross-call output dedupe state.");
4
+ dedupe.command("stats").description("Print dedupe index stats.").action(async () => {
5
+ const records = await new DedupeStore().readAll();
6
+ const hashes = new Set(records.map((record) => record.output_sha256));
7
+ const duplicateGroups = records.length - hashes.size;
8
+ process.stdout.write([
9
+ "SotuRail dedupe stats",
10
+ `records: ${records.length}`,
11
+ `unique_output_hashes: ${hashes.size}`,
12
+ `repeat_records: ${duplicateGroups}`
13
+ ].join("\n") + "\n");
14
+ });
15
+ }
16
+ //# sourceMappingURL=dedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../src/commands/dedupe.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,yCAAyC,CAAC,CAAC;IAChG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,2BAA2B,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QACjF,MAAM,OAAO,GAAG,MAAM,IAAI,WAAW,EAAE,CAAC,OAAO,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC;QACtE,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC;QACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;YACE,uBAAuB;YACvB,YAAY,OAAO,CAAC,MAAM,EAAE;YAC5B,yBAAyB,MAAM,CAAC,IAAI,EAAE;YACtC,mBAAmB,eAAe,EAAE;SACrC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CACpB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Command } from "commander";
2
+ interface Check {
3
+ name: string;
4
+ ok: boolean;
5
+ message: string;
6
+ }
7
+ export declare function runDoctor(root?: string): Promise<Check[]>;
8
+ export declare function formatChecks(title: string, checks: Check[]): string;
9
+ export declare function runDoctorCache(root?: string): Promise<{
10
+ checks: Check[];
11
+ score: number;
12
+ }>;
13
+ export declare function registerDoctorCommand(program: Command): void;
14
+ export {};
@@ -0,0 +1,122 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { buildCachePayload } from "../core/cache-normalizer.js";
4
+ import { getWorkspacePaths, validateConfigFile } from "../core/config.js";
5
+ import { MetricsStore } from "../core/metrics-store.js";
6
+ async function exists(filePath) {
7
+ try {
8
+ await fs.access(filePath);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ async function checkWritePermission(workspace) {
16
+ if (!(await exists(workspace))) {
17
+ return { name: "write permissions", ok: false, message: "workspace does not exist" };
18
+ }
19
+ const probe = path.resolve(workspace, `.write-probe-${process.pid}-${Date.now()}`);
20
+ try {
21
+ await fs.writeFile(probe, "ok", { flag: "wx" });
22
+ await fs.unlink(probe);
23
+ return { name: "write permissions", ok: true, message: "workspace is writable" };
24
+ }
25
+ catch (error) {
26
+ return { name: "write permissions", ok: false, message: error instanceof Error ? error.message : String(error) };
27
+ }
28
+ }
29
+ function checkNodeVersion() {
30
+ const major = Number(process.versions.node.split(".")[0] ?? 0);
31
+ return {
32
+ name: "Node.js",
33
+ ok: major >= 20,
34
+ message: `current version is ${process.version}; SotuRail requires Node.js >=20`
35
+ };
36
+ }
37
+ export async function runDoctor(root = process.cwd()) {
38
+ const paths = getWorkspacePaths(root);
39
+ const config = await validateConfigFile(root);
40
+ return [
41
+ {
42
+ name: "workspace",
43
+ ok: await exists(paths.workspace),
44
+ message: (await exists(paths.workspace)) ? ".soturail workspace exists" : ".soturail workspace is missing; run soturail init"
45
+ },
46
+ await checkWritePermission(paths.workspace),
47
+ checkNodeVersion(),
48
+ { name: "config", ok: config.ok, message: config.message }
49
+ ];
50
+ }
51
+ export function formatChecks(title, checks) {
52
+ return `${title}\n${checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.message}`).join("\n")}\n`;
53
+ }
54
+ function promptCacheEnvKeys() {
55
+ const known = [
56
+ "ANTHROPIC_PROMPT_CACHING",
57
+ "CLAUDE_CODE_ENABLE_PROMPT_CACHE",
58
+ "OPENAI_PROMPT_CACHE",
59
+ "GEMINI_PROMPT_CACHE",
60
+ "SOTURAIL_PROMPT_CACHE"
61
+ ];
62
+ return known.filter((key) => process.env[key] !== undefined);
63
+ }
64
+ export async function runDoctorCache(root = process.cwd()) {
65
+ const result = await buildCachePayload(root, "dynamic footer: raw_id, timestamp and command status stay here");
66
+ const firstDynamic = result.sections.findIndex((section) => !section.stable);
67
+ const stableAfterDynamic = result.sections.some((section, index) => section.stable && index > firstDynamic);
68
+ const envKeys = promptCacheEnvKeys();
69
+ const checks = [
70
+ {
71
+ name: "cache normalizer",
72
+ ok: result.blocks.length > 0,
73
+ message: `${result.blocks.length} stable block(s) written to .soturail/cache/blocks.jsonl`
74
+ },
75
+ {
76
+ name: "stable before dynamic",
77
+ ok: firstDynamic > -1 && !stableAfterDynamic,
78
+ message: "dynamic footer is ordered after stable blocks"
79
+ },
80
+ {
81
+ name: "prompt-cache env",
82
+ ok: true,
83
+ message: envKeys.length > 0 ? `present: ${envKeys.join(", ")}` : "no prompt-cache-related environment variables detected"
84
+ },
85
+ {
86
+ name: "estimated cache stability",
87
+ ok: true,
88
+ message: `${result.estimated_cache_stability_score} (local estimate, not provider cache hits)`
89
+ }
90
+ ];
91
+ const metrics = new MetricsStore(root);
92
+ await metrics.append({
93
+ type: "doctor_cache",
94
+ estimated_cache_stability_score: result.estimated_cache_stability_score,
95
+ details: {
96
+ stable_blocks: result.blocks.length,
97
+ env_keys_present: envKeys
98
+ }
99
+ });
100
+ return { checks, score: result.estimated_cache_stability_score };
101
+ }
102
+ export function registerDoctorCommand(program) {
103
+ const doctor = program.command("doctor").description("Check SotuRail local health.");
104
+ doctor.action(async () => {
105
+ const checks = await runDoctor();
106
+ process.stdout.write(formatChecks("SotuRail doctor", checks));
107
+ if (checks.some((check) => !check.ok)) {
108
+ process.exitCode = 1;
109
+ }
110
+ });
111
+ doctor
112
+ .command("cache")
113
+ .description("Check prompt-cache-friendly stable/dynamic separation.")
114
+ .action(async () => {
115
+ const result = await runDoctorCache();
116
+ process.stdout.write(formatChecks("SotuRail doctor cache", result.checks));
117
+ if (result.checks.some((check) => !check.ok)) {
118
+ process.exitCode = 1;
119
+ }
120
+ });
121
+ }
122
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ export declare function expandRawLog(rawId: string, root?: string): Promise<Buffer>;
3
+ export declare function registerExpandCommand(program: Command): void;
@@ -0,0 +1,23 @@
1
+ import { MetricsStore } from "../core/metrics-store.js";
2
+ import { RawStore } from "../core/raw-store.js";
3
+ export async function expandRawLog(rawId, root = process.cwd()) {
4
+ const rawStore = new RawStore(root);
5
+ const buffer = await rawStore.readRaw(rawId);
6
+ if (!buffer) {
7
+ throw new Error(`No raw log found for raw_id "${rawId}". Check .soturail/raw/index.jsonl or run soturail stats.`);
8
+ }
9
+ const metrics = new MetricsStore(root);
10
+ await metrics.append({ type: "expand", raw_id: rawId });
11
+ return buffer;
12
+ }
13
+ export function registerExpandCommand(program) {
14
+ program
15
+ .command("expand")
16
+ .description("Print the original raw log for a raw_id.")
17
+ .argument("<raw_id>", "Raw log id from soturail run")
18
+ .action(async (rawId) => {
19
+ const buffer = await expandRawLog(rawId);
20
+ process.stdout.write(buffer);
21
+ });
22
+ }
23
+ //# sourceMappingURL=expand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand.js","sourceRoot":"","sources":["../../src/commands/expand.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE;IACpE,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,2DAA2D,CAAC,CAAC;IACpH,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,0CAA0C,CAAC;SACvD,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC;SACpD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;QAC9B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Command } from "commander";
2
+ export interface FormatOptions {
3
+ mode?: string;
4
+ }
5
+ export declare function formatFile(file: string, options?: FormatOptions, root?: string): Promise<string>;
6
+ export declare function registerFormatCommand(program: Command): void;