@bli-cockpit/cli 0.1.18 → 0.1.20
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 +41 -8
- package/dist/adapters/raw-evidence.js +278 -19
- package/dist/agent-rules.js +133 -15
- package/dist/commands/local-args.js +51 -2
- package/dist/commands/local.js +74 -12
- package/dist/commands/public-root.js +2 -2
- package/dist/local-state.js +7 -1
- package/dist/upload.js +31 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,18 +42,25 @@ pairing. Already-onboarded users update with
|
|
|
42
42
|
`cockpit sync --workspace "$PWD" --json`. `--repo <path>` remains supported
|
|
43
43
|
for older prompts and the agent ticket-binding guardrail.
|
|
44
44
|
|
|
45
|
-
On machines where Codex or Claude agents will do ticketed work,
|
|
46
|
-
|
|
45
|
+
On machines where Codex or Claude agents will do ticketed work, interactive
|
|
46
|
+
`cockpit onboard` checks `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` after
|
|
47
|
+
harvest proof. If equivalent Cockpit ticket-binding guidance already exists, it
|
|
48
|
+
leaves the files alone. If guidance is missing or clearly stale, it asks whether
|
|
49
|
+
to install or replace it. The managed guidance is scoped to the workspace path
|
|
50
|
+
used for onboarding, so agents should ignore it in private chats or unrelated
|
|
51
|
+
repos.
|
|
47
52
|
|
|
48
53
|
```bash
|
|
49
|
-
|
|
54
|
+
# Repair/manual path, or headless/json onboarding where Cockpit cannot prompt.
|
|
55
|
+
cockpit agent-rules install --workspace "$PWD"
|
|
50
56
|
```
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
rule to bind known Linear tickets before edits, or ask once when
|
|
54
|
-
is missing. That binding starts attributing
|
|
55
|
-
ticket in Cockpit. It checks for
|
|
56
|
-
left unchanged and stale
|
|
58
|
+
The direct command updates `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` with
|
|
59
|
+
the Cockpit rule to bind known Linear tickets before edits, or ask once when
|
|
60
|
+
the ticket ID is missing inside that workspace. That binding starts attributing
|
|
61
|
+
the session's work to the specific ticket in Cockpit. It checks for managed or
|
|
62
|
+
equivalent guidance first, so current files are left unchanged and stale Cockpit
|
|
63
|
+
ticket-binding sections are replaced.
|
|
57
64
|
|
|
58
65
|
Parent mode scans child git repos/worktrees (3 folder levels deep, up to 50
|
|
59
66
|
repos by default — tune with `--max-depth` / `--max-repos`; a warning prints
|
|
@@ -108,6 +115,30 @@ cockpit sync \
|
|
|
108
115
|
No ticket is required for setup, chatting, planning, or general ambient capture.
|
|
109
116
|
Only pass `--ticket` when the work really belongs to a visible ticket.
|
|
110
117
|
|
|
118
|
+
When the work is important but ticketless, label it explicitly before syncing so
|
|
119
|
+
later analysis does not have to guess the topic:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cockpit start \
|
|
123
|
+
--workspace "$PWD" \
|
|
124
|
+
--topic "lead ingestion rewrite planning" \
|
|
125
|
+
--intent planning \
|
|
126
|
+
--phase discovery \
|
|
127
|
+
--intent-confidence 0.9
|
|
128
|
+
|
|
129
|
+
cockpit sync \
|
|
130
|
+
--workspace "$PWD" \
|
|
131
|
+
--json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Supported `--intent` values are `implementation`, `bug_fix`,
|
|
135
|
+
`root_cause_analysis`, `planning`, `discovery`, `review`, `testing`,
|
|
136
|
+
`documentation`, `release`, `learning`, `coordination`, `maintenance`,
|
|
137
|
+
`analysis`, `unknown`, and `other`. Supported `--phase` values are `planning`,
|
|
138
|
+
`discovery`, `implementation`, `debugging`, `review`, `testing`,
|
|
139
|
+
`documentation`, `release`, `handoff`, `analysis`, `unknown`, and `other`.
|
|
140
|
+
Use `--topic-summary` only for short redacted summaries, not transcript text.
|
|
141
|
+
|
|
111
142
|
## What gets saved
|
|
112
143
|
|
|
113
144
|
Local files:
|
|
@@ -175,3 +206,5 @@ This public package intentionally excludes Cockpit admin bootstrap commands,
|
|
|
175
206
|
service-role credential handling, source maps, tests, and internal runbooks.
|
|
176
207
|
Clean `npm pack` and `npm publish` run the public CLI build before packaging so
|
|
177
208
|
`dist/cli.js` is present in emergency releases.
|
|
209
|
+
When collector changes depend on new telemetry-core exports, publish
|
|
210
|
+
`@bli-cockpit/telemetry-core` first, then publish `@bli-cockpit/cli`.
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { SECRET_FILE_SEGMENT_PATTERN, SourceScanResultSchema, containsSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
2
|
-
import {
|
|
1
|
+
import { EvidenceCompletenessPayloadSchema, SECRET_FILE_SEGMENT_PATTERN, SourceScanResultSchema, containsSecretLikeContent, } from "@bli-cockpit/telemetry-core";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { promisify } from "node:util";
|
|
8
7
|
import { makeSourceAdapterIdentity, } from "./common.js";
|
|
9
8
|
import { collectAgentImageEvidenceFromJsonlFile, } from "./agent-image-evidence.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
9
|
const DEFAULT_SINCE_MINUTES = 24 * 60;
|
|
12
10
|
const DEFAULT_SESSION_LIMIT = 50;
|
|
13
11
|
const MAX_GIT_DIFF_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
const GIT_DIFF_TIMEOUT_MS = 3_000;
|
|
14
13
|
export const RAW_EVIDENCE_BUCKET = "ambient-raw-evidence";
|
|
15
14
|
export const RAW_EVIDENCE_RETENTION_MODE = "remote_durable";
|
|
16
15
|
// Per-sync upload budgets enforced at COLLECTION time (D7b). With sidecars a
|
|
@@ -28,18 +27,65 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
28
27
|
const filesDir = path.join(evidenceDir, "files");
|
|
29
28
|
const entries = [];
|
|
30
29
|
const skipped = [];
|
|
30
|
+
const truncated = [];
|
|
31
31
|
const reused = [];
|
|
32
|
+
const sinceMinutes = options.sinceMinutes ?? DEFAULT_SINCE_MINUTES;
|
|
33
|
+
const sessionLimit = options.sessionLimit ?? DEFAULT_SESSION_LIMIT;
|
|
34
|
+
const byteBudget = options.byteBudget ?? RAW_EVIDENCE_DEFAULT_BYTE_BUDGET;
|
|
35
|
+
const objectBudget = options.objectBudget ?? RAW_EVIDENCE_DEFAULT_OBJECT_BUDGET;
|
|
32
36
|
const collection = {
|
|
33
37
|
context,
|
|
34
38
|
filesDir,
|
|
35
39
|
packId,
|
|
36
40
|
entries,
|
|
37
41
|
skipped,
|
|
42
|
+
truncated,
|
|
38
43
|
reused,
|
|
44
|
+
scanned: new Map(),
|
|
45
|
+
caps: [
|
|
46
|
+
{
|
|
47
|
+
source: "raw_evidence",
|
|
48
|
+
cap_type: "byte_budget",
|
|
49
|
+
limit: byteBudget,
|
|
50
|
+
observed: options.budget?.remainingBytes ?? byteBudget,
|
|
51
|
+
applied: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
source: "raw_evidence",
|
|
55
|
+
cap_type: "object_budget",
|
|
56
|
+
limit: objectBudget,
|
|
57
|
+
observed: options.budget?.remainingObjects ?? objectBudget,
|
|
58
|
+
applied: false,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
source: "git_diff",
|
|
62
|
+
cap_type: "max_bytes_per_diff",
|
|
63
|
+
limit: MAX_GIT_DIFF_BYTES,
|
|
64
|
+
applied: false,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
source: "git_diff",
|
|
68
|
+
cap_type: "timeout_ms",
|
|
69
|
+
limit: GIT_DIFF_TIMEOUT_MS,
|
|
70
|
+
applied: false,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
source: "claude_jsonl",
|
|
74
|
+
cap_type: "max_file_bytes",
|
|
75
|
+
limit: CLAUDE_MAX_COLLECT_FILE_BYTES,
|
|
76
|
+
applied: false,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
source: "claude_jsonl_sidecar",
|
|
80
|
+
cap_type: "max_file_bytes",
|
|
81
|
+
limit: CLAUDE_MAX_COLLECT_FILE_BYTES,
|
|
82
|
+
applied: false,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
39
85
|
skipContentHashes: options.skipContentHashes ?? new Set(),
|
|
40
86
|
budget: options.budget ?? {
|
|
41
|
-
remainingBytes:
|
|
42
|
-
remainingObjects:
|
|
87
|
+
remainingBytes: byteBudget,
|
|
88
|
+
remainingObjects: objectBudget,
|
|
43
89
|
},
|
|
44
90
|
index: { value: 0 },
|
|
45
91
|
};
|
|
@@ -50,8 +96,8 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
50
96
|
await collectCodexJsonlFiles(collection, {
|
|
51
97
|
codexSessionFiles: options.codexSessionFiles,
|
|
52
98
|
sessionsDir: options.sessionsDir,
|
|
53
|
-
sinceMinutes
|
|
54
|
-
limit:
|
|
99
|
+
sinceMinutes,
|
|
100
|
+
limit: sessionLimit,
|
|
55
101
|
});
|
|
56
102
|
}
|
|
57
103
|
if (options.includeClaudeJsonl !== false && options.claudeSessionFiles) {
|
|
@@ -61,6 +107,11 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
61
107
|
const deferredByteBudgetCount = skipped.filter((entry) => entry.reason === "deferred_byte_budget").length;
|
|
62
108
|
const deferredObjectBudgetCount = skipped.filter((entry) => entry.reason === "deferred_object_budget").length;
|
|
63
109
|
if (entries.length === 0) {
|
|
110
|
+
const evidenceCompleteness = makeEvidenceCompleteness(collection, {
|
|
111
|
+
startedAt,
|
|
112
|
+
finishedAt: context.now.toISOString(),
|
|
113
|
+
sinceMinutes,
|
|
114
|
+
});
|
|
64
115
|
const facts = {
|
|
65
116
|
pack_id: packId,
|
|
66
117
|
manifest_path: path.join(evidenceDir, "manifest.json"),
|
|
@@ -73,6 +124,7 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
73
124
|
deferred_byte_budget_count: deferredByteBudgetCount,
|
|
74
125
|
deferred_object_budget_count: deferredObjectBudgetCount,
|
|
75
126
|
content_kinds: [],
|
|
127
|
+
evidence_completeness: evidenceCompleteness,
|
|
76
128
|
pointers: [],
|
|
77
129
|
upload_files: [],
|
|
78
130
|
reused,
|
|
@@ -109,6 +161,11 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
109
161
|
bytes: manifestBytes,
|
|
110
162
|
});
|
|
111
163
|
entries.push(manifestEntry);
|
|
164
|
+
const evidenceCompleteness = makeEvidenceCompleteness(collection, {
|
|
165
|
+
startedAt,
|
|
166
|
+
finishedAt: context.now.toISOString(),
|
|
167
|
+
sinceMinutes,
|
|
168
|
+
});
|
|
112
169
|
const facts = {
|
|
113
170
|
pack_id: packId,
|
|
114
171
|
manifest_path: manifestPath,
|
|
@@ -121,6 +178,7 @@ export async function collectRawEvidencePack(context, options) {
|
|
|
121
178
|
deferred_byte_budget_count: deferredByteBudgetCount,
|
|
122
179
|
deferred_object_budget_count: deferredObjectBudgetCount,
|
|
123
180
|
content_kinds: [...new Set(entries.map((entry) => entry.kind))],
|
|
181
|
+
evidence_completeness: evidenceCompleteness,
|
|
124
182
|
pointers: entries.map(pointerFromEntry),
|
|
125
183
|
upload_files: entries.map((entry) => ({
|
|
126
184
|
pointer: pointerFromEntry(entry),
|
|
@@ -185,20 +243,33 @@ function makeRawEvidenceScan(options) {
|
|
|
185
243
|
`bytes:${options.facts.byte_size}`,
|
|
186
244
|
`skipped:${options.facts.skipped_count}`,
|
|
187
245
|
`reused:${options.facts.reused_count}`,
|
|
246
|
+
`completeness:${options.facts.evidence_completeness.status}`,
|
|
247
|
+
`truncated:${options.facts.evidence_completeness.totals.truncated_count}`,
|
|
248
|
+
`deferred:${options.facts.evidence_completeness.totals.deferred_count}`,
|
|
188
249
|
...options.facts.content_kinds.map((kind) => `kind:${kind}`),
|
|
189
250
|
],
|
|
190
251
|
});
|
|
191
252
|
}
|
|
192
253
|
async function collectCodexJsonlFiles(collection, options) {
|
|
193
|
-
const
|
|
254
|
+
const resolvedCandidates = options.codexSessionFiles
|
|
194
255
|
? options.codexSessionFiles.map((file) => ({
|
|
195
256
|
filePath: file.local_path,
|
|
196
257
|
codexSessionId: file.codex_session_id,
|
|
197
258
|
}))
|
|
198
259
|
: (await walkJsonlFiles(options.sessionsDir ?? path.join(os.homedir(), ".codex", "sessions"), collection.context.now.getTime() - options.sinceMinutes * 60 * 1000))
|
|
199
|
-
.slice(0, options.limit)
|
|
200
260
|
.map((filePath) => ({ filePath, codexSessionId: null }));
|
|
261
|
+
collection.caps.push({
|
|
262
|
+
source: "codex_jsonl",
|
|
263
|
+
cap_type: "session_limit",
|
|
264
|
+
limit: options.limit,
|
|
265
|
+
observed: resolvedCandidates.length,
|
|
266
|
+
applied: !options.codexSessionFiles && resolvedCandidates.length > options.limit,
|
|
267
|
+
});
|
|
268
|
+
const candidates = options.codexSessionFiles
|
|
269
|
+
? resolvedCandidates
|
|
270
|
+
: resolvedCandidates.slice(0, options.limit);
|
|
201
271
|
for (const candidate of candidates) {
|
|
272
|
+
recordScanned(collection, "codex_jsonl");
|
|
202
273
|
const codexSessionId = candidate.codexSessionId ?? shortHash(candidate.filePath);
|
|
203
274
|
const transcriptAccepted = await collectOneEvidenceFile(collection, {
|
|
204
275
|
filePath: candidate.filePath,
|
|
@@ -220,6 +291,7 @@ async function collectCodexJsonlFiles(collection, options) {
|
|
|
220
291
|
}
|
|
221
292
|
}
|
|
222
293
|
async function collectClaudeJsonlFiles(collection, sessions) {
|
|
294
|
+
recordScanned(collection, "claude_jsonl", sessions.reduce((count, session) => count + 1 + session.sidecar_files.length, 0));
|
|
223
295
|
for (const session of sessions) {
|
|
224
296
|
const sessionId = session.claude_session_id;
|
|
225
297
|
if (session.main_file_oversized) {
|
|
@@ -300,6 +372,7 @@ async function collectAgentImagesFromTranscript(collection, options) {
|
|
|
300
372
|
sessionId: options.sessionId,
|
|
301
373
|
sidecarId: options.sidecarId,
|
|
302
374
|
});
|
|
375
|
+
recordScanned(collection, options.kind, result.images.length + result.skipped.length);
|
|
303
376
|
for (const skipped of result.skipped) {
|
|
304
377
|
collection.skipped.push({
|
|
305
378
|
kind: options.kind,
|
|
@@ -336,6 +409,7 @@ async function collectOneAgentImageFile(collection, options) {
|
|
|
336
409
|
}
|
|
337
410
|
const deferReason = admitToBudget(collection.budget, raw.byteLength);
|
|
338
411
|
if (deferReason) {
|
|
412
|
+
markBudgetCapApplied(collection, deferReason);
|
|
339
413
|
collection.skipped.push({
|
|
340
414
|
kind: options.kind,
|
|
341
415
|
label: options.image.label,
|
|
@@ -392,6 +466,7 @@ async function collectOneEvidenceFile(collection, options) {
|
|
|
392
466
|
return false;
|
|
393
467
|
}
|
|
394
468
|
if (options.maxFileBytes && raw.byteLength > options.maxFileBytes) {
|
|
469
|
+
markCapApplied(collection, options.kind, "max_file_bytes");
|
|
395
470
|
collection.skipped.push({
|
|
396
471
|
kind: options.kind,
|
|
397
472
|
label: fileName,
|
|
@@ -419,6 +494,7 @@ async function collectOneEvidenceFile(collection, options) {
|
|
|
419
494
|
}
|
|
420
495
|
const deferReason = admitToBudget(collection.budget, raw.byteLength);
|
|
421
496
|
if (deferReason) {
|
|
497
|
+
markBudgetCapApplied(collection, deferReason);
|
|
422
498
|
collection.skipped.push({
|
|
423
499
|
kind: options.kind,
|
|
424
500
|
label: fileName,
|
|
@@ -465,10 +541,23 @@ async function collectGitDiffFiles(collection, repoRoot) {
|
|
|
465
541
|
{ label: "staged", args: ["diff", "--cached", "--no-ext-diff", "--"] },
|
|
466
542
|
];
|
|
467
543
|
for (const target of diffTargets) {
|
|
544
|
+
recordScanned(collection, "git_diff");
|
|
468
545
|
const diff = await runGitDiff(target.args, repoRoot);
|
|
469
|
-
if (
|
|
546
|
+
if (diff.truncated) {
|
|
547
|
+
markCapApplied(collection, "git_diff", diff.truncationCapType);
|
|
548
|
+
collection.truncated.push({
|
|
549
|
+
kind: "git_diff",
|
|
550
|
+
reason: diff.truncationReason,
|
|
551
|
+
...(diff.truncationCapType === "max_bytes_per_diff"
|
|
552
|
+
? { max_bytes: MAX_GIT_DIFF_BYTES }
|
|
553
|
+
: {}),
|
|
554
|
+
observed_bytes: diff.observedBytes,
|
|
555
|
+
included_bytes: Buffer.byteLength(diff.stdout, "utf8"),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (!diff.stdout.trim())
|
|
470
559
|
continue;
|
|
471
|
-
if (containsSecretLikeContent(diff)) {
|
|
560
|
+
if (containsSecretLikeContent(diff.stdout)) {
|
|
472
561
|
collection.skipped.push({
|
|
473
562
|
kind: "git_diff",
|
|
474
563
|
label: target.label,
|
|
@@ -476,7 +565,7 @@ async function collectGitDiffFiles(collection, repoRoot) {
|
|
|
476
565
|
});
|
|
477
566
|
continue;
|
|
478
567
|
}
|
|
479
|
-
const raw = Buffer.from(diff.
|
|
568
|
+
const raw = Buffer.from(diff.stdout, "utf8");
|
|
480
569
|
const contentHash = sha256(raw);
|
|
481
570
|
if (collection.skipContentHashes.has(contentHash)) {
|
|
482
571
|
collection.reused.push({
|
|
@@ -489,6 +578,7 @@ async function collectGitDiffFiles(collection, repoRoot) {
|
|
|
489
578
|
}
|
|
490
579
|
const deferReason = admitToBudget(collection.budget, raw.byteLength);
|
|
491
580
|
if (deferReason) {
|
|
581
|
+
markBudgetCapApplied(collection, deferReason);
|
|
492
582
|
collection.skipped.push({
|
|
493
583
|
kind: "git_diff",
|
|
494
584
|
label: target.label,
|
|
@@ -507,7 +597,9 @@ async function collectGitDiffFiles(collection, repoRoot) {
|
|
|
507
597
|
localPath: destination,
|
|
508
598
|
relativePath,
|
|
509
599
|
mediaType: "text/x-diff",
|
|
510
|
-
redactedSummary:
|
|
600
|
+
redactedSummary: diff.truncated
|
|
601
|
+
? `Raw git ${target.label} diff truncated to the capture cap and preserved locally with env/secret paths excluded.`
|
|
602
|
+
: `Raw git ${target.label} diff preserved locally with env/secret paths excluded.`,
|
|
511
603
|
bytes: raw,
|
|
512
604
|
contentAddress: `git-diff/${target.label}-${contentHash.slice(0, 16)}.diff`,
|
|
513
605
|
}));
|
|
@@ -526,12 +618,179 @@ async function runGitDiff(args, repoRoot) {
|
|
|
526
618
|
":(exclude)**/*.pem",
|
|
527
619
|
":(exclude)**/*.key",
|
|
528
620
|
];
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
621
|
+
return new Promise((resolve, reject) => {
|
|
622
|
+
const child = spawn("git", [...args, ...pathspec], {
|
|
623
|
+
cwd: repoRoot,
|
|
624
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
625
|
+
});
|
|
626
|
+
const stdoutChunks = [];
|
|
627
|
+
const stderrChunks = [];
|
|
628
|
+
let observedBytes = 0;
|
|
629
|
+
let includedBytes = 0;
|
|
630
|
+
let truncated = false;
|
|
631
|
+
let timedOut = false;
|
|
632
|
+
const timeout = setTimeout(() => {
|
|
633
|
+
timedOut = true;
|
|
634
|
+
truncated = true;
|
|
635
|
+
child.kill("SIGTERM");
|
|
636
|
+
}, GIT_DIFF_TIMEOUT_MS);
|
|
637
|
+
child.stdout.on("data", (chunk) => {
|
|
638
|
+
observedBytes += chunk.byteLength;
|
|
639
|
+
if (includedBytes < MAX_GIT_DIFF_BYTES) {
|
|
640
|
+
const remaining = MAX_GIT_DIFF_BYTES - includedBytes;
|
|
641
|
+
const next = chunk.subarray(0, remaining);
|
|
642
|
+
stdoutChunks.push(next);
|
|
643
|
+
includedBytes += next.byteLength;
|
|
644
|
+
}
|
|
645
|
+
if (observedBytes > MAX_GIT_DIFF_BYTES) {
|
|
646
|
+
truncated = true;
|
|
647
|
+
child.kill("SIGTERM");
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
child.stderr.on("data", (chunk) => {
|
|
651
|
+
if (stderrChunks.reduce((sum, item) => sum + item.byteLength, 0) < 4096) {
|
|
652
|
+
stderrChunks.push(chunk.subarray(0, 4096));
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
child.on("error", (error) => {
|
|
656
|
+
clearTimeout(timeout);
|
|
657
|
+
reject(error);
|
|
658
|
+
});
|
|
659
|
+
child.on("close", (code, signal) => {
|
|
660
|
+
clearTimeout(timeout);
|
|
661
|
+
if (code === 0 || truncated || signal === "SIGTERM") {
|
|
662
|
+
resolve({
|
|
663
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
664
|
+
truncated,
|
|
665
|
+
observedBytes,
|
|
666
|
+
truncationReason: timedOut
|
|
667
|
+
? "git_diff_timeout"
|
|
668
|
+
: "max_git_diff_bytes",
|
|
669
|
+
truncationCapType: timedOut ? "timeout_ms" : "max_bytes_per_diff",
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
674
|
+
reject(new Error(stderr || `git diff failed with code ${code ?? signal}`));
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function recordScanned(collection, source, count = 1) {
|
|
679
|
+
collection.scanned.set(source, (collection.scanned.get(source) ?? 0) + count);
|
|
680
|
+
}
|
|
681
|
+
function markBudgetCapApplied(collection, reason) {
|
|
682
|
+
markCapApplied(collection, "raw_evidence", reason === "deferred_object_budget" ? "object_budget" : "byte_budget");
|
|
683
|
+
}
|
|
684
|
+
function markCapApplied(collection, source, capType) {
|
|
685
|
+
const cap = collection.caps.find((candidate) => candidate.source === source && candidate.cap_type === capType);
|
|
686
|
+
if (cap)
|
|
687
|
+
cap.applied = true;
|
|
688
|
+
}
|
|
689
|
+
function makeEvidenceCompleteness(collection, options) {
|
|
690
|
+
const sources = new Set(collection.scanned.keys());
|
|
691
|
+
for (const entry of collection.entries)
|
|
692
|
+
sources.add(entry.kind);
|
|
693
|
+
for (const entry of collection.skipped)
|
|
694
|
+
sources.add(entry.kind);
|
|
695
|
+
for (const entry of collection.reused)
|
|
696
|
+
sources.add(entry.kind);
|
|
697
|
+
for (const entry of collection.truncated)
|
|
698
|
+
sources.add(entry.kind);
|
|
699
|
+
const sourceCounts = [...sources].sort().map((source) => {
|
|
700
|
+
const skipped = collection.skipped.filter((entry) => entry.kind === source);
|
|
701
|
+
return {
|
|
702
|
+
source,
|
|
703
|
+
scanned_count: collection.scanned.get(source) ?? 0,
|
|
704
|
+
included_count: collection.entries.filter((entry) => entry.kind === source).length,
|
|
705
|
+
skipped_count: skipped.length,
|
|
706
|
+
truncated_count: collection.truncated.filter((entry) => entry.kind === source).length,
|
|
707
|
+
deferred_count: skipped.filter((entry) => entry.reason.startsWith("deferred_")).length,
|
|
708
|
+
reused_count: collection.reused.filter((entry) => entry.kind === source).length,
|
|
709
|
+
};
|
|
710
|
+
});
|
|
711
|
+
const totals = sourceCounts.reduce((sum, count) => ({
|
|
712
|
+
scanned_count: sum.scanned_count + count.scanned_count,
|
|
713
|
+
included_count: sum.included_count + count.included_count,
|
|
714
|
+
skipped_count: sum.skipped_count + count.skipped_count,
|
|
715
|
+
truncated_count: sum.truncated_count + count.truncated_count,
|
|
716
|
+
deferred_count: sum.deferred_count + count.deferred_count,
|
|
717
|
+
reused_count: sum.reused_count + count.reused_count,
|
|
718
|
+
}), {
|
|
719
|
+
scanned_count: 0,
|
|
720
|
+
included_count: 0,
|
|
721
|
+
skipped_count: 0,
|
|
722
|
+
truncated_count: 0,
|
|
723
|
+
deferred_count: 0,
|
|
724
|
+
reused_count: 0,
|
|
725
|
+
});
|
|
726
|
+
const skipReasonCounts = new Map();
|
|
727
|
+
for (const skipped of collection.skipped) {
|
|
728
|
+
const key = `${skipped.kind}:${skipped.reason}`;
|
|
729
|
+
const existing = skipReasonCounts.get(key);
|
|
730
|
+
if (existing) {
|
|
731
|
+
existing.count += 1;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
skipReasonCounts.set(key, {
|
|
735
|
+
source: skipped.kind,
|
|
736
|
+
reason: skipped.reason,
|
|
737
|
+
count: 1,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const truncationCounts = new Map();
|
|
742
|
+
for (const truncated of collection.truncated) {
|
|
743
|
+
const key = `${truncated.kind}:${truncated.reason}`;
|
|
744
|
+
const existing = truncationCounts.get(key);
|
|
745
|
+
if (existing) {
|
|
746
|
+
existing.count += 1;
|
|
747
|
+
existing.observed_bytes = Math.max(existing.observed_bytes ?? 0, truncated.observed_bytes ?? 0);
|
|
748
|
+
existing.included_bytes = Math.max(existing.included_bytes ?? 0, truncated.included_bytes ?? 0);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
truncationCounts.set(key, {
|
|
752
|
+
source: truncated.kind,
|
|
753
|
+
reason: truncated.reason,
|
|
754
|
+
count: 1,
|
|
755
|
+
...(truncated.max_bytes !== undefined
|
|
756
|
+
? { max_bytes: truncated.max_bytes }
|
|
757
|
+
: {}),
|
|
758
|
+
...(truncated.observed_bytes !== undefined
|
|
759
|
+
? { observed_bytes: truncated.observed_bytes }
|
|
760
|
+
: {}),
|
|
761
|
+
...(truncated.included_bytes !== undefined
|
|
762
|
+
? { included_bytes: truncated.included_bytes }
|
|
763
|
+
: {}),
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const hasGaps = totals.skipped_count > 0 ||
|
|
768
|
+
totals.truncated_count > 0 ||
|
|
769
|
+
totals.deferred_count > 0 ||
|
|
770
|
+
collection.caps.some((cap) => cap.applied);
|
|
771
|
+
const status = totals.included_count + totals.reused_count === 0 && !hasGaps
|
|
772
|
+
? "empty"
|
|
773
|
+
: hasGaps
|
|
774
|
+
? "partial"
|
|
775
|
+
: "complete";
|
|
776
|
+
return EvidenceCompletenessPayloadSchema.parse({
|
|
777
|
+
schema_version: "evidence-completeness.v1",
|
|
778
|
+
status,
|
|
779
|
+
generated_at: options.finishedAt,
|
|
780
|
+
scan_window: {
|
|
781
|
+
started_at: options.startedAt,
|
|
782
|
+
finished_at: options.finishedAt,
|
|
783
|
+
since_minutes: options.sinceMinutes,
|
|
784
|
+
},
|
|
785
|
+
source_counts: sourceCounts,
|
|
786
|
+
totals,
|
|
787
|
+
caps: collection.caps,
|
|
788
|
+
skip_reasons: [...skipReasonCounts.values()].sort((a, b) => `${a.source}:${a.reason}`.localeCompare(`${b.source}:${b.reason}`)),
|
|
789
|
+
truncation_markers: [...truncationCounts.values()].sort((a, b) => `${a.source}:${a.reason}`.localeCompare(`${b.source}:${b.reason}`)),
|
|
790
|
+
notes: hasGaps
|
|
791
|
+
? ["Evidence is incomplete; downstream analysis should lower confidence."]
|
|
792
|
+
: [],
|
|
533
793
|
});
|
|
534
|
-
return stdout;
|
|
535
794
|
}
|
|
536
795
|
async function walkJsonlFiles(dir, cutoffMs) {
|
|
537
796
|
const out = [];
|
package/dist/agent-rules.js
CHANGED
|
@@ -15,7 +15,7 @@ export async function installAgentRules(options = {}) {
|
|
|
15
15
|
}
|
|
16
16
|
async function installAgentRulesForHost(host, options = {}) {
|
|
17
17
|
const rulesFile = agentRulesFile(host, options.homeDir);
|
|
18
|
-
const block = cockpitAgentRulesBlock();
|
|
18
|
+
const block = cockpitAgentRulesBlock({ scopePath: options.scopePath });
|
|
19
19
|
let existing = "";
|
|
20
20
|
let existed = true;
|
|
21
21
|
try {
|
|
@@ -24,13 +24,17 @@ async function installAgentRulesForHost(host, options = {}) {
|
|
|
24
24
|
catch {
|
|
25
25
|
existed = false;
|
|
26
26
|
}
|
|
27
|
-
const
|
|
27
|
+
const prepared = prepareManagedBlockInstall(existing, block, options.scopePath);
|
|
28
|
+
if (!prepared.next) {
|
|
29
|
+
return agentRulesResult(host, rulesFile, "unchanged", block, prepared.state);
|
|
30
|
+
}
|
|
31
|
+
const next = prepared.next;
|
|
28
32
|
if (next === existing) {
|
|
29
|
-
return agentRulesResult(host, rulesFile, "unchanged", block);
|
|
33
|
+
return agentRulesResult(host, rulesFile, "unchanged", block, prepared.state);
|
|
30
34
|
}
|
|
31
35
|
await mkdir(path.dirname(rulesFile), { recursive: true });
|
|
32
36
|
await writeFile(rulesFile, next, "utf8");
|
|
33
|
-
return agentRulesResult(host, rulesFile, existed ? "updated" : "created", block);
|
|
37
|
+
return agentRulesResult(host, rulesFile, existed ? "updated" : "created", block, "managed", prepared.state === "stale");
|
|
34
38
|
}
|
|
35
39
|
export async function uninstallCodexAgentRules(options = {}) {
|
|
36
40
|
return uninstallAgentRulesForHost("codex", options);
|
|
@@ -50,14 +54,14 @@ async function uninstallAgentRulesForHost(host, options = {}) {
|
|
|
50
54
|
existing = await readFile(rulesFile, "utf8");
|
|
51
55
|
}
|
|
52
56
|
catch {
|
|
53
|
-
return agentRulesResult(host, rulesFile, "missing", block);
|
|
57
|
+
return agentRulesResult(host, rulesFile, "missing", block, "missing");
|
|
54
58
|
}
|
|
55
59
|
const next = removeManagedBlock(existing);
|
|
56
60
|
if (next === existing) {
|
|
57
|
-
return agentRulesResult(host, rulesFile, "missing", block);
|
|
61
|
+
return agentRulesResult(host, rulesFile, "missing", block, "missing");
|
|
58
62
|
}
|
|
59
63
|
await writeFile(rulesFile, next, "utf8");
|
|
60
|
-
return agentRulesResult(host, rulesFile, "updated", block);
|
|
64
|
+
return agentRulesResult(host, rulesFile, "updated", block, "missing");
|
|
61
65
|
}
|
|
62
66
|
export async function inspectCodexAgentRules(options = {}) {
|
|
63
67
|
return inspectAgentRulesForHost("codex", options);
|
|
@@ -74,30 +78,39 @@ export async function inspectAgentRules(options = {}) {
|
|
|
74
78
|
}
|
|
75
79
|
async function inspectAgentRulesForHost(host, options = {}) {
|
|
76
80
|
const rulesFile = agentRulesFile(host, options.homeDir);
|
|
77
|
-
const block = cockpitAgentRulesBlock();
|
|
81
|
+
const block = cockpitAgentRulesBlock({ scopePath: options.scopePath });
|
|
78
82
|
let existing = "";
|
|
79
83
|
try {
|
|
80
84
|
existing = await readFile(rulesFile, "utf8");
|
|
81
85
|
}
|
|
82
86
|
catch {
|
|
83
|
-
return {
|
|
87
|
+
return {
|
|
88
|
+
...agentRulesResult(host, rulesFile, "missing", block, "missing"),
|
|
89
|
+
installed: false,
|
|
90
|
+
};
|
|
84
91
|
}
|
|
85
|
-
const
|
|
92
|
+
const state = inspectAgentRulesContents(existing, block, options.scopePath);
|
|
93
|
+
const installed = state === "managed" || state === "equivalent";
|
|
86
94
|
return {
|
|
87
|
-
...agentRulesResult(host, rulesFile, installed ? "unchanged" : "missing", block),
|
|
95
|
+
...agentRulesResult(host, rulesFile, installed ? "unchanged" : "missing", block, state),
|
|
88
96
|
installed,
|
|
89
97
|
};
|
|
90
98
|
}
|
|
91
|
-
export function cockpitAgentRulesBlock() {
|
|
99
|
+
export function cockpitAgentRulesBlock(options = {}) {
|
|
100
|
+
const scopePath = options.scopePath ? path.resolve(options.scopePath) : null;
|
|
101
|
+
const scopeLine = scopePath
|
|
102
|
+
? `- This guidance only applies when the current working directory is inside the Cockpit-onboarded workspace/repo: \`${scopePath}\`. Outside that folder, do not run Cockpit ticket binding or sync commands for private chats or unrelated repos.`
|
|
103
|
+
: "- This guidance only applies when the current working directory is inside the workspace/repo that ran `cockpit onboard` or `cockpit agent-rules install`. Outside that folder, do not run Cockpit ticket binding or sync commands for private chats or unrelated repos.";
|
|
92
104
|
return [
|
|
93
105
|
MANAGED_BLOCK_START,
|
|
94
106
|
"## Cockpit Ticket Binding",
|
|
95
107
|
"",
|
|
96
|
-
|
|
108
|
+
scopeLine,
|
|
109
|
+
"- For implementation, debugging, review, PR, or ship work tied to a clear Linear ticket, run `cockpit start --ticket <ticket-id> --workspace \"$PWD\"` before the first code edit or mutating tool call. This starts attributing the session's work to that specific ticket in Cockpit.",
|
|
97
110
|
"- Use `--ticket`; do not invent `--ticketId` or other flag shapes.",
|
|
98
111
|
"- If the user mentions ticketed work but no ticket ID is visible, ask once for the Linear ticket ID before editing. Agents cannot reliably infer it from context.",
|
|
99
112
|
"- If there is truly no ticket, state that the work remains in general ambient capture and do not invent a ticket.",
|
|
100
|
-
"- After the first meaningful checkpoint, run `cockpit sync --
|
|
113
|
+
"- After the first meaningful checkpoint, run `cockpit sync --workspace \"$PWD\" --json` so Cockpit has fresh ticket/session binding metadata.",
|
|
101
114
|
MANAGED_BLOCK_END,
|
|
102
115
|
].join("\n");
|
|
103
116
|
}
|
|
@@ -118,19 +131,52 @@ export function removeManagedBlock(contents) {
|
|
|
118
131
|
return contents;
|
|
119
132
|
return contents.replace(managedBlockPattern(), "").replace(/\n{3,}/gu, "\n\n").trimEnd() + "\n";
|
|
120
133
|
}
|
|
134
|
+
function prepareManagedBlockInstall(contents, block, scopePath) {
|
|
135
|
+
if (!contents.trim())
|
|
136
|
+
return { next: `${block}\n`, state: "missing" };
|
|
137
|
+
if (hasManagedBlock(contents)) {
|
|
138
|
+
return {
|
|
139
|
+
next: upsertManagedBlock(contents, block),
|
|
140
|
+
state: extractManagedBlock(contents) === block ? "managed" : "stale",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (hasEquivalentUnmanagedTicketBinding(contents, scopePath)) {
|
|
144
|
+
return { next: null, state: "equivalent" };
|
|
145
|
+
}
|
|
146
|
+
const staleBlock = findStaleUnmanagedTicketBindingBlock(contents, scopePath);
|
|
147
|
+
if (staleBlock) {
|
|
148
|
+
return {
|
|
149
|
+
next: replaceLineSpan(contents, staleBlock.startLine, staleBlock.endLine, block),
|
|
150
|
+
state: "stale",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return { next: `${contents.replace(/\s+$/u, "")}\n\n${block}\n`, state: "missing" };
|
|
154
|
+
}
|
|
155
|
+
function inspectAgentRulesContents(contents, block, scopePath) {
|
|
156
|
+
if (hasManagedBlock(contents)) {
|
|
157
|
+
return extractManagedBlock(contents) === block ? "managed" : "stale";
|
|
158
|
+
}
|
|
159
|
+
if (hasEquivalentUnmanagedTicketBinding(contents, scopePath))
|
|
160
|
+
return "equivalent";
|
|
161
|
+
if (findStaleUnmanagedTicketBindingBlock(contents, scopePath))
|
|
162
|
+
return "stale";
|
|
163
|
+
return "missing";
|
|
164
|
+
}
|
|
121
165
|
function agentRulesFile(host, homeDir = os.homedir()) {
|
|
122
166
|
if (host === "claude") {
|
|
123
167
|
return path.join(homeDir, ".claude", "CLAUDE.md");
|
|
124
168
|
}
|
|
125
169
|
return path.join(homeDir, ".codex", "AGENTS.md");
|
|
126
170
|
}
|
|
127
|
-
function agentRulesResult(host, rulesFile, status, block) {
|
|
171
|
+
function agentRulesResult(host, rulesFile, status, block, state, staleBlockReplaced = false) {
|
|
128
172
|
return {
|
|
129
173
|
host,
|
|
130
174
|
status,
|
|
131
175
|
rules_file: rulesFile,
|
|
132
176
|
agents_file: rulesFile,
|
|
133
177
|
block,
|
|
178
|
+
state,
|
|
179
|
+
...(staleBlockReplaced ? { stale_block_replaced: true } : {}),
|
|
134
180
|
};
|
|
135
181
|
}
|
|
136
182
|
function targetHosts(hosts) {
|
|
@@ -151,6 +197,78 @@ function aggregateAgentRulesResult(targets) {
|
|
|
151
197
|
function managedBlockPattern() {
|
|
152
198
|
return new RegExp(`${escapeRegExp(MANAGED_BLOCK_START)}[\\s\\S]*?${escapeRegExp(MANAGED_BLOCK_END)}`, "u");
|
|
153
199
|
}
|
|
200
|
+
function extractManagedBlock(contents) {
|
|
201
|
+
return contents.match(managedBlockPattern())?.[0] ?? null;
|
|
202
|
+
}
|
|
203
|
+
function hasEquivalentUnmanagedTicketBinding(contents, scopePath) {
|
|
204
|
+
const text = normalizeRuleText(contents);
|
|
205
|
+
if (!hasTicketBindingCues(text))
|
|
206
|
+
return false;
|
|
207
|
+
if (!hasRepoScopeGuard(text))
|
|
208
|
+
return false;
|
|
209
|
+
if (scopePath && !hasScopePath(text, scopePath))
|
|
210
|
+
return false;
|
|
211
|
+
const signals = [
|
|
212
|
+
/cockpit\s+start\s+--ticket\b/u,
|
|
213
|
+
/before\s+(?:the\s+)?first\s+code\s+edit|before\s+ticketed\s+implementation/u,
|
|
214
|
+
/ticket\s+id\s+(?:is\s+)?(?:missing|visible)|ask\s+once/u,
|
|
215
|
+
/general\s+ambient/u,
|
|
216
|
+
/cockpit\s+sync\s+--repo|fresh\s+ticket\/session\s+binding\s+metadata/u,
|
|
217
|
+
/use\s+--ticket|do\s+not\s+invent\s+--ticketid/u,
|
|
218
|
+
];
|
|
219
|
+
const score = signals.filter((signal) => signal.test(text)).length;
|
|
220
|
+
return score >= 5;
|
|
221
|
+
}
|
|
222
|
+
function hasScopePath(text, scopePath) {
|
|
223
|
+
return text.includes(normalizeRuleText(path.resolve(scopePath)));
|
|
224
|
+
}
|
|
225
|
+
function hasRepoScopeGuard(text) {
|
|
226
|
+
return (/only\s+applies\s+when\s+the\s+current\s+working\s+directory\s+is\s+inside/u.test(text) ||
|
|
227
|
+
/outside\s+that\s+(?:folder|workspace|repo).*(?:do\s+not|dont)\s+run\s+cockpit/u.test(text) ||
|
|
228
|
+
/private\s+chats\s+or\s+unrelated\s+repos/u.test(text));
|
|
229
|
+
}
|
|
230
|
+
function findStaleUnmanagedTicketBindingBlock(contents, scopePath) {
|
|
231
|
+
const lines = contents.split("\n");
|
|
232
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
233
|
+
if (!/^#{1,6}\s+.*(?:cockpit\s+)?ticket\s+binding\b/iu.test(lines[index] ?? "")) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
let endLine = lines.length;
|
|
237
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
238
|
+
if (/^#{1,6}\s+\S/u.test(lines[cursor] ?? "")) {
|
|
239
|
+
endLine = cursor;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const candidate = lines.slice(index, endLine).join("\n");
|
|
244
|
+
const normalized = normalizeRuleText(candidate);
|
|
245
|
+
if (hasTicketBindingCues(normalized) &&
|
|
246
|
+
!hasEquivalentUnmanagedTicketBinding(candidate, scopePath)) {
|
|
247
|
+
return { startLine: index, endLine };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
function replaceLineSpan(contents, startLine, endLine, replacement) {
|
|
253
|
+
const lines = contents.split("\n");
|
|
254
|
+
const before = lines.slice(0, startLine).join("\n").trimEnd();
|
|
255
|
+
const after = lines.slice(endLine).join("\n").trimStart();
|
|
256
|
+
return [before, replacement, after]
|
|
257
|
+
.filter((part) => part.trim().length > 0)
|
|
258
|
+
.join("\n\n")
|
|
259
|
+
.replace(/\n{3,}/gu, "\n\n")
|
|
260
|
+
.trimEnd() + "\n";
|
|
261
|
+
}
|
|
262
|
+
function hasTicketBindingCues(text) {
|
|
263
|
+
return /\bcockpit\b/u.test(text) && /\bticket\b/u.test(text) && /binding|agent|linear/u.test(text);
|
|
264
|
+
}
|
|
265
|
+
function normalizeRuleText(contents) {
|
|
266
|
+
return contents
|
|
267
|
+
.toLowerCase()
|
|
268
|
+
.replace(/[`"'<>]/gu, "")
|
|
269
|
+
.replace(/\s+/gu, " ")
|
|
270
|
+
.trim();
|
|
271
|
+
}
|
|
154
272
|
function escapeRegExp(value) {
|
|
155
273
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
156
274
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// Behavior-preserving extraction: functions moved verbatim, no logic change.
|
|
6
6
|
import { DEFAULT_DASHBOARD_URL } from "../local-state.js";
|
|
7
7
|
import { DEFAULT_AUTOSTART_INTERVAL_SECONDS } from "../autostart.js";
|
|
8
|
+
import { IntentSourceSchema, WorkIntentSchema, WorkPhaseSchema, } from "@bli-cockpit/telemetry-core";
|
|
8
9
|
const WORK_ROOT_FLAGS = ["--repo", "--workspace"];
|
|
9
10
|
export function parseLocalArgs(argv) {
|
|
10
11
|
const command = argv[0];
|
|
@@ -165,6 +166,12 @@ function parseStartArgs(args) {
|
|
|
165
166
|
"--workspace",
|
|
166
167
|
"--branch",
|
|
167
168
|
"--ticket",
|
|
169
|
+
"--topic",
|
|
170
|
+
"--topic-summary",
|
|
171
|
+
"--intent",
|
|
172
|
+
"--phase",
|
|
173
|
+
"--intent-source",
|
|
174
|
+
"--intent-confidence",
|
|
168
175
|
"--operator-id",
|
|
169
176
|
"--session-id",
|
|
170
177
|
"--json",
|
|
@@ -177,6 +184,12 @@ function parseStartArgs(args) {
|
|
|
177
184
|
"--workspace",
|
|
178
185
|
"--branch",
|
|
179
186
|
"--ticket",
|
|
187
|
+
"--topic",
|
|
188
|
+
"--topic-summary",
|
|
189
|
+
"--intent",
|
|
190
|
+
"--phase",
|
|
191
|
+
"--intent-source",
|
|
192
|
+
"--intent-confidence",
|
|
180
193
|
"--operator-id",
|
|
181
194
|
"--session-id",
|
|
182
195
|
"--max-depth",
|
|
@@ -184,12 +197,28 @@ function parseStartArgs(args) {
|
|
|
184
197
|
],
|
|
185
198
|
});
|
|
186
199
|
assertNoPositionals(values.positionals, "start");
|
|
200
|
+
const topicLabel = optionalNonEmpty(values.flags.get("--topic"));
|
|
201
|
+
const topicSummaryRedacted = optionalNonEmpty(values.flags.get("--topic-summary"));
|
|
202
|
+
const workIntent = optionalSchemaValue(WorkIntentSchema, values.flags.get("--intent"), "--intent");
|
|
203
|
+
const workPhase = optionalSchemaValue(WorkPhaseSchema, values.flags.get("--phase"), "--phase");
|
|
204
|
+
const intentConfidence = optionalConfidence(values.flags.get("--intent-confidence"), "--intent-confidence");
|
|
205
|
+
const explicitIntentMetadata = Boolean(topicLabel ||
|
|
206
|
+
topicSummaryRedacted ||
|
|
207
|
+
workIntent ||
|
|
208
|
+
workPhase ||
|
|
209
|
+
intentConfidence !== undefined);
|
|
187
210
|
return {
|
|
188
211
|
kind: "start",
|
|
189
212
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
190
213
|
repoRoot: optionalNonEmpty(workRootFlagValue(values)),
|
|
191
214
|
branch: optionalNonEmpty(values.flags.get("--branch")),
|
|
192
215
|
activeTicketId: optionalNonEmpty(values.flags.get("--ticket")),
|
|
216
|
+
topicLabel,
|
|
217
|
+
topicSummaryRedacted,
|
|
218
|
+
workIntent,
|
|
219
|
+
workPhase,
|
|
220
|
+
intentSource: optionalSchemaValue(IntentSourceSchema, values.flags.get("--intent-source"), "--intent-source") ?? (explicitIntentMetadata ? "explicit_user" : undefined),
|
|
221
|
+
intentConfidence,
|
|
193
222
|
operatorId: optionalNonEmpty(values.flags.get("--operator-id")),
|
|
194
223
|
sessionId: optionalNonEmpty(values.flags.get("--session-id")),
|
|
195
224
|
json: values.booleans.has("--json"),
|
|
@@ -345,8 +374,8 @@ function parseAutostartArgs(args) {
|
|
|
345
374
|
}
|
|
346
375
|
function parseAgentRulesArgs(args) {
|
|
347
376
|
const values = parseNamedArgs(args, {
|
|
348
|
-
allowedFlags: ["--home", "--host", "--json"],
|
|
349
|
-
valueFlags: ["--home", "--host"],
|
|
377
|
+
allowedFlags: ["--home", "--host", "--repo", "--workspace", "--json"],
|
|
378
|
+
valueFlags: ["--home", "--host", "--repo", "--workspace"],
|
|
350
379
|
});
|
|
351
380
|
if (values.positionals.length > 1) {
|
|
352
381
|
throw new Error("agent-rules accepts at most one action (install|uninstall|status).");
|
|
@@ -360,6 +389,7 @@ function parseAgentRulesArgs(args) {
|
|
|
360
389
|
action,
|
|
361
390
|
host: parseAgentRulesHost(values.flags.get("--host")),
|
|
362
391
|
homeDir: optionalNonEmpty(values.flags.get("--home")),
|
|
392
|
+
repoRoot: optionalNonEmpty(workRootFlagValue(values)),
|
|
363
393
|
json: values.booleans.has("--json"),
|
|
364
394
|
};
|
|
365
395
|
}
|
|
@@ -443,6 +473,25 @@ function optionalPositiveInteger(value, flag) {
|
|
|
443
473
|
}
|
|
444
474
|
return parsed;
|
|
445
475
|
}
|
|
476
|
+
function optionalConfidence(value, flag) {
|
|
477
|
+
if (value === undefined)
|
|
478
|
+
return undefined;
|
|
479
|
+
const parsed = Number(value);
|
|
480
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
481
|
+
throw new Error(`${flag} must be a number between 0 and 1.`);
|
|
482
|
+
}
|
|
483
|
+
return parsed;
|
|
484
|
+
}
|
|
485
|
+
function optionalSchemaValue(schema, value, flag) {
|
|
486
|
+
const trimmed = optionalNonEmpty(value);
|
|
487
|
+
if (!trimmed)
|
|
488
|
+
return undefined;
|
|
489
|
+
const parsed = schema.safeParse(trimmed);
|
|
490
|
+
if (!parsed.success || parsed.data === undefined) {
|
|
491
|
+
throw new Error(`${flag} has an unsupported value.`);
|
|
492
|
+
}
|
|
493
|
+
return parsed.data;
|
|
494
|
+
}
|
|
446
495
|
export function normalizeUrl(value) {
|
|
447
496
|
const trimmed = value.trim().replace(/\/+$/, "");
|
|
448
497
|
if (!trimmed)
|
package/dist/commands/local.js
CHANGED
|
@@ -80,13 +80,13 @@ export function localCommandHelp(command) {
|
|
|
80
80
|
" cockpit login [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--json]",
|
|
81
81
|
" cockpit pair [--email <owner@email>] [--device-name <name>] [--dashboard-url <url>] [--json]",
|
|
82
82
|
" cockpit logout",
|
|
83
|
-
" cockpit start [--ticket <id>] [--workspace <path>] [--branch <name>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
83
|
+
" cockpit start [--ticket <id>] [--topic <label>] [--intent <intent>] [--phase <phase>] [--workspace <path>] [--branch <name>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
84
84
|
" cockpit sync [--workspace <path>] [--dashboard-url <url>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
85
85
|
" cockpit status [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
86
86
|
" cockpit sessions [--source codex|claude] [--workspace <path>] [--max-depth <n>] [--max-repos <n>] [--json]",
|
|
87
87
|
" cockpit serve [--port <port>] [--workspace <path>]",
|
|
88
88
|
" cockpit autostart [install|uninstall|status] [--workspace <path>] [--dashboard-url <url>] [--interval-seconds <n>] [--json]",
|
|
89
|
-
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--json]",
|
|
89
|
+
" cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
|
|
90
90
|
"",
|
|
91
91
|
`Default dashboard: ${DEFAULT_DASHBOARD_URL}. Omit --dashboard-url for normal production use; pass it only for staging/custom dashboards or to force a different pairing.`,
|
|
92
92
|
].join("\n");
|
|
@@ -104,6 +104,7 @@ function localSubcommandHelp(command) {
|
|
|
104
104
|
`Omit --dashboard-url for normal production setup (${DEFAULT_DASHBOARD_URL}).`,
|
|
105
105
|
"Pass --dashboard-url only for staging/custom dashboards or to force a different pairing.",
|
|
106
106
|
"Run with no flags in a terminal and it prompts for the dashboard email; pass --email to skip the prompt (and on shared/reused machines, where mismatched sessions are re-paired).",
|
|
107
|
+
"Interactive runs also offer to add Cockpit ticket-binding rules to AGENTS.md and CLAUDE.md after readiness proof.",
|
|
107
108
|
],
|
|
108
109
|
],
|
|
109
110
|
[
|
|
@@ -137,11 +138,14 @@ function localSubcommandHelp(command) {
|
|
|
137
138
|
[
|
|
138
139
|
"start",
|
|
139
140
|
[
|
|
140
|
-
"Usage: cockpit start [--ticket <id>] [--workspace <path>] [--branch <name>] [--json]",
|
|
141
|
+
"Usage: cockpit start [--ticket <id>] [--topic <label>] [--topic-summary <summary>] [--intent <intent>] [--phase <phase>] [--intent-confidence <0..1>] [--workspace <path>] [--branch <name>] [--json]",
|
|
141
142
|
"",
|
|
142
143
|
"Starts local ambient capture. Parent folders start each child git worktree.",
|
|
143
144
|
"Add --ticket only when the work already has a visible ticket.",
|
|
144
|
-
"
|
|
145
|
+
"Use --topic/--intent/--phase for planning, discovery, and learning work that has no ticket yet.",
|
|
146
|
+
"Supported intents: implementation, bug_fix, root_cause_analysis, planning, discovery, review, testing, documentation, release, learning, coordination, maintenance, analysis, unknown, other.",
|
|
147
|
+
"Supported phases: planning, discovery, implementation, debugging, review, testing, documentation, release, handoff, analysis, unknown, other.",
|
|
148
|
+
"`--repo <path>` remains supported as a backward-compatible alias; use --workspace in agent guidance.",
|
|
145
149
|
],
|
|
146
150
|
],
|
|
147
151
|
[
|
|
@@ -208,13 +212,12 @@ function localSubcommandHelp(command) {
|
|
|
208
212
|
[
|
|
209
213
|
"agent-rules",
|
|
210
214
|
[
|
|
211
|
-
"Usage: cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--json]",
|
|
215
|
+
"Usage: cockpit agent-rules [install|uninstall|status] [--host codex|claude|all] [--workspace <path>] [--json]",
|
|
212
216
|
"",
|
|
213
217
|
"Installs a managed Cockpit Ticket Binding block into ~/.codex/AGENTS.md",
|
|
214
218
|
"and ~/.claude/CLAUDE.md by default. Pass --host to manage only one.",
|
|
215
|
-
"
|
|
216
|
-
"
|
|
217
|
-
"once when the ticket ID is missing.",
|
|
219
|
+
"The block is scoped to --workspace, or the current directory when omitted,",
|
|
220
|
+
"so Codex and Claude only run Cockpit ticket binding inside that onboarded folder.",
|
|
218
221
|
"Action defaults to `install`.",
|
|
219
222
|
],
|
|
220
223
|
],
|
|
@@ -310,6 +313,52 @@ async function maybeOfferAutostart(command, io) {
|
|
|
310
313
|
: "Background autostart installed, but launchctl load reported a problem; check `cockpit autostart status`.");
|
|
311
314
|
writeLine(io.stdout, `Plist: ${result.plist_path}`);
|
|
312
315
|
}
|
|
316
|
+
async function maybeOfferAgentRules(command, io) {
|
|
317
|
+
if (command.json || !isInteractiveStdin(io))
|
|
318
|
+
return;
|
|
319
|
+
const scopePath = path.resolve(command.repoRoot ?? process.cwd());
|
|
320
|
+
const current = await inspectAgentRules({
|
|
321
|
+
homeDir: command.homeDir,
|
|
322
|
+
scopePath,
|
|
323
|
+
});
|
|
324
|
+
if (current.installed) {
|
|
325
|
+
writeLine(io.stdout, onboardAgentRulesAlreadyInstalledLine(current));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const answer = (await readLine(io, `Add Cockpit ticket-binding rules scoped to ${scopePath} to AGENTS.md and CLAUDE.md? [Y/n] `))
|
|
329
|
+
.trim()
|
|
330
|
+
.toLowerCase();
|
|
331
|
+
if (answer === "n" || answer === "no") {
|
|
332
|
+
writeLine(io.stdout, "Skipped agent rules. Run `cockpit agent-rules install --workspace \"$PWD\"` anytime.");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const result = await installAgentRules({ homeDir: command.homeDir, scopePath });
|
|
336
|
+
writeLine(io.stdout, `Agent rules: ${onboardAgentRulesInstallLine(result)}`);
|
|
337
|
+
for (const target of result.targets) {
|
|
338
|
+
writeLine(io.stdout, `${agentRuleHostLabel(target.host)}: ${target.rules_file}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function onboardAgentRulesAlreadyInstalledLine(result) {
|
|
342
|
+
const hasEquivalent = result.targets.some((target) => target.state === "equivalent");
|
|
343
|
+
return hasEquivalent
|
|
344
|
+
? "Agent rules: matching Cockpit ticket-binding guidance already exists."
|
|
345
|
+
: "Agent rules: already current in AGENTS.md and CLAUDE.md.";
|
|
346
|
+
}
|
|
347
|
+
function onboardAgentRulesInstallLine(result) {
|
|
348
|
+
if (result.targets.some((target) => target.stale_block_replaced)) {
|
|
349
|
+
return "updated; replaced stale Cockpit ticket-binding guidance.";
|
|
350
|
+
}
|
|
351
|
+
switch (result.status) {
|
|
352
|
+
case "created":
|
|
353
|
+
return "installed.";
|
|
354
|
+
case "updated":
|
|
355
|
+
return "updated.";
|
|
356
|
+
case "unchanged":
|
|
357
|
+
return "already current.";
|
|
358
|
+
case "missing":
|
|
359
|
+
return "not installed.";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
313
362
|
async function runOnboard(command, io) {
|
|
314
363
|
let install = null;
|
|
315
364
|
let pair = null;
|
|
@@ -383,8 +432,10 @@ async function runOnboard(command, io) {
|
|
|
383
432
|
codex_sessions: multi.codex_sessions,
|
|
384
433
|
}, null, 2));
|
|
385
434
|
}
|
|
386
|
-
if (multi.ok)
|
|
435
|
+
if (multi.ok) {
|
|
436
|
+
await maybeOfferAgentRules(command, io);
|
|
387
437
|
await maybeOfferAutostart(command, io);
|
|
438
|
+
}
|
|
388
439
|
return multi.ok ? 0 : 1;
|
|
389
440
|
}
|
|
390
441
|
const context = await startLocalWorkContext({
|
|
@@ -449,6 +500,7 @@ async function runOnboard(command, io) {
|
|
|
449
500
|
writeLine(io.stdout, `Upload state: ${status.upload_state}`);
|
|
450
501
|
writeLine(io.stdout, "PASS: Cockpit collector is ready for harvest.");
|
|
451
502
|
writeLine(io.stdout, `Open: ${command.dashboardUrl}/my-work`);
|
|
503
|
+
await maybeOfferAgentRules(command, io);
|
|
452
504
|
await maybeOfferAutostart(command, io);
|
|
453
505
|
return 0;
|
|
454
506
|
}
|
|
@@ -730,7 +782,7 @@ function nextStepForOnboardBlocker(blocker) {
|
|
|
730
782
|
case "install":
|
|
731
783
|
return "Rerun `cockpit onboard` from the repo root; it will reinstall local config.";
|
|
732
784
|
case "work_context":
|
|
733
|
-
return "Run `cockpit start --ticket <id> --
|
|
785
|
+
return "Run `cockpit start --ticket <id> --workspace \"$PWD\"`, then retry `cockpit sync`.";
|
|
734
786
|
default:
|
|
735
787
|
return "Run `cockpit status --json` and report the blocker label plus last failure reason.";
|
|
736
788
|
}
|
|
@@ -754,6 +806,12 @@ async function runStart(command, io) {
|
|
|
754
806
|
repoRoot: worktree.repo_root,
|
|
755
807
|
branch: command.branch,
|
|
756
808
|
activeTicketId: command.activeTicketId,
|
|
809
|
+
topicLabel: command.topicLabel,
|
|
810
|
+
topicSummaryRedacted: command.topicSummaryRedacted,
|
|
811
|
+
workIntent: command.workIntent,
|
|
812
|
+
workPhase: command.workPhase,
|
|
813
|
+
intentSource: command.intentSource,
|
|
814
|
+
intentConfidence: command.intentConfidence,
|
|
757
815
|
operatorId: command.operatorId,
|
|
758
816
|
sessionId: command.sessionId,
|
|
759
817
|
})));
|
|
@@ -776,6 +834,9 @@ async function runStart(command, io) {
|
|
|
776
834
|
writeLine(io.stdout, `Repo: ${context.repo}`);
|
|
777
835
|
writeLine(io.stdout, `Branch: ${context.branch}`);
|
|
778
836
|
writeLine(io.stdout, `Ticket: ${displayTicketId(context.active_ticket_id)}`);
|
|
837
|
+
if (context.topic_label || context.work_intent || context.work_phase) {
|
|
838
|
+
writeLine(io.stdout, `Topic: ${context.topic_label ?? "unlabeled"} · ${context.work_intent ?? "unknown"} · ${context.work_phase ?? "unknown"}`);
|
|
839
|
+
}
|
|
779
840
|
writeLine(io.stdout, `Context: ${context.work_context_id}`);
|
|
780
841
|
return 0;
|
|
781
842
|
}
|
|
@@ -1035,11 +1096,12 @@ async function runAutostart(command, io) {
|
|
|
1035
1096
|
}
|
|
1036
1097
|
async function runAgentRules(command, io) {
|
|
1037
1098
|
const hosts = agentRuleHosts(command.host);
|
|
1099
|
+
const scopePath = path.resolve(command.repoRoot ?? process.cwd());
|
|
1038
1100
|
const result = command.action === "install"
|
|
1039
|
-
? await installAgentRules({ homeDir: command.homeDir, hosts })
|
|
1101
|
+
? await installAgentRules({ homeDir: command.homeDir, hosts, scopePath })
|
|
1040
1102
|
: command.action === "uninstall"
|
|
1041
1103
|
? await uninstallAgentRules({ homeDir: command.homeDir, hosts })
|
|
1042
|
-
: await inspectAgentRules({ homeDir: command.homeDir, hosts });
|
|
1104
|
+
: await inspectAgentRules({ homeDir: command.homeDir, hosts, scopePath });
|
|
1043
1105
|
if (command.json) {
|
|
1044
1106
|
writeLine(io.stdout, JSON.stringify(result, null, 2));
|
|
1045
1107
|
return 0;
|
|
@@ -25,9 +25,9 @@ function cockpitHelp() {
|
|
|
25
25
|
"Install/update: `npm install -g @bli-cockpit/cli@latest`.",
|
|
26
26
|
"Intern path: run `cockpit onboard` from the repo root; add `--ticket <id>` only when work already has a ticket.",
|
|
27
27
|
"Already onboarded: run `cockpit sync --workspace \"$PWD\" --json`.",
|
|
28
|
-
"Agent setup:
|
|
28
|
+
"Agent setup: interactive `cockpit onboard` offers AGENTS.md/CLAUDE.md rules; use `cockpit agent-rules install --workspace \"$PWD\"` for repair/headless setup.",
|
|
29
29
|
"Dashboard URL is optional for normal production use; pass `--dashboard-url` only for staging/custom dashboards or forced re-pairing.",
|
|
30
|
-
"Manual collector path: `install`, `login`, `start [--ticket <id>]`, `sync`, `status`, `agent-rules`.",
|
|
30
|
+
"Manual collector path: `install`, `login`, `start [--ticket <id>] [--topic <label>] [--intent <intent>] [--phase <phase>]`, `sync`, `status`, `agent-rules`.",
|
|
31
31
|
].join("\n");
|
|
32
32
|
}
|
|
33
33
|
|
package/dist/local-state.js
CHANGED
|
@@ -170,6 +170,12 @@ export async function startLocalWorkContext(options = {}) {
|
|
|
170
170
|
updated_at: now.toISOString(),
|
|
171
171
|
active_ticket_id: options.activeTicketId ?? undefined,
|
|
172
172
|
ticket_binding_candidates: ticketBindingCandidates,
|
|
173
|
+
topic_label: options.topicLabel,
|
|
174
|
+
topic_summary_redacted: options.topicSummaryRedacted,
|
|
175
|
+
work_intent: options.workIntent,
|
|
176
|
+
work_phase: options.workPhase,
|
|
177
|
+
intent_source: options.intentSource,
|
|
178
|
+
intent_confidence: options.intentConfidence,
|
|
173
179
|
pull_request_url: existingContext?.pull_request_url,
|
|
174
180
|
provenance: {
|
|
175
181
|
capture_source: "collector_runtime",
|
|
@@ -285,7 +291,7 @@ export async function readLocalWorkContextForRepo(paths, repoRoot) {
|
|
|
285
291
|
path.resolve(active.repo) === identity.repo_root) {
|
|
286
292
|
return active;
|
|
287
293
|
}
|
|
288
|
-
throw new Error(`Active work context missing for ${identity.worktree_label}. Run \`cockpit start --
|
|
294
|
+
throw new Error(`Active work context missing for ${identity.worktree_label}. Run \`cockpit start --workspace "${identity.repo_root}"\`.`);
|
|
289
295
|
}
|
|
290
296
|
async function readLocalWorkContextByFingerprint(paths, worktreeFingerprint) {
|
|
291
297
|
return LocalWorkContextSchema.parse(await readJsonFile(workContextFile(paths, worktreeFingerprint)));
|
package/dist/upload.js
CHANGED
|
@@ -575,6 +575,7 @@ function makeSourceScanCompletedEvent(options) {
|
|
|
575
575
|
throw new Error("Upload work context is missing provenance.");
|
|
576
576
|
}
|
|
577
577
|
const rawEvidencePointers = options.rawEvidenceFacts?.pointers ?? [];
|
|
578
|
+
const evidenceCompleteness = options.rawEvidenceFacts?.evidence_completeness;
|
|
578
579
|
const hasRawEvidence = rawEvidencePointers.length > 0;
|
|
579
580
|
const eventPrivacyClassification = hasRawEvidence
|
|
580
581
|
? "redacted_summary"
|
|
@@ -617,6 +618,12 @@ function makeSourceScanCompletedEvent(options) {
|
|
|
617
618
|
raw_evidence_byte_size: options.rawEvidenceFacts?.byte_size ?? 0,
|
|
618
619
|
raw_evidence_skipped_count: options.rawEvidenceFacts?.skipped_count ?? 0,
|
|
619
620
|
raw_evidence_reused_count: options.rawEvidenceFacts?.reused_count ?? 0,
|
|
621
|
+
evidence_scanned_count: evidenceCompleteness?.totals.scanned_count ?? 0,
|
|
622
|
+
evidence_included_count: evidenceCompleteness?.totals.included_count ?? 0,
|
|
623
|
+
evidence_skipped_count: evidenceCompleteness?.totals.skipped_count ?? 0,
|
|
624
|
+
evidence_truncated_count: evidenceCompleteness?.totals.truncated_count ?? 0,
|
|
625
|
+
evidence_deferred_count: evidenceCompleteness?.totals.deferred_count ?? 0,
|
|
626
|
+
evidence_reused_count: evidenceCompleteness?.totals.reused_count ?? 0,
|
|
620
627
|
},
|
|
621
628
|
attributes: {
|
|
622
629
|
repo_label: options.context.repo,
|
|
@@ -634,7 +641,31 @@ function makeSourceScanCompletedEvent(options) {
|
|
|
634
641
|
? "remote_durable_raw_evidence"
|
|
635
642
|
: "metadata_only",
|
|
636
643
|
raw_payload_included: false,
|
|
644
|
+
evidence_completeness_schema_version: evidenceCompleteness?.schema_version ?? "evidence-completeness.v1",
|
|
645
|
+
evidence_completeness_status: evidenceCompleteness?.status ?? "unknown",
|
|
646
|
+
evidence_incomplete: evidenceCompleteness
|
|
647
|
+
? evidenceCompleteness.status !== "complete"
|
|
648
|
+
: true,
|
|
649
|
+
...(options.context.topic_label
|
|
650
|
+
? { topic_label: options.context.topic_label }
|
|
651
|
+
: {}),
|
|
652
|
+
...(options.context.topic_summary_redacted
|
|
653
|
+
? { topic_summary_redacted: options.context.topic_summary_redacted }
|
|
654
|
+
: {}),
|
|
655
|
+
...(options.context.work_intent
|
|
656
|
+
? { work_intent: options.context.work_intent }
|
|
657
|
+
: {}),
|
|
658
|
+
...(options.context.work_phase
|
|
659
|
+
? { work_phase: options.context.work_phase }
|
|
660
|
+
: {}),
|
|
661
|
+
...(options.context.intent_source
|
|
662
|
+
? { intent_source: options.context.intent_source }
|
|
663
|
+
: {}),
|
|
664
|
+
...(options.context.intent_confidence !== undefined
|
|
665
|
+
? { intent_confidence: options.context.intent_confidence }
|
|
666
|
+
: {}),
|
|
637
667
|
},
|
|
668
|
+
evidence_completeness: evidenceCompleteness,
|
|
638
669
|
ticket_binding: options.ticketBinding ?? undefined,
|
|
639
670
|
risk_flags: options.riskFlags,
|
|
640
671
|
raw_evidence_pointers: rawEvidencePointers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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.6"
|
|
30
30
|
}
|
|
31
31
|
}
|