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

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.
Files changed (35) hide show
  1. package/dist/claudeOrchestrator.d.ts +12 -0
  2. package/dist/claudeOrchestrator.js +154 -12
  3. package/dist/claudeOrchestrator.js.map +1 -1
  4. package/dist/deploymentFreshness.d.ts +18 -0
  5. package/dist/deploymentFreshness.js +94 -0
  6. package/dist/deploymentFreshness.js.map +1 -1
  7. package/dist/index.js +75 -102
  8. package/dist/index.js.map +1 -1
  9. package/dist/maintenance/prOutcomeLedger.d.ts +12 -1
  10. package/dist/maintenance/prOutcomeLedger.js +28 -0
  11. package/dist/maintenance/prOutcomeLedger.js.map +1 -1
  12. package/dist/maintenance/sweep.d.ts +98 -0
  13. package/dist/maintenance/sweep.js +197 -0
  14. package/dist/maintenance/sweep.js.map +1 -0
  15. package/dist/maintenance/sweepCollect.d.ts +33 -0
  16. package/dist/maintenance/sweepCollect.js +109 -0
  17. package/dist/maintenance/sweepCollect.js.map +1 -0
  18. package/dist/privacy/boundaryReceiptLog.d.ts +33 -0
  19. package/dist/privacy/boundaryReceiptLog.js +32 -0
  20. package/dist/privacy/boundaryReceiptLog.js.map +1 -1
  21. package/dist/privacy/boundaryReceipts.d.ts +8 -0
  22. package/dist/privacy/boundaryReceipts.js +8 -0
  23. package/dist/privacy/boundaryReceipts.js.map +1 -1
  24. package/dist/privacy/shadowLog.d.ts +33 -8
  25. package/dist/privacy/shadowLog.js +11 -0
  26. package/dist/privacy/shadowLog.js.map +1 -1
  27. package/dist/privacy/undeclaredSteps.d.ts +14 -0
  28. package/dist/privacy/undeclaredSteps.js +44 -0
  29. package/dist/privacy/undeclaredSteps.js.map +1 -1
  30. package/dist/recipes/agentExecutor.d.ts +8 -1
  31. package/dist/recipes/agentExecutor.js +5 -0
  32. package/dist/recipes/agentExecutor.js.map +1 -1
  33. package/dist/recipes/yamlRunner.js +11 -21
  34. package/dist/recipes/yamlRunner.js.map +1 -1
  35. package/package.json +2 -2
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));