salmon-loop 0.4.1 → 0.5.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 (146) hide show
  1. package/dist/cli/authorization/provider.js +2 -10
  2. package/dist/cli/commands/config.js +2 -2
  3. package/dist/cli/commands/mode.js +2 -2
  4. package/dist/cli/commands/run/handler.js +3 -1
  5. package/dist/cli/commands/run/loop-params.js +1 -0
  6. package/dist/cli/commands/run/runtime-options.js +3 -1
  7. package/dist/cli/config.js +0 -8
  8. package/dist/cli/locales/en.js +2 -2
  9. package/dist/cli/reporters/standard.js +10 -0
  10. package/dist/core/adapters/fs/file-adapter.js +3 -1
  11. package/dist/core/adapters/git/git-adapter.js +6 -3
  12. package/dist/core/adapters/git/git-runner.js +5 -2
  13. package/dist/core/adapters/git/lock-manager.js +7 -4
  14. package/dist/core/checkpoint-domain/manifest-store.js +21 -13
  15. package/dist/core/checkpoint-domain/service.js +3 -1
  16. package/dist/core/config/limits.js +1 -1
  17. package/dist/core/config/model-pricing.js +61 -0
  18. package/dist/core/context/ast/skeleton-extractor.js +225 -0
  19. package/dist/core/context/ast/source-outline.js +24 -1
  20. package/dist/core/context/budget/dynamic-adjuster.js +20 -5
  21. package/dist/core/context/builder.js +7 -3
  22. package/dist/core/context/cache/store-factory.js +3 -1
  23. package/dist/core/context/dependencies.js +2 -1
  24. package/dist/core/context/effectiveness/persistence.js +50 -0
  25. package/dist/core/context/effectiveness/tracker.js +24 -0
  26. package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
  27. package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
  28. package/dist/core/context/gatherers/ast-gatherer.js +30 -28
  29. package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
  30. package/dist/core/context/gatherers/knowledge-gatherer.js +18 -2
  31. package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
  32. package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
  33. package/dist/core/context/service.js +4 -2
  34. package/dist/core/context/steps/context-gather.js +14 -3
  35. package/dist/core/context/steps/context-targets.js +1 -0
  36. package/dist/core/context/targeting/target-resolver.js +29 -11
  37. package/dist/core/context/token/cache.js +5 -2
  38. package/dist/core/context/truncation/strategies/json.js +5 -2
  39. package/dist/core/context/truncation/type-detector.js +3 -1
  40. package/dist/core/extensions/paths.js +2 -2
  41. package/dist/core/facades/cli-authorization-provider.js +1 -0
  42. package/dist/core/feedback/parsers.js +290 -1
  43. package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
  44. package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
  45. package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
  46. package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
  47. package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
  48. package/dist/core/grizzco/execution/RejectionManager.js +7 -5
  49. package/dist/core/grizzco/runtime/apply-back-runtime.js +3 -1
  50. package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
  51. package/dist/core/grizzco/steps/autopilot.js +21 -32
  52. package/dist/core/grizzco/steps/explore.js +5 -2
  53. package/dist/core/grizzco/steps/generateReview.js +3 -1
  54. package/dist/core/grizzco/steps/research.js +3 -1
  55. package/dist/core/grizzco/steps/verify.js +7 -1
  56. package/dist/core/grizzco/validation/AstValidationService.js +3 -1
  57. package/dist/core/history/input-history.js +3 -1
  58. package/dist/core/intent/chat-intent.js +3 -1
  59. package/dist/core/llm/ai-sdk/message-mapper.js +13 -8
  60. package/dist/core/llm/ai-sdk/request-params.js +1 -3
  61. package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
  62. package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
  63. package/dist/core/llm/errors.js +5 -4
  64. package/dist/core/llm/retry-utils.js +8 -2
  65. package/dist/core/llm/stream-utils.js +5 -3
  66. package/dist/core/llm/sub-agent-factory.js +3 -0
  67. package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
  68. package/dist/core/mcp/catalog/discovery.js +3 -1
  69. package/dist/core/mcp/client/connection-manager.js +4 -2
  70. package/dist/core/mcp/client/transport-factory.js +7 -3
  71. package/dist/core/observability/audit-file.js +2 -1
  72. package/dist/core/observability/audit-trail.js +3 -1
  73. package/dist/core/observability/logger.js +2 -1
  74. package/dist/core/observability/monitor.js +24 -0
  75. package/dist/core/observability/run-outcome-reporter.js +1 -0
  76. package/dist/core/permission-gate/default-gate.js +5 -8
  77. package/dist/core/plan/storage.js +7 -4
  78. package/dist/core/plugin/loader.js +3 -1
  79. package/dist/core/prompts/registry.js +1 -1
  80. package/dist/core/prompts/runtime.js +3 -1
  81. package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
  82. package/dist/core/protocols/a2a/sdk/executor.js +3 -1
  83. package/dist/core/protocols/a2a/sdk/server.js +3 -1
  84. package/dist/core/protocols/acp/acp-command-runner.js +7 -6
  85. package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
  86. package/dist/core/protocols/acp/formal-agent.js +3 -2
  87. package/dist/core/protocols/acp/permission-provider.js +3 -2
  88. package/dist/core/reflection/engine.js +114 -14
  89. package/dist/core/runtime/batch-runner.js +81 -0
  90. package/dist/core/runtime/initialize.js +2 -1
  91. package/dist/core/runtime/loop-finalize.js +3 -0
  92. package/dist/core/runtime/loop-session-runner.js +5 -0
  93. package/dist/core/runtime/loop.js +4 -0
  94. package/dist/core/runtime/paths.js +9 -6
  95. package/dist/core/runtime/spawn-interactive.js +5 -4
  96. package/dist/core/security/redaction.js +3 -2
  97. package/dist/core/session/compression.js +3 -1
  98. package/dist/core/session/manager.js +2 -1
  99. package/dist/core/session/pruning-strategy.js +2 -1
  100. package/dist/core/session/token-tracker.js +11 -4
  101. package/dist/core/skills/permissions.js +2 -2
  102. package/dist/core/strata/checkpoint/manager.js +16 -10
  103. package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
  104. package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
  105. package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
  106. package/dist/core/strata/interaction/file-system-provider.js +2 -1
  107. package/dist/core/strata/layers/file-state-resolver.js +9 -7
  108. package/dist/core/strata/layers/immutable-git-layer.js +3 -1
  109. package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
  110. package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
  111. package/dist/core/strata/layers/worktree.js +2 -1
  112. package/dist/core/strata/runtime/environment.js +2 -1
  113. package/dist/core/strata/runtime/synchronizer.js +18 -17
  114. package/dist/core/structured-output/json-extract.js +3 -1
  115. package/dist/core/sub-agent/artifacts/store.js +2 -1
  116. package/dist/core/sub-agent/core/manager.js +24 -1
  117. package/dist/core/sub-agent/registry-defaults.js +2 -2
  118. package/dist/core/sub-agent/summary.js +96 -0
  119. package/dist/core/sub-agent/tools/task-spawn.js +7 -4
  120. package/dist/core/target-runtime/profile.js +3 -1
  121. package/dist/core/tools/audit.js +3 -2
  122. package/dist/core/tools/budget.js +3 -1
  123. package/dist/core/tools/builtin/ast.js +144 -0
  124. package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
  125. package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
  126. package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
  127. package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
  128. package/dist/core/tools/builtin/fs.js +76 -1
  129. package/dist/core/tools/builtin/git.js +242 -0
  130. package/dist/core/tools/builtin/glob.js +79 -0
  131. package/dist/core/tools/builtin/index.js +12 -4
  132. package/dist/core/tools/builtin/knowledge.js +146 -4
  133. package/dist/core/tools/builtin/proposal.js +3 -1
  134. package/dist/core/tools/builtin/verify.js +35 -3
  135. package/dist/core/tools/permissions/permission-rules.js +3 -1
  136. package/dist/core/tools/router.js +88 -5
  137. package/dist/core/tools/session.js +10 -5
  138. package/dist/core/types/batch.js +2 -0
  139. package/dist/core/utils/sanitizer.js +5 -2
  140. package/dist/core/utils/serialize.js +5 -2
  141. package/dist/core/verification/detect-runner.js +86 -0
  142. package/dist/core/verification/runner.js +76 -0
  143. package/dist/core/version.js +3 -1
  144. package/dist/languages/python/index.js +154 -0
  145. package/dist/locales/en.js +6 -0
  146. package/package.json +2 -1
