@telora/daemon 0.20.54 → 0.20.60

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 (87) hide show
  1. package/build-info.json +6 -3
  2. package/dist/cli/session-state.d.ts +9 -0
  3. package/dist/cli/session-state.d.ts.map +1 -1
  4. package/dist/cli/session-state.js.map +1 -1
  5. package/dist/completion/review-exit-escalations.d.ts +85 -0
  6. package/dist/completion/review-exit-escalations.d.ts.map +1 -0
  7. package/dist/completion/review-exit-escalations.js +203 -0
  8. package/dist/completion/review-exit-escalations.js.map +1 -0
  9. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  10. package/dist/completion/review-exit-phase.js +43 -135
  11. package/dist/completion/review-exit-phase.js.map +1 -1
  12. package/dist/delivery-advance-block.d.ts.map +1 -1
  13. package/dist/delivery-advance-block.js +8 -1
  14. package/dist/delivery-advance-block.js.map +1 -1
  15. package/dist/external-hold.d.ts +104 -0
  16. package/dist/external-hold.d.ts.map +1 -0
  17. package/dist/external-hold.js +149 -0
  18. package/dist/external-hold.js.map +1 -0
  19. package/dist/listener-auto-advance.d.ts.map +1 -1
  20. package/dist/listener-auto-advance.js +12 -0
  21. package/dist/listener-auto-advance.js.map +1 -1
  22. package/dist/listener.d.ts.map +1 -1
  23. package/dist/listener.js +56 -11
  24. package/dist/listener.js.map +1 -1
  25. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  26. package/dist/prompt-sections/execution-sections.js +25 -23
  27. package/dist/prompt-sections/execution-sections.js.map +1 -1
  28. package/dist/queries/deliveries.d.ts +62 -0
  29. package/dist/queries/deliveries.d.ts.map +1 -1
  30. package/dist/queries/deliveries.js +1 -0
  31. package/dist/queries/deliveries.js.map +1 -1
  32. package/dist/queries/holds.d.ts +177 -0
  33. package/dist/queries/holds.d.ts.map +1 -0
  34. package/dist/queries/holds.js +232 -0
  35. package/dist/queries/holds.js.map +1 -0
  36. package/dist/queries/issues.d.ts +3 -99
  37. package/dist/queries/issues.d.ts.map +1 -1
  38. package/dist/queries/issues.js +4 -137
  39. package/dist/queries/issues.js.map +1 -1
  40. package/dist/queries/schemas.d.ts +2 -0
  41. package/dist/queries/schemas.d.ts.map +1 -1
  42. package/dist/queries/schemas.js +5 -0
  43. package/dist/queries/schemas.js.map +1 -1
  44. package/dist/resolver-dispatch.d.ts +108 -0
  45. package/dist/resolver-dispatch.d.ts.map +1 -0
  46. package/dist/resolver-dispatch.js +83 -0
  47. package/dist/resolver-dispatch.js.map +1 -0
  48. package/dist/resolver-reach.d.ts +192 -0
  49. package/dist/resolver-reach.d.ts.map +1 -0
  50. package/dist/resolver-reach.js +168 -0
  51. package/dist/resolver-reach.js.map +1 -0
  52. package/dist/resolvers/git-diff-against-base.d.ts.map +1 -1
  53. package/dist/resolvers/git-diff-against-base.js +8 -2
  54. package/dist/resolvers/git-diff-against-base.js.map +1 -1
  55. package/dist/resolvers/index.d.ts +1 -1
  56. package/dist/resolvers/index.d.ts.map +1 -1
  57. package/dist/resolvers/index.js +1 -1
  58. package/dist/resolvers/index.js.map +1 -1
  59. package/dist/resolvers/shared/git-diff.d.ts +47 -0
  60. package/dist/resolvers/shared/git-diff.d.ts.map +1 -1
  61. package/dist/resolvers/shared/git-diff.js +103 -15
  62. package/dist/resolvers/shared/git-diff.js.map +1 -1
  63. package/dist/spawn/review-role-fallback.d.ts +37 -3
  64. package/dist/spawn/review-role-fallback.d.ts.map +1 -1
  65. package/dist/spawn/review-role-fallback.js +38 -3
  66. package/dist/spawn/review-role-fallback.js.map +1 -1
  67. package/dist/stall-detectors.d.ts +33 -4
  68. package/dist/stall-detectors.d.ts.map +1 -1
  69. package/dist/stall-detectors.js +64 -5
  70. package/dist/stall-detectors.js.map +1 -1
  71. package/dist/verification-engine.d.ts +10 -0
  72. package/dist/verification-engine.d.ts.map +1 -1
  73. package/dist/verification-engine.js +142 -96
  74. package/dist/verification-engine.js.map +1 -1
  75. package/dist/verification-feedback.d.ts +44 -1
  76. package/dist/verification-feedback.d.ts.map +1 -1
  77. package/dist/verification-feedback.js +130 -1
  78. package/dist/verification-feedback.js.map +1 -1
  79. package/dist/verify-reconciliation.d.ts +25 -1
  80. package/dist/verify-reconciliation.d.ts.map +1 -1
  81. package/dist/verify-reconciliation.js +64 -2
  82. package/dist/verify-reconciliation.js.map +1 -1
  83. package/dist/verify-restatement.d.ts +22 -4
  84. package/dist/verify-restatement.d.ts.map +1 -1
  85. package/dist/verify-restatement.js +18 -1
  86. package/dist/verify-restatement.js.map +1 -1
  87. package/package.json +2 -2
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Resolver reach is BOUNDED up front, and its out-of-worktree actions are ON
3
+ * THE RECORD. The negative branch of the resolver-dispatch seam
4
+ * (resolver-dispatch.ts).
5
+ *
6
+ * The resolver only closes anything if it holds reach the sandboxed worktree
7
+ * agent lacks (spawn-sandbox.ts documents that asymmetry: a git worktree bounds
8
+ * the branch but NOT the OS -- credentials to other machines, sibling repos, the
9
+ * operator's home are all reachable). That same reach, granted without a
10
+ * declared bound, is unattended credentialed action on hosts with nothing
11
+ * reviewing what it did: a resolver's host actions produce no diff and pass no
12
+ * review gate, unlike code changes. Today's paste-into-Claude-Code workaround at
13
+ * least happens with a human watching; removing the human removes that too.
14
+ *
15
+ * Two invariants, both provable without a live host:
16
+ *
17
+ * 1. REACH IS DECLARED IN ADVANCE, PER BLOCKAGE CLASS, and FAIL-CLOSED. A
18
+ * resolver acts only within a bound declared before it runs ("this class may
19
+ * restart a container on host H"); an action outside that bound is REFUSED,
20
+ * not permitted-and-logged. An unrecognized or undeclared blockage class
21
+ * yields NO reach -- never a default to broad reach. This follows the
22
+ * spawn-sandbox.ts shape: explicit, fail-closed, declared rather than
23
+ * ambient. Reach is a property of the declared grant, never inferred from
24
+ * what an attempt happened to touch (a reach granted by outcome is not a
25
+ * bound).
26
+ *
27
+ * 2. EVERY OUT-OF-WORKTREE ACTION IS RECORDED, durably and credential-free, on
28
+ * the success, failure, AND throwing paths -- a failed unattended action is
29
+ * exactly the one that must not vanish. The record identifies the blockage,
30
+ * the action attempted, and its outcome. The sink is injectable (tests
31
+ * capture records without a live host); the default writes a structured line
32
+ * to the canonical daemon log, which is durable and human-reviewable.
33
+ *
34
+ * This module is NOT a second resolver and does not re-open the dispatch design.
35
+ * It supplies the reach `bound` that feeds `BlockageResolver.reach` and wraps a
36
+ * single host action so it is authorized against the bound and recorded --
37
+ * composing with dispatchBlockage, not rewriting it.
38
+ *
39
+ * @module resolver-reach
40
+ */
41
+ import { createLogger } from './log.js';
42
+ const log = createLogger({ module: 'resolver-reach' });
43
+ /**
44
+ * Resolve a blockage class to its declared reach bound. FAIL-CLOSED: an
45
+ * unrecognized/undeclared class -- or a declared grant that permits no kind or
46
+ * no target -- yields `reach: false` and `grant: null`. There is no path that
47
+ * defaults an unknown class to broad reach.
48
+ */
49
+ export function resolveReachBound(blockageClass, registry) {
50
+ const grant = registry.get(blockageClass) ?? null;
51
+ const reach = grant !== null &&
52
+ grant.allowedActionKinds.length > 0 &&
53
+ grant.allowedTargets.length > 0;
54
+ return { blockageClass, reach, grant: reach ? grant : null };
55
+ }
56
+ /**
57
+ * Authorize a single action against a resolved bound. REFUSES (does not
58
+ * permit-and-log) when the bound has no reach, the action serves a different
59
+ * class than the bound, or the action's kind/target is outside the declared
60
+ * grant. A refusal means the action must NOT run.
61
+ */
62
+ export function authorizeAction(bound, action) {
63
+ if (!bound.reach || bound.grant === null) {
64
+ return { authorized: false, reason: `no declared reach for blockage class "${action.blockageClass}"` };
65
+ }
66
+ if (action.blockageClass !== bound.blockageClass) {
67
+ return {
68
+ authorized: false,
69
+ reason: `action serves class "${action.blockageClass}" but bound is for "${bound.blockageClass}"`,
70
+ };
71
+ }
72
+ if (!bound.grant.allowedActionKinds.includes(action.kind)) {
73
+ return { authorized: false, reason: `action kind "${action.kind}" is outside the declared bound for "${bound.blockageClass}"` };
74
+ }
75
+ if (!bound.grant.allowedTargets.includes(action.target)) {
76
+ return { authorized: false, reason: `target "${action.target}" is outside the declared bound for "${bound.blockageClass}"` };
77
+ }
78
+ return { authorized: true };
79
+ }
80
+ /**
81
+ * The default production recorder: a structured line in the canonical daemon
82
+ * log (~/.telora/daemon.log) -- durable, rotated, and human-reviewable after
83
+ * the fact, which is precisely what a diff-less host action otherwise lacks.
84
+ * The record's `detail` is already redacted before it arrives here, so this
85
+ * sink cannot leak credentials.
86
+ */
87
+ export const logResolverAction = (record) => {
88
+ log.info('resolver action recorded', {
89
+ blockageClass: record.blockageClass,
90
+ actionKind: record.action.kind,
91
+ target: record.action.target,
92
+ summary: record.action.summary,
93
+ outcome: record.outcome,
94
+ ...(record.detail !== undefined ? { detail: record.detail } : {}),
95
+ recordedAt: record.recordedAt,
96
+ });
97
+ };
98
+ /**
99
+ * Redact credential-shaped material from free-text before it enters a record.
100
+ * A record a human reviews must never itself become a credential leak, so this
101
+ * scrubs bearer/authorization values, `key=value` pairs whose key names a
102
+ * secret, and long opaque token blobs. Conservative and lossy on purpose --
103
+ * over-redaction is safe; under-redaction is the failure.
104
+ */
105
+ export function redactResolverDetail(text) {
106
+ return text
107
+ // Authorization: Bearer <token> / bearer <token>
108
+ .replace(/\b(bearer)\s+[A-Za-z0-9._\-+/=]+/gi, '$1 [REDACTED]')
109
+ // key=value / key: value where the key names a secret
110
+ .replace(/\b(authorization|api[_-]?key|secret|password|passwd|token|access[_-]?key|private[_-]?key|client[_-]?secret)\b(\s*[:=]\s*)("?)[^\s"']+\3/gi, '$1$2[REDACTED]')
111
+ // Long opaque token blobs (>=24 chars of token alphabet) not otherwise caught.
112
+ .replace(/\b[A-Za-z0-9._\-+/=]{24,}\b/g, '[REDACTED]');
113
+ }
114
+ /**
115
+ * Run one out-of-worktree resolver action within its declared bound, recording
116
+ * every path.
117
+ *
118
+ * Guarantees, all provable without a live host:
119
+ * - The action is AUTHORIZED against the pre-declared bound FIRST. An action
120
+ * outside the bound (or under an undeclared class) is REFUSED -- `execute` is
121
+ * never called -- and the refusal is recorded.
122
+ * - On execution, `success` or `failure` is recorded from the result.
123
+ * - If `execute` THROWS, a `failure` record is written (via catch) BEFORE the
124
+ * error propagates -- the failed unattended action never vanishes.
125
+ * - The recorded `detail` is always redacted, so no credential material enters
126
+ * the record regardless of the sink.
127
+ */
128
+ export async function runReachBoundedAction(deps, action, execute) {
129
+ const record = deps.record ?? logResolverAction;
130
+ const redact = deps.redact ?? redactResolverDetail;
131
+ const clock = deps.clock ?? (() => new Date().toISOString());
132
+ const write = async (outcome, detail) => {
133
+ const entry = {
134
+ blockageClass: action.blockageClass,
135
+ action: { kind: action.kind, target: action.target, summary: action.summary },
136
+ outcome,
137
+ recordedAt: clock(),
138
+ };
139
+ if (detail !== undefined)
140
+ entry.detail = redact(detail);
141
+ await record(entry);
142
+ };
143
+ const bound = resolveReachBound(action.blockageClass, deps.registry);
144
+ const decision = authorizeAction(bound, action);
145
+ if (!decision.authorized) {
146
+ // Refused: the action is NOT executed. Record the refusal for the audit trail.
147
+ await write('refused', decision.reason);
148
+ return { outcome: 'refused', reason: decision.reason };
149
+ }
150
+ try {
151
+ const result = await execute();
152
+ if (result.ok) {
153
+ await write('success', result.detail);
154
+ return { outcome: 'success' };
155
+ }
156
+ await write('failure', result.detail);
157
+ return result.detail !== undefined
158
+ ? { outcome: 'failure', detail: redact(result.detail) }
159
+ : { outcome: 'failure' };
160
+ }
161
+ catch (err) {
162
+ // A throwing action is exactly the one whose record must not vanish.
163
+ const message = err instanceof Error ? err.message : String(err);
164
+ await write('failure', message);
165
+ throw err;
166
+ }
167
+ }
168
+ //# sourceMappingURL=resolver-reach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver-reach.js","sourceRoot":"","sources":["../src/resolver-reach.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff-against-base.d.ts","sourceRoot":"","sources":["../../src/resolvers/git-diff-against-base.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,oGAAoG;AACpG,wBAAgB,kCAAkC,IAAI,IAAI,CAqCzD"}
1
+ {"version":3,"file":"git-diff-against-base.d.ts","sourceRoot":"","sources":["../../src/resolvers/git-diff-against-base.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,oGAAoG;AACpG,wBAAgB,kCAAkC,IAAI,IAAI,CA2CzD"}
@@ -1,5 +1,5 @@
1
1
  import { registerSourceResolver } from '../assembly-engine.js';
2
- import { getGitDiff, getDiffStatSummary, MAX_DIFF_BYTES } from './shared/git-diff.js';
2
+ import { getGitDiff, getDeliveryDiff, getDiffStatSummary, MAX_DIFF_BYTES } from './shared/git-diff.js';
3
3
  import { createLogger } from '../log.js';
4
4
  const log = createLogger({ module: 'assembly-engine' });
5
5
  /**
@@ -19,7 +19,13 @@ export function registerGitDiffAgainstBaseResolver() {
19
19
  // (reviewed_through_sha) so a resumed reviewer sees only watermark..tip;
20
20
  // every other caller diffs against the integration branch.
21
21
  const baseBranch = context.diffBaseOverride ?? context.config.integrationBranch;
22
- const diff = getGitDiff(context.worktreePath, baseBranch);
22
+ // For the watermark-override case the range is always ahead by construction,
23
+ // so a plain base...HEAD diff is correct. For the default integration base,
24
+ // use the delivery-diff resolver so a focus branch that has ALREADY merged
25
+ // (level with integration) still yields its work rather than an empty diff.
26
+ const diff = context.diffBaseOverride
27
+ ? getGitDiff(context.worktreePath, baseBranch)
28
+ : getDeliveryDiff(context.worktreePath, baseBranch);
23
29
  if (!diff.trim())
24
30
  return '';
25
31
  if (diff.length > MAX_DIFF_BYTES) {
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff-against-base.js","sourceRoot":"","sources":["../../src/resolvers/git-diff-against-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;AAGxD;;;;;GAKG;AACH,oGAAoG;AACpG,MAAM,UAAU,kCAAkC;IAChD,sBAAsB,CAAC,uBAAuB,EAAE,KAAK,EAAE,OAAwB,EAAmB,EAAE;QAClG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YAC9D,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,wEAAwE;QACxE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC;QAChF,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAE1D,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAE5B,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAClE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAChD,OAAO;gBACL,wBAAwB,UAAU,GAAG;gBACrC,EAAE;gBACF,8BAA8B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,KAAK;gBACvH,EAAE;gBACF,kBAAkB;gBAClB,EAAE;gBACF,KAAK;gBACL,IAAI,CAAC,IAAI,EAAE;gBACX,KAAK;gBACL,EAAE;gBACF,SAAS;gBACT,SAAS;gBACT,KAAK;aACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;QAED,OAAO,wBAAwB,UAAU,oBAAoB,IAAI,UAAU,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"git-diff-against-base.js","sourceRoot":"","sources":["../../src/resolvers/git-diff-against-base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;AAGxD;;;;;GAKG;AACH,oGAAoG;AACpG,MAAM,UAAU,kCAAkC;IAChD,sBAAsB,CAAC,uBAAuB,EAAE,KAAK,EAAE,OAAwB,EAAmB,EAAE;QAClG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;YAC9D,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,wEAAwE;QACxE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC;QAChF,6EAA6E;QAC7E,4EAA4E;QAC5E,2EAA2E;QAC3E,4EAA4E;QAC5E,MAAM,IAAI,GAAG,OAAO,CAAC,gBAAgB;YACnC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC;YAC9C,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAEtD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,EAAE,CAAC;QAE5B,IAAI,IAAI,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAClE,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;YAChD,OAAO;gBACL,wBAAwB,UAAU,GAAG;gBACrC,EAAE;gBACF,8BAA8B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,qBAAqB,IAAI,CAAC,KAAK,CAAC,cAAc,GAAG,IAAI,CAAC,KAAK;gBACvH,EAAE;gBACF,kBAAkB;gBAClB,EAAE;gBACF,KAAK;gBACL,IAAI,CAAC,IAAI,EAAE;gBACX,KAAK;gBACL,EAAE;gBACF,SAAS;gBACT,SAAS;gBACT,KAAK;aACN,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;QAED,OAAO,wBAAwB,UAAU,oBAAoB,IAAI,UAAU,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -28,7 +28,7 @@
28
28
  * (FocusEngine.init) can list them in the startup log.
29
29
  */
30
30
  export declare function initResolvers(): string[];
31
- export { MAX_DIFF_BYTES, getGitDiff, getDiffStatSummary, getChangedFiles, getCommitsAhead } from './shared/git-diff.js';
31
+ export { MAX_DIFF_BYTES, getGitDiff, getDiffStatSummary, getChangedFiles, getCommitsAhead, getGitDiffRange, getDiffStatRange, getChangedFilesRange, findDeliveryMergeCommit, resolveDeliveryDiffRange, getDeliveryDiff, } from './shared/git-diff.js';
32
32
  export { CRT_DUMP_BUDGET_CHARS, renderRealityTreeDump, FOCUS_REALITY_TREE_EMPTY_STATE, } from './shared/reality-tree.js';
33
33
  export type { RealityTreeRow, RealityTreeListResponse, RealityNodeRow, RealityEdgeRow, NodeListResponse, EdgeListResponse, } from './shared/reality-tree.js';
34
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAyFH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAgBxC;AAGD,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACxH,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,8BAA8B,GAC/B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAyFH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAgBxC;AAGD,OAAO,EACL,cAAc,EACd,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,8BAA8B,GAC/B,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,cAAc,EACd,uBAAuB,EACvB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC"}
@@ -125,6 +125,6 @@ export function initResolvers() {
125
125
  return keys;
126
126
  }
127
127
  // ── Shared public surface (re-exports for external consumers) ─────────
128
- export { MAX_DIFF_BYTES, getGitDiff, getDiffStatSummary, getChangedFiles, getCommitsAhead } from './shared/git-diff.js';
128
+ export { MAX_DIFF_BYTES, getGitDiff, getDiffStatSummary, getChangedFiles, getCommitsAhead, getGitDiffRange, getDiffStatRange, getChangedFilesRange, findDeliveryMergeCommit, resolveDeliveryDiffRange, getDeliveryDiff, } from './shared/git-diff.js';
129
129
  export { CRT_DUMP_BUDGET_CHARS, renderRealityTreeDump, FOCUS_REALITY_TREE_EMPTY_STATE, } from './shared/reality-tree.js';
130
130
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resolvers/index.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,16 @@
2
2
  * Git diff helpers shared across resolvers.
3
3
  */
4
4
  export declare const MAX_DIFF_BYTES: number;
5
+ /**
6
+ * Run `git diff <range>` in a worktree. Returns the raw diff output, or empty
7
+ * string on failure. `range` is any ref expression git diff accepts, e.g.
8
+ * `integration...HEAD` or `<merge>^1...<merge>`.
9
+ */
10
+ export declare function getGitDiffRange(worktreePath: string, range: string): string;
11
+ /** `git diff --stat <range>` (for the truncation header). Empty on failure. */
12
+ export declare function getDiffStatRange(worktreePath: string, range: string): string;
13
+ /** `git diff --name-only <range>` (name-only). Empty array on failure. */
14
+ export declare function getChangedFilesRange(worktreePath: string, range: string): string[];
5
15
  /**
6
16
  * Run git diff in a worktree against the base branch.
7
17
  * Returns the raw diff output, or empty string on failure.
@@ -24,4 +34,41 @@ export declare function getChangedFiles(worktreePath: string, baseBranch: string
24
34
  * worktree. Returns 0 on failure so callers treat "unknown" as "no commits".
25
35
  */
26
36
  export declare function getCommitsAhead(worktreePath: string, baseBranch: string): number;
37
+ /**
38
+ * Find the merge commit on `baseBranch` that merged the current focus branch
39
+ * (the worktree's HEAD) into it.
40
+ *
41
+ * `git diff base...HEAD` answers "what is left to merge"; it empties the moment
42
+ * the focus branch merges into the base. But when the base advanced past the
43
+ * fork point, the merge is a real merge commit whose non-first parent IS the
44
+ * focus tip -- so `git diff <merge>^1 <merge>` still recovers exactly the
45
+ * delivery's contribution. This locates that merge commit deterministically
46
+ * from what is in the repo (no session ids, no reflog): scan the base branch's
47
+ * merge commits for one whose second (or later) parent equals HEAD.
48
+ *
49
+ * Returns null when no such merge exists -- an unmerged branch, or a merge that
50
+ * FAST-FORWARDED (no merge commit, so the fork point leaves no in-tree marker).
51
+ * The caller then yields an empty diff, which the instrument-fault path handles.
52
+ */
53
+ export declare function findDeliveryMergeCommit(worktreePath: string, baseBranch: string): string | null;
54
+ /**
55
+ * Resolve the git range that contains a delivery's work, robust to the focus
56
+ * branch having already merged into its base:
57
+ *
58
+ * - branch AHEAD of base -> `base...HEAD` (historic behaviour,
59
+ * byte-identical to the old getGitDiff)
60
+ * - branch LEVEL/BEHIND after merge -> `<merge>^1...<merge>` (recovered from
61
+ * the merge commit that brought it in)
62
+ * - no attributable commits -> null (caller yields empty; the
63
+ * instrument-fault path handles it)
64
+ */
65
+ export declare function resolveDeliveryDiffRange(worktreePath: string, baseBranch: string): string | null;
66
+ /**
67
+ * The delivery's diff, resolved from the range that actually contains its work
68
+ * (see resolveDeliveryDiffRange). For a branch still ahead of base this is
69
+ * byte-identical to `getGitDiff(worktreePath, baseBranch)`; for a merged branch
70
+ * it recovers the delivery's contribution from the merge commit; for a delivery
71
+ * with no attributable commits it returns ''.
72
+ */
73
+ export declare function getDeliveryDiff(worktreePath: string, baseBranch: string): string;
27
74
  //# sourceMappingURL=git-diff.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc,QAAY,CAAC;AAExC;;;GAGG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAU3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CASnF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAWhF"}
1
+ {"version":3,"file":"git-diff.d.ts","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,eAAO,MAAM,cAAc,QAAY,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU3E;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS5E;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAUlF;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAElF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAWhF;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsB/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKhG;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGhF"}
@@ -4,42 +4,58 @@
4
4
  import { execSync } from 'node:child_process';
5
5
  export const MAX_DIFF_BYTES = 50 * 1024; // 50 KB truncation limit for git diffs
6
6
  /**
7
- * Run git diff in a worktree against the base branch.
8
- * Returns the raw diff output, or empty string on failure.
7
+ * Run `git diff <range>` in a worktree. Returns the raw diff output, or empty
8
+ * string on failure. `range` is any ref expression git diff accepts, e.g.
9
+ * `integration...HEAD` or `<merge>^1...<merge>`.
9
10
  */
10
- export function getGitDiff(worktreePath, baseBranch) {
11
+ export function getGitDiffRange(worktreePath, range) {
11
12
  try {
12
- const diff = execSync(`git diff ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 10 * 1024 * 1024, encoding: 'utf-8' });
13
+ const diff = execSync(`git diff ${range}`, { cwd: worktreePath, maxBuffer: 10 * 1024 * 1024, encoding: 'utf-8' });
13
14
  return diff;
14
15
  }
15
16
  catch {
16
17
  return '';
17
18
  }
18
19
  }
19
- /**
20
- * Get a summary of files changed in a diff (for truncation header).
21
- */
22
- export function getDiffStatSummary(worktreePath, baseBranch) {
20
+ /** `git diff --stat <range>` (for the truncation header). Empty on failure. */
21
+ export function getDiffStatRange(worktreePath, range) {
23
22
  try {
24
- return execSync(`git diff --stat ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
23
+ return execSync(`git diff --stat ${range}`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
25
24
  }
26
25
  catch {
27
26
  return '';
28
27
  }
29
28
  }
30
- /**
31
- * List the files changed between the base branch and HEAD (name-only).
32
- * Returns an empty array on failure (missing worktree / bad range).
33
- */
34
- export function getChangedFiles(worktreePath, baseBranch) {
29
+ /** `git diff --name-only <range>` (name-only). Empty array on failure. */
30
+ export function getChangedFilesRange(worktreePath, range) {
35
31
  try {
36
- const out = execSync(`git diff --name-only ${baseBranch}...HEAD`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
32
+ const out = execSync(`git diff --name-only ${range}`, { cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8' });
37
33
  return out.split('\n').map((l) => l.trim()).filter((l) => l !== '');
38
34
  }
39
35
  catch {
40
36
  return [];
41
37
  }
42
38
  }
39
+ /**
40
+ * Run git diff in a worktree against the base branch.
41
+ * Returns the raw diff output, or empty string on failure.
42
+ */
43
+ export function getGitDiff(worktreePath, baseBranch) {
44
+ return getGitDiffRange(worktreePath, `${baseBranch}...HEAD`);
45
+ }
46
+ /**
47
+ * Get a summary of files changed in a diff (for truncation header).
48
+ */
49
+ export function getDiffStatSummary(worktreePath, baseBranch) {
50
+ return getDiffStatRange(worktreePath, `${baseBranch}...HEAD`);
51
+ }
52
+ /**
53
+ * List the files changed between the base branch and HEAD (name-only).
54
+ * Returns an empty array on failure (missing worktree / bad range).
55
+ */
56
+ export function getChangedFiles(worktreePath, baseBranch) {
57
+ return getChangedFilesRange(worktreePath, `${baseBranch}...HEAD`);
58
+ }
43
59
  /**
44
60
  * Count commits on HEAD that are not reachable from the base branch
45
61
  * (`git rev-list --count base..HEAD`). This is the delivery's own commit
@@ -57,4 +73,76 @@ export function getCommitsAhead(worktreePath, baseBranch) {
57
73
  return 0;
58
74
  }
59
75
  }
76
+ /** How many merge commits back to scan on the base branch for a delivery merge. */
77
+ const MERGE_SCAN_LIMIT = 2000;
78
+ /**
79
+ * Find the merge commit on `baseBranch` that merged the current focus branch
80
+ * (the worktree's HEAD) into it.
81
+ *
82
+ * `git diff base...HEAD` answers "what is left to merge"; it empties the moment
83
+ * the focus branch merges into the base. But when the base advanced past the
84
+ * fork point, the merge is a real merge commit whose non-first parent IS the
85
+ * focus tip -- so `git diff <merge>^1 <merge>` still recovers exactly the
86
+ * delivery's contribution. This locates that merge commit deterministically
87
+ * from what is in the repo (no session ids, no reflog): scan the base branch's
88
+ * merge commits for one whose second (or later) parent equals HEAD.
89
+ *
90
+ * Returns null when no such merge exists -- an unmerged branch, or a merge that
91
+ * FAST-FORWARDED (no merge commit, so the fork point leaves no in-tree marker).
92
+ * The caller then yields an empty diff, which the instrument-fault path handles.
93
+ */
94
+ export function findDeliveryMergeCommit(worktreePath, baseBranch) {
95
+ try {
96
+ const head = execSync('git rev-parse HEAD', {
97
+ cwd: worktreePath, maxBuffer: 1024 * 1024, encoding: 'utf-8',
98
+ }).trim();
99
+ if (head === '')
100
+ return null;
101
+ const out = execSync(`git rev-list --merges --parents -n ${MERGE_SCAN_LIMIT} ${baseBranch}`, { cwd: worktreePath, maxBuffer: 10 * 1024 * 1024, encoding: 'utf-8' });
102
+ for (const line of out.split('\n')) {
103
+ const parts = line.trim().split(/\s+/).filter((p) => p !== '');
104
+ // Format: <merge> <parent1> <parent2> [...]. A merge has >= 2 parents.
105
+ if (parts.length < 3)
106
+ continue;
107
+ const merge = parts[0];
108
+ const nonFirstParents = parts.slice(2); // second+ parents = the merged-in tips
109
+ if (nonFirstParents.includes(head))
110
+ return merge ?? null;
111
+ }
112
+ return null;
113
+ }
114
+ catch {
115
+ return null;
116
+ }
117
+ }
118
+ /**
119
+ * Resolve the git range that contains a delivery's work, robust to the focus
120
+ * branch having already merged into its base:
121
+ *
122
+ * - branch AHEAD of base -> `base...HEAD` (historic behaviour,
123
+ * byte-identical to the old getGitDiff)
124
+ * - branch LEVEL/BEHIND after merge -> `<merge>^1...<merge>` (recovered from
125
+ * the merge commit that brought it in)
126
+ * - no attributable commits -> null (caller yields empty; the
127
+ * instrument-fault path handles it)
128
+ */
129
+ export function resolveDeliveryDiffRange(worktreePath, baseBranch) {
130
+ if (getCommitsAhead(worktreePath, baseBranch) > 0)
131
+ return `${baseBranch}...HEAD`;
132
+ const merge = findDeliveryMergeCommit(worktreePath, baseBranch);
133
+ if (merge)
134
+ return `${merge}^1...${merge}`;
135
+ return null;
136
+ }
137
+ /**
138
+ * The delivery's diff, resolved from the range that actually contains its work
139
+ * (see resolveDeliveryDiffRange). For a branch still ahead of base this is
140
+ * byte-identical to `getGitDiff(worktreePath, baseBranch)`; for a merged branch
141
+ * it recovers the delivery's contribution from the merge commit; for a delivery
142
+ * with no attributable commits it returns ''.
143
+ */
144
+ export function getDeliveryDiff(worktreePath, baseBranch) {
145
+ const range = resolveDeliveryDiffRange(worktreePath, baseBranch);
146
+ return range ? getGitDiffRange(worktreePath, range) : '';
147
+ }
60
148
  //# sourceMappingURL=git-diff.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,uCAAuC;AAEhF;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,UAAkB;IACjE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CACnB,YAAY,UAAU,SAAS,EAC/B,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACtE,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,YAAoB,EAAE,UAAkB;IACzE,IAAI,CAAC;QACH,OAAO,QAAQ,CACb,mBAAmB,UAAU,SAAS,EACtC,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,UAAkB;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAClB,wBAAwB,UAAU,SAAS,EAC3C,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;QACF,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,YAAoB,EAAE,UAAkB;IACtE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAClB,wBAAwB,UAAU,QAAQ,EAC1C,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CACjE,CAAC;QACF,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"git-diff.js","sourceRoot":"","sources":["../../../src/resolvers/shared/git-diff.ts"],"names":[],"mappings":"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"}
@@ -61,14 +61,48 @@ export declare function resolveReviewerFallbackRole(roles: Iterable<AgentRole>):
61
61
  /**
62
62
  * Decide what to do with a null-role pending spawn directive.
63
63
  *
64
- * The layered fallback applies ONLY to review directives; a coding directive
65
- * with a null role is the intended off-state and is always skipped. A review
66
- * directive spawns under the fallback role when one resolves, else clears.
64
+ * A null assigned_agent_role_id now means one of TWO things, and they must be
65
+ * told apart:
66
+ *
67
+ * - An OPERATOR STOP (product_focuses.operator_stopped_at is set): the human
68
+ * pulled the Play toggle to halt the focus. This is authoritative and wins
69
+ * over everything -- when `operatorStopped` is true we `skip` for EVERY
70
+ * sessionType, review included. The stop lever must never become the trigger
71
+ * for the path it was meant to stop.
72
+ * - A LIFECYCLE CLEAR (operator_stopped_at null): auto_terminate_focus_team()
73
+ * nulled the role at completion. Here the pre-existing layered fallback
74
+ * applies -- a review directive is a SECOND team that must spawn AFTER
75
+ * completion (spawn under the fallback role, else clear); a coding directive
76
+ * is the intended off-state and is skipped.
77
+ *
78
+ * With `operatorStopped` false the behaviour is byte-identical to before the
79
+ * operator-stop fact existed.
67
80
  *
68
81
  * Pure and exported for unit testing (peer of resolveReviewerFallbackRole).
69
82
  */
70
83
  export declare function decideNullRoleSpawn(input: {
71
84
  sessionType: 'coding' | 'review' | undefined;
72
85
  fallbackRole: AgentRole | undefined;
86
+ /**
87
+ * Whether an operator has stopped this focus (product_focuses.
88
+ * operator_stopped_at is set). When true, no spawn happens for any
89
+ * sessionType -- the operator's stop is authoritative.
90
+ */
91
+ operatorStopped: boolean;
73
92
  }): NullRoleSpawnDecision;
93
+ /**
94
+ * Once-per-blocked-episode gate for the null-role "coding off-state" skip
95
+ * warning. The listener holds a Set of focus ids already warned for the current
96
+ * continuous blocked episode; this mutates it and returns whether to emit the
97
+ * warning now:
98
+ *
99
+ * - focus not yet in the set -> add it and return true (warn once, on entry).
100
+ * - focus already in the set -> return false (suppress the per-poll repeat).
101
+ *
102
+ * The listener removes the focus from the set when it resumes (role assigned,
103
+ * directive cleared, or focus leaves the active set), so a later re-entry warns
104
+ * again. Pure w.r.t. its inputs (Set in, boolean out) and exported for unit
105
+ * testing without standing up the poll loop.
106
+ */
107
+ export declare function shouldWarnNullRoleSkip(warnedFocuses: Set<string>, focusId: string): boolean;
74
108
  //# sourceMappingURL=review-role-fallback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"review-role-fallback.d.ts","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,cAAc,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,GACzB,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;CACrC,GAAG,qBAAqB,CAIxB"}
1
+ {"version":3,"file":"review-role-fallback.d.ts","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,cAAc,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAC;AAExB;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,GACzB,SAAS,GAAG,SAAS,CAKvB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,WAAW,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,YAAY,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;CAC1B,GAAG,qBAAqB,CAKxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAIT"}
@@ -46,17 +46,52 @@ export function resolveReviewerFallbackRole(roles) {
46
46
  /**
47
47
  * Decide what to do with a null-role pending spawn directive.
48
48
  *
49
- * The layered fallback applies ONLY to review directives; a coding directive
50
- * with a null role is the intended off-state and is always skipped. A review
51
- * directive spawns under the fallback role when one resolves, else clears.
49
+ * A null assigned_agent_role_id now means one of TWO things, and they must be
50
+ * told apart:
51
+ *
52
+ * - An OPERATOR STOP (product_focuses.operator_stopped_at is set): the human
53
+ * pulled the Play toggle to halt the focus. This is authoritative and wins
54
+ * over everything -- when `operatorStopped` is true we `skip` for EVERY
55
+ * sessionType, review included. The stop lever must never become the trigger
56
+ * for the path it was meant to stop.
57
+ * - A LIFECYCLE CLEAR (operator_stopped_at null): auto_terminate_focus_team()
58
+ * nulled the role at completion. Here the pre-existing layered fallback
59
+ * applies -- a review directive is a SECOND team that must spawn AFTER
60
+ * completion (spawn under the fallback role, else clear); a coding directive
61
+ * is the intended off-state and is skipped.
62
+ *
63
+ * With `operatorStopped` false the behaviour is byte-identical to before the
64
+ * operator-stop fact existed.
52
65
  *
53
66
  * Pure and exported for unit testing (peer of resolveReviewerFallbackRole).
54
67
  */
55
68
  export function decideNullRoleSpawn(input) {
69
+ if (input.operatorStopped)
70
+ return { action: 'skip' };
56
71
  if (input.sessionType !== 'review')
57
72
  return { action: 'skip' };
58
73
  if (input.fallbackRole)
59
74
  return { action: 'spawn', role: input.fallbackRole };
60
75
  return { action: 'clear' };
61
76
  }
77
+ /**
78
+ * Once-per-blocked-episode gate for the null-role "coding off-state" skip
79
+ * warning. The listener holds a Set of focus ids already warned for the current
80
+ * continuous blocked episode; this mutates it and returns whether to emit the
81
+ * warning now:
82
+ *
83
+ * - focus not yet in the set -> add it and return true (warn once, on entry).
84
+ * - focus already in the set -> return false (suppress the per-poll repeat).
85
+ *
86
+ * The listener removes the focus from the set when it resumes (role assigned,
87
+ * directive cleared, or focus leaves the active set), so a later re-entry warns
88
+ * again. Pure w.r.t. its inputs (Set in, boolean out) and exported for unit
89
+ * testing without standing up the poll loop.
90
+ */
91
+ export function shouldWarnNullRoleSkip(warnedFocuses, focusId) {
92
+ if (warnedFocuses.has(focusId))
93
+ return false;
94
+ warnedFocuses.add(focusId);
95
+ return true;
96
+ }
62
97
  //# sourceMappingURL=review-role-fallback.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"review-role-fallback.js","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,WAAW,CAAC;AAmBtD;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAA0B;IAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAGnC;IACC,IAAI,KAAK,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,YAAY;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"review-role-fallback.js","sourceRoot":"","sources":["../../src/spawn/review-role-fallback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,WAAW,CAAC;AAmBtD;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CACzC,KAA0B;IAE1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,0BAA0B;YAAE,OAAO,IAAI,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,mBAAmB,CAAC,KASnC;IACC,IAAI,KAAK,CAAC,eAAe;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACrD,IAAI,KAAK,CAAC,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC9D,IAAI,KAAK,CAAC,YAAY;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;IAC7E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,aAA0B,EAC1B,OAAe;IAEf,IAAI,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC"}