@popovych.co/witness 0.14.5 → 0.14.6
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/calibrate.js +13 -3
- package/dist/drive.js +11 -3
- package/dist/probe.js +14 -8
- package/dist/reviewer.js +11 -2
- package/dist/spawn.js +107 -0
- package/package.json +1 -1
- 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/dist/calibrate.js
CHANGED
|
@@ -9,9 +9,10 @@ import { loadConfig } from './config.js';
|
|
|
9
9
|
import { changedFiles, diffBase, evidenceForDiff } from './evidence.js';
|
|
10
10
|
import { readDoc, splitDoc, writeDoc } from './fm.js';
|
|
11
11
|
import { git, stateCommit } from './gitio.js';
|
|
12
|
-
import { ok, refuse, v } from './refusal.js';
|
|
12
|
+
import { ok, refuse, renderRefusal, v } from './refusal.js';
|
|
13
13
|
import { PROMPT_NAMES, invokeReviewer, parseVerdictText, resolvePrompt } from './reviewer.js';
|
|
14
14
|
import { findById, loadCanon } from './scan.js';
|
|
15
|
+
import { resolveSpawn } from './spawn.js';
|
|
15
16
|
import { anchorMenu, parseVerdict, verdictViolations } from './verdict.js';
|
|
16
17
|
import { createWorktree } from './worktree.js';
|
|
17
18
|
export function calibrationDir() {
|
|
@@ -478,11 +479,20 @@ export async function runPlanSeed(ctx, harness, model, seed, extras) {
|
|
|
478
479
|
// a pure-pi user, the residual the routing decision exists to kill.
|
|
479
480
|
export async function defaultAgent(ctx, harness, worktree, prompt) {
|
|
480
481
|
const { cmd, args, env } = harness.worker.spawn(prompt);
|
|
481
|
-
|
|
482
|
+
// Row 164. The prompt rides argv, and the implement seed's is a whole SKILL.md: on
|
|
483
|
+
// Windows no `.cmd` shim can receive its line breaks. Say so — a worker that never
|
|
484
|
+
// started otherwise reads, one step later, as an agent that made no diff.
|
|
485
|
+
const run = resolveSpawn(cmd, args, { ...ctx.env, ...env, WITNESS_BIN: `node ${join(dirname(fileURLToPath(import.meta.url)), 'bin.js')}` });
|
|
486
|
+
if (!run.ok) {
|
|
487
|
+
renderRefusal(run.violations).forEach((l) => ctx.err(l));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
spawnSync(run.value.file, run.value.args, {
|
|
482
491
|
cwd: worktree,
|
|
483
|
-
env:
|
|
492
|
+
env: run.value.env,
|
|
484
493
|
timeout: 900_000,
|
|
485
494
|
stdio: 'ignore',
|
|
495
|
+
windowsVerbatimArguments: run.value.windowsVerbatimArguments,
|
|
486
496
|
});
|
|
487
497
|
}
|
|
488
498
|
export async function runImplementSeed(ctx, harness, seed, agent) {
|
package/dist/drive.js
CHANGED
|
@@ -6,6 +6,7 @@ import { EXIT, main } from './cli.js';
|
|
|
6
6
|
import { resolveJudge } from './harness.js';
|
|
7
7
|
import { renderRefusal } from './refusal.js';
|
|
8
8
|
import { loadCanon } from './scan.js';
|
|
9
|
+
import { resolveSpawn } from './spawn.js';
|
|
9
10
|
import { computeNext, flowAction, resolveFlow } from './verbs/next.js';
|
|
10
11
|
// A conversation is a stage whose product IS the exchange with a human: brainstorm has no
|
|
11
12
|
// artifact to author headlessly, and design authoring/showing needs eyes on a rendered
|
|
@@ -69,7 +70,7 @@ const EXIT_GRACE_MS = 2_000;
|
|
|
69
70
|
// This CLI, for the child. A spawned session shells out to `witness` through
|
|
70
71
|
// ${WITNESS_BIN:-npx …} in every stage skill, and an unset value means the child resolves
|
|
71
72
|
// the PUBLISHED version while its parent runs an unpublished one — which the state floor
|
|
72
|
-
// then refuses (cli-behind-state). calibrate.ts:
|
|
73
|
+
// then refuses (cli-behind-state). calibrate.ts:520 sets it for its worker for exactly
|
|
73
74
|
// this reason. An explicitly set value wins: a session that already knows its bin is
|
|
74
75
|
// stating a fact drive has no better answer for.
|
|
75
76
|
function localBin(ctx) {
|
|
@@ -115,14 +116,21 @@ export async function spawnSession(step, cfg, ctx, spawnN) {
|
|
|
115
116
|
// a scripted fake that performs real CLI acts. Never documented for users — a spawn
|
|
116
117
|
// binary chosen by ambient env is exactly the identity confusion row 90 removed.
|
|
117
118
|
const bin = ctx.env.WITNESS_DRIVE_AGENT_BIN ?? cmd;
|
|
119
|
+
// Row 164: on Windows the agent CLI may be a `.cmd` shim, which only cmd.exe can start.
|
|
120
|
+
const run = resolveSpawn(bin, args, { ...ctx.env, ...env, WITNESS_BIN: localBin(ctx) });
|
|
121
|
+
if (!run.ok) {
|
|
122
|
+
renderRefusal(run.violations).forEach((l) => ctx.err(l));
|
|
123
|
+
return 'spawn-failed';
|
|
124
|
+
}
|
|
118
125
|
const prefix = `[${spawnN} ${step.stage ?? 'drive'}/${step.target ?? step.home.split('/').pop() ?? '-'}] `;
|
|
119
126
|
const out = lineSink(prefix, (l) => ctx.out(l));
|
|
120
127
|
const err = lineSink(prefix, (l) => ctx.out(l));
|
|
121
128
|
return await new Promise((resolve) => {
|
|
122
|
-
const child = spawn(
|
|
129
|
+
const child = spawn(run.value.file, run.value.args, {
|
|
123
130
|
cwd: step.home,
|
|
124
|
-
env:
|
|
131
|
+
env: run.value.env,
|
|
125
132
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
133
|
+
windowsVerbatimArguments: run.value.windowsVerbatimArguments,
|
|
126
134
|
});
|
|
127
135
|
let outcome = 'exited';
|
|
128
136
|
let settled = false;
|
package/dist/probe.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { resolveSpawn } from './spawn.js';
|
|
2
3
|
// Revision 3. The old form passed no `env`, so it silently answered about process.env
|
|
3
|
-
// while reviewer.ts:
|
|
4
|
+
// while reviewer.ts:133 spawns `claude` with `env: ctx.env` — two different questions,
|
|
4
5
|
// one answer. It also made the finding untestable: helpers.ts runs main() in-process,
|
|
5
6
|
// so a test can control ctx.env and nothing else.
|
|
7
|
+
//
|
|
8
|
+
// Row 164, the same argument one level down: the probe starts the binary through
|
|
9
|
+
// resolveSpawn, exactly as a gate will — a probe that resolved differently would call a
|
|
10
|
+
// `.cmd` shim missing while the gate ran it. spawnSync, not execFileSync, whose typings
|
|
11
|
+
// omit windowsVerbatimArguments; the question is unchanged: did it start and exit 0.
|
|
6
12
|
export function probe(cmd, args, env) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
13
|
+
const run = resolveSpawn(cmd, args, env);
|
|
14
|
+
if (!run.ok)
|
|
12
15
|
return false;
|
|
13
|
-
|
|
16
|
+
const r = spawnSync(run.value.file, run.value.args, {
|
|
17
|
+
stdio: 'ignore', env: run.value.env, windowsVerbatimArguments: run.value.windowsVerbatimArguments,
|
|
18
|
+
});
|
|
19
|
+
return r.error === undefined && r.status === 0;
|
|
14
20
|
}
|
package/dist/reviewer.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { validatePin } from './harness.js';
|
|
7
7
|
import { ok, refuse, v } from './refusal.js';
|
|
8
|
+
import { resolveSpawn } from './spawn.js';
|
|
8
9
|
export const PROMPT_NAMES = [
|
|
9
10
|
'slicing-critic', 'plan-critic', 'code-reviewer', 'silent-failure-hunter',
|
|
10
11
|
'type-design', 'pr-test', 'drift-reviewer', 'design-critic', 'design-reviewer',
|
|
@@ -95,15 +96,23 @@ export function invokeReviewer(ctx, harness, opts) {
|
|
|
95
96
|
pin = pinR.value;
|
|
96
97
|
}
|
|
97
98
|
const { cmd, args, env } = harness.reviewer.spawn(pin, opts.extensions);
|
|
99
|
+
// Row 164: on Windows the CLI may be a `.cmd` shim, which only cmd.exe can start. The
|
|
100
|
+
// prompt rides stdin, so the shim's second parse of the command line never sees it. A
|
|
101
|
+
// reviewer that cannot be started is an invocation failure on every platform: the
|
|
102
|
+
// resolution's own got/want, under the rule a missing binary has always reported.
|
|
103
|
+
const run = resolveSpawn(cmd, args, { ...ctx.env, ...env });
|
|
104
|
+
if (!run.ok)
|
|
105
|
+
return refuse(run.violations.map((x) => ({ ...x, rule: 'reviewer-invocation' })));
|
|
98
106
|
const timeout = opts.timeoutMs ?? REVIEWER_TIMEOUT_MS;
|
|
99
107
|
for (let attempt = 0;; attempt += 1) {
|
|
100
|
-
const r = spawnSync(
|
|
108
|
+
const r = spawnSync(run.value.file, run.value.args, {
|
|
101
109
|
cwd: opts.cwd,
|
|
102
|
-
env:
|
|
110
|
+
env: run.value.env,
|
|
103
111
|
input: opts.prompt,
|
|
104
112
|
encoding: 'utf8',
|
|
105
113
|
timeout,
|
|
106
114
|
maxBuffer: 64 * 1024 * 1024,
|
|
115
|
+
windowsVerbatimArguments: run.value.windowsVerbatimArguments,
|
|
107
116
|
});
|
|
108
117
|
if (r.error) {
|
|
109
118
|
// A stall is NOT the missing-binary case. Reporting it as one sent the last
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { statSync } from 'node:fs';
|
|
2
|
+
import { extname, join, win32 } from 'node:path';
|
|
3
|
+
import { ok, refuse, v } from './refusal.js';
|
|
4
|
+
// The two ways Windows starts a program: an image CreateProcess runs itself, or a batch
|
|
5
|
+
// file only cmd.exe can run. PATHEXT also lists script-host types (.js, .vbs, .wsf) that
|
|
6
|
+
// cmd.exe would open with Windows Script Host — never what a CLI's `.js` means — so
|
|
7
|
+
// those are not candidates at all.
|
|
8
|
+
const IMAGES = ['.com', '.exe'];
|
|
9
|
+
const BATCH = ['.bat', '.cmd'];
|
|
10
|
+
// Every cmd.exe metacharacter, `"` included, and whatever follows a `%`. Escaped, a quote
|
|
11
|
+
// never opens cmd's quote mode, and no `%NAME%` or `%NAME:a=b%` names a variable: cmd.exe
|
|
12
|
+
// expands those before it reads a single caret, so the name itself must start with one.
|
|
13
|
+
// Nothing in an argument is then special to cmd.exe, and each caret is one escape.
|
|
14
|
+
const CMD_META = /[()[\]%!^"`<>&|;, *?]|(?<=%)[^]/gu;
|
|
15
|
+
// Windows env names are case-insensitive, and a spread process.env carries `Path` beside
|
|
16
|
+
// any `PATH` a caller adds. Node gives the child the key that sorts first, so resolution
|
|
17
|
+
// reads that one — any other answer names a binary the child never sees.
|
|
18
|
+
function envGet(env, name) {
|
|
19
|
+
const key = Object.keys(env).sort().find((k) => k.toUpperCase() === name);
|
|
20
|
+
return key === undefined ? undefined : env[key];
|
|
21
|
+
}
|
|
22
|
+
function isFile(p) {
|
|
23
|
+
try {
|
|
24
|
+
return statSync(p).isFile();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false; // absent, or under a PATH entry that is not a directory: no candidate
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// PATH order, then PATHEXT order inside each directory — cmd.exe's walk minus its first
|
|
31
|
+
// step: the current directory is never searched, and neither is a relative entry (`.`, or
|
|
32
|
+
// the empty one a trailing `;` leaves), so a `pi.cmd` committed to a reviewed repo cannot
|
|
33
|
+
// make itself the judge. A name that carries a path is checked where it points.
|
|
34
|
+
function whichWin32(cmd, env) {
|
|
35
|
+
const runnable = [...IMAGES, ...BATCH];
|
|
36
|
+
const listed = (envGet(env, 'PATHEXT') ?? '').split(';').map((e) => e.toLowerCase())
|
|
37
|
+
.filter((e) => runnable.includes(e));
|
|
38
|
+
const exts = runnable.includes(extname(cmd).toLowerCase()) ? [''] : listed.length > 0 ? listed : runnable;
|
|
39
|
+
const bases = /[\\/]/.test(cmd) ? [cmd] : (envGet(env, 'PATH') ?? '').split(';')
|
|
40
|
+
.map((d) => d.replace(/^"(.*)"$/, '$1')).filter((d) => win32.isAbsolute(d)).map((d) => join(d, cmd));
|
|
41
|
+
for (const base of bases) {
|
|
42
|
+
for (const ext of exts)
|
|
43
|
+
if (isFile(`${base}${ext}`))
|
|
44
|
+
return `${base}${ext}`;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
// The C runtime's rules (CommandLineToArgvW; node's own argv parser follows them): inside
|
|
49
|
+
// quotes a backslash is literal unless a quote follows it, where the run doubles and the
|
|
50
|
+
// quote takes one more.
|
|
51
|
+
function crtQuote(arg) {
|
|
52
|
+
let out = '"';
|
|
53
|
+
let slashes = 0;
|
|
54
|
+
for (const ch of arg) {
|
|
55
|
+
if (ch === '\\') {
|
|
56
|
+
slashes += 1;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
out += ch === '"' ? `${'\\'.repeat(slashes * 2 + 1)}"` : `${'\\'.repeat(slashes)}${ch}`;
|
|
60
|
+
slashes = 0;
|
|
61
|
+
}
|
|
62
|
+
return `${out}${'\\'.repeat(slashes * 2)}"`;
|
|
63
|
+
}
|
|
64
|
+
const caret = (s) => s.replace(CMD_META, '^$&');
|
|
65
|
+
// Off win32 this is the identity: the spawn every site made before. On win32 the name
|
|
66
|
+
// is resolved here, not by libuv, which tries only `.com` and `.exe` — and looks in the
|
|
67
|
+
// child's cwd first — while npm installs `pi` and `claude` as `.cmd` shims: a bare-name
|
|
68
|
+
// spawn of either was ENOENT, and the resolved shim will not start without a shell
|
|
69
|
+
// (EINVAL). A name nothing on PATH answers to is refused rather than handed to libuv's
|
|
70
|
+
// cwd search. An image starts directly; a batch file runs as
|
|
71
|
+
// `cmd.exe /d /v:off /s /c ""<file>" <args>"`, built here verbatim:
|
|
72
|
+
//
|
|
73
|
+
// - the file is quoted, not caret-escaped: carets cannot keep a `=` in an install path
|
|
74
|
+
// from splitting the command (measured);
|
|
75
|
+
// - each argument is CRT-quoted, then caret-escaped TWICE, because a shim forwards `%*`
|
|
76
|
+
// and cmd.exe parses that line again — escaped once, `" & echo pwned & "` ran `echo`;
|
|
77
|
+
// - /v:off, because the registry can turn delayed expansion on, and then `!VAR!` in an
|
|
78
|
+
// argument expands; /d skips AutoRun commands;
|
|
79
|
+
// - cmd.exe is System32's, never ComSpec, which may name a shell with other syntax; and
|
|
80
|
+
// it runs with NoDefaultCurrentDirectoryInExePath, because the shim runs a bare `node`
|
|
81
|
+
// when no node.exe sits beside it and cmd.exe would look in the cwd first — measured:
|
|
82
|
+
// a `node.cmd` planted in the reviewed repo answered as the judge. The CLI inherits it.
|
|
83
|
+
//
|
|
84
|
+
// Nothing makes cmd.exe carry a line break: it ends the command at LF, silently dropping
|
|
85
|
+
// every later argument, and deletes CR. Such an argument is refused, never truncated.
|
|
86
|
+
export function resolveSpawn(cmd, args, env, platform = process.platform) {
|
|
87
|
+
if (platform !== 'win32')
|
|
88
|
+
return ok({ file: cmd, args: [...args], env, windowsVerbatimArguments: false });
|
|
89
|
+
const file = whichWin32(cmd, env);
|
|
90
|
+
if (file === undefined) {
|
|
91
|
+
return refuse([v(cmd, 'not-on-path', 'no .com, .exe, .bat or .cmd by that name on PATH', `the ${cmd} CLI installed on PATH — witness never looks for it in the current directory, where a repo could plant one`)]);
|
|
92
|
+
}
|
|
93
|
+
if (IMAGES.includes(extname(file).toLowerCase()))
|
|
94
|
+
return ok({ file, args: [...args], env, windowsVerbatimArguments: false });
|
|
95
|
+
for (const [i, arg] of args.entries()) {
|
|
96
|
+
if (/[\r\n]/.test(arg)) {
|
|
97
|
+
return refuse([v(cmd, 'batch-argv-line-break', `argument ${i + 1}: ${JSON.stringify(arg.slice(0, 60))}`, `an argument without CR or LF — ${file} is a batch file, and cmd.exe ends a command at a line feed and deletes carriage returns, so the CLI would run on a truncated command line`)]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const line = [`"${file}"`, ...args.map((a) => caret(caret(crtQuote(a))))].join(' ');
|
|
101
|
+
return ok({
|
|
102
|
+
file: win32.join(envGet(env, 'SYSTEMROOT') ?? 'C:\\Windows', 'System32', 'cmd.exe'),
|
|
103
|
+
args: ['/d', '/v:off', '/s', '/c', `"${line}"`],
|
|
104
|
+
env: { ...env, NoDefaultCurrentDirectoryInExePath: '1' },
|
|
105
|
+
windowsVerbatimArguments: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@popovych.co/witness",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.6",
|
|
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.14.
|
|
4
|
+
"version": "0.14.6",
|
|
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.14.
|
|
13
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
6
|
+
${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6} 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.14.
|
|
18
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
13
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
19
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
18
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
18
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.14.
|
|
18
|
+
WITNESS="${WITNESS_BIN:-npx -y @popovych.co/witness@0.14.6}"
|
|
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.
|