harnery 0.43.0 → 0.44.0

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 (278) hide show
  1. package/README.md +3 -3
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +41 -48
  4. package/dist/commands/browse.d.ts.map +1 -1
  5. package/dist/commands/browse.js +38 -11
  6. package/dist/commands/diagnostics.d.ts.map +1 -1
  7. package/dist/commands/diagnostics.js +10 -0
  8. package/dist/commands/doctor.d.ts +12 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +22 -7
  11. package/dist/commands/governor.js +22 -10
  12. package/dist/commands/inbox.d.ts +1 -1
  13. package/dist/commands/inbox.d.ts.map +1 -1
  14. package/dist/commands/inbox.js +55 -2
  15. package/dist/commands/rm.d.ts.map +1 -1
  16. package/dist/commands/rm.js +28 -4
  17. package/dist/commands/servers.d.ts.map +1 -1
  18. package/dist/commands/servers.js +16 -1
  19. package/dist/commands/supervisor.d.ts.map +1 -1
  20. package/dist/commands/supervisor.js +7 -2
  21. package/dist/commands/toc.d.ts +14 -0
  22. package/dist/commands/toc.d.ts.map +1 -1
  23. package/dist/commands/toc.js +7 -4
  24. package/dist/commands/workflow.js +24 -30
  25. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  26. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  27. package/dist/core/agents/live-authority-v3.js +11 -0
  28. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  29. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  30. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  31. package/dist/core/agents/mailbox.d.ts +9 -8
  32. package/dist/core/agents/mailbox.d.ts.map +1 -1
  33. package/dist/core/agents/mailbox.js +24 -17
  34. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  35. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  36. package/dist/core/agents/render/prompt-context.js +8 -12
  37. package/dist/core/agents/render/session-context.d.ts +2 -8
  38. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  39. package/dist/core/agents/render/session-context.js +7 -30
  40. package/dist/core/agents/rules/stop-hook.js +3 -3
  41. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  42. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  43. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  44. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  45. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  46. package/dist/core/agents/state/pidmap.js +5 -0
  47. package/dist/core/agents/state/proc-start.d.ts +19 -0
  48. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  49. package/dist/core/agents/state/proc-start.js +41 -0
  50. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  51. package/dist/core/agents/state/stale-sweep.js +180 -70
  52. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  53. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  54. package/dist/core/agents/state/unchanged-file.js +390 -0
  55. package/dist/core/context/index.d.ts +22 -10
  56. package/dist/core/context/index.d.ts.map +1 -1
  57. package/dist/core/context/index.js +381 -119
  58. package/dist/core/context/observation.d.ts +26 -0
  59. package/dist/core/context/observation.d.ts.map +1 -0
  60. package/dist/core/context/observation.js +11 -0
  61. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  62. package/dist/core/diagnostics/bundle.js +25 -14
  63. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  64. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  65. package/dist/core/diagnostics/path-identity.js +12 -0
  66. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  67. package/dist/core/diagnostics/sanitize.js +4 -3
  68. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  69. package/dist/core/events/v3/authority-outbox.js +29 -0
  70. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  71. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  72. package/dist/core/events/v3/generation-custody.js +50 -0
  73. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  74. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  75. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  76. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  77. package/dist/core/events/v3/producers/recorder.js +77 -64
  78. package/dist/core/governor/planning.d.ts.map +1 -1
  79. package/dist/core/governor/planning.js +14 -1
  80. package/dist/core/governor/service-read.d.ts +10 -2
  81. package/dist/core/governor/service-read.d.ts.map +1 -1
  82. package/dist/core/governor/service-read.js +105 -22
  83. package/dist/core/governor/service.d.ts +20 -2
  84. package/dist/core/governor/service.d.ts.map +1 -1
  85. package/dist/core/governor/service.js +72 -47
  86. package/dist/core/hooks/cli.js +33 -5
  87. package/dist/core/inbox/contract.d.ts +2 -1
  88. package/dist/core/inbox/contract.d.ts.map +1 -1
  89. package/dist/core/inbox/index.d.ts +2 -0
  90. package/dist/core/inbox/index.d.ts.map +1 -1
  91. package/dist/core/inbox/index.js +2 -0
  92. package/dist/core/inbox/peer-messages.d.ts +37 -0
  93. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  94. package/dist/core/inbox/peer-messages.js +72 -0
  95. package/dist/core/inbox/service.d.ts +8 -1
  96. package/dist/core/inbox/service.d.ts.map +1 -1
  97. package/dist/core/inbox/service.js +67 -21
  98. package/dist/core/inbox/targets.d.ts +22 -0
  99. package/dist/core/inbox/targets.d.ts.map +1 -0
  100. package/dist/core/inbox/targets.js +108 -0
  101. package/dist/core/inbox/watcher.js +7 -3
  102. package/dist/core/journal/index.d.ts.map +1 -1
  103. package/dist/core/journal/index.js +17 -2
  104. package/dist/core/resources/contract.d.ts +3 -0
  105. package/dist/core/resources/contract.d.ts.map +1 -1
  106. package/dist/core/resources/service-status.d.ts +14 -1
  107. package/dist/core/resources/service-status.d.ts.map +1 -1
  108. package/dist/core/resources/service-status.js +23 -13
  109. package/dist/core/resources/service.d.ts +9 -1
  110. package/dist/core/resources/service.d.ts.map +1 -1
  111. package/dist/core/resources/service.js +47 -27
  112. package/dist/core/resources/status.d.ts +2 -5
  113. package/dist/core/resources/status.d.ts.map +1 -1
  114. package/dist/core/resources/status.js +36 -7
  115. package/dist/core/servers/index.d.ts +24 -13
  116. package/dist/core/servers/index.d.ts.map +1 -1
  117. package/dist/core/servers/index.js +132 -92
  118. package/dist/core/servers/tunnels.d.ts +3 -5
  119. package/dist/core/servers/tunnels.d.ts.map +1 -1
  120. package/dist/core/servers/tunnels.js +3 -9
  121. package/dist/core/supervisor/contract.d.ts +12 -0
  122. package/dist/core/supervisor/contract.d.ts.map +1 -1
  123. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  124. package/dist/core/supervisor/explanations.js +2 -1
  125. package/dist/core/supervisor/findings.d.ts.map +1 -1
  126. package/dist/core/supervisor/findings.js +54 -9
  127. package/dist/core/supervisor/service.d.ts +9 -1
  128. package/dist/core/supervisor/service.d.ts.map +1 -1
  129. package/dist/core/supervisor/service.js +59 -29
  130. package/dist/core/supervisor/services.d.ts.map +1 -1
  131. package/dist/core/supervisor/services.js +22 -1
  132. package/dist/core/supervisor/status.d.ts +17 -2
  133. package/dist/core/supervisor/status.d.ts.map +1 -1
  134. package/dist/core/supervisor/status.js +70 -12
  135. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  136. package/dist/core/supervisor/timeline.js +2 -1
  137. package/dist/core/work/state.js +7 -7
  138. package/dist/core/workflow/engine.d.ts.map +1 -1
  139. package/dist/core/workflow/engine.js +105 -32
  140. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  141. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  142. package/dist/core/workflow/failure-evidence.js +114 -0
  143. package/dist/core/workflow/index.d.ts +1 -1
  144. package/dist/core/workflow/index.d.ts.map +1 -1
  145. package/dist/core/workflow/index.js +1 -1
  146. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  147. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  148. package/dist/core/workflow/live-session-v3.js +126 -5
  149. package/dist/core/workflow/proof.d.ts +5 -5
  150. package/dist/core/workflow/proof.d.ts.map +1 -1
  151. package/dist/core/workflow/proof.js +115 -20
  152. package/dist/core/workflow/run-state.d.ts +3 -1
  153. package/dist/core/workflow/run-state.d.ts.map +1 -1
  154. package/dist/core/workflow/run-state.js +31 -27
  155. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  156. package/dist/core/workflow/spawn-claude.js +19 -0
  157. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  158. package/dist/core/workflow/spawn-codex.js +13 -0
  159. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  160. package/dist/core/workflow/spawn-cursor.js +19 -0
  161. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  162. package/dist/core/workflow/spawn-opencode.js +14 -0
  163. package/dist/core/workflow/types.d.ts +64 -17
  164. package/dist/core/workflow/types.d.ts.map +1 -1
  165. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  166. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  167. package/dist/core/workflow/workspaces/index.js +0 -1
  168. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  169. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  170. package/dist/core/workflow/workspaces/leases.js +30 -16
  171. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  172. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  173. package/dist/core/workflow/workspaces/local-git.js +270 -6
  174. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  175. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  176. package/dist/core/workflow/workspaces/ownership.js +194 -0
  177. package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
  178. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  179. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  180. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  181. package/dist/core/workflow/workspaces/recovery.js +21 -13
  182. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  183. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  184. package/dist/core/workflow/workspaces/state.js +21 -1
  185. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  186. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  187. package/dist/lib/browser/windows-chrome.js +262 -73
  188. package/dist/lib/docs-links.d.ts.map +1 -1
  189. package/dist/lib/docs-links.js +45 -2
  190. package/dist/lib/guarded-remove.d.ts +7 -0
  191. package/dist/lib/guarded-remove.d.ts.map +1 -1
  192. package/dist/lib/guarded-remove.js +106 -12
  193. package/dist/lib/identities/assume.d.ts.map +1 -1
  194. package/dist/lib/identities/assume.js +4 -0
  195. package/dist/lib/zip/index.d.ts +41 -0
  196. package/dist/lib/zip/index.d.ts.map +1 -0
  197. package/dist/lib/zip/index.js +398 -0
  198. package/package.json +7 -1
  199. package/src/commands/agents.ts +54 -63
  200. package/src/commands/browse.ts +50 -14
  201. package/src/commands/diagnostics.ts +10 -0
  202. package/src/commands/doctor.ts +26 -7
  203. package/src/commands/governor.ts +23 -10
  204. package/src/commands/inbox.ts +52 -2
  205. package/src/commands/rm.ts +43 -4
  206. package/src/commands/servers.ts +16 -4
  207. package/src/commands/supervisor.ts +7 -2
  208. package/src/commands/toc.ts +14 -6
  209. package/src/commands/workflow.ts +30 -30
  210. package/src/core/agents/live-authority-v3.ts +17 -0
  211. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  212. package/src/core/agents/mailbox.ts +26 -17
  213. package/src/core/agents/render/prompt-context.ts +10 -12
  214. package/src/core/agents/render/session-context.ts +9 -32
  215. package/src/core/agents/rules/stop-hook.ts +3 -3
  216. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  217. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  218. package/src/core/agents/state/pidmap.ts +13 -0
  219. package/src/core/agents/state/proc-start.ts +57 -0
  220. package/src/core/agents/state/stale-sweep.ts +247 -82
  221. package/src/core/agents/state/unchanged-file.ts +456 -0
  222. package/src/core/context/index.ts +446 -136
  223. package/src/core/context/observation.ts +29 -0
  224. package/src/core/diagnostics/bundle.ts +23 -18
  225. package/src/core/diagnostics/path-identity.ts +15 -0
  226. package/src/core/diagnostics/sanitize.ts +4 -3
  227. package/src/core/events/v3/authority-outbox.ts +56 -0
  228. package/src/core/events/v3/generation-custody.ts +59 -0
  229. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  230. package/src/core/events/v3/producers/recorder.ts +94 -68
  231. package/src/core/governor/planning.ts +20 -8
  232. package/src/core/governor/service-read.ts +128 -20
  233. package/src/core/governor/service.ts +119 -42
  234. package/src/core/hooks/cli.ts +33 -5
  235. package/src/core/inbox/contract.ts +2 -1
  236. package/src/core/inbox/index.ts +2 -0
  237. package/src/core/inbox/peer-messages.ts +94 -0
  238. package/src/core/inbox/service.ts +88 -22
  239. package/src/core/inbox/targets.ts +148 -0
  240. package/src/core/inbox/watcher.ts +8 -9
  241. package/src/core/journal/index.ts +19 -2
  242. package/src/core/resources/contract.ts +4 -0
  243. package/src/core/resources/service-status.ts +35 -12
  244. package/src/core/resources/service.ts +71 -24
  245. package/src/core/resources/status.ts +57 -8
  246. package/src/core/servers/index.ts +177 -91
  247. package/src/core/servers/tunnels.ts +3 -11
  248. package/src/core/supervisor/contract.ts +13 -0
  249. package/src/core/supervisor/explanations.ts +4 -1
  250. package/src/core/supervisor/findings.ts +68 -10
  251. package/src/core/supervisor/service.ts +88 -26
  252. package/src/core/supervisor/services.ts +26 -8
  253. package/src/core/supervisor/status.ts +89 -12
  254. package/src/core/supervisor/timeline.ts +2 -1
  255. package/src/core/work/state.ts +7 -7
  256. package/src/core/workflow/engine.ts +131 -49
  257. package/src/core/workflow/failure-evidence.ts +141 -0
  258. package/src/core/workflow/index.ts +0 -1
  259. package/src/core/workflow/live-session-v3.ts +146 -5
  260. package/src/core/workflow/proof.ts +134 -26
  261. package/src/core/workflow/run-state.ts +31 -29
  262. package/src/core/workflow/spawn-claude.ts +18 -0
  263. package/src/core/workflow/spawn-codex.ts +13 -0
  264. package/src/core/workflow/spawn-cursor.ts +18 -0
  265. package/src/core/workflow/spawn-opencode.ts +14 -0
  266. package/src/core/workflow/types.ts +51 -17
  267. package/src/core/workflow/workspaces/index.ts +0 -1
  268. package/src/core/workflow/workspaces/leases.ts +48 -17
  269. package/src/core/workflow/workspaces/local-git.ts +315 -5
  270. package/src/core/workflow/workspaces/ownership.ts +232 -0
  271. package/src/core/workflow/workspaces/reclaim.ts +58 -0
  272. package/src/core/workflow/workspaces/recovery.ts +25 -13
  273. package/src/core/workflow/workspaces/state.ts +20 -0
  274. package/src/lib/browser/windows-chrome.ts +346 -86
  275. package/src/lib/docs-links.ts +49 -2
  276. package/src/lib/guarded-remove.ts +132 -14
  277. package/src/lib/identities/assume.ts +4 -0
  278. package/src/lib/zip/index.ts +483 -0
