@popovych.co/witness 0.14.7 → 0.14.8
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 +43 -3
- package/dist/adopt.js +5 -3
- package/dist/departure.js +22 -0
- package/dist/gate.js +3 -1
- package/dist/gates/plan.js +6 -2
- package/dist/refusal.js +7 -2
- package/dist/verbs/abandon.js +10 -9
- package/dist/verbs/adopt.js +6 -1
- package/dist/verbs/write.js +10 -3
- 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/abandon.js
CHANGED
|
@@ -10,7 +10,7 @@ import { crashPoint, withTxn } from './txn.js';
|
|
|
10
10
|
import { appendEntry, effortAbandoned, effortStreams, entryLine, journalRel, readStream } from './journal.js';
|
|
11
11
|
import { findById, plansOn } from './scan.js';
|
|
12
12
|
import { effortOf, effortWrites } from './reviewed.js';
|
|
13
|
-
import { ok, refuse, v } from './refusal.js';
|
|
13
|
+
import { humanCall, ok, refuse, v } from './refusal.js';
|
|
14
14
|
import { prepareStamp, writeStamp } from './stamp.js';
|
|
15
15
|
import { kv } from './toon.js';
|
|
16
16
|
import { removeWorktree, worktreePath } from './worktree.js';
|
|
@@ -37,6 +37,21 @@ export function pinCommit(root, rel, pin, from) {
|
|
|
37
37
|
return found;
|
|
38
38
|
}
|
|
39
39
|
export const keptLine = (k) => `${String(k.doc.meta.id)} — not unwound · still standing on it: ${k.standing.join(' ')}`;
|
|
40
|
+
// D167. The abandon says everything it did to canon, on both paths, as `kept:` already did: what
|
|
41
|
+
// it reverted, what came back with it, and what the walk took back on the way.
|
|
42
|
+
export function abandonLines(items, kept) {
|
|
43
|
+
return [
|
|
44
|
+
...items.flatMap((item) => [
|
|
45
|
+
...(item.revert ? [kv('reverted', `${String(item.revert.doc.meta.id)} (${item.revert.action}) — journal untouched`)] : []),
|
|
46
|
+
...(item.revert?.returns ?? []).flatMap((r) => [
|
|
47
|
+
kv('returned', `${r.entry.artifact} — back in canon as ${r.entry.to} · the re-slice into ${r.by} is taken back`),
|
|
48
|
+
...r.unwound.map((u) => kv('unwound', `${u.artifact} — stays out of canon · an abandoned effort created it`)),
|
|
49
|
+
]),
|
|
50
|
+
...(item.kept ? [kv('kept', keptLine(item.kept))] : []),
|
|
51
|
+
]),
|
|
52
|
+
...kept.map((k) => kv('kept', keptLine(k))),
|
|
53
|
+
];
|
|
54
|
+
}
|
|
40
55
|
// D166 (review). The writes the way back takes back besides this abandon's own targets: every
|
|
41
56
|
// write of an effort already abandoned — whose abandon skipped a spec a later re-slice had
|
|
42
57
|
// departed — and every write of the effort being abandoned now (`effort`, absent on the plan
|
|
@@ -117,6 +132,28 @@ export function specRevertFor(root, canon, spec, pin, abandonSet, effort) {
|
|
|
117
132
|
const design = priorDesign !== undefined || existsSync(join(root, designR)) ? { rel: designR, prior: priorDesign } : undefined;
|
|
118
133
|
return ok({ doc: spec, action: 'restore', content: prior.out, toStatus, design, returns });
|
|
119
134
|
}
|
|
135
|
+
// D167. A return lands on the path its spec left from, and `rename` moves an id but leaves its
|
|
136
|
+
// file, so another doc may hold that path — or another spec this abandon brings back may have
|
|
137
|
+
// left from it too. Written over, either left canon with nothing to say so. Only a doc this same
|
|
138
|
+
// abandon departs may stand there; any other holder, or a second return, is a human's call.
|
|
139
|
+
export function returnCollisions(root, canon, items) {
|
|
140
|
+
const departing = new Set(items.flatMap((i) => (i.revert?.action === 'delete' ? [i.revert.doc.rel] : [])));
|
|
141
|
+
const returns = items.flatMap((i) => (i.revert?.returns ?? []).map((r) => ({ r, going: String(i.revert.doc.meta.id) })));
|
|
142
|
+
const landing = new Map();
|
|
143
|
+
for (const { r } of returns) {
|
|
144
|
+
const ids = landing.get(r.rel) ?? [];
|
|
145
|
+
if (!ids.includes(r.entry.artifact))
|
|
146
|
+
landing.set(r.rel, [...ids, r.entry.artifact]);
|
|
147
|
+
}
|
|
148
|
+
return [
|
|
149
|
+
...returns.filter(({ r }) => !departing.has(r.rel) && existsSync(join(root, r.rel))).map(({ r, going }) => {
|
|
150
|
+
const held = canon.docs.find((d) => d.rel === r.rel);
|
|
151
|
+
const holder = held ? String(held.meta.id) : 'a doc canon cannot read';
|
|
152
|
+
return humanCall(v('spec', 'path-taken', `${r.rel} holds ${holder}`, `that path free first — ${r.entry.artifact} comes back to it when ${going} goes · what becomes of ${holder} is a human's call`));
|
|
153
|
+
}),
|
|
154
|
+
...[...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`))),
|
|
155
|
+
];
|
|
156
|
+
}
|
|
120
157
|
export function executeAbandon(root, ctx, items, effortEntry, subject) {
|
|
121
158
|
const files = new Set();
|
|
122
159
|
const journalMulti = [];
|
|
@@ -187,10 +224,13 @@ export function executeAbandon(root, ctx, items, effortEntry, subject) {
|
|
|
187
224
|
else if (d !== undefined && existsSync(join(root, d.rel)))
|
|
188
225
|
unlinkSync(join(root, d.rel));
|
|
189
226
|
}
|
|
190
|
-
for (const r of item.revert.returns)
|
|
191
|
-
r.restore();
|
|
192
227
|
}
|
|
193
228
|
}
|
|
229
|
+
// D167. Every departure lands before any return (P9b): restored per item, a later item's
|
|
230
|
+
// departure unlinked the spec an earlier item's return had just brought back to its path.
|
|
231
|
+
for (const item of items)
|
|
232
|
+
for (const r of item.revert?.returns ?? [])
|
|
233
|
+
r.restore();
|
|
194
234
|
for (const s of stamps)
|
|
195
235
|
writeStamp(root, s);
|
|
196
236
|
for (const se of specEntries)
|
package/dist/adopt.js
CHANGED
|
@@ -6,9 +6,11 @@ import { readStream } from './journal.js';
|
|
|
6
6
|
import { canonicalSha } from './sha.js';
|
|
7
7
|
const hasTrailer = (root, sha) => git(root, 'show', '-s', '--format=%B', sha).split('\n').some((l) => l.trim() === TRAILER);
|
|
8
8
|
const commitsTouching = (root, rel) => git(root, 'log', '--format=%H', '--', rel).split('\n').filter(Boolean);
|
|
9
|
-
|
|
9
|
+
// `absolved`: commits an `adopt` entry took in (`adoptedCommits`), which count as witnessed too —
|
|
10
|
+
// a hand commit that `adopt` absolved is the doc's witnessed state from then on.
|
|
11
|
+
export function lastWitnessed(root, rel, absolved = new Set()) {
|
|
10
12
|
for (const sha of commitsTouching(root, rel)) {
|
|
11
|
-
if (!hasTrailer(root, sha))
|
|
13
|
+
if (!hasTrailer(root, sha) && !absolved.has(sha))
|
|
12
14
|
continue;
|
|
13
15
|
const raw = tryGit(root, 'show', `${sha}:${rel}`);
|
|
14
16
|
if (!raw.ok)
|
|
@@ -16,7 +18,7 @@ export function lastWitnessed(root, rel) {
|
|
|
16
18
|
const doc = splitDoc(raw.out);
|
|
17
19
|
if (!doc.ok)
|
|
18
20
|
continue;
|
|
19
|
-
return { sha: canonicalSha(doc.value.meta, doc.value.body), criteria: doc.value.meta.criteria ?? null };
|
|
21
|
+
return { sha: canonicalSha(doc.value.meta, doc.value.body), criteria: doc.value.meta.criteria ?? null, id: String(doc.value.meta.id) };
|
|
20
22
|
}
|
|
21
23
|
return undefined;
|
|
22
24
|
}
|
package/dist/departure.js
CHANGED
|
@@ -4,6 +4,7 @@ import { deferralMoves } from './deferral.js';
|
|
|
4
4
|
import { designRel } from './design.js';
|
|
5
5
|
import { FLOOR_STREAM } from './floor.js';
|
|
6
6
|
import { splitDoc } from './fm.js';
|
|
7
|
+
import { adoptedCommits, lastWitnessed } from './adopt.js';
|
|
7
8
|
import { gitBlob, tryGit } from './gitio.js';
|
|
8
9
|
import { journalRel, readStream } from './journal.js';
|
|
9
10
|
import { v } from './refusal.js';
|
|
@@ -121,6 +122,8 @@ function returnsThrough(root, paths, commit, chain, unwound, takenBack, seen) {
|
|
|
121
122
|
// Each link of the chain hands home the debts that came to it from the next one down.
|
|
122
123
|
moves: chain.flatMap((s, k) => deferralMoves(readStream(root, s), s, id, (d) => d.moved_from === (chain[k + 1] ?? id))),
|
|
123
124
|
unwound,
|
|
125
|
+
rel,
|
|
126
|
+
by: successor,
|
|
124
127
|
}];
|
|
125
128
|
});
|
|
126
129
|
}
|
|
@@ -145,3 +148,22 @@ export function idRetired(root, id, type) {
|
|
|
145
148
|
return v('id', 'id-taken', id, `a new id — ${id} was a spec its abandon unwound`);
|
|
146
149
|
return undefined;
|
|
147
150
|
}
|
|
151
|
+
// D167. Whether a doc under this id may land on this path, which was last witnessed (by a
|
|
152
|
+
// trailer, or by an `adopt` that absolved the commit) under `prior`. A superseded spec comes back
|
|
153
|
+
// to the path it left from, so its departure holds that path as it holds the id. Any other id
|
|
154
|
+
// left a path only by hand, since `rename` keeps the file: the path is held only while the doc
|
|
155
|
+
// on it is what `prior` became — an in-place id change, which is `rename` done by hand. A
|
|
156
|
+
// removal, a `prior` living at another path, or a path left empty holds nothing: nothing lands back.
|
|
157
|
+
export function pathRetired(root, canon, rel, id) {
|
|
158
|
+
const prior = lastWitnessed(root, rel, adoptedCommits(root))?.id;
|
|
159
|
+
if (prior === undefined || prior === id)
|
|
160
|
+
return undefined;
|
|
161
|
+
const exit = canonExit(readStream(root, prior));
|
|
162
|
+
if (exit?.to === 'superseded') {
|
|
163
|
+
return v('id', 'path-taken', rel, `a new id — ${rel} is where ${prior} comes back if the re-slice that superseded it is taken back`);
|
|
164
|
+
}
|
|
165
|
+
const elsewhere = findById(canon, prior);
|
|
166
|
+
if (exit !== undefined || (elsewhere !== undefined && elsewhere.rel !== rel) || !existsSync(join(root, rel)))
|
|
167
|
+
return undefined;
|
|
168
|
+
return v('id', 'path-taken', rel, `the id it was witnessed under — ${rel} held ${prior} · witness rename is how an id changes`);
|
|
169
|
+
}
|
package/dist/gate.js
CHANGED
|
@@ -470,10 +470,12 @@ export async function runGate(ctx, gateName, target, flags) {
|
|
|
470
470
|
const marker = {
|
|
471
471
|
op: `gate-${spec.gate}`,
|
|
472
472
|
files: [...new Set(files)],
|
|
473
|
+
// D167. Per stream, the marker lists lines in the order the body appends them, because
|
|
474
|
+
// `landedJournal` reads a crash by that order — and the plan gate stamps its own target.
|
|
473
475
|
journalMulti: [
|
|
474
476
|
{ stream: target, line: entryLine(entry) },
|
|
475
|
-
...stamps.map((s) => ({ stream: s.stream, line: s.line })),
|
|
476
477
|
...discharged.map((d) => ({ stream: target, line: entryLine(d) })),
|
|
478
|
+
...stamps.map((s) => ({ stream: s.stream, line: s.line })),
|
|
477
479
|
],
|
|
478
480
|
};
|
|
479
481
|
const txn = withTxn(root, marker, () => {
|
package/dist/gates/plan.js
CHANGED
|
@@ -8,6 +8,10 @@ import { effortOf, planPairSha } from '../reviewed.js';
|
|
|
8
8
|
import { validateDoc } from '../schemas.js';
|
|
9
9
|
import { registerGate } from '../gate.js';
|
|
10
10
|
import { designPending, designStamp } from '../design.js';
|
|
11
|
+
// D167. One derivation for the gate's stamp and `decide`'s: a pass approves a draft and nothing
|
|
12
|
+
// else. Row 95 keeps an in-flight status through a re-author, and a passing re-gate must not
|
|
13
|
+
// take it back — nor stamp a finished plan approved again.
|
|
14
|
+
const draftApproval = (plan) => String(plan.meta.status) === 'draft' ? [{ artifact: String(plan.meta.id), to: 'approved' }] : [];
|
|
11
15
|
registerGate({
|
|
12
16
|
gate: 'plan',
|
|
13
17
|
targetKind: 'plan',
|
|
@@ -69,7 +73,7 @@ registerGate({
|
|
|
69
73
|
},
|
|
70
74
|
promptBody,
|
|
71
75
|
checks,
|
|
72
|
-
stamps:
|
|
76
|
+
stamps: draftApproval(plan),
|
|
73
77
|
});
|
|
74
78
|
},
|
|
75
79
|
currentSha(_root, canon, _cfg, planId) {
|
|
@@ -83,6 +87,6 @@ registerGate({
|
|
|
83
87
|
},
|
|
84
88
|
approveStamps(_root, canon, planId) {
|
|
85
89
|
const doc = findById(canon, planId);
|
|
86
|
-
return doc
|
|
90
|
+
return doc ? draftApproval(doc) : [];
|
|
87
91
|
},
|
|
88
92
|
});
|
package/dist/refusal.js
CHANGED
|
@@ -11,13 +11,18 @@ export function v(field, rule, got, want, remedy) {
|
|
|
11
11
|
export const humanCall = (x) => ({ ...x, human: true });
|
|
12
12
|
export function renderRefusal(violations) {
|
|
13
13
|
const runnable = [...new Set(violations.map((x) => x.remedy).filter((r) => r !== undefined && !/<[^>]+>/.test(r)))];
|
|
14
|
+
// D167. Row 108's principle cuts both ways: the stop is true for the human's rows and false for
|
|
15
|
+
// the reader's own, so the trailer names the human's rules and leaves the rest self-repairable.
|
|
16
|
+
const human = [...new Set(violations.filter((x) => x.human).map((x) => x.rule))];
|
|
17
|
+
const mixed = violations.some((x) => !x.human);
|
|
14
18
|
return [
|
|
15
19
|
// The destructure keeps `remedy` out of the row objects: the column list already
|
|
16
20
|
// governs what prints, but a remedy is not a column and must not become one.
|
|
17
21
|
...rows('refused', ['field', 'rule', 'got', 'want'], violations.map(({ remedy: _remedy, ...rest }) => rest)),
|
|
18
22
|
...runnable.map((r) => `run: ${r}`),
|
|
19
|
-
|
|
20
|
-
?
|
|
23
|
+
human.length
|
|
24
|
+
? `help: a human's decision: ${human.join(' · ')} — stop and surface the list; re-running cannot clear it`
|
|
25
|
+
+ (mixed ? ' · the other rows are structured for self-repair once it is settled' : '')
|
|
21
26
|
: 'help: fix each row and re-run — rows are structured for self-repair',
|
|
22
27
|
];
|
|
23
28
|
}
|
package/dist/verbs/abandon.js
CHANGED
|
@@ -7,7 +7,7 @@ import { effortWrites } from '../reviewed.js';
|
|
|
7
7
|
import { canonicalSha } from '../sha.js';
|
|
8
8
|
import { renderRefusal, v } from '../refusal.js';
|
|
9
9
|
import { kv } from '../toon.js';
|
|
10
|
-
import {
|
|
10
|
+
import { abandonLines, executeAbandon, planItems, returnCollisions, specRevertFor } 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,17 +35,18 @@ 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]);
|
|
39
|
+
if (taken.length > 0) {
|
|
40
|
+
renderRefusal(taken).forEach((l) => ctx.err(l));
|
|
41
|
+
return EXIT.REFUSED;
|
|
42
|
+
}
|
|
38
43
|
const done = executeAbandon(root, ctx, [itemR.value], undefined, `abandon(${target})`);
|
|
39
44
|
if (!done.ok) {
|
|
40
45
|
renderRefusal(done.violations).forEach((l) => ctx.err(l));
|
|
41
46
|
return EXIT.REFUSED;
|
|
42
47
|
}
|
|
43
48
|
ctx.out(kv('abandoned', target));
|
|
44
|
-
|
|
45
|
-
ctx.out(kv('reverted', `${String(itemR.value.revert.doc.meta.id)} (${itemR.value.revert.action}) — journal untouched`));
|
|
46
|
-
}
|
|
47
|
-
if (itemR.value.kept)
|
|
48
|
-
ctx.out(kv('kept', keptLine(itemR.value.kept)));
|
|
49
|
+
abandonLines([itemR.value], []).forEach((l) => ctx.out(l));
|
|
49
50
|
return EXIT.OK;
|
|
50
51
|
}
|
|
51
52
|
if (streamExists(root, target) && latestRecap(root, target)) {
|
|
@@ -103,6 +104,7 @@ export async function run(ctx, argv) {
|
|
|
103
104
|
else if (outcome)
|
|
104
105
|
items.push({ revert: outcome });
|
|
105
106
|
}
|
|
107
|
+
problems.push(...returnCollisions(root, canon, items));
|
|
106
108
|
if (problems.length > 0) {
|
|
107
109
|
renderRefusal(problems).forEach((l) => ctx.err(l));
|
|
108
110
|
return EXIT.REFUSED;
|
|
@@ -112,9 +114,8 @@ export async function run(ctx, argv) {
|
|
|
112
114
|
renderRefusal(done.violations).forEach((l) => ctx.err(l));
|
|
113
115
|
return EXIT.REFUSED;
|
|
114
116
|
}
|
|
115
|
-
ctx.out(kv('abandoned', `effort ${target} — ${items.length} artifacts walked
|
|
116
|
-
|
|
117
|
-
ctx.out(kv('kept', keptLine(k)));
|
|
117
|
+
ctx.out(kv('abandoned', `effort ${target} — ${items.length} artifacts walked · journal remembers the attempt`));
|
|
118
|
+
abandonLines(items, kept).forEach((l) => ctx.out(l));
|
|
118
119
|
return EXIT.OK;
|
|
119
120
|
}
|
|
120
121
|
renderRefusal([v('target', 'unknown-target', target, 'a plan id or an effort slug')]).forEach((l) => ctx.err(l));
|
package/dist/verbs/adopt.js
CHANGED
|
@@ -9,7 +9,7 @@ import { dirtyStatePaths, primaryRoot, stateCommit } from '../gitio.js';
|
|
|
9
9
|
import { appendEntry, entryLine, journalRel } from '../journal.js';
|
|
10
10
|
import { acquireLock } from '../lock.js';
|
|
11
11
|
import { renderRefusal, v } from '../refusal.js';
|
|
12
|
-
import { idRetired } from '../departure.js';
|
|
12
|
+
import { idRetired, pathRetired } from '../departure.js';
|
|
13
13
|
import { findById, findCycle, loadCanon } from '../scan.js';
|
|
14
14
|
import { validateDoc } from '../schemas.js';
|
|
15
15
|
import { canonicalSha, short } from '../sha.js';
|
|
@@ -47,6 +47,11 @@ export async function run(ctx, argv) {
|
|
|
47
47
|
const taken = idRetired(root, id, String(meta.type));
|
|
48
48
|
if (taken)
|
|
49
49
|
violations.push(taken);
|
|
50
|
+
// D167. Nor land on a path another id was last witnessed at — the same check `write` runs on a
|
|
51
|
+
// create. A doc witnessed where it sits passes on its own id, so any amendment stays adoptable.
|
|
52
|
+
const retired = pathRetired(root, canon, rel, id);
|
|
53
|
+
if (retired)
|
|
54
|
+
violations.push(retired);
|
|
50
55
|
for (const dep of Array.isArray(meta.depends) ? meta.depends : []) {
|
|
51
56
|
if (!findById(canon, dep))
|
|
52
57
|
violations.push(v('depends', 'unknown-dep', dep, 'an existing doc id'));
|
package/dist/verbs/write.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parseArgs } from 'node:util';
|
|
|
4
4
|
import { EXIT } from '../cli.js';
|
|
5
5
|
import { loadConfig } from '../config.js';
|
|
6
6
|
import { deferralMoves } from '../deferral.js';
|
|
7
|
-
import { canonExit, idRetired, prepareDeparture } from '../departure.js';
|
|
7
|
+
import { canonExit, idRetired, pathRetired, prepareDeparture } from '../departure.js';
|
|
8
8
|
import { designPending, designStamp } from '../design.js';
|
|
9
9
|
import { ID_RE } from '../dsl.js';
|
|
10
10
|
import { serializeDoc, splitDoc, writeDoc } from '../fm.js';
|
|
@@ -129,8 +129,9 @@ export async function run(ctx, argv) {
|
|
|
129
129
|
renderRefusal(viol).forEach(ctx.err);
|
|
130
130
|
return EXIT.REFUSED;
|
|
131
131
|
};
|
|
132
|
-
// D166. A create may not take an id that already names something (`idRetired`
|
|
133
|
-
//
|
|
132
|
+
// D166. A create may not take an id that already names something (`idRetired`), with the one
|
|
133
|
+
// re-use of a spec its abandon unwound; a stray stream holds no id. `rename` is stricter on its
|
|
134
|
+
// target — no stream at all — because it moves its own stream onto that id.
|
|
134
135
|
if (existing === undefined) {
|
|
135
136
|
const taken = idRetired(root, id, String(manifest.type));
|
|
136
137
|
if (taken)
|
|
@@ -213,6 +214,12 @@ export async function run(ctx, argv) {
|
|
|
213
214
|
? `a new id — ${rel} holds ${String(holder.meta.id)}`
|
|
214
215
|
: `a new id — ${rel} holds a doc canon cannot read (fix or revert it first)`)]);
|
|
215
216
|
}
|
|
217
|
+
// D167. Nor on the path a superseded spec comes back to (`pathRetired`).
|
|
218
|
+
if (existing === undefined) {
|
|
219
|
+
const retired = pathRetired(root, canon, rel, id);
|
|
220
|
+
if (retired)
|
|
221
|
+
return refuseWrite([retired]);
|
|
222
|
+
}
|
|
216
223
|
const stream = journalRel(effort);
|
|
217
224
|
const unrelated = dirtyStatePaths(root).filter((p) => p !== rel && p !== stream);
|
|
218
225
|
if (unrelated.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@popovych.co/witness",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.8",
|
|
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.8",
|
|
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.8}"
|
|
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.8} 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.8}"
|
|
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.8}"
|
|
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.8}"
|
|
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.8}"
|
|
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.8}"
|
|
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.8}"
|
|
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.
|