@@ -1,15 +1,21 @@
1
1
  import { getLogger } from '../observability/logger.js';
2
2
  import { getPromptRegistry } from '../prompts/registry.js';
3
3
  import { executeUpdateKnowledge } from '../tools/builtin/knowledge.js';
4
+ // Frequency limiter for success reflections: at most once per N successes
5
+ let successReflectionCounter = 0;
6
+ const SUCCESS_REFLECTION_INTERVAL = 5;
4
7
  export class ReflectionEngine {
5
8
  llm;
6
9
  constructor(llm) {
7
10
  this.llm = llm;
8
11
  }
12
+ /**
13
+ * Main reflection: failures followed by success.
14
+ * Extracts "what went wrong and how it was fixed".
15
+ */
9
16
  async reflect(input, repoRoot) {
10
17
  const promptRegistry = getPromptRegistry();
11
18
  await promptRegistry.init();
12
- // Only reflect if there were failures and final success
13
19
  const failures = input.history.filter((h) => h.error);
14
20
  if (failures.length === 0 || !input.success) {
15
21
  return { lessons: [] };
@@ -21,19 +27,45 @@ export class ReflectionEngine {
21
27
  responseFormat: 'json_object',
22
28
  });
23
29
  const content = response.content;
24
- // Extract JSON from response (handle potential markdown markers)
25
30
  const jsonStr = content.match(/\{[\s\S]*\}/)?.[0] || content;
26
31
  const result = JSON.parse(jsonStr);
27
- // Persist suggested knowledge if any
28
- if (result.suggestedRules && result.suggestedRules.length > 0) {
29
- // Since we are in the engine, we can call the executor directly
30
- const mockCtx = { repoRoot };
31
- await executeUpdateKnowledge({
32
- category: 'project_rules',
33
- rules: result.suggestedRules,
34
- deprecated_rules: result.deprecatedRules,
35
- }, mockCtx);
36
- }
32
+ await this.persistKnowledge(result, repoRoot);
33
+ getLogger().debug(`[Reflection] Reflection completed with ${result.lessons?.length ?? 0} lessons.`);
34
+ return result;
35
+ }
36
+ catch (e) {
37
+ getLogger().warn(`[Reflection] Failed to perform reflection: ${e instanceof Error ? e.message : String(e)}`);
38
+ return { lessons: [] };
39
+ }
40
+ }
41
+ /**
42
+ * Reflect on first-try success (attempt === 1).
43
+ * Extracts "what was done right" as positive patterns.
44
+ * Frequency-limited: at most once every SUCCESS_REFLECTION_INTERVAL successes.
45
+ */
46
+ async reflectOnSuccess(input, repoRoot) {
47
+ successReflectionCounter++;
48
+ if (successReflectionCounter % SUCCESS_REFLECTION_INTERVAL !== 0) {
49
+ return { lessons: [] };
50
+ }
51
+ const promptRegistry = getPromptRegistry();
52
+ await promptRegistry.init();
53
+ getLogger().debug('[Reflection] Triggering success reflection (first-try success).');
54
+ const successPrompt = [
55
+ 'The task was completed successfully on the first attempt.',
56
+ `Instruction: ${input.instruction}`,
57
+ '',
58
+ 'Analyze what was done correctly. Extract 1-2 concise positive patterns that contributed to success.',
59
+ 'Return JSON: { "lessons": ["..."], "suggestedDecisions": ["..."] }',
60
+ 'Each decision should be a short, actionable pattern (10-200 chars).',
61
+ ].join('\n');
62
+ try {
63
+ const response = await this.llm.chat([{ role: 'user', content: successPrompt }], {
64
+ responseFormat: 'json_object',
65
+ });
66
+ const content = response.content;
67
+ const jsonStr = content.match(/\{[\s\S]*\}/)?.[0] || content;
68
+ const result = JSON.parse(jsonStr);
37
69
  if (result.suggestedDecisions && result.suggestedDecisions.length > 0) {
38
70
  const mockCtx = { repoRoot };
39
71
  for (const decision of result.suggestedDecisions) {
@@ -43,13 +75,81 @@ export class ReflectionEngine {
43
75
  }, mockCtx);
44
76
  }
45
77
  }
46
- getLogger().debug(`[Reflection] Reflection completed with ${result.lessons?.length ?? 0} lessons.`);
78
+ await this.persistLessons(result.lessons, repoRoot, 'success');
47
79
  return result;
48
80
  }
