patchwork-os 1.2.0-beta.2.canary.764 → 1.2.0-beta.2.canary.765

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.
@@ -90,3 +90,21 @@ export declare function assessDeploymentFreshness(input: FreshnessInput): Freshn
90
90
  /** mtime in ms, or undefined when the build cannot be located. */
91
91
  export declare function buildTimeFromStat(stat: Stats | undefined): number | undefined;
92
92
  export declare function formatFreshness(report: FreshnessReport, buildTimeMs: number): string;
93
+ /**
94
+ * Where the bridge lock files live. Honours `CLAUDE_CONFIG_DIR`.
95
+ *
96
+ * Extracted from the `doctor` handler so `patchwork sweep` counts bridges the
97
+ * same way `doctor` judges them. Two discoveries of "which bridges exist" would
98
+ * drift, and the drift is silent: the sweep would report a green gate over a set
99
+ * of locks the verb never looked at.
100
+ */
101
+ export declare function bridgeLockDir(): string;
102
+ /** Every parseable `.lock` in `dir`. An unparseable one is skipped, not fixed. */
103
+ export declare function discoverLocks(dir?: string): BridgeLock[];
104
+ /**
105
+ * mtime of the installed build, or undefined when none can be located.
106
+ *
107
+ * Falls back to this process's own `dist` when the global package is absent
108
+ * (running from a checkout) — the honest reference in that case.
109
+ */
110
+ export declare function installedBuildTimeMs(): number | undefined;
@@ -1,3 +1,41 @@
1
+ /**
2
+ * Is the code that is RUNNING the code we think shipped?
3
+ *
4
+ * Every gate in this repo verifies the repository: tests, lint, coverage, the
5
+ * audit scripts, even the wiring guard that exists specifically to catch
6
+ * "declared but supplied nowhere". Not one of them looks at a running process.
7
+ *
8
+ * That was invisible until it wasn't. On 2026-08-19 both live bridges were found
9
+ * to contain neither the ADR-0021 privacy code nor the `butler` command — merged,
10
+ * wired, tested, gated, and absent from every process serving requests. Three
11
+ * separate workstreams had been blocked for five days on evidence those bridges
12
+ * structurally could not produce, and every check was green throughout.
13
+ *
14
+ * ## Why not compare versions
15
+ *
16
+ * The obvious check — running version vs installed version — would NOT have
17
+ * caught it. Both were `1.2.0-beta.2`: the package version had not been bumped
18
+ * between the stale install and the fresh one, because a version is a release
19
+ * marker and not a build identity. A check that reports OK in the exact case it
20
+ * was written for is worse than no check, so this compares TIME instead.
21
+ *
22
+ * A bridge that started BEFORE the currently-installed build was written is
23
+ * running something older, whatever its version string says. That is a fact
24
+ * about processes rather than about a git tree, which is the whole point.
25
+ *
26
+ * ## What it deliberately does not claim
27
+ *
28
+ * It cannot see inside a running process. A bridge launched from a repo checkout
29
+ * (`node dist/index.js`) loads that checkout's `dist`, not the global install, so
30
+ * comparing it against the global build time answers a slightly different
31
+ * question than it appears to. Those are reported as `unknown-origin` rather than
32
+ * silently judged — the failure this file exists to prevent is a confident
33
+ * statement about code nobody actually inspected.
34
+ */
35
+ import { readdirSync, readFileSync, statSync } from "node:fs";
36
+ import os from "node:os";
37
+ import nodePath from "node:path";
38
+ import { fileURLToPath } from "node:url";
1
39
  export function assessDeploymentFreshness(input) {
2
40
  const findings = [];
3
41
  for (const lock of input.locks) {
@@ -120,4 +158,60 @@ export function formatFreshness(report, buildTimeMs) {
120
158
  }
121
159
  return L.join("\n");
122
160
  }
161
+ /**
162
+ * Where the bridge lock files live. Honours `CLAUDE_CONFIG_DIR`.
163
+ *
164
+ * Extracted from the `doctor` handler so `patchwork sweep` counts bridges the
165
+ * same way `doctor` judges them. Two discoveries of "which bridges exist" would
166
+ * drift, and the drift is silent: the sweep would report a green gate over a set
167
+ * of locks the verb never looked at.
168
+ */
169
+ export function bridgeLockDir() {
170
+ return nodePath.join(process.env.CLAUDE_CONFIG_DIR || os.homedir() + nodePath.sep + ".claude", "ide");
171
+ }
172
+ /** Every parseable `.lock` in `dir`. An unparseable one is skipped, not fixed. */
173
+ export function discoverLocks(dir = bridgeLockDir()) {
174
+ const locks = [];
175
+ try {
176
+ for (const f of readdirSync(dir)) {
177
+ if (!f.endsWith(".lock"))
178
+ continue;
179
+ try {
180
+ const d = JSON.parse(readFileSync(nodePath.join(dir, f), "utf-8"));
181
+ if (typeof d?.pid === "number")
182
+ locks.push({ ...d, file: f });
183
+ }
184
+ catch {
185
+ // An unparseable lock is not this module's problem to solve.
186
+ }
187
+ }
188
+ }
189
+ catch {
190
+ // No lock dir ⇒ no bridges ⇒ "nothing running", which is legitimate.
191
+ }
192
+ return locks;
193
+ }
194
+ /**
195
+ * mtime of the installed build, or undefined when none can be located.
196
+ *
197
+ * Falls back to this process's own `dist` when the global package is absent
198
+ * (running from a checkout) — the honest reference in that case.
199
+ */
200
+ export function installedBuildTimeMs() {
201
+ for (const candidate of [
202
+ nodePath.join(process.env.npm_config_prefix ?? "/opt/homebrew", "lib/node_modules/patchwork-os/dist/index.js"),
203
+ // `fileURLToPath`, NOT `.pathname`. On Windows a file URL's pathname is
204
+ // `/D:/a/...` — a leading slash before the drive letter — which statSync
205
+ // cannot open, so this fallback never resolved there.
206
+ fileURLToPath(new URL("./index.js", import.meta.url)),
207
+ ]) {
208
+ try {
209
+ return statSync(candidate).mtimeMs;
210
+ }
211
+ catch {
212
+ // try the next candidate
213
+ }
214
+ }
215
+ return undefined;
216
+ }
123
217
  //# sourceMappingURL=deploymentFreshness.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploymentFreshness.js","sourceRoot":"","sources":["../src/deploymentFreshness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deploymentFreshness.js","sourceRoot":"","sources":["../src/deploymentFreshness.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -179,6 +179,7 @@ async function downloadVsixFromOpenVsx() {
179
179
  const KNOWN_SUBCOMMANDS = [
180
180
  "init",
181
181
  "evidence",
182
+ "sweep",
182
183
  "patchwork-init",
183
184
  "start-all",
184
185
  "install-extension",
@@ -4189,30 +4190,13 @@ if (process.argv[2] === "pr-outcomes") {
4189
4190
  process.exit(0);
4190
4191
  }
4191
4192
  (async () => {
4192
- const { readFileSync, appendFileSync, existsSync } = await import("node:fs");
4193
+ const { appendFileSync } = await import("node:fs");
4193
4194
  const { join } = await import("node:path");
4194
4195
  const { patchworkPath } = await import("./patchworkHome.js");
4195
4196
  const { execFileSync } = await import("node:child_process");
4196
- const { toObservation, dedupeAgainst, summarise, formatLedgerSummary } = await import("./maintenance/prOutcomeLedger.js");
4197
+ const { toObservation, dedupeAgainst, readObservations, summarise, formatLedgerSummary, } = await import("./maintenance/prOutcomeLedger.js");
4197
4198
  const file = join(patchworkPath(), "pr_outcomes.jsonl");
4198
- const read = () => {
4199
- if (!existsSync(file))
4200
- return [];
4201
- const out = [];
4202
- for (const line of readFileSync(file, "utf-8").split("\n")) {
4203
- if (line.trim() === "")
4204
- continue;
4205
- try {
4206
- out.push(JSON.parse(line));
4207
- }
4208
- catch {
4209
- // A corrupt line is skipped, never repaired: rewriting an
4210
- // append-only evidence file to make it parse is how evidence
4211
- // quietly becomes fiction.
4212
- }
4213
- }
4214
- return out;
4215
- };
4199
+ const read = () => readObservations(file);
4216
4200
  const sub = args[0] ?? "show";
4217
4201
  if (sub === "show") {
4218
4202
  const rows = read();
@@ -4331,6 +4315,71 @@ if (process.argv[2] === "pr-outcomes") {
4331
4315
  process.exit(2);
4332
4316
  })();
4333
4317
  }
4318
+ if (process.argv[2] === "sweep") {
4319
+ const args = process.argv.slice(3);
4320
+ if (args.includes("--help") || args.includes("-h")) {
4321
+ process.stdout.write("Usage: patchwork sweep [--dir <path>] [--expect-running [N]] [--no-write] [--json]\n\n" +
4322
+ "What MOVED since the last sweep. Composes doctor, workers validate,\n" +
4323
+ "evidence, privacy undeclared and pr-outcomes into one reading, appends it\n" +
4324
+ "to sweep_snapshots.jsonl, and diffs it against the previous one.\n\n" +
4325
+ "Two of the readings are GATES and can fail this command: deployment\n" +
4326
+ "freshness and worker-manifest validity. Everything else is drift and is\n" +
4327
+ "reported without failing — the evidence ratios fall by construction as\n" +
4328
+ "ledgers accrue rows, and an undeclared agent step is ADR-0021's documented\n" +
4329
+ "default. Wiring those to an exit code would make sweep permanently red,\n" +
4330
+ "which is how a real warning gets ignored.\n\n" +
4331
+ "A first run is a BASELINE, never 'no changes': nothing was observed to\n" +
4332
+ "change, and there was nothing to compare against, are different facts.\n\n" +
4333
+ "The snapshot holds COUNTS ONLY — no recipe name, no path, no id — because\n" +
4334
+ "two of its inputs return operator data and a health check is the last\n" +
4335
+ "place anyone thinks to look for accumulated secrets.\n\n" +
4336
+ " --dir <path> ledger directory (default: $PATCHWORK_HOME)\n" +
4337
+ " --expect-running [N] bridges that ought to be up (default: no expectation)\n" +
4338
+ " --no-write report without appending a snapshot\n" +
4339
+ " --json emit the reading and the delta\n\n" +
4340
+ "Exits 1 only when a gate flipped healthy -> unhealthy.\n");
4341
+ process.exit(0);
4342
+ }
4343
+ (async () => {
4344
+ try {
4345
+ const { collectSweep } = await import("./maintenance/sweepCollect.js");
4346
+ const { appendSnapshot, diffReadings, formatSweep, readLastSnapshot } = await import("./maintenance/sweep.js");
4347
+ const { patchworkHome } = await import("./patchworkHome.js");
4348
+ const dirIdx = args.indexOf("--dir");
4349
+ const dir = dirIdx !== -1 && args[dirIdx + 1] ? args[dirIdx + 1] : patchworkHome();
4350
+ const expectIdx = args.indexOf("--expect-running");
4351
+ let expectRunning;
4352
+ if (expectIdx !== -1) {
4353
+ const raw = args[expectIdx + 1];
4354
+ const parsed = raw !== undefined && !raw.startsWith("--") ? Number(raw) : 1;
4355
+ if (!Number.isInteger(parsed) || parsed < 0) {
4356
+ process.stderr.write("[sweep] --expect-running takes a non-negative integer\n");
4357
+ process.exit(2);
4358
+ }
4359
+ expectRunning = parsed;
4360
+ }
4361
+ // Read the previous snapshot BEFORE appending this one, or the diff
4362
+ // compares the reading against itself and every sweep reports nothing.
4363
+ const previous = readLastSnapshot(dir);
4364
+ const reading = collectSweep({
4365
+ dir: dir,
4366
+ ...(expectRunning !== undefined ? { expectRunning } : {}),
4367
+ });
4368
+ const delta = diffReadings(previous, reading);
4369
+ const wrote = !args.includes("--no-write");
4370
+ if (wrote)
4371
+ appendSnapshot(dir, reading);
4372
+ process.stdout.write(args.includes("--json")
4373
+ ? `${JSON.stringify({ reading, delta }, null, 2)}\n`
4374
+ : formatSweep(reading, delta, { wrote }));
4375
+ process.exit(delta.regressed ? 1 : 0);
4376
+ }
4377
+ catch (err) {
4378
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
4379
+ process.exit(1);
4380
+ }
4381
+ })();
4382
+ }
4334
4383
  if (process.argv[2] === "evidence") {
4335
4384
  const args = process.argv.slice(3);
4336
4385
  if (args.includes("--help") || args.includes("-h")) {
@@ -4790,56 +4839,16 @@ if (process.argv[2] === "doctor" && process.argv[3] !== "health") {
4790
4839
  process.exit(0);
4791
4840
  }
4792
4841
  (async () => {
4793
- const { readdirSync, readFileSync, statSync } = await import("node:fs");
4794
- const { join } = await import("node:path");
4795
- const os = await import("node:os");
4796
- const { fileURLToPath } = await import("node:url");
4797
- const { assessDeploymentFreshness, formatFreshness } = await import("./deploymentFreshness.js");
4842
+ const { assessDeploymentFreshness, discoverLocks, formatFreshness, installedBuildTimeMs, } = await import("./deploymentFreshness.js");
4798
4843
  // The installed build we are comparing against. Falls back to this
4799
4844
  // process's own dist when the global package is absent (running from a
4800
4845
  // checkout), which is the honest reference in that case.
4801
- let buildTimeMs;
4802
- for (const candidate of [
4803
- join(process.env.npm_config_prefix ?? "/opt/homebrew", "lib/node_modules/patchwork-os/dist/index.js"),
4804
- // `fileURLToPath`, NOT `.pathname`. On Windows a file URL's pathname is
4805
- // `/D:/a/...` — a leading slash before the drive letter — which statSync
4806
- // cannot open, so this fallback never resolved there and `patchwork
4807
- // doctor` from a checkout always exited 2 with "could not locate an
4808
- // installed build". Found by the dispatch test added alongside this, on
4809
- // the Windows CI cells only.
4810
- fileURLToPath(new URL("./index.js", import.meta.url)),
4811
- ]) {
4812
- try {
4813
- buildTimeMs = statSync(candidate).mtimeMs;
4814
- break;
4815
- }
4816
- catch {
4817
- // try the next candidate
4818
- }
4819
- }
4846
+ const buildTimeMs = installedBuildTimeMs();
4820
4847
  if (buildTimeMs === undefined) {
4821
4848
  process.stderr.write("[deployment] could not locate an installed build to compare against\n");
4822
4849
  process.exit(2);
4823
4850
  }
4824
- const dir = join(process.env.CLAUDE_CONFIG_DIR || join(os.homedir(), ".claude"), "ide");
4825
- const locks = [];
4826
- try {
4827
- for (const f of readdirSync(dir)) {
4828
- if (!f.endsWith(".lock"))
4829
- continue;
4830
- try {
4831
- const d = JSON.parse(readFileSync(join(dir, f), "utf-8"));
4832
- if (typeof d?.pid === "number")
4833
- locks.push({ ...d, file: f });
4834
- }
4835
- catch {
4836
- // An unparseable lock is not this command's problem to solve.
4837
- }
4838
- }
4839
- }
4840
- catch {
4841
- // No lock dir ⇒ no bridges ⇒ reported as "nothing running".
4842
- }
4851
+ const locks = discoverLocks();
4843
4852
  // `--expect-running [N]` (#1481). Only the caller knows how many bridges
4844
4853
  // ought to be up — this command cannot discover it — so the expectation is
4845
4854
  // stated rather than inferred. Bare `--expect-running` means "at least one",
@@ -4998,55 +5007,19 @@ if (process.argv[2] === "privacy") {
4998
5007
  // every installed recipe, which agent steps declare no data_policy, and
4999
5008
  // what flows into them".
5000
5009
  if (args[0] === "undeclared") {
5001
- const { readdirSync, readFileSync } = await import("node:fs");
5002
- const pathMod = await import("node:path");
5003
- const { parse: parseYaml } = await import("yaml");
5004
- const { undeclaredInRecipe, formatUndeclared } = await import("./privacy/undeclaredSteps.js");
5010
+ const { scanRecipeDir, formatUndeclared } = await import("./privacy/undeclaredSteps.js");
5005
5011
  const dirIdx = args.indexOf("--dir");
5006
5012
  const dir = dirIdx !== -1 && args[dirIdx + 1]
5007
5013
  ? args[dirIdx + 1]
5008
5014
  : patchworkPath("recipes");
5009
- let entries = [];
5015
+ let report;
5010
5016
  try {
5011
- entries = readdirSync(dir);
5017
+ report = scanRecipeDir(dir);
5012
5018
  }
5013
5019
  catch {
5014
5020
  process.stderr.write(`[privacy] cannot read ${dir}\n`);
5015
5021
  process.exit(1);
5016
5022
  }
5017
- let agentSteps = 0;
5018
- let declared = 0;
5019
- const undeclared = [];
5020
- const unreadable = [];
5021
- let recipesScanned = 0;
5022
- for (const f of entries.sort()) {
5023
- if (!/\.ya?ml$/i.test(f))
5024
- continue;
5025
- recipesScanned++;
5026
- let parsed;
5027
- try {
5028
- parsed = parseYaml(readFileSync(pathMod.join(dir, f), "utf-8"));
5029
- }
5030
- catch {
5031
- unreadable.push(f);
5032
- continue;
5033
- }
5034
- const name = parsed?.name &&
5035
- typeof parsed.name === "string"
5036
- ? parsed.name
5037
- : f;
5038
- const r = undeclaredInRecipe(name, parsed);
5039
- agentSteps += r.agentSteps;
5040
- declared += r.declared;
5041
- undeclared.push(...r.steps);
5042
- }
5043
- const report = {
5044
- recipesScanned,
5045
- agentSteps,
5046
- declared,
5047
- undeclared,
5048
- unreadable,
5049
- };
5050
5023
  process.stdout.write(args.includes("--json")
5051
5024
  ? `${JSON.stringify(report, null, 2)}\n`
5052
5025
  : formatUndeclared(report));