@popovych.co/witness 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -216,8 +216,11 @@ pnpm install
216
216
  pnpm test # vitest, in-process CLI against throwaway git repos
217
217
  pnpm run typecheck
218
218
  pnpm run build
219
+ pnpm run release patch # bump + stamp pins + commit + tag, in one act — see docs/RELEASING.md
219
220
  ```
220
221
 
222
+ Releasing is `node scripts/release.mjs <major|minor|patch|x.y.z>` followed by two pushes; the tag push is what publishes. The order is fixed because CI checks the tagged tree, not the intent — [docs/RELEASING.md](docs/RELEASING.md) explains why and what to do if a tag was pushed early.
223
+
221
224
  ## Run economics (operator notes)
222
225
 
223
226
  Non-load-bearing knobs for the machine that hosts long implement runs — the design
package/dist/gate.js CHANGED
@@ -96,8 +96,9 @@ export function renderGateRun(ctx, entry, mode, opts = {}) {
96
96
  // --approve read as the default at stops with a live blocking finding. The block ranks them.
97
97
  // One renderer for all four decision surfaces in this file — a per-site copy is how D119's
98
98
  // nine hand-copied exits sets happened, and three of these four sites were among them.
99
- // `undefined` from `recommend` means no decision exists at this state (stale below the
100
- // bound, where the only live act is a re-gate); the exits line still answers that.
99
+ // `undefined` from `recommend` means no anchor resolves at this state — stale with no
100
+ // decision pending (reopened, or already revised), where every decide verb refuses with
101
+ // nothing-pending and the exits line's single re-gate act is the whole live set (D131).
101
102
  function renderChoices(ctx, gate, target, entries, upstream, stale = false) {
102
103
  const d = recommend({ gate, target, entries, upstream, stale });
103
104
  if (d)
package/dist/recommend.js CHANGED
@@ -1,4 +1,4 @@
1
- import { anchorRecurrence, boundReached, ladderSpent, lastGateRun, notePrefill, repairGranted, roundBudget, roundsSinceApprove, } from './rounds.js';
1
+ import { anchorRecurrence, boundReached, ladderSpent, lastGateRun, notePrefill, pendingDecision, repairGranted, roundBudget, roundsSinceApprove, } from './rounds.js';
2
2
  // Commands are emitted raw (D120): `esc` would quote the `--note "…"` argument and the
3
3
  // line would paste into a shell as an empty note.
4
4
  export function renderDecision(d) {
@@ -99,9 +99,34 @@ export function recommend(ctx) {
99
99
  ],
100
100
  };
101
101
  }
102
- // 2 — stale below the bound: no decision exists, the caller renders the re-gate act.
103
- if (stale && !atBound)
104
- return undefined;
102
+ // 2 — stale below the bound. Staleness blocks STAMPING, not judging: `--approve` asserts
103
+ // about current content and `decide` refuses it with `stale-verdict`, while a stop, a
104
+ // revise and an upstream judge the WORK and are all legal (probed 2026-08-12, all exit 0).
105
+ // Gated on a pending decision because that is what resolves an anchor: on the reopened
106
+ // and revised screens every decide verb refuses with `nothing-pending`, and there the
107
+ // caller's single re-gate act is the honest answer.
108
+ if (stale && !atBound) {
109
+ if (pendingDecision(entries, gate) === undefined)
110
+ return undefined;
111
+ const nextRound = spent + 1;
112
+ return {
113
+ key: 'decide', rule: 'stale-below-bound',
114
+ // No anchor: the findings describe bytes that no longer exist, and pinning a decision
115
+ // to one of them would file recurrence against a verdict nothing can still reproduce.
116
+ options: [
117
+ opt(`witness gate ${gate} ${target}`, 'root', {
118
+ why: `the verdict judged @${last.reviewed_sha.slice(0, 7)} and the content has moved since — no battery has read the current bytes, so every finding above describes a tree that no longer exists`,
119
+ tradeoff: `spends round ${nextRound} of ${budget}${nextRound >= budget ? ' — the last before the bound' : ''}`,
120
+ }),
121
+ opt(note, 'root', {
122
+ when: 'you already know the next edit and want no verdict on this state',
123
+ tradeoff: 'spends no round now — the round is spent by the re-gate that follows it',
124
+ }),
125
+ ...upAlt('the parent artifact is what is wrong, whatever the current bytes say', 'reopens the parent stage and resets this budget'),
126
+ stopOpt,
127
+ ],
128
+ };
129
+ }
105
130
  const anchors = blockingAnchors(last);
106
131
  const primary = anchors[0];
107
132
  const recurrence = primary === undefined ? 0 : anchorRecurrence(entries, gate, primary);
package/dist/rounds.js CHANGED
@@ -175,8 +175,15 @@ export function liveExits(gate, target, entries, stale, upstream) {
175
175
  return [`${d} ${[...approve, ...up, ...repair, '--stop'].join(' | ')}`,
176
176
  `witness abandon ${target}`].join(' | ');
177
177
  }
178
- if (stale)
179
- return `witness gate ${gate} ${target}`;
178
+ // Stale removes --approve and nothing else: `decide` refuses that one with `stale-verdict`
179
+ // because a stamp asserts about current content, while a stop or a revise judges the work.
180
+ // Both halves are measured (2026-08-12). The pending check is what separates this from the
181
+ // reopened and revised screens, where no anchor resolves and every decide verb refuses.
182
+ if (stale) {
183
+ if (pendingDecision(entries, gate) === undefined)
184
+ return `witness gate ${gate} ${target}`;
185
+ return [`witness gate ${gate} ${target}`, `${d} ${[note, ...up, '--stop'].join(' | ')}`].join(' | ');
186
+ }
180
187
  return `${d} ${['--approve', note, ...up, '--stop'].join(' | ')}`;
181
188
  }
182
189
  // What un-parks a gate stopped under D124. Below the bound a fresh run is the act — the
@@ -132,8 +132,8 @@ export async function run(ctx, argv) {
132
132
  ctx.out(kv('note', disposition.note));
133
133
  }
134
134
  // The surface a human actually reads while deciding, so it is the one that most owes a
135
- // ranking rather than a menu. `undefined` is stale below the bound, where no decision
136
- // exists and the exits line's single re-gate act is the honest answer.
135
+ // ranking rather than a menu. `undefined` is stale with no decision pending, where no
136
+ // act but the re-gate is legal and the exits line already says exactly that (D131).
137
137
  const shown = recommend({ gate, target, entries, upstream: upstreamId, stale });
138
138
  if (shown)
139
139
  renderDecision(shown).forEach((l) => ctx.out(l));
@@ -275,7 +275,13 @@ export async function run(ctx, argv) {
275
275
  // `decision` — so the two are directly comparable in a log query. The full command would
276
276
  // carry a prefilled note that changes with the findings, which would make two identical
277
277
  // recommendations look different.
278
- const rec = recommend({ gate, target, entries, upstream: upstreamId, stale: false });
278
+ const rec = recommend({
279
+ gate, target, entries, upstream: upstreamId,
280
+ // The truth, not `false`. The hardcode existed because a stale state produced no rule at
281
+ // all and the entry's fields would have been empty; with the stale rule in place it only
282
+ // misattributes — D130's audit was reporting rules that were never rendered.
283
+ stale: nowSha !== undefined && nowSha !== anchor.reviewed_sha,
284
+ });
279
285
  const recommendedVerb = rec?.options[0]?.command.match(/--(approve|revise|stop)/)?.[1];
280
286
  const entry = {
281
287
  v: 1, t: 'human-decision', gate, artifact: target, round: anchor.round,
@@ -256,7 +256,7 @@ function flowBlocked(root, plan, entries) {
256
256
  }
257
257
  // The upstream artifact a gate's `--revise --upstream` books its reopen against. The gate
258
258
  // registry answers this via `gateSpec().upstreamOf`, but `gate.ts` imports THIS module
259
- // (gate.ts:23), so importing the registry back would close the cycle the codebase keeps
259
+ // (gate.ts:24), so importing the registry back would close the cycle the codebase keeps
260
260
  // one-way on purpose — the same edge that moved `liveExits` out of gate.ts into rounds.ts.
261
261
  // Tier 2 hand-resolved it inline in three places; this is that derivation named once, and
262
262
  // it agrees with the registry by test (exits-line.test.ts pins upstreamOf for all five).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@popovych.co/witness",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
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",
@@ -57,7 +57,8 @@
57
57
  "typecheck": "tsc --noEmit",
58
58
  "test": "vitest run",
59
59
  "test:watch": "vitest",
60
- "sync-versions": "node scripts/sync-versions.mjs"
60
+ "sync-versions": "node scripts/sync-versions.mjs",
61
+ "release": "node scripts/release.mjs"
61
62
  },
62
63
  "dependencies": {
63
64
  "yaml": "^2.7.0"
@@ -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.11.0",
4
+ "version": "0.11.1",
5
5
  "author": {
6
6
  "name": "Kostiantyn Popovych"
7
7
  },
@@ -10,7 +10,7 @@ The CLI decides, you act. If `$ARGUMENTS` contains `--manual`, append `--manual`
10
10
  Resolve the CLI once:
11
11
 
12
12
  ```bash
13
- WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.0}"
13
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
14
14
  ```
15
15
 
16
16
  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.11.0} 2>/dev/null || true
6
+ ${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1} 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.11.0}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.11.0}"
13
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.11.0}"
19
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.11.0}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.11.0}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.11.0}"
18
+ WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.11.1}"
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.