@cogit/cli 0.29.0 → 0.31.0

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cogit",
3
3
  "displayName": "cogit",
4
- "version": "0.29.0",
4
+ "version": "0.31.0",
5
5
  "description": "Git remembers what changed. cogit remembers why.",
6
6
  "author": {
7
7
  "name": "Eren Sertkaya"
package/README.md CHANGED
@@ -11,6 +11,9 @@ asked, what came of it, and the message it answered. Your code commits point bac
11
11
  thought behind them, so `git log` tells you what changed and cogit tells you why. It works with
12
12
  Claude Code today.
13
13
 
14
+ It runs as a Claude Code plugin and hooks in at SessionStart, Stop and SessionEnd, so the
15
+ record is written while you work, not reconstructed from the diff afterwards.
16
+
14
17
  <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">
15
18
 
16
19
  *Your sessions as a git graph — one row per message, the decision behind it, and where you left
@@ -26,6 +29,15 @@ what the change does, and the diff.*
26
29
  *Tasks — what your sessions left undone, drafted when a session closes. You accept, dismiss or
27
30
  close each one.*
28
31
 
32
+ <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
+
34
+ *A changed file opens its history: every message that touched it, and the reason behind each —
35
+ including the option that was rejected.*
36
+
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
+
29
41
  ## What it does
30
42
 
31
43
  - **Every message becomes a thought commit**: the question, the outcome, the decisions with
@@ -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-UMAVUEBJ.js");
21209
+ const { installGitHooks: installGitHooks2 } = await import("./git-hooks-SGXDRKNY.js");
21210
21210
  await installGitHooks2(dir).catch(() => void 0);
21211
21211
  return true;
21212
21212
  }
@@ -24044,6 +24044,9 @@ var exec4 = promisify3(execFile3);
24044
24044
  var THOUGHT_TRAILER = "Thought";
24045
24045
  var THOUGHT_VALUE = /^h[0-9a-f]{12}$/;
24046
24046
  var FULL_HASH = /^[0-9a-f]{40}$/;
24047
+ var THOUGHT_LINE = new RegExp(`^[ \\t]*${THOUGHT_TRAILER}:[ \\t]*(h[0-9a-f]{12})[ \\t]*$`, "gim");
24048
+ var COMMIT_MARK = "";
24049
+ var LOG_MAX_BUFFER = 256 * 1024 * 1024;
24047
24050
  async function readable(path2) {
24048
24051
  return access2(path2).then(
24049
24052
  () => true,
@@ -24070,10 +24073,23 @@ async function writeTrailer(messageFile, value) {
24070
24073
  messagePath(messageFile)
24071
24074
  ]);
24072
24075
  }
