@unerr-ai/unerr 0.2.0 → 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 +6 -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,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Incomplete Work Detection — BA-2.1
|
|
3
|
-
*
|
|
4
|
-
* Session-end scan that verifies:
|
|
5
|
-
* 1. Broken callers — entity signature changed but callers not all updated
|
|
6
|
-
* 2. Orphaned imports — import targets deleted during session
|
|
7
|
-
* 3. Untested exports — new exported symbols with no test reference
|
|
8
|
-
*
|
|
9
|
-
* Persists incomplete items to disk so Session Continuity (BA-1.2) can
|
|
10
|
-
* inject them as resume context in the next session.
|
|
11
|
-
*
|
|
12
|
-
* Trigger: session_end (proxy shutdown)
|
|
13
|
-
* Assertiveness: suggestion (surface as checklist, don't block)
|
|
14
|
-
*/
|
|
15
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
import { Behavior, } from "./framework.js";
|
|
18
|
-
const PERSISTENCE_FILE = "incomplete-work.json";
|
|
19
|
-
export class IncompleteWorkDetector extends Behavior {
|
|
20
|
-
id = "incomplete_work";
|
|
21
|
-
hooks = ["session_end"];
|
|
22
|
-
defaultLevel = "suggestion";
|
|
23
|
-
graph = null;
|
|
24
|
-
ledger = null;
|
|
25
|
-
cascadeGuard = null;
|
|
26
|
-
unerrDir = null;
|
|
27
|
-
constructor(config) {
|
|
28
|
-
super(config, "suggestion");
|
|
29
|
-
}
|
|
30
|
-
attachGraph(graph) {
|
|
31
|
-
this.graph = graph;
|
|
32
|
-
}
|
|
33
|
-
attachLedger(ledger) {
|
|
34
|
-
this.ledger = ledger;
|
|
35
|
-
}
|
|
36
|
-
attachCascadeGuard(cascadeGuard) {
|
|
37
|
-
this.cascadeGuard = cascadeGuard;
|
|
38
|
-
}
|
|
39
|
-
setUnerrDir(dir) {
|
|
40
|
-
this.unerrDir = dir;
|
|
41
|
-
}
|
|
42
|
-
async onSessionEnd(_ctx) {
|
|
43
|
-
const items = [];
|
|
44
|
-
const brokenCallers = this.detectBrokenCallers();
|
|
45
|
-
items.push(...brokenCallers);
|
|
46
|
-
const orphanedImports = await this.detectOrphanedImports();
|
|
47
|
-
items.push(...orphanedImports);
|
|
48
|
-
const untestedExports = await this.detectUntestedExports();
|
|
49
|
-
items.push(...untestedExports);
|
|
50
|
-
if (items.length === 0)
|
|
51
|
-
return null;
|
|
52
|
-
items.sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
|
|
53
|
-
const highCount = items.filter((i) => i.severity === "high").length;
|
|
54
|
-
const summary = highCount > 0
|
|
55
|
-
? `${highCount} item(s) will cause immediate errors. Fix before committing.`
|
|
56
|
-
: `${items.length} potential issue(s) detected. Review before your next session.`;
|
|
57
|
-
const persisted = this.persistItems(items);
|
|
58
|
-
return {
|
|
59
|
-
behaviorId: this.id,
|
|
60
|
-
level: this.level,
|
|
61
|
-
relatedSkillId: "dependency-aware-refactor",
|
|
62
|
-
_meta: {
|
|
63
|
-
behavior: this.id,
|
|
64
|
-
items_found: items.length,
|
|
65
|
-
high_severity: highCount,
|
|
66
|
-
persisted,
|
|
67
|
-
},
|
|
68
|
-
_context: {
|
|
69
|
-
incomplete_items: items,
|
|
70
|
-
summary,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Check cascade guard for signature changes where not all callers were updated.
|
|
76
|
-
*/
|
|
77
|
-
detectBrokenCallers() {
|
|
78
|
-
if (!this.cascadeGuard)
|
|
79
|
-
return [];
|
|
80
|
-
const incompleteChanges = this.cascadeGuard.getIncompleteChanges();
|
|
81
|
-
const items = [];
|
|
82
|
-
for (const change of incompleteChanges) {
|
|
83
|
-
const remaining = change.callersAtRisk
|
|
84
|
-
.filter((c) => !change.callersUpdated.has(c.entity))
|
|
85
|
-
.map((c) => `${c.file}:${c.entity}`);
|
|
86
|
-
if (remaining.length === 0)
|
|
87
|
-
continue;
|
|
88
|
-
items.push({
|
|
89
|
-
severity: "high",
|
|
90
|
-
type: "broken_callers",
|
|
91
|
-
entity: change.entityKey,
|
|
92
|
-
detail: `Signature changed (${change.changeType}) but ${remaining.length}/${change.callersAtRisk.length} callers not updated`,
|
|
93
|
-
remaining,
|
|
94
|
-
impact: "Callers will fail at runtime or compile time",
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return items;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Check shadow ledger for files that were deleted but are still imported.
|
|
101
|
-
* Uses the graph to find import edges pointing to deleted files.
|
|
102
|
-
*/
|
|
103
|
-
async detectOrphanedImports() {
|
|
104
|
-
if (!this.ledger || !this.graph)
|
|
105
|
-
return [];
|
|
106
|
-
const sessionEntries = this.ledger.getRecentEntries(100);
|
|
107
|
-
const deletedFiles = new Set();
|
|
108
|
-
for (const entry of sessionEntries) {
|
|
109
|
-
if (entry.tool === "delete_file" || entry.tool === "remove_file") {
|
|
110
|
-
const path = extractPathFromArgs(entry.args_summary);
|
|
111
|
-
if (path)
|
|
112
|
-
deletedFiles.add(path);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (deletedFiles.size === 0)
|
|
116
|
-
return [];
|
|
117
|
-
const items = [];
|
|
118
|
-
for (const deletedFile of deletedFiles) {
|
|
119
|
-
const importers = await this.findImportersOf(deletedFile);
|
|
120
|
-
if (importers.length > 0) {
|
|
121
|
-
items.push({
|
|
122
|
-
severity: "medium",
|
|
123
|
-
type: "orphaned_import",
|
|
124
|
-
file: deletedFile,
|
|
125
|
-
detail: `File deleted this session but still imported by ${importers.length} file(s)`,
|
|
126
|
-
remaining: importers.slice(0, 5),
|
|
127
|
-
impact: "Runtime import error",
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return items;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Find new exports added this session that have no test coverage.
|
|
135
|
-
*/
|
|
136
|
-
async detectUntestedExports() {
|
|
137
|
-
if (!this.ledger || !this.graph)
|
|
138
|
-
return [];
|
|
139
|
-
const sessionEntries = this.ledger.getRecentEntries(100);
|
|
140
|
-
const modifiedFiles = new Set();
|
|
141
|
-
for (const entry of sessionEntries) {
|
|
142
|
-
if (isEditTool(entry.tool)) {
|
|
143
|
-
const path = extractPathFromArgs(entry.args_summary);
|
|
144
|
-
if (path && !isTestFile(path))
|
|
145
|
-
modifiedFiles.add(path);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
const items = [];
|
|
149
|
-
for (const file of modifiedFiles) {
|
|
150
|
-
const entities = await this.graph.getEntitiesByFile(file);
|
|
151
|
-
for (const entity of entities) {
|
|
152
|
-
if (entity.kind !== "function" && entity.kind !== "class")
|
|
153
|
-
continue;
|
|
154
|
-
if (entity.fan_in === 0) {
|
|
155
|
-
const hasTestRef = await this.hasTestReference(entity);
|
|
156
|
-
if (!hasTestRef) {
|
|
157
|
-
items.push({
|
|
158
|
-
severity: "low",
|
|
159
|
-
type: "untested_path",
|
|
160
|
-
entity: entity.name,
|
|
161
|
-
file: entity.file_path,
|
|
162
|
-
detail: `Exported ${entity.kind} "${entity.name}" has no test references`,
|
|
163
|
-
impact: "Regression risk — no test coverage",
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return items;
|
|
170
|
-
}
|
|
171
|
-
async findImportersOf(filePath) {
|
|
172
|
-
if (!this.graph)
|
|
173
|
-
return [];
|
|
174
|
-
const entities = await this.graph.getEntitiesByFile(filePath);
|
|
175
|
-
const importerFiles = new Set();
|
|
176
|
-
for (const entity of entities) {
|
|
177
|
-
const callers = await this.graph.getCallersOf(entity.key);
|
|
178
|
-
for (const caller of callers) {
|
|
179
|
-
if (caller.file_path !== filePath) {
|
|
180
|
-
importerFiles.add(caller.file_path);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return [...importerFiles];
|
|
185
|
-
}
|
|
186
|
-
async hasTestReference(entity) {
|
|
187
|
-
if (!this.graph)
|
|
188
|
-
return false;
|
|
189
|
-
const callers = await this.graph.getCallersOf(entity.key);
|
|
190
|
-
return callers.some((c) => isTestFile(c.file_path));
|
|
191
|
-
}
|
|
192
|
-
persistItems(items) {
|
|
193
|
-
if (!this.unerrDir)
|
|
194
|
-
return false;
|
|
195
|
-
try {
|
|
196
|
-
const stateDir = join(this.unerrDir, "state");
|
|
197
|
-
if (!existsSync(stateDir))
|
|
198
|
-
mkdirSync(stateDir, { recursive: true });
|
|
199
|
-
const filePath = join(stateDir, PERSISTENCE_FILE);
|
|
200
|
-
writeFileSync(filePath, JSON.stringify({
|
|
201
|
-
timestamp: new Date().toISOString(),
|
|
202
|
-
items,
|
|
203
|
-
}), "utf-8");
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Read persisted incomplete items from the previous session.
|
|
212
|
-
* Used by Session Continuity (BA-1.2).
|
|
213
|
-
*/
|
|
214
|
-
static readPersistedItems(unerrDir) {
|
|
215
|
-
try {
|
|
216
|
-
const filePath = join(unerrDir, "state", PERSISTENCE_FILE);
|
|
217
|
-
if (!existsSync(filePath))
|
|
218
|
-
return [];
|
|
219
|
-
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
220
|
-
return data.items ?? [];
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
function severityRank(severity) {
|
|
228
|
-
switch (severity) {
|
|
229
|
-
case "high":
|
|
230
|
-
return 0;
|
|
231
|
-
case "medium":
|
|
232
|
-
return 1;
|
|
233
|
-
case "low":
|
|
234
|
-
return 2;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
const TEST_FILE_PATTERNS = [
|
|
238
|
-
/\.test\.[jt]sx?$/,
|
|
239
|
-
/\.spec\.[jt]sx?$/,
|
|
240
|
-
/__tests__\//,
|
|
241
|
-
/test\//,
|
|
242
|
-
/tests\//,
|
|
243
|
-
];
|
|
244
|
-
const EDIT_TOOLS = new Set([
|
|
245
|
-
"file_write",
|
|
246
|
-
"write_file",
|
|
247
|
-
"edit_file",
|
|
248
|
-
"str_replace_editor",
|
|
249
|
-
"insert_code",
|
|
250
|
-
"replace_code",
|
|
251
|
-
"sync_local_diff",
|
|
252
|
-
]);
|
|
253
|
-
function isTestFile(filePath) {
|
|
254
|
-
return TEST_FILE_PATTERNS.some((p) => p.test(filePath));
|
|
255
|
-
}
|
|
256
|
-
function isEditTool(toolName) {
|
|
257
|
-
return EDIT_TOOLS.has(toolName);
|
|
258
|
-
}
|
|
259
|
-
function extractPathFromArgs(args) {
|
|
260
|
-
if (typeof args.path === "string")
|
|
261
|
-
return args.path;
|
|
262
|
-
if (typeof args.file_path === "string")
|
|
263
|
-
return args.file_path;
|
|
264
|
-
if (typeof args.file === "string")
|
|
265
|
-
return args.file;
|
|
266
|
-
if (typeof args.key === "string" && args.key.includes("/")) {
|
|
267
|
-
return args.key.includes("::") ? args.key.split("::")[0] : args.key;
|
|
268
|
-
}
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
@@ -1,300 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loop Detection & Cost Circuit Breaker — BA-1.1
|
|
3
|
-
*
|
|
4
|
-
* Monitors every tool call in real-time via a per-entity ring buffer.
|
|
5
|
-
* Detects 3 stuck-loop patterns:
|
|
6
|
-
* 1. Repetitive failure — same approach with minor variations
|
|
7
|
-
* 2. Context poisoning — wrong assumption contaminates reasoning
|
|
8
|
-
* 3. Over-planning — long reasoning, no converging action
|
|
9
|
-
*
|
|
10
|
-
* Circuit breaker states: CLOSED → OPEN → HALF_OPEN
|
|
11
|
-
* CLOSED: normal operation
|
|
12
|
-
* OPEN: halted — no further attempts on this entity for cooldown period
|
|
13
|
-
* HALF_OPEN: one retry allowed after cooldown expires
|
|
14
|
-
*
|
|
15
|
-
* TDD exemption: test file modifications are excluded from entity-retry count.
|
|
16
|
-
* $0.50 gate: guard moment only fires when estimated savings > $0.50.
|
|
17
|
-
*/
|
|
18
|
-
import { calculateDollarSavings, formatDollars, } from "../proxy/model-pricing.js";
|
|
19
|
-
import { Behavior, estimateWastedTokens, evaluateGate, } from "./framework.js";
|
|
20
|
-
const RING_BUFFER_SIZE = 20;
|
|
21
|
-
const DEFAULT_MAX_FAILURES = 4;
|
|
22
|
-
const DEFAULT_COOLDOWN_MS = 60_000;
|
|
23
|
-
const AVG_TOKENS_PER_FAILED_ATTEMPT = 8_000;
|
|
24
|
-
const EXPECTED_REMAINING_MULTIPLIER = 10;
|
|
25
|
-
const TEST_FILE_PATTERNS = [
|
|
26
|
-
/\.test\.[jt]sx?$/,
|
|
27
|
-
/\.spec\.[jt]sx?$/,
|
|
28
|
-
/__tests__\//,
|
|
29
|
-
/test\//,
|
|
30
|
-
/tests\//,
|
|
31
|
-
];
|
|
32
|
-
export class LoopCircuitBreaker extends Behavior {
|
|
33
|
-
id = "loop_circuit_breaker";
|
|
34
|
-
hooks = ["pre_tool_use", "post_tool_use"];
|
|
35
|
-
defaultLevel = "enforcement";
|
|
36
|
-
circuits = new Map();
|
|
37
|
-
loopsPrevented = 0;
|
|
38
|
-
totalTokensSaved = 0;
|
|
39
|
-
breakerConfig;
|
|
40
|
-
constructor(config) {
|
|
41
|
-
super(config, "enforcement");
|
|
42
|
-
this.breakerConfig = {
|
|
43
|
-
enabled: true,
|
|
44
|
-
level: "enforcement",
|
|
45
|
-
maxAttemptsPerEntity: DEFAULT_MAX_FAILURES,
|
|
46
|
-
cooldownMs: DEFAULT_COOLDOWN_MS,
|
|
47
|
-
...config,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* PreToolUse: check if entity is in OPEN state (circuit tripped).
|
|
52
|
-
* If HALF_OPEN, allow one retry and watch the result.
|
|
53
|
-
*/
|
|
54
|
-
async onPreToolUse(ctx) {
|
|
55
|
-
const entityKey = ctx.entityKey;
|
|
56
|
-
if (!entityKey)
|
|
57
|
-
return null;
|
|
58
|
-
if (isTestFile(ctx.filePath))
|
|
59
|
-
return null;
|
|
60
|
-
const circuit = this.circuits.get(entityKey);
|
|
61
|
-
if (!circuit)
|
|
62
|
-
return null;
|
|
63
|
-
if (circuit.state === "open") {
|
|
64
|
-
const elapsed = Date.now() - circuit.openedAt;
|
|
65
|
-
if (elapsed >= this.breakerConfig.cooldownMs) {
|
|
66
|
-
circuit.state = "half_open";
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
const remainingMs = this.breakerConfig.cooldownMs - elapsed;
|
|
70
|
-
return {
|
|
71
|
-
behaviorId: this.id,
|
|
72
|
-
level: "enforcement",
|
|
73
|
-
halt: true,
|
|
74
|
-
_meta: {
|
|
75
|
-
behavior: this.id,
|
|
76
|
-
loops_prevented_session: this.loopsPrevented,
|
|
77
|
-
tokens_saved: this.totalTokensSaved,
|
|
78
|
-
dollars_saved: formatDollars(calculateDollarSavings(this.totalTokensSaved, ctx.modelId)),
|
|
79
|
-
},
|
|
80
|
-
_context: {
|
|
81
|
-
halt: true,
|
|
82
|
-
reason: `Circuit breaker OPEN for "${entityKey}" — ${circuit.attempts.length} consecutive failed attempts detected`,
|
|
83
|
-
pattern: circuit.detectedPattern,
|
|
84
|
-
cooldown_remaining_s: Math.ceil(remainingMs / 1000),
|
|
85
|
-
suggestion: "Wait for cooldown or try a fundamentally different approach on a different entity.",
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* PostToolUse: record attempt outcome, detect patterns, trip breaker.
|
|
93
|
-
*/
|
|
94
|
-
async onPostToolUse(ctx) {
|
|
95
|
-
const entityKey = ctx.entityKey;
|
|
96
|
-
if (!entityKey)
|
|
97
|
-
return null;
|
|
98
|
-
if (isTestFile(ctx.filePath))
|
|
99
|
-
return null;
|
|
100
|
-
const attempt = {
|
|
101
|
-
toolName: ctx.toolName,
|
|
102
|
-
filePath: ctx.filePath ?? "",
|
|
103
|
-
entityKey,
|
|
104
|
-
timestamp: Date.now(),
|
|
105
|
-
hasError: detectError(ctx.result),
|
|
106
|
-
argFingerprint: fingerprint(ctx.args),
|
|
107
|
-
resultFingerprint: fingerprint(ctx.result),
|
|
108
|
-
};
|
|
109
|
-
if (!this.circuits.has(entityKey)) {
|
|
110
|
-
this.circuits.set(entityKey, {
|
|
111
|
-
state: "closed",
|
|
112
|
-
attempts: [],
|
|
113
|
-
openedAt: 0,
|
|
114
|
-
detectedPattern: null,
|
|
115
|
-
totalTokensWasted: 0,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
const circuit = this.circuits.get(entityKey);
|
|
119
|
-
circuit.attempts.push(attempt);
|
|
120
|
-
if (circuit.attempts.length > RING_BUFFER_SIZE) {
|
|
121
|
-
circuit.attempts.shift();
|
|
122
|
-
}
|
|
123
|
-
if (circuit.state === "half_open") {
|
|
124
|
-
if (attempt.hasError) {
|
|
125
|
-
circuit.state = "open";
|
|
126
|
-
circuit.openedAt = Date.now();
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
circuit.state = "closed";
|
|
130
|
-
circuit.detectedPattern = null;
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
if (circuit.state !== "closed")
|
|
134
|
-
return null;
|
|
135
|
-
const detection = this.detectStuckPattern(circuit);
|
|
136
|
-
if (!detection)
|
|
137
|
-
return null;
|
|
138
|
-
const failCount = detection.failedAttempts;
|
|
139
|
-
const estimatedFutureWaste = estimateWastedTokens(failCount * EXPECTED_REMAINING_MULTIPLIER, AVG_TOKENS_PER_FAILED_ATTEMPT);
|
|
140
|
-
const totalWasted = failCount * AVG_TOKENS_PER_FAILED_ATTEMPT;
|
|
141
|
-
const gate = evaluateGate(estimatedFutureWaste, `Loop detected: ${failCount} consecutive failures on ${entityKey} (${detection.pattern})`, ctx.modelId, entityKey);
|
|
142
|
-
if (!gate.passes)
|
|
143
|
-
return null;
|
|
144
|
-
circuit.state = "open";
|
|
145
|
-
circuit.openedAt = Date.now();
|
|
146
|
-
circuit.detectedPattern = detection.pattern;
|
|
147
|
-
circuit.totalTokensWasted += totalWasted;
|
|
148
|
-
this.loopsPrevented++;
|
|
149
|
-
this.totalTokensSaved += estimatedFutureWaste;
|
|
150
|
-
return {
|
|
151
|
-
behaviorId: this.id,
|
|
152
|
-
level: "enforcement",
|
|
153
|
-
halt: true,
|
|
154
|
-
guardMoment: gate.guardMoment,
|
|
155
|
-
_meta: {
|
|
156
|
-
behavior: this.id,
|
|
157
|
-
loops_prevented_session: this.loopsPrevented,
|
|
158
|
-
tokens_saved: this.totalTokensSaved,
|
|
159
|
-
dollars_saved: formatDollars(calculateDollarSavings(this.totalTokensSaved, ctx.modelId)),
|
|
160
|
-
},
|
|
161
|
-
_context: {
|
|
162
|
-
halt: true,
|
|
163
|
-
reason: `${failCount} consecutive failed attempts on ${entityKey}`,
|
|
164
|
-
pattern: detection.pattern,
|
|
165
|
-
attempts_summary: detection.summaries,
|
|
166
|
-
root_cause_hint: detection.hint,
|
|
167
|
-
suggestion: detection.suggestion,
|
|
168
|
-
},
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
getSessionStats() {
|
|
172
|
-
let openCount = 0;
|
|
173
|
-
for (const c of this.circuits.values()) {
|
|
174
|
-
if (c.state === "open")
|
|
175
|
-
openCount++;
|
|
176
|
-
}
|
|
177
|
-
return {
|
|
178
|
-
loopsPrevented: this.loopsPrevented,
|
|
179
|
-
totalTokensSaved: this.totalTokensSaved,
|
|
180
|
-
activeCircuits: this.circuits.size,
|
|
181
|
-
openCircuits: openCount,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
getCircuitState(entityKey) {
|
|
185
|
-
return this.circuits.get(entityKey)?.state ?? null;
|
|
186
|
-
}
|
|
187
|
-
detectStuckPattern(circuit) {
|
|
188
|
-
const attempts = circuit.attempts;
|
|
189
|
-
const threshold = this.breakerConfig.maxAttemptsPerEntity;
|
|
190
|
-
const recentFailures = this.getConsecutiveFailures(attempts);
|
|
191
|
-
if (recentFailures.length < threshold)
|
|
192
|
-
return null;
|
|
193
|
-
const pattern = this.classifyPattern(recentFailures);
|
|
194
|
-
const summaries = recentFailures
|
|
195
|
-
.slice(-threshold)
|
|
196
|
-
.map((a, i) => `Attempt ${i + 1}: ${a.toolName} on ${a.filePath.split("/").pop()} — ${a.hasError ? "error" : "no visible progress"}`);
|
|
197
|
-
return {
|
|
198
|
-
pattern: pattern.type,
|
|
199
|
-
failedAttempts: recentFailures.length,
|
|
200
|
-
summaries,
|
|
201
|
-
hint: pattern.hint,
|
|
202
|
-
suggestion: pattern.suggestion,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
getConsecutiveFailures(attempts) {
|
|
206
|
-
const failures = [];
|
|
207
|
-
for (let i = attempts.length - 1; i >= 0; i--) {
|
|
208
|
-
if (!attempts[i]?.hasError)
|
|
209
|
-
break;
|
|
210
|
-
failures.unshift(attempts[i]);
|
|
211
|
-
}
|
|
212
|
-
return failures;
|
|
213
|
-
}
|
|
214
|
-
classifyPattern(failures) {
|
|
215
|
-
if (failures.length < 2) {
|
|
216
|
-
return {
|
|
217
|
-
type: "repetitive_failure",
|
|
218
|
-
hint: "Repeated failures on the same entity.",
|
|
219
|
-
suggestion: "Step back and analyze the root cause before retrying.",
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
const fingerprints = failures.map((f) => f.argFingerprint);
|
|
223
|
-
const uniqueFingerprints = new Set(fingerprints).size;
|
|
224
|
-
const similarity = 1 - uniqueFingerprints / fingerprints.length;
|
|
225
|
-
if (similarity > 0.6) {
|
|
226
|
-
return {
|
|
227
|
-
type: "repetitive_failure",
|
|
228
|
-
hint: `All ${failures.length} attempts use similar arguments — the approach isn't changing meaningfully between retries.`,
|
|
229
|
-
suggestion: "Stop retrying the same approach. Examine the error output from attempt 1 and identify the root cause before acting.",
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
const resultFingerprints = failures.map((f) => f.resultFingerprint);
|
|
233
|
-
const uniqueResults = new Set(resultFingerprints).size;
|
|
234
|
-
if (uniqueResults === 1) {
|
|
235
|
-
return {
|
|
236
|
-
type: "context_poisoning",
|
|
237
|
-
hint: "Every attempt produces the exact same error — a wrong assumption is poisoning all reasoning.",
|
|
238
|
-
suggestion: "Discard your current hypothesis entirely. Re-read the original error message and the surrounding code with fresh eyes.",
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
const timespans = failures.map((f) => f.timestamp);
|
|
242
|
-
const totalTime = (timespans[timespans.length - 1] - timespans[0]) / 1000;
|
|
243
|
-
const avgTimePerAttempt = totalTime / failures.length;
|
|
244
|
-
if (avgTimePerAttempt > 15) {
|
|
245
|
-
return {
|
|
246
|
-
type: "over_planning",
|
|
247
|
-
hint: "Long gaps between attempts suggest extensive reasoning without convergence.",
|
|
248
|
-
suggestion: "Take the simplest possible action to test your hypothesis. Write a minimal reproduction first.",
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
type: "repetitive_failure",
|
|
253
|
-
hint: `${failures.length} failed attempts with varied approaches — none resolved the underlying issue.`,
|
|
254
|
-
suggestion: "Step back. The issue may be in a dependency, mock, or configuration rather than in this entity.",
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function isTestFile(filePath) {
|
|
259
|
-
if (!filePath)
|
|
260
|
-
return false;
|
|
261
|
-
return TEST_FILE_PATTERNS.some((p) => p.test(filePath));
|
|
262
|
-
}
|
|
263
|
-
function detectError(result) {
|
|
264
|
-
if (!result)
|
|
265
|
-
return true;
|
|
266
|
-
if (result.error)
|
|
267
|
-
return true;
|
|
268
|
-
if (result.isError === true)
|
|
269
|
-
return true;
|
|
270
|
-
if (typeof result.content === "string" &&
|
|
271
|
-
/error|fail|exception/i.test(result.content))
|
|
272
|
-
return true;
|
|
273
|
-
if (Array.isArray(result.content)) {
|
|
274
|
-
for (const item of result.content) {
|
|
275
|
-
if (typeof item === "object" &&
|
|
276
|
-
item !== null &&
|
|
277
|
-
"type" in item &&
|
|
278
|
-
"text" in item) {
|
|
279
|
-
const text = item.text;
|
|
280
|
-
if (/error|fail|exception/i.test(text))
|
|
281
|
-
return true;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
function fingerprint(obj) {
|
|
288
|
-
if (!obj)
|
|
289
|
-
return "empty";
|
|
290
|
-
const keys = Object.keys(obj).sort();
|
|
291
|
-
const significant = keys.slice(0, 5).map((k) => {
|
|
292
|
-
const v = obj[k];
|
|
293
|
-
if (typeof v === "string")
|
|
294
|
-
return `${k}:${v.slice(0, 50)}`;
|
|
295
|
-
if (typeof v === "number" || typeof v === "boolean")
|
|
296
|
-
return `${k}:${v}`;
|
|
297
|
-
return `${k}:${typeof v}`;
|
|
298
|
-
});
|
|
299
|
-
return significant.join("|");
|
|
300
|
-
}
|