@bli-cockpit/cli 0.1.19 → 0.1.21
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 +34 -6
- package/dist/adapters/claude-attribution.js +47 -4
- package/dist/adapters/codex-attribution.js +41 -5
- package/dist/adapters/local-sources.js +2 -0
- package/dist/adapters/raw-evidence.js +487 -35
- package/dist/agent-rules.js +44 -19
- package/dist/commands/local-args.js +51 -2
- package/dist/commands/local.js +31 -15
- package/dist/commands/public-root.js +2 -2
- package/dist/commands/session-sync.js +14 -0
- package/dist/local-state.js +7 -1
- package/dist/upload.js +117 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -46,19 +46,21 @@ On machines where Codex or Claude agents will do ticketed work, interactive
|
|
|
46
46
|
`cockpit onboard` checks `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` after
|
|
47
47
|
harvest proof. If equivalent Cockpit ticket-binding guidance already exists, it
|
|
48
48
|
leaves the files alone. If guidance is missing or clearly stale, it asks whether
|
|
49
|
-
to install or replace it.
|
|
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.
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
54
|
# Repair/manual path, or headless/json onboarding where Cockpit cannot prompt.
|
|
53
|
-
cockpit agent-rules install
|
|
55
|
+
cockpit agent-rules install --workspace "$PWD"
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
The direct command updates `~/.codex/AGENTS.md` and `~/.claude/CLAUDE.md` with
|
|
57
59
|
the Cockpit rule to bind known Linear tickets before edits, or ask once when
|
|
58
|
-
the ticket ID is missing. That binding starts attributing
|
|
59
|
-
the specific ticket in Cockpit. It checks for managed or
|
|
60
|
-
first, so current files are left unchanged and stale Cockpit
|
|
61
|
-
sections are replaced.
|
|
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.
|
|
62
64
|
|
|
63
65
|
Parent mode scans child git repos/worktrees (3 folder levels deep, up to 50
|
|
64
66
|
repos by default — tune with `--max-depth` / `--max-repos`; a warning prints
|
|
@@ -113,6 +115,30 @@ cockpit sync \
|
|
|
113
115
|
No ticket is required for setup, chatting, planning, or general ambient capture.
|
|
114
116
|
Only pass `--ticket` when the work really belongs to a visible ticket.
|
|
115
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
|
+
|
|
116
142
|
## What gets saved
|
|
117
143
|
|
|
118
144
|
Local files:
|
|
@@ -180,3 +206,5 @@ This public package intentionally excludes Cockpit admin bootstrap commands,
|
|
|
180
206
|
service-role credential handling, source maps, tests, and internal runbooks.
|
|
181
207
|
Clean `npm pack` and `npm publish` run the public CLI build before packaging so
|
|
182
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`.
|
|
@@ -54,8 +54,19 @@ export async function scanAndAttributeClaudeSessions(options) {
|
|
|
54
54
|
const countState = (state) => results.filter((result) => result.state === state).length;
|
|
55
55
|
return {
|
|
56
56
|
results,
|
|
57
|
+
discovered_session_count: discovery.sessions.length,
|
|
57
58
|
scanned_session_count: sessions.length,
|
|
59
|
+
since_minutes: sinceMinutes,
|
|
60
|
+
session_limit: limit,
|
|
61
|
+
session_limit_applied: discovery.sessions.length > limit,
|
|
62
|
+
max_file_bytes: CLAUDE_SESSION_MAX_FILE_BYTES,
|
|
63
|
+
max_sidecar_files: CLAUDE_SESSION_MAX_SIDECAR_FILES,
|
|
64
|
+
max_line_buffer_bytes: MAX_LINE_BUFFER_BYTES,
|
|
58
65
|
project_dirs_skipped: discovery.projectDirsSkipped,
|
|
66
|
+
project_dir_read_failed_count: discovery.projectDirReadFailedCount,
|
|
67
|
+
session_stat_failed_count: discovery.sessionStatFailedCount,
|
|
68
|
+
sidecar_dir_read_failed_count: discovery.sidecarDirReadFailedCount,
|
|
69
|
+
sidecar_stat_failed_count: discovery.sidecarStatFailedCount,
|
|
59
70
|
counts: {
|
|
60
71
|
attributed: countState("attributed"),
|
|
61
72
|
ambiguous: countState("ambiguous"),
|
|
@@ -72,12 +83,23 @@ export async function scanAndAttributeClaudeSessions(options) {
|
|
|
72
83
|
async function discoverClaudeSessions(projectsDir, cutoffMs) {
|
|
73
84
|
const sessions = [];
|
|
74
85
|
let projectDirsSkipped = 0;
|
|
86
|
+
let projectDirReadFailedCount = 0;
|
|
87
|
+
let sessionStatFailedCount = 0;
|
|
88
|
+
let sidecarDirReadFailedCount = 0;
|
|
89
|
+
let sidecarStatFailedCount = 0;
|
|
75
90
|
let projectEntries;
|
|
76
91
|
try {
|
|
77
92
|
projectEntries = await fs.readdir(projectsDir, { withFileTypes: true });
|
|
78
93
|
}
|
|
79
94
|
catch {
|
|
80
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
sessions,
|
|
97
|
+
projectDirsSkipped,
|
|
98
|
+
projectDirReadFailedCount: 1,
|
|
99
|
+
sessionStatFailedCount,
|
|
100
|
+
sidecarDirReadFailedCount,
|
|
101
|
+
sidecarStatFailedCount,
|
|
102
|
+
};
|
|
81
103
|
}
|
|
82
104
|
for (const projectEntry of projectEntries) {
|
|
83
105
|
if (!projectEntry.isDirectory())
|
|
@@ -96,6 +118,7 @@ async function discoverClaudeSessions(projectsDir, cutoffMs) {
|
|
|
96
118
|
sessionEntries = await fs.readdir(projectDir, { withFileTypes: true });
|
|
97
119
|
}
|
|
98
120
|
catch {
|
|
121
|
+
projectDirReadFailedCount += 1;
|
|
99
122
|
continue;
|
|
100
123
|
}
|
|
101
124
|
for (const sessionEntry of sessionEntries) {
|
|
@@ -109,10 +132,14 @@ async function discoverClaudeSessions(projectsDir, cutoffMs) {
|
|
|
109
132
|
mainStat = await fs.stat(mainFile);
|
|
110
133
|
}
|
|
111
134
|
catch {
|
|
135
|
+
sessionStatFailedCount += 1;
|
|
112
136
|
continue;
|
|
113
137
|
}
|
|
114
138
|
const sessionUuid = sessionEntry.name.replace(/\.jsonl$/i, "");
|
|
115
|
-
const
|
|
139
|
+
const sidecarDiscovery = await discoverSidecars(path.join(projectDir, sessionUuid, "subagents"));
|
|
140
|
+
const { sidecars, sidecarsCapped } = sidecarDiscovery;
|
|
141
|
+
sidecarDirReadFailedCount += sidecarDiscovery.dirReadFailedCount;
|
|
142
|
+
sidecarStatFailedCount += sidecarDiscovery.statFailedCount;
|
|
116
143
|
const recencyMs = Math.max(mainStat.mtimeMs, ...sidecars.map((sidecar) => sidecar.mtimeMs));
|
|
117
144
|
// D10: a session is recent if its main file OR any sidecar is in window.
|
|
118
145
|
if (recencyMs < cutoffMs)
|
|
@@ -127,7 +154,14 @@ async function discoverClaudeSessions(projectsDir, cutoffMs) {
|
|
|
127
154
|
});
|
|
128
155
|
}
|
|
129
156
|
}
|
|
130
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
sessions,
|
|
159
|
+
projectDirsSkipped,
|
|
160
|
+
projectDirReadFailedCount,
|
|
161
|
+
sessionStatFailedCount,
|
|
162
|
+
sidecarDirReadFailedCount,
|
|
163
|
+
sidecarStatFailedCount,
|
|
164
|
+
};
|
|
131
165
|
}
|
|
132
166
|
async function discoverSidecars(subagentsDir) {
|
|
133
167
|
let entries;
|
|
@@ -135,9 +169,15 @@ async function discoverSidecars(subagentsDir) {
|
|
|
135
169
|
entries = await fs.readdir(subagentsDir, { withFileTypes: true });
|
|
136
170
|
}
|
|
137
171
|
catch {
|
|
138
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
sidecars: [],
|
|
174
|
+
sidecarsCapped: 0,
|
|
175
|
+
dirReadFailedCount: 1,
|
|
176
|
+
statFailedCount: 0,
|
|
177
|
+
};
|
|
139
178
|
}
|
|
140
179
|
const discovered = [];
|
|
180
|
+
let statFailedCount = 0;
|
|
141
181
|
for (const entry of entries) {
|
|
142
182
|
// Only agent transcripts. `*.meta.json` carry operator-authored
|
|
143
183
|
// descriptions and are never harvested (D3).
|
|
@@ -152,6 +192,7 @@ async function discoverSidecars(subagentsDir) {
|
|
|
152
192
|
stat = await fs.stat(local_path);
|
|
153
193
|
}
|
|
154
194
|
catch {
|
|
195
|
+
statFailedCount += 1;
|
|
155
196
|
continue;
|
|
156
197
|
}
|
|
157
198
|
discovered.push({
|
|
@@ -166,6 +207,8 @@ async function discoverSidecars(subagentsDir) {
|
|
|
166
207
|
return {
|
|
167
208
|
sidecars: discovered.slice(0, CLAUDE_SESSION_MAX_SIDECAR_FILES),
|
|
168
209
|
sidecarsCapped: capped,
|
|
210
|
+
dirReadFailedCount: 0,
|
|
211
|
+
statFailedCount,
|
|
169
212
|
};
|
|
170
213
|
}
|
|
171
214
|
async function attributeOneSession(session, worktrees) {
|
|
@@ -23,14 +23,23 @@ export async function scanAndAttributeCodexSessions(options) {
|
|
|
23
23
|
const sinceMinutes = options.sinceMinutes ?? CODEX_ATTRIBUTION_DEFAULT_SINCE_MINUTES;
|
|
24
24
|
const limit = options.limit ?? CODEX_ATTRIBUTION_DEFAULT_SESSION_LIMIT;
|
|
25
25
|
const cutoffMs = options.now.getTime() - sinceMinutes * 60 * 1000;
|
|
26
|
-
const
|
|
26
|
+
const discovery = await discoverCodexJsonlFiles(options.sessionsDir, cutoffMs);
|
|
27
|
+
const files = discovery.files.slice(0, limit);
|
|
27
28
|
const results = [];
|
|
28
29
|
for (const file of files) {
|
|
29
30
|
results.push(await attributeOneSession(file, options.worktrees));
|
|
30
31
|
}
|
|
31
32
|
return {
|
|
32
33
|
results,
|
|
34
|
+
discovered_file_count: discovery.files.length,
|
|
33
35
|
scanned_file_count: files.length,
|
|
36
|
+
since_minutes: sinceMinutes,
|
|
37
|
+
session_limit: limit,
|
|
38
|
+
session_limit_applied: discovery.files.length > limit,
|
|
39
|
+
max_file_bytes: CODEX_SESSION_MAX_FILE_BYTES,
|
|
40
|
+
directory_read_failed_count: discovery.directoryReadFailedCount,
|
|
41
|
+
stat_failed_count: discovery.statFailedCount,
|
|
42
|
+
secret_path_skipped_count: discovery.secretPathSkippedCount,
|
|
34
43
|
counts: {
|
|
35
44
|
attributed: results.filter((entry) => entry.state === "attributed").length,
|
|
36
45
|
ambiguous: results.filter((entry) => entry.state === "ambiguous").length,
|
|
@@ -41,25 +50,40 @@ export async function scanAndAttributeCodexSessions(options) {
|
|
|
41
50
|
};
|
|
42
51
|
}
|
|
43
52
|
export async function walkCodexJsonlFiles(dir, cutoffMs) {
|
|
53
|
+
return (await discoverCodexJsonlFiles(dir, cutoffMs)).files;
|
|
54
|
+
}
|
|
55
|
+
async function discoverCodexJsonlFiles(dir, cutoffMs) {
|
|
44
56
|
const out = [];
|
|
57
|
+
let directoryReadFailedCount = 0;
|
|
58
|
+
let statFailedCount = 0;
|
|
59
|
+
let secretPathSkippedCount = 0;
|
|
45
60
|
const stack = [dir];
|
|
46
61
|
while (stack.length > 0) {
|
|
47
62
|
const current = stack.pop();
|
|
48
|
-
if (!current
|
|
63
|
+
if (!current)
|
|
64
|
+
continue;
|
|
65
|
+
if (isSecretLikePath(current)) {
|
|
66
|
+
secretPathSkippedCount += 1;
|
|
49
67
|
continue;
|
|
68
|
+
}
|
|
50
69
|
let entries;
|
|
51
70
|
try {
|
|
52
71
|
entries = await fs.readdir(current, { withFileTypes: true });
|
|
53
72
|
}
|
|
54
73
|
catch {
|
|
74
|
+
directoryReadFailedCount += 1;
|
|
55
75
|
continue;
|
|
56
76
|
}
|
|
57
77
|
for (const entry of entries) {
|
|
58
78
|
const full = path.join(current, entry.name);
|
|
59
79
|
if (entry.isDirectory()) {
|
|
60
80
|
// Never descend into secret-like directories.
|
|
61
|
-
if (
|
|
81
|
+
if (isSecretLikePath(full)) {
|
|
82
|
+
secretPathSkippedCount += 1;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
62
85
|
stack.push(full);
|
|
86
|
+
}
|
|
63
87
|
continue;
|
|
64
88
|
}
|
|
65
89
|
// Secret-like file NAMES stay in the list so attribution can record a
|
|
@@ -67,14 +91,26 @@ export async function walkCodexJsonlFiles(dir, cutoffMs) {
|
|
|
67
91
|
// instead of silently dropping the session.
|
|
68
92
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
|
|
69
93
|
continue;
|
|
70
|
-
|
|
94
|
+
let stat;
|
|
95
|
+
try {
|
|
96
|
+
stat = await fs.stat(full);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
statFailedCount += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
71
102
|
if (stat.mtimeMs >= cutoffMs) {
|
|
72
103
|
out.push({ file: full, mtimeMs: stat.mtimeMs, byteSize: stat.size });
|
|
73
104
|
}
|
|
74
105
|
}
|
|
75
106
|
}
|
|
76
107
|
out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
77
|
-
return
|
|
108
|
+
return {
|
|
109
|
+
files: out,
|
|
110
|
+
directoryReadFailedCount,
|
|
111
|
+
statFailedCount,
|
|
112
|
+
secretPathSkippedCount,
|
|
113
|
+
};
|
|
78
114
|
}
|
|
79
115
|
async function attributeOneSession(file, worktrees) {
|
|
80
116
|
const fileName = path.basename(file.file);
|
|
@@ -28,7 +28,9 @@ export async function runLocalSourceCollectors(options) {
|
|
|
28
28
|
includeCodexJsonl: options.rawEvidenceIncludeCodexJsonl,
|
|
29
29
|
includeClaudeJsonl: options.rawEvidenceIncludeClaudeJsonl,
|
|
30
30
|
codexSessionFiles: options.rawEvidenceCodexSessionFiles,
|
|
31
|
+
codexAttributionScan: options.rawEvidenceCodexAttributionScan,
|
|
31
32
|
claudeSessionFiles: options.rawEvidenceClaudeSessionFiles,
|
|
33
|
+
claudeAttributionScan: options.rawEvidenceClaudeAttributionScan,
|
|
32
34
|
skipContentHashes: options.rawEvidenceSkipContentHashes,
|
|
33
35
|
byteBudget: options.rawEvidenceByteBudget,
|
|
34
36
|
objectBudget: options.rawEvidenceObjectBudget,
|