@popovych.co/witness 0.19.1 → 0.19.3

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/dist/abandon.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { prepareDeparture, prepareReturns, takeBack } from './departure.js';
3
+ import { canonExit, prepareDeparture, prepareReturns, takeBack } from './departure.js';
4
4
  import { designRel } from './design.js';
5
5
  import { splitDoc } from './fm.js';
6
6
  import { canonicalSha } from './sha.js';
7
7
  import { gitBlob, tryGit, stateCommit } from './gitio.js';
8
8
  import { acquireLock } from './lock.js';
9
9
  import { crashPoint, withTxn } from './txn.js';
10
- import { appendEntry, effortAbandoned, effortStreams, entryLine, journalRel, readStream } from './journal.js';
10
+ import { appendEntry, effortAbandoned, effortStreams, entryLine, journalActs, journalRel, readStream } from './journal.js';
11
11
  import { findById, plansOn } from './scan.js';
12
12
  import { humanCall, ok, refuse, v } from './refusal.js';
13
13
  import { prepareStamp, writeStamp } from './stamp.js';
@@ -18,25 +18,15 @@ const isWrite = (e, id, sha) => {
18
18
  return w.t === 'write' && w.artifact === id && (sha === undefined || w.sha === sha);
19
19
  };
20
20
  const isAncestor = (root, a, b) => tryGit(root, 'merge-base', '--is-ancestor', a, b).ok;