@@ -1,20 +1,43 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { lstatSync, readdirSync, readlinkSync, realpathSync, rmSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { basename, dirname, isAbsolute, parse, relative, resolve, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
5
5
  const protectedNames = new Set([".git", ".harnery", ".credentials", ".ssh", ".gnupg"]);
6
6
  const MAX_ENTRIES = 10_000;
7
+ const MAX_OWNED_ARTIFACT_ENTRIES = 100_000;
7
8
  /** Permanent removal is deliberately limited to inspected, untracked local outputs. */
8
9
  export function guardedRemove(paths, options, checkClaims = () => { }) {
9
10
  const cwd = realpathSync(process.cwd());
10
11
  const root = checkedPath(options.root, new Set(), true);
11
- const allowed = allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? []);
12
+ if (options.cleanClone) {
13
+ if (!options.recursive)
14
+ throw new Error("Clean clone removal requires --recursive");
15
+ if (options.allowProtected?.length)
16
+ throw new Error("Clean clone removal cannot be combined with --allow-protected");
17
+ if (!options.assertOwnedArtifact)
18
+ throw new Error("Clean clone removal requires one current caller-owned managed artifact");
19
+ options.assertOwnedArtifact(root);
20
+ }
21
+ const entryLimit = options.entryLimit ?? MAX_ENTRIES;
22
+ if (!Number.isSafeInteger(entryLimit) ||
23
+ entryLimit < 1 ||
24
+ entryLimit > MAX_OWNED_ARTIFACT_ENTRIES)
25
+ throw new Error(`The entry limit must be an integer from 1 to ${MAX_OWNED_ARTIFACT_ENTRIES}`);
26
+ const assertEntryLimitAuthority = () => {
27
+ if (entryLimit <= MAX_ENTRIES)
28
+ return;
29
+ if (!options.assertOwnedArtifact)
30
+ throw new Error("An entry limit above 10000 requires one current caller-owned managed artifact");
31
+ options.assertOwnedArtifact(root);
32
+ };
33
+ const allowed = allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? [], options.assertOwnedArtifact);
12
34
  const rootStat = lstatSync(root);
13
35
  const rootFingerprint = fingerprint(rootStat);
14
36
  if (!rootStat.isDirectory())
15
37
  throw new Error("--root must be a directory");
16
38
  if (root === parse(root).root || root === realpathSync(homedir()))
17
39
  throw new Error("--root cannot be a filesystem root or home directory");
40
+ assertEntryLimitAuthority();
18
41
  if (paths.length === 0 || paths.length > 100)
19
42
  throw new Error("Supply 1-100 explicit targets");
20
43
  const targets = paths.map((path) => checkedPath(path, allowed));
@@ -22,6 +45,7 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
22
45
  const removedProtected = new Set();
23
46
  const links = [];
24
47
  const gitDirs = new Set();
48
+ const cleanClones = new Set();
25
49
  let bytes = 0;
26
50
  const devices = new Set();
27
51
  for (const target of targets) {
@@ -64,6 +88,10 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
64
88
  if (stat.isDirectory() && !options.recursive)
65
89
  throw new Error(`Directory removal requires --recursive: ${target}`);
66
90
  const device = stat.dev;
91
+ if (options.cleanClone) {
92
+ assertCleanClone(target);
93
+ cleanClones.add(target);
94
+ }
67
95
  devices.add(device);
68
96
  const pending = [target];
69
97
  while (pending.length) {
@@ -72,6 +100,12 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
72
100
  if (basename(path) === ARTIFACT_RECORD && insideArtifactWorkspace(path, null))
73
101
  throw new Error(`An artifact's record is managed by the artifact commands: ${path}`);
74
102
  if (protectedNames.has(basename(path).toLowerCase())) {
103
+ if (options.cleanClone && basename(path) === ".git") {
104
+ const clone = dirname(path);
105
+ assertCleanClone(clone);
106
+ cleanClones.add(clone);
107
+ allowed.add(path);
108
+ }
75
109
  if (!allowed.has(path))
76
110
  throw new Error(protectedMessage(path));
77
111
  removedProtected.add(path);
@@ -82,16 +116,16 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
82
116
  // were already refused if they were links, in checkedPath.)
83
117
  if (path === target)
84
118
  throw new Error(`Path contains a symlink: ${path}`);
85
- if (snapshots.size >= MAX_ENTRIES)
86
- throw new Error(`Removal exceeds the ${MAX_ENTRIES}-entry limit; use the owning cleanup command`);
119
+ if (snapshots.size >= entryLimit)
120
+ throw new Error(`Removal exceeds the ${entryLimit}-entry limit; use the owning cleanup command`);
87
121
  snapshots.set(path, fingerprint(info));
88
122
  links.push({ path, target: readlinkSync(path) });
89
123
  continue;
90
124
  }
91
125
  if (info.dev !== device || (!info.isDirectory() && !info.isFile()))
92
126
  throw new Error(`Mount boundary or special file cannot be removed: ${path}`);
93
- if (snapshots.size >= MAX_ENTRIES)
94
- throw new Error(`Removal exceeds the ${MAX_ENTRIES}-entry limit; use the owning cleanup command`);
127
+ if (snapshots.size >= entryLimit)
128
+ throw new Error(`Removal exceeds the ${entryLimit}-entry limit; use the owning cleanup command`);
95
129
  snapshots.set(path, fingerprint(info));
96
130
  if (info.isFile())
97
131
  bytes += info.size;
@@ -126,6 +160,7 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
126
160
  root,
127
161
  targets,
128
162
  entries: snapshots.size,
163
+ entry_limit: entryLimit,
129
164
  bytes,
130
165
  protected: [...removedProtected].sort(),
131
166
  links,
@@ -137,12 +172,19 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
137
172
  if (checkedPath(options.root, new Set(), true) !== root ||
138
173
  fingerprint(lstatSync(root)) !== rootFingerprint)
139
174
  throw new Error("Allowed root changed during inspection");
175
+ assertEntryLimitAuthority();
176
+ if (options.cleanClone)
177
+ options.assertOwnedArtifact(root);
178
+ // Ownership may have been released or changed while the contents were inspected.
179
+ allowedProtected(options.allowProtected ?? [], root, options.liveRoots ?? [], options.assertOwnedArtifact);
140
180
  for (const target of targets) {
141
181
  checkedPath(target, allowed);
142
182
  assertUntracked(target);
143
183
  }
144
184
  for (const dir of gitDirs)
145
185
  assertNoUnpushedHistory(dir);
186
+ for (const clone of cleanClones)
187
+ assertCleanClone(clone);
146
188
  checkClaims(targets);
147
189
  for (const [path, previous] of snapshots) {
148
190
  if (fingerprint(lstatSync(path)) !== previous)
@@ -152,6 +194,44 @@ export function guardedRemove(paths, options, checkClaims = () => { }) {
152
194
  rmSync(target, { recursive: Boolean(options.recursive), force: false });
153
195
  return { ...report, applied: true };
154
196
  }
197
+ /** A disposable clone must not be the only copy of work or a registered worktree. */
198
+ function assertCleanClone(clone) {
199
+ const metadata = join(clone, ".git");
200
+ const stat = lstatSync(metadata);
201
+ if (!stat.isDirectory() || stat.isSymbolicLink())
202
+ throw new Error(`Clean clone removal requires a standalone .git directory: ${clone}`);
203
+ const git = (args) => {
204
+ const result = spawnSync("git", ["-c", "core.fsmonitor=false", "-C", clone, ...args], {
205
+ encoding: "utf8",
206
+ timeout: 10_000,
207
+ maxBuffer: 4 * 1024 * 1024,
208
+ env: {
209
+ ...process.env,
210
+ LC_ALL: "C",
211
+ GIT_DIR: undefined,
212
+ GIT_COMMON_DIR: undefined,
213
+ GIT_WORK_TREE: undefined,
214
+ GIT_INDEX_FILE: undefined,
215
+ GIT_OPTIONAL_LOCKS: "0",
216
+ },
217
+ });
218
+ if (result.error || result.status !== 0)
219
+ throw new Error(`Cannot verify clean clone ${clone}: git ${args[0]} failed`);
220
+ return result.stdout;
221
+ };
222
+ if (realpathSync(git(["rev-parse", "--show-toplevel"]).trim()) !== clone ||
223
+ realpathSync(resolve(clone, git(["rev-parse", "--git-common-dir"]).trim())) !== metadata)
224
+ throw new Error(`Clean clone removal refuses shared repository metadata: ${clone}`);
225
+ const worktrees = git(["worktree", "list", "--porcelain", "-z"])
226
+ .split("\0")
227
+ .filter((entry) => entry.startsWith("worktree "));
228
+ if (worktrees.length !== 1 || worktrees[0] !== `worktree ${clone}`)
229
+ throw new Error(`Clean clone removal refuses registered or linked worktrees: ${clone}`);
230
+ if (git(["status", "--porcelain=v1", "--untracked-files=all"]).trim())
231
+ throw new Error(`Clean clone has uncommitted or untracked work: ${clone}`);
232
+ if (git(["rev-list", "--max-count=1", "HEAD", "--all", "--reflog", "--not", "--remotes"]).trim())
233
+ throw new Error(`Clean clone has HEAD, ref or reflog commits not on any remote: ${clone}`);
234
+ }
155
235
  /** The metadata file the artifact commands keep in each artifact workspace. */
156
236
  const ARTIFACT_RECORD = ".harnery-artifact.json";
157
237
  /**
@@ -188,9 +268,9 @@ function insideAllowed(path, allowed) {
188
268
  * protected path strictly inside the root, reached without symlinks, and
189
269
  * never the Git metadata or Harnery state of a live workspace root.
190
270
  */
191
- function allowedProtected(paths, root, liveRoots) {
271
+ function allowedProtected(paths, root, liveRoots, assertOwnedArtifact) {
192
272
  const allowed = new Set();
193
- const live = liveRoots.map((dir) => resolve(dir));
273
+ const live = liveRoots.map((dir) => realpathSync(dir));
194
274
  for (const input of paths) {
195
275
  if (!input?.trim() || /[\0\r\n*?[\]$]/.test(input))
196
276
  throw new Error("Use a nonempty explicit --allow-protected path without wildcards");
@@ -213,11 +293,25 @@ function allowedProtected(paths, root, liveRoots) {
213
293
  if (!named && !(info.isDirectory() && isRepositoryMetadata(path)))
214
294
  throw new Error(`--allow-protected must name a protected path itself: ${path}`);
215
295
  for (const dir of live) {
216
- const own = [".git", ".harnery"].map((name) => resolve(dir, name));
217
- if (path === dir ||
218
- strictlyInside(path, dir) ||
219
- own.some((state) => path === state || strictlyInside(state, path)))
296
+ if (path === dir || strictlyInside(path, dir))
220
297
  throw new Error(`The live workspace's own state cannot be allowed: ${path}`);
298
+ for (const name of [".git", ".harnery"]) {
299
+ const state = resolve(dir, name);
300
+ if (path !== state && !strictlyInside(state, path))
301
+ continue;
302
+ // Only an explicitly named copied state directory inside one owned
303
+ // artifact may cross the outer live-state ancestor. Other live roots
304
+ // (including a checkout inside that artifact) still veto the path.
305
+ if (name === ".harnery" &&
306
+ basename(path) === ".harnery" &&
307
+ info.isDirectory() &&
308
+ dirname(root) === resolve(state, "artifacts") &&
309
+ assertOwnedArtifact) {
310
+ assertOwnedArtifact(root);
311
+ continue;
312
+ }
313
+ throw new Error(`The live workspace's own state cannot be allowed: ${path}`);
314
+ }
221
315
  }
222
316
  allowed.add(realpathSync(path));
223
317
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assume.d.ts","sourceRoot":"","sources":["../../../src/lib/identities/assume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,mBAAmB,CAAC;AAExB,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAC7C,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAOD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAa,EAClB,mBAAmB,GAAE,WAAW,CAAC,MAAM,CAAa,GACnD,gBAAgB,GAAG,IAAI,CA6BzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAyCT;AAuED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,oBAAoB,CAyHtB"}
1
+ {"version":3,"file":"assume.d.ts","sourceRoot":"","sources":["../../../src/lib/identities/assume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,MAAM,MAAM,uBAAuB,GAC/B,eAAe,GACf,iBAAiB,GACjB,sBAAsB,GACtB,oBAAoB,GACpB,kBAAkB,GAClB,oBAAoB,GACpB,aAAa,GACb,mBAAmB,CAAC;AAExB,qBAAa,mBAAoB,SAAQ,KAAK;aAE1B,IAAI,EAAE,uBAAuB;gBAA7B,IAAI,EAAE,uBAAuB,EAC7C,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,+DAA+D;IAC/D,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;AAOD;+EAC+E;AAC/E,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAa,EAClB,mBAAmB,GAAE,WAAW,CAAC,MAAM,CAAa,GACnD,gBAAgB,GAAG,IAAI,CA6BzB;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CA6CT;AAuED,gEAAgE;AAChE,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,oBAAoB,CAyHtB"}
@@ -85,6 +85,7 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
85
85
  let sessionId = conflict.instance_id;
86
86
  let ageSecs;
87
87
  const liveRow = readLiveCoordinationRow(coordRoot, conflict.instance_id);
88
+ let expectedGenerationId = liveRow?.v3_generation_id ?? null;
88
89
  if (liveRow) {
89
90
  adapter = adapterOf(liveRow.platform);
90
91
  sessionId = liveRow.session_id || conflict.instance_id;
@@ -99,8 +100,10 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
99
100
  // It is required to join the exact producer state when recording the sweep.
100
101
  adapter = adapterOf(hb.platform);
101
102
  sessionId = hb.session_id || conflict.instance_id;
103
+ expectedGenerationId = hb.v3_generation_id ?? null;
102
104
  }
103
105
  catch {
106
+ expectedGenerationId = null;
104
107
  /* content unreadable — still reclaim the file */
105
108
  }
106
109
  try {
@@ -116,6 +119,7 @@ export function reclaimAbandonedLocalConflict(coordRoot, conflict) {
116
119
  owner: conflict.instance_id,
117
120
  nativeSessionId: sessionId,
118
121
  adapter,
122
+ expectedGenerationId,
119
123
  observation: "stale_heartbeat",
120
124
  ageMs: Math.max(0, (ageSecs ?? 0) * 1_000),
121
125
  });
@@ -0,0 +1,41 @@
1
+ export interface ZipLimits {
2
+ archiveBytes: number;
3
+ entryBytes: number;
4
+ totalBytes: number;
5
+ entries: number;
6
+ }
7
+ export declare const DEFAULT_ZIP_LIMITS: Readonly<ZipLimits>;
8
+ export interface ZipEntryInfo {
9
+ name: string;
10
+ size: number;
11
+ compressedSize: number;
12
+ directory: boolean;
13
+ crc32: number;
14
+ }
15
+ export type ZipSource = string | Uint8Array;
16
+ export interface ReadOptions {
17
+ limits?: Partial<ZipLimits>;
18
+ }
19
+ export declare function listZip(source: ZipSource, options?: ReadOptions): ZipEntryInfo[];
20
+ export declare function readZipEntries(source: ZipSource, keep?: (name: string) => boolean, options?: ReadOptions): Map<string, Uint8Array>;
21
+ export declare function readZipEntry(source: ZipSource, name: string, options?: ReadOptions): Uint8Array | undefined;
22
+ export declare function readZipText(source: ZipSource, name: string, options?: ReadOptions): string | undefined;
23
+ /** Portable names exclude traversal, device names, streams and ambiguous aliases. */
24
+ export declare function safeEntryPath(root: string, name: string): string;
25
+ export interface ExtractOptions extends ReadOptions {
26
+ overwrite?: boolean;
27
+ filter?: (name: string) => boolean;
28
+ }
29
+ /** Preflight all selected names, collisions and existing filesystem paths before writing. */
30
+ export declare function extractZip(source: ZipSource, dest: string, options?: ExtractOptions): string[];
31
+ export interface CreateOptions extends ReadOptions {
32
+ base?: string;
33
+ level?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
34
+ overwrite?: boolean;
35
+ }
36
+ export declare function buildZip(inputs: readonly string[], options?: CreateOptions): {
37
+ bytes: Uint8Array;
38
+ entries: string[];
39
+ };
40
+ export declare function createZip(archive: string, inputs: readonly string[], options?: CreateOptions): string[];
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/zip/index.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AACD,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,SAAS,CAKjD,CAAC;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AACD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,UAAU,CAAC;AAC5C,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC7B;AA+JD,wBAAgB,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,WAAgB,GAAG,YAAY,EAAE,CAKpF;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,EAC5C,OAAO,GAAE,WAAgB,GACxB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAoBzB;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,UAAU,GAAG,SAAS,CAExB;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,WAAgB,GACxB,MAAM,GAAG,SAAS,CAGpB;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAyBhE;AAsFD,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACpC;AAED,6FAA6F;AAC7F,wBAAgB,UAAU,CACxB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,cAAmB,GAC3B,MAAM,EAAE,CAsCV;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,QAAQ,CACtB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,GAAE,aAAkB,GAC1B;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CA8C1C;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,GAAE,aAAkB,GAC1B,MAAM,EAAE,CAQV"}