Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .coherence/runs/task-b53cdddb.jsonl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@posthog/coherence",
"version": "1.7.0",
"version": "1.7.1",
"description": "A prosthetic for proprioception: specs, invariants, and the readings that keep an agent balanced.",
"type": "module",
"license": "MIT",
Expand Down
7 changes: 7 additions & 0 deletions src/journal/Journal.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ Compression: one append-only file per session of attributed, durable outcomes, r
checklist: durable-dispatch-intent dismissed: nothing is dispatched after the append
checklist: declared-target-coverage dismissed: one session file, no fan-out
checklist: completion-evidence dismissed: the append is complete when the call returns, or nothing is written
- a shallow clone refuses only an introduced commit it can tell is none: In a shallow clone, defect --introduced with a commit the clone does not hold is written, because its depth may have cut the commit off; it is refused only when the hash names some other object the clone holds, or more than one; the same check, in commits.ts, answers a practice's cited commit.
over: a depth-1 clone given an older commit its depth cut off, and the hash of a file it holds
via: a shallow clone refuses only an --introduced git can tell is no commit
because: a hosted agent sandbox clones at depth 1, so a defect introduced by any older commit was refused as no commit, though the remote held it; the practice citation check had the same blind spot (review of PR #86), so both now ask git one way
crossing: project-source -> record
refuted: made missingCommits in commits.ts report nothing in a shallow clone, so a file's hash passed as a commit its depth cut off -> "a shallow clone refuses only an --introduced git can tell is no commit" went red in defects.test.ts on the file's hash; restored byte for byte, green batched and alone (2026-10-10)
kinds: none
- a defect is classified by a later record, never edited: classify writes a decision citing the defect with the class, introduced and caught it gives, and every reader folds them oldest first: the defect's own fields, then each classification not retracted, then its close, a later field overriding an earlier one; a field no record gave stays absent and is read as unknown.
over: a defect classified twice and once more by a retracted classification, a defect nobody classified, a classification with no field, and one of a record that is no defect
via: classify folds into the defect without editing it: a later classification overrides, a retracted one gives nothing, and a field nobody gave stays unknown
Expand Down
32 changes: 32 additions & 0 deletions src/journal/commits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* Commits a record or a practice names, asked of git. A shallow clone, such
* as a hosted agent's sandbox at depth 1, does not hold the commits its depth
* cut off, and cannot tell one from a name that is no commit at all: there a
* name is missing only when it names some other object the clone holds, or
* more than one. Outside a git checkout, or when git fails, nothing is known.
*/

import { spawnSync } from "../lifecycle/work-meter.ts";

/** The names among these that are no commit this repository can show; undefined when git cannot be asked. */
export function missingCommits(root: string, commits: readonly string[]): Set<string> | undefined {
if (commits.length === 0) return new Set();
const asCommits = batchCheck(root, commits.map((c) => `${c}^{commit}`));
if (asCommits === undefined) return undefined;
const missing = commits.filter((_, index) => / missing$| ambiguous$/.test(asCommits[index] ?? ""));
if (missing.length === 0 || !isShallow(root)) return new Set(missing);
const asObjects = batchCheck(root, missing);
if (asObjects === undefined) return undefined;
return new Set(missing.filter((_, index) => !/ missing$/.test(asObjects[index] ?? "")));
}

/** git cat-file --batch-check's answer for each name, in order; undefined when git cannot be asked. */
function batchCheck(root: string, names: readonly string[]): string[] | undefined {
const result = spawnSync("git", ["cat-file", "--batch-check"], { cwd: root, input: names.join("\n") + "\n", encoding: "utf8" });
return result.status === 0 ? result.stdout.split("\n") : undefined;
}

function isShallow(root: string): boolean {
const result = spawnSync("git", ["rev-parse", "--is-shallow-repository"], { cwd: root, encoding: "utf8" });
return result.status === 0 && result.stdout.trim() === "true";
}
20 changes: 19 additions & 1 deletion src/journal/defects.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { execFileSync } from "node:child_process";
import { appendFileSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { after, test } from "node:test";
import { after, describe, test } from "node:test";
import { journalVerbs, type Io } from "./cli.ts";
import { defectStates } from "./defects.ts";
import { convergence, convergenceText } from "./convergence.ts";
Expand Down Expand Up @@ -124,6 +124,24 @@ test("a defect carries a declared class, where it came in and what caught it, an
assert.match(j("journal", full).out.join("\n"), /class path-identity, introduced [0-9a-f]{7}, caught review/);
});

describe("a shallow clone refuses only an --introduced git can tell is no commit", () => {
for (const { introduced, refused } of [
{ introduced: "an older commit its depth cut off", refused: false },
{ introduced: "a file it holds", refused: true },
] as const) {
test(`${introduced} is ${refused ? "refused" : "written"}`, () => {
const origin = project({ "knob.ts": "export const knob = 1;\n" });
const older = commitAll(origin, "first", "2026-10-01T10:00:00Z");
commitAll(origin, "second", "2026-10-02T10:00:00Z");
const blob = execFileSync("git", ["rev-parse", "HEAD:knob.ts"], { cwd: origin, encoding: "utf8" }).trim();
const root = made(mkdtempSync(join(tmpdir(), "coherence-defects-shallow-")));
execFileSync("git", ["clone", "-q", "--depth", "1", `file://${origin}`, root]);
const run = journal(root, clock("2026-10-03T00:00:00Z"))("defect", "the knob stuck", "--evidence", "turned it", "--introduced", refused ? blob : older, ...WHO);
assert.equal(run.code, refused ? 1 : 0, run.err.join("\n"));
});
}
});

test("classify folds into the defect without editing it: a later classification overrides, a retracted one gives nothing, and a field nobody gave stays unknown", () => {
const root = project();
const j = journal(root, clock("2026-10-03T00:00:00Z"));
Expand Down
13 changes: 2 additions & 11 deletions src/journal/defects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
* in a family is never a guard failure.
*/

import { spawnSync } from "../lifecycle/work-meter.ts";
import { existsSync, readFileSync } from "node:fs";
import { dirname, isAbsolute, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { JournalError } from "./args.ts";
import { missingCommits } from "./commits.ts";
import { CAUGHT, type Caught, type Decision, type Defect, type DefectOrigin, type JournalRecord, type Resolution } from "./record.ts";

const here = dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -77,15 +77,6 @@ export function declaredFamilies(root: string): Set<string> {
return new Set([...classesIn(COHERENCE_LEXICON, true), ...(project === undefined ? [] : classesIn(project, true))]);
}

/** Whether git holds a commit by this name; undefined when git cannot be asked. */
function commitExists(root: string, sha: string): boolean | undefined {
const result = spawnSync("git", ["cat-file", "-e", `${sha}^{commit}`], { cwd: root, encoding: "utf8" });
if (result.error !== undefined) return undefined;
if (result.status === 0) return true;
const inside = spawnSync("git", ["rev-parse", "--git-dir"], { cwd: root, encoding: "utf8" });
return inside.status === 0 ? false : undefined;
}

/**
* The --class, --introduced and --caught a verb was given, checked: a class
* must be a declared kebab-case name, introduced a commit git holds, PR #<n>,
Expand All @@ -109,7 +100,7 @@ export function originFields(given: { class?: string | undefined; introduced?: s
const value = given.introduced.trim();
if (value === "pre-existing" || value === "unknown" || PULL.test(value)) out.introduced = value;
else if (COMMIT.test(value)) {
if (commitExists(root, value) === false) throw new JournalError(`--introduced ${value}: no commit by that name in this repository`);
if (missingCommits(root, [value])?.has(value)) throw new JournalError(`--introduced ${value}: no commit by that name in this repository`);
out.introduced = value;
} else throw new JournalError(`--introduced "${given.introduced}" reads a commit, PR #<n>, pre-existing or unknown`);
}
Expand Down
Loading
Loading