sweet-search 2.5.2 → 2.5.4
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/core/cli.js +24 -3
- package/core/graph/graph-expansion.js +215 -36
- package/core/graph/graph-extractor.js +196 -11
- package/core/graph/graph-search.js +395 -92
- package/core/graph/hcgs-generator.js +2 -1
- package/core/graph/index.js +2 -0
- package/core/graph/repo-map.js +28 -6
- package/core/graph/structural-answer-cues.js +168 -0
- package/core/graph/structural-callsite-hints.js +40 -0
- package/core/graph/structural-context-format.js +40 -0
- package/core/graph/structural-context.js +450 -0
- package/core/graph/structural-forward-push.js +156 -0
- package/core/graph/structural-header-context.js +19 -0
- package/core/graph/structural-importance.js +148 -0
- package/core/graph/structural-pagerank.js +197 -0
- package/core/graph/summary-manager.js +13 -9
- package/core/incremental-indexing/application/dirty-scan.mjs +236 -0
- package/core/incremental-indexing/application/file-watcher.mjs +197 -0
- package/core/incremental-indexing/application/maintenance-handlers.mjs +519 -0
- package/core/incremental-indexing/application/maintenance-worker.mjs +380 -0
- package/core/incremental-indexing/application/operator-cli.mjs +554 -0
- package/core/incremental-indexing/application/production-li-delta.mjs +192 -0
- package/core/incremental-indexing/application/production-reconciler-helpers.mjs +107 -0
- package/core/incremental-indexing/application/production-reconciler.mjs +583 -0
- package/core/incremental-indexing/application/reconciler.mjs +477 -0
- package/core/incremental-indexing/application/tombstone-injector.mjs +148 -0
- package/core/incremental-indexing/domain/chunk-identity.mjs +260 -0
- package/core/incremental-indexing/domain/encoder-deps.mjs +193 -0
- package/core/incremental-indexing/domain/encoder-input.mjs +225 -0
- package/core/incremental-indexing/domain/interval-autotune.mjs +255 -0
- package/core/incremental-indexing/domain/reconcile-counters.mjs +149 -0
- package/core/incremental-indexing/domain/watermark-scheduler.mjs +239 -0
- package/core/incremental-indexing/infrastructure/artifact-temp-sweep.mjs +163 -0
- package/core/incremental-indexing/infrastructure/baseline-readiness.mjs +121 -0
- package/core/incremental-indexing/infrastructure/dirty-set.mjs +233 -0
- package/core/incremental-indexing/infrastructure/graph-gc.mjs +314 -0
- package/core/incremental-indexing/infrastructure/hashing.mjs +298 -0
- package/core/incremental-indexing/infrastructure/hcgs-invalidation.mjs +182 -0
- package/core/incremental-indexing/infrastructure/li-segment-merge.mjs +278 -0
- package/core/incremental-indexing/infrastructure/li-segment-state.mjs +173 -0
- package/core/incremental-indexing/infrastructure/lockfile.mjs +119 -0
- package/core/incremental-indexing/infrastructure/maintenance-state-reader.mjs +283 -0
- package/core/incremental-indexing/infrastructure/manifest.mjs +194 -0
- package/core/incremental-indexing/infrastructure/path-filter.mjs +190 -0
- package/core/incremental-indexing/infrastructure/reader-heartbeat.mjs +201 -0
- package/core/incremental-indexing/infrastructure/schema-migrations.mjs +257 -0
- package/core/incremental-indexing/infrastructure/sparse-gram-delta.mjs +335 -0
- package/core/incremental-indexing/infrastructure/sqlite-fts5.mjs +176 -0
- package/core/incremental-indexing/infrastructure/staleness-display.mjs +105 -0
- package/core/incremental-indexing/infrastructure/tombstone-bitmap.mjs +234 -0
- package/core/incremental-indexing/infrastructure/vector-delta-writer.mjs +359 -0
- package/core/incremental-indexing/infrastructure/vector-gc.mjs +133 -0
- package/core/incremental-indexing/infrastructure/worktree-stamp.mjs +155 -0
- package/core/incremental-indexing/infrastructure/wsl2-detect.mjs +115 -0
- package/core/indexing/admission-policy.js +139 -0
- package/core/indexing/artifact-builder.js +29 -12
- package/core/indexing/ast-chunker.js +107 -30
- package/core/indexing/dedup/exemplar-selector.js +19 -1
- package/core/indexing/gitignore-filter.js +223 -0
- package/core/indexing/incremental-tracker.js +99 -30
- package/core/indexing/index-codebase-v21.js +6 -5
- package/core/indexing/index-maintainer.mjs +698 -6
- package/core/indexing/indexer-ann.js +99 -15
- package/core/indexing/indexer-build.js +158 -45
- package/core/indexing/indexer-empty-baseline.js +80 -0
- package/core/indexing/indexer-manifest.js +66 -0
- package/core/indexing/indexer-phases.js +56 -23
- package/core/indexing/indexer-sparse-gram.js +54 -13
- package/core/indexing/indexer-utils.js +26 -208
- package/core/indexing/indexing-file-policy.js +32 -7
- package/core/indexing/maintainer-launcher.mjs +137 -0
- package/core/indexing/merkle-tracker.js +251 -244
- package/core/indexing/model-pool.js +46 -5
- package/core/infrastructure/code-graph-repository.js +758 -6
- package/core/infrastructure/code-graph-visibility.js +157 -0
- package/core/infrastructure/codebase-repository.js +100 -13
- package/core/infrastructure/config/search.js +1 -1
- package/core/infrastructure/db-utils.js +118 -0
- package/core/infrastructure/dedup-hashing.js +10 -13
- package/core/infrastructure/hardware-capability.js +17 -7
- package/core/infrastructure/index.js +8 -2
- package/core/infrastructure/language-patterns/maps.js +4 -1
- package/core/infrastructure/language-patterns/registry-core.js +56 -17
- package/core/infrastructure/language-patterns/registry-object-oriented.js +12 -5
- package/core/infrastructure/language-patterns.js +69 -0
- package/core/infrastructure/model-registry.js +20 -0
- package/core/infrastructure/native-inference.js +7 -12
- package/core/infrastructure/native-resolver.js +52 -37
- package/core/infrastructure/native-sparse-gram.js +261 -20
- package/core/infrastructure/native-tokenizer.js +6 -15
- package/core/infrastructure/simd-distance.js +10 -16
- package/core/infrastructure/sparse-gram-delta-reader.js +76 -0
- package/core/infrastructure/structural-alias-resolver.js +122 -0
- package/core/infrastructure/structural-candidate-ranker.js +34 -0
- package/core/infrastructure/structural-context-repository.js +472 -0
- package/core/infrastructure/structural-context-utils.js +51 -0
- package/core/infrastructure/structural-graph-signals.js +121 -0
- package/core/infrastructure/structural-qualified-resolution.js +15 -0
- package/core/infrastructure/structural-source-definitions.js +100 -0
- package/core/infrastructure/tombstone-bitmap-reader.js +139 -0
- package/core/infrastructure/tree-sitter-provider.js +811 -37
- package/core/prompt-optimization/data/p7-final/sweet-search-system-prompt.md +50 -0
- package/core/query/query-router.js +55 -5
- package/core/ranking/file-kind-ranking.js +2192 -15
- package/core/ranking/late-interaction-index.js +87 -12
- package/core/search/cli-decoration.js +290 -0
- package/core/search/context-expander.js +988 -78
- package/core/search/index.js +1 -0
- package/core/search/output-policy.js +275 -0
- package/core/search/search-anchor.js +499 -0
- package/core/search/search-boost.js +93 -1
- package/core/search/search-cli.js +61 -204
- package/core/search/search-hybrid.js +250 -10
- package/core/search/search-pattern-chunks.js +57 -8
- package/core/search/search-pattern-planner.js +68 -9
- package/core/search/search-pattern-prefilter.js +30 -10
- package/core/search/search-pattern-ripgrep.js +40 -4
- package/core/search/search-pattern-sparse-overlay.js +256 -0
- package/core/search/search-pattern.js +117 -29
- package/core/search/search-postprocess.js +479 -5
- package/core/search/search-read-semantic.js +260 -23
- package/core/search/search-read.js +82 -64
- package/core/search/search-reader-pin.js +71 -0
- package/core/search/search-rrf.js +279 -0
- package/core/search/search-semantic.js +110 -5
- package/core/search/search-server.js +130 -57
- package/core/search/search-trace.js +107 -0
- package/core/search/server-identity.js +93 -0
- package/core/search/session-daemon-prewarm.mjs +33 -10
- package/core/search/sweet-search.js +399 -7
- package/core/skills/sweet-index/SKILL.md +8 -6
- package/core/vector-store/binary-hnsw-index.js +194 -30
- package/core/vector-store/float-vector-store.js +96 -6
- package/core/vector-store/hnsw-index.js +220 -49
- package/eval/agent-read-workflows/bin/_ss-helpers.mjs +471 -0
- package/eval/agent-read-workflows/bin/ss-find +15 -0
- package/eval/agent-read-workflows/bin/ss-grep +12 -0
- package/eval/agent-read-workflows/bin/ss-read +14 -0
- package/eval/agent-read-workflows/bin/ss-search +18 -0
- package/eval/agent-read-workflows/bin/ss-semantic +12 -0
- package/eval/agent-read-workflows/bin/ss-trace +11 -0
- package/mcp/read-tool.js +109 -0
- package/mcp/server.js +55 -15
- package/mcp/tool-handlers.js +14 -124
- package/mcp/trace-tool.js +81 -0
- package/package.json +25 -10
- package/scripts/hooks/intercept-read.mjs +55 -0
- package/scripts/hooks/remind-tools.mjs +40 -0
- package/scripts/init.js +698 -54
- package/scripts/inject-agent-instructions.js +431 -0
- package/scripts/install-prompt-reminders.js +188 -0
- package/scripts/install-tool-enforcement.js +220 -0
- package/scripts/smoke-test.js +12 -9
- package/scripts/uninstall.js +276 -18
- package/scripts/write-claude-rules.js +110 -0
package/core/search/index.js
CHANGED
|
@@ -18,3 +18,4 @@ export { hybridSearch, hybridSearchV2 } from './search-hybrid.js';
|
|
|
18
18
|
// Read tools — filesystem-grounded exact reads + hybrid semantic span selection
|
|
19
19
|
export { readFile, readFiles, formatReadResults, handleReadCli } from './search-read.js';
|
|
20
20
|
export { readSemantic, formatReadSemanticResult, handleReadSemanticCli } from './search-read-semantic.js';
|
|
21
|
+
export { traceSymbol, handleTraceCli, formatStructuralContext } from './search-trace.js';
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output-decoration policy for sweet-search CLI tools.
|
|
3
|
+
*
|
|
4
|
+
* Decides WHERE decoration (banner/colors/animation) may be emitted so it is
|
|
5
|
+
* shown only on channels that are free to the user/model, and suppressed
|
|
6
|
+
* wherever captured output would spend tokens. This module makes decisions; it
|
|
7
|
+
* does not render. Renderers (search-cli.js) consult the returned policy.
|
|
8
|
+
*
|
|
9
|
+
* Channel facts that drive the policy (see investigation notes):
|
|
10
|
+
* - MCP reads only the JSON-RPC response content — decoration must never be
|
|
11
|
+
* placed there. (Enforced structurally in the MCP layer, not here.)
|
|
12
|
+
* - Claude Code Bash captures stdout+stderr, but the controlling terminal is
|
|
13
|
+
* usually still reachable via /dev/tty, which the pipe does not intercept —
|
|
14
|
+
* so decoration written there is free.
|
|
15
|
+
* - Codex-style shells pipe stdio and sandbox /dev/tty — never rely on it.
|
|
16
|
+
* - Cursor/Cline PTY integrations capture the PTY stream itself, so /dev/tty
|
|
17
|
+
* writes are captured. They present as isTTY=true; we keep them out of the
|
|
18
|
+
* decorate-on-stdout tier only when a harness env marker is detectable.
|
|
19
|
+
*
|
|
20
|
+
* Decoration is NEVER routed to stderr — that channel is captured too.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { openSync, closeSync, writeSync } from 'node:fs';
|
|
24
|
+
|
|
25
|
+
// Best-effort env markers for captured harnesses where decoration on stdout
|
|
26
|
+
// (or on a PTY-backed /dev/tty) would cost tokens. Detection here means
|
|
27
|
+
// "suppress decoration". Extend conservatively — a false positive only costs a
|
|
28
|
+
// banner, a false negative costs the user tokens.
|
|
29
|
+
const OTHER_AGENT_ENV_KEYS = ['CURSOR_TRACE_ID', 'CLINE_ACTIVE', 'GEMINI_CLI', 'OPENCODE'];
|
|
30
|
+
|
|
31
|
+
function _truthy(value) {
|
|
32
|
+
if (value == null) return false;
|
|
33
|
+
const v = String(value).trim().toLowerCase();
|
|
34
|
+
return v !== '' && v !== '0' && v !== 'false' && v !== 'off' && v !== 'no';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function _hasNoColor(env) {
|
|
38
|
+
// NO_COLOR convention: any non-empty value disables color.
|
|
39
|
+
const v = env.NO_COLOR;
|
|
40
|
+
return v != null && String(v) !== '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Global decoration override: SWEET_SEARCH_DECORATION=auto|always|never.
|
|
44
|
+
// Unknown/empty values fall back to 'auto'.
|
|
45
|
+
function _decorationMode(env) {
|
|
46
|
+
const v = String(env.SWEET_SEARCH_DECORATION || '').trim().toLowerCase();
|
|
47
|
+
if (v === 'always' || v === 'never') return v;
|
|
48
|
+
return 'auto';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Classify the runtime harness from environment variables.
|
|
53
|
+
* @param {Record<string,string|undefined>} env
|
|
54
|
+
* @returns {{ codex: boolean, claudeCode: boolean, otherAgent: boolean }}
|
|
55
|
+
*/
|
|
56
|
+
export function detectAgentEnv(env = process.env) {
|
|
57
|
+
const e = env || {};
|
|
58
|
+
const keys = Object.keys(e);
|
|
59
|
+
const codex = keys.some((k) => k.startsWith('CODEX_'));
|
|
60
|
+
const claudeCode = _truthy(e.CLAUDECODE) || _truthy(e.CLAUDE_CODE);
|
|
61
|
+
const otherAgent =
|
|
62
|
+
OTHER_AGENT_ENV_KEYS.some((k) => _truthy(e[k])) ||
|
|
63
|
+
keys.some((k) => k.startsWith('CURSOR_'));
|
|
64
|
+
return { codex, claudeCode, otherAgent };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Best-effort probe for a separately-writable controlling terminal.
|
|
69
|
+
*
|
|
70
|
+
* Opens /dev/tty for writing without emitting any bytes, then closes it.
|
|
71
|
+
* Success means a controlling terminal exists that the captured stdout pipe
|
|
72
|
+
* does not intercept (the Claude Code Bash case). Any error → unavailable.
|
|
73
|
+
* Never throws; never affects the caller's results.
|
|
74
|
+
*
|
|
75
|
+
* @returns {string|null} the tty path if usable, else null
|
|
76
|
+
*/
|
|
77
|
+
export function defaultProbeTty() {
|
|
78
|
+
try {
|
|
79
|
+
const fd = openSync('/dev/tty', 'w');
|
|
80
|
+
closeSync(fd);
|
|
81
|
+
return '/dev/tty';
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @typedef {Object} OutputPolicy
|
|
89
|
+
* @property {('human-terminal'|'claude-tty-sidechannel'|'captured-plain'|'machine-readable')} mode
|
|
90
|
+
* @property {'stdout'} resultStream where search results go (always stdout)
|
|
91
|
+
* @property {('stdout'|'tty'|null)} decorationStream where banner/animation go
|
|
92
|
+
* @property {boolean} colorEnabled
|
|
93
|
+
* @property {boolean} bannerEnabled
|
|
94
|
+
* @property {boolean} animationEnabled
|
|
95
|
+
* @property {boolean} machineReadable
|
|
96
|
+
* @property {string|null} ttyPath
|
|
97
|
+
* @property {string} reason short tag for tests/debugging
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Decide the output policy for a CLI invocation.
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} [opts]
|
|
104
|
+
* @param {boolean} [opts.json=false] caller requested JSON output
|
|
105
|
+
* @param {string|null} [opts.format=null] explicit format (e.g. 'plain')
|
|
106
|
+
* @param {boolean} [opts.noBanner=false] --no-banner flag
|
|
107
|
+
* @param {Record<string,string|undefined>} [opts.env=process.env]
|
|
108
|
+
* @param {boolean} [opts.isTTY] override; defaults to stream.isTTY
|
|
109
|
+
* @param {{isTTY?: boolean}} [opts.stream=process.stdout]
|
|
110
|
+
* @param {() => (string|null)} [opts.probeTty=defaultProbeTty] injectable for tests
|
|
111
|
+
* @returns {OutputPolicy}
|
|
112
|
+
*/
|
|
113
|
+
export function detectOutputPolicy(opts = {}) {
|
|
114
|
+
const env = opts.env || process.env;
|
|
115
|
+
const stream = opts.stream || process.stdout;
|
|
116
|
+
const isTTY = opts.isTTY != null ? !!opts.isTTY : !!(stream && stream.isTTY);
|
|
117
|
+
const json = !!opts.json;
|
|
118
|
+
const plain = opts.format === 'plain';
|
|
119
|
+
const noBanner = !!opts.noBanner;
|
|
120
|
+
const noColor = _hasNoColor(env);
|
|
121
|
+
const agent = detectAgentEnv(env);
|
|
122
|
+
const decorationMode = _decorationMode(env); // 'auto' | 'always' | 'never'
|
|
123
|
+
const probe = typeof opts.probeTty === 'function' ? opts.probeTty : defaultProbeTty;
|
|
124
|
+
|
|
125
|
+
/** @type {OutputPolicy} */
|
|
126
|
+
const policy = {
|
|
127
|
+
mode: 'captured-plain',
|
|
128
|
+
resultStream: 'stdout',
|
|
129
|
+
decorationStream: null,
|
|
130
|
+
colorEnabled: false,
|
|
131
|
+
bannerEnabled: false,
|
|
132
|
+
animationEnabled: false,
|
|
133
|
+
machineReadable: false,
|
|
134
|
+
ttyPath: null,
|
|
135
|
+
reason: 'captured-plain',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Tier 4: an explicit machine-readable request wins outright — even over
|
|
139
|
+
// SWEET_SEARCH_DECORATION=always.
|
|
140
|
+
if (json) {
|
|
141
|
+
policy.mode = 'machine-readable';
|
|
142
|
+
policy.machineReadable = true;
|
|
143
|
+
policy.reason = 'json';
|
|
144
|
+
return policy;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (decorationMode === 'never') {
|
|
148
|
+
// Explicit global opt-out.
|
|
149
|
+
policy.reason = 'decoration-never';
|
|
150
|
+
} else if (decorationMode === 'always') {
|
|
151
|
+
// Explicit global opt-in: decorate on stdout regardless of capture. The
|
|
152
|
+
// user has accepted any token cost. Still routes only to stdout, never
|
|
153
|
+
// stderr.
|
|
154
|
+
policy.mode = 'human-terminal';
|
|
155
|
+
policy.decorationStream = 'stdout';
|
|
156
|
+
policy.bannerEnabled = true;
|
|
157
|
+
policy.colorEnabled = true;
|
|
158
|
+
policy.animationEnabled = true;
|
|
159
|
+
policy.reason = 'decoration-always';
|
|
160
|
+
} else {
|
|
161
|
+
// Harness markers force suppression regardless of TTY state (defensive: a
|
|
162
|
+
// sandboxed codex shell may still report isTTY in some configs; PTY-based
|
|
163
|
+
// capture harnesses look like a TTY but capture the stream).
|
|
164
|
+
const suppress = agent.codex || agent.otherAgent;
|
|
165
|
+
|
|
166
|
+
if (isTTY && !suppress) {
|
|
167
|
+
// Tier 1 — real human terminal.
|
|
168
|
+
policy.mode = 'human-terminal';
|
|
169
|
+
policy.decorationStream = 'stdout';
|
|
170
|
+
policy.bannerEnabled = true;
|
|
171
|
+
policy.colorEnabled = true;
|
|
172
|
+
policy.animationEnabled = true;
|
|
173
|
+
policy.reason = 'human-terminal';
|
|
174
|
+
} else if (!isTTY && !suppress && agent.claudeCode) {
|
|
175
|
+
// Tier 2 — captured pipe, but Claude Code is positively detected, so the
|
|
176
|
+
// controlling terminal is reachable via /dev/tty (the pipe does not
|
|
177
|
+
// intercept it). Only Claude Code unlocks this side-channel; a merely
|
|
178
|
+
// writable /dev/tty with no agent marker is NOT trusted, because a
|
|
179
|
+
// PTY-capturing harness would route those bytes straight back into the
|
|
180
|
+
// captured stream.
|
|
181
|
+
const ttyPath = probe();
|
|
182
|
+
if (ttyPath) {
|
|
183
|
+
policy.mode = 'claude-tty-sidechannel';
|
|
184
|
+
policy.decorationStream = 'tty';
|
|
185
|
+
policy.ttyPath = ttyPath;
|
|
186
|
+
policy.bannerEnabled = true;
|
|
187
|
+
policy.colorEnabled = true;
|
|
188
|
+
policy.animationEnabled = true;
|
|
189
|
+
policy.reason = 'claude-code-tty';
|
|
190
|
+
} else {
|
|
191
|
+
policy.reason = 'claude-no-tty';
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
// Tier 3 — captured / harness-detected / no trusted side-channel.
|
|
195
|
+
policy.reason = agent.codex
|
|
196
|
+
? 'codex-detected'
|
|
197
|
+
: agent.otherAgent
|
|
198
|
+
? 'agent-detected'
|
|
199
|
+
: isTTY
|
|
200
|
+
? 'captured-plain'
|
|
201
|
+
: 'captured-no-claude';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Tier 4: explicit opt-outs always reduce decoration, never increase it.
|
|
206
|
+
if (noColor) policy.colorEnabled = false;
|
|
207
|
+
if (noBanner) policy.bannerEnabled = false;
|
|
208
|
+
if (plain) {
|
|
209
|
+
policy.bannerEnabled = false;
|
|
210
|
+
policy.colorEnabled = false;
|
|
211
|
+
policy.animationEnabled = false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return policy;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Build a line writer for the decoration channel chosen by the policy.
|
|
219
|
+
*
|
|
220
|
+
* - 'stdout' → writes through the provided stdout stream.
|
|
221
|
+
* - 'tty' → opens the tty for writing; on any error falls back to a no-op
|
|
222
|
+
* (decoration is best-effort and must never break results).
|
|
223
|
+
* - null → no-op writer.
|
|
224
|
+
*
|
|
225
|
+
* The caller is responsible for only writing when policy.bannerEnabled (or
|
|
226
|
+
* animationEnabled) is true. Always call close() when done.
|
|
227
|
+
*
|
|
228
|
+
* @param {OutputPolicy} policy
|
|
229
|
+
* @param {Object} [opts] injectable I/O for tests
|
|
230
|
+
* @returns {{ write: (line?: string) => void, close: () => void }}
|
|
231
|
+
*/
|
|
232
|
+
export function createDecorationWriter(policy, opts = {}) {
|
|
233
|
+
const target = policy ? policy.decorationStream : null;
|
|
234
|
+
|
|
235
|
+
if (target === 'stdout') {
|
|
236
|
+
const out = opts.stdout || process.stdout;
|
|
237
|
+
return {
|
|
238
|
+
write: (line = '') => out.write(`${line}\n`),
|
|
239
|
+
close: () => {},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (target === 'tty') {
|
|
244
|
+
const open = opts.openTty || (() => openSync(policy.ttyPath || '/dev/tty', 'w'));
|
|
245
|
+
const _writeSync = opts.writeSync || writeSync;
|
|
246
|
+
const _closeSync = opts.closeSync || closeSync;
|
|
247
|
+
let fd = null;
|
|
248
|
+
try {
|
|
249
|
+
fd = open();
|
|
250
|
+
} catch {
|
|
251
|
+
fd = null;
|
|
252
|
+
}
|
|
253
|
+
if (fd == null) {
|
|
254
|
+
return { write: () => {}, close: () => {} };
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
write: (line = '') => {
|
|
258
|
+
try {
|
|
259
|
+
_writeSync(fd, `${line}\n`);
|
|
260
|
+
} catch {
|
|
261
|
+
/* decoration is best-effort; ignore write failures */
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
close: () => {
|
|
265
|
+
try {
|
|
266
|
+
_closeSync(fd);
|
|
267
|
+
} catch {
|
|
268
|
+
/* ignore */
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return { write: () => {}, close: () => {} };
|
|
275
|
+
}
|