@cogit/cli 0.31.0 → 0.31.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.
package/README.md
CHANGED
|
@@ -8,11 +8,47 @@ Git remembers what changed. cogit remembers why.
|
|
|
8
8
|
|
|
9
9
|
cogit records every message you send to an AI coding agent as a *thought commit*: what you
|
|
10
10
|
asked, what came of it, and the message it answered. Your code commits point back to the
|
|
11
|
-
thought behind them, so `
|
|
12
|
-
Claude Code today.
|
|
11
|
+
thought behind them, so `cogit why` takes any line of code back to the reason it exists. It works
|
|
12
|
+
with Claude Code today.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Agent memory helps your agent remember. cogit helps you understand.
|
|
15
|
+
|
|
16
|
+
It runs as a Claude Code plugin and works through Claude Code hooks, so the record is written
|
|
17
|
+
while you work, not reconstructed from the diff afterwards.
|
|
18
|
+
|
|
19
|
+
**[Click through a saved map](https://erensrtky.github.io/cogit/demo/)**: no install, no server.
|
|
20
|
+
It's the page `cogit open` writes, built from sample data and read-only.
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
I started by copying code out of ChatGPT, then moved to browser tools, editor extensions and now
|
|
25
|
+
terminal agents. Each step moved the code further from me, and I kept feeling something was
|
|
26
|
+
slipping away. When you write code yourself, it sinks in bit by bit: you know why that table
|
|
27
|
+
exists and why you wrote that class. When an agent writes it, that feeling goes away.
|
|
28
|
+
|
|
29
|
+
The information isn't really gone. It sits in your messages, the agent's replies and your commits,
|
|
30
|
+
just in separate places. cogit ties them together, so you can always walk back and see why
|
|
31
|
+
something was done. It helps most when you juggle projects: I run 5-10 agents at once across 2-3
|
|
32
|
+
of them, and a few minutes later I can lose track of what I did where.
|
|
33
|
+
|
|
34
|
+
<img src="docs/images/why-abstraction.png" alt="A conceptual chart from machine code to software factories: abstraction keeps rising, while how much of the why you can still trace falls sharply once AI starts writing code. cogit keeps what falls away.">
|
|
35
|
+
|
|
36
|
+
It's bigger than one person's memory: a reviewer sees what changed, not why.
|
|
37
|
+
[More, with research and sources](docs/guide/why.md).
|
|
38
|
+
|
|
39
|
+
cogit isn't:
|
|
40
|
+
|
|
41
|
+
- **Agent memory** (OpenViking, Mem0): that helps your agent recall what you talked about.
|
|
42
|
+
- **A token saver** (RTK, Headroom): that sends the model less.
|
|
43
|
+
- **A code map** (graphify): that shows how a codebase fits together.
|
|
44
|
+
- **An orchestrator** (Herdr, Conductor): that runs many agents at once.
|
|
45
|
+
- **A planning framework** (BMAD, Spec Kit): that decides what to build next.
|
|
46
|
+
- **An AI review bot** (CodeRabbit, Greptile): that comments on whether a diff looks right.
|
|
47
|
+
|
|
48
|
+
It answers the one question they leave open: why is this code the way it is? I use several of
|
|
49
|
+
them alongside it.
|
|
50
|
+
|
|
51
|
+
## See it
|
|
16
52
|
|
|
17
53
|
<img src="docs/images/map.png" alt="The map: sessions as a git graph, each with its messages, branches, commit chips and a left off here marker">
|
|
18
54
|
|
|
@@ -26,18 +62,14 @@ what the change does, and the diff.*
|
|
|
26
62
|
|
|
27
63
|
<img src="docs/images/tasks.png" alt="The Tasks panel: suggested items with Accept and Dismiss, open items with Done, and a Done section">
|
|
28
64
|
|
|
29
|
-
*Tasks —
|
|
30
|
-
close each one.*
|
|
65
|
+
*Tasks — work that came up in a session and didn't get finished, caught when the session closes.
|
|
66
|
+
You accept, dismiss or close each one.*
|
|
31
67
|
|
|
32
68
|
<img src="docs/images/files.png" alt="The Files view: a changed file opened, listing the messages that touched it with each message's reason and the option it rejected">
|
|
33
69
|
|
|
34
70
|
*A changed file opens its history: every message that touched it, and the reason behind each —
|
|
35
71
|
including the option that was rejected.*
|
|
36
72
|
|
|
37
|
-
**[Click through a saved map](https://erensrtky.github.io/cogit/demo/)** — no install, no server:
|
|
38
|
-
the same page `cogit open` writes, built from sample data. Read-only, so the Tasks buttons are
|
|
39
|
-
inert.
|
|
40
|
-
|
|
41
73
|
## What it does
|
|
42
74
|
|
|
43
75
|
- **Every message becomes a thought commit**: the question, the outcome, the decisions with
|
|
@@ -50,25 +82,21 @@ inert.
|
|
|
50
82
|
- **Intent, Shape, Code**: a message that landed commits shows why it happened, a small
|
|
51
83
|
picture of what the change does (a diagram, a before/after or interface table, an example
|
|
52
84
|
or plain steps), and the diff, in one place.
|
|
53
|
-
- **Tasks**: when a session ends, a small model
|
|
54
|
-
|
|
85
|
+
- **Tasks**: when a session ends, a small model catches the work that came up and didn't get
|
|
86
|
+
finished as a few user-level items; you accept, dismiss or close them (`cogit captures`, and
|
|
87
|
+
on the map).
|
|
55
88
|
- **Pull request descriptions with the decisions**: `cogit pr` writes the goal, the
|
|
56
89
|
decisions and the rejected options, never the raw chat.
|
|
57
90
|
- **Teams share the record through git**: after a pull, your teammates' sessions are on the
|
|
58
91
|
map too, with who ran them and what changed most.
|
|
59
|
-
- **The agent remembers**: a new session starts where the last one left off, and before
|
|
60
|
-
editing a file the agent hears what was already tried and rejected there.
|
|
61
92
|
- **Cheap by default**: a small side model records in the background; in our sessions it
|
|
62
93
|
cost about 1–2% of a session's tokens.
|
|
63
|
-
- **
|
|
64
|
-
and
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
718 firms, they opened 23% more pull requests while review time rose 49% and finished work did
|
|
70
|
-
not measurably grow. A reviewer sees what changed, not why; the why stays in a chat nobody else
|
|
71
|
-
reads. cogit keeps it next to the code. [More, with sources](docs/guide/why.md).
|
|
94
|
+
- **No server, no account**: it runs on your machine. Your chats are not committed. The record
|
|
95
|
+
(titles, decisions and reasons) is, so it travels with your branch; `cogit init` lets you keep
|
|
96
|
+
it local instead. Key and token patterns are redacted.
|
|
97
|
+
- **The agent gets the context too**: a new session starts where the last one left off, and
|
|
98
|
+
before editing a file the agent hears what was already tried and rejected there. It works
|
|
99
|
+
alongside memory tools; it doesn't replace them.
|
|
72
100
|
|
|
73
101
|
## Install
|
|
74
102
|
|
|
@@ -21206,7 +21206,7 @@ async function migrateProject(root) {
|
|
|
21206
21206
|
const next = renameGitignoreLines(text);
|
|
21207
21207
|
if (next !== text) await atomicWrite(gitignore, next).catch(() => void 0);
|
|
21208
21208
|
}
|
|
21209
|
-
const { installGitHooks: installGitHooks2 } = await import("./git-hooks-
|
|
21209
|
+
const { installGitHooks: installGitHooks2 } = await import("./git-hooks-KQ3BN2VC.js");
|
|
21210
21210
|
await installGitHooks2(dir).catch(() => void 0);
|
|
21211
21211
|
return true;
|
|
21212
21212
|
}
|
|
@@ -23793,6 +23793,9 @@ function isStoredTurn(value) {
|
|
|
23793
23793
|
const turn = value;
|
|
23794
23794
|
return typeof turn.uuid === "string" && turn.uuid !== "" && typeof turn.at === "string" && typeof turn.text === "string" && Array.isArray(turn.replies) && turn.replies.every((reply) => typeof reply === "string") && turn.tools !== null && typeof turn.tools === "object";
|
|
23795
23795
|
}
|
|
23796
|
+
function answerSize(turn) {
|
|
23797
|
+
return turn.replies.reduce((sum, reply) => sum + reply.length, 0) + JSON.stringify(turn.tools).length;
|
|
23798
|
+
}
|
|
23796
23799
|
async function readConversationStore(root, sessionId) {
|
|
23797
23800
|
let text;
|
|
23798
23801
|
try {
|
|
@@ -23819,16 +23822,28 @@ async function readConversationStore(root, sessionId) {
|
|
|
23819
23822
|
order.push(value.uuid);
|
|
23820
23823
|
continue;
|
|
23821
23824
|
}
|
|
23822
|
-
|
|
23825
|
+
const fuller = answerSize(value) > answerSize(prev) ? value : prev;
|
|
23826
|
+
const hash2 = prev.hash ?? value.hash;
|
|
23827
|
+
byUuid.set(value.uuid, {
|
|
23828
|
+
...prev,
|
|
23829
|
+
...hash2 === void 0 ? {} : { hash: hash2 },
|
|
23830
|
+
replies: fuller.replies,
|
|
23831
|
+
tools: fuller.tools
|
|
23832
|
+
});
|
|
23823
23833
|
}
|
|
23824
23834
|
return order.map((uuid3) => byUuid.get(uuid3));
|
|
23825
23835
|
}
|
|
23826
23836
|
async function appendConversationTurns(root, sessionId, turns) {
|
|
23827
|
-
const
|
|
23837
|
+
const stored = new Map(
|
|
23838
|
+
(await readConversationStore(root, sessionId)).map((turn) => [turn.uuid, turn])
|
|
23839
|
+
);
|
|
23840
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23828
23841
|
const fresh = [];
|
|
23829
23842
|
for (const turn of turns) {
|
|
23830
|
-
if (turn.uuid === void 0 ||
|
|
23831
|
-
|
|
23843
|
+
if (turn.uuid === void 0 || seen.has(turn.uuid)) continue;
|
|
23844
|
+
seen.add(turn.uuid);
|
|
23845
|
+
const held = stored.get(turn.uuid);
|
|
23846
|
+
if (held && answerSize(turn) <= answerSize(held)) continue;
|
|
23832
23847
|
fresh.push({
|
|
23833
23848
|
uuid: turn.uuid,
|
|
23834
23849
|
...turn.hash === void 0 ? {} : { hash: turn.hash },
|
|
@@ -23839,13 +23854,17 @@ async function appendConversationTurns(root, sessionId, turns) {
|
|
|
23839
23854
|
});
|
|
23840
23855
|
}
|
|
23841
23856
|
if (fresh.length === 0) return 0;
|
|
23842
|
-
const
|
|
23857
|
+
const value = redactDeep(fresh).value.filter((turn) => {
|
|
23858
|
+
const held = stored.get(turn.uuid);
|
|
23859
|
+
return !held || answerSize(turn) > answerSize(held);
|
|
23860
|
+
});
|
|
23861
|
+
if (value.length === 0) return 0;
|
|
23843
23862
|
const chunk = `${value.map((turn) => JSON.stringify(turn)).join("\n")}
|
|
23844
23863
|
`;
|
|
23845
23864
|
const file2 = projectPaths(root).conversation(sessionId);
|
|
23846
23865
|
await mkdir4(dirname6(file2), { recursive: true });
|
|
23847
23866
|
await appendFile(file2, gzipSync(Buffer.from(chunk, "utf8")));
|
|
23848
|
-
return
|
|
23867
|
+
return value.length;
|
|
23849
23868
|
}
|
|
23850
23869
|
async function appendConversationHashes(root, sessionId, hashes) {
|
|
23851
23870
|
if (hashes.size === 0) return 0;
|
|
@@ -24517,9 +24536,8 @@ function embedViewModel(template, model) {
|
|
|
24517
24536
|
if (!template.includes(EMPTY_DATA_ELEMENT)) {
|
|
24518
24537
|
throw new Error(`the template has no empty <script id="${DATA_ELEMENT_ID}"> element`);
|
|
24519
24538
|
}
|
|
24520
|
-
const
|
|
24521
|
-
|
|
24522
|
-
)}</script>`;
|
|
24539
|
+
const json2 = JSON.stringify(model, null, 1).replace(/\n +/g, "\n");
|
|
24540
|
+
const data = `<script id="${DATA_ELEMENT_ID}" type="application/json">${escapeForScript(json2)}</script>`;
|
|
24523
24541
|
return template.replace(EMPTY_DATA_ELEMENT, () => data);
|
|
24524
24542
|
}
|
|
24525
24543
|
|
|
@@ -25578,7 +25596,7 @@ async function onSessionEnd(ctx) {
|
|
|
25578
25596
|
// package.json
|
|
25579
25597
|
var package_default = {
|
|
25580
25598
|
name: "@cogit/cli",
|
|
25581
|
-
version: "0.31.
|
|
25599
|
+
version: "0.31.1",
|
|
25582
25600
|
description: "Git remembers what changed. cogit remembers why.",
|
|
25583
25601
|
license: "MIT",
|
|
25584
25602
|
keywords: [
|
package/dist/cli.js
CHANGED
|
@@ -121,7 +121,7 @@ import {
|
|
|
121
121
|
versionNotice,
|
|
122
122
|
why,
|
|
123
123
|
withLock
|
|
124
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-DJFQNOZT.js";
|
|
125
125
|
|
|
126
126
|
// node_modules/commander/lib/error.js
|
|
127
127
|
var CommanderError = class extends Error {
|
|
@@ -4194,8 +4194,7 @@ async function readTranscriptViews(sessions, transcripts, root, io) {
|
|
|
4194
4194
|
const file = transcripts.get(session.sessionId);
|
|
4195
4195
|
const recordsMessages = session.nodes.some((node) => node.kind === "message");
|
|
4196
4196
|
const roots = [session.worktree?.path, root].filter((r) => Boolean(r));
|
|
4197
|
-
|
|
4198
|
-
const storedByUuid = new Map(stored.map((turn) => [turn.uuid, turn]));
|
|
4197
|
+
let stored = await readConversationStore(root, session.sessionId);
|
|
4199
4198
|
let transcript = null;
|
|
4200
4199
|
if (file) {
|
|
4201
4200
|
try {
|
|
@@ -4209,21 +4208,18 @@ async function readTranscriptViews(sessions, transcripts, root, io) {
|
|
|
4209
4208
|
}
|
|
4210
4209
|
const fromTranscript = transcript && transcript.length > 0 ? hashTurns(session.sessionId, transcript) : null;
|
|
4211
4210
|
if (fromTranscript) {
|
|
4212
|
-
const fresh = fromTranscript.filter(
|
|
4213
|
-
(turn) => turn.uuid !== void 0 && !storedByUuid.has(turn.uuid)
|
|
4214
|
-
);
|
|
4215
4211
|
const hashes = new Map(
|
|
4216
4212
|
fromTranscript.flatMap(
|
|
4217
4213
|
(turn) => turn.uuid === void 0 ? [] : [[turn.uuid, turn.hash]]
|
|
4218
4214
|
)
|
|
4219
4215
|
);
|
|
4220
4216
|
try {
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4217
|
+
const appended = await appendConversationTurns(
|
|
4218
|
+
root,
|
|
4219
|
+
session.sessionId,
|
|
4220
|
+
fromTranscript.map((turn) => agent3.displayTurn(turn, roots))
|
|
4221
|
+
);
|
|
4222
|
+
if (appended > 0) stored = await readConversationStore(root, session.sessionId);
|
|
4227
4223
|
await appendConversationHashes(root, session.sessionId, hashes);
|
|
4228
4224
|
} catch (err) {
|
|
4229
4225
|
io.warn(
|
|
@@ -4239,6 +4235,7 @@ async function readTranscriptViews(sessions, transcripts, root, io) {
|
|
|
4239
4235
|
nodes: applyEvents(upgraded.nodes, events)
|
|
4240
4236
|
};
|
|
4241
4237
|
prepared.push(effective);
|
|
4238
|
+
const storedByUuid = new Map(stored.map((turn) => [turn.uuid, turn]));
|
|
4242
4239
|
let turns;
|
|
4243
4240
|
if (fromTranscript) {
|
|
4244
4241
|
turns = fromTranscript.map((turn) => {
|
|
@@ -5826,6 +5823,8 @@ function rules(language, messageSummaries, recorder) {
|
|
|
5826
5823
|
"- parent: an earlier message id, or null when the message starts a new thread; never a topic or phase node.",
|
|
5827
5824
|
"- Back to an older thread: that message is the parent and the previous one goes in links (at most 5). Unsure: use the previous message.",
|
|
5828
5825
|
'- When the message itself made a decision (the user asked for it, picked an option or approved a proposal): decidedBy ("user" or "agreed"), the reason in why, and the options not taken in alternatives [{"option": "...", "why": "..."}].',
|
|
5826
|
+
"- alternatives lists only options someone actually named in the conversation before the decision. Never infer one from the decision, never list the current state as an option, and leave the field out when none was named. Its why says what the conversation said: no durations, sizes or reasons it does not contain. An option only postponed was not rejected.",
|
|
5827
|
+
'- decidedBy "agreed" needs the user approving the proposal in this message. A proposal the user has not answered is not a decision.',
|
|
5829
5828
|
`- The agent's work belongs inside the message node that caused it, never in nodes of its own: work (short items), agentDecisions ([{"title": "...", "why": "...", "alternatives": [...]}]) only for technical choices with a reason \u2014 never for proposals or work items \u2014 commits (short hashes) and files (a few paths).`,
|
|
5830
5829
|
"- Write a branch summary ({title, asked, outcome, open}) on the first message of a branch when the branch ends.",
|
|
5831
5830
|
`- Never send "at"; the CLI takes each message's time from the transcript.`,
|
package/package.json
CHANGED
package/skills/record/SKILL.md
CHANGED
|
@@ -55,7 +55,11 @@ transcript is unreadable: send only `status`.
|
|
|
55
55
|
- `summary`: what came of it, ONLY when `messageSummaries` is true; otherwise leave it out,
|
|
56
56
|
like a commit with a subject and no body.
|
|
57
57
|
- A decision: `decidedBy` (`"user"` or `"agreed"`), `why`, `alternatives`
|
|
58
|
-
(`{ "option", "why" }`).
|
|
58
|
+
(`{ "option", "why" }`). `alternatives` lists only options someone actually named in the
|
|
59
|
+
conversation before the decision: never one inferred from the decision, never the current
|
|
60
|
+
state, and no durations, sizes or reasons the conversation does not contain. An option only
|
|
61
|
+
postponed was not rejected. `"agreed"` needs the user approving the proposal in this message;
|
|
62
|
+
a proposal the user has not answered is not a decision.
|
|
59
63
|
- Your work belongs to the message that caused it: `work`, `agentDecisions`
|
|
60
64
|
(`{ "title", "why", "alternatives" }`), `commits` (short hashes), `files` (a few paths).
|
|
61
65
|
Work spanning messages goes on the one it ended in; update an earlier node only to correct it.
|