opencode-swarm 7.166.1 → 7.166.3
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.
- package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
- package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
- package/dist/background/candidate-contract.d.ts +14 -0
- package/dist/background/pr-subscriptions.d.ts +33 -1
- package/dist/background/workspace-snapshot.d.ts +41 -0
- package/dist/cli/{coder-settlement-x0h3h6vd.js → coder-settlement-nx8ff9k1.js} +13 -13
- package/dist/cli/{config-doctor-pjcjeh8f.js → config-doctor-n9cjh11e.js} +4 -4
- package/dist/cli/{core-v11pd1yk.js → core-cbwrwhcz.js} +2 -2
- package/dist/cli/{curation-policy-jtzwz6bc.js → curation-policy-bvxf5pxv.js} +11 -6
- package/dist/cli/{curator-ssvd7evd.js → curator-c7amtass.js} +40 -39
- package/dist/cli/{curator-drift-jpdfwae9.js → curator-drift-20m2ab5w.js} +6 -6
- package/dist/cli/curator-llm-factory-2gv6kt1v.js +71 -0
- package/dist/cli/{dispatch-591v13pp.js → dispatch-tmb929w8.js} +2 -2
- package/dist/cli/{evidence-summary-service-mw6tnsp9.js → evidence-summary-service-5th3zm9c.js} +17 -17
- package/dist/cli/{gate-evidence-jymaxb82.js → gate-evidence-py8v0w28.js} +9 -8
- package/dist/cli/guardrail-explain-qzhjhtyk.js +72 -0
- package/dist/cli/{guardrail-log-kzq2sktb.js → guardrail-log-rtdpk545.js} +9 -9
- package/dist/cli/{guardrail-reset-ea1g3g7d.js → guardrail-reset-s27fehv6.js} +40 -39
- package/dist/cli/{hive-promoter-ghjbgsms.js → hive-promoter-56mfysq6.js} +40 -39
- package/dist/cli/{index-52xdezsh.js → index-0jpv5z94.js} +5 -5
- package/dist/cli/{index-8hqkrd2j.js → index-1aex5zd9.js} +1 -1
- package/dist/cli/{index-6chhqq2n.js → index-251vrmge.js} +94 -3
- package/dist/cli/{index-w11whwn3.js → index-2c7eabkr.js} +9 -9
- package/dist/cli/{index-edm41q4j.js → index-2w0beg3s.js} +2 -2
- package/dist/cli/{index-nph5g4em.js → index-3a2xznmd.js} +2 -2
- package/dist/cli/{index-2y4kargd.js → index-3f1tr4ha.js} +4 -4
- package/dist/cli/{index-xezfx7ge.js → index-3nm1b3t2.js} +47 -13
- package/dist/cli/{index-gr8ppbq3.js → index-4c50tm9j.js} +3 -3
- package/dist/cli/{index-wf12wckd.js → index-4mgggb8x.js} +8 -3
- package/dist/cli/{index-17yszqs5.js → index-5bd84nxc.js} +1 -1
- package/dist/cli/{index-jx9f4ke1.js → index-6d4dc99f.js} +1 -1
- package/dist/cli/{index-79sr3fd1.js → index-75f9jj3h.js} +2 -2
- package/dist/cli/{index-ybcxvv17.js → index-87wqes24.js} +2 -2
- package/dist/cli/{index-bkd3450m.js → index-8cky7ksv.js} +36 -10
- package/dist/cli/{index-1zqf2pny.js → index-9wv6a3jx.js} +2 -2
- package/dist/cli/{index-fc1dqzjp.js → index-b7ra4hwj.js} +1 -1
- package/dist/cli/{index-6rff2h8x.js → index-bwvbj3fp.js} +4 -4
- package/dist/cli/{index-j29nkq5d.js → index-ch293kd3.js} +4 -4
- package/dist/cli/{index-gerkwyrr.js → index-d5gvr6z4.js} +1 -1
- package/dist/cli/{index-399nq780.js → index-dvhecfa9.js} +3839 -3182
- package/dist/cli/{index-hwj25q9r.js → index-f6zgz02s.js} +70 -53
- package/dist/cli/{index-3tyym20r.js → index-gbv76wf7.js} +2 -2
- package/dist/cli/{index-8fr2wn18.js → index-gk1w1h9k.js} +5 -1
- package/dist/cli/{index-13egyjgv.js → index-h92cqbqa.js} +5 -5
- package/dist/cli/{index-k5w284js.js → index-hfrb77tj.js} +17 -17
- package/dist/cli/{index-gy0nyfbw.js → index-m21x1bkx.js} +78 -25
- package/dist/cli/{index-hvk3zs62.js → index-m3tx1q9v.js} +1 -1
- package/dist/cli/{index-xwkn5hse.js → index-ms16gaa4.js} +7 -7
- package/dist/cli/{index-066xxa4q.js → index-qa7t42e7.js} +96 -31
- package/dist/cli/{index-2v17wb75.js → index-qd619x3r.js} +2 -2
- package/dist/cli/{index-axf65x87.js → index-qd726agw.js} +5 -5
- package/dist/cli/{index-c20gf089.js → index-qww0h314.js} +11 -6
- package/dist/cli/{index-aftapk6j.js → index-rk10x4zr.js} +3 -3
- package/dist/cli/{index-bfr392e7.js → index-sahf5yz3.js} +13 -6
- package/dist/cli/{index-a7rw4g4s.js → index-t8tw1bhs.js} +3 -3
- package/dist/cli/{index-0g5v4s0x.js → index-t9kjxzdr.js} +14 -14
- package/dist/cli/{index-zha9bdj9.js → index-tzq6t9wc.js} +11 -11
- package/dist/cli/index-vzm6zre7.js +167 -0
- package/dist/cli/{index-crjx4sx0.js → index-w50dk71y.js} +42 -41
- package/dist/cli/{index-yt25kpvz.js → index-xn16akay.js} +4 -4
- package/dist/cli/index.js +40 -39
- package/dist/cli/{knowledge-escalator-3pjed4tq.js → knowledge-escalator-stnveccq.js} +15 -14
- package/dist/cli/{knowledge-events-a5dwkg30.js → knowledge-events-ph6txm8h.js} +12 -12
- package/dist/cli/{knowledge-link-1zykfk1p.js → knowledge-link-w1fwqts4.js} +6 -4
- package/dist/cli/{knowledge-store-h85efsak.js → knowledge-store-tgyhr9nf.js} +10 -5
- package/dist/cli/{knowledge-validator-76qrf1ev.js → knowledge-validator-2g547bcn.js} +10 -7
- package/dist/cli/{pending-delegations-zgwfqfac.js → pending-delegations-873dtyw6.js} +14 -13
- package/dist/cli/{pr-subscriptions-fzy3ks1k.js → pr-subscriptions-xmxdrj1z.js} +20 -13
- package/dist/cli/{pr-workflow-gate-1gtsn7f0.js → pr-workflow-gate-h7644byf.js} +40 -39
- package/dist/cli/{runner-j3acybhs.js → runner-nmrjv7te.js} +8 -5
- package/dist/cli/{scan-cursor-ahwkxa8j.js → scan-cursor-tx72gxp0.js} +9 -6
- package/dist/cli/{schema-27sedbfk.js → schema-cpc92vq0.js} +4 -2
- package/dist/cli/{scope-persistence-q792yfxh.js → scope-persistence-xy3xs55f.js} +17 -15
- package/dist/cli/{skill-generator-g8wbjw6v.js → skill-generator-4f1v5zwx.js} +17 -16
- package/dist/cli/{snapshot-coordination-init-y6spakwm.js → snapshot-coordination-init-w2nw33pn.js} +40 -39
- package/dist/cli/{telemetry-9h1z2w9d.js → telemetry-ce0tx41h.js} +5 -1
- package/dist/cli/{workspace-snapshot-6z9rk7xw.js → workspace-snapshot-0kxj2grt.js} +11 -1
- package/dist/cli/{worktree-collision-ownership-pmpnftkd.js → worktree-collision-ownership-1g1qz8wc.js} +15 -14
- package/dist/cli/{worktree-isolation-4053n0px.js → worktree-isolation-jwxtrh3z.js} +40 -39
- package/dist/commands/close.d.ts +82 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/context-map/capsule-persistence.d.ts +13 -8
- package/dist/evidence/phase-participation.d.ts +1 -1
- package/dist/hooks/delegate-ack-collector.d.ts +7 -0
- package/dist/hooks/guardrails/execution-stall.d.ts +10 -5
- package/dist/hooks/hive-promoter.d.ts +28 -5
- package/dist/hooks/knowledge-store.d.ts +7 -0
- package/dist/hooks/repo-graph-builder.d.ts +14 -0
- package/dist/hooks/review-receipt.d.ts +29 -1
- package/dist/hooks/system-enhancer.d.ts +21 -0
- package/dist/index.js +578 -573
- package/dist/knowledge/curation-policy.d.ts +7 -0
- package/dist/memory/consolidation-log.d.ts +9 -0
- package/dist/memory/injector.d.ts +7 -0
- package/dist/memory/run-log.d.ts +8 -0
- package/dist/observability/catalog.d.ts +1 -1
- package/dist/retention/caps.d.ts +33 -0
- package/dist/retention/dir-prune.d.ts +72 -0
- package/dist/retention/jsonl-cap.d.ts +84 -0
- package/dist/retention/sweep.d.ts +67 -0
- package/dist/services/compaction-service.d.ts +14 -0
- package/dist/services/skill-changelog.d.ts +20 -0
- package/dist/session/snapshot-writer.d.ts +42 -0
- package/dist/summaries/manager.d.ts +41 -4
- package/dist/telemetry.d.ts +31 -1
- package/dist/test-impact/history-store.d.ts +16 -0
- package/dist/turbo/epic/calibration.d.ts +16 -1
- package/dist/turbo/epic/divergence-recorder.d.ts +11 -0
- package/dist/utils/atomic-write.d.ts +32 -0
- package/dist/utils/swarm-artifact-cache.d.ts +6 -0
- package/opencode-swarm.schema.json +15 -0
- package/package.json +1 -1
- package/dist/cli/curator-llm-factory-da5q3gg6.js +0 -70
- package/dist/cli/guardrail-explain-eva1yrtf.js +0 -71
- package/dist/cli/{index-2sqgbmtq.js → index-d5qf7hvh.js} +3 -3
|
@@ -85,6 +85,13 @@ export interface CohortQuorumConfig {
|
|
|
85
85
|
/** Minimum distinct cohort events (e.g. violations) required to authorize. */
|
|
86
86
|
minCohortEvidence: number;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Global FIFO cap on the curation-proposals audit stream (issue #2483 §2).
|
|
90
|
+
* Enforcement resolves the effective value through `resolveRetentionCap` so
|
|
91
|
+
* the #2483 acceptance checks can shrink the cap below this default and prove
|
|
92
|
+
* the writer clamps.
|
|
93
|
+
*/
|
|
94
|
+
export declare const MAX_CURATION_PROPOSALS = 200;
|
|
88
95
|
/**
|
|
89
96
|
* Authorize a destructive curation action against a single entry.
|
|
90
97
|
*
|
|
@@ -31,5 +31,14 @@ export interface ConsolidationLogRecord {
|
|
|
31
31
|
errored: number;
|
|
32
32
|
processedProposalIds: string[];
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Global FIFO cap on the consolidation log (issue #2483 §2). Enforcement and
|
|
36
|
+
* the bounded read both resolve the effective value through
|
|
37
|
+
* `resolveRetentionCap` so the #2483 acceptance checks can shrink the cap
|
|
38
|
+
* below this default and prove the writer clamps. The cap keeps last-N
|
|
39
|
+
* semantics: records are returned oldest-to-newest, so callers that
|
|
40
|
+
* `slice(-limit)` for the CLI's last-N view keep working.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_CONSOLIDATION_LOG_ENTRIES = 500;
|
|
34
43
|
export declare function readConsolidationLog(target: string | VettedMemoryRoot): Promise<ConsolidationLogRecord[]>;
|
|
35
44
|
export declare function appendConsolidationLog(target: string | VettedMemoryRoot, record: ConsolidationLogRecord): Promise<void>;
|
|
@@ -33,6 +33,13 @@ export interface MemoryLifecycleHooks {
|
|
|
33
33
|
toolAfter(input: unknown, output: unknown): Promise<void>;
|
|
34
34
|
}
|
|
35
35
|
export declare function createMemoryLifecycleHooks(options: MemoryLifecycleHookOptions): MemoryLifecycleHooks;
|
|
36
|
+
/**
|
|
37
|
+
* Global FIFO cap on the (env-gated) unitid-probe audit stream (issue #2483
|
|
38
|
+
* §2). Enforcement resolves the effective value through
|
|
39
|
+
* `resolveRetentionCap` so the #2483 acceptance checks can shrink the cap
|
|
40
|
+
* below this default and prove the writer clamps.
|
|
41
|
+
*/
|
|
42
|
+
export declare const MAX_UNITID_PROBE_ENTRIES = 2000;
|
|
36
43
|
declare function messagesContainRecall(messages: unknown[]): boolean;
|
|
37
44
|
declare function recallMessageInsertIndex(messages: unknown[]): number;
|
|
38
45
|
declare function compactText(text: string): string;
|
package/dist/memory/run-log.d.ts
CHANGED
|
@@ -20,5 +20,13 @@ export interface MemoryRunLogEvent {
|
|
|
20
20
|
memoriesDecayed?: number;
|
|
21
21
|
metadata?: Record<string, unknown>;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Global FIFO cap on each run's `memory.jsonl` run log (issue #2483 §2).
|
|
25
|
+
* Enforcement resolves the effective value through `resolveRetentionCap` so
|
|
26
|
+
* the #2483 acceptance checks can shrink the cap below this default and
|
|
27
|
+
* prove the writer clamps. Keyspace (run directories) is handled by the
|
|
28
|
+
* retention sweep and close lifecycle.
|
|
29
|
+
*/
|
|
30
|
+
export declare const MAX_RUN_LOG_ENTRIES = 2000;
|
|
23
31
|
export declare function appendMemoryRunLog(directory: string, runId: string | undefined, event: MemoryRunLogEvent): Promise<void>;
|
|
24
32
|
export declare function sanitizeRunId(runId: string | undefined): string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The event catalog (issue #2029).
|
|
3
3
|
*
|
|
4
4
|
* Exactly 61 entries, matching the `TelemetryEvent` union at
|
|
5
|
-
* `src/telemetry.ts:
|
|
5
|
+
* `src/telemetry.ts:20-172`. 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
|
|
8
8
|
* structured close/archive result event), the 41st is the bounded diagnostic
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-visible cap seam for every #2483 retention bound (issue #2483 §0).
|
|
3
|
+
*
|
|
4
|
+
* Writers keep their cap constants exported on their own modules (registry
|
|
5
|
+
* citations point there), but ENFORCEMENT must resolve the effective value
|
|
6
|
+
* through {@link resolveRetentionCap} so tests and acceptance checks can
|
|
7
|
+
* shrink a cap below its production default and prove the writer actually
|
|
8
|
+
* clamps. A writer that reads only its own constant is unfalsifiable at cap+1
|
|
9
|
+
* test widths that are impractical to write (10 000 entries / 8 MiB).
|
|
10
|
+
*
|
|
11
|
+
* Pure module: no filesystem, no imports — safe on the init path and under
|
|
12
|
+
* both the Bun and Node hosts (AGENTS.md invariant 2).
|
|
13
|
+
*/
|
|
14
|
+
/** Canonical cap names (frozen by the #2483 acceptance checks). */
|
|
15
|
+
export type RetentionCapName = 'MAX_RETRACTION_RECORDS' | 'MAX_UNACKNOWLEDGED_CRITICALS' | 'MAX_CURATION_PROPOSALS' | 'MAX_CONSOLIDATION_LOG_ENTRIES' | 'MAX_CONTEXT_SNAPSHOT_BYTES' | 'MAX_CONTEXT_SNAPSHOT_ENTRIES' | 'MAX_CALIBRATION_MODULES' | 'MAX_DIVERGENCE_BYTES' | 'MAX_TEST_HISTORY_ENTRIES' | 'MAX_TEST_HISTORY_KEYS' | 'MAX_SKILL_CHANGELOG_ENTRIES_PER_SKILL' | 'MAX_SKILL_CHANGELOG_GLOBAL_ENTRIES' | 'MAX_RECEIPTS_READ' | 'MAX_CAPSULES_LISTED' | 'MAX_SUMMARIES_LISTED' | 'MAX_RUN_LOG_ENTRIES' | 'MAX_UNITID_PROBE_ENTRIES';
|
|
16
|
+
/**
|
|
17
|
+
* Install test cap overrides (partial). Production code never calls this;
|
|
18
|
+
* tests and the #2483 acceptance checks do. Values are validated to be
|
|
19
|
+
* positive finite numbers so a bad fixture fails loudly at install time.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setRetentionCapOverrides(partial: Partial<Record<RetentionCapName, number>>): void;
|
|
22
|
+
/** Clear every override (test teardown). */
|
|
23
|
+
export declare function clearRetentionCapOverrides(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the effective cap for {@link name}: the installed override when
|
|
26
|
+
* present, otherwise {@link defaultValue}. Every capped writer routes its
|
|
27
|
+
* enforcement through this function.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveRetentionCap(name: RetentionCapName, defaultValue: number): number;
|
|
30
|
+
/** Test/inspection seam. */
|
|
31
|
+
export declare const _internals: {
|
|
32
|
+
overrides: Map<string, number>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Age/count directory pruner (issue #2483 §1).
|
|
3
|
+
*
|
|
4
|
+
* Deletes the OLDEST entries (mtime ascending, deterministic code-unit name
|
|
5
|
+
* tie-break — never localeCompare) until the directory is within its caps.
|
|
6
|
+
*
|
|
7
|
+
* A DIRECTORY entry is aged by its CONTENT, not by the directory node's own
|
|
8
|
+
* mtime: the effective mtime is the newest regular-file mtime anywhere in the
|
|
9
|
+
* subtree (bounded, symlink-refusing). A directory's own mtime only reflects
|
|
10
|
+
* metadata churn (entry creates/deletes/renames — e.g. another pruner
|
|
11
|
+
* deleting a sibling refreshes the parent), so content age is the honest
|
|
12
|
+
* "last write activity" signal. An empty subtree falls back to the
|
|
13
|
+
* directory's own mtime; an unreadable or scan-budget-exhausted subtree is
|
|
14
|
+
* treated as unprunable (effective mtime +Infinity — never a victim).
|
|
15
|
+
*
|
|
16
|
+
* Containment properties (issue #2483 edge cases):
|
|
17
|
+
* - symlink/junction entries are NEVER traversed or deleted (lstat check —
|
|
18
|
+
* the #2127 marker-symlink precedent), including inside subtrees;
|
|
19
|
+
* - entries with future mtimes (clock skew) are NEVER pruned;
|
|
20
|
+
* - enumeration is bounded (`maxScan`, per directory level AND per subtree
|
|
21
|
+
* walk) so a huge directory cannot turn the pruner into an unbounded scan
|
|
22
|
+
* on a cold Windows filesystem;
|
|
23
|
+
* - per-entry failures are swallowed (fail-open): one unreadable entry
|
|
24
|
+
* never aborts the family.
|
|
25
|
+
*/
|
|
26
|
+
export interface PruneDirectoryOptions {
|
|
27
|
+
/** Keep at most this many entries (newest by mtime). */
|
|
28
|
+
maxEntries?: number;
|
|
29
|
+
/** Delete entries strictly older than this (ms before `now`). */
|
|
30
|
+
maxAgeMs?: number;
|
|
31
|
+
/** Clock injection point for tests/checks. */
|
|
32
|
+
now?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Enumeration bound (default 100000 — review FB-12: 20000 stat ops could
|
|
35
|
+
* exhaust before a legitimately large keyspace finished enumerating,
|
|
36
|
+
* silently skipping stale tail entries; the budget is stats, not
|
|
37
|
+
* deletions, so the higher ceiling stays bounded).
|
|
38
|
+
*/
|
|
39
|
+
maxScan?: number;
|
|
40
|
+
/** Count victims without deleting (blast-radius rehearsal / dry_run). */
|
|
41
|
+
dryRun?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface PruneCandidate {
|
|
44
|
+
name: string;
|
|
45
|
+
mtimeMs: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Effective mtime of a subtree: the newest regular-file mtime found inside
|
|
49
|
+
* (never traversing symlinks), or `null` when the subtree contains no files.
|
|
50
|
+
* `POSITIVE_INFINITY` signals "could not verify within budget" — callers must
|
|
51
|
+
* treat that as unprunable. The walk is bounded by `maxScan` stat operations
|
|
52
|
+
* total, so a pathological subtree degrades to keep, never to an unbounded
|
|
53
|
+
* scan.
|
|
54
|
+
*/
|
|
55
|
+
export declare function subtreeNewestFileMtime(root: string, maxScan: number): number | null;
|
|
56
|
+
/**
|
|
57
|
+
* Per-subtree walk budget. Deliberately NOT the caller's `maxScan`: the
|
|
58
|
+
* top-level enumeration bound must not multiply into every child subtree.
|
|
59
|
+
* Run/batch/candidate directories hold a handful of files each; 2000 stat
|
|
60
|
+
* operations per subtree is generous, and exhausting it degrades to keep.
|
|
61
|
+
*/
|
|
62
|
+
export declare const SUBTREE_SCAN_CAP = 2000;
|
|
63
|
+
/** List bounded entries with effective mtimes; symlinked entries are collected separately and refused. */
|
|
64
|
+
export declare function listPruneCandidates(dir: string, maxScan: number): {
|
|
65
|
+
candidates: PruneCandidate[];
|
|
66
|
+
refusedSymlinks: string[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Prune `dir` down to its caps. Returns the number of entries deleted. A
|
|
70
|
+
* missing directory is a no-op (0). `dryRun` counts without deleting.
|
|
71
|
+
*/
|
|
72
|
+
export declare function pruneDirectory(dir: string, opts: PruneDirectoryOptions): Promise<number>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capped JSONL append + bounded tail read (issue #2483 §1).
|
|
3
|
+
*
|
|
4
|
+
* `appendCappedJsonl` enforces the entry/byte caps on EVERY completed state:
|
|
5
|
+
* when an append would push the file past its caps, the compacted prefix
|
|
6
|
+
* (newest whole records that fit, minus the incoming record) is first
|
|
7
|
+
* swapped in crash-atomically (temp file + rename with a Windows
|
|
8
|
+
* transient-error retry), then the new record is appended — so an
|
|
9
|
+
* interleaved reader never observes more than the caps. Compaction has a
|
|
10
|
+
* whole-record floor: a non-empty stream is never emptied by compaction —
|
|
11
|
+
* at least the newest single record always survives, whatever the effective
|
|
12
|
+
* cap (the #2483 acceptance contract).
|
|
13
|
+
*
|
|
14
|
+
* Concurrency: all calls for the same `filePath` serialize through a
|
|
15
|
+
* per-file async mutex, so in-process concurrent appenders can never lose a
|
|
16
|
+
* line to the compaction rename (review finding FB-4). Cross-process
|
|
17
|
+
* writers remain best-effort by the documented fail-open contract (audit
|
|
18
|
+
* streams; the one lockfile-guarded writer predates this module).
|
|
19
|
+
*
|
|
20
|
+
* `readTailJsonl` reads only the last `maxBytes` bytes (newline-aligned) and
|
|
21
|
+
* parses at most `maxEntries` records from the end, so every reader that
|
|
22
|
+
* routes through it is O(cap), not O(history). `readTailJsonlDetailed`
|
|
23
|
+
* additionally reports whether a torn trailing line was skipped, for
|
|
24
|
+
* callers whose conservative decisions depend on tail completeness.
|
|
25
|
+
*
|
|
26
|
+
* Plumbing module: callers own their streams (retention-registry exemption;
|
|
27
|
+
* callers' rows carry the citations).
|
|
28
|
+
*/
|
|
29
|
+
export interface CappedJsonlOptions {
|
|
30
|
+
maxEntries: number;
|
|
31
|
+
/** Optional byte cap for the compacted file. */
|
|
32
|
+
maxBytes?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Invoked when compaction drops `droppedCount` oldest records to enforce
|
|
35
|
+
* the caps. Lets stream owners surface truncation (review finding FB-5)
|
|
36
|
+
* instead of failing silently; failures thrown here are swallowed.
|
|
37
|
+
*/
|
|
38
|
+
onPrune?: (droppedCount: number) => void;
|
|
39
|
+
}
|
|
40
|
+
declare function withFileLock<T>(filePath: string, task: () => Promise<T>): Promise<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Test/inspection seam (review round 2): exposes the mutex so the
|
|
43
|
+
* concurrent-append test can assert the chain fully drains instead of
|
|
44
|
+
* reasoning about the self-deletion logic.
|
|
45
|
+
*/
|
|
46
|
+
export declare const _internals: {
|
|
47
|
+
appendChains: Map<string, Promise<void>>;
|
|
48
|
+
withFileLock: typeof withFileLock;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Append `line` (a bare JSON record without the trailing newline) to
|
|
52
|
+
* `filePath`, enforcing the caps with a crash-atomic compaction when
|
|
53
|
+
* exceeded. Calls for the same file serialize through a per-file async
|
|
54
|
+
* mutex (in-process writers never lose lines to the compaction rename);
|
|
55
|
+
* the compaction rewrite is temp+rename, so a crash mid-compaction leaves
|
|
56
|
+
* either the old complete file or the new complete file — never a torn one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function appendCappedJsonl(filePath: string, line: string, opts: CappedJsonlOptions): Promise<void>;
|
|
59
|
+
export interface TailJsonlOptions {
|
|
60
|
+
maxEntries?: number;
|
|
61
|
+
maxBytes?: number;
|
|
62
|
+
}
|
|
63
|
+
export interface TailJsonlResult<T> {
|
|
64
|
+
records: T[];
|
|
65
|
+
/**
|
|
66
|
+
* True when a torn (unterminated) trailing line was skipped by the
|
|
67
|
+
* newline-alignment or whole-line filter. Conservative callers (e.g.
|
|
68
|
+
* evolution terminal detection) treat this as "state uncertain".
|
|
69
|
+
*/
|
|
70
|
+
tailTruncated: boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Bounded tail read: returns at most `maxEntries` records parsed from the
|
|
74
|
+
* end of the file, reading no more than `maxBytes` bytes. Malformed lines
|
|
75
|
+
* are skipped (they can be a torn final write); a missing file is `[]`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readTailJsonl<T>(filePath: string, opts: TailJsonlOptions): Promise<T[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Bounded tail read that also reports whether a torn trailing line was
|
|
80
|
+
* skipped, so conservative callers can distinguish "no record" from
|
|
81
|
+
* "state uncertain" (review FB-11).
|
|
82
|
+
*/
|
|
83
|
+
export declare function readTailJsonlDetailed<T>(filePath: string, opts: TailJsonlOptions): Promise<TailJsonlResult<T>>;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One bounded, fail-open retention sweep over the residual keyspace families
|
|
3
|
+
* under `<projectRoot>/.swarm/` (issue #2483 §3).
|
|
4
|
+
*
|
|
5
|
+
* Production triggers: the wrapper-owned post-resolution task registered in
|
|
6
|
+
* `src/index.ts` (withTimeout-bounded, off the `server()`-resolution path —
|
|
7
|
+
* AGENTS.md invariant 1) and one pass before the `/swarm close` clean stage.
|
|
8
|
+
*
|
|
9
|
+
* Safety properties (tested in tests/unit/retention/sweep-2483.test.ts and
|
|
10
|
+
* edge-cases-2483.test.ts):
|
|
11
|
+
* - only ever touches paths under `<projectRoot>/.swarm/`; nothing outside
|
|
12
|
+
* survives-or-dies by accident (containment asserted by test);
|
|
13
|
+
* - fail-open per family — one family's error never aborts the others and
|
|
14
|
+
* never throws to the caller;
|
|
15
|
+
* - future-mtime entries are never pruned; symlinked entries are never
|
|
16
|
+
* traversed (dir-prune guards);
|
|
17
|
+
* - `dryRun` reports what WOULD be pruned without deleting (the blast-
|
|
18
|
+
* radius rehearsal mode; also the `retention.dry_run` config surface);
|
|
19
|
+
* - the summaries family honors `summaries.retention_days` (default 7) by
|
|
20
|
+
* delegating to `cleanupSummaries` (content-timestamp with mtime
|
|
21
|
+
* fallback), which this sweep is the first production caller of — the
|
|
22
|
+
* previously-dead `retention_days` setting becomes live.
|
|
23
|
+
* - authoritative streams (plan ledger, knowledge store, council, evidence,
|
|
24
|
+
* scopes, swarm.db, telemetry) are NOT in any family and are never
|
|
25
|
+
* touched (negative test).
|
|
26
|
+
*/
|
|
27
|
+
/** Default age horizon for keyspace families (30 days). */
|
|
28
|
+
export declare const DEFAULT_FAMILY_AGE_DAYS = 30;
|
|
29
|
+
/** Summaries horizon comes from summaries.retention_days (default 7). */
|
|
30
|
+
export declare const DEFAULT_SUMMARIES_RETENTION_DAYS = 7;
|
|
31
|
+
/** Age-only backstop for evolution candidates whose state is unreadable. */
|
|
32
|
+
export declare const EVOLUTION_BACKSTOP_AGE_DAYS = 90;
|
|
33
|
+
/** Keep-newest caps layered on top of age for high-churn run/batch dirs. */
|
|
34
|
+
export declare const PR_REVIEW_KEEP_NEWEST_RUNS = 50;
|
|
35
|
+
export declare const LANE_RESULTS_KEEP_NEWEST_BATCHES = 100;
|
|
36
|
+
export declare const REVIEW_RECEIPTS_KEEP_NEWEST = 1000;
|
|
37
|
+
export interface RetentionSweepOptions {
|
|
38
|
+
/** Clock injection point for tests/checks. */
|
|
39
|
+
now?: number;
|
|
40
|
+
/** Count-and-report without deleting (blast-radius rehearsal / config). */
|
|
41
|
+
dryRun?: boolean;
|
|
42
|
+
/** Honored from `retention.enabled`; when false the sweep is a no-op. */
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
/** Honored from `summaries.retention_days`. */
|
|
45
|
+
summariesRetentionDays?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Cooperative stop token, polled between families/passes (review FB-10).
|
|
48
|
+
* The post-init scheduler flips it when the outer `withTimeout` budget
|
|
49
|
+
* expires so an in-flight sweep abandons the remaining families instead
|
|
50
|
+
* of finishing its full pass after the awaiter already moved on.
|
|
51
|
+
*/
|
|
52
|
+
shouldContinue?: () => boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface RetentionSweepResult {
|
|
55
|
+
pruned: Record<string, number>;
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
dryRun?: boolean;
|
|
58
|
+
errors: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Prune stale keyspace families under `.swarm/`, age-delete the rebuildable
|
|
62
|
+
* epic diagnostics, expire legacy `.imported` doc-drift cold archives, run
|
|
63
|
+
* the summaries retention pass, and prune evolution candidates that reached
|
|
64
|
+
* a terminal lifecycle state (with an age-only backstop). Every family is
|
|
65
|
+
* individually fail-open; the function itself never throws.
|
|
66
|
+
*/
|
|
67
|
+
export declare function runRetentionSweep(projectRoot: string, options?: RetentionSweepOptions): Promise<RetentionSweepResult>;
|
|
@@ -20,6 +20,20 @@
|
|
|
20
20
|
import type { CompactionConfig } from '../config/schema';
|
|
21
21
|
export type { CompactionConfig };
|
|
22
22
|
export declare const MAX_TRACKED_COMPACTION_SESSIONS = 500;
|
|
23
|
+
/**
|
|
24
|
+
* Global byte cap on `.swarm/context-snapshot.md` (issue #2483 §2). Enforced
|
|
25
|
+
* via `appendCappedJsonl` with a high entry count so only the byte budget
|
|
26
|
+
* binds; compaction keeps the newest whole records that fit and never empties
|
|
27
|
+
* a non-empty snapshot (whole-record floor ≥ 1 entry).
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_CONTEXT_SNAPSHOT_BYTES = 65536;
|
|
30
|
+
/**
|
|
31
|
+
* Whole-record ceiling for the snapshot file (review FB-8). Only the byte
|
|
32
|
+
* budget binds in practice; this entry cap is a pure defense-in-depth bound
|
|
33
|
+
* and resolves through the seam like every other #2483 cap so tests can
|
|
34
|
+
* shrink it.
|
|
35
|
+
*/
|
|
36
|
+
export declare const MAX_CONTEXT_SNAPSHOT_ENTRIES = 100000;
|
|
23
37
|
export interface CompactionServiceHook {
|
|
24
38
|
toolAfter: (input: {
|
|
25
39
|
tool: string;
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-skill FIFO cap (issue #2483 §2). Enforcement routes the append through
|
|
3
|
+
* `appendCappedJsonl`, so the trim is crash-atomic (temp + rename), and
|
|
4
|
+
* resolves the effective value through `resolveRetentionCap` so the #2483
|
|
5
|
+
* acceptance checks can shrink the cap below this default and prove the
|
|
6
|
+
* writer clamps.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_SKILL_CHANGELOG_ENTRIES_PER_SKILL = 200;
|
|
9
|
+
/**
|
|
10
|
+
* Legacy alias for {@link MAX_SKILL_CHANGELOG_ENTRIES_PER_SKILL} — retained
|
|
11
|
+
* for existing citations (`scripts/retention-registry.data.ts`) and tests.
|
|
12
|
+
*/
|
|
1
13
|
export declare const MAX_CHANGELOG_ENTRIES_PER_SKILL = 200;
|
|
14
|
+
/**
|
|
15
|
+
* Global ceiling on TOTAL changelog entries across every per-skill file
|
|
16
|
+
* (issue #2483 §2). Enforced on EVERY append (critic N3 — deliberately no
|
|
17
|
+
* cadence) by scanning the changelog directory; the scan is bounded by the
|
|
18
|
+
* ceiling itself (one entry per slug ⇒ at most `cap` files / `cap` entries
|
|
19
|
+
* to enumerate). Appends are rare, so the per-append scan cost is accepted.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_SKILL_CHANGELOG_GLOBAL_ENTRIES = 10000;
|
|
2
22
|
export interface SkillChangelogEntry {
|
|
3
23
|
version: number;
|
|
4
24
|
timestamp: string;
|
|
@@ -104,6 +104,48 @@ export interface SerializedAgentSession {
|
|
|
104
104
|
/** Provider id paired with lastObservedModel (#1896). Omitted when undefined. */
|
|
105
105
|
lastObservedProviderID?: string;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Compile-time snapshot-field parity map (issue #2472 W6 / AC-7; frozen check C7).
|
|
109
|
+
*
|
|
110
|
+
* WHY THIS MAP EXISTS — the serializer reads FROM `AgentSessionState` and
|
|
111
|
+
* writes TO `SerializedAgentSession`, so the drift-prone direction is a field
|
|
112
|
+
* that exists on the live state but silently never reaches the snapshot:
|
|
113
|
+
* nothing fails, the field is just lost on every restart. The
|
|
114
|
+
* `Record<Exclude<keyof AgentSessionState, keyof SerializedAgentSession>, string>`
|
|
115
|
+
* key type makes that direction exhaustive at compile time — EVERY state field
|
|
116
|
+
* that is not a serialized field MUST appear here with a one-line rationale,
|
|
117
|
+
* and a key that is NOT such a field is an excess-property type error. Adding
|
|
118
|
+
* a field to either interface without reconciling this map fails
|
|
119
|
+
* `bun run typecheck` (CI gate).
|
|
120
|
+
*
|
|
121
|
+
* The serialized-only direction (a `SerializedAgentSession` field with no
|
|
122
|
+
* `AgentSessionState` counterpart) is guarded separately by the
|
|
123
|
+
* `_serializedFieldsExistOnState` assertion below. The asymmetry is
|
|
124
|
+
* deliberate: the serializer can only *drop* state fields, while the reader
|
|
125
|
+
* could otherwise *invent* fields the live state does not have.
|
|
126
|
+
*
|
|
127
|
+
* This is NOT the same list as `TRANSIENT_SESSION_FIELDS` in
|
|
128
|
+
* `src/session/snapshot-reader.ts` (fields RESET on rehydration — a subset of
|
|
129
|
+
* the *serialized* fields). This map lists fields that are NEVER serialized.
|
|
130
|
+
* Most are genuinely process-local; `workspaceDirectory` is deliberately
|
|
131
|
+
* unserializable for trust reasons (see its TRUST BOUNDARY comment in
|
|
132
|
+
* `src/state.ts`), which is exactly why it must carry an entry here rather
|
|
133
|
+
* than fall out of the type silently.
|
|
134
|
+
*
|
|
135
|
+
* The runtime mirror of this guard lives at
|
|
136
|
+
* `tests/unit/session/snapshot-field-parity-guard.test.ts` (what frozen
|
|
137
|
+
* check C7 runs).
|
|
138
|
+
*/
|
|
139
|
+
export declare const SESSION_TRANSIENT_FIELDS: Readonly<Record<Exclude<keyof AgentSessionState, keyof SerializedAgentSession>, string>>;
|
|
140
|
+
/**
|
|
141
|
+
* Reverse-direction parity guard: every `SerializedAgentSession` field must
|
|
142
|
+
* exist on `AgentSessionState`. The const assignment is what makes tsc
|
|
143
|
+
* actually enforce the conditional type — when a serialized field has no
|
|
144
|
+
* state counterpart, the type degrades to the error tuple below and assigning
|
|
145
|
+
* `true` fails `bun run typecheck` with that message.
|
|
146
|
+
*/
|
|
147
|
+
export type SerializedFieldsAllExistOnState = Exclude<keyof SerializedAgentSession, keyof AgentSessionState> extends never ? true : ['SerializedAgentSession field missing from AgentSessionState'];
|
|
148
|
+
export declare const _serializedFieldsExistOnState: SerializedFieldsAllExistOnState;
|
|
107
149
|
/**
|
|
108
150
|
* Minimal interface for serialized InvocationWindow
|
|
109
151
|
*/
|
|
@@ -17,13 +17,50 @@ export declare function storeSummary(directory: string, id: string, fullOutput:
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function loadFullOutput(directory: string, id: string): Promise<string | null>;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Listing cap for `.swarm/summaries/` enumeration (issue #2483 §2, R12): the
|
|
21
|
+
* public list returns NEWEST-first (mtime descending, code-unit filename
|
|
22
|
+
* tie-break — never localeCompare) and is capped, so every listing read is
|
|
23
|
+
* O(cap), not O(unbounded summary history). The effective value resolves
|
|
24
|
+
* through `resolveRetentionCap` so the #2483 acceptance checks can shrink it.
|
|
25
|
+
* Retention cleanup does NOT go through this capped view — it enumerates the
|
|
26
|
+
* directory directly so stale files beyond the newest N are still pruned.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MAX_SUMMARIES_LISTED = 500;
|
|
29
|
+
/**
|
|
30
|
+
* List summary IDs that have summary entries, newest-first (mtime
|
|
31
|
+
* descending, code-unit filename tie-break), capped at
|
|
32
|
+
* {@link MAX_SUMMARIES_LISTED} entries. Returns an empty array if the
|
|
33
|
+
* summaries directory doesn't exist.
|
|
23
34
|
*/
|
|
24
35
|
export declare function listSummaries(directory: string): Promise<string[]>;
|
|
36
|
+
/**
|
|
37
|
+
* List summary IDs past the retention horizon WITHOUT deleting them (review
|
|
38
|
+
* FB-9: the single detection predicate shared by `cleanupSummaries` and the
|
|
39
|
+
* sweep's dry-run counter, so rehearsal and deletion can never diverge).
|
|
40
|
+
*
|
|
41
|
+
* Retention cleanup enumerates the directory DIRECTLY (uncapped): the
|
|
42
|
+
* public listSummaries is capped to the newest MAX_SUMMARIES_LISTED, and
|
|
43
|
+
* routing cleanup through it would skip stale files beyond that window
|
|
44
|
+
* (issue #2483).
|
|
45
|
+
*
|
|
46
|
+
* Retention also enumerates LENIENTLY: `enumerateSummaryIds` applies the
|
|
47
|
+
* strict write-side `^S\d+$` id grammar, but retention's job is to bound
|
|
48
|
+
* the keyspace — ANY `S*.json` occupant past the horizon is stale
|
|
49
|
+
* regardless of whether its id would pass write validation (legacy or
|
|
50
|
+
* foreign producers included).
|
|
51
|
+
*
|
|
52
|
+
* Detection is content-timestamp-first: a numeric `timestamp` older than the
|
|
53
|
+
* cutoff marks the entry stale; a missing/unparsable timestamp falls back to
|
|
54
|
+
* the file mtime (issue #2483); unreadable content or a parse failure is
|
|
55
|
+
* never stale (a `warn` is emitted for the latter, as deletion does).
|
|
56
|
+
*/
|
|
57
|
+
export declare function listStaleSummaryIds(directory: string, retentionDays: number, options?: {
|
|
58
|
+
now?: number;
|
|
59
|
+
}): Promise<string[]>;
|
|
25
60
|
/**
|
|
26
61
|
* Delete summaries older than retentionDays.
|
|
27
62
|
* Returns array of deleted summary IDs.
|
|
28
63
|
*/
|
|
29
|
-
export declare function cleanupSummaries(directory: string, retentionDays: number
|
|
64
|
+
export declare function cleanupSummaries(directory: string, retentionDays: number, options?: {
|
|
65
|
+
now?: number;
|
|
66
|
+
}): Promise<string[]>;
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -66,7 +66,37 @@ export declare function resetTelemetryForTesting(): void;
|
|
|
66
66
|
/**
|
|
67
67
|
* Initialize telemetry with the project directory.
|
|
68
68
|
* Creates `.swarm/` if it doesn't exist and opens `telemetry.jsonl` for appending.
|
|
69
|
-
* Idempotent — calling multiple times
|
|
69
|
+
* Idempotent — calling multiple times with the SAME directory has no effect
|
|
70
|
+
* after the first successful call.
|
|
71
|
+
*
|
|
72
|
+
* Re-home (issue #2472 W9): when called with a projectDirectory DIFFERENT
|
|
73
|
+
* from the latched `_projectDirectory` while a stream exists (a second
|
|
74
|
+
* server() instance initializing in this process for another project root),
|
|
75
|
+
* telemetry re-initializes for the new directory exactly as a fresh init
|
|
76
|
+
* does — the previous behavior latched the first directory forever, so the
|
|
77
|
+
* second project's `.swarm/telemetry.jsonl` was never created. A disabled
|
|
78
|
+
* telemetry state stays disabled.
|
|
79
|
+
*
|
|
80
|
+
* No-drop ordering + fail-open ownership retention (PR #2588, review finding
|
|
81
|
+
* 6 / PRR-019): the NEW stream is created and published FIRST, and only then
|
|
82
|
+
* is the old stream flushed/closed best-effort. The previous
|
|
83
|
+
* close-then-create order nulled `_writeStream` before the replacement
|
|
84
|
+
* existed, so any emit racing the re-home was dropped by the
|
|
85
|
+
* `_writeStream === null` guard, and a new-stream creation FAILURE took the
|
|
86
|
+
* whole process's telemetry down (`_disabled = true`) after the old stream
|
|
87
|
+
* was already gone. On creation failure the OLD stream is now retained
|
|
88
|
+
* (logged non-fatally, no re-home) — events keep flowing to the previous
|
|
89
|
+
* directory until a later successful init.
|
|
90
|
+
*
|
|
91
|
+
* OWNERSHIP CONTRACT (last-init-wins): the newest successful initTelemetry
|
|
92
|
+
* call owns THE single live stream for the whole process. In a
|
|
93
|
+
* multi-project process, a project initialized earlier SHARES the latest
|
|
94
|
+
* stream until it re-initializes; per-event project routing is out of scope
|
|
95
|
+
* — the issue #2472 obligation is directory-CHANGE detection, not per-event
|
|
96
|
+
* fan-out. Same-directory detection uses canonical project-root identity
|
|
97
|
+
* (`sameProjectRoot`), so Windows case differences and symlink/junction
|
|
98
|
+
* spellings of one root do not churn the stream.
|
|
99
|
+
*
|
|
70
100
|
* @param projectDirectory - Absolute path to the project root
|
|
71
101
|
*/
|
|
72
102
|
export declare function initTelemetry(projectDirectory: string): void;
|
|
@@ -11,6 +11,22 @@ export interface TestRunRecord {
|
|
|
11
11
|
stackPrefix?: string;
|
|
12
12
|
changedFiles: string[];
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Global cap on TOTAL entries in `.swarm/cache/test-history.jsonl` (issue
|
|
16
|
+
* #2483 §2). Enforced inside the existing read-prune-write pass on EVERY
|
|
17
|
+
* append — deliberately no amortized cadence (critic N3): a cadence like
|
|
18
|
+
* every-512-appends would never fire inside a shrunken test override width
|
|
19
|
+
* and silently fail the #2483 acceptance check. The effective value resolves
|
|
20
|
+
* through `resolveRetentionCap`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const MAX_TEST_HISTORY_ENTRIES = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Global cap on DISTINCT (testFile, testName) keys in the history file
|
|
25
|
+
* (issue #2483 §2): when distinct keys exceed the cap, the oldest-key records
|
|
26
|
+
* are dropped entirely (keyspace prune). Enforced on every append alongside
|
|
27
|
+
* {@link MAX_TEST_HISTORY_ENTRIES}.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_TEST_HISTORY_KEYS = 1000;
|
|
14
30
|
export declare function batchAppendTestRuns(records: TestRunRecord[], workingDir?: string): void;
|
|
15
31
|
export declare function appendTestRun(record: TestRunRecord, workingDir?: string): void;
|
|
16
32
|
export declare function getTestHistory(testFile: string, workingDir?: string): TestRunRecord[];
|
|
@@ -27,7 +27,11 @@ export interface CalibrationState {
|
|
|
27
27
|
* Modules promoted to the hot-module list by observed divergence.
|
|
28
28
|
* Monotonically grows — never auto-shrinks (loosening the hot-module
|
|
29
29
|
* list requires manual intervention; the calibration loop only adds).
|
|
30
|
-
* Sorted lexicographically for stable diffs.
|
|
30
|
+
* Sorted lexicographically for stable diffs. Bounded at
|
|
31
|
+
* {@link MAX_CALIBRATION_MODULES} on every save and load (issue #2483):
|
|
32
|
+
* once the cap binds, late-alphabet module paths are PERMANENTLY
|
|
33
|
+
* excluded from the list and — because additions are monotonic — can
|
|
34
|
+
* never re-enter (critic N5).
|
|
31
35
|
*/
|
|
32
36
|
hotModuleAdditions: string[];
|
|
33
37
|
/**
|
|
@@ -42,6 +46,17 @@ export interface CalibrationState {
|
|
|
42
46
|
/** Number of divergence records processed by the engine so far. */
|
|
43
47
|
processedRecords: number;
|
|
44
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Global cap on the persisted `hotModuleAdditions` list (issue #2483 §2).
|
|
51
|
+
* Enforcement (save AND load) keeps the lexicographically SMALLEST prefix of
|
|
52
|
+
* the sorted list — deterministic and order-respecting, with no invented
|
|
53
|
+
* score. N5 bias, stated plainly: once the cap binds, late-alphabet module
|
|
54
|
+
* paths are permanently excluded from the list and — because additions are
|
|
55
|
+
* monotonic — can never re-enter. The effective value resolves through
|
|
56
|
+
* `resolveRetentionCap` so the #2483 acceptance checks can shrink the cap
|
|
57
|
+
* below this default and prove the truncation clamps.
|
|
58
|
+
*/
|
|
59
|
+
export declare const MAX_CALIBRATION_MODULES = 500;
|
|
45
60
|
export declare function emptyCalibrationState(): CalibrationState;
|
|
46
61
|
export declare function isCalibrationStateUnreadable(directory: string): boolean;
|
|
47
62
|
export declare function repairCalibrationUnreadable(directory: string): void;
|
|
@@ -40,6 +40,17 @@ export interface DivergenceRecord {
|
|
|
40
40
|
/** True when divergenceRatio === 0 (no undeclared writes). */
|
|
41
41
|
isClean: boolean;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Global byte cap on `.swarm/epic/divergence.jsonl` (issue #2483 §2).
|
|
45
|
+
* Byte-cap contract (critic N2): compaction retains the newest WHOLE records
|
|
46
|
+
* that fit within the effective cap, with a floor of at least one record — a
|
|
47
|
+
* non-empty stream is never emptied by compaction (one record ≈ 320 B; under
|
|
48
|
+
* any smaller effective cap the file keeps the newest single record). The
|
|
49
|
+
* effective value resolves through `resolveRetentionCap` so the #2483
|
|
50
|
+
* acceptance checks can shrink the cap below this default and prove the
|
|
51
|
+
* writer clamps.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_DIVERGENCE_BYTES: number;
|
|
43
54
|
/**
|
|
44
55
|
* Compute the divergence between a declared scope and the files actually
|
|
45
56
|
* modified. Pure — no I/O, no side effects. Returns the diff sets plus the
|
|
@@ -128,6 +128,38 @@ export declare function assertWellFormedTargetPath(targetPath: string): void;
|
|
|
128
128
|
export declare function assertSwarmContainedTarget(targetPath: string): SwarmContainment;
|
|
129
129
|
/** Hard bound on a single atomic write (bounded-write requirement). */
|
|
130
130
|
export declare const MAX_ATOMIC_WRITE_BYTES: number;
|
|
131
|
+
/**
|
|
132
|
+
* Default window within which an ENOENT read of a target this process wrote
|
|
133
|
+
* (or is writing) is treated as plausibly mid-rename rather than never-existed
|
|
134
|
+
* — see `wasRecentlyWrittenByThisProcess` and the three-way ENOENT policy in
|
|
135
|
+
* `readSwarmFileAsync` (src/hooks/utils.ts).
|
|
136
|
+
*/
|
|
137
|
+
export declare const RECENT_WRITE_WINDOW_MS = 500;
|
|
138
|
+
/**
|
|
139
|
+
* Remove the recent-write marker for `targetPath` (PRR-008). Called from the
|
|
140
|
+
* canonical writer's failure path: a write that throws before its rename
|
|
141
|
+
* completes must not leave a stale INTENT stamp behind, or a subsequent ENOENT
|
|
142
|
+
* read of the target would be misclassified as a rename race and retried
|
|
143
|
+
* against a file this process is no longer producing. Successful writes are
|
|
144
|
+
* unaffected — they reach the WRITE COMPLETION refresh, which re-stamps the
|
|
145
|
+
* marker for the post-rename visibility window. Returns true when a stamp was
|
|
146
|
+
* present and removed.
|
|
147
|
+
*/
|
|
148
|
+
export declare function clearRecentWriteActivity(targetPath: string): boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Whether this process recorded a write INTENT or write COMPLETION for
|
|
151
|
+
* `targetPath` within the last `withinMs` milliseconds (default
|
|
152
|
+
* {@link RECENT_WRITE_WINDOW_MS}).
|
|
153
|
+
*
|
|
154
|
+
* Consumers: the ENOENT retry ladders (`readSwarmFileAsync`,
|
|
155
|
+
* `readFileOrEmpty` in src/services/context-budget-service.ts) use this as
|
|
156
|
+
* the "evidenced rename-race" arm — an ENOENT on a file THIS process just
|
|
157
|
+
* wrote (or is mid-write on: intent is recorded before the temp file write
|
|
158
|
+
* begins) retries, while an ENOENT with no evidence the file ever existed
|
|
159
|
+
* returns immediately (issue #2472 PERF-1: 4×10ms of sleeps per missing-file
|
|
160
|
+
* read, ~1.9s/turn on plan-less projects).
|
|
161
|
+
*/
|
|
162
|
+
export declare function wasRecentlyWrittenByThisProcess(targetPath: string, withinMs?: number): boolean;
|
|
131
163
|
export interface AtomicWriteOptions {
|
|
132
164
|
/** Skip the pre-rename fsync (tests / truly ephemeral data). */
|
|
133
165
|
readonly skipFsync?: boolean;
|
|
@@ -15,6 +15,12 @@ export interface SwarmArtifactCacheStats {
|
|
|
15
15
|
textEntryCount: number;
|
|
16
16
|
parsedEntryCount: number;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Whether a previous successful stat/read inside this module observed
|
|
20
|
+
* `filePath` to exist (resolved the same way the cache keys are). Cheap Set
|
|
21
|
+
* lookup — safe on hot paths.
|
|
22
|
+
*/
|
|
23
|
+
export declare function wasObservedToExist(filePath: string): boolean;
|
|
18
24
|
/**
|
|
19
25
|
* Dependency-injection seam for testing. Tests can temporarily replace these
|
|
20
26
|
* to force a specific (e.g. colliding) stat stamp without relying on
|