21
- // D168. Each line of a stream as history stood at `at`, with the commit that appended it — which
22
- // `git blame` names because the journal is append-only. `--no-ignore-revs-file`: a blame config
23
- // (a dotfile's `blame.ignoreRevsFile`) re-attributes lines, or makes blame fail outright in a repo
24
- // that lacks the file. A blank line is skipped, as `readStream` skips it.
25
- function journalActs(root, stream, at = 'HEAD') {
26
- const blame = tryGit(root, 'blame', '--line-porcelain', '--no-ignore-revs-file', at, '--', journalRel(stream));
27
- if (!blame.ok)
28
- return [];
29
- const out = [];
30
- let commit = '';
31
- for (const line of blame.out.split('\n')) {
32
- if (/^[0-9a-f]{40} \d+ \d+/.test(line)) {
33
- commit = line.slice(0, 40);
34
- continue;
35
- }
36
- if (line.startsWith('\t') && line.trim() !== '')
37
- out.push({ entry: JSON.parse(line.slice(1)), commit });
38
- }
39
- return out;
21
+ // D189. Of two commits, the later: by ancestry, and on parallel branches — where neither is an
22
+ // ancestor, as after a merge of two clones' journals — by commit time.
23
+ function later(root, a, b) {
24
+ if (isAncestor(root, a, b))
25
+ return b;
26
+ if (isAncestor(root, b, a))
27
+ return a;
28
+ const time = (c) => Number(tryGit(root, 'log', '-1', '--format=%ct', c).out) || 0;
29
+ return time(b) > time(a) ? b : a;
40
30
  }
41
31
  // D168. The act this content of a spec is, as history stood at `at`: the newest write of it, by
42
32
  // whichever effort made it. Rows 29 and 84 resolved a pin by walking the spec's path for matching
@@ -53,7 +43,7 @@ export function writeAct(root, id, sha, at = 'HEAD') {
53
43
  for (const { entry, commit } of journalActs(root, effort, at)) {
54
44
  if (!isWrite(entry, id, sha))
55
45
  continue;
56
- if (found === undefined || isAncestor(root, found.commit, commit)) {
46
+ if (found === undefined || later(root, found.commit, commit) === commit) {
57
47
  found = { effort, commit, created: entry.created === true };
58
48
  }
59
49
  }
@@ -69,18 +59,20 @@ const shaAt = (root, rev, rel) => {
69
59
  // stood on it or on a sibling slice, or the walk stopped at it. That content is canon of record:
70
60
  // no later walk goes past it, or an amendment on top of it took the keep with it.
71
61
  function stoodAtAbandon(root, effort, rel, sha) {
72
- const at = journalActs(root, effort).find(({ entry }) => entry.t === 'human-decision' && entry.decision === 'abandon-effort');
62
+ const at = journalActs(root, effort, 'HEAD').find(({ entry }) => entry.t === 'human-decision' && entry.decision === 'abandon-effort');
73
63
  return at !== undefined && shaAt(root, at.commit, rel) === sha;
74
64
  }
65
+ // This effort's newest write of an artifact (of this content, when `sha` is given).
66
+ export const effortWrite = (root, effort, id, sha) => journalActs(root, effort, 'HEAD').filter(({ entry }) => isWrite(entry, id, sha)).at(-1);
75
67
  // D168 (review). Whether an earlier abandon already took this effort's newest write of a spec
76
68
  // back — `abandon <plan>` does, before the effort's own abandon: a status entry the abandon
77
69
  // journaled on the spec after that write, which is not a return, made where the spec stood at
78
70
  // the write's content. Then the effort's abandon has nothing of its own left there to take back.
79
71
  export function takenBackAlready(root, effort, id, rel, sha) {
80
- const write = journalActs(root, effort).filter(({ entry }) => isWrite(entry, id, sha)).at(-1);
72
+ const write = effortWrite(root, effort, id, sha);
81
73
  if (write === undefined)
82
74
  return false;
83
- return journalActs(root, id).some(({ entry, commit }) => {
75
+ return journalActs(root, id, 'HEAD').some(({ entry, commit }) => {
84
76
  const s = entry;
85
77
  const takes = s.t === 'status' && s.cause === 'abandon' && !(s.from === 'superseded' && s.to !== 'removed');
86
78
  return takes && commit !== write.commit && isAncestor(root, write.commit, commit) && shaAt(root, `${commit}^`, rel) === sha;
@@ -170,9 +162,16 @@ export function specRevertFor(root, canon, spec, pin, abandonSet, act, effort) {
170
162
  // abandon set, or inside it but kept because a plan outside the set stands on it. It stands for
171
163
  // good when a plan shipped it — and then so does this keep, since nothing can take the
172
164
  // sibling back. Every spec the walk passes through counts: each was a re-slice being taken back.
173
- const cut = new Set([...returns.map((r) => r.entry.artifact), ...unwound.map((u) => u.artifact)]);
165
+ // D188: on the plan path, also a slice whose own write departed nothing — another slice's
166
+ // write took the predecessor out. Its effort is still live, and its own abandon is the act that
167
+ // takes the split back together; deleting the slice now left the split half described. The
168
+ // effort path is that last act, and there a slice another effort cut cannot go back together
169
+ // with this one: holding it would leave two efforts' abandons each refusing for the other's.
170
+ const own = spec.meta.supersedes === undefined ? undefined : String(spec.meta.supersedes);
171
+ const slice = effort === undefined && back.end === 'create' && own !== undefined && canonExit(readStream(root, own))?.to === 'superseded' ? [own] : [];
172
+ const cut = new Set([...returns.map((r) => r.entry.artifact), ...unwound.map((u) => u.artifact), ...slice]);
174
173
  const slices = canon.docs
175
- .filter((d) => d.meta.type === 'spec' && cut.has(String(d.meta.supersedes)) && String(d.meta.id) !== String(spec.meta.id))
174
+ .filter((d) => d.meta.type === 'spec' && cut.has(String(d.meta.supersedes)) && String(d.meta.id) !== id)
176
175
  .flatMap((d) => {
177
176
  const id = String(d.meta.id);
178
177
  const leaving = abandonSet.has(id);
@@ -229,6 +228,54 @@ export function returnCollisions(root, canon, items) {
229
228
  ...[...landing].filter(([, ids]) => ids.length > 1).map(([rel, ids]) => humanCall(v('spec', 'path-taken', `${rel} takes ${ids.join(' and ')}`, `one return per path — each left from it · which one comes back is a human's call`))),
230
229
  ];
231
230
  }
231
+ // D188. What an abandon lands — a restore's older content, a return's departed bytes — comes
232
+ // back only beside what it depends on: a dependency in canon that this abandon does not take
233
+ // out, or one it brings back. `write` refuses a dangling `depends`, and an abandon landing one
234
+ // left `check` red after it exited 0. Bringing the dependency back first is a human's call.
235
+ export function landingEdges(root, canon, items) {
236
+ const leaving = new Set(items.flatMap((i) => (i.revert?.action === 'delete' ? [String(i.revert.doc.meta.id)] : [])));
237
+ const coming = new Set(items.flatMap((i) => (i.revert?.returns ?? []).map((r) => r.entry.artifact)));
238
+ const lands = items.flatMap((i) => {
239
+ if (!i.revert)
240
+ return [];
241
+ const restored = i.revert.action === 'restore' ? splitDoc(i.revert.content) : undefined;
242
+ return [
243
+ ...(restored?.ok ? [{ id: String(i.revert.doc.meta.id), depends: (restored.value.meta.depends ?? []).map(String) }] : []),
244
+ ...i.revert.returns.map((r) => ({ id: r.entry.artifact, depends: r.depends })),
245
+ ];
246
+ });
247
+ return lands.flatMap(({ id, depends }) => depends
248
+ .filter((dep) => !coming.has(dep) && (leaving.has(dep) || findById(canon, dep) === undefined))
249
+ .map((dep) => {
250
+ const exit = canonExit(readStream(root, dep));
251
+ const why = leaving.has(dep) ? 'it leaves with this abandon'
252
+ : exit?.to === 'superseded' ? `it left superseded by ${exit.by ?? 'a successor'}` : 'it left canon';
253
+ return humanCall(v('spec', 'dangling-depends', `${id} depends on ${dep}`, `${dep} in canon when ${id} comes back — ${why} · a human's call`));
254
+ }));
255
+ }
256
+ // D188 (review). A spec kept for good stays in canon with what it depends on: a dependency this
257
+ // abandon would delete is kept beside it, and for good with it. `waiting-dependents` reads only
258
+ // docs outside the abandon set, and a kept one is inside it, so the delete left `check` red after
259
+ // the abandon exited 0; refusing instead would hold the effort for good behind a shipped plan.
260
+ export function keepDependencies(items, kept) {
261
+ let walked = items;
262
+ const held = [...kept];
263
+ for (let grew = true; grew;) {
264
+ grew = false;
265
+ walked = walked.filter((item) => {
266
+ if (item.revert?.action !== 'delete')
267
+ return true;
268
+ const id = String(item.revert.doc.meta.id);
269
+ const on = held.filter((k) => (k.doc.meta.depends ?? []).map(String).includes(id));
270
+ if (on.length === 0)
271
+ return true;
272
+ held.push({ doc: item.revert.doc, action: 'keep', standing: on.map((k) => String(k.doc.meta.id)), shipped: on.some((k) => k.shipped) });
273
+ grew = true;
274
+ return false;
275
+ });
276
+ }
277
+ return { items: walked, kept: held };
278
+ }
232
279
  export function executeAbandon(root, ctx, items, effortEntry, subject) {
233
280
  const files = new Set();
234
281
  const journalMulti = [];
package/dist/departure.js CHANGED
@@ -164,6 +164,7 @@ function returnsThrough(root, paths, commit, chain, takenBack, seen) {
164
164
  moves: chain.flatMap((s, k) => deferralMoves(readStream(root, s), s, id, (d) => d.moved_from === (chain[k + 1] ?? id))),
165
165
  rel,
166
166
  by: successor,
167
+ depends: (walked.doc.meta.depends ?? []).map(String),
167
168
  }],
168
169
  unwound: [],
169
170
  };
@@ -59,7 +59,8 @@ registerGate({
59
59
  detail: staleChildren.length
60
60
  ? `amended under in-progress plans (approve = explicit ack): ${staleChildren.join(' ')}`
61
61
  : '' });
62
- const created = [...writes.values()].some((w) => w.created === true);
62
+ // D189. A spec the effort created — a plan it created is no misroute.
63
+ const created = docs.some((d) => writes.get(String(d.meta.id))?.created === true);
63
64
  const standingStop = recap.class === 'feature' ? 'feature scope approval — scope is approved as its concrete slicing'
64
65
  : recap.class === 'fix' && created
65
66
  ? 'fix created a new spec — misroute tripwire (young canon: expect new-spec stops at first)'
package/dist/journal.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { tryGit } from './gitio.js';
3
4
  import { version } from './version.js';
4
5
  const dir = (root) => join(root, '.witness', 'journal');
5
6
  export const journalRel = (id) => `.witness/journal/${id}.jsonl`;
@@ -27,6 +28,28 @@ export function readStream(root, id) {
27
28
  .filter(Boolean)
28
29
  .map((l) => JSON.parse(l));
29
30
  }
31
+ // D168, D189. Each line of a stream with the commit that appended it — `git blame` names it,
32
+ // since the journal is append-only — as history stood at `at`, or as the file stands now when
33
+ // `at` is absent (a line not yet committed carries git's all-zero id). `--no-ignore-revs-file`: a
34
+ // dotfile's `blame.ignoreRevsFile` re-attributes lines, or fails blame outright in a repo that
35
+ // lacks the file. A blank line is skipped, as `readStream` skips it. SHA-1 or SHA-256 ids.
36
+ export function journalActs(root, stream, at) {
37
+ const blame = tryGit(root, 'blame', '--line-porcelain', '--no-ignore-revs-file', ...(at === undefined ? [] : [at]), '--', journalRel(stream));
38
+ if (!blame.ok)
39
+ return [];
40
+ const out = [];
41
+ let commit = '';
42
+ for (const line of blame.out.split('\n')) {
43
+ const head = /^([0-9a-f]{40}(?:[0-9a-f]{24})?) \d+ \d+/.exec(line);
44
+ if (head) {
45
+ commit = head[1];
46
+ continue;
47
+ }
48
+ if (line.startsWith('\t') && line.trim() !== '')
49
+ out.push({ entry: JSON.parse(line.slice(1)), commit });
50
+ }
51
+ return out;
52
+ }
30
53
  export function latestRecap(root, effort) {
31
54
  const recaps = readStream(root, effort).filter((e) => e.t === 'recap');
32
55
  return recaps.at(-1);
@@ -7,7 +7,7 @@ import { effortWrites } from '../reviewed.js';
7
7
  import { canonicalSha } from '../sha.js';
8
8
  import { humanCall, renderRefusal, v } from '../refusal.js';
9
9
  import { kv } from '../toon.js';
10
- import { abandonLines, executeAbandon, planItems, returnCollisions, specRevertFor, takenBackAlready, writeAct } from '../abandon.js';
10
+ import { abandonLines, executeAbandon, keepDependencies, landingEdges, planItems, returnCollisions, specRevertFor, takenBackAlready, writeAct } from '../abandon.js';
11
11
  export async function run(ctx, argv) {
12
12
  const target = argv.find((a) => !a.startsWith('--'));
13
13
  if (!target) {
@@ -35,7 +35,7 @@ export async function run(ctx, argv) {
35
35
  renderRefusal(itemR.violations).forEach((l) => ctx.err(l));
36
36
  return EXIT.REFUSED;
37
37
  }
38
- const taken = returnCollisions(root, canon, [itemR.value]);
38
+ const taken = [...returnCollisions(root, canon, [itemR.value]), ...landingEdges(root, canon, [itemR.value])];
39
39
  if (taken.length > 0) {
40
40
  renderRefusal(taken).forEach((l) => ctx.err(l));
41
41
  return EXIT.REFUSED;
@@ -66,6 +66,8 @@ export async function run(ctx, argv) {
66
66
  continue; // already gone (re-slice, earlier abandon)
67
67
  if (artifact.meta.type !== 'plan')
68
68
  continue;
69
+ if (String(artifact.meta.status) === 'abandoned')
70
+ continue; // D189: an earlier abandon withdrew it
69
71
  // 'stamp-only': the spec loop below is this path's sole spec revert, pinned on
70
72
  // THIS effort's write sha. A paired revert here would re-derive the owner and
71
73
  // could land on a sibling effort's amendment — see Pairing in abandon.ts.
@@ -113,18 +115,19 @@ export async function run(ctx, argv) {
113
115
  else if (outcome)
114
116
  items.push({ revert: outcome });
115
117
  }
116
- problems.push(...returnCollisions(root, canon, items));
118
+ const held = keepDependencies(items, kept);
119
+ problems.push(...returnCollisions(root, canon, held.items), ...landingEdges(root, canon, held.items));
117
120
  if (problems.length > 0) {
118
121
  renderRefusal(problems).forEach((l) => ctx.err(l));
119
122
  return EXIT.REFUSED;
120
123
  }
121
- const done = executeAbandon(root, ctx, items, { effort: target }, `abandon(${target})`);
124
+ const done = executeAbandon(root, ctx, held.items, { effort: target }, `abandon(${target})`);
122
125
  if (!done.ok) {
123
126
  renderRefusal(done.violations).forEach((l) => ctx.err(l));
124
127
  return EXIT.REFUSED;
125
128
  }
126
- ctx.out(kv('abandoned', `effort ${target} — ${items.length} artifacts walked · journal remembers the attempt`));
127
- abandonLines(items, kept).forEach((l) => ctx.out(l));
129
+ ctx.out(kv('abandoned', `effort ${target} — ${held.items.length} artifacts walked · journal remembers the attempt`));
130
+ abandonLines(held.items, held.kept).forEach((l) => ctx.out(l));
128
131
  return EXIT.OK;
129
132
  }
130
133
  renderRefusal([v('target', 'unknown-target', target, 'a plan id or an effort slug')]).forEach((l) => ctx.err(l));
package/dist/verbs/log.js CHANGED
@@ -3,9 +3,9 @@ import { join } from 'node:path';
3
3
  import { parseArgs } from 'node:util';
4
4
  import { EXIT } from '../cli.js';
5
5
  import { askText } from '../ask.js';
6
- import { git, primaryRoot, tryGit } from '../gitio.js';
6
+ import { git, primaryRoot } from '../gitio.js';
7
7
  import { canonExit } from '../departure.js';
8
- import { journalRel, readStream, streamExists } from '../journal.js';
8
+ import { journalActs, readStream, streamExists } from '../journal.js';
9
9
  import { renderRefusal, v } from '../refusal.js';
10
10
  import { short } from '../sha.js';
11
11
  import { rows } from '../toon.js';
@@ -93,23 +93,12 @@ function commitIndex(root) {
93
93
  .forEach((sha, i) => map.set(sha, i));
94
94
  return map;
95
95
  }
96
- function lineOrigins(root, rel) {
97
- const blame = tryGit(root, 'blame', '--porcelain', '--', rel);
98
- if (!blame.ok)
99
- return [];
100
- const shas = [];
101
- for (const line of blame.out.split('\n')) {
102
- if (/^[0-9a-f]{40} \d+ \d+/.test(line))
103
- shas.push(line.slice(0, 40));
104
- }
105
- return shas;
106
- }
107
96
  export function mergedRows(root) {
108
97
  const order = commitIndex(root);
109
98
  const rowsOut = [];
110
99
  for (const stream of streamIds(root)) {
111
100
  const entries = readStream(root, stream);
112
- const origins = lineOrigins(root, journalRel(stream));
101
+ const origins = journalActs(root, stream).map((a) => a.commit);
113
102
  entries.forEach((entry, lineNo) => {
114
103
  const sha = origins[lineNo];
115
104
  rowsOut.push({ stream, entry, lineNo, idx: sha !== undefined ? (order.get(sha) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popovych.co/witness",
3
- "version": "0.19.1",
3
+ "version": "0.19.3",
4
4
  "description": "Evidence-gated spec-driven development pipeline for AI coding agents: brainstorm to PR in six stages, where specs are state, plans are motion, and no transition passes without evidence. Drives from Claude Code or Pi.",
5
5
  "keywords": [
6
6
  "spec-driven-development",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "witness",
3
3
  "description": "Spec-driven development with evidence gates: brainstorm → PR through six stages and five gates. Skills author, the pinned witness CLI writes state and witnesses evidence; gates stop where judgment is owed.",
4
- "version": "0.19.1",
4
+ "version": "0.19.3",
5
5
  "author": {
6
6
  "name": "Kostiantyn Popovych"
7
7
  },
@@ -12,7 +12,7 @@ Any other words in `$ARGUMENTS` are the human's ask: run `$WITNESS ask "<those w
12
12
  Resolve the CLI once:
13
13
 
14
14
  ```bash
15
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
15
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
16
16
  ```
17
17
 
18
18
  If the repo has no `witness.config.yaml`, run `$WITNESS init` first (one bootstrap commit), then proceed. If init or any verb refuses on a dirty tree, the human commits or cleans it — **never `git commit` yourself, anywhere, at any stage**: the CLI makes its own state commits, and the ship phase makes the sole code commit.
@@ -3,4 +3,4 @@
3
3
  # context). Silent everywhere that isn't a witness repo; failures swallowed:
4
4
  # session start must never hang or die on this.
5
5
  [ -f witness.config.yaml ] || exit 0
6
- ${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1} 2>/dev/null || true
6
+ ${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3} 2>/dev/null || true
@@ -15,7 +15,7 @@ witness: ends in a confirmed scope recap, writes no document. See NOTICE.md. -->
15
15
  Resolve the CLI once per session:
16
16
 
17
17
  ```bash
18
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
19
19
  ```
20
20
 
21
21
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
@@ -10,7 +10,7 @@ description: Slice a witness effort's confirmed recap into vertical spec slices,
10
10
  Resolve the CLI once per session:
11
11
 
12
12
  ```bash
13
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
13
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
14
14
  ```
15
15
 
16
16
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
@@ -16,7 +16,7 @@ No text copied verbatim; the design-critic lens is new material. -->
16
16
  Resolve the CLI once per session:
17
17
 
18
18
  ```bash
19
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
19
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
20
20
  ```
21
21
 
22
22
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
@@ -15,7 +15,7 @@ tests carry the parent spec's tag in their NAME. See NOTICE.md. -->
15
15
  Resolve the CLI once per session:
16
16
 
17
17
  ```bash
18
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
19
19
  ```
20
20
 
21
21
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
@@ -15,7 +15,7 @@ parent's criteria. See NOTICE.md. -->
15
15
  Resolve the CLI once per session:
16
16
 
17
17
  ```bash
18
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
19
19
  ```
20
20
 
21
21
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.
@@ -15,7 +15,7 @@ model can. See NOTICE.md. -->
15
15
  Resolve the CLI once per session:
16
16
 
17
17
  ```bash
18
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.1}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.19.3}"
19
19
  ```
20
20
 
21
21
  - **Render the CLI's decision output verbatim and in full — every line, unmodified.** Never print a command set you remember; never recompose, reformat, summarise or reorder what the CLI emitted. Which decisions are live, how they rank, and what each costs are the CLI's answers, and they change with the round, the bound, the repair grant and the content sha — a remembered set is wrong in more states than it is right.