opencode-swarm 7.153.0 → 7.153.2

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 (89) hide show
  1. package/dist/background/pending-delegations.d.ts +7 -0
  2. package/dist/cli/{config-doctor-7s7xga6f.js → config-doctor-tv0ar88m.js} +2 -2
  3. package/dist/cli/{core-vkpy14vv.js → core-kmsm50zn.js} +1 -1
  4. package/dist/cli/{curation-policy-xy7tm4ad.js → curation-policy-twa6agf8.js} +7 -7
  5. package/dist/cli/{curator-llm-factory-q5cjk7mt.js → curator-llm-factory-dxkhdhwg.js} +30 -30
  6. package/dist/cli/{curator-6freqnhf.js → curator-wxe863k9.js} +30 -30
  7. package/dist/cli/{evidence-summary-service-engtgjww.js → evidence-summary-service-9akrdg9t.js} +13 -13
  8. package/dist/cli/{gate-evidence-6bzzz8kq.js → gate-evidence-bdzqbg2y.js} +7 -7
  9. package/dist/cli/{guardrail-explain-vsck39dh.js → guardrail-explain-kx9rem51.js} +31 -31
  10. package/dist/cli/{guardrail-log-0dn8m3k6.js → guardrail-log-zxzyx1de.js} +5 -5
  11. package/dist/cli/{guardrail-reset-6e2vyfn9.js → guardrail-reset-1mc3st34.js} +30 -30
  12. package/dist/cli/{hive-promoter-021abs6z.js → hive-promoter-gncwtq0z.js} +30 -30
  13. package/dist/cli/{index-babtfqa0.js → index-02rbfrab.js} +450 -48
  14. package/dist/cli/{index-eqh3t8eh.js → index-0azn976d.js} +14 -14
  15. package/dist/cli/{index-9gjbe4e4.js → index-1gzdnjg9.js} +2 -2
  16. package/dist/cli/{index-g0m6kypc.js → index-21pt4jwf.js} +1 -1
  17. package/dist/cli/{index-z4bg1hv8.js → index-2mqmvxac.js} +9 -2
  18. package/dist/cli/{index-xv881ahz.js → index-3bh4h6ve.js} +108 -8
  19. package/dist/cli/index-3cegckkq.js +239 -0
  20. package/dist/cli/{index-hkmgzesw.js → index-3fpqpyp6.js} +2 -2
  21. package/dist/cli/{index-1yfpw7w0.js → index-3ttktt56.js} +1 -1
  22. package/dist/cli/{index-x6zy6veh.js → index-4pjkdcgc.js} +1 -1
  23. package/dist/cli/{index-5w915x8b.js → index-56ejcj07.js} +4 -4
  24. package/dist/cli/{index-7q56f3se.js → index-5ad8xcv3.js} +2 -2
  25. package/dist/cli/{index-e2hs0bh7.js → index-5zp4c36c.js} +1 -1
  26. package/dist/cli/{index-9vdh4kdx.js → index-84m4per1.js} +2 -2
  27. package/dist/cli/{index-txbd25bc.js → index-891g7hbf.js} +1 -1
  28. package/dist/cli/{index-3z5jtyan.js → index-a58nmnmz.js} +9 -2
  29. package/dist/cli/{index-77xy577c.js → index-bw55r5bz.js} +9 -9
  30. package/dist/cli/{index-80ekaf5b.js → index-ebshrg4x.js} +3 -3
  31. package/dist/cli/{index-5x67858f.js → index-h06112sh.js} +5 -5
  32. package/dist/cli/{index-6v77adf7.js → index-h3zhb18p.js} +2 -2
  33. package/dist/cli/{index-hw49515c.js → index-hhv2pvab.js} +1 -1
  34. package/dist/cli/{index-95qra69h.js → index-j4f8sz67.js} +4489 -2475
  35. package/dist/cli/{index-dh1gk5c5.js → index-kenz5bja.js} +32 -32
  36. package/dist/cli/{index-hexhzq5j.js → index-kvk65qzz.js} +3 -3
  37. package/dist/cli/{index-0q2mjxp2.js → index-meg33yyv.js} +1 -1
  38. package/dist/cli/{index-6zkm4p7p.js → index-pk1gbqmq.js} +1 -1
  39. package/dist/cli/{index-eajfs7v4.js → index-s6b8jgtn.js} +1 -1
  40. package/dist/cli/{index-k2ydsqs7.js → index-td0jz8xe.js} +4 -4
  41. package/dist/cli/{index-3z2f1cy8.js → index-vhbds89e.js} +4 -4
  42. package/dist/cli/{index-qgfnd206.js → index-x3y6zhgj.js} +1 -1
  43. package/dist/cli/{index-aw492hpn.js → index-x8vw6ap1.js} +58 -41
  44. package/dist/cli/{index-4gn111h3.js → index-yvepnbyd.js} +5 -5
  45. package/dist/cli/{index-0n7nm9e8.js → index-zppxpc3y.js} +2 -2
  46. package/dist/cli/index.js +30 -30
  47. package/dist/cli/{knowledge-escalator-79zbhpz5.js → knowledge-escalator-gp3mpbqe.js} +12 -12
  48. package/dist/cli/{knowledge-events-79cak7eb.js → knowledge-events-nvkhtqvn.js} +10 -10
  49. package/dist/cli/{knowledge-link-40zsr5ja.js → knowledge-link-ge0rddxw.js} +6 -6
  50. package/dist/cli/{knowledge-store-w29aw5w0.js → knowledge-store-2ngh8xvv.js} +7 -7
  51. package/dist/cli/{knowledge-validator-qsecnt2d.js → knowledge-validator-4bphyrec.js} +9 -9
  52. package/dist/cli/{pending-delegations-a2nmssr1.js → pending-delegations-nk78wx7h.js} +4 -4
  53. package/dist/cli/{pr-subscriptions-7879nq5k.js → pr-subscriptions-dq1f61ye.js} +4 -4
  54. package/dist/cli/{runner-fbgsxs74.js → runner-n50w1y4w.js} +7 -7
  55. package/dist/cli/{scan-cursor-1r3pcdqp.js → scan-cursor-c16xtygz.js} +8 -8
  56. package/dist/cli/{schema-19qsv6fw.js → schema-83tbte2g.js} +1 -1
  57. package/dist/cli/{scope-persistence-ypcrvqtt.js → scope-persistence-gdbw8xh8.js} +9 -9
  58. package/dist/cli/{skill-generator-wpp9kpa0.js → skill-generator-gr13xeha.js} +14 -14
  59. package/dist/cli/{telemetry-52pw5xac.js → telemetry-hh1j99za.js} +1 -1
  60. package/dist/cli/{worktree-collision-ownership-p31zm5k0.js → worktree-collision-ownership-4drcjymy.js} +15 -10
  61. package/dist/cli/{worktree-isolation-wcry8qnn.js → worktree-isolation-jjkfg2pg.js} +30 -30
  62. package/dist/commands/lanes.d.ts +25 -0
  63. package/dist/commands/registry.d.ts +1 -1
  64. package/dist/commands/reset-session.d.ts +8 -2
  65. package/dist/consensus/corpus.d.ts +16 -0
  66. package/dist/hooks/delegation-gate/worktree-isolation.d.ts +50 -3
  67. package/dist/hooks/delegation-gate/worktree-provisioning-owner.d.ts +40 -2
  68. package/dist/hooks/delegation-gate/worktree-recovery-authority.d.ts +201 -0
  69. package/dist/hooks/init-orphan-recovery.d.ts +5 -1
  70. package/dist/hooks/knowledge-application-gate.d.ts +10 -1
  71. package/dist/hooks/knowledge-receipt-ledger.d.ts +1 -1
  72. package/dist/hooks/trajectory-logger.d.ts +22 -6
  73. package/dist/hooks/trajectory-step-state.d.ts +43 -2
  74. package/dist/index.js +557 -552
  75. package/dist/observability/catalog.d.ts +6 -4
  76. package/dist/prm/index.d.ts +3 -3
  77. package/dist/prm/replay.d.ts +29 -2
  78. package/dist/prm/trajectory-store.d.ts +210 -20
  79. package/dist/state.d.ts +6 -4
  80. package/dist/telemetry.d.ts +21 -1
  81. package/dist/utils/atomic-write.d.ts +2 -0
  82. package/dist/utils/canonical-root.d.ts +31 -0
  83. package/dist/workflow/coder-settlement.d.ts +2 -0
  84. package/dist/worktree/index.d.ts +2 -2
  85. package/dist/worktree/merge.d.ts +47 -0
  86. package/package.json +8 -1
  87. package/dist/cli/index-fxvgaeq4.js +0 -125
  88. package/dist/cli/worktree-provisioning-owner-cve95q4f.js +0 -22
  89. package/dist/cli/{curator-drift-2h1d2fnq.js → curator-drift-h57z6tm3.js} +1 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The event catalog (issue #2029).
3
3
  *
4
- * Exactly 49 entries, matching the `TelemetryEvent` union at
4
+ * Exactly 50 entries, matching the `TelemetryEvent` union at
5
5
  * `src/telemetry.ts:15-131`. Thirty-eight predate the #2029 contract; the 39th is
6
6
  * `agent_conflict_detected` (previously emitted through a force-cast past the
7
7
  * type system), the 40th is `close_archive_result` (issue #2030 — the
@@ -16,9 +16,11 @@
16
16
  * `skill_usage_health` storage audit for the issue-#2038
17
17
  * `.swarm/skill-usage.jsonl` store and its authoritative pending sidecar,
18
18
  * the 48th is the bounded `core_events_health` storage audit for the
19
- * issue-#2039 `.swarm/events.jsonl` store, and the 49th is the bounded
19
+ * issue-#2039 `.swarm/events.jsonl` store, the 49th is the bounded
20
20
  * `shell_audit_health` storage audit for the issue-#2040
21
- * `.swarm/session/shell-audit.jsonl` security-audit store. These late additions are instances
21
+ * `.swarm/session/shell-audit.jsonl` security-audit store, and the 50th is
22
+ * the bounded `trajectory_health` storage audit for the issue-#2041
23
+ * `.swarm/trajectories/` PRM session store. These late additions are instances
22
24
  * of the defect class this contract exists to close: an event kind entering
23
25
  * the stream with no registration.
24
26
  *
@@ -73,7 +75,7 @@ export interface CatalogEntry {
73
75
  /**
74
76
  * Whether an event of this kind must carry `trace.parentSpanId`.
75
77
  *
76
- * `false` for all 49 entries today, and that is a truthful statement about
78
+ * `false` for all 50 entries today, and that is a truthful statement about
77
79
  * the current system rather than a placeholder: no producer supplies a
78
80
  * parent span, so `createObservation` never sets one. Setting this to `true`
79
81
  * for a kind whose producer cannot supply a parent would make every
@@ -26,7 +26,7 @@ import { formatCourseCorrectionForInjection, generateCourseCorrection } from './
26
26
  import { EscalationTracker } from './escalation';
27
27
  import { detectPatterns } from './pattern-detector';
28
28
  import { recordReplayEntry, startReplayRecording } from './replay';
29
- import { cleanupOldTrajectoryFiles, clearTrajectoryCache, getInMemoryTrajectory, readTrajectory } from './trajectory-store';
29
+ import { clearTrajectoryCache, getInMemoryTrajectory, readTrajectoryWithCoverage, scheduleTrajectoryCleanup } from './trajectory-store';
30
30
  import type { PrmConfig } from './types';
31
31
  /**
32
32
  * Test-only dependency-injection seam — see `gitignore-warning.ts:_internals`.
@@ -38,12 +38,12 @@ import type { PrmConfig } from './types';
38
38
  */
39
39
  export declare const _internals: {
40
40
  getAgentSession: typeof getAgentSession;
41
- readTrajectory: typeof readTrajectory;
41
+ readTrajectoryWithCoverage: typeof readTrajectoryWithCoverage;
42
42
  getInMemoryTrajectory: typeof getInMemoryTrajectory;
43
43
  detectPatterns: typeof detectPatterns;
44
44
  generateCourseCorrection: typeof generateCourseCorrection;
45
45
  formatCourseCorrectionForInjection: typeof formatCourseCorrectionForInjection;
46
- cleanupOldTrajectoryFiles: typeof cleanupOldTrajectoryFiles;
46
+ scheduleTrajectoryCleanup: typeof scheduleTrajectoryCleanup;
47
47
  clearTrajectoryCache: typeof clearTrajectoryCache;
48
48
  recordReplayEntry: typeof recordReplayEntry;
49
49
  startReplayRecording: typeof startReplayRecording;
@@ -60,8 +60,25 @@ declare function sanitizeFilename(input: string): string;
60
60
  */
61
61
  export declare function startReplayRecording(sessionID: string, directory: string): Promise<string | null>;
62
62
  /**
63
- * Appends a ReplayEntry to the replay artifact file.
64
- * Non-blocking: errors are caught and logged, never thrown.
63
+ * Hard budgets for replay artifacts (issue #2041 Required 1 — the PRM budget
64
+ * set covers replays as well as trajectories). Replays are write-only
65
+ * best-effort diagnostics: at the cap, further entries for that artifact are
66
+ * skipped with a one-time warning rather than rotated (rotation would create
67
+ * unbounded sibling files, which is exactly what this cap exists to prevent).
68
+ * The 7-day age sweep + per-directory count cap in trajectory-store's
69
+ * `cleanupOldTrajectoryFiles` bound the directory as a whole.
70
+ */
71
+ export declare const REPLAY_LIMITS: {
72
+ /** Per-artifact byte ceiling. */
73
+ readonly maxBytes: number;
74
+ /** Stat cadence per artifact (bytes are tracked in memory between stats). */
75
+ readonly checkIntervalEntries: 16;
76
+ /** Bound on tracked artifacts (Invariant 8). */
77
+ readonly maxTrackedArtifacts: 256;
78
+ };
79
+ /**
80
+ * Appends a ReplayEntry to the replay artifact file, enforcing the per-artifact
81
+ * byte cap. Non-blocking: errors are caught and logged, never thrown.
65
82
  *
66
83
  * @param artifactPath - Path to the replay artifact file
67
84
  * @param sessionID - Session identifier
@@ -72,5 +89,15 @@ export declare const _test_exports: {
72
89
  readonly isPathSafe: typeof isPathSafe;
73
90
  readonly isWithinReplaysDir: typeof isWithinReplaysDir;
74
91
  readonly sanitizeFilename: typeof sanitizeFilename;
92
+ readonly REPLAY_LIMITS: {
93
+ /** Per-artifact byte ceiling. */
94
+ readonly maxBytes: number;
95
+ /** Stat cadence per artifact (bytes are tracked in memory between stats). */
96
+ readonly checkIntervalEntries: 16;
97
+ /** Bound on tracked artifacts (Invariant 8). */
98
+ readonly maxTrackedArtifacts: 256;
99
+ };
100
+ /** Test isolation: drop per-artifact byte-cap bookkeeping. */
101
+ readonly resetReplayByteTracking: () => void;
75
102
  };
76
103
  export {};
@@ -1,40 +1,215 @@
1
1
  /**
2
- * TRAJECTORY STORE (Session-Level)
2
+ * TRAJECTORY STORE (Session-Level) — bounded (issue #2041)
3
3
  *
4
4
  * Per-session trajectory storage for PRM pattern detection.
5
- * Writes to .swarm/trajectories/{sessionId}.jsonl
5
+ * Writes to .swarm/trajectories/{sessionId}.jsonl (+ a small
6
+ * {sessionId}.jsonl.meta.json checkpoint and a transient {sessionId}.jsonl.lock).
7
+ *
8
+ * Bounded session-trajectory contract (mirrors the #2040 shell-audit store):
9
+ * - ONE knob, `maxLines`, governs BOTH the in-memory cache trim and the disk
10
+ * compaction: over budget, the newest floor(maxLines/2) entries are retained
11
+ * (the cache has always used exactly this rule — the disk now matches it).
12
+ * - The disk byte ceiling sessionMaxBytesFor(maxLines) is SOVEREIGN and is
13
+ * enforced at APPEND time (the stat needed for torn-tail re-framing doubles
14
+ * as the size probe): over ceiling, compaction runs before the append.
15
+ * - A line-count check runs every `checkIntervalAppends` appends (the only
16
+ * part that needs a read; it is tail-bounded, never a whole-file read).
17
+ * - Readers (`readTrajectory`, `getCurrentStep`) read a bounded tail window and
18
+ * disclose coverage; `getCurrentStep` also consults the atomically persisted
19
+ * checkpoint so a restart never scans history to continue step numbering.
20
+ * - Crash semantics: compaction publishes via tmp+rename (a partial rewrite is
21
+ * never visible); the checkpoint is written after the data rewrite under the
22
+ * same lock, and merges `max(previous, observed)` so it can only ratchet UP.
23
+ * Compaction keeps the NEWEST entries, so the global max step always survives
24
+ * on disk — except when every retained line is corrupt, in which case the
25
+ * checkpoint alone preserves step continuity (that is why it exists).
26
+ * - Two writers: an in-process per-key promise chain (same-process appends
27
+ * never burn the cross-process retry budget) plus a per-file `.lock`
28
+ * (wx existence create — the PID inside is diagnostic only, never
29
+ * liveness-checked — stale-break, bounded retry). Lock exhaustion skips the
30
+ * append with a warning — telemetry loss is preferred over file corruption
31
+ * in this best-effort store — and is counted into `trajectory_health`.
6
32
  *
7
33
  * Coexists with task-level trajectory-logger.ts which writes to
8
- * .swarm/evidence/{taskId}/trajectory.jsonl for audit/evidence.
34
+ * .swarm/evidence/{taskId}/trajectory.jsonl for audit/evidence (bounded by its
35
+ * own write-side truncation; untouched by this contract).
9
36
  */
37
+ import { telemetry } from '../telemetry';
10
38
  import type { TrajectoryEntry } from './types';
39
+ /**
40
+ * Hard budgets for the bounded session-trajectory contract (issue #2041).
41
+ * Exported for tests and for the retention registry's documented contract.
42
+ */
43
+ export declare const TRAJECTORY_LIMITS: {
44
+ /** Line-count compaction check cadence (append-path, amortized). */
45
+ readonly checkIntervalAppends: 25;
46
+ /** Tail-bounded read window for readTrajectoryWithCoverage. */
47
+ readonly readMaxBytes: number;
48
+ /** Tail-bounded read window for getCurrentStep (newest lines carry max step). */
49
+ readonly stepReadMaxBytes: number;
50
+ /** Tail-bounded input window for compaction (legacy files may be huge). */
51
+ readonly compactMaxBytes: number;
52
+ /** A JSONL line longer than this is shed as oversize (append skips it). */
53
+ readonly maxLineBytes: number;
54
+ /** Estimated bytes per entry used to derive the per-session byte ceiling. */
55
+ readonly estimatedLineBytes: 512;
56
+ /** Floor for the derived per-session byte ceiling. */
57
+ readonly minSessionMaxBytes: number;
58
+ /** Per-file cross-process lock: bounded retries, then skip + warn. */
59
+ readonly lockRetries: 20;
60
+ readonly lockRetryDelayMs: 5;
61
+ /** A lock older than this is considered crashed and broken. */
62
+ readonly lockStaleMs: number;
63
+ /** Per-directory session-file count cap (age-sweep backstop). */
64
+ readonly maxFilesPerDir: 200;
65
+ /** Unlinks per cleanup invocation (converges across runs). */
66
+ readonly maxDeletionsPerRun: 256;
67
+ /** Default age sweep horizon for trajectories/ and replays/. */
68
+ readonly defaultMaxAgeDays: 7;
69
+ /** Debounce for the lazily scheduled cleanup pass. */
70
+ readonly cleanupDebounceMs: number;
71
+ /** Cooldown for append_skip health events (lock-exhaust observability). */
72
+ readonly appendSkipEventCooldownMs: 60000;
73
+ };
74
+ /**
75
+ * Sovereign per-session disk byte ceiling, derived from the same `maxLines`
76
+ * knob that governs cache trimming and line-count compaction.
77
+ */
78
+ export declare function sessionMaxBytesFor(maxLines: number): number;
79
+ /** Test/DI seam — see `gitignore-warning.ts:_internals` for the convention. */
80
+ export declare const _internals: {
81
+ telemetry: typeof telemetry;
82
+ };
11
83
  /**
12
84
  * Returns cached trajectory entries for a session (empty array if not cached).
85
+ *
86
+ * @param sessionId - Session identifier
87
+ * @param directory - Workspace root that owns the session's trajectory file
13
88
  */
14
- export declare function getInMemoryTrajectory(sessionId: string): TrajectoryEntry[];
89
+ export declare function getInMemoryTrajectory(sessionId: string, directory: string): TrajectoryEntry[];
15
90
  /**
16
91
  * Clears trajectory cache (for test isolation or session cleanup).
92
+ *
93
+ * With a sessionId but no directory, every root's entry for that session is
94
+ * cleared (suffix scan over the bounded map) — reset paths that legitimately
95
+ * lack a directory still release the cached window.
17
96
  */
18
97
  export declare function clearTrajectoryCache(sessionId?: string): void;
98
+ interface TrajectoryCheckpoint {
99
+ version: 1;
100
+ highestStep: number;
101
+ droppedEntries: number;
102
+ /**
103
+ * Cumulative bytes discarded whole — chiefly the pre-window portion of an
104
+ * oversized file that a tail-bounded compaction rewrote away (shed
105
+ * windowed entries also contribute their bytes here, alongside their
106
+ * counts in droppedEntries: the two metrics are independent dimensions
107
+ * of the same loss). Without this, a compaction whose window kept every
108
+ * windowed entry (droppedEntries delta 0) would silently erase megabytes
109
+ * beyond the window while coverage still reported `complete`. Optional
110
+ * on READ for checkpoints written before this field existed.
111
+ */
112
+ droppedBytes?: number;
113
+ compactedAt: string;
114
+ }
115
+ /**
116
+ * Reads a session's persisted step checkpoint (null when absent/corrupt).
117
+ *
118
+ * Production consumers: `getCurrentStep` (restart continuity) and the consensus
119
+ * corpus's default PRM coverage signal (dropped-by-compaction disclosure).
120
+ */
121
+ export declare function readTrajectoryCheckpoint(sessionId: string, directory: string): Promise<TrajectoryCheckpoint | null>;
122
+ /**
123
+ * One bounded stat of a session's trajectory file (null when absent).
124
+ *
125
+ * Production consumer: the consensus corpus's default PRM coverage verdict —
126
+ * a file larger than the read window means the live bounded read was
127
+ * window-truncated, exactly matching `readTrajectoryWithCoverage`'s coverage
128
+ * semantics so the two paths cannot diverge (implementation-review round 1).
129
+ */
130
+ export declare function trajectoryFileBytes(sessionId: string, directory: string): Promise<number | null>;
19
131
  /**
20
- * Appends a single TrajectoryEntry to the session's trajectory file.
132
+ * Appends a single TrajectoryEntry to the session's trajectory file, enforcing
133
+ * the bounded session-trajectory contract on disk as well as in memory.
21
134
  *
22
135
  * @param sessionId - Session identifier
23
136
  * @param entry - Trajectory entry to append
24
137
  * @param directory - Base directory (workspace root)
25
- * @param maxLines - Maximum lines before in-memory cache trimming (default 1000)
138
+ * @param maxLines - Line budget for BOTH the in-memory cache trim and disk
139
+ * compaction (compaction retains the newest floor(maxLines/2) entries; the
140
+ * derived byte ceiling sessionMaxBytesFor(maxLines) is sovereign)
26
141
  */
27
142
  export declare function appendTrajectoryEntry(sessionId: string, entry: TrajectoryEntry, directory: string, maxLines?: number): Promise<void>;
143
+ export type TrajectoryCoverage = 'complete' | 'truncated' | 'empty';
144
+ export interface TrajectoryReadWithCoverage {
145
+ entries: TrajectoryEntry[];
146
+ coverage: TrajectoryCoverage;
147
+ /** Entries removed by prior compactions (persisted checkpoint total). */
148
+ droppedByCompaction: number;
149
+ skippedMalformed: number;
150
+ }
28
151
  /**
29
- * Reads all TrajectoryEntry records from a session's trajectory file.
152
+ * Tail-bounded read of a session's trajectory with explicit coverage
153
+ * disclosure (issue #2041 Required 3/4). Reads at most
154
+ * TRAJECTORY_LIMITS.readMaxBytes regardless of file size; the returned entries
155
+ * are the newest window in file order.
156
+ *
157
+ * `maxLines` is the SAME knob the append path uses: a cold read populates the
158
+ * in-memory cache trimmed to the configured budget, so a legacy pre-fix file
159
+ * cannot inflate the cache PRM pattern detection reads (review round 1).
160
+ */
161
+ export declare function readTrajectoryWithCoverage(sessionId: string, directory: string, maxLines?: number): Promise<TrajectoryReadWithCoverage>;
162
+ /**
163
+ * Reads the newest bounded window of TrajectoryEntry records from a session's
164
+ * trajectory file.
30
165
  *
31
166
  * @param sessionId - Session identifier
32
167
  * @param directory - Base directory (workspace root)
168
+ * @param maxLines - Optional cache-trim budget (same knob as the append path)
33
169
  * @returns Array of trajectory entries (empty array if file doesn't exist)
34
170
  */
35
- export declare function readTrajectory(sessionId: string, directory: string): Promise<TrajectoryEntry[]>;
171
+ export declare function readTrajectory(sessionId: string, directory: string, maxLines?: number): Promise<TrajectoryEntry[]>;
36
172
  export declare const _test_exports: {
37
173
  readonly MAX_TRACKED_TRAJECTORY_SESSIONS: 500;
174
+ readonly TRAJECTORY_LIMITS: {
175
+ /** Line-count compaction check cadence (append-path, amortized). */
176
+ readonly checkIntervalAppends: 25;
177
+ /** Tail-bounded read window for readTrajectoryWithCoverage. */
178
+ readonly readMaxBytes: number;
179
+ /** Tail-bounded read window for getCurrentStep (newest lines carry max step). */
180
+ readonly stepReadMaxBytes: number;
181
+ /** Tail-bounded input window for compaction (legacy files may be huge). */
182
+ readonly compactMaxBytes: number;
183
+ /** A JSONL line longer than this is shed as oversize (append skips it). */
184
+ readonly maxLineBytes: number;
185
+ /** Estimated bytes per entry used to derive the per-session byte ceiling. */
186
+ readonly estimatedLineBytes: 512;
187
+ /** Floor for the derived per-session byte ceiling. */
188
+ readonly minSessionMaxBytes: number;
189
+ /** Per-file cross-process lock: bounded retries, then skip + warn. */
190
+ readonly lockRetries: 20;
191
+ readonly lockRetryDelayMs: 5;
192
+ /** A lock older than this is considered crashed and broken. */
193
+ readonly lockStaleMs: number;
194
+ /** Per-directory session-file count cap (age-sweep backstop). */
195
+ readonly maxFilesPerDir: 200;
196
+ /** Unlinks per cleanup invocation (converges across runs). */
197
+ readonly maxDeletionsPerRun: 256;
198
+ /** Default age sweep horizon for trajectories/ and replays/. */
199
+ readonly defaultMaxAgeDays: 7;
200
+ /** Debounce for the lazily scheduled cleanup pass. */
201
+ readonly cleanupDebounceMs: number;
202
+ /** Cooldown for append_skip health events (lock-exhaust observability). */
203
+ readonly appendSkipEventCooldownMs: 60000;
204
+ };
205
+ readonly sessionMaxBytesFor: typeof sessionMaxBytesFor;
206
+ readonly getCacheSize: () => number;
207
+ readonly getChainSize: () => number;
208
+ /** Test isolation: the cleanup debounce is module-global state. */
209
+ readonly resetCleanupDebounce: () => void;
210
+ /** Test isolation: lock-skip counters are module-global state. */
211
+ readonly resetLockSkipCounters: () => void;
212
+ readonly appendFileIsTorn: (filePath: string) => Promise<boolean>;
38
213
  };
39
214
  /**
40
215
  * Alias for readTrajectory - retrieves trajectory entries for a session.
@@ -44,17 +219,16 @@ export declare const _test_exports: {
44
219
  * @returns Array of trajectory entries
45
220
  */
46
221
  export declare function getTrajectoryForSession(sessionId: string, directory: string): Promise<TrajectoryEntry[]>;
47
- /**
48
- * Truncates the trajectory file to the newest half if lines exceed maxLines.
49
- *
50
- * @param sessionId - Session identifier
51
- * @param directory - Base directory (workspace root)
52
- * @param maxLines - Maximum number of lines to retain
53
- */
54
- export declare function truncateTrajectoryIfNeeded(sessionId: string, directory: string, maxLines: number): Promise<void>;
55
222
  /**
56
223
  * Returns the highest step number in the session's trajectory.
57
- * Used to determine the next step number when appending.
224
+ * Used to determine the next step number when appending — the production
225
+ * restart-seeding path in trajectory-logger.ts calls this before the first
226
+ * mint of a session so step numbers stay monotonic across process restarts.
227
+ *
228
+ * Bounded: reads at most TRAJECTORY_LIMITS.stepReadMaxBytes of the file
229
+ * (the newest lines carry the max step) and merges the atomically persisted
230
+ * checkpoint, so a restart never scans unbounded history — and the high-water
231
+ * mark survives even a fully-corrupt data file.
58
232
  *
59
233
  * @param sessionId - Session identifier
60
234
  * @param directory - Base directory (workspace root)
@@ -62,8 +236,24 @@ export declare function truncateTrajectoryIfNeeded(sessionId: string, directory:
62
236
  */
63
237
  export declare function getCurrentStep(sessionId: string, directory: string): Promise<number>;
64
238
  /**
65
- * Deletes trajectory and replay files older than maxAgeDays.
66
- * Runs against .swarm/trajectories/ and .swarm/replays/ directories.
67
- * Non-blocking: errors logged, not thrown.
239
+ * Debounced, fire-and-forget cleanup scheduling used by the PRM hook's
240
+ * once-per-session trigger. The plugin's post-resolution init pass calls
241
+ * `cleanupOldTrajectoryFiles` directly (the debounce must not suppress the
242
+ * startup sweep).
243
+ */
244
+ export declare function scheduleTrajectoryCleanup(directory: string): void;
245
+ /**
246
+ * Deletes aged trajectory and replay files, then enforces a per-directory
247
+ * session-file count cap (oldest-first) so a hostile or skewed mtime cannot
248
+ * grow `.swarm/trajectories/` or `.swarm/replays/` without bound. A session's
249
+ * `.meta.json` checkpoint is removed with its `.jsonl`; orphan checkpoints and
250
+ * stale atomic-write `*.tmp` leftovers age out on their own. Bounded to
251
+ * maxDeletionsPerRun unlinks per invocation (converges across runs); every
252
+ * failure is swallowed. Never touches `.swarm/evidence/` (task evidence is a
253
+ * different directory tree governed by its own contract).
254
+ *
255
+ * Replays and trajectories are independent artifacts sharing this sweep: no
256
+ * cross-linkage is enforced or implied.
68
257
  */
69
258
  export declare function cleanupOldTrajectoryFiles(directory: string, maxAgeDays?: number): Promise<void>;
259
+ export {};
package/dist/state.d.ts CHANGED
@@ -1411,10 +1411,12 @@ export declare function setCriticalShownIds(sessionID: string, value: {
1411
1411
  * whether an entry was removed. */
1412
1412
  export declare function clearCriticalShownIds(sessionID: string): boolean;
1413
1413
  /** Build the directive-identity fingerprint used to key the gate denial
1414
- * counter — a sorted, joined snapshot of a critical-directive-id set. Two
1415
- * calls with the same id set (regardless of order) produce the same key,
1416
- * so re-injecting the identical directive on a later turn does not reset
1417
- * the counter, but swapping to a genuinely different directive set does. */
1414
+ * counter — a sorted, joined snapshot of a critical-directive ENTRY-id set.
1415
+ * Two calls with the same entry-id set (regardless of order, and regardless
1416
+ * of which trace ids re-displayed those entries issue #2398) produce the
1417
+ * same key, so re-injecting the same directive on a later turn does not
1418
+ * reset the counter, but swapping to a genuinely different directive set
1419
+ * does. */
1418
1420
  export declare function buildGateDenialDirectiveKey(ids: string[]): string;
1419
1421
  /** Increment (or start) the per-session gate denial counter for the given
1420
1422
  * directive identity, FIFO-evicting the oldest session entry if the cap is
@@ -32,7 +32,7 @@ export type TelemetryEvent = 'session_started' | 'session_ended' | 'agent_activa
32
32
  * `prm_hard_stop` TRIGGER emitted by `src/prm/escalation.ts`. A trigger with
33
33
  * no matching delivery means the containment never reached the agent.
34
34
  */
35
- | 'prm_hard_stop_delivered' | 'agent_conflict_detected' | 'close_archive_result' | 'knowledge_receipt_transition' | 'knowledge_maintenance' | 'residue_health' | 'context_telemetry_health' | 'skill_usage_health' | 'core_events_health' | 'shell_audit_health';
35
+ | 'prm_hard_stop_delivered' | 'agent_conflict_detected' | 'close_archive_result' | 'knowledge_receipt_transition' | 'knowledge_maintenance' | 'residue_health' | 'context_telemetry_health' | 'skill_usage_health' | 'core_events_health' | 'shell_audit_health' | 'trajectory_health';
36
36
  /** Stable classification for how a reviewer-gate decision was established. */
37
37
  export type ReviewerGateEvidenceKind = 'genuine' | 'fallback' | 'data_quality' | 'block';
38
38
  /**
@@ -335,6 +335,26 @@ export declare const telemetry: {
335
335
  bytes: number;
336
336
  limit_bytes: number;
337
337
  }): void;
338
+ /**
339
+ * PRM session-trajectory store health (issue #2041): emitted on
340
+ * compaction, cleanup sweeps, and (cooldown-bounded) lock-skipped appends
341
+ * for the bounded `.swarm/trajectories/` store. Counts ONLY — no session
342
+ * identifiers, no filesystem paths, no trajectory content — matching the
343
+ * observability contract's no-content-in-metrics rule.
344
+ * `dropped_count` discloses entries folded by compaction (line budget or
345
+ * the sovereign byte ceiling) and files removed by cleanup; `skipped_lock_count`
346
+ * discloses appends skipped because the per-file cross-process lock stayed
347
+ * busy (best-effort store: telemetry loss is preferred over corruption).
348
+ */
349
+ trajectoryHealth(data: {
350
+ trigger: "compaction" | "cleanup" | "append_skip";
351
+ retained_count: number;
352
+ dropped_count: number;
353
+ corrupt_count: number;
354
+ skipped_lock_count: number;
355
+ bytes: number;
356
+ limit_bytes: number;
357
+ }): void;
338
358
  /**
339
359
  * Issue #2063 C2 — the PRM hard-stop DENIAL was actually delivered to the
340
360
  * agent (thrown by the guardrails `toolBefore` consumer). `prm_hard_stop`
@@ -131,6 +131,8 @@ export declare const MAX_ATOMIC_WRITE_BYTES: number;
131
131
  export interface AtomicWriteOptions {
132
132
  /** Skip the pre-rename fsync (tests / truly ephemeral data). */
133
133
  readonly skipFsync?: boolean;
134
+ /** File mode for the per-write temporary file before rename. */
135
+ readonly mode?: number;
134
136
  /**
135
137
  * Override the bounded-write cap (default MAX_ATOMIC_WRITE_BYTES). Exposed
136
138
  * so the bound itself is testable without allocating 256 MiB.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Canonical project-root keying for session-scoped module state (issue #2041).
3
+ *
4
+ * The trajectory cache (src/prm/trajectory-store.ts), the step counters
5
+ * (src/hooks/trajectory-step-state.ts), and the logger's restart-seed gate
6
+ * (src/hooks/trajectory-logger.ts) are all process-global maps that a
7
+ * multi-root host can share through one plugin module instance. They must key
8
+ * by canonical root + session id, and they must agree on what "canonical"
9
+ * means: realpath resolves junction/symlink aliases (linked worktrees on
10
+ * Windows are junctions), `path.resolve` is the fallback for a root that does
11
+ * not exist yet, and Windows roots are case-folded because the filesystem is.
12
+ * A symlinked `.swarm` directory itself is already rejected by
13
+ * `validateSwarmPath`, so the aliasing this resolves lives strictly ABOVE
14
+ * `.swarm`. One shared implementation so the three maps cannot drift.
15
+ */
16
+ /**
17
+ * Separator between canonical root and session id in composite keys.
18
+ * `String.fromCharCode(0)` rather than an escape literal so the source stays
19
+ * clean text (a literal NUL byte in source makes tooling treat the file as
20
+ * binary); NUL cannot appear in a path on any supported platform.
21
+ */
22
+ export declare const SESSION_KEY_SEPARATOR: string;
23
+ export declare function canonicalRootKey(directory: string): string;
24
+ /** Composite map key: canonical root + NUL + session id. */
25
+ export declare function compositeSessionKey(directory: string, sessionId: string): string;
26
+ /**
27
+ * Suffix every root's key for a session ends with. Suffix-scan clear paths
28
+ * that legitimately lack a directory (the `/swarm reset` entry points) match
29
+ * all roots with this.
30
+ */
31
+ export declare function sessionKeySuffix(sessionId: string): string;
@@ -1,5 +1,6 @@
1
1
  import type { BackgroundTaskChangeContext, BackgroundWorktreeDescriptor } from '../background/pending-delegations.js';
2
2
  import { type TaskEvidence } from '../gate-evidence.js';
3
+ import { removeWorktreeProvisioningOwner } from '../hooks/delegation-gate/worktree-provisioning-owner.js';
3
4
  import type { MergeOperationProvenance } from '../worktree/merge.js';
4
5
  import type { CoderSettlementWal } from './workflow-wal-schema.js';
5
6
  export interface CoderSettlementResult {
@@ -161,6 +162,7 @@ export declare function recoverStaleCoderSettlements(directory: string, options?
161
162
  }>;
162
163
  export declare const _internals: {
163
164
  liveDispatches: Set<string>;
165
+ removeWorktreeProvisioningOwner: typeof removeWorktreeProvisioningOwner;
164
166
  /**
165
167
  * Test seam for the lane-branch existence probe. Production reads it here,
166
168
  * so the #2236 fail-closed invariants — never mark a WAL terminal while the
@@ -1,5 +1,5 @@
1
1
  export type { AutoCommitSkip, AutoCommitSuccess, CleanCheckFailure, CleanCheckSuccess, CleanFailure, CleanSuccess, ProvisionFailure, ProvisionSuccess, RemoveFailure, RemoveSuccess, } from './core';
2
2
  export { _internals as coreInternals, assertCleanWorkingTree, autoCommitDirty, checkPathBudget, cleanUntrackedFiles, isCleanWorktree, isPathUnderSwarmWorktreeBase, makeWorktreeBranchName, provisionWorktree, removeWorktree, resolveWorktreeBaseDir, shortenWorktreePath, } from './core';
3
- export type { CleanupFailure, CleanupSuccess, ConflictHandlingError, ConflictInfo, DirtyMergeFailure, DirtyMergePartial, DirtyMergeSuccess, MergeConflict, MergeFailure, MergeSuccess, OrphanCleanupResult, StartupRecoveryResult, } from './merge';
4
- export { _internals as mergeInternals, attemptMergeBackFromDirty, cleanupOrphanedBranches, getMergeStrategy, handleMergeConflict, mergeLaneBranch, postMergeCleanup, pruneStaleWorktreeMetadata, scanRegisteredWorktreeLiveness, startupOrphanRecovery, } from './merge';
3
+ export type { CleanupFailure, CleanupSuccess, ConflictHandlingError, ConflictInfo, DirtyMergeFailure, DirtyMergePartial, DirtyMergeSuccess, ImmutableMergeRecoveryCoordinates, ImmutableMergeRecoveryResult, MergeConflict, MergeFailure, MergeSuccess, OrphanCleanupResult, StartupRecoveryResult, } from './merge';
4
+ export { _internals as mergeInternals, attemptMergeBackFromDirty, cleanupOrphanedBranches, getMergeStrategy, handleMergeConflict, mergeLaneBranch, postMergeCleanup, pruneStaleWorktreeMetadata, recoverMergeBackFromImmutableCoordinates, scanRegisteredWorktreeLiveness, startupOrphanRecovery, } from './merge';
5
5
  export * from './types';
@@ -30,6 +30,8 @@ export declare const _internals: {
30
30
  cleanupOrphanedBranches: typeof cleanupOrphanedBranches;
31
31
  /** Test seam for startupOrphanRecovery — allows tests to intercept the recovery call. */
32
32
  startupOrphanRecovery: typeof startupOrphanRecovery;
33
+ /** Test seam for the dirty merge overlap preflight. */
34
+ captureMergeOverlapSnapshot: typeof captureMergeOverlapSnapshot;
33
35
  /** FR-001b: exposes extractSessionId for lane ownership validation. */
34
36
  extractSessionId: typeof extractSessionId;
35
37
  /**
@@ -156,6 +158,26 @@ export type MergeReconciliationResult = {
156
158
  landed: false;
157
159
  error?: string;
158
160
  };
161
+ /**
162
+ * Immutable preserved-lane coordinates captured by the #2105 recovery
163
+ * authority. This path deliberately consumes exact object ids rather than a
164
+ * mutable branch ref, so a redispatch can recover the preserved lane even if
165
+ * the lane branch later advances, rewinds, or is renamed.
166
+ */
167
+ export interface ImmutableMergeRecoveryCoordinates {
168
+ sourceBaseOid: string;
169
+ sourceHeadOid: string;
170
+ targetHeadOid: string;
171
+ strategy: MergeStrategy;
172
+ }
173
+ export type ImmutableMergeRecoveryResult = (MergeSuccess & {
174
+ sourceCommitOrder?: string[];
175
+ rewrittenCommitOrder?: string[];
176
+ }) | (MergeConflict & {
177
+ sourceCommitOrder?: string[];
178
+ }) | (MergeFailure & {
179
+ sourceCommitOrder?: string[];
180
+ });
159
181
  export interface DirtyMergeOptions {
160
182
  /** Stable identifier allocated before the merge-back begins. */
161
183
  operationId?: string;
@@ -167,6 +189,20 @@ export interface DirtyMergeOptions {
167
189
  */
168
190
  onBeforeMerge?: (provenance: MergeOperationProvenance) => Promise<void>;
169
191
  }
192
+ interface MergeOverlapSnapshot {
193
+ targetHead: string;
194
+ laneHead: string;
195
+ mergeBase: string;
196
+ indexDigest: string;
197
+ statusDigest: string;
198
+ incomingDigest: string;
199
+ overlapPaths: string[];
200
+ }
201
+ declare function captureMergeOverlapSnapshot(primaryDir: string, branchName: string): Promise<{
202
+ snapshot: MergeOverlapSnapshot;
203
+ } | {
204
+ error: string;
205
+ }>;
170
206
  export interface OrphanCleanupResult {
171
207
  removed: string[];
172
208
  skipped: string[];
@@ -219,6 +255,17 @@ export declare function getMergeStrategy(config: {
219
255
  * @returns Discriminated union: success, conflict, or failure.
220
256
  */
221
257
  export declare function mergeLaneBranch(primaryDir: string, branchName: string, strategy: MergeStrategy): Promise<MergeSuccess | MergeConflict | MergeFailure>;
258
+ /**
259
+ * Re-applies a preserved lane using exact immutable object ids captured by the
260
+ * #2105 recovery authority.
261
+ *
262
+ * This is the strategy-specific recovery primitive that `worktree-isolation`
263
+ * can call when a same-task redispatch claims a preserved lane. It refuses to
264
+ * operate on mutable branch refs, verifies that the captured lane ancestry is
265
+ * coherent, and fails closed if the current primary checkout no longer
266
+ * descends from the captured target head.
267
+ */
268
+ export declare function recoverMergeBackFromImmutableCoordinates(primaryDir: string, coordinates: ImmutableMergeRecoveryCoordinates): Promise<ImmutableMergeRecoveryResult>;
222
269
  /**
223
270
  * Detect whether a previously-started merge-back already landed on the target.
224
271
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.153.0",
3
+ "version": "7.153.2",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -111,7 +111,14 @@
111
111
  "check:retention": "bun run scripts/check-retention-registry.ts",
112
112
  "check:core-events": "bun run scripts/check-core-events-usage.ts",
113
113
  "check:shell-audit": "bun run scripts/check-shell-audit-usage.ts",
114
+ "check:trajectory-store": "bun run scripts/check-trajectory-store-usage.ts",
115
+ "check:mock-cleanup": "bun run scripts/check-mock-cleanup.ts",
116
+ "check:invariants": "bun run scripts/check-invariants.ts",
117
+ "check:cross-contamination": "bun run scripts/check-cross-contamination.ts",
118
+ "check:test-clock": "bun run scripts/check-test-clock.ts",
114
119
  "check:test-file-cap": "bun run scripts/check-test-file-cap.ts",
120
+ "check:test-tmpdir": "bun run scripts/check-test-tmpdir.ts",
121
+ "check:bash-portability": "bun run scripts/check-bash-portability.ts",
115
122
  "check:pending-fragment": "bun run scripts/check-pending-fragment.ts",
116
123
  "check:gate-portability": "bun run scripts/check-gate-portability.ts",
117
124
  "check:bare-spawn": "bun run scripts/check-bare-executable-spawn.ts",