@bli-cockpit/cli 0.1.30 → 0.1.32
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { normalizeGitOrigin, repoFingerprintFromOrigin, repoLabelFromOrigin, stableWorktreeFingerprint, } from "../repo-identity.js";
|
|
3
4
|
/**
|
|
4
5
|
* Shared deterministic attribution core for agent session transcripts (Codex
|
|
5
6
|
* and Claude Code). Both adapters extract source-shaped signals, normalize them
|
|
@@ -73,6 +74,12 @@ export function scoreSignalsAgainstWorktrees(signals, worktrees, options = {}) {
|
|
|
73
74
|
const secondBestScore = scored[1]?.score ?? 0;
|
|
74
75
|
if (!best || best.score === 0) {
|
|
75
76
|
if (repoPathAbsentFromDisk([...signals.cwds, ...signals.workspaceRoots], options.collectionRoots ?? [])) {
|
|
77
|
+
const transcriptFallback = fallbackToTranscriptOriginForDeletedRepo({
|
|
78
|
+
originLabel,
|
|
79
|
+
signals,
|
|
80
|
+
});
|
|
81
|
+
if (transcriptFallback)
|
|
82
|
+
return transcriptFallback;
|
|
76
83
|
return skipped("repo_not_on_disk");
|
|
77
84
|
}
|
|
78
85
|
const reason = signals.cwds.length > 0 || signals.workspaceRoots.length > 0
|
|
@@ -173,6 +180,49 @@ function fallbackToKnownRepoPrimaryWorkContext(options) {
|
|
|
173
180
|
worktree: primary,
|
|
174
181
|
};
|
|
175
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Reconstructs a repo/worktree identity from transcript origin metadata only
|
|
185
|
+
* after the caller has already proven a recorded cwd/workspace root sits under
|
|
186
|
+
* an operator-approved collection root. The origin is never allowed to widen
|
|
187
|
+
* consent by itself: it only rescues sessions for repos that were once inside
|
|
188
|
+
* the approved scan boundary but have since been deleted from disk, where the
|
|
189
|
+
* transcript is still durable enough to carry the normalized remote identity.
|
|
190
|
+
*/
|
|
191
|
+
function fallbackToTranscriptOriginForDeletedRepo(options) {
|
|
192
|
+
const origins = [
|
|
193
|
+
...new Set(options.signals.originUrls
|
|
194
|
+
.map((origin) => normalizeGitOrigin(origin))
|
|
195
|
+
.filter(Boolean)),
|
|
196
|
+
];
|
|
197
|
+
if (origins.length === 0)
|
|
198
|
+
return null;
|
|
199
|
+
if (origins.length > 1)
|
|
200
|
+
return skipped("multiple_transcript_origins");
|
|
201
|
+
const recordedPath = options.signals.workspaceRoots[0] ?? options.signals.cwds[0] ?? null;
|
|
202
|
+
if (!recordedPath)
|
|
203
|
+
return null;
|
|
204
|
+
const resolvedPath = path.resolve(recordedPath);
|
|
205
|
+
const origin = origins[0] ?? "";
|
|
206
|
+
return {
|
|
207
|
+
state: "attributed_fallback",
|
|
208
|
+
reason: "transcript_origin_fallback",
|
|
209
|
+
signals: ["transcript_origin_fallback", options.originLabel],
|
|
210
|
+
attribution_score: clampScore(SCORE_ORIGIN_MATCH),
|
|
211
|
+
path_score: 0,
|
|
212
|
+
worktree: {
|
|
213
|
+
requested_path: resolvedPath,
|
|
214
|
+
repo_root: resolvedPath,
|
|
215
|
+
repo_label: repoLabelFromOrigin(origin),
|
|
216
|
+
repo_fingerprint: repoFingerprintFromOrigin(origin),
|
|
217
|
+
repo_origin_url: origin,
|
|
218
|
+
branch: options.signals.branches[0] ?? "unknown",
|
|
219
|
+
head_sha: options.signals.headShas[0] ?? null,
|
|
220
|
+
worktree_label: path.basename(resolvedPath),
|
|
221
|
+
worktree_fingerprint: stableWorktreeFingerprint(resolvedPath),
|
|
222
|
+
worktree_is_primary: false,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
176
226
|
/**
|
|
177
227
|
* For each path signal, the single worktree whose root most specifically
|
|
178
228
|
* contains it (the longest containing root). Returns the set of worktree
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SECRET_FILE_SEGMENT_PATTERN, containsSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
1
|
+
import { RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES, SECRET_FILE_SEGMENT_PATTERN, containsSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { createReadStream } from "node:fs";
|
|
@@ -24,7 +24,7 @@ export const CLAUDE_ATTRIBUTION_DEFAULT_SINCE_MINUTES = 24 * 60;
|
|
|
24
24
|
export const CLAUDE_ATTRIBUTION_DEFAULT_SESSION_LIMIT = 50;
|
|
25
25
|
// Upload cap, NOT an attribution cap (D7): an oversized main file is still
|
|
26
26
|
// scored and attributed via a streamed read; only the file's bytes stay local.
|
|
27
|
-
export const CLAUDE_SESSION_MAX_FILE_BYTES =
|
|
27
|
+
export const CLAUDE_SESSION_MAX_FILE_BYTES = RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES;
|
|
28
28
|
export const CLAUDE_SESSION_MAX_SIDECAR_FILES = 40;
|
|
29
29
|
// JSONL lines holding a large tool result can be multi-MiB; a streamed read of
|
|
30
30
|
// an oversized main caps the per-line buffer so one giant line cannot blow
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EvidenceCompletenessPayloadSchema, SECRET_FILE_SEGMENT_PATTERN, SourceScanResultSchema, containsSecretLikeContent, redactSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
1
|
+
import { EvidenceCompletenessPayloadSchema, RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES, SECRET_FILE_SEGMENT_PATTERN, SourceScanResultSchema, containsSecretLikeContent, redactSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
@@ -13,14 +13,12 @@ const MAX_GIT_DIFF_BYTES = 2 * 1024 * 1024;
|
|
|
13
13
|
const GIT_DIFF_TIMEOUT_MS = 3_000;
|
|
14
14
|
export const RAW_EVIDENCE_BUCKET = "ambient-raw-evidence";
|
|
15
15
|
export const RAW_EVIDENCE_RETENTION_MODE = "remote_durable";
|
|
16
|
-
// Per-sync upload budgets enforced at COLLECTION time (D7b).
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
// (content addressing makes this convergent).
|
|
21
|
-
export const RAW_EVIDENCE_DEFAULT_BYTE_BUDGET = 512 * 1024 * 1024;
|
|
16
|
+
// Per-sync upload budgets enforced at COLLECTION time (D7b). A single marathon
|
|
17
|
+
// transcript can now approach 512 MiB, so 2 GiB leaves room for several files
|
|
18
|
+
// without starving the sync; overflow still defers and converges next run.
|
|
19
|
+
export const RAW_EVIDENCE_DEFAULT_BYTE_BUDGET = 2 * 1024 * 1024 * 1024;
|
|
22
20
|
export const RAW_EVIDENCE_DEFAULT_OBJECT_BUDGET = 300;
|
|
23
|
-
const CLAUDE_MAX_COLLECT_FILE_BYTES =
|
|
21
|
+
const CLAUDE_MAX_COLLECT_FILE_BYTES = RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES;
|
|
24
22
|
export async function collectRawEvidencePack(context, options) {
|
|
25
23
|
const startedAt = context.now.toISOString();
|
|
26
24
|
const packId = rawEvidencePackId(context);
|
|
@@ -587,6 +585,29 @@ async function collectOneEvidenceFile(collection, options) {
|
|
|
587
585
|
});
|
|
588
586
|
return false;
|
|
589
587
|
}
|
|
588
|
+
if (options.maxFileBytes) {
|
|
589
|
+
let stat;
|
|
590
|
+
try {
|
|
591
|
+
stat = await fs.stat(options.filePath);
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
collection.skipped.push({
|
|
595
|
+
kind: options.kind,
|
|
596
|
+
label: fileName,
|
|
597
|
+
reason: "file_read_failed",
|
|
598
|
+
});
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
if (stat.size > options.maxFileBytes) {
|
|
602
|
+
markCapApplied(collection, options.kind, "max_file_bytes");
|
|
603
|
+
collection.skipped.push({
|
|
604
|
+
kind: options.kind,
|
|
605
|
+
label: fileName,
|
|
606
|
+
reason: "file_too_large",
|
|
607
|
+
});
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
590
611
|
let raw;
|
|
591
612
|
try {
|
|
592
613
|
raw = await fs.readFile(options.filePath);
|
|
@@ -557,7 +557,7 @@ function reasonClassification(reason) {
|
|
|
557
557
|
if (reason === "file_too_large") {
|
|
558
558
|
return {
|
|
559
559
|
classification: "retryable",
|
|
560
|
-
note: "until
|
|
560
|
+
note: "until the evidence file cap is raised",
|
|
561
561
|
};
|
|
562
562
|
}
|
|
563
563
|
if (reason === "repo_not_on_disk") {
|
|
@@ -12,6 +12,22 @@ export async function uploadRawEvidenceFilesChunked(options) {
|
|
|
12
12
|
const loaded = [];
|
|
13
13
|
const loadedByObjectKey = new Map();
|
|
14
14
|
for (const file of options.files) {
|
|
15
|
+
let stat;
|
|
16
|
+
try {
|
|
17
|
+
stat = await fs.stat(file.local_path);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
outcomes.push(failedOutcome(file, "file_read_failed"));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (stat.size === 0) {
|
|
24
|
+
outcomes.push(failedOutcome(file, "empty_file"));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (stat.size > RAW_EVIDENCE_UPLOAD_MAX_FILE_BYTES) {
|
|
28
|
+
outcomes.push(failedOutcome(file, "file_too_large"));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
15
31
|
let bytes;
|
|
16
32
|
try {
|
|
17
33
|
bytes = await fs.readFile(file.local_path);
|
package/dist/repo-identity.js
CHANGED
|
@@ -32,10 +32,9 @@ export async function resolveRepoWorktreeIdentity(repoRoot) {
|
|
|
32
32
|
const repoLabel = repoOriginUrl
|
|
33
33
|
? repoLabelFromOrigin(repoOriginUrl)
|
|
34
34
|
: path.basename(resolvedRoot);
|
|
35
|
-
const
|
|
36
|
-
?
|
|
37
|
-
: `local:${sha256(commonGitDir ?? resolvedRoot)}`;
|
|
38
|
-
const repoFingerprint = `repo-${sha256(repoMaterial).slice(0, 24)}`;
|
|
35
|
+
const repoFingerprint = repoOriginUrl
|
|
36
|
+
? repoFingerprintFromOrigin(repoOriginUrl)
|
|
37
|
+
: `repo-${sha256(`local:${sha256(commonGitDir ?? resolvedRoot)}`).slice(0, 24)}`;
|
|
39
38
|
const gitFilePath = path.join(resolvedRoot, ".git");
|
|
40
39
|
const worktreeIsPrimary = await fs.stat(gitFilePath).then((stat) => stat.isDirectory(), () => false);
|
|
41
40
|
return {
|
|
@@ -162,6 +161,10 @@ export async function stableWorktreeRoot(repoRoot) {
|
|
|
162
161
|
export function stableWorktreeFingerprint(repoRoot) {
|
|
163
162
|
return `wt-${sha256(`worktree:${normalizeFingerprintPath(repoRoot)}`).slice(0, 24)}`;
|
|
164
163
|
}
|
|
164
|
+
export function repoFingerprintFromOrigin(origin) {
|
|
165
|
+
const normalizedOrigin = normalizeGitOrigin(origin);
|
|
166
|
+
return `repo-${sha256(`origin:${normalizedOrigin}`).slice(0, 24)}`;
|
|
167
|
+
}
|
|
165
168
|
async function resolveBranchFromHead(repoRoot) {
|
|
166
169
|
try {
|
|
167
170
|
const gitPath = path.join(repoRoot, ".git");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"test": "node dist/cli.js --help && node ../../scripts/assert-public-package-pack.mjs --workspace=@bli-cockpit/cli"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
29
|
+
"@bli-cockpit/telemetry-core": "0.1.11"
|
|
30
30
|
}
|
|
31
31
|
}
|