patchwork-os 1.2.0-beta.2.canary.745 → 1.2.0-beta.2.canary.747
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/evidenceCoverage.d.ts +66 -0
- package/dist/evidenceCoverage.js +188 -0
- package/dist/evidenceCoverage.js.map +1 -0
- package/dist/index.js +94 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patchwork evidence` — how much of the evidence spine can actually be joined?
|
|
3
|
+
*
|
|
4
|
+
* CLAUDE.md's Evidence Spine section tells the next session to re-measure this
|
|
5
|
+
* before scoping a cross-ledger reader, and says why in its own words: two of
|
|
6
|
+
* its load-bearing claims "went stale or were wrong within two days". Doing that
|
|
7
|
+
* measurement took a bespoke throwaway script every time. This makes it a verb.
|
|
8
|
+
*
|
|
9
|
+
* ## It reports denominators. It is NOT the reader.
|
|
10
|
+
*
|
|
11
|
+
* The spine's rule is "do NOT build the readers ahead of the evidence" — a
|
|
12
|
+
* cross-ledger graph built now would be a view over data that does not exist,
|
|
13
|
+
* and the shape of the view would then dictate the shape of the evidence,
|
|
14
|
+
* backwards. So this answers the question that comes FIRST: how many rows carry
|
|
15
|
+
* a correlation id at all, and how many runs appear in more than one ledger.
|
|
16
|
+
* Measured 2026-08-26 on the reference machine, the answer was ZERO runs in
|
|
17
|
+
* both — which is the fact that keeps the reader unbuilt.
|
|
18
|
+
*
|
|
19
|
+
* ## Absent is not zero
|
|
20
|
+
*
|
|
21
|
+
* A ledger that does not exist is reported ABSENT, never as `0 rows`. They are
|
|
22
|
+
* different facts: `butler/permission_exercises.jsonl` is absent because no
|
|
23
|
+
* standing permission has ever been granted, and that absence is CORRECT rather
|
|
24
|
+
* than a gap to plumb. Rendering it as a zero row would invite someone to go and
|
|
25
|
+
* "fix" it.
|
|
26
|
+
*
|
|
27
|
+
* ## Counts only, never contents
|
|
28
|
+
*
|
|
29
|
+
* These ledgers hold real task titles, captured output tails and third-party
|
|
30
|
+
* record ids, and a correlation id IS a run's `taskId`. So nothing here prints a
|
|
31
|
+
* row, an id, or any value — only counts and the file name. The house rule is
|
|
32
|
+
* that a measurement may leave the machine and the rows may not.
|
|
33
|
+
*/
|
|
34
|
+
/** Ledgers on the spine, with where each lives relative to `$PATCHWORK_HOME`. */
|
|
35
|
+
export declare const SPINE_LEDGERS: ReadonlyArray<{
|
|
36
|
+
key: string;
|
|
37
|
+
file: string;
|
|
38
|
+
}>;
|
|
39
|
+
export interface LedgerCoverage {
|
|
40
|
+
key: string;
|
|
41
|
+
file: string;
|
|
42
|
+
/** True when the file does not exist. Distinct from `rows === 0`. */
|
|
43
|
+
absent: boolean;
|
|
44
|
+
rows: number;
|
|
45
|
+
/** Rows carrying a `correlationId` — the joinable population. */
|
|
46
|
+
joinable: number;
|
|
47
|
+
/** Distinct correlation ids, i.e. how many runs this ledger can speak about. */
|
|
48
|
+
distinctRuns: number;
|
|
49
|
+
/** Lines that would not parse. Reported, never silently skipped. */
|
|
50
|
+
corrupt: number;
|
|
51
|
+
}
|
|
52
|
+
export interface PairCoverage {
|
|
53
|
+
a: string;
|
|
54
|
+
b: string;
|
|
55
|
+
shared: number;
|
|
56
|
+
}
|
|
57
|
+
export interface EvidenceCoverage {
|
|
58
|
+
dir: string;
|
|
59
|
+
ledgers: LedgerCoverage[];
|
|
60
|
+
/** Only pairs where BOTH sides have at least one joinable row. */
|
|
61
|
+
pairs: PairCoverage[];
|
|
62
|
+
/** Runs reachable in more than one ledger — what a reader could traverse. */
|
|
63
|
+
runsInMoreThanOneLedger: number;
|
|
64
|
+
}
|
|
65
|
+
export declare function evidenceCoverage(dir?: string): EvidenceCoverage;
|
|
66
|
+
export declare function formatEvidenceCoverage(c: EvidenceCoverage): string;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `patchwork evidence` — how much of the evidence spine can actually be joined?
|
|
3
|
+
*
|
|
4
|
+
* CLAUDE.md's Evidence Spine section tells the next session to re-measure this
|
|
5
|
+
* before scoping a cross-ledger reader, and says why in its own words: two of
|
|
6
|
+
* its load-bearing claims "went stale or were wrong within two days". Doing that
|
|
7
|
+
* measurement took a bespoke throwaway script every time. This makes it a verb.
|
|
8
|
+
*
|
|
9
|
+
* ## It reports denominators. It is NOT the reader.
|
|
10
|
+
*
|
|
11
|
+
* The spine's rule is "do NOT build the readers ahead of the evidence" — a
|
|
12
|
+
* cross-ledger graph built now would be a view over data that does not exist,
|
|
13
|
+
* and the shape of the view would then dictate the shape of the evidence,
|
|
14
|
+
* backwards. So this answers the question that comes FIRST: how many rows carry
|
|
15
|
+
* a correlation id at all, and how many runs appear in more than one ledger.
|
|
16
|
+
* Measured 2026-08-26 on the reference machine, the answer was ZERO runs in
|
|
17
|
+
* both — which is the fact that keeps the reader unbuilt.
|
|
18
|
+
*
|
|
19
|
+
* ## Absent is not zero
|
|
20
|
+
*
|
|
21
|
+
* A ledger that does not exist is reported ABSENT, never as `0 rows`. They are
|
|
22
|
+
* different facts: `butler/permission_exercises.jsonl` is absent because no
|
|
23
|
+
* standing permission has ever been granted, and that absence is CORRECT rather
|
|
24
|
+
* than a gap to plumb. Rendering it as a zero row would invite someone to go and
|
|
25
|
+
* "fix" it.
|
|
26
|
+
*
|
|
27
|
+
* ## Counts only, never contents
|
|
28
|
+
*
|
|
29
|
+
* These ledgers hold real task titles, captured output tails and third-party
|
|
30
|
+
* record ids, and a correlation id IS a run's `taskId`. So nothing here prints a
|
|
31
|
+
* row, an id, or any value — only counts and the file name. The house rule is
|
|
32
|
+
* that a measurement may leave the machine and the rows may not.
|
|
33
|
+
*/
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
import { patchworkHome } from "./patchworkHome.js";
|
|
37
|
+
/** Ledgers on the spine, with where each lives relative to `$PATCHWORK_HOME`. */
|
|
38
|
+
export const SPINE_LEDGERS = [
|
|
39
|
+
{ key: "gate decisions", file: "worker_gate_decisions.jsonl" },
|
|
40
|
+
{ key: "boundary receipts", file: "boundary_receipts.jsonl" },
|
|
41
|
+
{ key: "privacy shadow", file: "privacy_shadow.jsonl" },
|
|
42
|
+
{ key: "outcomes", file: "outcome-log.jsonl" },
|
|
43
|
+
{ key: "approvals", file: "approval_log.jsonl" },
|
|
44
|
+
{ key: "permission exercises", file: "butler/permission_exercises.jsonl" },
|
|
45
|
+
];
|
|
46
|
+
function scan(dir, key, file) {
|
|
47
|
+
const full = path.join(dir, file);
|
|
48
|
+
if (!existsSync(full)) {
|
|
49
|
+
return {
|
|
50
|
+
cov: {
|
|
51
|
+
key,
|
|
52
|
+
file,
|
|
53
|
+
absent: true,
|
|
54
|
+
rows: 0,
|
|
55
|
+
joinable: 0,
|
|
56
|
+
distinctRuns: 0,
|
|
57
|
+
corrupt: 0,
|
|
58
|
+
},
|
|
59
|
+
ids: new Set(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let text = "";
|
|
63
|
+
try {
|
|
64
|
+
text = readFileSync(full, "utf-8");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Unreadable is not absent — say so by reporting it as present with a
|
|
68
|
+
// corrupt count rather than pretending the file is not there.
|
|
69
|
+
return {
|
|
70
|
+
cov: {
|
|
71
|
+
key,
|
|
72
|
+
file,
|
|
73
|
+
absent: false,
|
|
74
|
+
rows: 0,
|
|
75
|
+
joinable: 0,
|
|
76
|
+
distinctRuns: 0,
|
|
77
|
+
corrupt: 1,
|
|
78
|
+
},
|
|
79
|
+
ids: new Set(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const ids = new Set();
|
|
83
|
+
let rows = 0;
|
|
84
|
+
let joinable = 0;
|
|
85
|
+
let corrupt = 0;
|
|
86
|
+
for (const line of text.split("\n")) {
|
|
87
|
+
const t = line.trim();
|
|
88
|
+
if (!t)
|
|
89
|
+
continue;
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(t);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
corrupt++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
rows++;
|
|
99
|
+
const c = parsed.correlationId;
|
|
100
|
+
if (typeof c === "string" && c !== "") {
|
|
101
|
+
joinable++;
|
|
102
|
+
ids.add(c);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
cov: {
|
|
107
|
+
key,
|
|
108
|
+
file,
|
|
109
|
+
absent: false,
|
|
110
|
+
rows,
|
|
111
|
+
joinable,
|
|
112
|
+
distinctRuns: ids.size,
|
|
113
|
+
corrupt,
|
|
114
|
+
},
|
|
115
|
+
ids,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function evidenceCoverage(dir = patchworkHome()) {
|
|
119
|
+
const ledgers = [];
|
|
120
|
+
const idsByKey = new Map();
|
|
121
|
+
for (const { key, file } of SPINE_LEDGERS) {
|
|
122
|
+
const { cov, ids } = scan(dir, key, file);
|
|
123
|
+
ledgers.push(cov);
|
|
124
|
+
idsByKey.set(key, ids);
|
|
125
|
+
}
|
|
126
|
+
const pairs = [];
|
|
127
|
+
const withIds = [...idsByKey.entries()].filter(([, s]) => s.size > 0);
|
|
128
|
+
for (let i = 0; i < withIds.length; i++) {
|
|
129
|
+
for (let j = i + 1; j < withIds.length; j++) {
|
|
130
|
+
const [a, sa] = withIds[i];
|
|
131
|
+
const [b, sb] = withIds[j];
|
|
132
|
+
let shared = 0;
|
|
133
|
+
for (const id of sa)
|
|
134
|
+
if (sb.has(id))
|
|
135
|
+
shared++;
|
|
136
|
+
pairs.push({ a, b, shared });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const seen = new Map();
|
|
140
|
+
for (const [, s] of idsByKey) {
|
|
141
|
+
for (const id of s)
|
|
142
|
+
seen.set(id, (seen.get(id) ?? 0) + 1);
|
|
143
|
+
}
|
|
144
|
+
let runsInMoreThanOneLedger = 0;
|
|
145
|
+
for (const [, n] of seen)
|
|
146
|
+
if (n > 1)
|
|
147
|
+
runsInMoreThanOneLedger++;
|
|
148
|
+
return { dir, ledgers, pairs, runsInMoreThanOneLedger };
|
|
149
|
+
}
|
|
150
|
+
export function formatEvidenceCoverage(c) {
|
|
151
|
+
const L = [];
|
|
152
|
+
L.push("[evidence] how much of the spine can be joined?");
|
|
153
|
+
L.push(` ${c.dir}`);
|
|
154
|
+
L.push("");
|
|
155
|
+
for (const l of c.ledgers) {
|
|
156
|
+
if (l.absent) {
|
|
157
|
+
// Absence can be the CORRECT state — no standing permission has ever been
|
|
158
|
+
// granted, so its ledger not existing is not a gap to plumb.
|
|
159
|
+
L.push(` ABSENT ${l.file}`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
// Always the denominator first: "14 joinable" reads as coverage,
|
|
163
|
+
// "14 of 170" is the fact.
|
|
164
|
+
L.push(` ${String(l.joinable).padStart(5)} of ${String(l.rows).padEnd(6)} rows carry a run id` +
|
|
165
|
+
` (${l.distinctRuns} distinct run${l.distinctRuns === 1 ? "" : "s"}) ${l.file}` +
|
|
166
|
+
(l.corrupt > 0 ? ` [${l.corrupt} unparseable]` : ""));
|
|
167
|
+
}
|
|
168
|
+
L.push("");
|
|
169
|
+
if (c.pairs.length === 0) {
|
|
170
|
+
L.push(" No two ledgers both carry run ids yet, so nothing can be joined.");
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
L.push(" Runs shared between ledgers:");
|
|
174
|
+
for (const p of c.pairs) {
|
|
175
|
+
L.push(` ${p.shared} ${p.a} ↔ ${p.b}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
L.push("");
|
|
179
|
+
L.push(` A cross-ledger reader could traverse ${c.runsInMoreThanOneLedger} run(s) today.`);
|
|
180
|
+
if (c.runsInMoreThanOneLedger === 0) {
|
|
181
|
+
L.push(" That is the number that keeps the evidence graph unbuilt — a reader now");
|
|
182
|
+
L.push(" would be a view over data that does not exist, and its shape would then");
|
|
183
|
+
L.push(" dictate the shape of the evidence rather than the other way round.");
|
|
184
|
+
}
|
|
185
|
+
L.push("");
|
|
186
|
+
return `${L.join("\n")}\n`;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=evidenceCoverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidenceCoverage.js","sourceRoot":"","sources":["../src/evidenceCoverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,iFAAiF;AACjF,MAAM,CAAC,MAAM,aAAa,GAAiD;IACzE,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,6BAA6B,EAAE;IAC9D,EAAE,GAAG,EAAE,mBAAmB,EAAE,IAAI,EAAE,yBAAyB,EAAE;IAC7D,EAAE,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,sBAAsB,EAAE;IACvD,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,mBAAmB,EAAE;IAC9C,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAChD,EAAE,GAAG,EAAE,sBAAsB,EAAE,IAAI,EAAE,mCAAmC,EAAE;CAC3E,CAAC;AA+BF,SAAS,IAAI,CAAC,GAAW,EAAE,GAAW,EAAE,IAAY;IAClD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,GAAG,EAAE;gBACH,GAAG;gBACH,IAAI;gBACJ,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,CAAC;gBACP,QAAQ,EAAE,CAAC;gBACX,YAAY,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;aACc;YAC1B,GAAG,EAAE,IAAI,GAAG,EAAU;SACvB,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;QACtE,8DAA8D;QAC9D,OAAO;YACL,GAAG,EAAE;gBACH,GAAG;gBACH,IAAI;gBACJ,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,CAAC;gBACP,QAAQ,EAAE,CAAC;gBACX,YAAY,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;aACc;YAC1B,GAAG,EAAE,IAAI,GAAG,EAAU;SACvB,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;YACV,SAAS;QACX,CAAC;QACD,IAAI,EAAE,CAAC;QACP,MAAM,CAAC,GAAI,MAAsC,CAAC,aAAa,CAAC;QAChE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YACtC,QAAQ,EAAE,CAAC;YACX,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IACD,OAAO;QACL,GAAG,EAAE;YACH,GAAG;YACH,IAAI;YACJ,MAAM,EAAE,KAAK;YACb,IAAI;YACJ,QAAQ;YACR,YAAY,EAAE,GAAG,CAAC,IAAI;YACtB,OAAO;SACiB;QAC1B,GAAG;KACJ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAG,GAAG,aAAa,EAAE;IACpD,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,aAAa,EAAE,CAAC;QAC1C,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;IACtE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAA0B,CAAC;YACpD,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAA0B,CAAC;YACpD,IAAI,MAAM,GAAG,CAAC,CAAC;YACf,KAAK,MAAM,EAAE,IAAI,EAAE;gBAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC7B,KAAK,MAAM,EAAE,IAAI,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,uBAAuB,GAAG,CAAC,CAAC;IAChC,KAAK,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,IAAI;QAAE,IAAI,CAAC,GAAG,CAAC;YAAE,uBAAuB,EAAE,CAAC;IAE/D,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,CAAmB;IACxD,MAAM,CAAC,GAAa,EAAE,CAAC;IACvB,CAAC,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;IAC1D,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACrB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YACb,0EAA0E;YAC1E,6DAA6D;YAC7D,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/B,SAAS;QACX,CAAC;QACD,iEAAiE;QACjE,2BAA2B;QAC3B,CAAC,CAAC,IAAI,CACJ,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,sBAAsB;YACtF,MAAM,CAAC,CAAC,YAAY,gBAAgB,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE;YACjF,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,CACxD,CAAC;IACJ,CAAC;IACD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,CAAC,CAAC,IAAI,CACJ,oEAAoE,CACrE,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,CAAC,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YACxB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IACD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,CAAC,CAAC,IAAI,CACJ,0CAA0C,CAAC,CAAC,uBAAuB,gBAAgB,CACpF,CAAC;IACF,IAAI,CAAC,CAAC,uBAAuB,KAAK,CAAC,EAAE,CAAC;QACpC,CAAC,CAAC,IAAI,CACJ,2EAA2E,CAC5E,CAAC;QACF,CAAC,CAAC,IAAI,CACJ,2EAA2E,CAC5E,CAAC;QACF,CAAC,CAAC,IAAI,CACJ,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACX,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAC7B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -180,6 +180,7 @@ async function downloadVsixFromOpenVsx() {
|
|
|
180
180
|
// same list is also used by the unknown-command suggester at L2570.
|
|
181
181
|
const KNOWN_SUBCOMMANDS = [
|
|
182
182
|
"init",
|
|
183
|
+
"evidence",
|
|
183
184
|
"patchwork-init",
|
|
184
185
|
"start-all",
|
|
185
186
|
"install-extension",
|
|
@@ -4000,12 +4001,32 @@ Options:
|
|
|
4000
4001
|
process.exit(0);
|
|
4001
4002
|
}
|
|
4002
4003
|
// `patchwork doctor` — run CLI-safe bridge health checks.
|
|
4003
|
-
|
|
4004
|
-
|
|
4004
|
+
// `doctor health` — the config-sanity checks.
|
|
4005
|
+
//
|
|
4006
|
+
// These lived on a SECOND top-level `argv[2] === "doctor"` block that could
|
|
4007
|
+
// never produce output: the deployment-freshness block below also matched, and
|
|
4008
|
+
// won every time. `runDoctor` had one production caller and it was unreachable,
|
|
4009
|
+
// while its tests passed because they call it directly with a mocked
|
|
4010
|
+
// `runBridgeHealthChecks` — logic proven, wiring never exercised. `--help`
|
|
4011
|
+
// printed THIS block's text, so the documented behaviour of `patchwork doctor`
|
|
4012
|
+
// described checks that did not run and omitted `--expect-running`, the flag
|
|
4013
|
+
// that does.
|
|
4014
|
+
//
|
|
4015
|
+
// Given a subcommand rather than folded into `doctor`: that verb's exit code is
|
|
4016
|
+
// load-bearing (it is run straight after a kickstart, and `patchwork doctor &&
|
|
4017
|
+
// echo deployed` is a real shape), so making a failing config check newly able
|
|
4018
|
+
// to fail it would change a contract people already depend on.
|
|
4019
|
+
if (process.argv[2] === "doctor" && process.argv[3] === "health") {
|
|
4020
|
+
const args = process.argv.slice(4);
|
|
4005
4021
|
if (args.includes("--help") || args.includes("-h")) {
|
|
4006
|
-
process.stdout.write("Usage: patchwork doctor [--workspace <path>] [--port <n>] [--json]\n\n" +
|
|
4007
|
-
"
|
|
4008
|
-
"Exits 1 if any check fails.\n"
|
|
4022
|
+
process.stdout.write("Usage: patchwork doctor health [--workspace <path>] [--port <n>] [--json]\n\n" +
|
|
4023
|
+
"Bridge config health: workspace, git binary, lock file, automation policy.\n" +
|
|
4024
|
+
"Exits 1 if any check fails.\n\n" +
|
|
4025
|
+
" --port <n> check the lock of a RUNNING bridge. Without it the lock check\n" +
|
|
4026
|
+
" looks for one belonging to THIS process, which is a CLI and\n" +
|
|
4027
|
+
" never a bridge, so it warns. That warning means 'you did not\n" +
|
|
4028
|
+
" say which bridge', not 'your bridge is broken'.\n\n" +
|
|
4029
|
+
"For 'is the running code the installed code?', use `patchwork doctor`.\n");
|
|
4009
4030
|
process.exit(0);
|
|
4010
4031
|
}
|
|
4011
4032
|
(async () => {
|
|
@@ -4125,6 +4146,48 @@ if (process.argv[2] === "shadow-scan") {
|
|
|
4125
4146
|
}
|
|
4126
4147
|
})();
|
|
4127
4148
|
}
|
|
4149
|
+
// `patchwork evidence` — how much of the evidence spine can be joined?
|
|
4150
|
+
//
|
|
4151
|
+
// CLAUDE.md tells the next session to re-measure this before scoping a
|
|
4152
|
+
// cross-ledger reader, and records that its own figures went stale within two
|
|
4153
|
+
// days. That measurement was a bespoke throwaway script every time.
|
|
4154
|
+
//
|
|
4155
|
+
// Reports DENOMINATORS. It is not the reader, and building the reader is
|
|
4156
|
+
// exactly what the spine's own rule defers until there is evidence to read.
|
|
4157
|
+
if (process.argv[2] === "evidence") {
|
|
4158
|
+
const args = process.argv.slice(3);
|
|
4159
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
4160
|
+
process.stdout.write("Usage: patchwork evidence [--dir <path>] [--json]\n\n" +
|
|
4161
|
+
"How much of the evidence spine can actually be joined: per ledger, how many\n" +
|
|
4162
|
+
"rows carry a run id (correlationId) out of how many rows, and how many runs\n" +
|
|
4163
|
+
"appear in more than one ledger.\n\n" +
|
|
4164
|
+
"Read-only. Prints COUNTS ONLY — never a row, an id or any value, because a\n" +
|
|
4165
|
+
"correlationId is a run's taskId and these ledgers hold real task titles and\n" +
|
|
4166
|
+
"third-party record ids. A measurement may leave the machine; the rows may not.\n\n" +
|
|
4167
|
+
" --dir <path> ledger directory (default: $PATCHWORK_HOME)\n" +
|
|
4168
|
+
" --json emit the raw report\n");
|
|
4169
|
+
process.exit(0);
|
|
4170
|
+
}
|
|
4171
|
+
(async () => {
|
|
4172
|
+
try {
|
|
4173
|
+
const dirIdx = args.indexOf("--dir");
|
|
4174
|
+
const dir = dirIdx !== -1 ? args[dirIdx + 1] : undefined;
|
|
4175
|
+
const { evidenceCoverage, formatEvidenceCoverage } = await import("./evidenceCoverage.js");
|
|
4176
|
+
const report = dir ? evidenceCoverage(dir) : evidenceCoverage();
|
|
4177
|
+
process.stdout.write(args.includes("--json")
|
|
4178
|
+
? `${JSON.stringify(report, null, 2)}\n`
|
|
4179
|
+
: formatEvidenceCoverage(report));
|
|
4180
|
+
// Always 0: this is a REPORT, not a gate. Zero joinable rows is a true
|
|
4181
|
+
// and expected state early on, and exiting non-zero for it would make a
|
|
4182
|
+
// correct answer look like a failure.
|
|
4183
|
+
process.exit(0);
|
|
4184
|
+
}
|
|
4185
|
+
catch (err) {
|
|
4186
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
4187
|
+
process.exit(1);
|
|
4188
|
+
}
|
|
4189
|
+
})();
|
|
4190
|
+
}
|
|
4128
4191
|
// Handle workers subcommand — read-only worker trust-dial (shadow). Replays the
|
|
4129
4192
|
// recipe run log + gate decision log through the (worker × action-class) ramp
|
|
4130
4193
|
// and prints the dial + a "ramp would vs gate did" comparison. Changes nothing.
|
|
@@ -4463,12 +4526,30 @@ if (process.argv[2] === "members") {
|
|
|
4463
4526
|
// installed? Every check in this repo verifies the repository. On 2026-08-19
|
|
4464
4527
|
// both live bridges were found running neither the privacy code nor `butler`,
|
|
4465
4528
|
// merged and wired and green throughout.
|
|
4466
|
-
if (process.argv[2] === "doctor") {
|
|
4529
|
+
if (process.argv[2] === "doctor" && process.argv[3] !== "health") {
|
|
4467
4530
|
const args = process.argv.slice(3);
|
|
4531
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
4532
|
+
// Previously this block had no --help of its own, so the OTHER doctor
|
|
4533
|
+
// answered it and described the wrong command.
|
|
4534
|
+
process.stdout.write("Usage: patchwork doctor [--expect-running [N]] [--json]\n\n" +
|
|
4535
|
+
"Is the running code the installed code? Compares each bridge lock's\n" +
|
|
4536
|
+
"startedAt against the installed build's mtime. Deliberately NOT a version\n" +
|
|
4537
|
+
"comparison: a stale process and a fresh one report the same version,\n" +
|
|
4538
|
+
"because a version marks a release and not a build.\n\n" +
|
|
4539
|
+
" --expect-running [N] require at least N live bridges (bare = 1). Absent,\n" +
|
|
4540
|
+
" ZERO bridges is HEALTHY — 'nothing is running' is\n" +
|
|
4541
|
+
" legitimate, and a check whose denominator is the\n" +
|
|
4542
|
+
" locks it found cannot see one that is absent.\n" +
|
|
4543
|
+
" --json emit the raw report\n\n" +
|
|
4544
|
+
"For workspace / git / lock-file / automation-policy checks, use\n" +
|
|
4545
|
+
"`patchwork doctor health`.\n");
|
|
4546
|
+
process.exit(0);
|
|
4547
|
+
}
|
|
4468
4548
|
(async () => {
|
|
4469
4549
|
const { readdirSync, readFileSync, statSync } = await import("node:fs");
|
|
4470
4550
|
const { join } = await import("node:path");
|
|
4471
4551
|
const os = await import("node:os");
|
|
4552
|
+
const { fileURLToPath } = await import("node:url");
|
|
4472
4553
|
const { assessDeploymentFreshness, formatFreshness } = await import("./deploymentFreshness.js");
|
|
4473
4554
|
// The installed build we are comparing against. Falls back to this
|
|
4474
4555
|
// process's own dist when the global package is absent (running from a
|
|
@@ -4476,7 +4557,13 @@ if (process.argv[2] === "doctor") {
|
|
|
4476
4557
|
let buildTimeMs;
|
|
4477
4558
|
for (const candidate of [
|
|
4478
4559
|
join(process.env.npm_config_prefix ?? "/opt/homebrew", "lib/node_modules/patchwork-os/dist/index.js"),
|
|
4479
|
-
|
|
4560
|
+
// `fileURLToPath`, NOT `.pathname`. On Windows a file URL's pathname is
|
|
4561
|
+
// `/D:/a/...` — a leading slash before the drive letter — which statSync
|
|
4562
|
+
// cannot open, so this fallback never resolved there and `patchwork
|
|
4563
|
+
// doctor` from a checkout always exited 2 with "could not locate an
|
|
4564
|
+
// installed build". Found by the dispatch test added alongside this, on
|
|
4565
|
+
// the Windows CI cells only.
|
|
4566
|
+
fileURLToPath(new URL("./index.js", import.meta.url)),
|
|
4480
4567
|
]) {
|
|
4481
4568
|
try {
|
|
4482
4569
|
buildTimeMs = statSync(candidate).mtimeMs;
|