24076
+ async function replayingCommits(dir, env) {
24077
+ let gitDir;
24078
+ try {
24079
+ const { stdout } = await exec4("git", ["-C", dir, "rev-parse", "--absolute-git-dir"], { env });
24080
+ gitDir = String(stdout).trim();
24081
+ } catch {
24082
+ return false;
24083
+ }
24084
+ const states = ["rebase-merge", "rebase-apply", "CHERRY_PICK_HEAD"];
24085
+ const found = await Promise.all(states.map((state) => readable(resolve3(gitDir, state))));
24086
+ return found.includes(true);
24087
+ }
24073
24088
  async function addThoughtTrailer(opts) {
24074
24089
  const env = opts.env ?? process.env;
24075
24090
  if (captureDisabled(env)) return false;
24076
24091
  if (opts.source === "merge" || opts.source === "squash") return false;
24092
+ if (await replayingCommits(opts.dir, env)) return false;
24077
24093
  const agent = defaultAgent();
24078
24094
  const named = (env[agent.commitSessionEnv] ?? "").trim();
24079
24095
  if (named !== "") {
@@ -24101,17 +24117,14 @@ async function addThoughtTrailer(opts) {
24101
24117
  }
24102
24118
  function parseThoughtTrailers(stdout) {
24103
24119
  const out = /* @__PURE__ */ new Map();
24104
- for (const line of stdout.split("\n")) {
24105
- const separator = line.indexOf("\0");
24120
+ for (const record2 of stdout.split(COMMIT_MARK)) {
24121
+ const separator = record2.indexOf("\0");
24106
24122
  if (separator === -1) continue;
24107
- const commit = line.slice(0, separator);
24123
+ const commit = record2.slice(0, separator);
24108
24124
  if (!FULL_HASH.test(commit)) continue;
24109
- for (const value of line.slice(separator + 1).split(/[,\s]+/)) {
24110
- if (THOUGHT_VALUE.test(value)) {
24111
- out.set(commit, value);
24112
- break;
24113
- }
24114
- }
24125
+ const body = record2.slice(separator + 1);
24126
+ const thoughts = [...body.matchAll(THOUGHT_LINE)].map((match) => match[1]);
24127
+ if (thoughts.length > 0) out.set(commit, [...new Set(thoughts)]);
24115
24128
  }
24116
24129
  return out;
24117
24130
  }
@@ -24122,15 +24135,17 @@ async function readThoughtTrailers(root, opts = {}) {
24122
24135
  "-C",
24123
24136
  gitRoot,
24124
24137
  "log",
24125
- "--format=%H%x00%(trailers:key=Thought,valueonly)",
24138
+ "--format=%x01%H%x00%B",
24139
+ "--extended-regexp",
24140
+ "--regexp-ignore-case",
24141
+ `--grep=^[[:space:]]*${THOUGHT_TRAILER}:`,
24126
24142
  ...opts.since === void 0 ? [] : [`--since=${opts.since}`]
24127
24143
  ];
24128
24144
  try {
24129
- const { stdout } = await exec4(
24130
- "git",
24131
- args,
24132
- opts.env === void 0 ? void 0 : { env: opts.env }
24133
- );
24145
+ const { stdout } = await exec4("git", args, {
24146
+ maxBuffer: LOG_MAX_BUFFER,
24147
+ ...opts.env === void 0 ? {} : { env: opts.env }
24148
+ });
24134
24149
  return parseThoughtTrailers(String(stdout));
24135
24150
  } catch {
24136
24151
  return /* @__PURE__ */ new Map();
@@ -24158,19 +24173,21 @@ function earliestSessionStart(sessions) {
24158
24173
  function sameCommit(recorded, commit) {
24159
24174
  return commit.startsWith(recorded) || recorded.startsWith(commit);
24160
24175
  }
24161
- function thoughtOf(recorded, trailers) {
24162
- for (const [commit, thought] of trailers) {
24163
- if (sameCommit(recorded, commit)) return thought;
24176
+ function thoughtsOf(recorded, trailers) {
24177
+ for (const [commit, thoughts] of trailers) {
24178
+ if (sameCommit(recorded, commit)) return thoughts;
24164
24179
  }
24165
24180
  return void 0;
24166
24181
  }
24167
24182
  function attachThoughtTrailers(sessions, trailers) {
24168
24183
  if (trailers.size === 0) return [...sessions];
24169
24184
  const byThought = /* @__PURE__ */ new Map();
24170
- for (const [commit, thought] of trailers) {
24171
- const list = byThought.get(thought);
24172
- if (list === void 0) byThought.set(thought, [commit]);
24173
- else if (!list.some((hash2) => sameCommit(hash2, commit))) list.push(commit);
24185
+ for (const [commit, thoughts] of trailers) {
24186
+ for (const thought of thoughts) {
24187
+ const list = byThought.get(thought);
24188
+ if (list === void 0) byThought.set(thought, [commit]);
24189
+ else if (!list.some((hash2) => sameCommit(hash2, commit))) list.push(commit);
24190
+ }
24174
24191
  }
24175
24192
  return sessions.map((session) => {
24176
24193
  let changed = false;
@@ -24178,8 +24195,8 @@ function attachThoughtTrailers(sessions, trailers) {
24178
24195
  if (node2.kind !== "message") return node2;
24179
24196
  const current = node2.commits ?? [];
24180
24197
  const kept = current.filter((hash2) => {
24181
- const thought = thoughtOf(hash2, trailers);
24182
- return thought === void 0 || thought === node2.id;
24198
+ const thoughts = thoughtsOf(hash2, trailers);
24199
+ return thoughts === void 0 || thoughts.includes(node2.id);
24183
24200
  });
24184
24201
  const claimed = (byThought.get(node2.id) ?? []).filter(
24185
24202
  (commit) => !kept.some((hash2) => sameCommit(hash2, commit))
@@ -25090,7 +25107,9 @@ async function show(opts) {
25090
25107
  ` ${candidate.sessionId} \xB7 ${candidate.label}${candidate.id === candidate.label ? "" : ` (${candidate.id})`} ${candidate.title}`
25091
25108
  );
25092
25109
  }
25093
- throw new CliError(`"${opts.ref}" is ambiguous; use a longer prefix`);
25110
+ throw new CliError(
25111
+ COMMIT_REF.test(opts.ref) ? `commit ${opts.ref} comes from several thoughts; open one by its message hash` : `"${opts.ref}" is ambiguous; use a longer prefix`
25112
+ );
25094
25113
  }
25095
25114
  const { session, messages, message } = result.hit;
25096
25115
  const sessionId = session.sessionId;
@@ -25559,7 +25578,7 @@ async function onSessionEnd(ctx) {
25559
25578
  // package.json
25560
25579
  var package_default = {
25561
25580
  name: "@cogit/cli",
25562
- version: "0.29.0",
25581
+ version: "0.31.0",
25563
25582
  description: "Git remembers what changed. cogit remembers why.",
25564
25583
  license: "MIT",
25565
25584
  keywords: [