@unerr-ai/unerr 0.2.1 → 0.2.2
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.
- package/README.md +6 -0
- package/dist/cli.js +37236 -35793
- package/package.json +1 -1
- package/dist/behaviors/agent-llm-bridge.js +0 -166
- package/dist/behaviors/architecture-guard.js +0 -256
- package/dist/behaviors/auto-doc.js +0 -247
- package/dist/behaviors/cascade-guard.js +0 -289
- package/dist/behaviors/change-narrative.js +0 -270
- package/dist/behaviors/convention-drift.js +0 -290
- package/dist/behaviors/framework.js +0 -235
- package/dist/behaviors/guard-formatter.js +0 -44
- package/dist/behaviors/incomplete-work.js +0 -270
- package/dist/behaviors/loop-breaker.js +0 -300
- package/dist/behaviors/session-continuity.js +0 -208
- package/dist/commands/branches.js +0 -97
- package/dist/commands/check-commit.js +0 -225
- package/dist/commands/compress-output.js +0 -64
- package/dist/commands/config-verify.js +0 -243
- package/dist/commands/daemon.js +0 -905
- package/dist/commands/dashboard.js +0 -52
- package/dist/commands/debug.js +0 -200
- package/dist/commands/enrich.js +0 -184
- package/dist/commands/exec.js +0 -233
- package/dist/commands/gain.js +0 -156
- package/dist/commands/hook.js +0 -88
- package/dist/commands/index.js +0 -88
- package/dist/commands/init.js +0 -74
- package/dist/commands/install.js +0 -505
- package/dist/commands/learn.js +0 -116
- package/dist/commands/manifest.js +0 -193
- package/dist/commands/rewind.js +0 -103
- package/dist/commands/serve.js +0 -19
- package/dist/commands/setup-wizard.js +0 -414
- package/dist/commands/skills.js +0 -64
- package/dist/commands/stats.js +0 -20
- package/dist/commands/status.js +0 -654
- package/dist/commands/timeline.js +0 -139
- package/dist/commands/uninstall.js +0 -230
- package/dist/components/App.js +0 -109
- package/dist/components/Banner.js +0 -12
- package/dist/components/ConfirmPrompt.js +0 -25
- package/dist/components/DriftSummary.js +0 -23
- package/dist/components/GradeBadge.js +0 -15
- package/dist/components/HealthCard.js +0 -18
- package/dist/components/InkSpinner.js +0 -22
- package/dist/components/InputBox.js +0 -17
- package/dist/components/KeyValue.js +0 -13
- package/dist/components/MessageList.js +0 -14
- package/dist/components/ProgressBar.js +0 -26
- package/dist/components/Section.js +0 -16
- package/dist/components/SessionSummaryCard.js +0 -73
- package/dist/components/StartupDisplay.js +0 -24
- package/dist/components/StatusDashboard.js +0 -57
- package/dist/components/StatusLine.js +0 -8
- package/dist/components/StepLine.js +0 -22
- package/dist/components/Theme.js +0 -20
- package/dist/components/ToolProgress.js +0 -8
- package/dist/components/ViolationList.js +0 -21
- package/dist/components/render.js +0 -13
- package/dist/config/agent-registry.js +0 -237
- package/dist/config/claude-settings-hooks.js +0 -304
- package/dist/config/hook-installer.js +0 -65
- package/dist/config/instruction-writer.js +0 -388
- package/dist/config/mcp-config-writer.js +0 -266
- package/dist/config/settings.js +0 -174
- package/dist/config/tool-detector.js +0 -42
- package/dist/config/value-surfacing.js +0 -119
- package/dist/core/context-assembly.js +0 -108
- package/dist/core/conversation.js +0 -33
- package/dist/core/local-chat-provider.js +0 -475
- package/dist/core/provider-factory.js +0 -55
- package/dist/core/providers.js +0 -90
- package/dist/core/query-engine.js +0 -174
- package/dist/daemon/api.js +0 -312
- package/dist/daemon/autostart.js +0 -119
- package/dist/daemon/bootstrap.js +0 -39
- package/dist/daemon/client.js +0 -164
- package/dist/daemon/detect-ci.js +0 -81
- package/dist/daemon/platform-linux.js +0 -146
- package/dist/daemon/platform-macos.js +0 -134
- package/dist/daemon/platform-windows.js +0 -116
- package/dist/daemon/process-manager.js +0 -299
- package/dist/daemon/protocol.js +0 -23
- package/dist/daemon/registry.js +0 -270
- package/dist/daemon/settings-schema.js +0 -72
- package/dist/daemon/system-health.js +0 -134
- package/dist/daemon/version-checker.js +0 -262
- package/dist/daemon/warm-start.js +0 -223
- package/dist/entrypoints/cli.js +0 -1043
- package/dist/entrypoints/daemon.js +0 -380
- package/dist/entrypoints/repl.js +0 -147
- package/dist/hooks/adapters/claude-code.js +0 -90
- package/dist/hooks/adapters/cline.js +0 -100
- package/dist/hooks/adapters/cursor.js +0 -98
- package/dist/hooks/hook-dedup.js +0 -79
- package/dist/hooks/hook-runner.js +0 -113
- package/dist/hooks/navigation-hooks.js +0 -175
- package/dist/hooks/prompt-hooks.js +0 -63
- package/dist/hooks/shell-hooks.js +0 -47
- package/dist/ignore.js +0 -111
- package/dist/intelligence/approach-suggester.js +0 -61
- package/dist/intelligence/ast-extractor.js +0 -2615
- package/dist/intelligence/ast-worker.js +0 -34
- package/dist/intelligence/background-indexer.js +0 -121
- package/dist/intelligence/blast-radius.js +0 -200
- package/dist/intelligence/community-detection.js +0 -691
- package/dist/intelligence/community-detector.js +0 -184
- package/dist/intelligence/computation-scheduler.js +0 -75
- package/dist/intelligence/confidence-propagation.js +0 -47
- package/dist/intelligence/convention-detector.js +0 -242
- package/dist/intelligence/convention-learner.js +0 -205
- package/dist/intelligence/convention-matcher.js +0 -205
- package/dist/intelligence/cozo-schema.js +0 -376
- package/dist/intelligence/decision-point-detector.js +0 -90
- package/dist/intelligence/deep-dive-tools.js +0 -586
- package/dist/intelligence/durability-scorer.js +0 -84
- package/dist/intelligence/exploration-cost.js +0 -204
- package/dist/intelligence/exploration-pattern-tracker.js +0 -61
- package/dist/intelligence/fact-generator.js +0 -322
- package/dist/intelligence/facts-schema.js +0 -90
- package/dist/intelligence/file-intelligence.js +0 -59
- package/dist/intelligence/graph-holder.js +0 -220
- package/dist/intelligence/graph-temporal-joiner.js +0 -238
- package/dist/intelligence/health-grade.js +0 -423
- package/dist/intelligence/health-grader.js +0 -200
- package/dist/intelligence/health-map-data.js +0 -259
- package/dist/intelligence/import-symbols.js +0 -136
- package/dist/intelligence/incremental-indexer.js +0 -658
- package/dist/intelligence/indexer/centrality.js +0 -62
- package/dist/intelligence/indexer/cfg-context.js +0 -95
- package/dist/intelligence/indexer/confidence.js +0 -34
- package/dist/intelligence/indexer/cross-file-resolver.js +0 -104
- package/dist/intelligence/indexer/edge-repair.js +0 -89
- package/dist/intelligence/indexer/entity-key.js +0 -17
- package/dist/intelligence/indexer/export-map.js +0 -132
- package/dist/intelligence/indexer/git-cochange.js +0 -128
- package/dist/intelligence/indexer/graph-patch.js +0 -147
- package/dist/intelligence/indexer/incremental.js +0 -78
- package/dist/intelligence/indexer/ingest.js +0 -160
- package/dist/intelligence/indexer/language-detect.js +0 -226
- package/dist/intelligence/indexer/metadata.js +0 -63
- package/dist/intelligence/indexer/mutation-tracker.js +0 -79
- package/dist/intelligence/indexer/orchestrator.js +0 -155
- package/dist/intelligence/indexer/plugin-interface.js +0 -31
- package/dist/intelligence/indexer/plugins/csharp.js +0 -440
- package/dist/intelligence/indexer/plugins/go.js +0 -335
- package/dist/intelligence/indexer/plugins/java.js +0 -370
- package/dist/intelligence/indexer/plugins/python.js +0 -358
- package/dist/intelligence/indexer/plugins/regex-fallback.js +0 -82
- package/dist/intelligence/indexer/plugins/ruby.js +0 -290
- package/dist/intelligence/indexer/plugins/rust.js +0 -484
- package/dist/intelligence/indexer/plugins/tier2-generic.js +0 -310
- package/dist/intelligence/indexer/plugins/typescript.js +0 -456
- package/dist/intelligence/indexer/resource-monitor.js +0 -93
- package/dist/intelligence/indexer/scip/decoder.js +0 -253
- package/dist/intelligence/indexer/scip/detector.js +0 -232
- package/dist/intelligence/indexer/scip/downloader.js +0 -427
- package/dist/intelligence/indexer/scip/fallback.js +0 -34
- package/dist/intelligence/indexer/scip/merger.js +0 -109
- package/dist/intelligence/indexer/scip/orchestrator.js +0 -433
- package/dist/intelligence/indexer/scip/runner.js +0 -98
- package/dist/intelligence/indexer/snapshot.js +0 -66
- package/dist/intelligence/indexer/test-detector.js +0 -196
- package/dist/intelligence/indexer/watch-integration.js +0 -61
- package/dist/intelligence/indexer/worker.js +0 -85
- package/dist/intelligence/local-convention-detector.js +0 -437
- package/dist/intelligence/local-embeddings.js +0 -190
- package/dist/intelligence/local-graph.js +0 -1946
- package/dist/intelligence/local-indexer.js +0 -1575
- package/dist/intelligence/local-llm.js +0 -163
- package/dist/intelligence/local-rule-generator.js +0 -154
- package/dist/intelligence/local-snapshot.js +0 -213
- package/dist/intelligence/negative-knowledge.js +0 -103
- package/dist/intelligence/persistent-db.js +0 -85
- package/dist/intelligence/query-router.js +0 -2556
- package/dist/intelligence/risk-classifier.js +0 -116
- package/dist/intelligence/rule-evaluator.js +0 -380
- package/dist/intelligence/rule-generator.js +0 -49
- package/dist/intelligence/search-index.js +0 -173
- package/dist/intelligence/semantic/docstring-extractor.js +0 -67
- package/dist/intelligence/semantic/embedding-store.js +0 -52
- package/dist/intelligence/semantic/enrichment-orchestrator.js +0 -48
- package/dist/intelligence/semantic/git-message-miner.js +0 -114
- package/dist/intelligence/semantic/identifier-tokenizer.js +0 -51
- package/dist/intelligence/semantic/node2vec-embeddings.js +0 -71
- package/dist/intelligence/semantic/node2vec-walks.js +0 -103
- package/dist/intelligence/semantic/path-domain-inference.js +0 -112
- package/dist/intelligence/semantic/similarity-engine.js +0 -60
- package/dist/intelligence/semantic/tfidf-vectors.js +0 -88
- package/dist/intelligence/session-brief-builder.js +0 -159
- package/dist/intelligence/session-context.js +0 -221
- package/dist/intelligence/session-health-monitor.js +0 -211
- package/dist/intelligence/session-narrative.js +0 -197
- package/dist/intelligence/session-pattern-analyzer.js +0 -218
- package/dist/intelligence/signal-scorer.js +0 -390
- package/dist/intelligence/signal-show-store.js +0 -182
- package/dist/intelligence/smart-truncate.js +0 -158
- package/dist/intelligence/subgraph-cache.js +0 -88
- package/dist/intelligence/temporal-facts.js +0 -494
- package/dist/intelligence/token-estimator.js +0 -100
- package/dist/intelligence/tool-injector.js +0 -87
- package/dist/intelligence/tree-sitter-loader.js +0 -71
- package/dist/intelligence/worker-pool.js +0 -116
- package/dist/proxy/arg-validator.js +0 -79
- package/dist/proxy/auto-bootstrap.js +0 -167
- package/dist/proxy/bridge.js +0 -147
- package/dist/proxy/budget-enforcer.js +0 -70
- package/dist/proxy/compression-quality-monitor.js +0 -160
- package/dist/proxy/compression-stats.js +0 -51
- package/dist/proxy/context-rot-detector.js +0 -137
- package/dist/proxy/drift-detector.js +0 -139
- package/dist/proxy/efficiency-tracker.js +0 -79
- package/dist/proxy/fact-ranking.js +0 -154
- package/dist/proxy/format-encoder.js +0 -266
- package/dist/proxy/http-transport.js +0 -90
- package/dist/proxy/lifecycle-actor.js +0 -55
- package/dist/proxy/lifecycle-machine.js +0 -187
- package/dist/proxy/log-tailer.js +0 -265
- package/dist/proxy/model-pricing.js +0 -98
- package/dist/proxy/network-firewall.js +0 -141
- package/dist/proxy/nudge-state.js +0 -93
- package/dist/proxy/output-compressor.js +0 -185
- package/dist/proxy/pid-lock.js +0 -291
- package/dist/proxy/proxy-context.js +0 -11
- package/dist/proxy/proxy.js +0 -2633
- package/dist/proxy/response-enrichment.js +0 -32
- package/dist/proxy/response-envelope.js +0 -313
- package/dist/proxy/session-dedup.js +0 -82
- package/dist/proxy/session-legend.js +0 -30
- package/dist/proxy/session-persistence.js +0 -210
- package/dist/proxy/session-resume.js +0 -94
- package/dist/proxy/session-stats.js +0 -513
- package/dist/proxy/shell-classifier.js +0 -1346
- package/dist/proxy/shell-compression-log.js +0 -93
- package/dist/proxy/shell-compressor.js +0 -390
- package/dist/proxy/shell-graph-boost.js +0 -202
- package/dist/proxy/shell-monitor-map.js +0 -18
- package/dist/proxy/shell-stats.js +0 -54
- package/dist/proxy/shell-strategies/cloud.js +0 -215
- package/dist/proxy/shell-strategies/diff.js +0 -159
- package/dist/proxy/shell-strategies/error-diagnostic.js +0 -796
- package/dist/proxy/shell-strategies/filter-dsl.js +0 -358
- package/dist/proxy/shell-strategies/git-status.js +0 -177
- package/dist/proxy/shell-strategies/key-value.js +0 -193
- package/dist/proxy/shell-strategies/log-text.js +0 -154
- package/dist/proxy/shell-strategies/omni.js +0 -188
- package/dist/proxy/shell-strategies/progress.js +0 -55
- package/dist/proxy/shell-strategies/redact.js +0 -76
- package/dist/proxy/shell-strategies/structured.js +0 -241
- package/dist/proxy/shell-strategies/tabular.js +0 -243
- package/dist/proxy/shell-strategies/test-results-types.js +0 -13
- package/dist/proxy/shell-strategies/test-results.js +0 -784
- package/dist/proxy/shell-strategies/tree-paths.js +0 -144
- package/dist/proxy/shell-strategies/yaml.js +0 -182
- package/dist/proxy/shell-tee.js +0 -111
- package/dist/proxy/signal-dedup.js +0 -171
- package/dist/proxy/startup-renderer.js +0 -158
- package/dist/proxy/task-token-display.js +0 -38
- package/dist/proxy/token-counter.js +0 -61
- package/dist/proxy/tool-clusters.js +0 -273
- package/dist/proxy/tool-definitions.js +0 -525
- package/dist/proxy/transport-mux.js +0 -229
- package/dist/proxy/wire-cap.js +0 -268
- package/dist/rules/developer.mozilla.org.json +0 -9
- package/dist/rules/github.com.json +0 -21
- package/dist/schemas/api/skills.js +0 -19
- package/dist/schemas/common/errors.js +0 -7
- package/dist/schemas/common/headers.js +0 -5
- package/dist/schemas/entities/edge.js +0 -25
- package/dist/schemas/entities/entity.js +0 -22
- package/dist/schemas/entities/rule.js +0 -18
- package/dist/schemas/index.js +0 -14
- package/dist/server/event-bus.js +0 -59
- package/dist/server/http.js +0 -156
- package/dist/server/middleware.js +0 -70
- package/dist/server/routes/drift.js +0 -97
- package/dist/server/routes/intelligence.js +0 -1217
- package/dist/server/routes/reasoning-quality.js +0 -444
- package/dist/server/routes/session.js +0 -86
- package/dist/server/routes/stream.js +0 -120
- package/dist/server/routes/system.js +0 -73
- package/dist/server/routes/temporal.js +0 -170
- package/dist/server/routes/timeline.js +0 -232
- package/dist/server/routes/token-flow.js +0 -403
- package/dist/skills/effectiveness-tracker.js +0 -93
- package/dist/skills/local-pack.js +0 -380
- package/dist/skills/resolver.js +0 -495
- package/dist/state-detector.js +0 -83
- package/dist/timeline/intent-detector.js +0 -263
- package/dist/timeline/loop-miner.js +0 -140
- package/dist/timeline/open-threads.js +0 -49
- package/dist/timeline/signal-reinforcer.js +0 -62
- package/dist/timeline/timeline-bootstrap.js +0 -151
- package/dist/timeline/timeline-store.js +0 -618
- package/dist/tools/coding/bash.js +0 -49
- package/dist/tools/coding/file-edit.js +0 -72
- package/dist/tools/coding/file-outline.js +0 -227
- package/dist/tools/coding/file-read-protocol.js +0 -425
- package/dist/tools/coding/file-read.js +0 -35
- package/dist/tools/coding/file-write.js +0 -43
- package/dist/tools/coding/glob-tool.js +0 -109
- package/dist/tools/coding/grep.js +0 -162
- package/dist/tools/coding/index.js +0 -27
- package/dist/tools/intelligence/index.js +0 -269
- package/dist/tools/intelligence/record-fact.js +0 -48
- package/dist/tools/intelligence/timeline-markers.js +0 -130
- package/dist/tools/registry.js +0 -47
- package/dist/tools/types.js +0 -8
- package/dist/tracking/auto-snapshot-triggers.js +0 -246
- package/dist/tracking/branch-context.js +0 -115
- package/dist/tracking/branch-snapshot.js +0 -217
- package/dist/tracking/causal-bridge.js +0 -317
- package/dist/tracking/circuit-breaker.js +0 -147
- package/dist/tracking/commit-watcher.js +0 -114
- package/dist/tracking/context-ledger.js +0 -119
- package/dist/tracking/correction-detector.js +0 -324
- package/dist/tracking/drift-tracker.js +0 -874
- package/dist/tracking/durability-tracker.js +0 -94
- package/dist/tracking/entity-rewind.js +0 -200
- package/dist/tracking/file-hash-state.js +0 -114
- package/dist/tracking/git-attribution.js +0 -132
- package/dist/tracking/git-trailers.js +0 -171
- package/dist/tracking/intelligence-counter.js +0 -46
- package/dist/tracking/intent-correlator.js +0 -202
- package/dist/tracking/intent-encoder.js +0 -52
- package/dist/tracking/intent-token-tracker.js +0 -159
- package/dist/tracking/ledger-archiver.js +0 -94
- package/dist/tracking/ledger-chains.js +0 -245
- package/dist/tracking/metrics-store.js +0 -361
- package/dist/tracking/native-watcher.js +0 -131
- package/dist/tracking/offline-rewind.js +0 -295
- package/dist/tracking/pending-violations.js +0 -74
- package/dist/tracking/persistence-effectiveness.js +0 -167
- package/dist/tracking/prompt-durability.js +0 -202
- package/dist/tracking/quality-signals.js +0 -213
- package/dist/tracking/redactor.js +0 -73
- package/dist/tracking/rewind-engine.js +0 -161
- package/dist/tracking/session-history.js +0 -128
- package/dist/tracking/session-receipt.js +0 -88
- package/dist/tracking/session-summary-writer.js +0 -157
- package/dist/tracking/shadow-ledger.js +0 -321
- package/dist/tracking/stash-manager.js +0 -258
- package/dist/tracking/timeline-fork.js +0 -213
- package/dist/tracking/timeline.js +0 -69
- package/dist/tracking/token-flow.js +0 -276
- package/dist/tracking/turn-segmenter.js +0 -122
- package/dist/tracking/weekly-accumulator.js +0 -179
- package/dist/tracking/working-snapshots.js +0 -188
- package/dist/tracking/workspace-manifest.js +0 -176
- package/dist/transport/http.js +0 -102
- package/dist/utils/counterfactual.js +0 -65
- package/dist/utils/deep-link.js +0 -34
- package/dist/utils/detect.js +0 -193
- package/dist/utils/exec.js +0 -73
- package/dist/utils/file-logger.js +0 -87
- package/dist/utils/format-error.js +0 -29
- package/dist/utils/git.js +0 -181
- package/dist/utils/log.js +0 -57
- package/dist/utils/logger.js +0 -35
- package/dist/utils/mcp-content-json.js +0 -8
- package/dist/utils/session-logger.js +0 -154
- package/dist/utils/startup-log.js +0 -512
- package/dist/utils/ui.js +0 -56
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cascade Consistency Guard — BA-1.3
|
|
3
|
-
*
|
|
4
|
-
* PostToolUse: when an agent modifies a function signature or type definition,
|
|
5
|
-
* detects the change via AST diff, queries the blast radius graph for all
|
|
6
|
-
* dependents, and injects an update checklist.
|
|
7
|
-
*
|
|
8
|
-
* Tracks which callers have been updated during the session so it can
|
|
9
|
-
* hand off incomplete items to the Incomplete Work detector (BA-2.1).
|
|
10
|
-
*
|
|
11
|
-
* Performance: CozoDB blast radius query must resolve in <5ms.
|
|
12
|
-
*/
|
|
13
|
-
import { Behavior, } from "./framework.js";
|
|
14
|
-
const TEST_FILE_PATTERNS = [
|
|
15
|
-
/\.test\.[jt]sx?$/,
|
|
16
|
-
/\.spec\.[jt]sx?$/,
|
|
17
|
-
/__tests__\//,
|
|
18
|
-
/test\//,
|
|
19
|
-
/tests\//,
|
|
20
|
-
];
|
|
21
|
-
const EDIT_TOOLS = new Set([
|
|
22
|
-
"file_write",
|
|
23
|
-
"write_file",
|
|
24
|
-
"edit_file",
|
|
25
|
-
"str_replace_editor",
|
|
26
|
-
"insert_code",
|
|
27
|
-
"replace_code",
|
|
28
|
-
]);
|
|
29
|
-
export class CascadeConsistencyGuard extends Behavior {
|
|
30
|
-
id = "cascade_guard";
|
|
31
|
-
hooks = ["post_tool_use"];
|
|
32
|
-
defaultLevel = "suggestion";
|
|
33
|
-
graph = null;
|
|
34
|
-
trackedChanges = new Map();
|
|
35
|
-
cascadeConfig;
|
|
36
|
-
constructor(config) {
|
|
37
|
-
super(config, "suggestion");
|
|
38
|
-
this.cascadeConfig = {
|
|
39
|
-
enabled: true,
|
|
40
|
-
level: "suggestion",
|
|
41
|
-
minCallersToWarn: 2,
|
|
42
|
-
includeTests: true,
|
|
43
|
-
...config,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
attachGraph(graph) {
|
|
47
|
-
this.graph = graph;
|
|
48
|
-
}
|
|
49
|
-
async onPostToolUse(ctx) {
|
|
50
|
-
if (!this.graph)
|
|
51
|
-
return null;
|
|
52
|
-
if (!isEditTool(ctx.toolName))
|
|
53
|
-
return null;
|
|
54
|
-
const filePath = extractFilePath(ctx.args);
|
|
55
|
-
if (!filePath)
|
|
56
|
-
return null;
|
|
57
|
-
const entities = await this.graph.getEntitiesByFile(filePath);
|
|
58
|
-
if (entities.length === 0)
|
|
59
|
-
return null;
|
|
60
|
-
const oldContent = extractOldContent(ctx.args);
|
|
61
|
-
const newContent = extractNewContent(ctx.args);
|
|
62
|
-
if (!oldContent && !newContent)
|
|
63
|
-
return null;
|
|
64
|
-
const warnings = [];
|
|
65
|
-
for (const entity of entities) {
|
|
66
|
-
this.markCallerUpdated(entity.key, filePath);
|
|
67
|
-
const changeType = detectSignatureChange(entity, oldContent, newContent);
|
|
68
|
-
if (!changeType)
|
|
69
|
-
continue;
|
|
70
|
-
const callers = await this.graph.getCallersOf(entity.key);
|
|
71
|
-
if (callers.length < this.cascadeConfig.minCallersToWarn)
|
|
72
|
-
continue;
|
|
73
|
-
const callersAtRisk = callers.map((c) => toCallerAtRisk(c));
|
|
74
|
-
const directCallers = callersAtRisk.filter((c) => !c.isTest);
|
|
75
|
-
const testCallers = callersAtRisk.filter((c) => c.isTest);
|
|
76
|
-
const totalAtRisk = this.cascadeConfig.includeTests
|
|
77
|
-
? callersAtRisk.length
|
|
78
|
-
: directCallers.length;
|
|
79
|
-
if (totalAtRisk < this.cascadeConfig.minCallersToWarn)
|
|
80
|
-
continue;
|
|
81
|
-
this.trackedChanges.set(entity.key, {
|
|
82
|
-
entityKey: entity.key,
|
|
83
|
-
changeType,
|
|
84
|
-
callersAtRisk,
|
|
85
|
-
callersUpdated: new Set(),
|
|
86
|
-
timestamp: Date.now(),
|
|
87
|
-
});
|
|
88
|
-
warnings.push({
|
|
89
|
-
changed_entity: entity.signature || entity.name,
|
|
90
|
-
change_type: changeType,
|
|
91
|
-
blast_radius: {
|
|
92
|
-
direct_callers: directCallers,
|
|
93
|
-
test_files: testCallers,
|
|
94
|
-
indirect_callers: 0,
|
|
95
|
-
total_at_risk: totalAtRisk,
|
|
96
|
-
},
|
|
97
|
-
suggestion: buildSuggestion(entity.name, directCallers, testCallers),
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
if (warnings.length === 0)
|
|
101
|
-
return null;
|
|
102
|
-
const totalCallersAtRisk = warnings.reduce((sum, w) => sum + w.blast_radius.total_at_risk, 0);
|
|
103
|
-
return {
|
|
104
|
-
behaviorId: this.id,
|
|
105
|
-
level: this.level,
|
|
106
|
-
relatedSkillId: "blast-radius-first",
|
|
107
|
-
_meta: {
|
|
108
|
-
behavior: this.id,
|
|
109
|
-
callers_at_risk: totalCallersAtRisk,
|
|
110
|
-
entities_changed: warnings.length,
|
|
111
|
-
},
|
|
112
|
-
_context: {
|
|
113
|
-
cascade_warnings: warnings,
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Get all signature changes where not all callers have been updated.
|
|
119
|
-
* Used by Incomplete Work Detection (BA-2.1).
|
|
120
|
-
*/
|
|
121
|
-
getIncompleteChanges() {
|
|
122
|
-
const incomplete = [];
|
|
123
|
-
for (const change of this.trackedChanges.values()) {
|
|
124
|
-
const remaining = change.callersAtRisk.filter((c) => !change.callersUpdated.has(c.entity));
|
|
125
|
-
if (remaining.length > 0)
|
|
126
|
-
incomplete.push(change);
|
|
127
|
-
}
|
|
128
|
-
return incomplete;
|
|
129
|
-
}
|
|
130
|
-
getSessionStats() {
|
|
131
|
-
let totalCallers = 0;
|
|
132
|
-
let incompleteCount = 0;
|
|
133
|
-
for (const change of this.trackedChanges.values()) {
|
|
134
|
-
totalCallers += change.callersAtRisk.length;
|
|
135
|
-
const remaining = change.callersAtRisk.filter((c) => !change.callersUpdated.has(c.entity));
|
|
136
|
-
if (remaining.length > 0)
|
|
137
|
-
incompleteCount++;
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
signatureChangesDetected: this.trackedChanges.size,
|
|
141
|
-
totalCallersNotified: totalCallers,
|
|
142
|
-
incompleteUpdates: incompleteCount,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
markCallerUpdated(entityKey, filePath) {
|
|
146
|
-
for (const change of this.trackedChanges.values()) {
|
|
147
|
-
for (const caller of change.callersAtRisk) {
|
|
148
|
-
if (caller.file === filePath || caller.entity === entityKey) {
|
|
149
|
-
change.callersUpdated.add(caller.entity);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function isEditTool(toolName) {
|
|
156
|
-
return EDIT_TOOLS.has(toolName);
|
|
157
|
-
}
|
|
158
|
-
function isTestFilePath(filePath) {
|
|
159
|
-
return TEST_FILE_PATTERNS.some((p) => p.test(filePath));
|
|
160
|
-
}
|
|
161
|
-
function extractFilePath(args) {
|
|
162
|
-
if (typeof args.path === "string")
|
|
163
|
-
return args.path;
|
|
164
|
-
if (typeof args.file_path === "string")
|
|
165
|
-
return args.file_path;
|
|
166
|
-
if (typeof args.file === "string")
|
|
167
|
-
return args.file;
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
function extractOldContent(args) {
|
|
171
|
-
if (typeof args.old_str === "string")
|
|
172
|
-
return args.old_str;
|
|
173
|
-
if (typeof args.old_string === "string")
|
|
174
|
-
return args.old_string;
|
|
175
|
-
if (typeof args.before === "string")
|
|
176
|
-
return args.before;
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
|
-
function extractNewContent(args) {
|
|
180
|
-
if (typeof args.new_str === "string")
|
|
181
|
-
return args.new_str;
|
|
182
|
-
if (typeof args.new_string === "string")
|
|
183
|
-
return args.new_string;
|
|
184
|
-
if (typeof args.after === "string")
|
|
185
|
-
return args.after;
|
|
186
|
-
if (typeof args.content === "string")
|
|
187
|
-
return args.content;
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Lightweight signature change detection without full AST parsing.
|
|
192
|
-
* Looks for function/method definition patterns that differ between
|
|
193
|
-
* old and new content.
|
|
194
|
-
*/
|
|
195
|
-
function detectSignatureChange(entity, oldContent, newContent) {
|
|
196
|
-
if (!oldContent || !newContent) {
|
|
197
|
-
if (newContent && entity.signature) {
|
|
198
|
-
const fnPattern = new RegExp(`(?:function|async\\s+function|export\\s+(?:async\\s+)?function)\\s+${escapeRegex(entity.name)}\\s*\\(`);
|
|
199
|
-
if (fnPattern.test(newContent)) {
|
|
200
|
-
return "signature_modified";
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
const oldSigs = extractSignatures(oldContent, entity.name);
|
|
206
|
-
const newSigs = extractSignatures(newContent, entity.name);
|
|
207
|
-
if (oldSigs.length === 0 || newSigs.length === 0)
|
|
208
|
-
return null;
|
|
209
|
-
const oldSig = oldSigs[0];
|
|
210
|
-
const newSig = newSigs[0];
|
|
211
|
-
if (oldSig === newSig)
|
|
212
|
-
return null;
|
|
213
|
-
const oldParams = extractParams(oldSig);
|
|
214
|
-
const newParams = extractParams(newSig);
|
|
215
|
-
if (newParams.length > oldParams.length)
|
|
216
|
-
return "parameter_added";
|
|
217
|
-
if (newParams.length < oldParams.length)
|
|
218
|
-
return "parameter_removed";
|
|
219
|
-
const oldParamNames = oldParams.map((p) => p.split(/[:\s=]/)[0]?.trim());
|
|
220
|
-
const newParamNames = newParams.map((p) => p.split(/[:\s=]/)[0]?.trim());
|
|
221
|
-
for (let i = 0; i < oldParamNames.length; i++) {
|
|
222
|
-
if (oldParamNames[i] !== newParamNames[i])
|
|
223
|
-
return "parameter_renamed";
|
|
224
|
-
}
|
|
225
|
-
const oldReturn = extractReturnType(oldSig);
|
|
226
|
-
const newReturn = extractReturnType(newSig);
|
|
227
|
-
if (oldReturn !== newReturn && oldReturn && newReturn)
|
|
228
|
-
return "return_type_changed";
|
|
229
|
-
return "type_changed";
|
|
230
|
-
}
|
|
231
|
-
function extractSignatures(content, entityName) {
|
|
232
|
-
const escapedName = escapeRegex(entityName);
|
|
233
|
-
const patterns = [
|
|
234
|
-
new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${escapedName}\\s*\\([^)]*\\)(?:\\s*:\\s*[^{]+)?`, "g"),
|
|
235
|
-
new RegExp(`(?:export\\s+)?(?:async\\s+)?${escapedName}\\s*\\([^)]*\\)(?:\\s*:\\s*[^{]+)?`, "g"),
|
|
236
|
-
];
|
|
237
|
-
const results = [];
|
|
238
|
-
for (const pattern of patterns) {
|
|
239
|
-
let match;
|
|
240
|
-
match = pattern.exec(content);
|
|
241
|
-
while (match !== null) {
|
|
242
|
-
results.push(match[0]);
|
|
243
|
-
match = pattern.exec(content);
|
|
244
|
-
}
|
|
245
|
-
if (results.length > 0)
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
return results;
|
|
249
|
-
}
|
|
250
|
-
function extractParams(signature) {
|
|
251
|
-
const match = signature.match(/\(([^)]*)\)/);
|
|
252
|
-
if (!match || !match[1]?.trim())
|
|
253
|
-
return [];
|
|
254
|
-
return match[1]
|
|
255
|
-
.split(",")
|
|
256
|
-
.map((p) => p.trim())
|
|
257
|
-
.filter(Boolean);
|
|
258
|
-
}
|
|
259
|
-
function extractReturnType(signature) {
|
|
260
|
-
const afterParen = signature.split(")").slice(1).join(")").trim();
|
|
261
|
-
if (!afterParen.startsWith(":"))
|
|
262
|
-
return null;
|
|
263
|
-
return afterParen.slice(1).trim();
|
|
264
|
-
}
|
|
265
|
-
function escapeRegex(str) {
|
|
266
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
267
|
-
}
|
|
268
|
-
function toCallerAtRisk(entity) {
|
|
269
|
-
return {
|
|
270
|
-
file: entity.file_path,
|
|
271
|
-
entity: entity.name,
|
|
272
|
-
line: entity.start_line,
|
|
273
|
-
isTest: isTestFilePath(entity.file_path),
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
function buildSuggestion(entityName, direct, tests) {
|
|
277
|
-
const total = direct.length + tests.length;
|
|
278
|
-
const parts = [`Update all ${total} caller(s) of ${entityName}.`];
|
|
279
|
-
if (direct.length > 0) {
|
|
280
|
-
parts.push(`Start with ${direct.length} direct caller(s): ${direct
|
|
281
|
-
.map((c) => `${c.file.split("/").pop()}:${c.entity}`)
|
|
282
|
-
.slice(0, 3)
|
|
283
|
-
.join(", ")}${direct.length > 3 ? ` (+${direct.length - 3} more)` : ""}.`);
|
|
284
|
-
}
|
|
285
|
-
if (tests.length > 0) {
|
|
286
|
-
parts.push(`Then update ${tests.length} test file(s).`);
|
|
287
|
-
}
|
|
288
|
-
return parts.join(" ");
|
|
289
|
-
}
|
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Change Impact Narrative — BA-3.2
|
|
3
|
-
*
|
|
4
|
-
* Session-end synthesis: combines signals from ALL behaviors (cascade guard,
|
|
5
|
-
* convention drift, incomplete work, architecture boundary) into a structured
|
|
6
|
-
* markdown summary suitable for PR descriptions or commit messages.
|
|
7
|
-
*
|
|
8
|
-
* Risk levels computed from the aggregate of all behavior signals:
|
|
9
|
-
* - critical: architecture violations or broken callers
|
|
10
|
-
* - high: multiple convention violations or incomplete work
|
|
11
|
-
* - medium: minor convention drift or documentation gaps
|
|
12
|
-
* - low: clean session
|
|
13
|
-
*
|
|
14
|
-
* Uses counterfactual framing: "Without unerr, X would have..."
|
|
15
|
-
*/
|
|
16
|
-
import { calculateDollarSavings, formatDollars, } from "../proxy/model-pricing.js";
|
|
17
|
-
import { Behavior, } from "./framework.js";
|
|
18
|
-
export class ChangeNarrativeBehavior extends Behavior {
|
|
19
|
-
id = "change_narrative";
|
|
20
|
-
hooks = ["session_end"];
|
|
21
|
-
defaultLevel = "suggestion";
|
|
22
|
-
cascadeGuard = null;
|
|
23
|
-
conventionDrift = null;
|
|
24
|
-
incompleteWork = null;
|
|
25
|
-
architectureGuard = null;
|
|
26
|
-
loopBreaker = null;
|
|
27
|
-
autoDoc = null;
|
|
28
|
-
constructor(config) {
|
|
29
|
-
super(config, "suggestion");
|
|
30
|
-
}
|
|
31
|
-
attachBehaviors(behaviors) {
|
|
32
|
-
if (behaviors.cascadeGuard)
|
|
33
|
-
this.cascadeGuard = behaviors.cascadeGuard;
|
|
34
|
-
if (behaviors.conventionDrift)
|
|
35
|
-
this.conventionDrift = behaviors.conventionDrift;
|
|
36
|
-
if (behaviors.incompleteWork)
|
|
37
|
-
this.incompleteWork = behaviors.incompleteWork;
|
|
38
|
-
if (behaviors.architectureGuard)
|
|
39
|
-
this.architectureGuard = behaviors.architectureGuard;
|
|
40
|
-
if (behaviors.loopBreaker)
|
|
41
|
-
this.loopBreaker = behaviors.loopBreaker;
|
|
42
|
-
if (behaviors.autoDoc)
|
|
43
|
-
this.autoDoc = behaviors.autoDoc;
|
|
44
|
-
}
|
|
45
|
-
async onSessionEnd(_ctx) {
|
|
46
|
-
const sections = [];
|
|
47
|
-
sections.push(this.buildCascadeSection());
|
|
48
|
-
sections.push(this.buildConventionSection());
|
|
49
|
-
sections.push(this.buildArchitectureSection());
|
|
50
|
-
sections.push(this.buildIncompleteSection());
|
|
51
|
-
sections.push(this.buildLoopSection());
|
|
52
|
-
sections.push(this.buildDocSection());
|
|
53
|
-
const activeSections = sections.filter((s) => s.items.length > 0);
|
|
54
|
-
if (activeSections.length === 0)
|
|
55
|
-
return null;
|
|
56
|
-
const riskLevel = computeRiskLevel(activeSections);
|
|
57
|
-
const markdown = renderMarkdown(activeSections, riskLevel);
|
|
58
|
-
const counterfactual = buildCounterfactual(activeSections, this.loopBreaker);
|
|
59
|
-
return {
|
|
60
|
-
behaviorId: this.id,
|
|
61
|
-
level: this.level,
|
|
62
|
-
_meta: {
|
|
63
|
-
behavior: this.id,
|
|
64
|
-
risk_level: riskLevel,
|
|
65
|
-
sections_active: activeSections.length,
|
|
66
|
-
},
|
|
67
|
-
_context: {
|
|
68
|
-
change_narrative: {
|
|
69
|
-
risk_level: riskLevel,
|
|
70
|
-
sections: activeSections,
|
|
71
|
-
markdown,
|
|
72
|
-
counterfactual,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
buildCascadeSection() {
|
|
78
|
-
const section = {
|
|
79
|
-
title: "Cascade Status",
|
|
80
|
-
status: "pass",
|
|
81
|
-
items: [],
|
|
82
|
-
};
|
|
83
|
-
if (!this.cascadeGuard)
|
|
84
|
-
return section;
|
|
85
|
-
const stats = this.cascadeGuard.getSessionStats();
|
|
86
|
-
if (stats.signatureChangesDetected > 0) {
|
|
87
|
-
section.items.push(`${stats.signatureChangesDetected} signature change(s) detected, ${stats.totalCallersNotified} caller(s) notified`);
|
|
88
|
-
}
|
|
89
|
-
if (stats.incompleteUpdates > 0) {
|
|
90
|
-
section.status = "fail";
|
|
91
|
-
section.items.push(`${stats.incompleteUpdates} signature change(s) have callers NOT yet updated`);
|
|
92
|
-
}
|
|
93
|
-
else if (stats.signatureChangesDetected > 0) {
|
|
94
|
-
section.status = "pass";
|
|
95
|
-
section.items.push("All callers updated — no cascade risk");
|
|
96
|
-
}
|
|
97
|
-
return section;
|
|
98
|
-
}
|
|
99
|
-
buildConventionSection() {
|
|
100
|
-
const section = {
|
|
101
|
-
title: "Convention Compliance",
|
|
102
|
-
status: "pass",
|
|
103
|
-
items: [],
|
|
104
|
-
};
|
|
105
|
-
if (!this.conventionDrift)
|
|
106
|
-
return section;
|
|
107
|
-
const stats = this.conventionDrift.getSessionStats();
|
|
108
|
-
if (stats.violationsDetected > 0) {
|
|
109
|
-
section.status =
|
|
110
|
-
stats.autoFixes === stats.violationsDetected ? "warn" : "fail";
|
|
111
|
-
section.items.push(`${stats.violationsDetected} convention violation(s), ${stats.autoFixes} auto-fixed`);
|
|
112
|
-
if (stats.violationsDetected > stats.autoFixes) {
|
|
113
|
-
section.items.push(`${stats.violationsDetected - stats.autoFixes} violation(s) require manual review`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return section;
|
|
117
|
-
}
|
|
118
|
-
buildArchitectureSection() {
|
|
119
|
-
const section = {
|
|
120
|
-
title: "Architecture Check",
|
|
121
|
-
status: "pass",
|
|
122
|
-
items: [],
|
|
123
|
-
};
|
|
124
|
-
if (!this.architectureGuard)
|
|
125
|
-
return section;
|
|
126
|
-
const stats = this.architectureGuard.getSessionStats();
|
|
127
|
-
if (stats.violationsBlocked > 0) {
|
|
128
|
-
section.status = "fail";
|
|
129
|
-
section.items.push(`${stats.violationsBlocked} cross-community import(s) blocked`);
|
|
130
|
-
}
|
|
131
|
-
if (stats.typeImportsAllowed > 0) {
|
|
132
|
-
section.items.push(`${stats.typeImportsAllowed} type import(s) across boundaries (allowed)`);
|
|
133
|
-
}
|
|
134
|
-
if (stats.autoBridges > 0) {
|
|
135
|
-
section.items.push(`${stats.autoBridges} auto-bridge rule(s) created from repeated overrides`);
|
|
136
|
-
}
|
|
137
|
-
return section;
|
|
138
|
-
}
|
|
139
|
-
buildIncompleteSection() {
|
|
140
|
-
const section = {
|
|
141
|
-
title: "Incomplete Items",
|
|
142
|
-
status: "pass",
|
|
143
|
-
items: [],
|
|
144
|
-
};
|
|
145
|
-
if (!this.incompleteWork)
|
|
146
|
-
return section;
|
|
147
|
-
if (!this.cascadeGuard)
|
|
148
|
-
return section;
|
|
149
|
-
const changes = this.cascadeGuard.getIncompleteChanges();
|
|
150
|
-
if (changes.length > 0) {
|
|
151
|
-
section.status = "warn";
|
|
152
|
-
section.items.push(`${changes.length} entity(s) with incomplete caller updates`);
|
|
153
|
-
for (const change of changes.slice(0, 3)) {
|
|
154
|
-
const remaining = change.callersAtRisk.filter((c) => !change.callersUpdated.has(c.entity));
|
|
155
|
-
section.items.push(` → ${change.entityKey}: ${remaining.length} caller(s) still need updating`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return section;
|
|
159
|
-
}
|
|
160
|
-
buildLoopSection() {
|
|
161
|
-
const section = {
|
|
162
|
-
title: "Loop Prevention",
|
|
163
|
-
status: "pass",
|
|
164
|
-
items: [],
|
|
165
|
-
};
|
|
166
|
-
if (!this.loopBreaker)
|
|
167
|
-
return section;
|
|
168
|
-
const stats = this.loopBreaker.getSessionStats();
|
|
169
|
-
if (stats.loopsPrevented > 0) {
|
|
170
|
-
section.status = "warn";
|
|
171
|
-
const dollars = calculateDollarSavings(stats.totalTokensSaved);
|
|
172
|
-
section.items.push(`${stats.loopsPrevented} loop(s) prevented, saving ~${formatDollars(dollars)}`);
|
|
173
|
-
}
|
|
174
|
-
return section;
|
|
175
|
-
}
|
|
176
|
-
buildDocSection() {
|
|
177
|
-
const section = {
|
|
178
|
-
title: "Documentation",
|
|
179
|
-
status: "pass",
|
|
180
|
-
items: [],
|
|
181
|
-
};
|
|
182
|
-
if (!this.autoDoc)
|
|
183
|
-
return section;
|
|
184
|
-
const stats = this.autoDoc.getSessionStats();
|
|
185
|
-
if (stats.docsGenerated > 0) {
|
|
186
|
-
section.items.push(`${stats.docsGenerated} doc(s) generated or updated`);
|
|
187
|
-
}
|
|
188
|
-
if (stats.docsFlagged > 0) {
|
|
189
|
-
section.status = "warn";
|
|
190
|
-
section.items.push(`${stats.docsFlagged} reference doc(s) may be stale`);
|
|
191
|
-
}
|
|
192
|
-
return section;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function computeRiskLevel(sections) {
|
|
196
|
-
const hasFailure = sections.some((s) => s.status === "fail");
|
|
197
|
-
const failCount = sections.filter((s) => s.status === "fail").length;
|
|
198
|
-
const warnCount = sections.filter((s) => s.status === "warn").length;
|
|
199
|
-
if (failCount >= 2)
|
|
200
|
-
return "critical";
|
|
201
|
-
if (hasFailure)
|
|
202
|
-
return "high";
|
|
203
|
-
if (warnCount >= 2)
|
|
204
|
-
return "medium";
|
|
205
|
-
return "low";
|
|
206
|
-
}
|
|
207
|
-
function renderMarkdown(sections, riskLevel) {
|
|
208
|
-
const statusIcon = (s) => {
|
|
209
|
-
switch (s) {
|
|
210
|
-
case "pass":
|
|
211
|
-
return "✅";
|
|
212
|
-
case "warn":
|
|
213
|
-
return "⚠️";
|
|
214
|
-
case "fail":
|
|
215
|
-
return "❌";
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
const riskBadge = (r) => {
|
|
219
|
-
switch (r) {
|
|
220
|
-
case "critical":
|
|
221
|
-
return "🔴 CRITICAL";
|
|
222
|
-
case "high":
|
|
223
|
-
return "🟠 HIGH";
|
|
224
|
-
case "medium":
|
|
225
|
-
return "🟡 MEDIUM";
|
|
226
|
-
case "low":
|
|
227
|
-
return "🟢 LOW";
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
const lines = [
|
|
231
|
-
`## Change Impact: ${riskBadge(riskLevel)}`,
|
|
232
|
-
"",
|
|
233
|
-
"*Generated by unerr behavioral automation*",
|
|
234
|
-
"",
|
|
235
|
-
];
|
|
236
|
-
for (const section of sections) {
|
|
237
|
-
lines.push(`### ${statusIcon(section.status)} ${section.title}`);
|
|
238
|
-
for (const item of section.items) {
|
|
239
|
-
lines.push(`- ${item}`);
|
|
240
|
-
}
|
|
241
|
-
lines.push("");
|
|
242
|
-
}
|
|
243
|
-
return lines.join("\n");
|
|
244
|
-
}
|
|
245
|
-
function buildCounterfactual(sections, loopBreaker) {
|
|
246
|
-
const parts = [];
|
|
247
|
-
const cascadeFails = sections.find((s) => s.title === "Cascade Status" && s.status === "fail");
|
|
248
|
-
if (cascadeFails) {
|
|
249
|
-
parts.push("broken callers would have gone unnoticed until runtime");
|
|
250
|
-
}
|
|
251
|
-
const convFails = sections.find((s) => s.title === "Convention Compliance" && s.status !== "pass");
|
|
252
|
-
if (convFails) {
|
|
253
|
-
parts.push("convention violations would have spread through the codebase");
|
|
254
|
-
}
|
|
255
|
-
const archFails = sections.find((s) => s.title === "Architecture Check" && s.status === "fail");
|
|
256
|
-
if (archFails) {
|
|
257
|
-
parts.push("cross-community imports would have degraded module boundaries");
|
|
258
|
-
}
|
|
259
|
-
if (loopBreaker) {
|
|
260
|
-
const stats = loopBreaker.getSessionStats();
|
|
261
|
-
if (stats.loopsPrevented > 0) {
|
|
262
|
-
const dollars = calculateDollarSavings(stats.totalTokensSaved);
|
|
263
|
-
parts.push(`~${formatDollars(dollars)} would have been wasted in retry loops`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (parts.length === 0) {
|
|
267
|
-
return "Clean session — no issues prevented.";
|
|
268
|
-
}
|
|
269
|
-
return `Without unerr, ${parts.join(", ")}.`;
|
|
270
|
-
}
|