From 00f1d8d0f0193225af9c6fa84b75bd08a1cbc583 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 9 Oct 2026 23:20:16 +0100 Subject: [PATCH 1/6] A shallow clone leaves a cited commit unchecked A practice's cited commit was a problem whenever git cat-file said missing. In a depth-1 clone, such as a hosted agent sandbox, every older commit is missing, so Stop reported correct citations as "no commit in this repository". When some cited commit is missing, git is now asked if the clone is shallow; if it is, the citations are left unchecked. A full clone, as the workflow ci install writes fetches, still catches a wrong one. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- src/spec/Spec.spec.md | 5 +++++ src/spec/practice.test.ts | 25 +++++++++++++++++++++++++ src/spec/practices.ts | 11 ++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/spec/Spec.spec.md b/src/spec/Spec.spec.md index f3831716..fe1ae580 100644 --- a/src/spec/Spec.spec.md +++ b/src/spec/Spec.spec.md @@ -166,6 +166,11 @@ The spec grammar and the model: one bullet shape, with each bullet's state deriv because: a practice is learned, not wished: what makes it binding is the record of the failure it prevents, and a citation of nothing would make an invented step read as witnessed refuted: made practiceProblems accept a cited record id that no store holds -> "every record a practice cites must exist, and every invariant it names must be declared" went red in practice.test.ts on its own assertion; restored byte for byte, green (2026-10-05) kinds: none +- a shallow clone leaves a cited commit unchecked: A commit a practice cites is no commit only where git can tell: a full clone that does not hold it is a problem, and a shallow clone that does not hold it never is, because its depth may have cut the commit off; git is asked whether the clone is shallow only when some cited commit is missing. + over: a cited commit a full clone holds, one no clone holds, and one a depth-1 clone cut off + via: a cited commit is no commit only where git can tell + because: a hosted agent sandbox clones at depth 1, so every commit an adopter's practices cited read as no commit and Stop reported each one, though the remote held them all; a shallow clone cannot confirm a citation, so it says nothing, and a full clone, as the workflow ci install writes fetches, still catches a wrong one + kinds: none - a leaves line belongs to the step above it: A leaves: line in a practice belongs to the step written above it whatever its indentation: four spaces, two or a tab parse to the same steps and the same version. over: one practice with leaves: under its second step at four spaces, two spaces and a tab via: a leaves: line belongs to the step above it whatever its indentation: four spaces, two, or a tab parse alike diff --git a/src/spec/practice.test.ts b/src/spec/practice.test.ts index 9a05c1c7..8524c555 100644 --- a/src/spec/practice.test.ts +++ b/src/spec/practice.test.ts @@ -173,6 +173,31 @@ test("every record a practice cites must exist, and every invariant it names mus assert.ok(messages.some((m) => /names invariant dial clicks, which its sister spec does not declare/.test(m)), messages.join("\n")); }); +for (const { clone, cites, problem } of [ + { clone: "full", cites: (old: string) => old, problem: false }, + { clone: "full", cites: () => "0123abc", problem: true }, + { clone: "shallow", cites: (old: string) => old, problem: false }, +] as const) { + test(`a cited commit is no commit only where git can tell: ${clone} clone, ${problem ? "a problem" : "no problem"}`, () => { + const origin = project({ "src/widget/Widget.spec.md": SPEC, "src/widget/knob.ts": "export const knob = 1;\n" }); + const git = (cwd: string, ...args: string[]): string => execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@t", ...args], { cwd, encoding: "utf8" }).trim(); + git(origin, "add", "-A"); + git(origin, "commit", "-q", "-m", "the knob seized"); + const old = git(origin, "rev-parse", "--short", "HEAD"); + writeFileSync(join(origin, "src/widget/Widget.practice.md"), practiceText(cites(old))); + git(origin, "add", "-A"); + git(origin, "commit", "-q", "-m", "oil the knob"); + let root = origin; + if (clone === "shallow") { + root = mkdtempSync(join(tmpdir(), "coherence-practice-shallow-")); + projects.push(root); + git(tmpdir(), "clone", "-q", "--depth", "1", `file://${origin}`, root); + } + const messages = loadSpecModel(root, { runs: false }).problems.map((p) => p.message); + assert.equal(messages.some((m) => /which is no commit in this repository/.test(m)), problem, messages.join("\n")); + }); +} + test("an enactment needs an outcome for every step, a because for a deviation or a skip, and keeps the text it enacted", () => { const { root, run } = widget(); const missing = run("enact", "oil the knob", "--step", "1=done", ...WHO); diff --git a/src/spec/practices.ts b/src/spec/practices.ts index 4c30d17c..69f0da46 100644 --- a/src/spec/practices.ts +++ b/src/spec/practices.ts @@ -109,7 +109,10 @@ export function kernelPractices(enactments: readonly Enactment[] = []): ModelPra return out; } -/** The commits among the citations that this repository does not hold; undefined when git cannot be asked. */ +/** + * The commits among the citations that this repository does not hold; undefined when git cannot be asked, + * or when the clone is shallow and cannot tell a commit cut off by its depth from no commit. + */ function missingCommits(root: string, commits: readonly string[]): Set | undefined { if (commits.length === 0) return new Set(); const result = spawnSync("git", ["cat-file", "--batch-check"], { cwd: root, input: commits.map((c) => `${c}^{commit}`).join("\n") + "\n", encoding: "utf8" }); @@ -118,9 +121,15 @@ function missingCommits(root: string, commits: readonly string[]): Set | result.stdout.split("\n").forEach((line, index) => { if (/ missing$| ambiguous$/.test(line) && commits[index] !== undefined) missing.add(commits[index]!); }); + if (missing.size > 0 && isShallow(root)) return undefined; return missing; } +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"; +} + export interface PracticeChecks { root: string; practices: readonly ModelPractice[]; From c3ad7bb853da042bbed419fc4dc73b381ce1aa23 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 9 Oct 2026 23:29:33 +0100 Subject: [PATCH 2/6] A shallow clone still reports a hash that names no commit here The shallow fallback left every commit citation unchecked once one was missing. But a hash that names a file or a tree the clone holds, or several objects, is no commit at any depth. In a shallow clone, the missing citations are now asked of git again without ^{commit}, and only those that name nothing here stay unchecked. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- src/spec/Spec.spec.md | 6 +++--- src/spec/practice.test.ts | 14 ++++++++------ src/spec/practices.ts | 26 ++++++++++++++++---------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/spec/Spec.spec.md b/src/spec/Spec.spec.md index fe1ae580..4f9bdb64 100644 --- a/src/spec/Spec.spec.md +++ b/src/spec/Spec.spec.md @@ -166,10 +166,10 @@ The spec grammar and the model: one bullet shape, with each bullet's state deriv because: a practice is learned, not wished: what makes it binding is the record of the failure it prevents, and a citation of nothing would make an invented step read as witnessed refuted: made practiceProblems accept a cited record id that no store holds -> "every record a practice cites must exist, and every invariant it names must be declared" went red in practice.test.ts on its own assertion; restored byte for byte, green (2026-10-05) kinds: none -- a shallow clone leaves a cited commit unchecked: A commit a practice cites is no commit only where git can tell: a full clone that does not hold it is a problem, and a shallow clone that does not hold it never is, because its depth may have cut the commit off; git is asked whether the clone is shallow only when some cited commit is missing. - over: a cited commit a full clone holds, one no clone holds, and one a depth-1 clone cut off +- a shallow clone leaves a cited commit unchecked: A commit a practice cites is no commit only where git can tell: a full clone that does not hold it is a problem; a shallow clone that does not hold it is a problem only when the hash names some other object the clone holds, or more than one, because its depth may have cut the commit off; git is asked whether the clone is shallow only when some cited commit is missing. + over: a cited commit a full clone holds, one no clone holds, one a depth-1 clone cut off, and a file's hash a depth-1 clone holds via: a cited commit is no commit only where git can tell - because: a hosted agent sandbox clones at depth 1, so every commit an adopter's practices cited read as no commit and Stop reported each one, though the remote held them all; a shallow clone cannot confirm a citation, so it says nothing, and a full clone, as the workflow ci install writes fetches, still catches a wrong one + because: a hosted agent sandbox clones at depth 1, so every commit an adopter's practices cited read as no commit and Stop reported each one, though the remote held them all; a shallow clone cannot confirm a commit it lacks, so it says nothing of one, but a hash that names a file or a tree here, or several objects, is no commit at any depth (review of PR #86); a full clone, as the workflow ci install writes fetches, still catches every wrong one kinds: none - a leaves line belongs to the step above it: A leaves: line in a practice belongs to the step written above it whatever its indentation: four spaces, two or a tab parse to the same steps and the same version. over: one practice with leaves: under its second step at four spaces, two spaces and a tab diff --git a/src/spec/practice.test.ts b/src/spec/practice.test.ts index 8524c555..5ea18c8f 100644 --- a/src/spec/practice.test.ts +++ b/src/spec/practice.test.ts @@ -173,18 +173,20 @@ test("every record a practice cites must exist, and every invariant it names mus assert.ok(messages.some((m) => /names invariant dial clicks, which its sister spec does not declare/.test(m)), messages.join("\n")); }); -for (const { clone, cites, problem } of [ - { clone: "full", cites: (old: string) => old, problem: false }, - { clone: "full", cites: () => "0123abc", problem: true }, - { clone: "shallow", cites: (old: string) => old, problem: false }, +for (const { clone, cited, cites, problem } of [ + { clone: "full", cited: "an older commit", cites: (old: string) => old, problem: false }, + { clone: "full", cited: "a hash nothing has", cites: () => "0123abc", problem: true }, + { clone: "shallow", cited: "an older commit its depth cut off", cites: (old: string) => old, problem: false }, + { clone: "shallow", cited: "a file it holds", cites: (_old: string, blob: string) => blob, problem: true }, ] as const) { - test(`a cited commit is no commit only where git can tell: ${clone} clone, ${problem ? "a problem" : "no problem"}`, () => { + test(`a cited commit is no commit only where git can tell: a ${clone} clone citing ${cited}, ${problem ? "a problem" : "no problem"}`, () => { const origin = project({ "src/widget/Widget.spec.md": SPEC, "src/widget/knob.ts": "export const knob = 1;\n" }); const git = (cwd: string, ...args: string[]): string => execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@t", ...args], { cwd, encoding: "utf8" }).trim(); git(origin, "add", "-A"); git(origin, "commit", "-q", "-m", "the knob seized"); const old = git(origin, "rev-parse", "--short", "HEAD"); - writeFileSync(join(origin, "src/widget/Widget.practice.md"), practiceText(cites(old))); + const blob = git(origin, "rev-parse", "HEAD:src/widget/knob.ts"); + writeFileSync(join(origin, "src/widget/Widget.practice.md"), practiceText(cites(old, blob))); git(origin, "add", "-A"); git(origin, "commit", "-q", "-m", "oil the knob"); let root = origin; diff --git a/src/spec/practices.ts b/src/spec/practices.ts index 69f0da46..000baefa 100644 --- a/src/spec/practices.ts +++ b/src/spec/practices.ts @@ -110,19 +110,25 @@ export function kernelPractices(enactments: readonly Enactment[] = []): ModelPra } /** - * The commits among the citations that this repository does not hold; undefined when git cannot be asked, - * or when the clone is shallow and cannot tell a commit cut off by its depth from no commit. + * The commits among the citations that this repository does not hold; undefined when git cannot be asked. + * A shallow clone cannot tell a commit its depth cut off from no commit, so there a citation is missing + * only when it names some other object here, or more than one. */ function missingCommits(root: string, commits: readonly string[]): Set | undefined { if (commits.length === 0) return new Set(); - const result = spawnSync("git", ["cat-file", "--batch-check"], { cwd: root, input: commits.map((c) => `${c}^{commit}`).join("\n") + "\n", encoding: "utf8" }); - if (result.status !== 0) return undefined; - const missing = new Set(); - result.stdout.split("\n").forEach((line, index) => { - if (/ missing$| ambiguous$/.test(line) && commits[index] !== undefined) missing.add(commits[index]!); - }); - if (missing.size > 0 && isShallow(root)) return undefined; - return missing; + 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 { From ae1534cc30454835a8c62dade23e9762e412b835 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 9 Oct 2026 23:52:08 +0100 Subject: [PATCH 3/6] A shallow clone refuses only an --introduced git can tell is no commit defect --introduced asked git cat-file -e ^{commit}, so in a depth-1 clone it refused any older commit as "no commit by that name", the same blind spot the practice citation check had. The shallow-aware check moves from spec/practices.ts to journal/commits.ts, and both the practice check and defect --introduced use it. Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- src/journal/Journal.spec.md | 6 ++++++ src/journal/commits.ts | 32 ++++++++++++++++++++++++++++++++ src/journal/defects.test.ts | 16 ++++++++++++++++ src/journal/defects.ts | 13 ++----------- src/spec/practices.ts | 28 +--------------------------- 5 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 src/journal/commits.ts diff --git a/src/journal/Journal.spec.md b/src/journal/Journal.spec.md index 22b0c446..9677c7ba 100644 --- a/src/journal/Journal.spec.md +++ b/src/journal/Journal.spec.md @@ -187,6 +187,12 @@ 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 + 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 diff --git a/src/journal/commits.ts b/src/journal/commits.ts new file mode 100644 index 00000000..65804360 --- /dev/null +++ b/src/journal/commits.ts @@ -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 | 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"; +} diff --git a/src/journal/defects.test.ts b/src/journal/defects.test.ts index e3f52aac..1785b211 100644 --- a/src/journal/defects.test.ts +++ b/src/journal/defects.test.ts @@ -124,6 +124,22 @@ 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/); }); +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(`a shallow clone refuses only an --introduced git can tell is no commit: ${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")); diff --git a/src/journal/defects.ts b/src/journal/defects.ts index dd289838..5eca0330 100644 --- a/src/journal/defects.ts +++ b/src/journal/defects.ts @@ -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)); @@ -77,15 +77,6 @@ export function declaredFamilies(root: string): Set { 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 #, @@ -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 #, pre-existing or unknown`); } diff --git a/src/spec/practices.ts b/src/spec/practices.ts index 000baefa..95c44308 100644 --- a/src/spec/practices.ts +++ b/src/spec/practices.ts @@ -15,7 +15,6 @@ */ import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs"; -import { spawnSync } from "../lifecycle/work-meter.ts"; import { basename, dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { PRACTICE_SUFFIX, RECORD_ID, globMatches, parsePractices, type Practice } from "./practice.ts"; @@ -24,6 +23,7 @@ import type { Problem } from "./grammar.ts"; import { loadJournal } from "../journal/store.ts"; import { loadWork } from "../journal/work.ts"; import { recordsOnOtherBranches } from "../journal/branches.ts"; +import { missingCommits } from "../journal/commits.ts"; import type { Decision, Enactment, JournalRecord } from "../journal/record.ts"; const here = dirname(fileURLToPath(import.meta.url)); @@ -109,32 +109,6 @@ export function kernelPractices(enactments: readonly Enactment[] = []): ModelPra return out; } -/** - * The commits among the citations that this repository does not hold; undefined when git cannot be asked. - * A shallow clone cannot tell a commit its depth cut off from no commit, so there a citation is missing - * only when it names some other object here, or more than one. - */ -function missingCommits(root: string, commits: readonly string[]): Set | 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"; -} export interface PracticeChecks { root: string; From b8899dd9566f974f7f0824cc12386ff5be2b3a4d Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Sat, 10 Oct 2026 00:02:08 +0100 Subject: [PATCH 4/6] Remove a double blank line left by the moved helpers Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- src/spec/practices.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spec/practices.ts b/src/spec/practices.ts index 95c44308..80dcacae 100644 --- a/src/spec/practices.ts +++ b/src/spec/practices.ts @@ -109,7 +109,6 @@ export function kernelPractices(enactments: readonly Enactment[] = []): ModelPra return out; } - export interface PracticeChecks { root: string; practices: readonly ModelPractice[]; From 1402f8f41d6be5779b140d9dbafb4287eb1ab840 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Sat, 10 Oct 2026 11:00:55 +0100 Subject: [PATCH 5/6] Refute both shallow-clone invariants and make their tests selectable by via MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The parameterized tests gave each case its own name, so no test was named by the via: lines, and run and refute selected nothing. Each loop now sits in a describe block named by its via. Both invariants were then refuted with the break staged, restored byte for byte, and run green batched and alone; the run store records it. The alone run used NODE_OPTIONS=--test-reporter=tap, because Node 24 prints "ℹ pass" by default and the config's testMatch reads "# pass". Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- .coherence/runs/task-b53cdddb.jsonl | 9 +++++ src/journal/Journal.spec.md | 1 + src/journal/defects.test.ts | 34 +++++++++--------- src/spec/Spec.spec.md | 1 + src/spec/practice.test.ts | 56 +++++++++++++++-------------- 5 files changed, 58 insertions(+), 43 deletions(-) create mode 100644 .coherence/runs/task-b53cdddb.jsonl diff --git a/.coherence/runs/task-b53cdddb.jsonl b/.coherence/runs/task-b53cdddb.jsonl new file mode 100644 index 00000000..a57b996c --- /dev/null +++ b/.coherence/runs/task-b53cdddb.jsonl @@ -0,0 +1,9 @@ +{"at":"2026-10-10T09:55:52.528Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":17290,"load":{"average":0.13,"cores":8},"batch":{"ms":16897},"invariants":[{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"fail","refutation":"missing","bypasses":[],"testReferences":0,"files":[],"latency":16897,"reason":"no test ran under the name \"a cited commit is no commit only where git can tell\" in node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-reporter=./src/enforcement/node-test-reporter.ts --test-reporter-destination= --test-name-pattern=<1 names> src/**/*.test.ts","mode":"batched","state":"requirement","enforces":["a cited commit is no commit only where git can tell"]}]} +{"at":"2026-10-10T09:57:05.902Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":17625,"load":{"average":0.33,"cores":8},"batch":{"ms":17243},"invariants":[{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"fail","refutation":"missing","bypasses":[],"testReferences":0,"files":[],"latency":17243,"reason":"2 of 5 tests under \"a cited commit is no commit only where git can tell\" failed: a cited commit is no commit only where git can tell a shallow clone citing an older commit its depth cut off: no problem; a cited commit is no commit only where git can tell","testMs":424,"mode":"batched","state":"requirement","enforces":["a cited commit is no commit only where git can tell"]}]} +{"kind":"refutation","at":"2026-10-10T09:57:06.275Z","session":"task-b53cdddb","agent":"main","component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","broke":"made missingCommits in commits.ts report every missing commit without asking whether the clone is shallow, as before the fix","verdict":"fail","reason":"2 of 5 tests under \"a cited commit is no commit only where git can tell\" failed: a cited commit is no commit only where git can tell a shallow clone citing an older commit its depth cut off: no problem; a cited commit is no commit only where git can tell","commit":"b8899dd","dirty":true} +{"at":"2026-10-10T09:57:24.387Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":17632,"load":{"average":0.69,"cores":8},"batch":{"ms":17230},"invariants":[{"component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","verdict":"fail","refutation":"missing","bypasses":[],"testReferences":0,"files":[],"latency":17230,"reason":"2 of 3 tests under \"a shallow clone refuses only an --introduced git can tell is no commit\" failed: a shallow clone refuses only an --introduced git can tell is no commit a file it holds is refused; a shallow clone refuses only an --introduced git can tell is no commit","testMs":256,"mode":"batched","state":"requirement","enforces":["a shallow clone refuses only an --introduced git can tell is no commit"]}]} +{"kind":"refutation","at":"2026-10-10T09:57:24.761Z","session":"task-b53cdddb","agent":"main","component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","broke":"made missingCommits in commits.ts report nothing in a shallow clone, so a file's hash passed as a commit its depth cut off","verdict":"fail","reason":"2 of 3 tests under \"a shallow clone refuses only an --introduced git can tell is no commit\" failed: a shallow clone refuses only an --introduced git can tell is no commit a file it holds is refused; a shallow clone refuses only an --introduced git can tell is no commit","commit":"b8899dd","dirty":true} +{"at":"2026-10-10T09:57:48.444Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":18146,"load":{"average":0.84,"cores":8},"batch":{"ms":17719},"invariants":[{"component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","verdict":"pass","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":17719,"reason":"3 tests under \"a shallow clone refuses only an --introduced git can tell is no commit\" passed in one invocation of node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-reporter=./src/enforcement/node-test-reporter.ts --test-reporter-destination= --test-name-pattern=<2 names> src/**/*.test.ts","testMs":244,"mode":"batched","state":"invariant","enforces":["a shallow clone refuses only an --introduced git can tell is no commit"]},{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"pass","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":17719,"reason":"5 tests under \"a cited commit is no commit only where git can tell\" passed in one invocation of node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-reporter=./src/enforcement/node-test-reporter.ts --test-reporter-destination= --test-name-pattern=<2 names> src/**/*.test.ts","testMs":496,"mode":"batched","state":"invariant","enforces":["a cited commit is no commit only where git can tell"]}]} +{"at":"2026-10-10T09:58:23.109Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":34299,"load":{"average":1.37,"cores":8},"invariants":[{"component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","verdict":"fail","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":17186,"reason":"passed in the batched invocation, failed in its own: node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-name-pattern='a shallow clone refuses only an --introduced git can tell is no commit' 'src/**/*.test.ts' exited 0 but its output did not match /# pass [1-9][0-9]*/; no test ran under that name","mode":"one-at-a-time","state":"structural defect","enforces":["a shallow clone refuses only an --introduced git can tell is no commit"]},{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"fail","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":17101,"reason":"passed in the batched invocation, failed in its own: node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-name-pattern='a cited commit is no commit only where git can tell' 'src/**/*.test.ts' exited 0 but its output did not match /# pass [1-9][0-9]*/; no test ran under that name","mode":"one-at-a-time","state":"structural defect","enforces":["a cited commit is no commit only where git can tell"]}]} +{"at":"2026-10-10T10:00:01.602Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":447,"load":{"average":0.69,"cores":8},"batch":{"ms":33},"invariants":[{"component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","verdict":"not run","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":33,"reason":"node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-reporter=./src/enforcement/node-test-reporter.ts --test-reporter-destination= --test-name-pattern=<2 names> src/**/*.test.ts exited 1 and wrote no report at {out}","mode":"batched","state":"invariant","enforces":["a shallow clone refuses only an --introduced git can tell is no commit"]},{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"not run","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":33,"reason":"node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-reporter=./src/enforcement/node-test-reporter.ts --test-reporter-destination= --test-name-pattern=<2 names> src/**/*.test.ts exited 1 and wrote no report at {out}","mode":"batched","state":"invariant","enforces":["a cited commit is no commit only where git can tell"]}]} +{"at":"2026-10-10T10:00:35.747Z","session":"task-b53cdddb","agent":"main","binding":"none: this session owns no active work order","commit":"b8899dd","dirty":true,"instrument":{"language":"typescript","server":"none"},"latency":33779,"load":{"average":1.11,"cores":8},"invariants":[{"component":"src/journal","name":"a shallow clone refuses only an introduced commit it can tell is none","form":"totality oracle","verdict":"pass","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":16771,"reason":"node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-name-pattern='a shallow clone refuses only an --introduced git can tell is no commit' 'src/**/*.test.ts' exited 0 and its output matched /# pass [1-9][0-9]*/","mode":"one-at-a-time","state":"invariant","enforces":["a shallow clone refuses only an --introduced git can tell is no commit"]},{"component":"src/spec","name":"a shallow clone leaves a cited commit unchecked","form":"totality oracle","verdict":"pass","refutation":"witnessed","bypasses":[],"testReferences":0,"files":[],"latency":16998,"reason":"node --disable-warning=ExperimentalWarning --import=./src/enforcement/leak-guard.ts --test --test-concurrency=1 --test-name-pattern='a cited commit is no commit only where git can tell' 'src/**/*.test.ts' exited 0 and its output matched /# pass [1-9][0-9]*/","mode":"one-at-a-time","state":"invariant","enforces":["a cited commit is no commit only where git can tell"]}]} diff --git a/src/journal/Journal.spec.md b/src/journal/Journal.spec.md index 9677c7ba..fdb1268f 100644 --- a/src/journal/Journal.spec.md +++ b/src/journal/Journal.spec.md @@ -192,6 +192,7 @@ Compression: one append-only file per session of attributed, durable outcomes, r 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 diff --git a/src/journal/defects.test.ts b/src/journal/defects.test.ts index 1785b211..5eda9fb8 100644 --- a/src/journal/defects.test.ts +++ b/src/journal/defects.test.ts @@ -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"; @@ -124,21 +124,23 @@ 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/); }); -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(`a shallow clone refuses only an --introduced git can tell is no commit: ${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")); - }); -} +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(); diff --git a/src/spec/Spec.spec.md b/src/spec/Spec.spec.md index 4f9bdb64..864090b7 100644 --- a/src/spec/Spec.spec.md +++ b/src/spec/Spec.spec.md @@ -170,6 +170,7 @@ The spec grammar and the model: one bullet shape, with each bullet's state deriv over: a cited commit a full clone holds, one no clone holds, one a depth-1 clone cut off, and a file's hash a depth-1 clone holds via: a cited commit is no commit only where git can tell because: a hosted agent sandbox clones at depth 1, so every commit an adopter's practices cited read as no commit and Stop reported each one, though the remote held them all; a shallow clone cannot confirm a commit it lacks, so it says nothing of one, but a hash that names a file or a tree here, or several objects, is no commit at any depth (review of PR #86); a full clone, as the workflow ci install writes fetches, still catches every wrong one + refuted: made missingCommits in commits.ts report every missing commit without asking whether the clone is shallow, as before the fix -> "a cited commit is no commit only where git can tell" went red in practice.test.ts on the shallow clone citing an older commit; restored byte for byte, green batched and alone (2026-10-10) kinds: none - a leaves line belongs to the step above it: A leaves: line in a practice belongs to the step written above it whatever its indentation: four spaces, two or a tab parse to the same steps and the same version. over: one practice with leaves: under its second step at four spaces, two spaces and a tab diff --git a/src/spec/practice.test.ts b/src/spec/practice.test.ts index 5ea18c8f..1e5090ec 100644 --- a/src/spec/practice.test.ts +++ b/src/spec/practice.test.ts @@ -9,7 +9,7 @@ import { execFileSync } from "node:child_process"; import { 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 { commandMatches, firedBy, globMatches, parsePractices, type ToolUse } from "./practice.ts"; import { loadSpecModel, projectPractices } from "./model.ts"; import { journalVerbs, type Io } from "../journal/cli.ts"; @@ -173,32 +173,34 @@ test("every record a practice cites must exist, and every invariant it names mus assert.ok(messages.some((m) => /names invariant dial clicks, which its sister spec does not declare/.test(m)), messages.join("\n")); }); -for (const { clone, cited, cites, problem } of [ - { clone: "full", cited: "an older commit", cites: (old: string) => old, problem: false }, - { clone: "full", cited: "a hash nothing has", cites: () => "0123abc", problem: true }, - { clone: "shallow", cited: "an older commit its depth cut off", cites: (old: string) => old, problem: false }, - { clone: "shallow", cited: "a file it holds", cites: (_old: string, blob: string) => blob, problem: true }, -] as const) { - test(`a cited commit is no commit only where git can tell: a ${clone} clone citing ${cited}, ${problem ? "a problem" : "no problem"}`, () => { - const origin = project({ "src/widget/Widget.spec.md": SPEC, "src/widget/knob.ts": "export const knob = 1;\n" }); - const git = (cwd: string, ...args: string[]): string => execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@t", ...args], { cwd, encoding: "utf8" }).trim(); - git(origin, "add", "-A"); - git(origin, "commit", "-q", "-m", "the knob seized"); - const old = git(origin, "rev-parse", "--short", "HEAD"); - const blob = git(origin, "rev-parse", "HEAD:src/widget/knob.ts"); - writeFileSync(join(origin, "src/widget/Widget.practice.md"), practiceText(cites(old, blob))); - git(origin, "add", "-A"); - git(origin, "commit", "-q", "-m", "oil the knob"); - let root = origin; - if (clone === "shallow") { - root = mkdtempSync(join(tmpdir(), "coherence-practice-shallow-")); - projects.push(root); - git(tmpdir(), "clone", "-q", "--depth", "1", `file://${origin}`, root); - } - const messages = loadSpecModel(root, { runs: false }).problems.map((p) => p.message); - assert.equal(messages.some((m) => /which is no commit in this repository/.test(m)), problem, messages.join("\n")); - }); -} +describe("a cited commit is no commit only where git can tell", () => { + for (const { clone, cited, cites, problem } of [ + { clone: "full", cited: "an older commit", cites: (old: string) => old, problem: false }, + { clone: "full", cited: "a hash nothing has", cites: () => "0123abc", problem: true }, + { clone: "shallow", cited: "an older commit its depth cut off", cites: (old: string) => old, problem: false }, + { clone: "shallow", cited: "a file it holds", cites: (_old: string, blob: string) => blob, problem: true }, + ] as const) { + test(`a ${clone} clone citing ${cited}: ${problem ? "a problem" : "no problem"}`, () => { + const origin = project({ "src/widget/Widget.spec.md": SPEC, "src/widget/knob.ts": "export const knob = 1;\n" }); + const git = (cwd: string, ...args: string[]): string => execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@t", ...args], { cwd, encoding: "utf8" }).trim(); + git(origin, "add", "-A"); + git(origin, "commit", "-q", "-m", "the knob seized"); + const old = git(origin, "rev-parse", "--short", "HEAD"); + const blob = git(origin, "rev-parse", "HEAD:src/widget/knob.ts"); + writeFileSync(join(origin, "src/widget/Widget.practice.md"), practiceText(cites(old, blob))); + git(origin, "add", "-A"); + git(origin, "commit", "-q", "-m", "oil the knob"); + let root = origin; + if (clone === "shallow") { + root = mkdtempSync(join(tmpdir(), "coherence-practice-shallow-")); + projects.push(root); + git(tmpdir(), "clone", "-q", "--depth", "1", `file://${origin}`, root); + } + const messages = loadSpecModel(root, { runs: false }).problems.map((p) => p.message); + assert.equal(messages.some((m) => /which is no commit in this repository/.test(m)), problem, messages.join("\n")); + }); + } +}); test("an enactment needs an outcome for every step, a because for a deviation or a skip, and keeps the text it enacted", () => { const { root, run } = widget(); From 41cceccc10740e1368c37bcd337c44cfa0514017 Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Sat, 10 Oct 2026 11:07:26 +0100 Subject: [PATCH 6/6] Release 1.7.1 A shallow clone no longer reads a correct commit citation as no commit: practice citations and defect --introduced ask git whether the clone is shallow, and there report only a hash that names some other object, or several (#86). Co-Authored-By: Claude Opus 5.5 Generated-By: PostHog Desktop Task-Id: b53cdddb-2e4d-43d2-97ec-a3b067257f29 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 97011769..a72de609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@posthog/coherence", - "version": "1.7.0", + "version": "1.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@posthog/coherence", - "version": "1.7.0", + "version": "1.7.1", "license": "MIT", "os": [ "darwin", diff --git a/package.json b/package.json index 9f46a3a3..a8faccca 100644 --- a/package.json +++ b/package.json @@ -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",