@cogit/cli 0.21.0 → 0.21.1
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.
|
@@ -20470,7 +20470,12 @@ var HookState = external_exports.object({
|
|
|
20470
20470
|
* and a file with nothing to say is remembered too, so every edit does not
|
|
20471
20471
|
* walk the history again.
|
|
20472
20472
|
*/
|
|
20473
|
-
notedFiles: external_exports.array(external_exports.string().min(1).max(500)).default([])
|
|
20473
|
+
notedFiles: external_exports.array(external_exports.string().min(1).max(500)).default([]),
|
|
20474
|
+
/**
|
|
20475
|
+
* Closing passes a later session started for this one because its own, at
|
|
20476
|
+
* the session end, never finished (machine shut down, run hung or failed).
|
|
20477
|
+
*/
|
|
20478
|
+
closeRetries: external_exports.number().int().min(0).optional()
|
|
20474
20479
|
});
|
|
20475
20480
|
|
|
20476
20481
|
// src/schema/versioned.ts
|
package/dist/cli.js
CHANGED
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
updateState,
|
|
82
82
|
upgradeSessionIds,
|
|
83
83
|
withLock
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-MPOCC62M.js";
|
|
85
85
|
|
|
86
86
|
// ../../node_modules/cli-width/index.js
|
|
87
87
|
var require_cli_width = __commonJS({
|
|
@@ -5529,7 +5529,7 @@ var program = new Command();
|
|
|
5529
5529
|
// package.json
|
|
5530
5530
|
var package_default = {
|
|
5531
5531
|
name: "@cogit/cli",
|
|
5532
|
-
version: "0.21.
|
|
5532
|
+
version: "0.21.1",
|
|
5533
5533
|
description: "Git remembers what changed. cogit remembers why.",
|
|
5534
5534
|
license: "MIT",
|
|
5535
5535
|
keywords: [
|
|
@@ -5748,7 +5748,7 @@ async function migrateProject(root) {
|
|
|
5748
5748
|
const next = renameGitignoreLines(text);
|
|
5749
5749
|
if (next !== text) await atomicWrite(gitignore, next).catch(() => void 0);
|
|
5750
5750
|
}
|
|
5751
|
-
const { installGitHooks: installGitHooks2 } = await import("./git-hooks-
|
|
5751
|
+
const { installGitHooks: installGitHooks2 } = await import("./git-hooks-2F5ALCNN.js");
|
|
5752
5752
|
await installGitHooks2(dir).catch(() => void 0);
|
|
5753
5753
|
return true;
|
|
5754
5754
|
}
|
|
@@ -6032,7 +6032,8 @@ function repairTree(nodes) {
|
|
|
6032
6032
|
}
|
|
6033
6033
|
|
|
6034
6034
|
// src/commands/build.ts
|
|
6035
|
-
import {
|
|
6035
|
+
import { readdir, stat as stat3 } from "fs/promises";
|
|
6036
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
6036
6037
|
|
|
6037
6038
|
// src/store/changes.ts
|
|
6038
6039
|
async function readProjectChanges(root, hashes) {
|
|
@@ -6881,6 +6882,37 @@ function toViewTurn(turn) {
|
|
|
6881
6882
|
tools: turn.tools
|
|
6882
6883
|
};
|
|
6883
6884
|
}
|
|
6885
|
+
async function unrecordedSessions(root, sessions, transcripts) {
|
|
6886
|
+
const dir = dirname3(projectPaths(root).state("x"));
|
|
6887
|
+
let names;
|
|
6888
|
+
try {
|
|
6889
|
+
names = await readdir(dir);
|
|
6890
|
+
} catch {
|
|
6891
|
+
return [];
|
|
6892
|
+
}
|
|
6893
|
+
const known = new Set(sessions.map((session) => session.sessionId));
|
|
6894
|
+
const found = [];
|
|
6895
|
+
for (const name of names) {
|
|
6896
|
+
if (!name.endsWith(".json")) continue;
|
|
6897
|
+
const id = name.slice(0, -".json".length);
|
|
6898
|
+
if (known.has(id) || !transcripts.has(id) || !Slug.safeParse(id).success) continue;
|
|
6899
|
+
const info = await stat3(resolve4(dir, name)).catch(() => void 0);
|
|
6900
|
+
if (!info) continue;
|
|
6901
|
+
const startedAt = (info.birthtimeMs > 0 ? info.birthtime : info.mtime).toISOString();
|
|
6902
|
+
found.push({
|
|
6903
|
+
schema: SESSION_SCHEMA,
|
|
6904
|
+
sessionId: id,
|
|
6905
|
+
agent: "claude-code",
|
|
6906
|
+
title: "",
|
|
6907
|
+
epoch: null,
|
|
6908
|
+
startedAt,
|
|
6909
|
+
updatedAt: info.mtime.toISOString(),
|
|
6910
|
+
status: { goal: "", done: [], pending: [], next: "" },
|
|
6911
|
+
nodes: []
|
|
6912
|
+
});
|
|
6913
|
+
}
|
|
6914
|
+
return found;
|
|
6915
|
+
}
|
|
6884
6916
|
async function readTranscriptViews(sessions, transcripts, root, io) {
|
|
6885
6917
|
const agent3 = defaultAgent();
|
|
6886
6918
|
const chats = /* @__PURE__ */ new Map();
|
|
@@ -7013,8 +7045,9 @@ async function buildProject(opts) {
|
|
|
7013
7045
|
const model = await withLock(
|
|
7014
7046
|
paths.lock("build"),
|
|
7015
7047
|
async () => {
|
|
7016
|
-
const
|
|
7048
|
+
const recorded = await loadAllSessions(root, io.warn);
|
|
7017
7049
|
const transcripts = await defaultAgent().collectTranscripts(io.env);
|
|
7050
|
+
const sessions = [...recorded, ...await unrecordedSessions(root, recorded, transcripts)];
|
|
7018
7051
|
const views = await readTranscriptViews(sessions, transcripts, root, io);
|
|
7019
7052
|
const trailers = await readThoughtTrailers(root, {
|
|
7020
7053
|
env: io.env,
|
|
@@ -7530,6 +7563,7 @@ function closePrompt(turns, record, opts) {
|
|
|
7530
7563
|
"- For each thread give `from`: the message whose outcome it starts from. Usually the message just before the thread's first message; for a detour the message it interrupts (the same); for a new task after a finished one, the last message of the finished thread; null only if the first message would work as the first message of a brand-new chat.",
|
|
7531
7564
|
"",
|
|
7532
7565
|
"Then write a branch summary for every thread (asked: what the user wanted; outcome: what it meant and what came of it; open: what is still open, or empty), and leftOff: one line saying where the session left off, so a later session can decide whether to continue it.",
|
|
7566
|
+
"Outcome, open and leftOff describe the state at the end: read each thread to its last message, and the whole session to its last messages. A problem or task a later message fixed, finished or dropped is done, not open; never report a mid-session state as where things stand.",
|
|
7533
7567
|
`Write text in ${writing}; JSON keys stay English. Never invent what is not in the record. Never write secrets, credentials or personal data.`,
|
|
7534
7568
|
"",
|
|
7535
7569
|
...candidatePart(opts.candidates),
|
|
@@ -7743,7 +7777,9 @@ function headlessEnv(env) {
|
|
|
7743
7777
|
function claudeBin() {
|
|
7744
7778
|
return process.platform === "win32" ? "claude.cmd" : "claude";
|
|
7745
7779
|
}
|
|
7780
|
+
var CLAUDE_TIMEOUT_MS = 5 * 6e4;
|
|
7746
7781
|
function runClaude(prompt, model, env) {
|
|
7782
|
+
const limit = Number(env.COGIT_CLAUDE_TIMEOUT_MS) || CLAUDE_TIMEOUT_MS;
|
|
7747
7783
|
return new Promise((resolve12, reject) => {
|
|
7748
7784
|
const child = spawn(
|
|
7749
7785
|
claudeBin(),
|
|
@@ -7760,6 +7796,11 @@ function runClaude(prompt, model, env) {
|
|
|
7760
7796
|
);
|
|
7761
7797
|
let out = "";
|
|
7762
7798
|
let err = "";
|
|
7799
|
+
let timedOut = false;
|
|
7800
|
+
const timer = setTimeout(() => {
|
|
7801
|
+
timedOut = true;
|
|
7802
|
+
child.kill("SIGKILL");
|
|
7803
|
+
}, limit);
|
|
7763
7804
|
child.stdin.on("error", () => {
|
|
7764
7805
|
});
|
|
7765
7806
|
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
@@ -7769,10 +7810,14 @@ function runClaude(prompt, model, env) {
|
|
|
7769
7810
|
err += chunk;
|
|
7770
7811
|
});
|
|
7771
7812
|
child.on("error", (error) => {
|
|
7813
|
+
clearTimeout(timer);
|
|
7772
7814
|
reject(new CliError(`could not run claude: ${error.message}`));
|
|
7773
7815
|
});
|
|
7774
7816
|
child.on("close", (code) => {
|
|
7775
|
-
|
|
7817
|
+
clearTimeout(timer);
|
|
7818
|
+
if (timedOut)
|
|
7819
|
+
reject(new CliError(`claude did not answer within ${Math.round(limit / 1e3)} s`));
|
|
7820
|
+
else if (code === 0) resolve12(out);
|
|
7776
7821
|
else reject(new CliError(`claude exited with ${code}: ${err.trim() || "no output"}`));
|
|
7777
7822
|
});
|
|
7778
7823
|
child.stdin.end(prompt);
|
|
@@ -7834,7 +7879,7 @@ async function backfillTail(dir, sessionId, io, opts = {}) {
|
|
|
7834
7879
|
}
|
|
7835
7880
|
|
|
7836
7881
|
// src/commands/blame.ts
|
|
7837
|
-
import { stat as
|
|
7882
|
+
import { stat as stat4 } from "fs/promises";
|
|
7838
7883
|
import { isAbsolute, relative as relative2, resolve as resolve6, sep as sep2 } from "path";
|
|
7839
7884
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
7840
7885
|
function parseBlameTarget(target) {
|
|
@@ -7867,7 +7912,7 @@ async function blame(opts) {
|
|
|
7867
7912
|
const path3 = relative2(gitRoot, absolute);
|
|
7868
7913
|
if (path3 === "" || path3.startsWith("..") || isAbsolute(path3))
|
|
7869
7914
|
throw new CliError(`${file} is outside ${gitRoot}`);
|
|
7870
|
-
const exists = await
|
|
7915
|
+
const exists = await stat4(absolute).then(
|
|
7871
7916
|
(info) => info.isFile(),
|
|
7872
7917
|
() => false
|
|
7873
7918
|
);
|
|
@@ -8808,7 +8853,7 @@ async function logFile(opts) {
|
|
|
8808
8853
|
}
|
|
8809
8854
|
|
|
8810
8855
|
// src/commands/migrate.ts
|
|
8811
|
-
import { copyFile, mkdir as mkdir3, readdir } from "fs/promises";
|
|
8856
|
+
import { copyFile, mkdir as mkdir3, readdir as readdir2 } from "fs/promises";
|
|
8812
8857
|
import { join as join3 } from "path";
|
|
8813
8858
|
var BACKUP_DIR = "backup-pre-8.5";
|
|
8814
8859
|
async function migrateIds(dir, io) {
|
|
@@ -8865,7 +8910,7 @@ async function migrateIds(dir, io) {
|
|
|
8865
8910
|
}
|
|
8866
8911
|
async function backupRecords(sessions, backup) {
|
|
8867
8912
|
await mkdir3(backup, { recursive: true });
|
|
8868
|
-
for (const entry of await
|
|
8913
|
+
for (const entry of await readdir2(sessions)) {
|
|
8869
8914
|
if (!entry.endsWith(".json") && !entry.endsWith(".events.jsonl")) continue;
|
|
8870
8915
|
await copyFile(join3(sessions, entry), join3(backup, entry));
|
|
8871
8916
|
}
|
|
@@ -10341,8 +10386,8 @@ async function search(opts) {
|
|
|
10341
10386
|
|
|
10342
10387
|
// src/commands/why.ts
|
|
10343
10388
|
import { existsSync as existsSync3 } from "fs";
|
|
10344
|
-
import { stat as
|
|
10345
|
-
import { dirname as
|
|
10389
|
+
import { stat as stat5 } from "fs/promises";
|
|
10390
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, relative as relative4, resolve as resolve10, sep as sep4 } from "path";
|
|
10346
10391
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
10347
10392
|
function dayStamp(iso) {
|
|
10348
10393
|
const date = new Date(iso);
|
|
@@ -10443,9 +10488,9 @@ function formatWhy(hits) {
|
|
|
10443
10488
|
return lines;
|
|
10444
10489
|
}
|
|
10445
10490
|
async function nearestProjectDir(start) {
|
|
10446
|
-
for (let dir = resolve10(start); ; dir =
|
|
10491
|
+
for (let dir = resolve10(start); ; dir = dirname4(dir)) {
|
|
10447
10492
|
if (existsSync3(projectPaths(dir).project) || existsSync3(join5(dir, ".git"))) return dir;
|
|
10448
|
-
if (
|
|
10493
|
+
if (dirname4(dir) === dir) return void 0;
|
|
10449
10494
|
}
|
|
10450
10495
|
}
|
|
10451
10496
|
async function why(opts) {
|
|
@@ -10462,7 +10507,7 @@ async function why(opts) {
|
|
|
10462
10507
|
if (!project.ok) throw new CliError(project.error);
|
|
10463
10508
|
if (await findGitRoot(root) === void 0)
|
|
10464
10509
|
throw new CliError(`${root} is not inside a git repository; why needs one`);
|
|
10465
|
-
const exists = await
|
|
10510
|
+
const exists = await stat5(resolve10(root, file)).then(
|
|
10466
10511
|
(info) => info.isFile(),
|
|
10467
10512
|
() => false
|
|
10468
10513
|
);
|
|
@@ -10524,8 +10569,8 @@ function tryBeforeNote(file, hits) {
|
|
|
10524
10569
|
|
|
10525
10570
|
// src/hooks/run.ts
|
|
10526
10571
|
import { spawn as spawn5 } from "child_process";
|
|
10527
|
-
import { closeSync, existsSync as existsSync4, mkdirSync, openSync, statSync, writeSync } from "fs";
|
|
10528
|
-
import { dirname as
|
|
10572
|
+
import { closeSync, existsSync as existsSync4, mkdirSync, openSync, statSync as statSync2, writeSync } from "fs";
|
|
10573
|
+
import { dirname as dirname6 } from "path";
|
|
10529
10574
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10530
10575
|
|
|
10531
10576
|
// src/hooks/pretooluse.ts
|
|
@@ -10604,17 +10649,17 @@ async function onSessionEnd(ctx) {
|
|
|
10604
10649
|
}
|
|
10605
10650
|
|
|
10606
10651
|
// src/cli-version.ts
|
|
10607
|
-
import { readFile as readFile6, realpath, stat as
|
|
10608
|
-
import { delimiter, dirname as
|
|
10652
|
+
import { readFile as readFile6, realpath, stat as stat6 } from "fs/promises";
|
|
10653
|
+
import { delimiter, dirname as dirname5, join as join6 } from "path";
|
|
10609
10654
|
var PACKAGE = "@cogit/cli";
|
|
10610
10655
|
async function installedCliVersion(env) {
|
|
10611
10656
|
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
10612
10657
|
if (dir === "") continue;
|
|
10613
10658
|
const bin = join6(dir, "cogit");
|
|
10614
10659
|
try {
|
|
10615
|
-
if (!(await
|
|
10660
|
+
if (!(await stat6(bin)).isFile()) continue;
|
|
10616
10661
|
const pkg = JSON.parse(
|
|
10617
|
-
await readFile6(join6(
|
|
10662
|
+
await readFile6(join6(dirname5(dirname5(await realpath(bin))), "package.json"), "utf8")
|
|
10618
10663
|
);
|
|
10619
10664
|
if (pkg.name === PACKAGE && typeof pkg.version === "string") return pkg.version;
|
|
10620
10665
|
return void 0;
|
|
@@ -10642,6 +10687,55 @@ function versionNotice(running, installed) {
|
|
|
10642
10687
|
return void 0;
|
|
10643
10688
|
}
|
|
10644
10689
|
|
|
10690
|
+
// src/hooks/close-retry.ts
|
|
10691
|
+
import { statSync } from "fs";
|
|
10692
|
+
var CLOSE_RETRY_IDLE_MS = 10 * 6e4;
|
|
10693
|
+
var CLOSE_RETRY_WINDOW_MS = 3 * 24 * 60 * 6e4;
|
|
10694
|
+
var CLOSE_RETRY_LIMIT = 2;
|
|
10695
|
+
var CLOSE_RETRY_BATCH = 3;
|
|
10696
|
+
async function retryUnclosed(ctx) {
|
|
10697
|
+
const config = await loadConfig(ctx.env);
|
|
10698
|
+
if (!config.sessionEndCatchUp) return;
|
|
10699
|
+
const now = Date.parse(ctx.now);
|
|
10700
|
+
const sessions = await loadAllSessions(ctx.root, () => {
|
|
10701
|
+
});
|
|
10702
|
+
const candidates = sessions.filter((session) => session.sessionId !== ctx.sessionId && unclosed(session, now)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
10703
|
+
let started = 0;
|
|
10704
|
+
for (const session of candidates) {
|
|
10705
|
+
if (started >= CLOSE_RETRY_BATCH) break;
|
|
10706
|
+
const id = session.sessionId;
|
|
10707
|
+
const transcript = await ctx.agent.findTranscript(id, ctx.env);
|
|
10708
|
+
const written = transcript ? statSync(transcript, { throwIfNoEntry: false })?.mtimeMs : void 0;
|
|
10709
|
+
if (written !== void 0 && now - written < CLOSE_RETRY_IDLE_MS) continue;
|
|
10710
|
+
let allowed = false;
|
|
10711
|
+
await updateState(ctx.root, id, (cur) => {
|
|
10712
|
+
const tries = cur?.closeRetries ?? 0;
|
|
10713
|
+
if (tries >= CLOSE_RETRY_LIMIT) return cur ?? emptyState2();
|
|
10714
|
+
allowed = true;
|
|
10715
|
+
return { ...cur ?? emptyState2(), closeRetries: tries + 1 };
|
|
10716
|
+
});
|
|
10717
|
+
if (!allowed) continue;
|
|
10718
|
+
ctx.spawnDetached(["backfill", id, "--tail", "--close", "-C", ctx.root]);
|
|
10719
|
+
started += 1;
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
function unclosed(session, now) {
|
|
10723
|
+
if (session.leftOff !== void 0) return false;
|
|
10724
|
+
if (!session.nodes.some((node) => node.kind === "message")) return false;
|
|
10725
|
+
const age = now - Date.parse(session.updatedAt);
|
|
10726
|
+
return age >= CLOSE_RETRY_IDLE_MS && age <= CLOSE_RETRY_WINDOW_MS;
|
|
10727
|
+
}
|
|
10728
|
+
function emptyState2() {
|
|
10729
|
+
return {
|
|
10730
|
+
schema: STATE_SCHEMA,
|
|
10731
|
+
turns: 0,
|
|
10732
|
+
turnsSinceUpdate: 0,
|
|
10733
|
+
recordUpdatedAt: null,
|
|
10734
|
+
turnsSinceSide: 0,
|
|
10735
|
+
notedFiles: []
|
|
10736
|
+
};
|
|
10737
|
+
}
|
|
10738
|
+
|
|
10645
10739
|
// src/hooks/sessionstart.ts
|
|
10646
10740
|
function compactRequestText(skill) {
|
|
10647
10741
|
return `cogit: this session was compacted and some of its user messages are not in the lineage record yet. Run the ${skill} skill now; \`session show\` lists the messages that have no node. It records only what is missing and replies "up to date" when nothing is; do not mention this request otherwise.`;
|
|
@@ -10675,6 +10769,7 @@ async function sessionStartOutput(ctx) {
|
|
|
10675
10769
|
if (await findGitRoot(ctx.root) === void 0) return void 0;
|
|
10676
10770
|
return additionalContext(ctx, setupHint(ctx));
|
|
10677
10771
|
}
|
|
10772
|
+
await retryUnclosed(ctx).catch(() => void 0);
|
|
10678
10773
|
const config = await loadConfig(ctx.env);
|
|
10679
10774
|
if (!config.startupContext) return void 0;
|
|
10680
10775
|
return additionalContext(ctx, await startupContext(ctx, config.recordCommit));
|
|
@@ -10856,10 +10951,10 @@ var HOOK_EVENTS = Object.keys(HANDLERS);
|
|
|
10856
10951
|
var LOG_LIMIT = 256 * 1024;
|
|
10857
10952
|
function openLog(log2, args) {
|
|
10858
10953
|
try {
|
|
10859
|
-
mkdirSync(
|
|
10954
|
+
mkdirSync(dirname6(log2), { recursive: true });
|
|
10860
10955
|
let flag = "a";
|
|
10861
10956
|
try {
|
|
10862
|
-
if (
|
|
10957
|
+
if (statSync2(log2).size > LOG_LIMIT) flag = "w";
|
|
10863
10958
|
} catch {
|
|
10864
10959
|
}
|
|
10865
10960
|
const fd = openSync(log2, flag);
|