49
81
  catch (e) {
50
- getLogger().warn(`[Reflection] Failed to perform reflection: ${e instanceof Error ? e.message : String(e)}`);
82
+ getLogger().warn(`[Reflection] Failed to perform success reflection: ${e instanceof Error ? e.message : String(e)}`);
83
+ return { lessons: [] };
84
+ }
85
+ }
86
+ /**
87
+ * Reflect on final failure (all retries exhausted).
88
+ * Extracts "why it failed" as deprecated approaches.
89
+ * No LLM call — structured extraction from failure history.
90
+ */
91
+ async reflectOnFailure(input, repoRoot) {
92
+ const failures = input.history.filter((h) => h.error);
93
+ if (failures.length === 0)
51
94
  return { lessons: [] };
95
+ getLogger().debug(`[Reflection] Extracting failure lessons from ${failures.length} failed attempts.`);
96
+ // Structured extraction: collect unique error patterns
97
+ const errorPatterns = new Set();
98
+ for (const entry of failures) {
99
+ if (!entry.error)
100
+ continue;
101
+ const summary = typeof entry.contextSummary === 'string'
102
+ ? entry.contextSummary.slice(0, 200)
103
+ : String(entry.error).slice(0, 200);
104
+ errorPatterns.add(summary);
52
105
  }
106
+ const lessons = Array.from(errorPatterns).map((pattern) => `Approach failed: ${pattern}`);
107
+ // Write deprecated rules
108
+ const deprecatedApproaches = Array.from(errorPatterns).map((pattern) => `Avoid: ${pattern.slice(0, 100)}`);
109
+ if (deprecatedApproaches.length > 0) {
110
+ const mockCtx = { repoRoot };
111
+ await executeUpdateKnowledge({
112
+ category: 'project_rules',
113
+ rules: [],
114
+ deprecated_rules: deprecatedApproaches,
115
+ }, mockCtx).catch((e) => {
116
+ getLogger().warn(`[Reflection] Failed to persist failure lessons: ${String(e)}`);
117
+ });
118
+ }
119
+ await this.persistLessons(lessons, repoRoot, 'failure');
120
+ return { lessons };
121
+ }
122
+ async persistKnowledge(result, repoRoot) {
123
+ if (result.suggestedRules && result.suggestedRules.length > 0) {
124
+ const mockCtx = { repoRoot };
125
+ await executeUpdateKnowledge({
126
+ category: 'project_rules',
127
+ rules: result.suggestedRules,
128
+ deprecated_rules: result.deprecatedRules,
129
+ }, mockCtx);
130
+ }
131
+ if (result.suggestedDecisions && result.suggestedDecisions.length > 0) {
132
+ const mockCtx = { repoRoot };
133
+ for (const decision of result.suggestedDecisions) {
134
+ await executeUpdateKnowledge({
135
+ category: 'architectural_decisions',
136
+ decision,
137
+ }, mockCtx);
138
+ }
139
+ }
140
+ await this.persistLessons(result.lessons, repoRoot, 'success');
141
+ }
142
+ async persistLessons(lessons, repoRoot, source) {
143
+ if (lessons.length === 0)
144
+ return;
145
+ const mockCtx = { repoRoot };
146
+ await executeUpdateKnowledge({
147
+ category: 'lessons_learned',
148
+ lessons,
149
+ source,
150
+ }, mockCtx).catch((e) => {
151
+ getLogger().warn(`[Reflection] Failed to persist lessons: ${String(e)}`);
152
+ });
53
153
  }
