@telora/daemon 0.21.7 → 0.21.26

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 (168) hide show
  1. package/build-info.json +14 -3
  2. package/dist/audit-hooks.d.ts +62 -3
  3. package/dist/audit-hooks.d.ts.map +1 -1
  4. package/dist/audit-hooks.js +123 -3
  5. package/dist/audit-hooks.js.map +1 -1
  6. package/dist/completion/event.d.ts +1 -0
  7. package/dist/completion/event.d.ts.map +1 -1
  8. package/dist/completion/event.js.map +1 -1
  9. package/dist/completion/review-exit-phase.d.ts +12 -0
  10. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  11. package/dist/completion/review-exit-phase.js +45 -10
  12. package/dist/completion/review-exit-phase.js.map +1 -1
  13. package/dist/completion/team-completion.d.ts.map +1 -1
  14. package/dist/completion/team-completion.js +6 -0
  15. package/dist/completion/team-completion.js.map +1 -1
  16. package/dist/completion/team-lifecycle.d.ts.map +1 -1
  17. package/dist/completion/team-lifecycle.js +2 -1
  18. package/dist/completion/team-lifecycle.js.map +1 -1
  19. package/dist/delivery-advance-block.d.ts +9 -1
  20. package/dist/delivery-advance-block.d.ts.map +1 -1
  21. package/dist/delivery-advance-block.js +37 -1
  22. package/dist/delivery-advance-block.js.map +1 -1
  23. package/dist/delivery-lifecycle.d.ts.map +1 -1
  24. package/dist/delivery-lifecycle.js +9 -0
  25. package/dist/delivery-lifecycle.js.map +1 -1
  26. package/dist/delivery-reconciler.d.ts +16 -0
  27. package/dist/delivery-reconciler.d.ts.map +1 -1
  28. package/dist/delivery-reconciler.js +12 -0
  29. package/dist/delivery-reconciler.js.map +1 -1
  30. package/dist/external-hold.d.ts +42 -0
  31. package/dist/external-hold.d.ts.map +1 -1
  32. package/dist/external-hold.js +85 -11
  33. package/dist/external-hold.js.map +1 -1
  34. package/dist/focus-engine.d.ts.map +1 -1
  35. package/dist/focus-engine.js +10 -1
  36. package/dist/focus-engine.js.map +1 -1
  37. package/dist/focus-executor.d.ts.map +1 -1
  38. package/dist/focus-executor.js +5 -0
  39. package/dist/focus-executor.js.map +1 -1
  40. package/dist/focus-lifecycle.d.ts.map +1 -1
  41. package/dist/focus-lifecycle.js +9 -11
  42. package/dist/focus-lifecycle.js.map +1 -1
  43. package/dist/focus-provisioning.d.ts.map +1 -1
  44. package/dist/focus-provisioning.js +9 -11
  45. package/dist/focus-provisioning.js.map +1 -1
  46. package/dist/focus-team-state.d.ts +23 -0
  47. package/dist/focus-team-state.d.ts.map +1 -1
  48. package/dist/focus-team-state.js +42 -0
  49. package/dist/focus-team-state.js.map +1 -1
  50. package/dist/git/merge.d.ts +24 -0
  51. package/dist/git/merge.d.ts.map +1 -1
  52. package/dist/git/merge.js +45 -5
  53. package/dist/git/merge.js.map +1 -1
  54. package/dist/git/types.d.ts +9 -0
  55. package/dist/git/types.d.ts.map +1 -1
  56. package/dist/git/types.js.map +1 -1
  57. package/dist/heartbeat.d.ts +53 -0
  58. package/dist/heartbeat.d.ts.map +1 -1
  59. package/dist/heartbeat.js +100 -7
  60. package/dist/heartbeat.js.map +1 -1
  61. package/dist/listener-auto-advance.d.ts.map +1 -1
  62. package/dist/listener-auto-advance.js +12 -2
  63. package/dist/listener-auto-advance.js.map +1 -1
  64. package/dist/nexus/escalation-bridge.d.ts +8 -0
  65. package/dist/nexus/escalation-bridge.d.ts.map +1 -1
  66. package/dist/nexus/escalation-bridge.js +8 -0
  67. package/dist/nexus/escalation-bridge.js.map +1 -1
  68. package/dist/nexus/return.d.ts +28 -2
  69. package/dist/nexus/return.d.ts.map +1 -1
  70. package/dist/nexus/return.js +53 -2
  71. package/dist/nexus/return.js.map +1 -1
  72. package/dist/output-monitor.d.ts.map +1 -1
  73. package/dist/output-monitor.js +14 -0
  74. package/dist/output-monitor.js.map +1 -1
  75. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  76. package/dist/prompt-sections/execution-sections.js +7 -0
  77. package/dist/prompt-sections/execution-sections.js.map +1 -1
  78. package/dist/prompt-sections/persona-sections.d.ts +10 -0
  79. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  80. package/dist/prompt-sections/persona-sections.js +4 -2
  81. package/dist/prompt-sections/persona-sections.js.map +1 -1
  82. package/dist/prompt-sections/scoping-sections.d.ts.map +1 -1
  83. package/dist/prompt-sections/scoping-sections.js +8 -0
  84. package/dist/prompt-sections/scoping-sections.js.map +1 -1
  85. package/dist/queries/agent-resolvable-promotion.d.ts +17 -5
  86. package/dist/queries/agent-resolvable-promotion.d.ts.map +1 -1
  87. package/dist/queries/agent-resolvable-promotion.js +24 -10
  88. package/dist/queries/agent-resolvable-promotion.js.map +1 -1
  89. package/dist/queries/daemon-connection.d.ts +21 -1
  90. package/dist/queries/daemon-connection.d.ts.map +1 -1
  91. package/dist/queries/daemon-connection.js +4 -1
  92. package/dist/queries/daemon-connection.js.map +1 -1
  93. package/dist/queries/deliveries.d.ts +44 -12
  94. package/dist/queries/deliveries.d.ts.map +1 -1
  95. package/dist/queries/deliveries.js +32 -15
  96. package/dist/queries/deliveries.js.map +1 -1
  97. package/dist/queries/delivery-gate-state.d.ts +23 -0
  98. package/dist/queries/delivery-gate-state.d.ts.map +1 -1
  99. package/dist/queries/issues.d.ts.map +1 -1
  100. package/dist/queries/issues.js +6 -0
  101. package/dist/queries/issues.js.map +1 -1
  102. package/dist/queries/requests.d.ts +30 -0
  103. package/dist/queries/requests.d.ts.map +1 -0
  104. package/dist/queries/requests.js +32 -0
  105. package/dist/queries/requests.js.map +1 -0
  106. package/dist/resolvers/shared/git-diff.d.ts +32 -3
  107. package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
  108. package/dist/resolvers/shared/git-diff.js +120 -16
  109. package/dist/resolvers/shared/git-diff.js.map +1 -1
  110. package/dist/retention-observation.d.ts +126 -0
  111. package/dist/retention-observation.d.ts.map +1 -0
  112. package/dist/retention-observation.js +143 -0
  113. package/dist/retention-observation.js.map +1 -0
  114. package/dist/spawn/prompt-assembly.d.ts +9 -0
  115. package/dist/spawn/prompt-assembly.d.ts.map +1 -1
  116. package/dist/spawn/prompt-assembly.js +2 -1
  117. package/dist/spawn/prompt-assembly.js.map +1 -1
  118. package/dist/spawn/worktree-session.d.ts.map +1 -1
  119. package/dist/spawn/worktree-session.js +13 -14
  120. package/dist/spawn/worktree-session.js.map +1 -1
  121. package/dist/stall-detector-config.d.ts +2 -0
  122. package/dist/stall-detector-config.d.ts.map +1 -1
  123. package/dist/stall-detector-config.js +4 -0
  124. package/dist/stall-detector-config.js.map +1 -1
  125. package/dist/stall-detectors.d.ts +55 -0
  126. package/dist/stall-detectors.d.ts.map +1 -1
  127. package/dist/stall-detectors.js +63 -2
  128. package/dist/stall-detectors.js.map +1 -1
  129. package/dist/state-cascade.d.ts.map +1 -1
  130. package/dist/state-cascade.js +5 -1
  131. package/dist/state-cascade.js.map +1 -1
  132. package/dist/templates/claude-md.d.ts +1 -1
  133. package/dist/templates/claude-md.d.ts.map +1 -1
  134. package/dist/templates/claude-md.js +25 -0
  135. package/dist/templates/claude-md.js.map +1 -1
  136. package/dist/types/focus.d.ts +14 -0
  137. package/dist/types/focus.d.ts.map +1 -1
  138. package/dist/unified-shell.d.ts.map +1 -1
  139. package/dist/unified-shell.js +8 -0
  140. package/dist/unified-shell.js.map +1 -1
  141. package/dist/verification-deps.d.ts +14 -0
  142. package/dist/verification-deps.d.ts.map +1 -1
  143. package/dist/verification-deterministic.d.ts.map +1 -1
  144. package/dist/verification-deterministic.js +31 -4
  145. package/dist/verification-deterministic.js.map +1 -1
  146. package/dist/verification-engine.d.ts.map +1 -1
  147. package/dist/verification-engine.js +78 -21
  148. package/dist/verification-engine.js.map +1 -1
  149. package/dist/verification-evidence.d.ts.map +1 -1
  150. package/dist/verification-evidence.js +2 -2
  151. package/dist/verification-evidence.js.map +1 -1
  152. package/dist/worktree/index.d.ts +1 -0
  153. package/dist/worktree/index.d.ts.map +1 -1
  154. package/dist/worktree/index.js +1 -0
  155. package/dist/worktree/index.js.map +1 -1
  156. package/dist/worktree/safety.d.ts +14 -6
  157. package/dist/worktree/safety.d.ts.map +1 -1
  158. package/dist/worktree/safety.js +17 -13
  159. package/dist/worktree/safety.js.map +1 -1
  160. package/dist/worktree/scratch-guard.d.ts +57 -0
  161. package/dist/worktree/scratch-guard.d.ts.map +1 -1
  162. package/dist/worktree/scratch-guard.js +168 -21
  163. package/dist/worktree/scratch-guard.js.map +1 -1
  164. package/dist/worktree/verify-interlock.d.ts +52 -0
  165. package/dist/worktree/verify-interlock.d.ts.map +1 -0
  166. package/dist/worktree/verify-interlock.js +94 -0
  167. package/dist/worktree/verify-interlock.js.map +1 -0
  168. package/package.json +3 -3
