codehere 1.0.1 → 1.0.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 +35 -193
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ship-readiness scan — orchestrates the installed OSS scanners (gitleaks + semgrep) and adds
|
|
3
|
-
* codehere's value: PRIORITIZE by CONSEQUENCE and surface the ONE blocker that gets you hacked,
|
|
4
|
-
* in plain language for a non-expert.
|
|
5
|
-
*
|
|
6
|
-
* Deterministic, ZERO model tokens — the detection is OSS (battle-tested, better than hand-rolled);
|
|
7
|
-
* codehere is the non-expert layer on top. See docs/PRODUCT_10X.md (R1 detect via OSS, R2 prioritize).
|
|
8
|
-
*/
|
|
9
|
-
import { spawnSync, execFileSync } from 'node:child_process';
|
|
10
|
-
import { mkdtempSync, readFileSync, existsSync } from 'node:fs';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { join, relative, isAbsolute } from 'node:path';
|
|
13
|
-
import { vibeChecks } from './vibe-checks.js';
|
|
14
|
-
const TIER_RANK = { critical: 0, high: 1, medium: 2 };
|
|
15
|
-
function installed(tool) {
|
|
16
|
-
try {
|
|
17
|
-
execFileSync('which', [tool], { stdio: 'ignore' });
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function rel(dir, file) {
|
|
25
|
-
if (!file)
|
|
26
|
-
return '';
|
|
27
|
-
return isAbsolute(file) ? relative(dir, file) : file;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* A secret parked in a gitignored file (the .env our own fix moves it to) is AT REST — it won't be
|
|
31
|
-
* committed or shipped, so it's not a ship-blocker. Filter those out, otherwise the scan re-flags the
|
|
32
|
-
* very file the fix created and "prove it's closed" contradicts itself. Honors full .gitignore
|
|
33
|
-
* semantics via git; falls back to a literal .gitignore read for the common .env case in a non-git dir.
|
|
34
|
-
*/
|
|
35
|
-
function filterAtRest(dir, findings) {
|
|
36
|
-
if (findings.length === 0)
|
|
37
|
-
return findings;
|
|
38
|
-
const files = [...new Set(findings.map((f) => f.file))];
|
|
39
|
-
const ignored = new Set();
|
|
40
|
-
const res = spawnSync('git', ['-C', dir, 'check-ignore', '--stdin'], { input: files.join('\n'), encoding: 'utf8' });
|
|
41
|
-
if (res.status === 0 && res.stdout) {
|
|
42
|
-
for (const p of res.stdout
|
|
43
|
-
.split('\n')
|
|
44
|
-
.map((s) => s.trim())
|
|
45
|
-
.filter(Boolean))
|
|
46
|
-
ignored.add(p);
|
|
47
|
-
}
|
|
48
|
-
else if (res.status === 128) {
|
|
49
|
-
// Not a git repo yet — minimal fallback so the fix→prove loop is honest even pre-`git init`.
|
|
50
|
-
const giPath = join(dir, '.gitignore');
|
|
51
|
-
const gi = existsSync(giPath) ? readFileSync(giPath, 'utf8') : '';
|
|
52
|
-
const patterns = gi
|
|
53
|
-
.split('\n')
|
|
54
|
-
.map((l) => l.trim())
|
|
55
|
-
.filter((l) => l && !l.startsWith('#'));
|
|
56
|
-
for (const f of files) {
|
|
57
|
-
const base = f.split('/').pop() || f;
|
|
58
|
-
if (patterns.some((p) => p === f || p === base || (p.startsWith('*') && base.endsWith(p.slice(1)))))
|
|
59
|
-
ignored.add(f);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return findings.filter((f) => !ignored.has(f.file));
|
|
63
|
-
}
|
|
64
|
-
/** gitleaks = exposed secrets/keys — the #1 way vibe-coded apps get owned. Always critical. */
|
|
65
|
-
function runGitleaks(dir) {
|
|
66
|
-
const tmp = join(mkdtempSync(join(tmpdir(), 'sscan-')), 'gl.json');
|
|
67
|
-
// gitleaks exits non-zero when leaks are found — spawnSync (no throw), read the report regardless.
|
|
68
|
-
spawnSync('gitleaks', ['detect', '--no-git', '--source', dir, '-f', 'json', '-r', tmp], { stdio: 'ignore' });
|
|
69
|
-
// No report file = the run died before writing one. Zero findings here means "we don't know",
|
|
70
|
-
// not "nothing found", so ok:false — the surface must not spend it as coverage.
|
|
71
|
-
if (!existsSync(tmp))
|
|
72
|
-
return { ok: false, findings: [] };
|
|
73
|
-
let arr;
|
|
74
|
-
try {
|
|
75
|
-
arr = JSON.parse(readFileSync(tmp, 'utf8'));
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
return { ok: false, findings: [] };
|
|
79
|
-
}
|
|
80
|
-
const findings = (arr || []).map((f) => {
|
|
81
|
-
const ruleId = String(f.RuleID || 'secret');
|
|
82
|
-
const file = rel(dir, String(f.File || ''));
|
|
83
|
-
return {
|
|
84
|
-
tool: 'gitleaks',
|
|
85
|
-
ruleId,
|
|
86
|
-
file,
|
|
87
|
-
line: Number(f.StartLine || 0),
|
|
88
|
-
tier: 'critical',
|
|
89
|
-
what: `A live secret (${ruleId}) is hardcoded in ${file}. Anyone who can read your code — in your repo, or shipped to your users — has it, and can use it to reach whatever it unlocks: your database, your users' data, your paid APIs.`,
|
|
90
|
-
};
|
|
91
|
-
});
|
|
92
|
-
return { ok: true, findings };
|
|
93
|
-
}
|
|
94
|
-
/** Map a semgrep check to a consequence tier + plain-language "what happens". */
|
|
95
|
-
function classifySemgrep(checkId, severity) {
|
|
96
|
-
const id = checkId.toLowerCase();
|
|
97
|
-
const danger = (kind) => (file) => `Untrusted input reaches ${kind} in ${file}. An attacker can ${{
|
|
98
|
-
command: 'run any command on your server',
|
|
99
|
-
sql: 'read or wipe your entire database',
|
|
100
|
-
code: 'run their own code in your app',
|
|
101
|
-
path: "read files outside your app (like other users' data or secrets)",
|
|
102
|
-
ssrf: 'make your server hit internal/private systems',
|
|
103
|
-
xss: "run scripts in your users' browsers (steal sessions, deface the page)",
|
|
104
|
-
}[kind] || 'do something you did not intend'}.`;
|
|
105
|
-
if (/(command|child.?process|os.?command|exec)/.test(id))
|
|
106
|
-
return { tier: 'critical', what: danger('command') };
|
|
107
|
-
if (/(sql|nosql|query.?injection)/.test(id))
|
|
108
|
-
return { tier: 'critical', what: danger('sql') };
|
|
109
|
-
if (/(eval|code.?injection|deserial)/.test(id))
|
|
110
|
-
return { tier: 'critical', what: danger('code') };
|
|
111
|
-
if (/(path.?traversal|directory.?traversal|zip.?slip)/.test(id))
|
|
112
|
-
return { tier: 'critical', what: danger('path') };
|
|
113
|
-
if (/ssrf/.test(id))
|
|
114
|
-
return { tier: 'critical', what: danger('ssrf') };
|
|
115
|
-
if (/(jwt|secret|hardcoded|api.?key|password)/.test(id))
|
|
116
|
-
return {
|
|
117
|
-
tier: 'critical',
|
|
118
|
-
what: (file) => `A secret or weak credential handling was found in ${file} — it can let an attacker into your data.`,
|
|
119
|
-
};
|
|
120
|
-
if (/xss/.test(id))
|
|
121
|
-
return { tier: 'high', what: danger('xss') };
|
|
122
|
-
const sev = String(severity || '').toUpperCase();
|
|
123
|
-
const tier = sev === 'ERROR' ? 'high' : 'medium';
|
|
124
|
-
return {
|
|
125
|
-
tier,
|
|
126
|
-
what: (file) => `A security issue (${checkId.split('.').pop()}) was found in ${file} that could be exploited before you ship.`,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function runSemgrep(dir) {
|
|
130
|
-
const tmp = join(mkdtempSync(join(tmpdir(), 'sscan-')), 'sg.json');
|
|
131
|
-
// p/default is the curated default registry ruleset; --quiet keeps output clean. It is NOT
|
|
132
|
-
// offline and NOT fast — measured 2026-08-08, `semgrep scan --config p/default` on a cold
|
|
133
|
-
// registry cache blew past 200s on a developer laptop and hit the timeout below. The earlier
|
|
134
|
-
// comment here claimed "offline-capable, fast", which is why the zero findings it returns on
|
|
135
|
-
// timeout were being read as a clean scan.
|
|
136
|
-
spawnSync('semgrep', ['scan', '--config', 'p/default', '--json', '--quiet', '--no-git-ignore', dir, '-o', tmp], {
|
|
137
|
-
stdio: 'ignore',
|
|
138
|
-
timeout: 180_000,
|
|
139
|
-
});
|
|
140
|
-
// Timeout, network failure, or crash: no report file. Zero findings is absence of evidence.
|
|
141
|
-
if (!existsSync(tmp))
|
|
142
|
-
return { ok: false, findings: [] };
|
|
143
|
-
let json;
|
|
144
|
-
try {
|
|
145
|
-
json = JSON.parse(readFileSync(tmp, 'utf8'));
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
return { ok: false, findings: [] };
|
|
149
|
-
}
|
|
150
|
-
const findings = (json.results || []).map((r) => {
|
|
151
|
-
const checkId = String(r.check_id || 'rule');
|
|
152
|
-
const file = rel(dir, String(r.path || ''));
|
|
153
|
-
const severity = String(r.extra?.severity || '');
|
|
154
|
-
const c = classifySemgrep(checkId, severity);
|
|
155
|
-
return {
|
|
156
|
-
tool: 'semgrep',
|
|
157
|
-
ruleId: checkId.split('.').pop() || checkId,
|
|
158
|
-
file,
|
|
159
|
-
line: Number(r.start?.line || 0),
|
|
160
|
-
tier: c.tier,
|
|
161
|
-
what: c.what(file),
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
return { ok: true, findings };
|
|
165
|
-
}
|
|
166
|
-
/** Run the OSS scanners on `dir`, normalize + prioritize by consequence, surface the ONE top blocker. */
|
|
167
|
-
export function shipScan(dir) {
|
|
168
|
-
const hasGl = installed('gitleaks');
|
|
169
|
-
const hasSg = installed('semgrep');
|
|
170
|
-
const missing = [];
|
|
171
|
-
if (!hasGl)
|
|
172
|
-
missing.push('gitleaks');
|
|
173
|
-
if (!hasSg)
|
|
174
|
-
missing.push('semgrep');
|
|
175
|
-
const all = [];
|
|
176
|
-
const gl = hasGl ? runGitleaks(dir) : { ok: false, findings: [] };
|
|
177
|
-
const sg = hasSg ? runSemgrep(dir) : { ok: false, findings: [] };
|
|
178
|
-
all.push(...gl.findings, ...sg.findings);
|
|
179
|
-
// Installed but produced nothing readable — a coverage hole the surface has to name out loud.
|
|
180
|
-
const failed = [];
|
|
181
|
-
if (hasGl && !gl.ok)
|
|
182
|
-
failed.push('gitleaks');
|
|
183
|
-
if (hasSg && !sg.ok)
|
|
184
|
-
failed.push('semgrep');
|
|
185
|
-
// codehere's own vibe-coder-specific checks — the dangers gitleaks/semgrep miss (always runs, free).
|
|
186
|
-
all.push(...vibeChecks(dir));
|
|
187
|
-
// Drop anything that lives in a gitignored file — it's at rest, won't ship or get committed, so it's
|
|
188
|
-
// not a ship-blocker. This is what makes the fix→prove loop honest: the fix parks a secret in a
|
|
189
|
-
// gitignored .env, and the re-scan must come back clean instead of re-flagging the file it just created.
|
|
190
|
-
const findings = filterAtRest(dir, all);
|
|
191
|
-
// Prioritize: consequence tier first, then secrets (gitleaks) ahead of code patterns at the same tier.
|
|
192
|
-
findings.sort((a, b) => TIER_RANK[a.tier] - TIER_RANK[b.tier] || (a.tool === 'gitleaks' ? -1 : 1));
|
|
193
|
-
return { ran: { gitleaks: gl.ok, semgrep: sg.ok }, missing, failed, findings, top: findings[0] || null };
|
|
194
|
-
}
|
|
195
|
-
//# sourceMappingURL=ship-scan.js.map
|