54
154
  }
55
155
  //# sourceMappingURL=engine.js.map
@@ -0,0 +1,81 @@
1
+ import { createRunSalmonLoop } from './loop.js';
2
+ export class BatchRunner {
3
+ runLoop;
4
+ constructor(deps) {
5
+ this.runLoop = createRunSalmonLoop({ semaphore: deps?.semaphore });
6
+ }
7
+ async run(tasks, batchOptions) {
8
+ const { rateLimitMs = 0, filter, signal } = batchOptions ?? {};
9
+ const filtered = filter ? tasks.filter(filter) : tasks;
10
+ const results = [];
11
+ for (let i = 0; i < filtered.length; i++) {
12
+ if (signal?.aborted)
13
+ break;
14
+ if (i > 0 && rateLimitMs > 0) {
15
+ await new Promise((r) => setTimeout(r, rateLimitMs));
16
+ }
17
+ const result = await this.runLoop(filtered[i]);
18
+ results.push(result);
19
+ }
20
+ return buildBatchRunReport(results);
21
+ }
22
+ }
23
+ function aggregateUsage(results) {
24
+ const total = { inputTokens: 0, outputTokens: 0, totalTokens: 0, estimatedCost: 0 };
25
+ for (const r of results) {
26
+ if (!r.usage)
27
+ continue;
28
+ total.inputTokens += r.usage.inputTokens;
29
+ total.outputTokens += r.usage.outputTokens;
30
+ total.totalTokens += r.usage.totalTokens;
31
+ if (r.usage.estimatedCost)
32
+ total.estimatedCost += r.usage.estimatedCost;
33
+ }
34
+ return total;
35
+ }
36
+ export function buildBatchRunReport(results) {
37
+ const completed = results.filter((r) => r.success).length;
38
+ const failed = results.length - completed;
39
+ const totalDuration = results.reduce((s, r) => s + (r.durationMs ?? 0), 0);
40
+ return {
41
+ totalRuns: results.length,
42
+ completedRuns: completed,
43
+ failedRuns: failed,
44
+ successRate: results.length > 0 ? completed / results.length : 0,
45
+ avgDurationMs: results.length > 0 ? totalDuration / results.length : 0,
46
+ results,
47
+ totalUsage: aggregateUsage(results),
48
+ byDimension: (dimension) => buildDimensionMap(results, dimension),
49
+ };
50
+ }
51
+ function buildDimensionMap(results, dimension) {
52
+ const map = {};
53
+ for (const r of results) {
54
+ const value = String(r.providerMeta?.[dimension] ?? 'unknown');
55
+ map[value] ??= {
56
+ total: 0,
57
+ success: 0,
58
+ failed: 0,
59
+ avgDurationMs: 0,
60
+ totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
61
+ };
62
+ const bucket = map[value];
63
+ bucket.total++;
64
+ if (r.success)
65
+ bucket.success++;
66
+ else
67
+ bucket.failed++;
68
+ if (r.durationMs)
69
+ bucket.avgDurationMs += r.durationMs;
70
+ if (r.usage) {
71
+ bucket.totalUsage.inputTokens += r.usage.inputTokens;
72
+ bucket.totalUsage.outputTokens += r.usage.outputTokens;
73
+ bucket.totalUsage.totalTokens += r.usage.totalTokens;
74
+ }
75
+ }
76
+ for (const bucket of Object.values(map)) {
77
+ bucket.avgDurationMs = bucket.total > 0 ? bucket.avgDurationMs / bucket.total : 0;
78
+ }
79
+ return map;
80
+ }
81
+ //# sourceMappingURL=batch-runner.js.map
@@ -143,7 +143,8 @@ function loadUserAgentProfiles(registry) {
143
143
  const content = readFileSync(filePath, 'utf-8');
144
144
  return AgentsConfigSchema.parse(JSON.parse(content));
145
145
  }
