codiedev 0.9.16 → 0.9.18
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/dist/captureReadiness.d.ts +11 -0
- package/dist/captureReadiness.js +21 -0
- package/dist/commands/doctor.js +33 -8
- package/dist/hook.js +13 -3
- package/dist/limitEvent.d.ts +1 -0
- package/dist/limitEvent.js +13 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +19 -0
- package/package.json +1 -1
|
@@ -15,6 +15,17 @@ export interface CaptureReadiness {
|
|
|
15
15
|
headSha: string | null;
|
|
16
16
|
}
|
|
17
17
|
export declare function assessCaptureReadiness<R>(cwd: string, repos: R[], deps: CaptureReadinessDeps<R>): CaptureReadiness;
|
|
18
|
+
export interface WorkspaceRepoScanDeps<R = unknown> {
|
|
19
|
+
listChildDirs: (cwd: string) => string[];
|
|
20
|
+
isRepo: (dir: string) => boolean;
|
|
21
|
+
getRemoteUrl: (dir: string) => string | null;
|
|
22
|
+
matchRepo: (url: string, repos: R[]) => R | null;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkspaceRepoScan {
|
|
25
|
+
repoCount: number;
|
|
26
|
+
matchedCount: number;
|
|
27
|
+
}
|
|
28
|
+
export declare function assessWorkspaceRepos<R>(cwd: string, repos: R[], deps: WorkspaceRepoScanDeps<R>): WorkspaceRepoScan;
|
|
18
29
|
export declare function formatRelativeAge(ms: number): string;
|
|
19
30
|
export interface LastCaptureSummary {
|
|
20
31
|
status: "pass" | "warn";
|
package/dist/captureReadiness.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// production wiring (in doctor.ts) plugs in execSync + the real git helpers.
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.assessCaptureReadiness = assessCaptureReadiness;
|
|
11
|
+
exports.assessWorkspaceRepos = assessWorkspaceRepos;
|
|
11
12
|
exports.formatRelativeAge = formatRelativeAge;
|
|
12
13
|
exports.summarizeLastCapture = summarizeLastCapture;
|
|
13
14
|
exports.summarizeServerCapture = summarizeServerCapture;
|
|
@@ -41,6 +42,26 @@ function assessCaptureReadiness(cwd, repos, deps) {
|
|
|
41
42
|
headSha: deps.getHeadSha(cwd),
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
function assessWorkspaceRepos(cwd, repos, deps) {
|
|
46
|
+
let childDirs;
|
|
47
|
+
try {
|
|
48
|
+
childDirs = deps.listChildDirs(cwd);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { repoCount: 0, matchedCount: 0 };
|
|
52
|
+
}
|
|
53
|
+
let repoCount = 0;
|
|
54
|
+
let matchedCount = 0;
|
|
55
|
+
for (const dir of childDirs) {
|
|
56
|
+
if (!deps.isRepo(dir))
|
|
57
|
+
continue;
|
|
58
|
+
repoCount++;
|
|
59
|
+
const url = deps.getRemoteUrl(dir);
|
|
60
|
+
if (url && deps.matchRepo(url, repos) !== null)
|
|
61
|
+
matchedCount++;
|
|
62
|
+
}
|
|
63
|
+
return { repoCount, matchedCount };
|
|
64
|
+
}
|
|
44
65
|
function formatRelativeAge(ms) {
|
|
45
66
|
if (ms < 0)
|
|
46
67
|
ms = 0;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -224,6 +224,23 @@ async function runDoctor(_args) {
|
|
|
224
224
|
getBranch: utils_1.getGitBranch,
|
|
225
225
|
getHeadSha: utils_1.getGitHeadSha,
|
|
226
226
|
});
|
|
227
|
+
// Workspace-root mode: the cwd isn't a repo itself, but git repos sit
|
|
228
|
+
// directly under it (one `.devcontainer/` at a parent folder of several
|
|
229
|
+
// cloned repos). Capture tags the sub-repo a session edits, so report that
|
|
230
|
+
// instead of flatly warning "not a work tree / no remote" — the exact output
|
|
231
|
+
// that made working workspace-root setups read as broken.
|
|
232
|
+
const workspaceScan = readiness.gitOnPath && !readiness.insideWorkTree
|
|
233
|
+
? (0, captureReadiness_1.assessWorkspaceRepos)(cwd, config.repos ?? [], {
|
|
234
|
+
listChildDirs: (dir) => fs
|
|
235
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
236
|
+
.filter((e) => e.isDirectory())
|
|
237
|
+
.map((e) => path.join(dir, e.name)),
|
|
238
|
+
isRepo: (dir) => fs.existsSync(path.join(dir, ".git")),
|
|
239
|
+
getRemoteUrl: utils_1.getGitRemoteUrl,
|
|
240
|
+
matchRepo: utils_1.matchRepo,
|
|
241
|
+
})
|
|
242
|
+
: { repoCount: 0, matchedCount: 0 };
|
|
243
|
+
const workspaceMode = workspaceScan.repoCount > 0;
|
|
227
244
|
checks.push({
|
|
228
245
|
name: "git on PATH",
|
|
229
246
|
status: readiness.gitOnPath ? "pass" : "fail",
|
|
@@ -233,26 +250,34 @@ async function runDoctor(_args) {
|
|
|
233
250
|
});
|
|
234
251
|
checks.push({
|
|
235
252
|
name: "Inside a git work tree (current dir)",
|
|
236
|
-
status: readiness.insideWorkTree ? "pass" : "warn",
|
|
253
|
+
status: readiness.insideWorkTree || workspaceMode ? "pass" : "warn",
|
|
237
254
|
detail: readiness.insideWorkTree
|
|
238
255
|
? undefined
|
|
239
|
-
:
|
|
256
|
+
: workspaceMode
|
|
257
|
+
? `workspace root — ${workspaceScan.repoCount} git repo${workspaceScan.repoCount === 1 ? "" : "s"} directly under it; sessions tag the repo they edit`
|
|
258
|
+
: "current dir isn't a git work tree — if your repo is a mounted submodule, mount the full project so its .git is present",
|
|
240
259
|
});
|
|
241
260
|
checks.push({
|
|
242
261
|
name: "Current repo resolves to a tracked repo",
|
|
243
|
-
status: readiness.matched ? "pass" : "warn",
|
|
262
|
+
status: readiness.matched || workspaceScan.matchedCount > 0 ? "pass" : "warn",
|
|
244
263
|
detail: readiness.matched
|
|
245
264
|
? `${readiness.remoteUrl}`
|
|
246
|
-
:
|
|
247
|
-
?
|
|
248
|
-
|
|
265
|
+
: workspaceMode
|
|
266
|
+
? workspaceScan.matchedCount > 0
|
|
267
|
+
? `${workspaceScan.matchedCount} of ${workspaceScan.repoCount} workspace repos are tracked`
|
|
268
|
+
: `none of the ${workspaceScan.repoCount} workspace repos are tracked — link them in the portal`
|
|
269
|
+
: readiness.remoteUrl
|
|
270
|
+
? `remote ${readiness.remoteUrl} isn't a tracked repo — link it in the portal`
|
|
271
|
+
: "no origin remote here — capture uploads but won't link to a repo",
|
|
249
272
|
});
|
|
250
273
|
checks.push({
|
|
251
274
|
name: "What this session would capture",
|
|
252
|
-
status: readiness.branch || readiness.headSha ? "pass" : "warn",
|
|
275
|
+
status: readiness.branch || readiness.headSha || workspaceMode ? "pass" : "warn",
|
|
253
276
|
detail: readiness.branch || readiness.headSha
|
|
254
277
|
? `branch ${readiness.branch ?? "—"} @ ${(readiness.headSha ?? "—").slice(0, 8)}`
|
|
255
|
-
:
|
|
278
|
+
: workspaceMode
|
|
279
|
+
? "branch/sha resolve from the sub-repo each session writes to"
|
|
280
|
+
: "no branch/sha resolved — session→merge attribution will be weaker",
|
|
256
281
|
});
|
|
257
282
|
// 4c. The payoff: did the last session actually land? Ask the server for
|
|
258
283
|
// portal truth (does the row exist for this user?), falling back to the local
|
package/dist/hook.js
CHANGED
|
@@ -112,10 +112,15 @@ async function runStopFailure(hookInput) {
|
|
|
112
112
|
if (!config)
|
|
113
113
|
process.exit(0);
|
|
114
114
|
let event = null;
|
|
115
|
+
let transcriptTail;
|
|
115
116
|
const tp = hookInput.transcript_path;
|
|
116
117
|
if (tp) {
|
|
117
118
|
try {
|
|
118
|
-
|
|
119
|
+
const transcript = fs.readFileSync(tp, "utf8");
|
|
120
|
+
event = (0, limitEvent_1.parseLimitEventFromTranscript)(transcript);
|
|
121
|
+
// Ship the tail with the event so the server can summarize "what they
|
|
122
|
+
// were working on" right now — no waiting for the SessionEnd capture.
|
|
123
|
+
transcriptTail = (0, limitEvent_1.tailForLimitEvent)(transcript) || undefined;
|
|
119
124
|
}
|
|
120
125
|
catch {
|
|
121
126
|
/* unreadable transcript — fall through to minimal event */
|
|
@@ -130,6 +135,7 @@ async function runStopFailure(hookInput) {
|
|
|
130
135
|
at: payload.at,
|
|
131
136
|
resetText: payload.resetText,
|
|
132
137
|
sessionId: hookInput.session_id,
|
|
138
|
+
transcriptTail,
|
|
133
139
|
}, config.token);
|
|
134
140
|
}
|
|
135
141
|
catch {
|
|
@@ -377,8 +383,13 @@ async function runCaptureFinish(input) {
|
|
|
377
383
|
if (mode === "codex") {
|
|
378
384
|
remoteUrl = (0, utils_1.extractCodexRepoUrl)(transcriptContent);
|
|
379
385
|
}
|
|
386
|
+
// The worktrees the session actually wrote to — resolved up front because
|
|
387
|
+
// they both anchor the branch/sha join keys below AND back-fill the repo tag
|
|
388
|
+
// when the launch dir is a multi-repo WORKSPACE root (a parent folder of
|
|
389
|
+
// several cloned repos) that has no remote of its own.
|
|
390
|
+
const captureDirs = (0, utils_1.resolveCaptureDirs)(transcriptContent, workspaceRoot);
|
|
380
391
|
if (!remoteUrl) {
|
|
381
|
-
remoteUrl = (0, utils_1.
|
|
392
|
+
remoteUrl = (0, utils_1.resolveCaptureRemoteUrl)(workspaceRoot, captureDirs);
|
|
382
393
|
}
|
|
383
394
|
// Capture-first: the transcript is the asset; the repo is just a tag. We do
|
|
384
395
|
// NOT bail when `remoteUrl` is null — that previously dropped EVERY session
|
|
@@ -403,7 +414,6 @@ async function runCaptureFinish(input) {
|
|
|
403
414
|
// One session can touch several worktrees (→ several PRs); capture EVERY
|
|
404
415
|
// worktree's branch+sha so all the resulting PRs attribute. branch/headSha
|
|
405
416
|
// remain the primary (most-recent) values for back-compat.
|
|
406
|
-
const captureDirs = (0, utils_1.resolveCaptureDirs)(transcriptContent, workspaceRoot);
|
|
407
417
|
const keys = (0, utils_1.collectCaptureKeys)(captureDirs);
|
|
408
418
|
// Union the worktree-dir branches with the branches the agent actually
|
|
409
419
|
// committed/pushed to (parsed from the transcript's git output). Like the
|
package/dist/limitEvent.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export interface LimitEvent {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function classifyLimitKind(text: string): LimitKind;
|
|
8
8
|
export declare function parseLimitEventFromTranscript(transcript: string): LimitEvent | null;
|
|
9
|
+
export declare function tailForLimitEvent(transcript: string, maxChars?: number): string;
|
package/dist/limitEvent.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.classifyLimitKind = classifyLimitKind;
|
|
4
4
|
exports.parseLimitEventFromTranscript = parseLimitEventFromTranscript;
|
|
5
|
+
exports.tailForLimitEvent = tailForLimitEvent;
|
|
5
6
|
function classifyLimitKind(text) {
|
|
6
7
|
const t = text.toLowerCase();
|
|
7
8
|
if (t.includes("session limit"))
|
|
@@ -49,3 +50,15 @@ function parseLimitEventFromTranscript(transcript) {
|
|
|
49
50
|
}
|
|
50
51
|
return found;
|
|
51
52
|
}
|
|
53
|
+
// Tail of the raw transcript to ship with the limit event so the server can
|
|
54
|
+
// summarize "what they were working on" immediately — without waiting for the
|
|
55
|
+
// SessionEnd capture (which can lag hours, or drop entirely on Windows).
|
|
56
|
+
// Resumes at a line boundary so every JSONL line in the tail still parses.
|
|
57
|
+
const DEFAULT_TAIL_CHARS = 65536;
|
|
58
|
+
function tailForLimitEvent(transcript, maxChars = DEFAULT_TAIL_CHARS) {
|
|
59
|
+
if (transcript.length <= maxChars)
|
|
60
|
+
return transcript;
|
|
61
|
+
const sliced = transcript.slice(-maxChars);
|
|
62
|
+
const nl = sliced.indexOf("\n");
|
|
63
|
+
return nl === -1 ? sliced : sliced.slice(nl + 1);
|
|
64
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare function writeConfig(config: CodiedevConfig): void;
|
|
|
17
17
|
export declare function parseGitRemoteFromConfig(configText: string): string | null;
|
|
18
18
|
export declare function readGitRemoteFromConfig(startDir: string): string | null;
|
|
19
19
|
export declare function getGitRemoteUrl(cwd: string): string | null;
|
|
20
|
+
export declare function resolveCaptureRemoteUrl(workspaceRoot: string, captureDirs: string[], getRemote?: (dir: string) => string | null): string | null;
|
|
20
21
|
export declare function normalizeRepoUrl(url: string): string;
|
|
21
22
|
export declare function matchRepo(remoteUrl: string, repos: Array<{
|
|
22
23
|
repoId: string;
|
package/dist/utils.js
CHANGED
|
@@ -39,6 +39,7 @@ exports.writeConfig = writeConfig;
|
|
|
39
39
|
exports.parseGitRemoteFromConfig = parseGitRemoteFromConfig;
|
|
40
40
|
exports.readGitRemoteFromConfig = readGitRemoteFromConfig;
|
|
41
41
|
exports.getGitRemoteUrl = getGitRemoteUrl;
|
|
42
|
+
exports.resolveCaptureRemoteUrl = resolveCaptureRemoteUrl;
|
|
42
43
|
exports.normalizeRepoUrl = normalizeRepoUrl;
|
|
43
44
|
exports.matchRepo = matchRepo;
|
|
44
45
|
exports.resolveCaptureDir = resolveCaptureDir;
|
|
@@ -235,6 +236,24 @@ function getGitRemoteUrl(cwd) {
|
|
|
235
236
|
return null;
|
|
236
237
|
}
|
|
237
238
|
}
|
|
239
|
+
// Resolve the remote that tags the session's repo. The workspace root is the
|
|
240
|
+
// primary source, but when the session was launched at a multi-repo WORKSPACE
|
|
241
|
+
// root — a parent folder holding several cloned repos with `.devcontainer/` at
|
|
242
|
+
// the top, which is not itself a git repo — the root has no remote, and tagging
|
|
243
|
+
// used to silently produce repo-UNMATCHED sessions. Fall back to the worktrees
|
|
244
|
+
// the session actually wrote to (resolveCaptureDirs order: most-recent first),
|
|
245
|
+
// so the session tags the repo the work happened in.
|
|
246
|
+
function resolveCaptureRemoteUrl(workspaceRoot, captureDirs, getRemote = getGitRemoteUrl) {
|
|
247
|
+
const fromRoot = getRemote(workspaceRoot);
|
|
248
|
+
if (fromRoot)
|
|
249
|
+
return fromRoot;
|
|
250
|
+
for (const dir of captureDirs) {
|
|
251
|
+
const url = getRemote(dir);
|
|
252
|
+
if (url)
|
|
253
|
+
return url;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
238
257
|
function normalizeRepoUrl(url) {
|
|
239
258
|
// Normalize SSH (git@github.com:org/repo.git) and HTTPS (https://github.com/org/repo.git)
|
|
240
259
|
// to a comparable form: github.com/org/repo
|
package/package.json
CHANGED