@unerr-ai/unerr 0.2.1 → 0.2.3
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 +36 -45
- package/dist/cli.js +37443 -36022
- package/package.json +2 -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,358 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* R3 — user-facing filter DSL.
|
|
3
|
-
*
|
|
4
|
-
* Reads `.unerr/filters.toml` (per-repo) and `~/.config/unerr/filters.toml`
|
|
5
|
-
* (user-global). Filters are checked BEFORE the built-in classifier. First
|
|
6
|
-
* filter whose `match_command` regex matches the command wins.
|
|
7
|
-
*
|
|
8
|
-
* Directives (subset of RTK's filter schema):
|
|
9
|
-
* match_command = "^my-tool\\b"
|
|
10
|
-
* strip_lines_matching = ["^\\s*$", "^noise"]
|
|
11
|
-
* keep_lines_matching = ["^ERROR"]
|
|
12
|
-
* replace = [ { pattern = "...", replacement = "..." } ]
|
|
13
|
-
* match_output = [ { pattern = "0 errors", message = "ok" } ]
|
|
14
|
-
* truncate_lines_at = 200
|
|
15
|
-
* max_lines = 80
|
|
16
|
-
* tail_lines = 40
|
|
17
|
-
* filter_stderr = true
|
|
18
|
-
* on_empty = "ok"
|
|
19
|
-
*
|
|
20
|
-
* To keep zero new deps, this module ships a tiny TOML reader that
|
|
21
|
-
* understands the directive subset above. Anything fancier (nested arrays,
|
|
22
|
-
* multi-line strings, dates) is not supported — the loader returns null
|
|
23
|
-
* and we fall through to the built-in classifier.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
26
|
-
import { homedir } from "node:os";
|
|
27
|
-
import { join } from "node:path";
|
|
28
|
-
let cachedFilters = null;
|
|
29
|
-
let cacheKey = "";
|
|
30
|
-
function readFile(path) {
|
|
31
|
-
try {
|
|
32
|
-
if (!existsSync(path))
|
|
33
|
-
return null;
|
|
34
|
-
return readFileSync(path, "utf8");
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// ─── Minimal TOML reader for our directive subset ──────────────────────────
|
|
41
|
-
function unquote(input) {
|
|
42
|
-
const s = input.trim();
|
|
43
|
-
if ((s.startsWith('"') && s.endsWith('"')) ||
|
|
44
|
-
(s.startsWith("'") && s.endsWith("'"))) {
|
|
45
|
-
return s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
46
|
-
}
|
|
47
|
-
return s;
|
|
48
|
-
}
|
|
49
|
-
function parseScalar(v) {
|
|
50
|
-
const t = v.trim();
|
|
51
|
-
if (t === "true")
|
|
52
|
-
return true;
|
|
53
|
-
if (t === "false")
|
|
54
|
-
return false;
|
|
55
|
-
if (/^-?\d+$/.test(t))
|
|
56
|
-
return Number.parseInt(t, 10);
|
|
57
|
-
if (/^-?\d+\.\d+$/.test(t))
|
|
58
|
-
return Number.parseFloat(t);
|
|
59
|
-
return unquote(t);
|
|
60
|
-
}
|
|
61
|
-
function parseArrayLiteral(v) {
|
|
62
|
-
const trimmed = v.trim();
|
|
63
|
-
if (!trimmed.startsWith("[") || !trimmed.endsWith("]"))
|
|
64
|
-
return null;
|
|
65
|
-
const inner = trimmed.slice(1, -1).trim();
|
|
66
|
-
if (!inner)
|
|
67
|
-
return [];
|
|
68
|
-
// Array of inline tables: [{ pattern = "x", replacement = "y" }, ...]
|
|
69
|
-
if (inner.includes("{")) {
|
|
70
|
-
const tables = [];
|
|
71
|
-
let depth = 0;
|
|
72
|
-
let start = -1;
|
|
73
|
-
for (let i = 0; i < inner.length; i++) {
|
|
74
|
-
const ch = inner[i];
|
|
75
|
-
if (ch === "{") {
|
|
76
|
-
if (depth === 0)
|
|
77
|
-
start = i + 1;
|
|
78
|
-
depth++;
|
|
79
|
-
}
|
|
80
|
-
else if (ch === "}") {
|
|
81
|
-
depth--;
|
|
82
|
-
if (depth === 0 && start >= 0) {
|
|
83
|
-
const body = inner.slice(start, i);
|
|
84
|
-
const obj = {};
|
|
85
|
-
for (const pair of body.split(",")) {
|
|
86
|
-
const eq = pair.indexOf("=");
|
|
87
|
-
if (eq < 0)
|
|
88
|
-
continue;
|
|
89
|
-
const k = pair.slice(0, eq).trim();
|
|
90
|
-
const val = pair.slice(eq + 1).trim();
|
|
91
|
-
obj[k] = parseScalar(val);
|
|
92
|
-
}
|
|
93
|
-
tables.push(obj);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return tables;
|
|
98
|
-
}
|
|
99
|
-
// Simple string/number array
|
|
100
|
-
return inner
|
|
101
|
-
.split(/,\s*/)
|
|
102
|
-
.map((s) => s.trim())
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.map(parseScalar);
|
|
105
|
-
}
|
|
106
|
-
function parseToml(src) {
|
|
107
|
-
const sections = [];
|
|
108
|
-
let current = null;
|
|
109
|
-
// Join continuation lines for arrays that span multiple lines
|
|
110
|
-
const compactLines = [];
|
|
111
|
-
let buf = "";
|
|
112
|
-
let depth = 0;
|
|
113
|
-
for (const rawLine of src.split("\n")) {
|
|
114
|
-
const line = rawLine.replace(/#.*$/, "");
|
|
115
|
-
if (depth > 0) {
|
|
116
|
-
buf += ` ${line.trim()}`;
|
|
117
|
-
}
|
|
118
|
-
else if (line.includes("[") && !line.match(/^\s*\[[^=]*\]\s*$/)) {
|
|
119
|
-
// Likely the start of an inline-table array or multi-line array
|
|
120
|
-
buf = line;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
compactLines.push(line);
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
for (const ch of line) {
|
|
127
|
-
if (ch === "[")
|
|
128
|
-
depth++;
|
|
129
|
-
else if (ch === "]")
|
|
130
|
-
depth--;
|
|
131
|
-
}
|
|
132
|
-
if (depth <= 0) {
|
|
133
|
-
compactLines.push(buf);
|
|
134
|
-
buf = "";
|
|
135
|
-
depth = 0;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (buf)
|
|
139
|
-
compactLines.push(buf);
|
|
140
|
-
for (const rawLine of compactLines) {
|
|
141
|
-
const line = rawLine.trim();
|
|
142
|
-
if (!line)
|
|
143
|
-
continue;
|
|
144
|
-
const sectionMatch = line.match(/^\[([^\]]+)\]\s*$/);
|
|
145
|
-
if (sectionMatch?.[1]) {
|
|
146
|
-
if (current)
|
|
147
|
-
sections.push(current);
|
|
148
|
-
current = { name: sectionMatch[1], values: {} };
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (!current)
|
|
152
|
-
continue;
|
|
153
|
-
const eq = line.indexOf("=");
|
|
154
|
-
if (eq < 0)
|
|
155
|
-
continue;
|
|
156
|
-
const key = line.slice(0, eq).trim();
|
|
157
|
-
const rest = line.slice(eq + 1).trim();
|
|
158
|
-
const arr = parseArrayLiteral(rest);
|
|
159
|
-
current.values[key] = arr ?? parseScalar(rest);
|
|
160
|
-
}
|
|
161
|
-
if (current)
|
|
162
|
-
sections.push(current);
|
|
163
|
-
return sections;
|
|
164
|
-
}
|
|
165
|
-
// ─── Loading + compilation ─────────────────────────────────────────────────
|
|
166
|
-
function compileOne(name, raw) {
|
|
167
|
-
const mc = raw.match_command;
|
|
168
|
-
if (typeof mc !== "string" || !mc)
|
|
169
|
-
return null;
|
|
170
|
-
let matchCommand;
|
|
171
|
-
try {
|
|
172
|
-
matchCommand = new RegExp(mc);
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
const toRegexes = (arr) => {
|
|
178
|
-
if (!Array.isArray(arr))
|
|
179
|
-
return undefined;
|
|
180
|
-
const out = [];
|
|
181
|
-
for (const s of arr) {
|
|
182
|
-
if (typeof s !== "string")
|
|
183
|
-
continue;
|
|
184
|
-
try {
|
|
185
|
-
out.push(new RegExp(s));
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
// skip bad regex
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return out.length > 0 ? out : undefined;
|
|
192
|
-
};
|
|
193
|
-
const toReplace = (arr) => {
|
|
194
|
-
if (!Array.isArray(arr))
|
|
195
|
-
return undefined;
|
|
196
|
-
const out = [];
|
|
197
|
-
for (const item of arr) {
|
|
198
|
-
if (item &&
|
|
199
|
-
typeof item === "object" &&
|
|
200
|
-
typeof item.pattern === "string") {
|
|
201
|
-
const p = item.pattern;
|
|
202
|
-
const r = item.replacement;
|
|
203
|
-
try {
|
|
204
|
-
out.push({ pattern: new RegExp(p, "g"), replacement: r ?? "" });
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
// skip
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return out.length > 0 ? out : undefined;
|
|
212
|
-
};
|
|
213
|
-
const toShortCircuit = (arr) => {
|
|
214
|
-
if (!Array.isArray(arr))
|
|
215
|
-
return undefined;
|
|
216
|
-
const out = [];
|
|
217
|
-
for (const item of arr) {
|
|
218
|
-
if (item &&
|
|
219
|
-
typeof item === "object" &&
|
|
220
|
-
typeof item.pattern === "string") {
|
|
221
|
-
const p = item.pattern;
|
|
222
|
-
const m = item.message ??
|
|
223
|
-
"ok";
|
|
224
|
-
try {
|
|
225
|
-
out.push({ pattern: new RegExp(p), message: m });
|
|
226
|
-
}
|
|
227
|
-
catch {
|
|
228
|
-
// skip
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return out.length > 0 ? out : undefined;
|
|
233
|
-
};
|
|
234
|
-
const f = {
|
|
235
|
-
name,
|
|
236
|
-
match_command: mc,
|
|
237
|
-
strip_lines_matching: Array.isArray(raw.strip_lines_matching)
|
|
238
|
-
? raw.strip_lines_matching.filter((x) => typeof x === "string")
|
|
239
|
-
: undefined,
|
|
240
|
-
keep_lines_matching: Array.isArray(raw.keep_lines_matching)
|
|
241
|
-
? raw.keep_lines_matching.filter((x) => typeof x === "string")
|
|
242
|
-
: undefined,
|
|
243
|
-
replace: Array.isArray(raw.replace)
|
|
244
|
-
? raw.replace
|
|
245
|
-
: undefined,
|
|
246
|
-
match_output: Array.isArray(raw.match_output)
|
|
247
|
-
? raw.match_output
|
|
248
|
-
: undefined,
|
|
249
|
-
truncate_lines_at: typeof raw.truncate_lines_at === "number"
|
|
250
|
-
? raw.truncate_lines_at
|
|
251
|
-
: undefined,
|
|
252
|
-
max_lines: typeof raw.max_lines === "number" ? raw.max_lines : undefined,
|
|
253
|
-
tail_lines: typeof raw.tail_lines === "number" ? raw.tail_lines : undefined,
|
|
254
|
-
filter_stderr: typeof raw.filter_stderr === "boolean" ? raw.filter_stderr : undefined,
|
|
255
|
-
on_empty: typeof raw.on_empty === "string" ? raw.on_empty : undefined,
|
|
256
|
-
strip_ansi: typeof raw.strip_ansi === "boolean" ? raw.strip_ansi : undefined,
|
|
257
|
-
};
|
|
258
|
-
return {
|
|
259
|
-
raw: f,
|
|
260
|
-
matchCommand,
|
|
261
|
-
strip: toRegexes(raw.strip_lines_matching),
|
|
262
|
-
keep: toRegexes(raw.keep_lines_matching),
|
|
263
|
-
replace: toReplace(raw.replace),
|
|
264
|
-
shortCircuit: toShortCircuit(raw.match_output),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
function loadFilters(cwd) {
|
|
268
|
-
const repoPath = join(cwd, ".unerr", "filters.toml");
|
|
269
|
-
const userPath = join(homedir(), ".config", "unerr", "filters.toml");
|
|
270
|
-
const key = `${repoPath}|${userPath}`;
|
|
271
|
-
if (cachedFilters && cacheKey === key)
|
|
272
|
-
return cachedFilters;
|
|
273
|
-
const sources = [readFile(repoPath), readFile(userPath)].filter((s) => Boolean(s));
|
|
274
|
-
const compiled = [];
|
|
275
|
-
for (const src of sources) {
|
|
276
|
-
const sections = parseToml(src);
|
|
277
|
-
for (const section of sections) {
|
|
278
|
-
if (!section.name.startsWith("filters."))
|
|
279
|
-
continue;
|
|
280
|
-
const name = section.name.slice("filters.".length);
|
|
281
|
-
const c = compileOne(name, section.values);
|
|
282
|
-
if (c)
|
|
283
|
-
compiled.push(c);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
cachedFilters = compiled;
|
|
287
|
-
cacheKey = key;
|
|
288
|
-
return compiled;
|
|
289
|
-
}
|
|
290
|
-
/**
|
|
291
|
-
* Apply the first matching user filter to the output. Returns null if no
|
|
292
|
-
* filter matches — caller falls through to the built-in classifier.
|
|
293
|
-
*/
|
|
294
|
-
export function applyUserFilter(command, stdout, cwd) {
|
|
295
|
-
const filters = loadFilters(cwd);
|
|
296
|
-
if (filters.length === 0)
|
|
297
|
-
return null;
|
|
298
|
-
const filter = filters.find((f) => f.matchCommand.test(command));
|
|
299
|
-
if (!filter)
|
|
300
|
-
return null;
|
|
301
|
-
// Short-circuit on success patterns first
|
|
302
|
-
if (filter.shortCircuit) {
|
|
303
|
-
for (const sc of filter.shortCircuit) {
|
|
304
|
-
if (sc.pattern.test(stdout)) {
|
|
305
|
-
return {
|
|
306
|
-
text: `_shell_fmt:user_filter\n${sc.message}`,
|
|
307
|
-
name: filter.raw.name,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
let text = stdout;
|
|
313
|
-
// Replace
|
|
314
|
-
if (filter.replace) {
|
|
315
|
-
for (const r of filter.replace) {
|
|
316
|
-
text = text.replace(r.pattern, r.replacement);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
// Strip / keep
|
|
320
|
-
let lines = text.split("\n");
|
|
321
|
-
if (filter.strip) {
|
|
322
|
-
lines = lines.filter((l) => !filter.strip?.some((r) => r.test(l)));
|
|
323
|
-
}
|
|
324
|
-
if (filter.keep) {
|
|
325
|
-
lines = lines.filter((l) => filter.keep?.some((r) => r.test(l)));
|
|
326
|
-
}
|
|
327
|
-
// Truncate long lines
|
|
328
|
-
if (filter.raw.truncate_lines_at) {
|
|
329
|
-
const limit = filter.raw.truncate_lines_at;
|
|
330
|
-
lines = lines.map((l) => (l.length > limit ? `${l.slice(0, limit)}…` : l));
|
|
331
|
-
}
|
|
332
|
-
// Window
|
|
333
|
-
if (filter.raw.tail_lines && lines.length > filter.raw.tail_lines) {
|
|
334
|
-
lines = [
|
|
335
|
-
`… (${lines.length - filter.raw.tail_lines} earlier lines suppressed)`,
|
|
336
|
-
...lines.slice(-filter.raw.tail_lines),
|
|
337
|
-
];
|
|
338
|
-
}
|
|
339
|
-
else if (filter.raw.max_lines && lines.length > filter.raw.max_lines) {
|
|
340
|
-
lines = [
|
|
341
|
-
...lines.slice(0, filter.raw.max_lines),
|
|
342
|
-
`… (${lines.length - filter.raw.max_lines} more lines suppressed)`,
|
|
343
|
-
];
|
|
344
|
-
}
|
|
345
|
-
text = lines.join("\n").trim();
|
|
346
|
-
if (!text && filter.raw.on_empty) {
|
|
347
|
-
text = filter.raw.on_empty;
|
|
348
|
-
}
|
|
349
|
-
return {
|
|
350
|
-
text: `_shell_fmt:user_filter[${filter.raw.name}]\n${text}`,
|
|
351
|
-
name: filter.raw.name,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
/** Test helper — flush cache so repeated calls in tests pick up new files. */
|
|
355
|
-
export function _clearFilterCache() {
|
|
356
|
-
cachedFilters = null;
|
|
357
|
-
cacheKey = "";
|
|
358
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dedicated `git status` parser (Fix F2 from the verification report).
|
|
3
|
-
*
|
|
4
|
-
* Default git status output is mostly boilerplate help text in parens
|
|
5
|
-
* ("(use git add ...)") that an agent does not need. Each section
|
|
6
|
-
* (modified, untracked, staged, deleted) has a predictable shape that
|
|
7
|
-
* compresses well with grouping by directory.
|
|
8
|
-
*
|
|
9
|
-
* Falls back to null on porcelain v2 / unrecognized formats so the caller
|
|
10
|
-
* can defer to the generic strategy.
|
|
11
|
-
*/
|
|
12
|
-
const HELP_RE = /^\s*\(use\b.*\)\s*$/i;
|
|
13
|
-
const BRANCH_RE = /^On branch (.+)$/;
|
|
14
|
-
const TRACKING_RE = /^Your branch is (.+)$/;
|
|
15
|
-
const SECTION_HEADERS = new Map([
|
|
16
|
-
["Changes to be committed:", "staged"],
|
|
17
|
-
["Changes not staged for commit:", "modified"],
|
|
18
|
-
["Untracked files:", "untracked"],
|
|
19
|
-
["Unmerged paths:", "unmerged"],
|
|
20
|
-
]);
|
|
21
|
-
function extOf(path) {
|
|
22
|
-
const base = path.slice(path.lastIndexOf("/") + 1);
|
|
23
|
-
const dot = base.lastIndexOf(".");
|
|
24
|
-
if (dot <= 0 || dot === base.length - 1)
|
|
25
|
-
return "(no-ext)";
|
|
26
|
-
return base.slice(dot).toLowerCase();
|
|
27
|
-
}
|
|
28
|
-
function dirOf(path) {
|
|
29
|
-
const i = path.lastIndexOf("/");
|
|
30
|
-
return i >= 0 ? `${path.slice(0, i)}/` : "./";
|
|
31
|
-
}
|
|
32
|
-
function topExt(byExt, k = 3) {
|
|
33
|
-
const sorted = [...byExt.entries()].sort((a, b) => b[1] - a[1]);
|
|
34
|
-
return sorted
|
|
35
|
-
.slice(0, k)
|
|
36
|
-
.map(([ext, n]) => `${n} ${ext}`)
|
|
37
|
-
.join(", ");
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Compress a `git status` output. Returns null if input doesn't look like
|
|
41
|
-
* default git status output.
|
|
42
|
-
*/
|
|
43
|
-
export function compressGitStatus(raw) {
|
|
44
|
-
const lines = raw.replace(/\r\n/g, "\n").split("\n");
|
|
45
|
-
if (lines.length === 0)
|
|
46
|
-
return null;
|
|
47
|
-
// Quick fingerprint check
|
|
48
|
-
if (!lines.some((l) => /^On branch /.test(l) || /^HEAD detached/.test(l))) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
let branch = "";
|
|
52
|
-
let tracking = "";
|
|
53
|
-
let currentBucket = null;
|
|
54
|
-
const buckets = {
|
|
55
|
-
staged: { files: [], statuses: new Map() },
|
|
56
|
-
modified: { files: [], statuses: new Map() },
|
|
57
|
-
untracked: { files: [], statuses: new Map() },
|
|
58
|
-
unmerged: { files: [], statuses: new Map() },
|
|
59
|
-
};
|
|
60
|
-
let trailingMsg = "";
|
|
61
|
-
for (const line of lines) {
|
|
62
|
-
if (!line.trim())
|
|
63
|
-
continue;
|
|
64
|
-
if (HELP_RE.test(line))
|
|
65
|
-
continue; // strip "(use git add ...)" help blocks
|
|
66
|
-
const branchMatch = line.match(BRANCH_RE);
|
|
67
|
-
if (branchMatch) {
|
|
68
|
-
branch = branchMatch[1] ?? "";
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const trackingMatch = line.match(TRACKING_RE);
|
|
72
|
-
if (trackingMatch) {
|
|
73
|
-
tracking = trackingMatch[1] ?? "";
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const sectionKey = SECTION_HEADERS.get(line.trim());
|
|
77
|
-
if (sectionKey) {
|
|
78
|
-
currentBucket = sectionKey;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
// Trailing summary like "no changes added to commit (use ...)" terminates
|
|
82
|
-
// any open section so it doesn't get misread as an untracked path.
|
|
83
|
-
if (/^(?:no changes added to commit|nothing to commit|nothing added|on branch \S+ nothing|.*working tree clean)/i.test(line.trim())) {
|
|
84
|
-
// Strip trailing "(use 'git add' ...)" boilerplate from the footer
|
|
85
|
-
trailingMsg = line
|
|
86
|
-
.trim()
|
|
87
|
-
.replace(/\s*\(use\b[^)]*\)\s*/g, "")
|
|
88
|
-
.trim();
|
|
89
|
-
currentBucket = null;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (currentBucket) {
|
|
93
|
-
// Real file lines are tab-indented in git's default output. Anything
|
|
94
|
-
// un-indented after a section header is footer text, not a file.
|
|
95
|
-
if (!line.startsWith("\t") && !line.startsWith(" ")) {
|
|
96
|
-
currentBucket = null;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
const stripped = line.replace(/^\t/, "").trim();
|
|
100
|
-
if (!stripped)
|
|
101
|
-
continue;
|
|
102
|
-
const m = stripped.match(/^([a-zA-Z ]+):\s+(.+?)(?:\s+->\s+(.+))?$/);
|
|
103
|
-
if (m) {
|
|
104
|
-
const status = m[1]?.trim() ?? "";
|
|
105
|
-
const path = (m[3] ?? m[2] ?? "").trim();
|
|
106
|
-
buckets[currentBucket].files.push(path);
|
|
107
|
-
buckets[currentBucket].statuses.set(status, (buckets[currentBucket].statuses.get(status) ?? 0) + 1);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
// Untracked section: each line is just a path
|
|
111
|
-
buckets[currentBucket].files.push(stripped);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const totalFiles = buckets.staged.files.length +
|
|
116
|
-
buckets.modified.files.length +
|
|
117
|
-
buckets.untracked.files.length +
|
|
118
|
-
buckets.unmerged.files.length;
|
|
119
|
-
// If we didn't recognize any files, this isn't a format we handle —
|
|
120
|
-
// let the generic strategy take it.
|
|
121
|
-
if (totalFiles === 0 && !branch)
|
|
122
|
-
return null;
|
|
123
|
-
const out = ["_shell_fmt:git_status"];
|
|
124
|
-
const branchLine = branch
|
|
125
|
-
? tracking
|
|
126
|
-
? `branch=${branch}; ${tracking}`
|
|
127
|
-
: `branch=${branch}`
|
|
128
|
-
: "";
|
|
129
|
-
if (branchLine)
|
|
130
|
-
out.push(branchLine);
|
|
131
|
-
// Per-section rollup: list files individually if ≤6, otherwise group by dir
|
|
132
|
-
const sectionOrder = [
|
|
133
|
-
"staged",
|
|
134
|
-
"modified",
|
|
135
|
-
"untracked",
|
|
136
|
-
"unmerged",
|
|
137
|
-
];
|
|
138
|
-
for (const bucket of sectionOrder) {
|
|
139
|
-
const b = buckets[bucket];
|
|
140
|
-
if (b.files.length === 0)
|
|
141
|
-
continue;
|
|
142
|
-
const statusSummary = b.statuses.size > 0
|
|
143
|
-
? ` (${[...b.statuses.entries()].map(([s, n]) => `${n} ${s}`).join(", ")})`
|
|
144
|
-
: "";
|
|
145
|
-
out.push(`${bucket}: ${b.files.length}${statusSummary}`);
|
|
146
|
-
if (b.files.length <= 6) {
|
|
147
|
-
// Small list — show each file
|
|
148
|
-
for (const f of b.files)
|
|
149
|
-
out.push(` ${f}`);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
// Big list — group by directory
|
|
153
|
-
const byDir = new Map();
|
|
154
|
-
for (const f of b.files) {
|
|
155
|
-
const dir = dirOf(f);
|
|
156
|
-
let agg = byDir.get(dir);
|
|
157
|
-
if (!agg) {
|
|
158
|
-
agg = { total: 0, byExt: new Map() };
|
|
159
|
-
byDir.set(dir, agg);
|
|
160
|
-
}
|
|
161
|
-
agg.total++;
|
|
162
|
-
const ext = extOf(f);
|
|
163
|
-
agg.byExt.set(ext, (agg.byExt.get(ext) ?? 0) + 1);
|
|
164
|
-
}
|
|
165
|
-
const sortedDirs = [...byDir.entries()].sort((a, b2) => b2[1].total - a[1].total);
|
|
166
|
-
for (const [dir, agg] of sortedDirs.slice(0, 8)) {
|
|
167
|
-
out.push(` ${dir} (${agg.total}: ${topExt(agg.byExt)})`);
|
|
168
|
-
}
|
|
169
|
-
if (sortedDirs.length > 8) {
|
|
170
|
-
out.push(` … ${sortedDirs.length - 8} more directories`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (trailingMsg)
|
|
175
|
-
out.push(trailingMsg);
|
|
176
|
-
return out.join("\n");
|
|
177
|
-
}
|