146
- catch {
146
+ catch (error) {
147
+ getLogger().debug(`[InitializeRuntime] Failed to load agent config from ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
147
148
  return null;
148
149
  }
149
150
  };
@@ -1,4 +1,5 @@
1
1
  import { text } from '../../locales/index.js';
2
+ import { persistEffectiveness } from '../context/effectiveness/persistence.js';
2
3
  import { appendAuditTrailToAuditFile } from '../observability/audit-file.js';
3
4
  import { clearAuditContext, drainAuditDropStats, recordAuditEvent, } from '../observability/audit-trail.js';
4
5
  import { getLogger } from '../observability/logger.js';
@@ -64,6 +65,8 @@ export async function finalizeLoopRun(params) {
64
65
  if (finalResult)
65
66
  finalResult.auditPath = appendedPath;
66
67
  }
68
+ // Persist effectiveness data for cross-session learning
69
+ await persistEffectiveness(options.repoPath).catch(() => { });
67
70
  clearAuditContext();
68
71
  }
69
72
  return { latestAuditPath, finalResult };
@@ -1,5 +1,10 @@
1
+ import { restoreEffectiveness } from '../context/effectiveness/persistence.js';
2
+ import { getEffectivenessTracker } from '../context/effectiveness/tracker.js';
1
3
  import { buildFlowTransactionRunner, runFlowSession } from '../grizzco/engine/transaction/index.js';
2
4
  export async function executeLoopSession(params) {
5
+ // Restore effectiveness data from previous sessions
6
+ await restoreEffectiveness(params.options.repoPath).catch(() => { });
7
+ getEffectivenessTracker().startSession();
3
8
  const hostContext = await params.lifecycle.hostRunner.boot();
4
9
  const runner = buildFlowTransactionRunner({
5
10
  flowMode: hostContext.flowMode,
@@ -25,6 +25,7 @@ export class SalmonLoop {
25
25
  this.config = config;
26
26
  }
27
27
  async run(options) {
28
+ const runStartedAt = Date.now();
28
29
  const lifecycle = initializeLoopLifecycle(options);
29
30
  let latestAuditPath;
30
31
  let finalResult;
@@ -78,6 +79,9 @@ export class SalmonLoop {
78
79
  });
79
80
  latestAuditPath = finalized.latestAuditPath;
80
81
  finalResult = finalized.finalResult;
82
+ if (finalResult) {
83
+ finalResult.durationMs = Date.now() - runStartedAt;
84
+ }
81
85
  }
82
86
  }
83
87
  }
@@ -2,12 +2,14 @@ import { createHash } from 'crypto';
2
2
  import * as os from 'os';
3
3
  import path from 'path';
4
4
  import { mkdir, readdir, rename, rm, stat } from '../adapters/fs/node-fs.js';
5
+ import { getLogger } from '../observability/logger.js';
5
6
  async function pathExists(target) {
6
7
  try {
7
8
  await stat(target);
8
9
  return true;
9
10
  }
10
- catch {
11
+ catch (error) {
12
+ getLogger().debug(`[RuntimePaths] pathExists check failed for ${target}: ${error instanceof Error ? error.message : String(error)}`);
11
13
  return false;
12
14
  }
13
15
  }
@@ -54,7 +56,8 @@ export async function migrateLegacyRuntime(repoRoot) {
54
56
  await rename(legacyRoot, runtimeRoot);
55
57
  return;
56
58
  }
57
- catch {
59
+ catch (error) {
60
+ getLogger().debug(`[RuntimePaths] Failed to rename legacy runtime root: ${error instanceof Error ? error.message : String(error)}`);
58
61
  await mkdir(runtimeRoot, { recursive: true });
59
62
  }
60
63
  }
@@ -67,8 +70,8 @@ export async function migrateLegacyRuntime(repoRoot) {
67
70
  try {
68
71
  await rename(from, to);
69
72
  }
70
- catch {
71
- // Best-effort migration; keep legacy data if move fails.
73
+ catch (error) {
74
+ getLogger().debug(`[RuntimePaths] Failed to migrate legacy entry ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
72
75
  }
73
76
  }
74
77
  try {
@@ -77,8 +80,8 @@ export async function migrateLegacyRuntime(repoRoot) {
77
80
  await rm(legacyRoot, { recursive: true, force: true });
78
81
  }
79
82
  }
80
- catch {
81
- // Ignore cleanup failures; legacy folder can be removed manually.
83
+ catch (error) {
84
+ getLogger().debug(`[RuntimePaths] Failed to clean up legacy runtime folder: ${error instanceof Error ? error.message : String(error)}`);
82
85
  }
83
86
  }
84
87
  //# sourceMappingURL=paths.js.map
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { EventEmitter } from 'events';
3
+ import { getLogger } from '../observability/logger.js';
3
4
  import { getBunRuntime, normalizeSignal, toNodeReadableStream } from './bun-runtime.js';
4
5
  export function spawnInteractiveProcess(input) {
5
6
  const bun = getBunRuntime();
@@ -37,8 +38,8 @@ export function spawnInteractiveProcess(input) {
37
38
  try {
38
39
  subprocess.kill(signal);
39
40
  }
40
- catch {
41
- // Ignore kill errors.
41
+ catch (error) {
42
+ getLogger().debug(`[SpawnInteractive] Failed to kill bun subprocess: ${error instanceof Error ? error.message : String(error)}`);
42
43
  }
43
44
  },
44
45
  on: (event, listener) => {
@@ -75,8 +76,8 @@ export function spawnInteractiveProcess(input) {
75
76
  try {
76
77
  child.kill(signal);
77
78
  }
78
- catch {
79
- // Ignore kill errors.
79
+ catch (error) {
80
+ getLogger().debug(`[SpawnInteractive] Failed to kill child process: ${error instanceof Error ? error.message : String(error)}`);
80
81
  }
81
82
  },
82
83
  on: (event, listener) => {
@@ -1,3 +1,4 @@
1
+ import { getLogger } from '../observability/logger.js';
1
2
  const DEFAULT_REDACTION_MARK = '[REDACTED]';
2
3
  const DEFAULT_LIMITS = {
3
4
  maxDepth: 6,
@@ -34,8 +35,8 @@ export function setRedactionConfig(options) {
34
35
  try {
35
36
  compiledPatterns.push(new RegExp(pattern, 'g'));
36
37
  }
37
- catch {
38
- // Ignore invalid patterns to avoid breaking runtime.
38
+ catch (error) {
39
+ getLogger().debug(`[SecurityRedaction] Skipping invalid redaction pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`);
39
40
  }
40
41
  }
41
42
  }
@@ -1,6 +1,7 @@
1
1
  import { promisify } from 'util';
2
2
  import { gzip, gunzip } from 'zlib';
3
3
  import { FileAdapter } from '../adapters/fs/index.js';
4
+ import { getLogger } from '../observability/logger.js';
4
5
  import { normalizeSessionArtifactState } from './artifact-state.js';
5
6
  import { normalizeToolResultReplacementState, } from './replacement-state.js';
6
7
  export const DEFAULT_COMPRESSION_CONFIG = {
@@ -307,7 +308,8 @@ export class CompressedSessionStore {
307
308
  const data = await this.readFile(filepath);
308
309
  return await this.compressor.decompressFromBinary(data);
309
310
  }
310
- catch {
311
+ catch (error) {
312
+ getLogger().debug(`[SessionCompression] Failed to load compressed session ${filename}: ${error instanceof Error ? error.message : String(error)}`);
311
313
  return null;
312
314
  }
313
315
  }
@@ -132,7 +132,8 @@ export class ChatSessionManager {
132
132
  this.currentSession = parsed;
133
133
  return this.currentSession;
134
134
  }
135
- catch {
135
+ catch (error) {
136
+ getLogger().warn(`[SessionManager] Failed to load session ${targetId}: ${error instanceof Error ? error.message : String(error)}`);
136
137
  return null;
137
138
  }
138
139
  }
@@ -178,7 +178,8 @@ export class SessionArchiver {
178
178
  })),
179
179
  };
180
180
  }
181
- catch {
181
+ catch (error) {
182
+ getLogger().debug(`[SessionPruning] Failed to restore session from archive: ${error instanceof Error ? error.message : String(error)}`);
182
183
  return null;
183
184
  }
184
185
  }
@@ -1,6 +1,8 @@
1
1
  import path from 'path';
2
2
  import { FileAdapter } from '../adapters/fs/index.js';
3
+ import { estimateCost } from '../config/model-pricing.js';
3
4
  import { logIgnoredError } from '../observability/ignored-error.js';
5
+ import { getLogger } from '../observability/logger.js';
4
6
  import { isRecord } from '../utils/serialize.js';
5
7
  /**
6
8
  * Token usage tracker for chat sessions.
@@ -39,8 +41,8 @@ export class TokenTracker {
39
41
  try {
40
42
  events.push(JSON.parse(line));
41
43
  }
42
- catch {
43
- // Skip malformed lines
44
+ catch (error) {
45
+ getLogger().debug(`[TokenTracker] Skipping malformed JSON line in events file: ${error instanceof Error ? error.message : String(error)}`);
44
46
  }
45
47
  }
46
48
  let inputTokens = 0;
@@ -76,11 +78,16 @@ export class TokenTracker {
76
78
  }
77
79
  }
78
80
  /**
79
- * Accumulate tokens into session metadata
81
+ * Accumulate tokens into session metadata.
82
+ * Computes estimated cost if model pricing is available.
80
83
  */
81
- static accumulate(session, usage) {
84
+ static accumulate(session, usage, modelId) {
82
85
  session.meta.totalTokens.input += usage.inputTokens;
83
86
  session.meta.totalTokens.output += usage.outputTokens;
87
+ const cost = usage.estimatedCost ?? estimateCost(usage.inputTokens, usage.outputTokens, modelId);
88
+ if (cost !== undefined) {
89
+ session.meta.totalTokens.estimatedCost = (session.meta.totalTokens.estimatedCost ?? 0) + cost;
90
+ }
84
91
  }
85
92
  /**
86
93
  * Estimate token count based on text length
@@ -89,9 +89,9 @@ export class SkillPermissionManager {
89
89
  this.policies = [];
90
90
  }
91
91
  }
92
- catch {
92
+ catch (error) {
93
93
  const logger = tryGetLogger();
94
- logger?.warn(text.skills.permissionFileLoadError(this.filePath));
94
+ logger?.warn(`${text.skills.permissionFileLoadError(this.filePath)}: ${error instanceof Error ? error.message : String(error)}`);
95
95
  this.policies = [];
96
96
  }
97
97
  }
@@ -63,7 +63,8 @@ export class CheckpointManager {
63
63
  try {
64
64
  writeTreeProbe = await probeWriteTreeFailure(git);
65
65
  }
66
- catch {
66
+ catch (probeError) {
67
+ getLogger().debug(`[CheckpointManager] probeWriteTreeFailure failed: ${probeError instanceof Error ? probeError.message : String(probeError)}`);
67
68
  writeTreeProbe = {};
68
69
  }
69
70
  }
@@ -129,7 +130,8 @@ export class CheckpointManager {
129
130
  try {
130
131
  meta = JSON.parse(msg);
131
132
  }
132
- catch {
133
+ catch (error) {
134
+ getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
133
135
  throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
134
136
  }
135
137
  if (!meta.staged) {
@@ -268,8 +270,8 @@ export class CheckpointManager {
268
270
  try {
269
271
  await rm(tempIndexFile, { force: true });
270
272
  }
271
- catch {
272
- // Ignore cleanup errors
273
+ catch (error) {
274
+ getLogger().debug(`[CheckpointManager] Failed to cleanup temp index: ${error instanceof Error ? error.message : String(error)}`);
273
275
  }
274
276
  }
275
277
  }
@@ -292,7 +294,8 @@ export class CheckpointManager {
292
294
  try {
293
295
  meta = JSON.parse(msg);
294
296
  }
295
- catch {
297
+ catch (error) {
298
+ getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
296
299
  throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
297
300
  }
298
301
  // 3. Get Parent Commit (Original HEAD)
@@ -339,7 +342,8 @@ export class CheckpointManager {
339
342
  return { hash, timestamp, message, ref };
340
343
  });
341
344
  }
342
- catch {
345
+ catch (error) {
346
+ getLogger().debug(`[CheckpointManager] Failed to list snapshots: ${error instanceof Error ? error.message : String(error)}`);
343
347
  return [];
344
348
  }
345
349
  }
@@ -354,8 +358,8 @@ export class CheckpointManager {
354
358
  await git.exec(['update-ref', '-d', `refs/s8p/snapshots/${snapshotHash}`]);
355
359
  return;
356
360
  }
357
- catch {
358
- // If direct deletion fails (maybe hash mismatch or short hash), try to find the ref
361
+ catch (error) {
362
+ getLogger().debug(`[CheckpointManager] Direct snapshot deletion failed, trying ref search: ${error instanceof Error ? error.message : String(error)}`);
359
363
  }
360
364
  // Fallback: find the ref pointing to this hash
361
365
  // Note: This is expensive if there are many snapshots, but safe.
@@ -392,7 +396,8 @@ export class CheckpointManager {
392
396
  try {
393
397
  meta = JSON.parse(msg);
394
398
  }
395
- catch {
399
+ catch (error) {
400
+ getLogger().debug(`[CheckpointManager] Invalid snapshot metadata for ${snapshotHash}: ${error instanceof Error ? error.message : String(error)}`);
396
401
  throw new Error(`Invalid snapshot metadata for ${snapshotHash}`);
397
402
  }
398
403
  // Staged files: Diff between Parent (HEAD at time of snapshot) and Staged Tree
@@ -475,7 +480,8 @@ export class CheckpointManager {
475
480
  try {
476
481
  meta = JSON.parse(msg);
477
482
  }
478
- catch {
483
+ catch (error) {
484
+ getLogger().debug(`[CheckpointManager] Invalid backup metadata for ${backupHash}: ${error instanceof Error ? error.message : String(error)}`);
479
485
  throw new Error(`Invalid backup metadata for ${backupHash}`);
480
486
  }
481
487
  // 2. Restore Worktree and Index to the T1 state
@@ -2,6 +2,7 @@ import { randomBytes } from 'crypto';
2
2
  import { tmpdir } from 'os';
3
3
  import { join } from 'path';
4
4
  import { rm } from '../../adapters/fs/node-fs.js';
5
+ import { getLogger } from '../../observability/logger.js';
5
6
  import { normalizePath } from '../../utils/path.js';
6
7
  async function getSafeUntrackedFiles(git) {
7
8
  // --exclude-standard: Respect .gitignore
@@ -50,8 +51,8 @@ export async function createSnapshotCommitFromStagedTree(input) {
50
51
  await git.exec(['add', '--', file], { env });
51
52
  }
52
53
  }
53
- catch {
54
- // Ignore per-file add failures to keep snapshot best-effort.
54
+ catch (error) {
55
+ getLogger().debug(`[SnapshotCreate] Failed to add file ${file} to snapshot index: ${error instanceof Error ? error.message : String(error)}`);
55
56
  }
56
57
  }
57
58
  }
@@ -71,8 +72,8 @@ export async function createSnapshotCommitFromStagedTree(input) {
71
72
  try {
72
73
  await rm(tempIndexFile, { force: true });
73
74
  }
74
- catch {
75
- // Ignore cleanup errors.
75
+ catch (error) {
76
+ getLogger().debug(`[SnapshotCreate] Failed to clean up temp index file: ${error instanceof Error ? error.message : String(error)}`);
76
77
  }
77
78
  }
78
79
  }
@@ -1,5 +1,6 @@
1
1
  import { join } from 'path';
2
2
  import { stat } from '../../adapters/fs/node-fs.js';
3
+ import { getLogger } from '../../observability/logger.js';
3
4
  import { classifyGitFailureHint } from './snapshot-audit.js';
4
5
  export async function tryWriteTreeWithRetry(git, retryDelaysMs) {
5
6
  let attempts = 0;
@@ -28,7 +29,8 @@ export async function probeWriteTreeFailure(git) {
28
29
  details.indexLockPresent = true;
29
30
  details.indexLockAgeMs = Math.max(0, Math.floor(Date.now() - lockStat.mtimeMs));
30
31
  }
31
- catch {
32
+ catch (error) {
33
+ getLogger().debug(`[SnapshotWriteTree] Index lock not present or unreadable: ${error instanceof Error ? error.message : String(error)}`);
32
34
  details.indexLockPresent = false;
33
35
  }
34
36
  try {
@@ -39,7 +41,8 @@ export async function probeWriteTreeFailure(git) {
39
41
  .filter(Boolean).length;
40
42
  details.unmergedCount = count;
41
43
  }
42
- catch {
44
+ catch (error) {
45
+ getLogger().debug(`[SnapshotWriteTree] Failed to list unmerged files: ${error instanceof Error ? error.message : String(error)}`);
43
46
  details.unmergedCount = undefined;
44
47
  }
45
48
  try {
@@ -64,7 +67,8 @@ export async function probeWriteTreeFailure(git) {
64
67
  });
65
68
  }
66
69
  }
67
- catch {
70
+ catch (error) {
71
+ getLogger().debug(`[SnapshotWriteTree] Failed to probe work tree status: ${error instanceof Error ? error.message : String(error)}`);
68
72
  details.isInsideWorkTree = undefined;
69
73
  }
70
74
  return details;
@@ -269,7 +269,8 @@ export class ShadowMergeEngine {
269
269
  return false;
270
270
  return this.guardian.inspect(buffer).isBinary;
271
271
  }
272
- catch {
272
+ catch (error) {
273
+ getLogger().debug(`[ShadowMergeEngine] Binary detection failed for ${relativePath}: ${error instanceof Error ? error.message : String(error)}`);
273
274
  return false;
274
275
  }
275
276
  }
@@ -282,7 +283,8 @@ export class ShadowMergeEngine {
282
283
  try {
283
284
  return await git.show(ref, relativePath);
284
285
  }
285
- catch {
286
+ catch (error) {
287
+ getLogger().debug(`[ShadowMergeEngine] git show failed for ${relativePath}@${ref}: ${error instanceof Error ? error.message : String(error)}`);
286
288
  return null;
287
289
  }
288
290
  }