@@ -21,6 +21,7 @@
21
21
  import { loadEnvConfig } from '@telora/daemon-core';
22
22
  import { runGitSync } from '../git/index.js';
23
23
  import { assertWorktreeIdentity, defaultWorktreeIdentityViolationReporter, } from './identity.js';
24
+ import { isWorktreeUnderVerify } from './verify-interlock.js';
24
25
  import { createLogger } from '../log.js';
25
26
  const log = createLogger({ module: 'worktree' });
26
27
  /** Conservative default sweep ceiling for a single automated commit. */
@@ -48,21 +49,83 @@ export function resolveSafetyCommitMaxFiles(env) {
48
49
  function underPrefix(path, prefixes) {
49
50
  return prefixes.some((p) => path === p || path.startsWith(p + '/'));
50
51
  }
52
+ function splitLines(out) {
53
+ return out
54
+ .split('\n')
55
+ .map((p) => p.trim())
56
+ .filter((p) => p.length > 0);
57
+ }
51
58
  /**
52
- * Enumerate the repo-relative paths a `git add -A` WOULD stage: tracked
53
- * changes (incl. deletions) plus untracked, non-ignored files. Uses name-only
54
- * commands rather than parsing `git status --porcelain` (whose fixed-width
55
- * status prefix is corrupted by output trimming) -- the same technique
56
- * `dirtyChangesAreGeneratedLockfilesOnly` uses.
59
+ * Enumerate paths whose WORKTREE content differs from HEAD: tracked worktree
60
+ * changes (incl. deletions) plus untracked, non-ignored files. This is the set
61
+ * with real content behind it -- the agent work a safety commit must preserve.
62
+ * A staged-only change (a torn write) is deliberately NOT here: it has no
63
+ * worktree content, so it is not work to preserve.
64
+ *
65
+ * Uses name-only commands rather than parsing `git status --porcelain` (whose
66
+ * fixed-width status prefix is corrupted by output trimming).
57
67
  */
58
- function enumerateWouldStage(worktreePath) {
68
+ export function enumerateWorktreeDelta(worktreePath) {
59
69
  const tracked = runGitSync(['diff', '--name-only', 'HEAD'], worktreePath);
60
70
  const untracked = runGitSync(['ls-files', '--others', '--exclude-standard'], worktreePath);
61
71
  if (!tracked.success || !untracked.success)
62
72
  return null;
63
- return [...tracked.output.split('\n'), ...untracked.output.split('\n')]
64
- .map((p) => p.trim())
65
- .filter((p) => p.length > 0);
73
+ return [...new Set([...splitLines(tracked.output), ...splitLines(untracked.output)])];
74
+ }
75
+ /**
76
+ * Enumerate paths STAGED differently from HEAD (index vs HEAD). A staged path
77
+ * whose worktree copy matches HEAD is a TORN WRITE -- an index entry with no
78
+ * worktree content behind it. This is the index the detector
79
+ * (`git status --porcelain`, which consults the index) sees but the
80
+ * worktree-only enumeration above is blind to.
81
+ */
82
+ export function enumerateIndexDelta(worktreePath) {
83
+ const staged = runGitSync(['diff', '--cached', '--name-only', 'HEAD'], worktreePath);
84
+ if (!staged.success)
85
+ return null;
86
+ return splitLines(staged.output);
87
+ }
88
+ /**
89
+ * INDEX-AWARE enumeration of every path dirty vs HEAD -- in the worktree OR the
90
+ * index. This is what `git status --porcelain` shows the DETECTOR
91
+ * (worktreeHasUncommittedChanges), so the REMEDIATOR agrees with the detector
92
+ * on what "dirty" means. `worktreeHasNonScratchChanges` deliberately does NOT
93
+ * use this (it asks the narrower "is there worktree content to preserve?" --
94
+ * see its doc).
95
+ */
96
+ export function enumerateDirtyVsHead(worktreePath) {
97
+ const worktree = enumerateWorktreeDelta(worktreePath);
98
+ const index = enumerateIndexDelta(worktreePath);
99
+ if (worktree === null || index === null)
100
+ return null;
101
+ return [...new Set([...worktree, ...index])];
102
+ }
103
+ /** Enumerate + scratch-partition the worktree and index deltas. Null on git error. */
104
+ function classifyDirt(worktreePath, scratchPrefixes) {
105
+ const worktreeDelta = enumerateWorktreeDelta(worktreePath);
106
+ const indexDelta = enumerateIndexDelta(worktreePath);
107
+ if (worktreeDelta === null || indexDelta === null)
108
+ return null;
109
+ const keptWorktree = worktreeDelta.filter((p) => !underPrefix(p, scratchPrefixes));
110
+ const keptIndex = indexDelta.filter((p) => !underPrefix(p, scratchPrefixes));
111
+ const denied = [
112
+ ...new Set([
113
+ ...worktreeDelta.filter((p) => underPrefix(p, scratchPrefixes)),
114
+ ...indexDelta.filter((p) => underPrefix(p, scratchPrefixes)),
115
+ ]),
116
+ ];
117
+ return { keptWorktree, keptIndex, denied };
118
+ }
119
+ /**
120
+ * Reconcile the index to the worktree by resetting all non-scratch staged paths
121
+ * to HEAD. Index-only: writes NO history and never touches a worktree file, so
122
+ * the sweep threshold (a commit-history guard) does not apply. The scratch
123
+ * exclusion is preserved for symmetry with the commit path.
124
+ */
125
+ function reconcileIndexToWorktree(worktreePath, scratchPrefixes) {
126
+ const excludeSpecs = scratchPrefixes.map((p) => `:(exclude)${p}`);
127
+ const reset = runGitSync(['reset', '-q', 'HEAD', '--', '.', ...excludeSpecs], worktreePath);
128
+ return { success: reset.success, error: reset.success ? '' : reset.error };
66
129
  }
67
130
  /**
68
131
  * Stage the worktree's changes for a daemon-authored commit, HARD-excluding
@@ -78,34 +141,87 @@ function enumerateWouldStage(worktreePath) {
78
141
  export function stageExcludingDaemonScratch(worktreePath, opts = {}) {
79
142
  const scratchPrefixes = opts.scratchPrefixes ?? DEFAULT_SCRATCH_PREFIXES;
80
143
  const maxFiles = opts.maxFiles ?? resolveSafetyCommitMaxFiles();
81
- const wouldStage = enumerateWouldStage(worktreePath);
82
- if (wouldStage === null) {
144
+ const dirt = classifyDirt(worktreePath, scratchPrefixes);
145
+ if (dirt === null) {
83
146
  return { kind: 'error', message: 'could not enumerate worktree changes' };
84
147
  }
85
- const denied = wouldStage.filter((p) => underPrefix(p, scratchPrefixes));
86
- const kept = wouldStage.filter((p) => !underPrefix(p, scratchPrefixes));
87
- if (kept.length > maxFiles) {
148
+ const { keptWorktree, keptIndex, denied } = dirt;
149
+ // TORN WRITE disposition: staged content with NO worktree content behind it
150
+ // (the worktree matches HEAD). A safety commit exists to PRESERVE agent work;
151
+ // an index entry with nothing behind it is not agent work, so committing it is
152
+ // both impossible (git finds nothing to commit) and wrong to escalate on.
153
+ // Reconcile the index to the worktree instead -- nothing is preserved because
154
+ // nothing was ever there.
155
+ if (keptWorktree.length === 0 && keptIndex.length > 0) {
156
+ const reset = reconcileIndexToWorktree(worktreePath, scratchPrefixes);
157
+ if (!reset.success) {
158
+ return { kind: 'error', message: reset.error };
159
+ }
160
+ return { kind: 'reconciled_torn_write', reconciled: keptIndex, denied };
161
+ }
162
+ // REAL WORK disposition: the committed set is the worktree delta (what
163
+ // `git add -A` then `git commit` records). The threshold guards that set.
164
+ if (keptWorktree.length > maxFiles) {
88
165
  // Refuse: stage nothing, leave the working tree untouched.
89
166
  return {
90
167
  kind: 'refused_threshold',
91
- count: kept.length,
92
- sample: kept.slice(0, 20),
168
+ count: keptWorktree.length,
169
+ sample: keptWorktree.slice(0, 20),
93
170
  denied,
94
171
  maxFiles,
95
172
  };
96
173
  }
97
- if (kept.length === 0) {
174
+ if (keptWorktree.length === 0) {
98
175
  // Nothing to stage once scratch is excluded (a clean or scratch-only tree).
99
176
  return { kind: 'staged', staged: [], denied };
100
177
  }
101
178
  // Stage everything EXCEPT the scratch prefixes. `.` selects the full tree;
102
179
  // each `:(exclude)<prefix>` removes that dir and its contents from the set.
180
+ // `git add -A` snapshots the worktree into the index, which also reconciles
181
+ // any co-present torn-write index entry (its worktree == HEAD, so it drops out
182
+ // of the commit) -- so the MIXED case (real work + a torn write) ends clean
183
+ // with the real work committed and nothing lost.
103
184
  const excludeSpecs = scratchPrefixes.map((p) => `:(exclude)${p}`);
104
185
  const addResult = runGitSync(['add', '-A', '--', '.', ...excludeSpecs], worktreePath);
105
186
  if (!addResult.success) {
106
187
  return { kind: 'error', message: addResult.error };
107
188
  }
108
- return { kind: 'staged', staged: kept, denied };
189
+ return { kind: 'staged', staged: keptWorktree, denied };
190
+ }
191
+ /**
192
+ * If the worktree's ONLY non-scratch dirt is a TORN WRITE -- staged index
193
+ * entries with no worktree content behind them (index != HEAD, worktree ==
194
+ * HEAD) -- reconcile the index to the worktree (reset to HEAD) and return the
195
+ * reconciled paths. Returns null when there is real worktree work to preserve
196
+ * (that is guardedCommit's job, not this one) or when the tree is clean/
197
+ * scratch-only. Writes NO history and never touches a worktree file.
198
+ *
199
+ * This is the dedicated reconcile handler the advance-block path calls BEFORE it
200
+ * considers re-waking a team: a torn write has no content for a team to commit,
201
+ * so nudging the team (and eventually escalating) would be a pointless loop --
202
+ * the daemon self-clears it here. Identity-guarded exactly like guardedCommit,
203
+ * so a `.git`-less checkout is never reset against the enclosing repo.
204
+ */
205
+ export function reconcileTornWrite(worktreePath, opts = {}) {
206
+ const identity = assertWorktreeIdentity(worktreePath);
207
+ if (!identity.ok) {
208
+ // Not this directory's worktree -- leave it to the commit path's identity
209
+ // reporter; never reset against the enclosing repo.
210
+ return null;
211
+ }
212
+ const scratchPrefixes = opts.scratchPrefixes ?? DEFAULT_SCRATCH_PREFIXES;
213
+ const dirt = classifyDirt(worktreePath, scratchPrefixes);
214
+ if (dirt === null)
215
+ return null;
216
+ const { keptWorktree, keptIndex } = dirt;
217
+ if (keptWorktree.length > 0 || keptIndex.length === 0)
218
+ return null; // not a pure torn write
219
+ const reset = reconcileIndexToWorktree(worktreePath, scratchPrefixes);
220
+ if (!reset.success)
221
+ return null;
222
+ log.info(`reconcileTornWrite: reconciled ${keptIndex.length} torn-write path(s) at ${worktreePath} ` +
223
+ `(index reset to HEAD; no worktree content to preserve): ${keptIndex.slice(0, 5).join(', ')}`);
224
+ return { reconciled: keptIndex };
109
225
  }
110
226
  /**
111
227
  * True iff the worktree has uncommitted changes OUTSIDE daemon scratch. Daemon
@@ -125,10 +241,17 @@ export function worktreeHasNonScratchChanges(worktreePath, scratchPrefixes = DEF
125
241
  `to attribute the enclosing repo's changes to it.`);
126
242
  return false;
127
243
  }
128
- const wouldStage = enumerateWouldStage(worktreePath);
129
- if (wouldStage === null)
244
+ // Worktree-delta ONLY (not index-aware): this asks "is there worktree content
245
+ // to PRESERVE before we discard this worktree?", which a torn write (staged,
246
+ // no worktree content) is not -- so removeWorktree does not block on a torn
247
+ // write, it discards it with the worktree. The advance-gate detector
248
+ // (worktreeHasUncommittedChanges, via `git status --porcelain`) IS index-aware
249
+ // and drives the torn-write reconcile; these are deliberately different
250
+ // questions.
251
+ const worktreeDelta = enumerateWorktreeDelta(worktreePath);
252
+ if (worktreeDelta === null)
130
253
  return false;
131
- return wouldStage.some((p) => !underPrefix(p, scratchPrefixes));
254
+ return worktreeDelta.some((p) => !underPrefix(p, scratchPrefixes));
132
255
  }
133
256
  /**
134
257
  * Paths the daemon itself authors into a READ-ONLY audit worktree, on top of
@@ -193,6 +316,19 @@ export const defaultSweepRefusalReporter = (r) => {
193
316
  export function guardedCommit(params) {
194
317
  const { worktreePath, message, context } = params;
195
318
  const reporter = params.reporter ?? defaultSweepRefusalReporter;
319
+ // Verify interlock: while a verify command is running in this worktree, defer
320
+ // -- stage and commit NOTHING. A `git add -A` inside a verify's mutation window
321
+ // would snapshot a transient tree the verify then restores, producing a torn
322
+ // write. This is a deliberate DEFERRAL, not a failure or a no-op: the next poll
323
+ // retries once the verify completes (the skip leaves no state behind). Checked
324
+ // FIRST, before the identity gate, so no daemon-authored commit path (safety /
325
+ // WIP / lockfile) touches a worktree mid-verify.
326
+ if (isWorktreeUnderVerify(worktreePath)) {
327
+ log.info(`guardedCommit: DEFERRING commit for "${context}" at ${worktreePath} -- a verify ` +
328
+ `command is running in this worktree; staging/committing a transient mid-verify tree ` +
329
+ `would tear a write. Deferred; the next poll retries once verify completes.`);
330
+ return null;
331
+ }
196
332
  // Identity gate: refuse before staging if git would act on a different repo.
197
333
  const identity = assertWorktreeIdentity(worktreePath);
198
334
  if (!identity.ok) {
@@ -219,6 +355,17 @@ export function guardedCommit(params) {
219
355
  });
220
356
  return null;
221
357
  }
358
+ if (stage.kind === 'reconciled_torn_write') {
359
+ // A torn write: staged index entries with no worktree content behind them.
360
+ // The index was reset to HEAD (the tree is now clean). No commit was made --
361
+ // there was nothing to preserve -- so this returns null like the clean case,
362
+ // but is logged DISTINCTLY so reading the log tells you a torn write was
363
+ // reconciled rather than a commit silently doing nothing.
364
+ log.info(`guardedCommit: reconciled torn write for "${context}" at ${worktreePath} -- ` +
365
+ `${stage.reconciled.length} staged path(s) had no worktree content behind them ` +
366
+ `(index reset to HEAD, nothing to preserve, no commit): ${stage.reconciled.slice(0, 5).join(', ')}`);
367
+ return null;
368
+ }
222
369
  if (stage.staged.length === 0) {
223
370
  // Clean or scratch-only tree -- nothing non-scratch to commit.
224
371
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"scratch-guard.js","sourceRoot":"","sources":["../../src/worktree/scratch-guard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scratch-guard.js","sourceRoot":"","sources":["../../src/worktree/scratch-guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Per-worktree verify interlock.
3
+ *
4
+ * The daemon runs a product's configured deterministic verify command INSIDE the
5
+ * focus worktree, and it also commits from that same worktree (safety / WIP /
6
+ * lockfile commits, all via `guardedCommit`). A verify command that mutates a
7
+ * tracked file and restores it (the general case -- e.g. this repo's
8
+ * verify-gate.test.mjs writes a drift marker into ACTIONS.md, then restores it in
9
+ * a `finally`) has a mutation WINDOW. If a 30s-poll safety-commit's `git add -A`
10
+ * lands inside that window it snapshots the transient drift into the index; the
11
+ * verify's restore then reverts the working tree; the commit never lands --
12
+ * leaving a staged entry with no worktree content behind it (a TORN WRITE, which
13
+ * wedged a focus permanently: escalation 2170fdde, 2026-08-16).
14
+ *
15
+ * The fix belongs at the daemon, not any one product's verify script: a daemon
16
+ * that runs arbitrary configured commands in a worktree it also commits from must
17
+ * serialise the two itself. This module is that serialisation -- an in-memory,
18
+ * per-worktree hold acquired around the verify spawn and consulted by every
19
+ * daemon-authored commit path. While a worktree is held, no commit path stages or
20
+ * commits in it; the commit is DEFERRED (retried on the next poll), never lost.
21
+ *
22
+ * In-memory by design: the hold protects a LIVE process's mutation window, so it
23
+ * is only meaningful while this daemon process is running. A daemon restart kills
24
+ * any in-flight verify AND resets the map -- so a restart can never leave a stale
25
+ * hold that wedges a worktree forever (which would reproduce the very stall this
26
+ * focus removes). The map is keyed by the CANONICAL worktree path (realpath, with
27
+ * a `path.resolve` fallback) so the verify's cwd and a commit path's worktreePath
28
+ * key identically even across symlinks or a trailing slash.
29
+ *
30
+ * @module worktree/verify-interlock
31
+ */
32
+ /**
33
+ * Acquire the verify interlock for `worktreePath` and return a release token.
34
+ *
35
+ * Call the returned token exactly once when the verify process has exited (any
36
+ * exit -- clean, killed, or abnormal). The token is IDEMPOTENT: calling it more
37
+ * than once is a no-op, so a spawn that fires both `'error'` and `'close'` cannot
38
+ * under-count the hold. Acquire BEFORE the spawn and release in a path that runs
39
+ * on every exit (a `finish`/`finally`), so the hold can never outlive the process.
40
+ */
41
+ export declare function acquireVerifyHold(worktreePath: string): () => void;
42
+ /**
43
+ * True iff a verify command is currently running in `worktreePath` (the hold is
44
+ * held). Consulted by every daemon-authored commit path before it stages/commits,
45
+ * and by the advance-block gate before it decides to remediate -- so a commit that
46
+ * would snapshot a mid-verify tree is deferred, not performed. Per-worktree: a
47
+ * verify in one focus's worktree never blocks a commit in another's.
48
+ */
49
+ export declare function isWorktreeUnderVerify(worktreePath: string): boolean;
50
+ /** Test seam: clear all holds between cases. */
51
+ export declare function __resetVerifyHolds(): void;
52
+ //# sourceMappingURL=verify-interlock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-interlock.d.ts","sourceRoot":"","sources":["../../src/worktree/verify-interlock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAyBH;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,IAAI,CAgBlE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,gDAAgD;AAChD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Per-worktree verify interlock.
3
+ *
4
+ * The daemon runs a product's configured deterministic verify command INSIDE the
5
+ * focus worktree, and it also commits from that same worktree (safety / WIP /
6
+ * lockfile commits, all via `guardedCommit`). A verify command that mutates a
7
+ * tracked file and restores it (the general case -- e.g. this repo's
8
+ * verify-gate.test.mjs writes a drift marker into ACTIONS.md, then restores it in
9
+ * a `finally`) has a mutation WINDOW. If a 30s-poll safety-commit's `git add -A`
10
+ * lands inside that window it snapshots the transient drift into the index; the
11
+ * verify's restore then reverts the working tree; the commit never lands --
12
+ * leaving a staged entry with no worktree content behind it (a TORN WRITE, which
13
+ * wedged a focus permanently: escalation 2170fdde, 2026-08-16).
14
+ *
15
+ * The fix belongs at the daemon, not any one product's verify script: a daemon
16
+ * that runs arbitrary configured commands in a worktree it also commits from must
17
+ * serialise the two itself. This module is that serialisation -- an in-memory,
18
+ * per-worktree hold acquired around the verify spawn and consulted by every
19
+ * daemon-authored commit path. While a worktree is held, no commit path stages or
20
+ * commits in it; the commit is DEFERRED (retried on the next poll), never lost.
21
+ *
22
+ * In-memory by design: the hold protects a LIVE process's mutation window, so it
23
+ * is only meaningful while this daemon process is running. A daemon restart kills
24
+ * any in-flight verify AND resets the map -- so a restart can never leave a stale
25
+ * hold that wedges a worktree forever (which would reproduce the very stall this
26
+ * focus removes). The map is keyed by the CANONICAL worktree path (realpath, with
27
+ * a `path.resolve` fallback) so the verify's cwd and a commit path's worktreePath
28
+ * key identically even across symlinks or a trailing slash.
29
+ *
30
+ * @module worktree/verify-interlock
31
+ */
32
+ import { realpathSync } from 'node:fs';
33
+ import { resolve } from 'node:path';
34
+ import { createLogger } from '../log.js';
35
+ const log = createLogger({ module: 'worktree' });
36
+ /**
37
+ * Active verify holds: canonical worktree path -> count of in-flight verifies.
38
+ * A COUNT (not a boolean) so overlapping verifies on the same worktree -- should
39
+ * they ever occur -- release correctly: the hold clears only when the LAST one
40
+ * releases.
41
+ */
42
+ const holds = new Map();
43
+ /** Canonicalize a worktree path so verify cwd and commit worktreePath match. */
44
+ function keyFor(worktreePath) {
45
+ try {
46
+ return realpathSync(worktreePath);
47
+ }
48
+ catch {
49
+ return resolve(worktreePath);
50
+ }
51
+ }
52
+ /**
53
+ * Acquire the verify interlock for `worktreePath` and return a release token.
54
+ *
55
+ * Call the returned token exactly once when the verify process has exited (any
56
+ * exit -- clean, killed, or abnormal). The token is IDEMPOTENT: calling it more
57
+ * than once is a no-op, so a spawn that fires both `'error'` and `'close'` cannot
58
+ * under-count the hold. Acquire BEFORE the spawn and release in a path that runs
59
+ * on every exit (a `finish`/`finally`), so the hold can never outlive the process.
60
+ */
61
+ export function acquireVerifyHold(worktreePath) {
62
+ const key = keyFor(worktreePath);
63
+ holds.set(key, (holds.get(key) ?? 0) + 1);
64
+ log.debug(`verify-interlock: hold acquired for ${key} (depth ${holds.get(key)})`);
65
+ let released = false;
66
+ return () => {
67
+ if (released)
68
+ return;
69
+ released = true;
70
+ const next = (holds.get(key) ?? 1) - 1;
71
+ if (next <= 0) {
72
+ holds.delete(key);
73
+ }
74
+ else {
75
+ holds.set(key, next);
76
+ }
77
+ log.debug(`verify-interlock: hold released for ${key} (depth ${next <= 0 ? 0 : next})`);
78
+ };
79
+ }
80
+ /**
81
+ * True iff a verify command is currently running in `worktreePath` (the hold is
82
+ * held). Consulted by every daemon-authored commit path before it stages/commits,
83
+ * and by the advance-block gate before it decides to remediate -- so a commit that
84
+ * would snapshot a mid-verify tree is deferred, not performed. Per-worktree: a
85
+ * verify in one focus's worktree never blocks a commit in another's.
86
+ */
87
+ export function isWorktreeUnderVerify(worktreePath) {
88
+ return (holds.get(keyFor(worktreePath)) ?? 0) > 0;
89
+ }
90
+ /** Test seam: clear all holds between cases. */
91
+ export function __resetVerifyHolds() {
92
+ holds.clear();
93
+ }
94
+ //# sourceMappingURL=verify-interlock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-interlock.js","sourceRoot":"","sources":["../../src/worktree/verify-interlock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExC,gFAAgF;AAChF,SAAS,MAAM,CAAC,YAAoB;IAClC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAoB;IACpD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IACjC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,GAAG,CAAC,KAAK,CAAC,uCAAuC,GAAG,WAAW,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAClF,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,OAAO,GAAG,EAAE;QACV,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACd,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,GAAG,CAAC,KAAK,CAAC,uCAAuC,GAAG,WAAW,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;IAC1F,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,YAAoB;IACxD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,kBAAkB;IAChC,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telora/daemon",
3
- "version": "0.21.7",
3
+ "version": "0.21.26",
4
4
  "description": "Agent orchestration daemon for Telora - spawns and manages Claude Code instances",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "//testRunner": "Uses Node.js built-in test runner (node:test) via tsx for TypeScript support. The root package uses Vitest; this is a deliberate choice for the daemon package to avoid framework dependencies. See src/__tests__/ for test files.",
39
39
  "dependencies": {
40
- "@telora/daemon-core": "^0.2.75",
41
- "@telora/mcp-products": "^0.22.114",
40
+ "@telora/daemon-core": "^0.2.76",
41
+ "@telora/mcp-products": "^0.22.115",
42
42
  "commander": "^14.0.3",
43
43
  "yaml": "^2.4.0",
44
44
  "zod": "^4.3.6"