@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 +3 -0
- package/dist/gate.js +3 -2
- package/dist/recommend.js +29 -4
- package/dist/rounds.js +9 -2
- package/dist/verbs/decide.js +9 -3
- package/dist/verbs/next.js +1 -1
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/commands/witness.md +1 -1
- package/plugin/hooks/session-dashboard.sh +1 -1
- package/plugin/skills/witness-brainstorm/SKILL.md +1 -1
- package/plugin/skills/witness-decompose/SKILL.md +1 -1
- package/plugin/skills/witness-design/SKILL.md +1 -1
- package/plugin/skills/witness-implement/SKILL.md +1 -1
- package/plugin/skills/witness-plan/SKILL.md +1 -1
- package/plugin/skills/witness-ship/SKILL.md +1 -1
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
|
|
100
|
-
//
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
package/dist/verbs/decide.js
CHANGED
|
@@ -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
|
|
136
|
-
//
|
|
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({
|
|
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,
|
package/dist/verbs/next.js
CHANGED
|
@@ -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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|