@reneturcios/tracewatch 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +138 -2
- package/dist/adapters.d.ts +31 -0
- package/dist/adapters.js +128 -0
- package/dist/adapters.js.map +1 -0
- package/dist/blame.d.ts +15 -0
- package/dist/blame.js +165 -0
- package/dist/blame.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +236 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +18 -0
- package/dist/core.js +19 -0
- package/dist/core.js.map +1 -0
- package/dist/diff.d.ts +21 -0
- package/dist/diff.js +131 -0
- package/dist/diff.js.map +1 -0
- package/dist/git.d.ts +16 -0
- package/dist/git.js +80 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +54 -0
- package/dist/ingest.js +182 -0
- package/dist/ingest.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +80 -0
- package/dist/mcp.js.map +1 -0
- package/dist/render.d.ts +13 -0
- package/dist/render.js +151 -0
- package/dist/render.js.map +1 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.js +61 -0
- package/dist/report.js.map +1 -0
- package/dist/store.d.ts +31 -0
- package/dist/store.js +163 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +16 -0
- package/dist/util.js +65 -0
- package/dist/util.js.map +1 -0
- package/dist/watch.d.ts +62 -0
- package/dist/watch.js +401 -0
- package/dist/watch.js.map +1 -0
- package/package.json +1 -6
- package/server.json +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rene Turcios
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,139 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tracewatch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Observability, tracking, and root-cause for the Security Wave.
|
|
4
|
+
|
|
5
|
+
The tranche 1 tools (hookcheck, actionpin, injectcheck) answer "what is wrong right now, in this one scan." tracewatch is the wave's memory and stopwatch. It ingests their JSON reports over time and answers the questions an incident actually turns on:
|
|
6
|
+
|
|
7
|
+
- What changed since the last scan? Did a pin silently regress to a mutable tag? Did the ref under an already-pinned action move? That move is the tj-actions shape from March 2025: a tag retargeted under every repo that trusted it, invisible to anyone who only ever ran one scan.
|
|
8
|
+
- Where did this finding come from? tracewatch blames a finding to the commit that introduced it (git pickaxe on the finding's own content) and to the pull request that carried it, and labels every statement confirmed, inferred, or not established.
|
|
9
|
+
- How fast do we notice? Watch mode scans new issues, PRs, files, and tree changes on arrival and records time-to-detect for each one, so "early detection" is a measured number. The comparison point matters: hosted AI firewalls detect at prompt time, after the agent has already read the content. tracewatch detects at rest and on arrival, before any agent session exists.
|
|
10
|
+
- Is the estate getting better? Verdict trends, open findings by tool and severity, mean time open for resolved findings, and detection latency stats, per repo, over the whole scan history.
|
|
11
|
+
|
|
12
|
+
Deterministic, offline-first, local store you can grep. No hosted backend, no account, no telemetry.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npx -y @reneturcios/tracewatch --version
|
|
18
|
+
npm install -g @reneturcios/tracewatch
|
|
19
|
+
docker run -i --rm ghcr.io/cloudcorprecords/tracewatch mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
# scan a repo with all three tools and start tracking it
|
|
26
|
+
tracewatch ingest ~/src/myrepo
|
|
27
|
+
|
|
28
|
+
# what changed since the previous scan?
|
|
29
|
+
tracewatch diff myrepo
|
|
30
|
+
|
|
31
|
+
# where did this finding come from?
|
|
32
|
+
tracewatch blame myrepo --finding tw_0123456789abcdef
|
|
33
|
+
|
|
34
|
+
# health over time
|
|
35
|
+
tracewatch report myrepo
|
|
36
|
+
|
|
37
|
+
# watch a directory and scan new content on arrival
|
|
38
|
+
tracewatch watch ~/src/myrepo --interval 30
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What it tracks
|
|
42
|
+
|
|
43
|
+
Every ingest stores one scan record (timestamp, tool versions, verdicts, git HEAD) plus one observation per finding. Findings carry stable IDs: a hash of tool, rule, normalized location, and a content signature. The same finding keeps its identity across scans and tool versions; a content edit keeps the identity and changes the ID version, which is what diff reports as CHANGED.
|
|
44
|
+
|
|
45
|
+
Diff groups: NEW, RESOLVED, CHANGED (with field-level old to new), verdict moves, and REF EVENTS. A ref event fires whenever an action's ref or classification moves, with both values spelled out:
|
|
46
|
+
|
|
47
|
+
- pin-regression: PINNED to MUTABLE or BRANCH
|
|
48
|
+
- pin-improvement: MUTABLE or BRANCH to PINNED
|
|
49
|
+
- ref-move: the ref changed inside one classification (for a pinned action, the SHA itself moved)
|
|
50
|
+
|
|
51
|
+
## Blame (root cause)
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
tracewatch blame myrepo --rule CURL-PIPE-SH --file package.json
|
|
55
|
+
tracewatch blame myrepo --finding tw_0123456789abcdef --json
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Evidence is labeled, never blended:
|
|
59
|
+
|
|
60
|
+
- CONFIRMED: the introducing commit from `git log -S` on the finding's needle in your local clone; the current-line commit from `git blame`; the PR association from the GitHub API (`commits/{sha}/pulls`) when the clone has a GitHub remote; ref history and first/last seen from the store itself.
|
|
61
|
+
- INFERRED: circumstantial statements, written as inferences (for example, when the introducing commit and the current-line commit differ, the line was rewritten later without introducing the content).
|
|
62
|
+
- NOT ESTABLISHED: whatever could not be proven, with the reason (no clone recorded, no GitHub remote, API unreachable). Provenance is never guessed to fill the page.
|
|
63
|
+
|
|
64
|
+
## Watch and time-to-detect
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
tracewatch watch ~/src/myrepo --interval 60 --alerts alerts.jsonl
|
|
68
|
+
tracewatch watch owner/repo --path ~/src/myrepo-clone --interval 60
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Local mode polls a directory: new or changed text files are scanned with injectcheck the poll they appear in; a git HEAD move triggers a full ingest plus diff. GitHub mode polls an owner/repo slug: new issues and PRs are scanned on arrival, HEAD moves are recorded (and scanned when you pass a local clone with --path). Every detection writes a record with time-to-detect = detection time minus the content's own creation time (issue created_at, commit time, file birth time). State checkpoints after every cycle, so a restarted watch resumes without re-alerting history. Alerts stream as JSONL to stdout and to the --alerts file.
|
|
72
|
+
|
|
73
|
+
Polling is the portable fallback. The fastest path is event-driven, in CI:
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
name: tracewatch-issues
|
|
77
|
+
on:
|
|
78
|
+
issues:
|
|
79
|
+
types: [opened, edited]
|
|
80
|
+
pull_request:
|
|
81
|
+
types: [opened, edited, synchronize]
|
|
82
|
+
permissions:
|
|
83
|
+
contents: read
|
|
84
|
+
jobs:
|
|
85
|
+
scan-new-content:
|
|
86
|
+
runs-on: ubuntu-latest
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
|
89
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
90
|
+
with:
|
|
91
|
+
node-version: 24
|
|
92
|
+
- name: Scan the new issue or PR body on arrival
|
|
93
|
+
env:
|
|
94
|
+
BODY: ${{ github.event.issue.body || github.event.pull_request.body }}
|
|
95
|
+
run: |
|
|
96
|
+
printf '%s' "$BODY" > /tmp/body.md
|
|
97
|
+
npx -y @reneturcios/injectcheck file /tmp/body.md --json > /tmp/report.json
|
|
98
|
+
npx -y @reneturcios/tracewatch import ${{ github.repository }} --report injectcheck=/tmp/report.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Known limit, stated plainly: GitHub watch mode reads through the local ghapi helper, whose output truncates long responses; an issue poll that cannot be parsed in full is logged as a watch note and retried next cycle rather than partially trusted. The CI pattern above has no such limit and is the recommended production path for issue scanning.
|
|
102
|
+
|
|
103
|
+
## Reports
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
tracewatch report myrepo # human
|
|
107
|
+
tracewatch report myrepo --json # machine
|
|
108
|
+
tracewatch findings myrepo --tool hookcheck --status open
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The health report carries the verdict trend across every scan, open findings by tool and severity, resolved count, mean time open for resolved findings (first-seen scan to the scan where the finding disappeared), all ref events, and detection latency stats (count, median, max, by kind).
|
|
112
|
+
|
|
113
|
+
## MCP
|
|
114
|
+
|
|
115
|
+
`tracewatch mcp` serves a stdio MCP server with seven tools:
|
|
116
|
+
|
|
117
|
+
| Tool | What it does |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| ingest_scan | Run the three tools on a local path and store the scan |
|
|
120
|
+
| diff_scans | NEW / RESOLVED / CHANGED, verdict moves, ref events between two scans |
|
|
121
|
+
| finding_provenance | Introducing commit, current-line commit, PR, ref history for one finding |
|
|
122
|
+
| repo_health | Verdict trend, open findings, mean time open, detection latency |
|
|
123
|
+
| watch_status | Every tracked repo: scans, last scan, open findings, median TTD |
|
|
124
|
+
| list_findings | Tracked findings filtered by tool, severity, status |
|
|
125
|
+
| explain_finding | The full evidence bundle for one finding, written to act on |
|
|
126
|
+
|
|
127
|
+
## Store
|
|
128
|
+
|
|
129
|
+
Append-only JSONL under ~/.tracewatch (override with --store <dir> or TRACEWATCH_STORE): scans.jsonl, findings.jsonl, detections.jsonl, alerts.jsonl, plus watch-state.json checkpoints. Plain text on purpose. Finding status is derived from the observation series on every read, never stored as mutable state, so the store cannot disagree with itself. Identical store state always renders identical diff and report output.
|
|
130
|
+
|
|
131
|
+
Exit codes: 0 success or no changes, 1 a diff found changes, 2 usage error or total failure, 141 the reader closed the output pipe before the report was delivered.
|
|
132
|
+
|
|
133
|
+
## Honesty rules
|
|
134
|
+
|
|
135
|
+
- A verdict is a scan result, never a safety claim. tracewatch reports what the three tools found and when; it does not certify a repo clean.
|
|
136
|
+
- Time-to-detect is measured against the content's own timestamps and recorded per detection; the report shows the distribution, not a best case.
|
|
137
|
+
- Blame separates confirmed evidence from inference and says "not established" out loud.
|
|
138
|
+
|
|
139
|
+
MIT. Part of the Security Wave: hookcheck (persistence hooks), actionpin (Actions pinning), injectcheck (prompt injection in agent-read content), tracewatch (observability and root cause).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapters: translate each tool's published JSON report shape into
|
|
3
|
+
* tracewatch's normalized finding observations.
|
|
4
|
+
*
|
|
5
|
+
* The shapes consumed are the tools' public contracts, verified against
|
|
6
|
+
* hookcheck 0.1.14, actionpin 0.1.6, injectcheck 0.1.13:
|
|
7
|
+
*
|
|
8
|
+
* - hookcheck: {verdict, findings: [{id, file, source, hook, command,
|
|
9
|
+
* line?, classification, rules, evidence}], counts, warnings}
|
|
10
|
+
* - actionpin: {verdict, actions: [{workflow, line, uses, action, ref,
|
|
11
|
+
* classification, ownerType, rawLine}], counts}
|
|
12
|
+
* - injectcheck: {verdict, findings: [{file, line, rule, severity,
|
|
13
|
+
* excerpt, detail}], counts, warnings}
|
|
14
|
+
*
|
|
15
|
+
* Adapters are pure and total: a malformed entry is skipped and counted
|
|
16
|
+
* in the returned warnings, never thrown through, so one bad record can
|
|
17
|
+
* not take down an ingest of the other tools.
|
|
18
|
+
*/
|
|
19
|
+
import type { FindingObs, ToolName } from "./types.js";
|
|
20
|
+
export type NormalizedFinding = Omit<FindingObs, "scanId" | "repo">;
|
|
21
|
+
export interface AdaptedReport {
|
|
22
|
+
tool: ToolName;
|
|
23
|
+
verdict: string;
|
|
24
|
+
counts: Record<string, number>;
|
|
25
|
+
findings: NormalizedFinding[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function adaptHookcheck(report: unknown, root: string): AdaptedReport;
|
|
29
|
+
export declare function adaptActionpin(report: unknown, root: string): AdaptedReport;
|
|
30
|
+
export declare function adaptInjectcheck(report: unknown, root: string): AdaptedReport;
|
|
31
|
+
export declare function adaptReport(tool: ToolName, report: unknown, root: string): AdaptedReport;
|
package/dist/adapters.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { collapseWs, makeFindingId, makeIdentity, normalizePath, signatureOf } from "./util.js";
|
|
2
|
+
function asObj(v) {
|
|
3
|
+
return typeof v === "object" && v !== null ? v : null;
|
|
4
|
+
}
|
|
5
|
+
function str(v, fallback = "") {
|
|
6
|
+
return typeof v === "string" ? v : fallback;
|
|
7
|
+
}
|
|
8
|
+
function num(v) {
|
|
9
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
10
|
+
}
|
|
11
|
+
function countsOf(raw) {
|
|
12
|
+
const c = asObj(raw.counts);
|
|
13
|
+
const out = {};
|
|
14
|
+
if (c)
|
|
15
|
+
for (const [k, v] of Object.entries(c))
|
|
16
|
+
if (typeof v === "number")
|
|
17
|
+
out[k] = v;
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
const HOOK_SEVERITY = { RISKY: "high", UNKNOWN: "medium", SAFE: "low" };
|
|
21
|
+
const ACTION_SEVERITY = { BRANCH: "high", MUTABLE: "medium", PINNED: "info", LOCAL: "low" };
|
|
22
|
+
const INJECT_SEVERITY = { high: "high", medium: "medium", low: "low" };
|
|
23
|
+
export function adaptHookcheck(report, root) {
|
|
24
|
+
const raw = asObj(report) ?? {};
|
|
25
|
+
const findings = [];
|
|
26
|
+
const warnings = (Array.isArray(raw.warnings) ? raw.warnings : []).map((w) => `hookcheck: ${String(w)}`);
|
|
27
|
+
const list = Array.isArray(raw.findings) ? raw.findings : [];
|
|
28
|
+
for (const entry of list) {
|
|
29
|
+
const f = asObj(entry);
|
|
30
|
+
if (!f) {
|
|
31
|
+
warnings.push("hookcheck: skipped a malformed finding entry");
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const location = str(f.id, str(f.file, "unknown"));
|
|
35
|
+
const rules = Array.isArray(f.rules) ? f.rules.map(String) : [];
|
|
36
|
+
const rule = rules.length > 0 ? rules.join("+") : "inventory";
|
|
37
|
+
const command = str(f.command);
|
|
38
|
+
const signature = signatureOf([command]);
|
|
39
|
+
findings.push({
|
|
40
|
+
findingId: makeFindingId("hookcheck", rule, location, signature),
|
|
41
|
+
identity: makeIdentity("hookcheck", location, rule),
|
|
42
|
+
tool: "hookcheck",
|
|
43
|
+
rule,
|
|
44
|
+
severity: HOOK_SEVERITY[str(f.classification)] ?? "medium",
|
|
45
|
+
file: normalizePath(str(f.file, location), root),
|
|
46
|
+
line: num(f.line),
|
|
47
|
+
detail: str(f.evidence),
|
|
48
|
+
excerpt: command ? command.slice(0, 200) : undefined,
|
|
49
|
+
signature,
|
|
50
|
+
data: { source: str(f.source), hook: str(f.hook), command, classification: str(f.classification) },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return { tool: "hookcheck", verdict: str(raw.verdict, "UNKNOWN"), counts: countsOf(raw), findings, warnings };
|
|
54
|
+
}
|
|
55
|
+
export function adaptActionpin(report, root) {
|
|
56
|
+
const raw = asObj(report) ?? {};
|
|
57
|
+
const findings = [];
|
|
58
|
+
const warnings = [];
|
|
59
|
+
const list = Array.isArray(raw.actions) ? raw.actions : [];
|
|
60
|
+
for (const entry of list) {
|
|
61
|
+
const a = asObj(entry);
|
|
62
|
+
if (!a) {
|
|
63
|
+
warnings.push("actionpin: skipped a malformed action entry");
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const file = normalizePath(str(a.workflow), root);
|
|
67
|
+
const action = str(a.action, str(a.uses));
|
|
68
|
+
const uses = str(a.uses);
|
|
69
|
+
const ref = str(a.ref);
|
|
70
|
+
const classification = str(a.classification, "MUTABLE");
|
|
71
|
+
const rule = "action-ref";
|
|
72
|
+
const location = `${file}|${action}`;
|
|
73
|
+
const signature = signatureOf([uses, ref, classification]);
|
|
74
|
+
findings.push({
|
|
75
|
+
findingId: makeFindingId("actionpin", rule, location, signature),
|
|
76
|
+
identity: makeIdentity("actionpin", location, rule),
|
|
77
|
+
tool: "actionpin",
|
|
78
|
+
rule,
|
|
79
|
+
severity: ACTION_SEVERITY[classification] ?? "medium",
|
|
80
|
+
file,
|
|
81
|
+
line: num(a.line),
|
|
82
|
+
detail: `${uses} is ${classification.toLowerCase()} (${str(a.ownerType, "unknown")} action)`,
|
|
83
|
+
excerpt: uses,
|
|
84
|
+
signature,
|
|
85
|
+
data: { action, uses, ref, classification, ownerType: str(a.ownerType) },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return { tool: "actionpin", verdict: str(raw.verdict, "UNKNOWN"), counts: countsOf(raw), findings, warnings };
|
|
89
|
+
}
|
|
90
|
+
export function adaptInjectcheck(report, root) {
|
|
91
|
+
const raw = asObj(report) ?? {};
|
|
92
|
+
const findings = [];
|
|
93
|
+
const warnings = (Array.isArray(raw.warnings) ? raw.warnings : []).map((w) => `injectcheck: ${String(w)}`);
|
|
94
|
+
const list = Array.isArray(raw.findings) ? raw.findings : [];
|
|
95
|
+
for (const entry of list) {
|
|
96
|
+
const f = asObj(entry);
|
|
97
|
+
if (!f) {
|
|
98
|
+
warnings.push("injectcheck: skipped a malformed finding entry");
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const file = normalizePath(str(f.file, str(raw.target, "text")), root);
|
|
102
|
+
const rule = str(f.rule, "unknown-rule");
|
|
103
|
+
const excerpt = str(f.excerpt);
|
|
104
|
+
const signature = signatureOf([collapseWs(excerpt)]);
|
|
105
|
+
findings.push({
|
|
106
|
+
findingId: makeFindingId("injectcheck", rule, file, signature),
|
|
107
|
+
identity: makeIdentity("injectcheck", file, rule),
|
|
108
|
+
tool: "injectcheck",
|
|
109
|
+
rule,
|
|
110
|
+
severity: INJECT_SEVERITY[str(f.severity)] ?? "low",
|
|
111
|
+
file,
|
|
112
|
+
line: num(f.line),
|
|
113
|
+
detail: str(f.detail),
|
|
114
|
+
excerpt: excerpt ? excerpt.slice(0, 200) : undefined,
|
|
115
|
+
signature,
|
|
116
|
+
data: { excerpt },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return { tool: "injectcheck", verdict: str(raw.verdict, "UNKNOWN"), counts: countsOf(raw), findings, warnings };
|
|
120
|
+
}
|
|
121
|
+
export function adaptReport(tool, report, root) {
|
|
122
|
+
if (tool === "hookcheck")
|
|
123
|
+
return adaptHookcheck(report, root);
|
|
124
|
+
if (tool === "actionpin")
|
|
125
|
+
return adaptActionpin(report, root);
|
|
126
|
+
return adaptInjectcheck(report, root);
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/blame.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
import type { Provenance, TrackedFinding } from "./types.js";
|
|
3
|
+
export declare const GHAPI: string;
|
|
4
|
+
export declare function blameFinding(store: Store, repo: string, tracked: TrackedFinding, opts?: {
|
|
5
|
+
path?: string;
|
|
6
|
+
offline?: boolean;
|
|
7
|
+
}): Promise<Provenance>;
|
|
8
|
+
/** explain_finding bundle: the tracked finding, its history, and its provenance. */
|
|
9
|
+
export declare function explainFinding(store: Store, repo: string, idOrPrefix: string, opts?: {
|
|
10
|
+
path?: string;
|
|
11
|
+
offline?: boolean;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
tracked: TrackedFinding;
|
|
14
|
+
provenance: Provenance;
|
|
15
|
+
} | null>;
|
package/dist/blame.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blame: root-cause provenance for one tracked finding.
|
|
3
|
+
*
|
|
4
|
+
* Evidence is labeled, never blended:
|
|
5
|
+
*
|
|
6
|
+
* - CONFIRMED: the introducing commit from `git log -S` (pickaxe) on the
|
|
7
|
+
* finding's own needle in the local clone; the current-line commit
|
|
8
|
+
* from `git blame`; PR association from the GitHub API (commits/{sha}/
|
|
9
|
+
* pulls) through the ghapi helper; ref history and first/last seen
|
|
10
|
+
* from the tracewatch store itself.
|
|
11
|
+
* - INFERRED: circumstantial statements, each written as one.
|
|
12
|
+
* - NOT ESTABLISHED: whatever could not be proven, with the reason
|
|
13
|
+
* (no clone, no remote, API unreachable). Provenance is never guessed.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { resolve } from "node:path";
|
|
17
|
+
import { runCapture } from "./ingest.js";
|
|
18
|
+
import { blameLine, logPickaxe, parseGithubSlug, remoteSlug } from "./git.js";
|
|
19
|
+
import { collapseWs } from "./util.js";
|
|
20
|
+
export const GHAPI = process.env.TRACEWATCH_GHAPI ?? `${process.env.HOME ?? ""}/workspace/custom-connectors/custom.github-pat/bin/ghapi.py`;
|
|
21
|
+
function needleFor(obs) {
|
|
22
|
+
if (obs.tool === "hookcheck")
|
|
23
|
+
return String(obs.data.command ?? obs.excerpt ?? "");
|
|
24
|
+
if (obs.tool === "actionpin")
|
|
25
|
+
return String(obs.data.uses ?? obs.excerpt ?? "");
|
|
26
|
+
return collapseWs(String(obs.data.excerpt ?? obs.excerpt ?? "")).slice(0, 80);
|
|
27
|
+
}
|
|
28
|
+
async function pullsForCommit(slug, sha) {
|
|
29
|
+
if (!existsSync(GHAPI))
|
|
30
|
+
return null;
|
|
31
|
+
const res = await runCapture("python3", [GHAPI, "GET", `/repos/${slug}/commits/${sha}/pulls`], 30000);
|
|
32
|
+
if (res.code !== 0 || !res.stdout.includes("HTTP 200"))
|
|
33
|
+
return null;
|
|
34
|
+
const body = res.stdout.slice(res.stdout.indexOf("\n") + 1);
|
|
35
|
+
try {
|
|
36
|
+
const arr = JSON.parse(body);
|
|
37
|
+
if (Array.isArray(arr)) {
|
|
38
|
+
return arr.map((p) => ({
|
|
39
|
+
number: Number(p.number),
|
|
40
|
+
title: String(p.title ?? ""),
|
|
41
|
+
url: String(p.html_url ?? ""),
|
|
42
|
+
state: String(p.state ?? ""),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Helper output is truncated at 4000 chars; fall back to field extraction.
|
|
48
|
+
}
|
|
49
|
+
const out = [];
|
|
50
|
+
const re = /"number":\s*(\d+)[\s\S]{0,800}?"html_url":\s*"([^"]+)"[\s\S]{0,400}?"title":\s*"([^"]*)"/g;
|
|
51
|
+
let m;
|
|
52
|
+
while ((m = re.exec(body)) !== null) {
|
|
53
|
+
out.push({ number: Number(m[1]), url: m[2], title: m[3], state: "unknown" });
|
|
54
|
+
}
|
|
55
|
+
return out.length > 0 ? out : null;
|
|
56
|
+
}
|
|
57
|
+
function refHistory(store, repo, identity) {
|
|
58
|
+
const obs = store.findings(repo).filter((o) => o.identity === identity && o.tool === "actionpin");
|
|
59
|
+
if (obs.length === 0)
|
|
60
|
+
return undefined;
|
|
61
|
+
const scanAt = new Map(store.scans(repo).map((s) => [s.scanId, s.at]));
|
|
62
|
+
const runs = [];
|
|
63
|
+
for (const o of obs) {
|
|
64
|
+
const ref = String(o.data.ref ?? "");
|
|
65
|
+
const classification = String(o.data.classification ?? "");
|
|
66
|
+
const last = runs[runs.length - 1];
|
|
67
|
+
if (last && last.ref === ref && last.classification === classification) {
|
|
68
|
+
last.lastScanId = o.scanId;
|
|
69
|
+
last.lastAt = scanAt.get(o.scanId) ?? last.lastAt;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
runs.push({
|
|
73
|
+
ref,
|
|
74
|
+
classification,
|
|
75
|
+
firstScanId: o.scanId,
|
|
76
|
+
firstAt: scanAt.get(o.scanId) ?? "",
|
|
77
|
+
lastScanId: o.scanId,
|
|
78
|
+
lastAt: scanAt.get(o.scanId) ?? "",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return runs;
|
|
83
|
+
}
|
|
84
|
+
export async function blameFinding(store, repo, tracked, opts = {}) {
|
|
85
|
+
const obs = tracked.latest;
|
|
86
|
+
const prov = {
|
|
87
|
+
repo,
|
|
88
|
+
finding: obs,
|
|
89
|
+
status: tracked.status,
|
|
90
|
+
firstSeenAt: tracked.firstSeenAt,
|
|
91
|
+
lastSeenAt: tracked.lastSeenAt,
|
|
92
|
+
confirmed: {},
|
|
93
|
+
inferred: [],
|
|
94
|
+
notEstablished: [],
|
|
95
|
+
};
|
|
96
|
+
const history = refHistory(store, repo, obs.identity);
|
|
97
|
+
if (history && history.length > 0)
|
|
98
|
+
prov.confirmed.refHistory = history;
|
|
99
|
+
const dataSource = obs.data.source;
|
|
100
|
+
if (dataSource && dataSource.kind) {
|
|
101
|
+
prov.confirmed.source = { kind: dataSource.kind, ref: String(dataSource.ref ?? ""), url: dataSource.url };
|
|
102
|
+
}
|
|
103
|
+
const scans = store.scans(repo);
|
|
104
|
+
const recordedPath = [...scans].reverse().find((s) => s.path)?.path;
|
|
105
|
+
const clonePath = opts.path ?? recordedPath;
|
|
106
|
+
if (!clonePath || !existsSync(clonePath)) {
|
|
107
|
+
prov.notEstablished.push("git provenance not established: no local clone path is recorded for this repo (ingest a local path, or pass a clone path)");
|
|
108
|
+
if (!prov.confirmed.source && obs.tool === "injectcheck") {
|
|
109
|
+
prov.notEstablished.push("text source not established: the ingest recorded no issue/PR or file origin beyond the scan target");
|
|
110
|
+
}
|
|
111
|
+
return prov;
|
|
112
|
+
}
|
|
113
|
+
const cwd = resolve(clonePath);
|
|
114
|
+
const needle = needleFor(obs);
|
|
115
|
+
if (needle) {
|
|
116
|
+
const commits = await logPickaxe(cwd, needle, obs.file);
|
|
117
|
+
if (commits.length > 0) {
|
|
118
|
+
prov.confirmed.introducingCommit = commits[commits.length - 1];
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
prov.notEstablished.push(`introducing commit not established: git log -S found no commit introducing the needle in ${obs.file} (the content may predate the clone's history or live in an uncommitted state)`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (obs.line !== undefined) {
|
|
125
|
+
const lineCommit = await blameLine(cwd, obs.file, obs.line);
|
|
126
|
+
if (lineCommit)
|
|
127
|
+
prov.confirmed.currentLineCommit = lineCommit;
|
|
128
|
+
}
|
|
129
|
+
const intro = prov.confirmed.introducingCommit;
|
|
130
|
+
const line = prov.confirmed.currentLineCommit;
|
|
131
|
+
if (intro && line && intro.sha !== line.sha) {
|
|
132
|
+
prov.inferred.push(`the finding's content first appeared in ${intro.sha.slice(0, 12)}, but the current line was last rewritten by ${line.sha.slice(0, 12)}; the later commit touched the line without introducing the content`);
|
|
133
|
+
}
|
|
134
|
+
if (intro && !opts.offline) {
|
|
135
|
+
const slug = scans.find((s) => s.github)?.github ?? (await remoteSlug(cwd));
|
|
136
|
+
if (slug && parseGithubSlug(slug)) {
|
|
137
|
+
const pulls = await pullsForCommit(slug, intro.sha);
|
|
138
|
+
if (pulls !== null) {
|
|
139
|
+
prov.confirmed.pullRequests = pulls;
|
|
140
|
+
if (pulls.length === 0) {
|
|
141
|
+
prov.inferred.push("the GitHub API lists no pull request for the introducing commit (direct push, or a PR from a deleted fork)");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
prov.notEstablished.push("PR association not established: the GitHub API helper did not answer for the introducing commit");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
prov.notEstablished.push("PR association not established: the clone has no GitHub remote");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (intro && opts.offline) {
|
|
153
|
+
prov.notEstablished.push("PR association not established: offline mode");
|
|
154
|
+
}
|
|
155
|
+
return prov;
|
|
156
|
+
}
|
|
157
|
+
/** explain_finding bundle: the tracked finding, its history, and its provenance. */
|
|
158
|
+
export async function explainFinding(store, repo, idOrPrefix, opts = {}) {
|
|
159
|
+
const tracked = store.findFinding(repo, idOrPrefix);
|
|
160
|
+
if (!tracked)
|
|
161
|
+
return null;
|
|
162
|
+
const provenance = await blameFinding(store, repo, tracked, opts);
|
|
163
|
+
return { tracked, provenance };
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=blame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blame.js","sourceRoot":"","sources":["../src/blame.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,CAAC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,6DAA6D,CAAC;AAE5I,SAAS,SAAS,CAAC,GAAe;IAChC,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IACnF,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAChF,OAAO,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,IAAY,EAAE,GAAW;IACrD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,IAAI,YAAY,GAAG,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IACtG,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACpE,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA8B,CAAC;QAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;gBACxB,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC5B,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAC7B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;aAC7B,CAAC,CAAC,CAAC;QACN,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,2EAA2E;IAC7E,CAAC;IACD,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,MAAM,EAAE,GAAG,2FAA2F,CAAC;IACvG,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACpC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrC,CAAC;AAED,SAAS,UAAU,CAAC,KAAY,EAAE,IAAY,EAAE,QAAgB;IAC9D,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IAClG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,IAAI,GAAuD,EAAE,CAAC;IACpE,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;QACrC,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;QAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACnC,IAAI,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,cAAc,KAAK,cAAc,EAAE,CAAC;YACvE,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;YAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,CAAC;gBACR,GAAG;gBACH,cAAc;gBACd,WAAW,EAAE,CAAC,CAAC,MAAM;gBACrB,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE;gBACnC,UAAU,EAAE,CAAC,CAAC,MAAM;gBACpB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE;aACnC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAY,EACZ,IAAY,EACZ,OAAuB,EACvB,OAA6C,EAAE;IAE/C,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC;IAC3B,MAAM,IAAI,GAAe;QACvB,IAAI;QACJ,OAAO,EAAE,GAAG;QACZ,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,EAAE;QACZ,cAAc,EAAE,EAAE;KACnB,CAAC;IAEF,MAAM,OAAO,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,CAAC,SAAS,CAAC,UAAU,GAAG,OAAO,CAAC;IAEvE,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,CAAC,MAAmE,CAAC;IAChG,IAAI,UAAU,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QAClC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,CAAC;IAC5G,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,YAAY,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACpE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,IAAI,YAAY,CAAC;IAC5C,IAAI,CAAC,SAAS,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,cAAc,CAAC,IAAI,CACtB,2HAA2H,CAC5H,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACzD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,oGAAoG,CAAC,CAAC;QACjI,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE/B,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,CAAC,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,cAAc,CAAC,IAAI,CACtB,4FAA4F,GAAG,CAAC,IAAI,gFAAgF,CACrL,CAAC;QACJ,CAAC;IACH,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5D,IAAI,UAAU;YAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,GAAG,UAAU,CAAC;IAChE,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC;IAC9C,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAChB,2CAA2C,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,gDAAgD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,qEAAqE,CAC5M,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,IAAI,IAAI,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YACpD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,IAAI,CAAC,SAAS,CAAC,YAAY,GAAG,KAAK,CAAC;gBACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,4GAA4G,CAAC,CAAC;gBACnI,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,iGAAiG,CAAC,CAAC;YAC9H,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAY,EACZ,IAAY,EACZ,UAAkB,EAClB,OAA6C,EAAE;IAE/C,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACjC,CAAC"}
|
package/dist/cli.d.ts
ADDED