@theokit/sdk 4.62.0 → 4.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{agent-NVBFTY5H.js → agent-536OOESZ.js} +8 -6
  3. package/dist/{agent-NVBFTY5H.js.map → agent-536OOESZ.js.map} +1 -1
  4. package/dist/{agent-VYPUO4UB.cjs → agent-5KYMG63I.cjs} +9 -7
  5. package/dist/{agent-VYPUO4UB.cjs.map → agent-5KYMG63I.cjs.map} +1 -1
  6. package/dist/{agent-DIu6FooJ.d.ts → agent-BFno7Sfn.d.ts} +36 -21
  7. package/dist/{agent-D3Xr_-6Z.d.cts → agent-X0DKA943.d.cts} +36 -21
  8. package/dist/chunk-A4RAL2ER.cjs +51 -0
  9. package/dist/chunk-A4RAL2ER.cjs.map +1 -0
  10. package/dist/chunk-A4VHSE56.cjs +21 -0
  11. package/dist/chunk-A4VHSE56.cjs.map +1 -0
  12. package/dist/{chunk-CQ2TQ32Y.js → chunk-AMFXSENK.js} +3 -48
  13. package/dist/chunk-AMFXSENK.js.map +1 -0
  14. package/dist/chunk-C7QPML3L.js +831 -0
  15. package/dist/chunk-C7QPML3L.js.map +1 -0
  16. package/dist/{chunk-CWHTMNNK.cjs → chunk-CASUEY42.cjs} +4 -4
  17. package/dist/{chunk-CWHTMNNK.cjs.map → chunk-CASUEY42.cjs.map} +1 -1
  18. package/dist/{chunk-SKOCYA55.js → chunk-CHKH5452.js} +37 -50
  19. package/dist/chunk-CHKH5452.js.map +1 -0
  20. package/dist/chunk-EHJEZAOO.cjs +864 -0
  21. package/dist/chunk-EHJEZAOO.cjs.map +1 -0
  22. package/dist/chunk-IRCJ7EHV.cjs +201 -0
  23. package/dist/chunk-IRCJ7EHV.cjs.map +1 -0
  24. package/dist/chunk-LW7G5DYW.js +19 -0
  25. package/dist/chunk-LW7G5DYW.js.map +1 -0
  26. package/dist/{chunk-IBYRA5PM.js → chunk-MG3SOM3M.js} +3 -3
  27. package/dist/{chunk-IBYRA5PM.js.map → chunk-MG3SOM3M.js.map} +1 -1
  28. package/dist/{chunk-AH6WD7JR.cjs → chunk-NLXOGBRJ.cjs} +45 -58
  29. package/dist/chunk-NLXOGBRJ.cjs.map +1 -0
  30. package/dist/{chunk-SKXBJ2NU.cjs → chunk-NXH4GPAQ.cjs} +2 -49
  31. package/dist/chunk-NXH4GPAQ.cjs.map +1 -0
  32. package/dist/chunk-R6TA2DRJ.js +194 -0
  33. package/dist/chunk-R6TA2DRJ.js.map +1 -0
  34. package/dist/{chunk-6OBIWHDR.cjs → chunk-UUP3MUZ6.cjs} +20 -361
  35. package/dist/chunk-UUP3MUZ6.cjs.map +1 -0
  36. package/dist/chunk-WE22OXQA.js +48 -0
  37. package/dist/chunk-WE22OXQA.js.map +1 -0
  38. package/dist/{chunk-QEKI3YKI.js → chunk-YTR3RBUZ.js} +13 -348
  39. package/dist/chunk-YTR3RBUZ.js.map +1 -0
  40. package/dist/{cron-BcWmzWzT.d.cts → cron-BuiRPrvt.d.cts} +1 -1
  41. package/dist/{cron-De6hzWCF.d.ts → cron-C2SIi31n.d.ts} +1 -1
  42. package/dist/cron.cjs +8 -6
  43. package/dist/cron.d.cts +2 -2
  44. package/dist/cron.d.ts +2 -2
  45. package/dist/cron.js +7 -5
  46. package/dist/eval.cjs +7 -5
  47. package/dist/eval.cjs.map +1 -1
  48. package/dist/eval.js +6 -4
  49. package/dist/eval.js.map +1 -1
  50. package/dist/{index-manager-RHPFVFSC.cjs → index-manager-6TEEIWM7.cjs} +7 -6
  51. package/dist/{index-manager-RHPFVFSC.cjs.map → index-manager-6TEEIWM7.cjs.map} +1 -1
  52. package/dist/{index-manager-AHAYJ33H.js → index-manager-ACEQG3IJ.js} +6 -5
  53. package/dist/{index-manager-AHAYJ33H.js.map → index-manager-ACEQG3IJ.js.map} +1 -1
  54. package/dist/index.cjs +35 -208
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +8 -3
  57. package/dist/index.d.ts +8 -3
  58. package/dist/index.js +22 -195
  59. package/dist/index.js.map +1 -1
  60. package/dist/internal/memory/active-memory.d.ts +8 -1
  61. package/dist/internal/memory/dreaming/diary.d.ts +39 -2
  62. package/dist/internal/memory/index-db.d.ts +44 -1
  63. package/dist/internal/memory/index-manager-contract.d.ts +10 -0
  64. package/dist/internal/memory/index-manager-helpers.d.ts +20 -1
  65. package/dist/internal/memory/index-manager.d.ts +1 -1
  66. package/dist/internal/memory/lance-index.d.ts +15 -1
  67. package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +43 -0
  68. package/dist/internal/memory/storage/index.cjs +95 -12
  69. package/dist/internal/memory/storage/index.d.cts +11 -1
  70. package/dist/internal/memory/storage/index.d.ts +11 -1
  71. package/dist/internal/memory/storage/index.js +4 -1
  72. package/dist/internal/memory/storage/markdown-store.d.cts +13 -42
  73. package/dist/internal/memory/storage/markdown-store.d.ts +13 -42
  74. package/dist/internal/memory/storage/memory-root.d.cts +93 -0
  75. package/dist/internal/memory/storage/memory-root.d.ts +93 -0
  76. package/dist/internal/memory/storage/session-loader.d.cts +35 -1
  77. package/dist/internal/memory/storage/session-loader.d.ts +35 -1
  78. package/dist/internal/memory/storage/session-summary-writer.d.cts +50 -2
  79. package/dist/internal/memory/storage/session-summary-writer.d.ts +50 -2
  80. package/dist/internal/memory/storage/transcript-store.d.cts +38 -1
  81. package/dist/internal/memory/storage/transcript-store.d.ts +38 -1
  82. package/dist/internal/memory/storage/wiki-loader.d.cts +27 -2
  83. package/dist/internal/memory/storage/wiki-loader.d.ts +27 -2
  84. package/dist/internal/memory/tools.d.ts +8 -1
  85. package/dist/internal/memory/types.d.ts +5 -0
  86. package/dist/internal/persistence/index.cjs +12 -11
  87. package/dist/internal/persistence/index.cjs.map +1 -1
  88. package/dist/internal/persistence/index.js +2 -1
  89. package/dist/internal/persistence/index.js.map +1 -1
  90. package/dist/internal/persistence/session-dir.d.cts +0 -15
  91. package/dist/internal/persistence/session-dir.d.ts +0 -15
  92. package/dist/internal/runtime/memory/memory-store.d.ts +2 -2
  93. package/dist/persistence.cjs +9 -8
  94. package/dist/persistence.cjs.map +1 -1
  95. package/dist/persistence.js +2 -1
  96. package/dist/persistence.js.map +1 -1
  97. package/dist/subagents-loader.d.cts +1 -1
  98. package/dist/subagents-loader.d.ts +1 -1
  99. package/dist/types/agent.d.ts +11 -0
  100. package/dist/types/memory-provider.d.ts +8 -0
  101. package/docs/error-codes.md +10 -9
  102. package/docs/harness-capability-map.md +57 -17
  103. package/docs/memory-decisions.md +44 -7
  104. package/package.json +1 -1
  105. package/dist/chunk-2UCFUSPW.cjs +0 -468
  106. package/dist/chunk-2UCFUSPW.cjs.map +0 -1
  107. package/dist/chunk-6OBIWHDR.cjs.map +0 -1
  108. package/dist/chunk-AH6WD7JR.cjs.map +0 -1
  109. package/dist/chunk-CQ2TQ32Y.js.map +0 -1
  110. package/dist/chunk-QEKI3YKI.js.map +0 -1
  111. package/dist/chunk-SKOCYA55.js.map +0 -1
  112. package/dist/chunk-SKXBJ2NU.cjs.map +0 -1
  113. package/dist/chunk-WCLDJSMY.js +0 -456
  114. package/dist/chunk-WCLDJSMY.js.map +0 -1
@@ -0,0 +1,93 @@
1
+ import type { MemoryConfig } from "../types.js";
2
+ /**
3
+ * A path that {@link resolveMemoryRoot} produced — the only thing the subsystem's path helpers
4
+ * accept.
5
+ *
6
+ * The brand is what makes "every path derives from one resolution" a compiler rule rather than a
7
+ * convention. Both a `cwd` and a root are strings, so without it the helpers would go on accepting
8
+ * either, and the next one added would have the same even chance of taking the wrong one that
9
+ * produced #463 in the first place. It costs one cast, at the one place a caller legitimately has a
10
+ * directory that did not come from here.
11
+ *
12
+ * STRUCTURAL, not a `unique symbol`. A `unique symbol` brand is identity-based, and the d.ts
13
+ * bundler inlines the declaration into each package that re-exports it — so `@theokit/sdk-memory`
14
+ * ended up with a `MemoryRoot` its own compiler considered incompatible with the SDK's, on values
15
+ * that were the same string. A structural tag refuses a bare `string` exactly as well and survives
16
+ * the package boundary, which is where this type has to work.
17
+ */
18
+ export type MemoryRoot = string & {
19
+ readonly __memoryRoot: "resolved";
20
+ };
21
+ /**
22
+ * Treat a directory as a memory root without resolving one.
23
+ *
24
+ * For the two honest cases: a test fixture, and a consumer opening an index against a store it
25
+ * located itself. Every other caller resolves.
26
+ */
27
+ export declare function asMemoryRoot(dir: string): MemoryRoot;
28
+ /** Only `directory` is read; the full config is accepted so callers pass what they already hold. */
29
+ export type MemoryLocationConfig = Partial<MemoryConfig>;
30
+ /**
31
+ * The project store: `<cwd>/.theokit/memory`. The default root, and always a READ root even when
32
+ * the write root moved — see {@link memoryReadRoots}.
33
+ */
34
+ export declare function projectMemoryDir(cwd: string): MemoryRoot;
35
+ /**
36
+ * Where the Claude Code CLI keeps THIS project's memories.
37
+ *
38
+ * `<claudeHome>/projects/<encoded-cwd>/memory` — the same `encodeProjectDir` scheme the transcripts
39
+ * already use, which is why no new encoding is invented here. `CLAUDE_CONFIG_DIR` names the home
40
+ * when set (the CLI's own variable); `~/.claude` otherwise.
41
+ *
42
+ * Read, never written. This is the half of the interop that decoupling the write MUST NOT cost: a
43
+ * memory the CLI recorded stays visible whatever `memory.directory` says.
44
+ */
45
+ export declare function claudeProjectMemoryDir(cwd: string): MemoryRoot;
46
+ /**
47
+ * The memory root for this agent: `memory.directory` when set, the project store otherwise.
48
+ *
49
+ * `directory` must be absolute or start with `~/`, which is the contract the interop partner
50
+ * documents for the same option. A relative path is REFUSED rather than resolved against the
51
+ * process cwd: the two plausible bases (workspace vs process) put memory in two different places,
52
+ * and picking one silently is how a store ends up split across both.
53
+ *
54
+ * @throws {ConfigurationError} `invalid_memory_directory` — the value is blank or relative.
55
+ */
56
+ export declare function resolveMemoryRoot(cwd: string, config?: MemoryLocationConfig): MemoryRoot;
57
+ /**
58
+ * The line limit the Claude Code CLI applies when it loads a `MEMORY.md`.
59
+ *
60
+ * Its number, not ours, and that is the point — see {@link indexBudgetWarning}.
61
+ */
62
+ export declare const MEMORY_INDEX_MAX_LINES = 200;
63
+ /**
64
+ * The byte limit the Claude Code CLI applies when it loads a `MEMORY.md`, whichever it reaches
65
+ * first. Also its number — see {@link indexBudgetWarning}.
66
+ */
67
+ export declare const MEMORY_INDEX_MAX_BYTES: number;
68
+ /**
69
+ * What to say about an index that the interop partner will truncate, or `undefined` when there is
70
+ * nothing true to say.
71
+ *
72
+ * **This is a statement about the PARTNER, not about us.** The CLI loads the first 200 lines / 25KB
73
+ * of `MEMORY.md` into every session and drops the rest in silence. We never load the index at all:
74
+ * the `<memory>` block is built from the per-memory FILES by `selectFactsForInjection`, ranked and
75
+ * capped, so our own recall does not degrade as the index grows. A warning that said "memory stops
76
+ * working" would be false here, and a warning that overstates is a warning somebody disables.
77
+ *
78
+ * It therefore speaks ONLY when the resolved root is the store the CLI reads. Anywhere else the
79
+ * index is ours, nobody truncates it, and the warning would be noise in every project that never
80
+ * opted into interop.
81
+ *
82
+ * Returns a string rather than emitting one: the caller decides what to do with it, and a pure
83
+ * function is testable without a filesystem or a captured stderr.
84
+ */
85
+ export declare function indexBudgetWarning(index: string, root: string): string | undefined;
86
+ /**
87
+ * Every directory a read must cover, deduplicated and in precedence order.
88
+ *
89
+ * WRITE ONE, READ ALL — this is what keeps a configured `directory` from orphaning anything. The
90
+ * configured root comes first, the project store second so memories recorded before the move stay
91
+ * readable, and the CLI's store last so interop survives the write being decoupled from it.
92
+ */
93
+ export declare function memoryReadRoots(cwd: string, config?: MemoryLocationConfig): readonly MemoryRoot[];
@@ -0,0 +1,93 @@
1
+ import type { MemoryConfig } from "../types.js";
2
+ /**
3
+ * A path that {@link resolveMemoryRoot} produced — the only thing the subsystem's path helpers
4
+ * accept.
5
+ *
6
+ * The brand is what makes "every path derives from one resolution" a compiler rule rather than a
7
+ * convention. Both a `cwd` and a root are strings, so without it the helpers would go on accepting
8
+ * either, and the next one added would have the same even chance of taking the wrong one that
9
+ * produced #463 in the first place. It costs one cast, at the one place a caller legitimately has a
10
+ * directory that did not come from here.
11
+ *
12
+ * STRUCTURAL, not a `unique symbol`. A `unique symbol` brand is identity-based, and the d.ts
13
+ * bundler inlines the declaration into each package that re-exports it — so `@theokit/sdk-memory`
14
+ * ended up with a `MemoryRoot` its own compiler considered incompatible with the SDK's, on values
15
+ * that were the same string. A structural tag refuses a bare `string` exactly as well and survives
16
+ * the package boundary, which is where this type has to work.
17
+ */
18
+ export type MemoryRoot = string & {
19
+ readonly __memoryRoot: "resolved";
20
+ };
21
+ /**
22
+ * Treat a directory as a memory root without resolving one.
23
+ *
24
+ * For the two honest cases: a test fixture, and a consumer opening an index against a store it
25
+ * located itself. Every other caller resolves.
26
+ */
27
+ export declare function asMemoryRoot(dir: string): MemoryRoot;
28
+ /** Only `directory` is read; the full config is accepted so callers pass what they already hold. */
29
+ export type MemoryLocationConfig = Partial<MemoryConfig>;
30
+ /**
31
+ * The project store: `<cwd>/.theokit/memory`. The default root, and always a READ root even when
32
+ * the write root moved — see {@link memoryReadRoots}.
33
+ */
34
+ export declare function projectMemoryDir(cwd: string): MemoryRoot;
35
+ /**
36
+ * Where the Claude Code CLI keeps THIS project's memories.
37
+ *
38
+ * `<claudeHome>/projects/<encoded-cwd>/memory` — the same `encodeProjectDir` scheme the transcripts
39
+ * already use, which is why no new encoding is invented here. `CLAUDE_CONFIG_DIR` names the home
40
+ * when set (the CLI's own variable); `~/.claude` otherwise.
41
+ *
42
+ * Read, never written. This is the half of the interop that decoupling the write MUST NOT cost: a
43
+ * memory the CLI recorded stays visible whatever `memory.directory` says.
44
+ */
45
+ export declare function claudeProjectMemoryDir(cwd: string): MemoryRoot;
46
+ /**
47
+ * The memory root for this agent: `memory.directory` when set, the project store otherwise.
48
+ *
49
+ * `directory` must be absolute or start with `~/`, which is the contract the interop partner
50
+ * documents for the same option. A relative path is REFUSED rather than resolved against the
51
+ * process cwd: the two plausible bases (workspace vs process) put memory in two different places,
52
+ * and picking one silently is how a store ends up split across both.
53
+ *
54
+ * @throws {ConfigurationError} `invalid_memory_directory` — the value is blank or relative.
55
+ */
56
+ export declare function resolveMemoryRoot(cwd: string, config?: MemoryLocationConfig): MemoryRoot;
57
+ /**
58
+ * The line limit the Claude Code CLI applies when it loads a `MEMORY.md`.
59
+ *
60
+ * Its number, not ours, and that is the point — see {@link indexBudgetWarning}.
61
+ */
62
+ export declare const MEMORY_INDEX_MAX_LINES = 200;
63
+ /**
64
+ * The byte limit the Claude Code CLI applies when it loads a `MEMORY.md`, whichever it reaches
65
+ * first. Also its number — see {@link indexBudgetWarning}.
66
+ */
67
+ export declare const MEMORY_INDEX_MAX_BYTES: number;
68
+ /**
69
+ * What to say about an index that the interop partner will truncate, or `undefined` when there is
70
+ * nothing true to say.
71
+ *
72
+ * **This is a statement about the PARTNER, not about us.** The CLI loads the first 200 lines / 25KB
73
+ * of `MEMORY.md` into every session and drops the rest in silence. We never load the index at all:
74
+ * the `<memory>` block is built from the per-memory FILES by `selectFactsForInjection`, ranked and
75
+ * capped, so our own recall does not degrade as the index grows. A warning that said "memory stops
76
+ * working" would be false here, and a warning that overstates is a warning somebody disables.
77
+ *
78
+ * It therefore speaks ONLY when the resolved root is the store the CLI reads. Anywhere else the
79
+ * index is ours, nobody truncates it, and the warning would be noise in every project that never
80
+ * opted into interop.
81
+ *
82
+ * Returns a string rather than emitting one: the caller decides what to do with it, and a pure
83
+ * function is testable without a filesystem or a captured stderr.
84
+ */
85
+ export declare function indexBudgetWarning(index: string, root: string): string | undefined;
86
+ /**
87
+ * Every directory a read must cover, deduplicated and in precedence order.
88
+ *
89
+ * WRITE ONE, READ ALL — this is what keeps a configured `directory` from orphaning anything. The
90
+ * configured root comes first, the project store second so memories recorded before the move stay
91
+ * readable, and the CLI's store last so interop survives the write being decoupled from it.
92
+ */
93
+ export declare function memoryReadRoots(cwd: string, config?: MemoryLocationConfig): readonly MemoryRoot[];
@@ -1 +1,35 @@
1
- export declare function discoverSessionFiles(cwd: string): Promise<SessionFile[]>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Session summary discovery (ADR D20).
4
+ *
5
+ * Mirrors `wiki-loader.ts:discoverWikiFiles`: scans
6
+ * `.theokit/memory/sessions/*.md` and returns `SessionFile` records —
7
+ * `{ absolutePath, relPath }`. IndexManager tags each chunk with
8
+ * `source="sessions"` so `memory_search({ corpus: "sessions" })` filters
9
+ * them in.
10
+ *
11
+ * B-140: this said "returns `MemoryFileEntry`-shaped records" and that was
12
+ * not true — `MemoryFileEntry` carried four fields (`path`, `relPath`,
13
+ * `mtime`, `hash`) against this function's two, and even the path field was
14
+ * named differently. Nothing consumed the interface, so it was removed and
15
+ * the sentence now names the type the function actually returns. A docblock
16
+ * asserting agreement between two shapes is worth no more than the agreement.
17
+ *
18
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
19
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
20
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
21
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
22
+ * the first draft of this very note did exactly that. See #430 and #463.
23
+ */
24
+ export interface SessionFile {
25
+ absolutePath: string;
26
+ relPath: string;
27
+ }
28
+ /**
29
+ * Every session summary under `<memory root>/sessions`, as `{ absolutePath, relPath }` records.
30
+ *
31
+ * Returns `[]` when the directory does not exist, so a workspace that has never finished a run is
32
+ * not an error. `IndexManager` tags what this returns with `source="sessions"`, which is what
33
+ * `memory_search({ corpus: "sessions" })` filters on.
34
+ */
35
+ export declare function discoverSessionFiles(root: MemoryRoot): Promise<SessionFile[]>;
@@ -1 +1,35 @@
1
- export declare function discoverSessionFiles(cwd: string): Promise<SessionFile[]>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Session summary discovery (ADR D20).
4
+ *
5
+ * Mirrors `wiki-loader.ts:discoverWikiFiles`: scans
6
+ * `.theokit/memory/sessions/*.md` and returns `SessionFile` records —
7
+ * `{ absolutePath, relPath }`. IndexManager tags each chunk with
8
+ * `source="sessions"` so `memory_search({ corpus: "sessions" })` filters
9
+ * them in.
10
+ *
11
+ * B-140: this said "returns `MemoryFileEntry`-shaped records" and that was
12
+ * not true — `MemoryFileEntry` carried four fields (`path`, `relPath`,
13
+ * `mtime`, `hash`) against this function's two, and even the path field was
14
+ * named differently. Nothing consumed the interface, so it was removed and
15
+ * the sentence now names the type the function actually returns. A docblock
16
+ * asserting agreement between two shapes is worth no more than the agreement.
17
+ *
18
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
19
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
20
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
21
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
22
+ * the first draft of this very note did exactly that. See #430 and #463.
23
+ */
24
+ export interface SessionFile {
25
+ absolutePath: string;
26
+ relPath: string;
27
+ }
28
+ /**
29
+ * Every session summary under `<memory root>/sessions`, as `{ absolutePath, relPath }` records.
30
+ *
31
+ * Returns `[]` when the directory does not exist, so a workspace that has never finished a run is
32
+ * not an error. `IndexManager` tags what this returns with `source="sessions"`, which is what
33
+ * `memory_search({ corpus: "sessions" })` filters on.
34
+ */
35
+ export declare function discoverSessionFiles(root: MemoryRoot): Promise<SessionFile[]>;
@@ -1,2 +1,50 @@
1
- export declare function sessionsDir(cwd: string): string;
2
- export declare function sessionSummaryPath(cwd: string, runId: string): string;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Per-run session summary writer (ADR D20).
4
+ *
5
+ * After every finished run, write a markdown summary to
6
+ * `.theokit/memory/sessions/<runId>.md`. IndexManager picks these up with
7
+ * `source="sessions"` so `memory_search({ corpus: "sessions" })` can recall
8
+ * past conversations.
9
+ *
10
+ * EC-9: only `status === "finished"` runs trigger a write. Cancelled/errored
11
+ * runs would otherwise pollute the recall corpus with partial transcripts.
12
+ *
13
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
14
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
15
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
16
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
17
+ * the first draft of this very note did exactly that. See #430 and #463.
18
+ */
19
+ export interface SessionSummaryInput {
20
+ /** The RESOLVED memory root, not a cwd — see `storage/memory-root.ts` (#463). */
21
+ memoryRoot: MemoryRoot;
22
+ runId: string;
23
+ agentId: string;
24
+ userText: string;
25
+ assistantText: string;
26
+ status: "finished" | "running" | "error" | "cancelled";
27
+ at: number;
28
+ }
29
+ /** `<memory root>/sessions`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */
30
+ export declare function sessionsDir(root: MemoryRoot): string;
31
+ /**
32
+ * The file one run's summary occupies: `<memory root>/sessions/<safe-id>.md`.
33
+ *
34
+ * The id passes through `safeFilenameForId`, so a UUID keeps its own name and anything else gets a
35
+ * deterministic `h-<16hex>` token. Deterministic matters more than readable here — a name that
36
+ * varied per call would orphan the summary it names.
37
+ */
38
+ export declare function sessionSummaryPath(root: MemoryRoot, runId: string): string;
39
+ /**
40
+ * Write a session summary file. EC-9: a non-finished status returns early
41
+ * without touching disk. Secrets in both user and assistant text are
42
+ * redacted via the shared `redactSecrets` pattern.
43
+ *
44
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
45
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
46
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
47
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
48
+ * the first draft of this very note did exactly that. See #430 and #463.
49
+ */
50
+ export declare function writeSessionSummary(input: SessionSummaryInput): Promise<void>;
@@ -1,2 +1,50 @@
1
- export declare function sessionsDir(cwd: string): string;
2
- export declare function sessionSummaryPath(cwd: string, runId: string): string;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Per-run session summary writer (ADR D20).
4
+ *
5
+ * After every finished run, write a markdown summary to
6
+ * `.theokit/memory/sessions/<runId>.md`. IndexManager picks these up with
7
+ * `source="sessions"` so `memory_search({ corpus: "sessions" })` can recall
8
+ * past conversations.
9
+ *
10
+ * EC-9: only `status === "finished"` runs trigger a write. Cancelled/errored
11
+ * runs would otherwise pollute the recall corpus with partial transcripts.
12
+ *
13
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
14
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
15
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
16
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
17
+ * the first draft of this very note did exactly that. See #430 and #463.
18
+ */
19
+ export interface SessionSummaryInput {
20
+ /** The RESOLVED memory root, not a cwd — see `storage/memory-root.ts` (#463). */
21
+ memoryRoot: MemoryRoot;
22
+ runId: string;
23
+ agentId: string;
24
+ userText: string;
25
+ assistantText: string;
26
+ status: "finished" | "running" | "error" | "cancelled";
27
+ at: number;
28
+ }
29
+ /** `<memory root>/sessions`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */
30
+ export declare function sessionsDir(root: MemoryRoot): string;
31
+ /**
32
+ * The file one run's summary occupies: `<memory root>/sessions/<safe-id>.md`.
33
+ *
34
+ * The id passes through `safeFilenameForId`, so a UUID keeps its own name and anything else gets a
35
+ * deterministic `h-<16hex>` token. Deterministic matters more than readable here — a name that
36
+ * varied per call would orphan the summary it names.
37
+ */
38
+ export declare function sessionSummaryPath(root: MemoryRoot, runId: string): string;
39
+ /**
40
+ * Write a session summary file. EC-9: a non-finished status returns early
41
+ * without touching disk. Secrets in both user and assistant text are
42
+ * redacted via the shared `redactSecrets` pattern.
43
+ *
44
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
45
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
46
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
47
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
48
+ * the first draft of this very note did exactly that. See #430 and #463.
49
+ */
50
+ export declare function writeSessionSummary(input: SessionSummaryInput): Promise<void>;
@@ -1 +1,38 @@
1
- export declare function persistActiveMemoryTranscript(cwd: string, transcript: ActiveMemoryTranscript): Promise<void>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Optional on-disk persistence for Active Memory recall transcripts (ADR D6).
4
+ *
5
+ * Writes one JSON file per run under
6
+ * `.theokit/memory/transcripts/active-memory/<runId>.json` when the agent
7
+ * passes `persistTranscripts: true`. Failures are swallowed with a stderr
8
+ * warning so transcript IO never crashes the agent run.
9
+ *
10
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
11
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
12
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
13
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
14
+ * the first draft of this very note did exactly that. See #430 and #463.
15
+ */
16
+ export interface ActiveMemoryTranscript {
17
+ runId: string;
18
+ startedAtMs: number;
19
+ userText: string;
20
+ queryMode: string;
21
+ status: string;
22
+ durationMs: number;
23
+ summary: string | undefined;
24
+ hits: ReadonlyArray<{
25
+ path: string;
26
+ startLine: number;
27
+ endLine: number;
28
+ score: number;
29
+ snippet: string;
30
+ }>;
31
+ }
32
+ /**
33
+ * Write one active-memory recall transcript under `<memory root>/transcripts/active-memory`.
34
+ *
35
+ * Never throws. Transcript IO is observability, and observability must not break the run it merely
36
+ * observes — a failure is reported through the diagnostics sink and swallowed.
37
+ */
38
+ export declare function persistActiveMemoryTranscript(root: MemoryRoot, transcript: ActiveMemoryTranscript): Promise<void>;
@@ -1 +1,38 @@
1
- export declare function persistActiveMemoryTranscript(cwd: string, transcript: ActiveMemoryTranscript): Promise<void>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Optional on-disk persistence for Active Memory recall transcripts (ADR D6).
4
+ *
5
+ * Writes one JSON file per run under
6
+ * `.theokit/memory/transcripts/active-memory/<runId>.json` when the agent
7
+ * passes `persistTranscripts: true`. Failures are swallowed with a stderr
8
+ * warning so transcript IO never crashes the agent run.
9
+ *
10
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
11
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
12
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
13
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
14
+ * the first draft of this very note did exactly that. See #430 and #463.
15
+ */
16
+ export interface ActiveMemoryTranscript {
17
+ runId: string;
18
+ startedAtMs: number;
19
+ userText: string;
20
+ queryMode: string;
21
+ status: string;
22
+ durationMs: number;
23
+ summary: string | undefined;
24
+ hits: ReadonlyArray<{
25
+ path: string;
26
+ startLine: number;
27
+ endLine: number;
28
+ score: number;
29
+ snippet: string;
30
+ }>;
31
+ }
32
+ /**
33
+ * Write one active-memory recall transcript under `<memory root>/transcripts/active-memory`.
34
+ *
35
+ * Never throws. Transcript IO is observability, and observability must not break the run it merely
36
+ * observes — a failure is reported through the diagnostics sink and swallowed.
37
+ */
38
+ export declare function persistActiveMemoryTranscript(root: MemoryRoot, transcript: ActiveMemoryTranscript): Promise<void>;
@@ -1,2 +1,27 @@
1
- export declare function wikiDir(cwd: string): string;
2
- export declare function discoverWikiFiles(cwd: string): Promise<WikiFile[]>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Wiki supplement discovery (ADR Phase 10 of memory-system-peer-project-parity).
4
+ *
5
+ * Wiki files live under `.theokit/memory/wiki/*.md`. They are READ-ONLY —
6
+ * the SDK never writes here. Each indexed chunk carries `source="wiki"` so
7
+ * `memory_search { corpus: "wiki" }` and `corpus: "all"` can scope hits.
8
+ *
9
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
10
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
11
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
12
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
13
+ * the first draft of this very note did exactly that. See #430 and #463.
14
+ */
15
+ export interface WikiFile {
16
+ absolutePath: string;
17
+ relPath: string;
18
+ }
19
+ /** `<memory root>/wiki`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */
20
+ export declare function wikiDir(root: MemoryRoot): string;
21
+ /**
22
+ * Every wiki supplement under `<memory root>/wiki`, as `{ absolutePath, relPath }` records.
23
+ *
24
+ * Returns `[]` when the directory does not exist. These are read-only supplements the indexer tags
25
+ * with `source="wiki"`, so `memory_search({ corpus: "wiki" })` can scope to them.
26
+ */
27
+ export declare function discoverWikiFiles(root: MemoryRoot): Promise<WikiFile[]>;
@@ -1,2 +1,27 @@
1
- export declare function wikiDir(cwd: string): string;
2
- export declare function discoverWikiFiles(cwd: string): Promise<WikiFile[]>;
1
+ import type { MemoryRoot } from "./memory-root.js";
2
+ /**
3
+ * Wiki supplement discovery (ADR Phase 10 of memory-system-peer-project-parity).
4
+ *
5
+ * Wiki files live under `.theokit/memory/wiki/*.md`. They are READ-ONLY —
6
+ * the SDK never writes here. Each indexed chunk carries `source="wiki"` so
7
+ * `memory_search { corpus: "wiki" }` and `corpus: "all"` can scope hits.
8
+ *
9
+ * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`
10
+ * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT
11
+ * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes
12
+ * this symbol from the published declarations and forces the satellite back onto a copy. Measured:
13
+ * the first draft of this very note did exactly that. See #430 and #463.
14
+ */
15
+ export interface WikiFile {
16
+ absolutePath: string;
17
+ relPath: string;
18
+ }
19
+ /** `<memory root>/wiki`. Takes the RESOLVED ROOT — see `storage/memory-root.ts` (#463). */
20
+ export declare function wikiDir(root: MemoryRoot): string;
21
+ /**
22
+ * Every wiki supplement under `<memory root>/wiki`, as `{ absolutePath, relPath }` records.
23
+ *
24
+ * Returns `[]` when the directory does not exist. These are read-only supplements the indexer tags
25
+ * with `source="wiki"`, so `memory_search({ corpus: "wiki" })` can scope to them.
26
+ */
27
+ export declare function discoverWikiFiles(root: MemoryRoot): Promise<WikiFile[]>;
@@ -1,4 +1,5 @@
1
1
  import type { MemoryIndex } from "./memory-index.js";
2
+ import type { MemoryRoot } from "./storage/memory-root.js";
2
3
  export interface MemoryTool extends MemoryToolJson {
3
4
  execute(input: Record<string, unknown>): Promise<string>;
4
5
  }
@@ -9,6 +10,12 @@ export interface MemorySearchToolOptions {
9
10
  }
10
11
  export declare function createMemorySearchTool(opts: MemorySearchToolOptions): MemoryTool;
11
12
  export interface MemoryGetToolOptions {
12
- cwd: string;
13
+ /**
14
+ * The RESOLVED memory root the tool may read inside — not a cwd (#463).
15
+ *
16
+ * This guard used to derive its own root from `cwd` while `appendFact` wrote somewhere else, so
17
+ * a relocated memory was unreadable by the tool whose whole job is reading memory.
18
+ */
19
+ root: MemoryRoot;
13
20
  }
14
21
  export declare function createMemoryGetTool(opts: MemoryGetToolOptions): MemoryTool;
@@ -4,6 +4,11 @@ export interface MemoryConfig {
4
4
  userId?: string;
5
5
  scope?: "agent" | "user" | "team";
6
6
  storePath?: string;
7
+ /**
8
+ * Absolute path (or `~/`-prefixed) of the memory root. Defaults to `<cwd>/.theokit/memory`.
9
+ * See `storage/memory-root.ts` for why a relative value is refused rather than resolved.
10
+ */
11
+ directory?: string;
7
12
  }
8
13
  /**
9
14
  * What a fact IS, which is what decides whether it ages (#389).
@@ -2,7 +2,8 @@
2
2
 
3
3
  var chunkVTYY7XL5_cjs = require('../../chunk-VTYY7XL5.cjs');
4
4
  var chunkJ2UROOIG_cjs = require('../../chunk-J2UROOIG.cjs');
5
- var chunkSKXBJ2NU_cjs = require('../../chunk-SKXBJ2NU.cjs');
5
+ var chunkA4RAL2ER_cjs = require('../../chunk-A4RAL2ER.cjs');
6
+ var chunkNXH4GPAQ_cjs = require('../../chunk-NXH4GPAQ.cjs');
6
7
  var chunk2ADR2GSO_cjs = require('../../chunk-2ADR2GSO.cjs');
7
8
  var chunkZF2LDKQQ_cjs = require('../../chunk-ZF2LDKQQ.cjs');
8
9
  var chunkI6TGFUCO_cjs = require('../../chunk-I6TGFUCO.cjs');
@@ -53,25 +54,25 @@ Object.defineProperty(exports, "writeVersionedJson", {
53
54
  enumerable: true,
54
55
  get: function () { return chunkJ2UROOIG_cjs.writeVersionedJson; }
55
56
  });
56
- Object.defineProperty(exports, "applyWalWithFallback", {
57
+ Object.defineProperty(exports, "containsCjk", {
57
58
  enumerable: true,
58
- get: function () { return chunkSKXBJ2NU_cjs.applyWalWithFallback; }
59
+ get: function () { return chunkA4RAL2ER_cjs.containsCjk; }
59
60
  });
60
- Object.defineProperty(exports, "containsCjk", {
61
+ Object.defineProperty(exports, "sanitizeFts5Query", {
61
62
  enumerable: true,
62
- get: function () { return chunkSKXBJ2NU_cjs.containsCjk; }
63
+ get: function () { return chunkA4RAL2ER_cjs.sanitizeFts5Query; }
63
64
  });
64
- Object.defineProperty(exports, "isCorruptionError", {
65
+ Object.defineProperty(exports, "applyWalWithFallback", {
65
66
  enumerable: true,
66
- get: function () { return chunkSKXBJ2NU_cjs.isCorruptionError; }
67
+ get: function () { return chunkNXH4GPAQ_cjs.applyWalWithFallback; }
67
68
  });
68
- Object.defineProperty(exports, "openSqliteResilient", {
69
+ Object.defineProperty(exports, "isCorruptionError", {
69
70
  enumerable: true,
70
- get: function () { return chunkSKXBJ2NU_cjs.openSqliteResilient; }
71
+ get: function () { return chunkNXH4GPAQ_cjs.isCorruptionError; }
71
72
  });
72
- Object.defineProperty(exports, "sanitizeFts5Query", {
73
+ Object.defineProperty(exports, "openSqliteResilient", {
73
74
  enumerable: true,
74
- get: function () { return chunkSKXBJ2NU_cjs.sanitizeFts5Query; }
75
+ get: function () { return chunkNXH4GPAQ_cjs.openSqliteResilient; }
75
76
  });
76
77
  Object.defineProperty(exports, "JsonlParseError", {
77
78
  enumerable: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/internal/persistence/exclusive-create.ts","../../../src/internal/persistence/sqlite-cas.ts"],"names":["open"],"mappings":";;;;;;;;;;;;;;AAwDA,eAAsB,eAAA,CACpB,IAAA,EACA,IAAA,EACA,OAAA,EACkB;AAClB,EAAA,MAAM,IAAA,GAAO,SAAS,IAAA,IAAQ,GAAA;AAC9B,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,MAAMA,aAAA,CAAK,IAAA,EAAM,MAAM,IAAI,CAAA;AAC1C,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,CAAO,UAAU,IAAI,CAAA;AAC3B,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,OAAO,KAAA,EAAM;AAAA,IACrB;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAA8B,SAAS,QAAA,EAAU;AACpD,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,GAAA;AAAA,EACR;AACF;;;ACdO,SAAS,SAAA,CACd,EAAA,EACA,GAAA,EACA,MAAA,EACA,kBAA0B,CAAA,EACjB;AACT,EAAA,MAAM,IAAA,GAAO,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,GAAI,MAAoB,CAAA;AAChD,EAAA,OAAO,OAAO,OAAA,KAAY,eAAA;AAC5B","file":"index.cjs","sourcesContent":["/**\n * O_EXCL exclusive file creation (ADR D82).\n *\n * `createExclusive(path, data, { mode })` creates a file in a single\n * syscall (`open(path, \"wx\", mode)`). Returns `true` if created, `false`\n * if it already existed (EEXIST swallowed — caller decides). All other\n * errors propagate.\n *\n * Default mode is 0o600 (owner-only) — EC-2 fix from edge-case review:\n * token files, lockfiles, and PID files MUST NOT default to world-\n * readable 0o644 under typical umask 022. Callers writing non-sensitive\n * files can pass `mode: 0o644` explicitly.\n *\n * NFS not honoring O_EXCL is documented (D61 — same stance as\n * `withFileLock`); the SDK target is ext4/APFS/NTFS.\n *\n * @internal\n */\n\nimport { open } from \"node:fs/promises\";\n\nexport interface CreateExclusiveOptions {\n /** Unix mode for the new file (default 0o600 — owner-only). */\n mode?: number;\n}\n\n/**\n * Create `path` holding `data`, but only if it does not exist yet. Returns `true` when this call\n * created it, `false` when it was already there.\n *\n * The check and the create are one `open(path, \"wx\")` syscall, so of N processes racing to create\n * the same path exactly one gets `true` — no window between testing and writing. The content is\n * written after the create, so the `false` branch tells you the file exists, not that another\n * writer has finished filling it.\n *\n * Only `EEXIST` becomes `false`. Every other error propagates: a missing parent directory is\n * `ENOENT`, an unwritable one `EACCES`. This never creates directories.\n *\n * The file is created with mode 0600 unless `options.mode` says otherwise, and the mode is\n * subject to the process umask. That default is deliberate — the callers are token files,\n * lockfiles and PID files, and 0644 under a typical umask would make them world-readable.\n *\n * **Choosing between this and the locks.** `createExclusive` claims a NAME once and is the right\n * tool for first-writer-wins: seeding a config, electing a single owner, writing a credential\n * exactly once. It cannot guard repeated updates, because a file that already exists always loses.\n * For read-modify-write on a path several writers touch, take a lock instead —\n * {@link withFileLock} across processes, `withCwdMutex` when the writers are all in this one. For\n * an in-place update guarded by a version column in SQLite, `casUpdate` is the equivalent\n * primitive.\n *\n * Atomicity is the filesystem's `O_EXCL`, which NFS does not reliably honor; the SDK targets\n * ext4, APFS and NTFS.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/persistence` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport async function createExclusive(\n path: string,\n data: string | Uint8Array,\n options?: CreateExclusiveOptions,\n): Promise<boolean> {\n const mode = options?.mode ?? 0o600;\n try {\n const handle = await open(path, \"wx\", mode);\n try {\n await handle.writeFile(data);\n return true;\n } finally {\n await handle.close();\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return false;\n }\n throw err;\n }\n}\n","/**\n * SQLite optimistic compare-and-swap (ADR D83).\n *\n * `casUpdate(db, sql, params, expectedChanges)` executes a prepared\n * UPDATE and returns true if `result.changes === expectedChanges`.\n * Caller provides the full SQL (including `WHERE version = ?` predicate);\n * helper does NOT generate SQL — DRY at the level of \"wrap the\n * convention\", not \"build queries\".\n *\n * Use case canonical (Hermes `kanban_db.py:1922-1934`):\n *\n * const won = casUpdate(\n * db,\n * \"UPDATE registry SET status = ?, version = version + 1 WHERE id = ? AND version = ?\",\n * [\"running\", \"agent-foo\", 3],\n * );\n * if (!won) { ... re-read and retry ... }\n *\n * Helper does NOT retry — caller responsible for backoff (avoids hidden\n * loops). Helper does NOT cache prepared statements — `better-sqlite3`\n * caches internally; SDK use is one-shot per mutation, not hot loops.\n *\n * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,\n * and TypeScript scans EVERY leading comment range of the declaration that follows, including the\n * import right below this one. The tag that used to sit here deleted that import from the emitted\n * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint\n * (usetheodev/theokit-sdk#283 records the same trap on a declaration).\n */\n\nimport type Database from \"better-sqlite3\";\n\ntype DatabaseInstance = InstanceType<typeof Database>;\n\n/**\n * Run an UPDATE and report whether it changed exactly the number of rows you expected — the\n * optimistic-concurrency equivalent of taking a lock.\n *\n * `sql` is yours, in full, including the guard that makes it a compare-and-swap: the\n * `WHERE ... AND version = ?` predicate and the `SET version = version + 1` that closes it. This\n * function generates nothing. It prepares the statement, runs it with `params`, and compares\n * `changes` against `expectedChanges` (default 1).\n *\n * `false` means the guard did not match — someone else moved the row first, or the id does not\n * exist. Those two are indistinguishable here; if you need to tell them apart, re-read the row.\n * A `false` return means NOTHING was written, so the caller owns the re-read-and-retry, with\n * whatever backoff it wants. There is no retry loop hidden in here, by design.\n *\n * SQL errors propagate — bad syntax, a closed database, a constraint violation, a busy writer.\n * Only the row-count mismatch is reported as `false`.\n *\n * Runs as a single implicit transaction, so no explicit BEGIN is needed for one statement. Wrap\n * the call yourself when the swap has to commit together with other writes.\n *\n * **Choosing between this and the locks.** `casUpdate` never blocks and never waits: the loser\n * finds out immediately and decides what to do. Prefer it when the contended state is already a\n * row with a version column. When the contended state is a FILE, there is no version column to\n * swap on — use `withFileLock` across processes, or `withCwdMutex` within one. When the goal is\n * to create something exactly once rather than update it, `createExclusive` is the primitive.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/persistence` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport function casUpdate(\n db: DatabaseInstance,\n sql: string,\n params: ReadonlyArray<unknown>,\n expectedChanges: number = 1,\n): boolean {\n const stmt = db.prepare(sql);\n const result = stmt.run(...(params as unknown[]));\n return result.changes === expectedChanges;\n}\n"]}
1
+ {"version":3,"sources":["../../../src/internal/persistence/exclusive-create.ts","../../../src/internal/persistence/sqlite-cas.ts"],"names":["open"],"mappings":";;;;;;;;;;;;;;;AAwDA,eAAsB,eAAA,CACpB,IAAA,EACA,IAAA,EACA,OAAA,EACkB;AAClB,EAAA,MAAM,IAAA,GAAO,SAAS,IAAA,IAAQ,GAAA;AAC9B,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,MAAMA,aAAA,CAAK,IAAA,EAAM,MAAM,IAAI,CAAA;AAC1C,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,CAAO,UAAU,IAAI,CAAA;AAC3B,MAAA,OAAO,IAAA;AAAA,IACT,CAAA,SAAE;AACA,MAAA,MAAM,OAAO,KAAA,EAAM;AAAA,IACrB;AAAA,EACF,SAAS,GAAA,EAAK;AACZ,IAAA,IAAK,GAAA,CAA8B,SAAS,QAAA,EAAU;AACpD,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,MAAM,GAAA;AAAA,EACR;AACF;;;ACdO,SAAS,SAAA,CACd,EAAA,EACA,GAAA,EACA,MAAA,EACA,kBAA0B,CAAA,EACjB;AACT,EAAA,MAAM,IAAA,GAAO,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA;AAC3B,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,GAAI,MAAoB,CAAA;AAChD,EAAA,OAAO,OAAO,OAAA,KAAY,eAAA;AAC5B","file":"index.cjs","sourcesContent":["/**\n * O_EXCL exclusive file creation (ADR D82).\n *\n * `createExclusive(path, data, { mode })` creates a file in a single\n * syscall (`open(path, \"wx\", mode)`). Returns `true` if created, `false`\n * if it already existed (EEXIST swallowed — caller decides). All other\n * errors propagate.\n *\n * Default mode is 0o600 (owner-only) — EC-2 fix from edge-case review:\n * token files, lockfiles, and PID files MUST NOT default to world-\n * readable 0o644 under typical umask 022. Callers writing non-sensitive\n * files can pass `mode: 0o644` explicitly.\n *\n * NFS not honoring O_EXCL is documented (D61 — same stance as\n * `withFileLock`); the SDK target is ext4/APFS/NTFS.\n *\n * @internal\n */\n\nimport { open } from \"node:fs/promises\";\n\nexport interface CreateExclusiveOptions {\n /** Unix mode for the new file (default 0o600 — owner-only). */\n mode?: number;\n}\n\n/**\n * Create `path` holding `data`, but only if it does not exist yet. Returns `true` when this call\n * created it, `false` when it was already there.\n *\n * The check and the create are one `open(path, \"wx\")` syscall, so of N processes racing to create\n * the same path exactly one gets `true` — no window between testing and writing. The content is\n * written after the create, so the `false` branch tells you the file exists, not that another\n * writer has finished filling it.\n *\n * Only `EEXIST` becomes `false`. Every other error propagates: a missing parent directory is\n * `ENOENT`, an unwritable one `EACCES`. This never creates directories.\n *\n * The file is created with mode 0600 unless `options.mode` says otherwise, and the mode is\n * subject to the process umask. That default is deliberate — the callers are token files,\n * lockfiles and PID files, and 0644 under a typical umask would make them world-readable.\n *\n * **Choosing between this and the locks.** `createExclusive` claims a NAME once and is the right\n * tool for first-writer-wins: seeding a config, electing a single owner, writing a credential\n * exactly once. It cannot guard repeated updates, because a file that already exists always loses.\n * For read-modify-write on a path several writers touch, take a lock instead —\n * {@link withFileLock} across processes, `withCwdMutex` when the writers are all in this one. For\n * an in-place update guarded by a version column in SQLite, `casUpdate` is the equivalent\n * primitive.\n *\n * Atomicity is the filesystem's `O_EXCL`, which NFS does not reliably honor; the SDK targets\n * ext4, APFS and NTFS.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/persistence` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport async function createExclusive(\n path: string,\n data: string | Uint8Array,\n options?: CreateExclusiveOptions,\n): Promise<boolean> {\n const mode = options?.mode ?? 0o600;\n try {\n const handle = await open(path, \"wx\", mode);\n try {\n await handle.writeFile(data);\n return true;\n } finally {\n await handle.close();\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return false;\n }\n throw err;\n }\n}\n","/**\n * SQLite optimistic compare-and-swap (ADR D83).\n *\n * `casUpdate(db, sql, params, expectedChanges)` executes a prepared\n * UPDATE and returns true if `result.changes === expectedChanges`.\n * Caller provides the full SQL (including `WHERE version = ?` predicate);\n * helper does NOT generate SQL — DRY at the level of \"wrap the\n * convention\", not \"build queries\".\n *\n * Use case canonical (Hermes `kanban_db.py:1922-1934`):\n *\n * const won = casUpdate(\n * db,\n * \"UPDATE registry SET status = ?, version = version + 1 WHERE id = ? AND version = ?\",\n * [\"running\", \"agent-foo\", 3],\n * );\n * if (!won) { ... re-read and retry ... }\n *\n * Helper does NOT retry — caller responsible for backoff (avoids hidden\n * loops). Helper does NOT cache prepared statements — `better-sqlite3`\n * caches internally; SDK use is one-shot per mutation, not hot loops.\n *\n * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,\n * and TypeScript scans EVERY leading comment range of the declaration that follows, including the\n * import right below this one. The tag that used to sit here deleted that import from the emitted\n * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint\n * (usetheodev/theokit-sdk#283 records the same trap on a declaration).\n */\n\nimport type Database from \"better-sqlite3\";\n\ntype DatabaseInstance = InstanceType<typeof Database>;\n\n/**\n * Run an UPDATE and report whether it changed exactly the number of rows you expected — the\n * optimistic-concurrency equivalent of taking a lock.\n *\n * `sql` is yours, in full, including the guard that makes it a compare-and-swap: the\n * `WHERE ... AND version = ?` predicate and the `SET version = version + 1` that closes it. This\n * function generates nothing. It prepares the statement, runs it with `params`, and compares\n * `changes` against `expectedChanges` (default 1).\n *\n * `false` means the guard did not match — someone else moved the row first, or the id does not\n * exist. Those two are indistinguishable here; if you need to tell them apart, re-read the row.\n * A `false` return means NOTHING was written, so the caller owns the re-read-and-retry, with\n * whatever backoff it wants. There is no retry loop hidden in here, by design.\n *\n * SQL errors propagate — bad syntax, a closed database, a constraint violation, a busy writer.\n * Only the row-count mismatch is reported as `false`.\n *\n * Runs as a single implicit transaction, so no explicit BEGIN is needed for one statement. Wrap\n * the call yourself when the swap has to commit together with other writes.\n *\n * **Choosing between this and the locks.** `casUpdate` never blocks and never waits: the loser\n * finds out immediately and decides what to do. Prefer it when the contended state is already a\n * row with a version column. When the contended state is a FILE, there is no version column to\n * swap on — use `withFileLock` across processes, or `withCwdMutex` within one. When the goal is\n * to create something exactly once rather than update it, `createExclusive` is the primitive.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/persistence` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport function casUpdate(\n db: DatabaseInstance,\n sql: string,\n params: ReadonlyArray<unknown>,\n expectedChanges: number = 1,\n): boolean {\n const stmt = db.prepare(sql);\n const result = stmt.run(...(params as unknown[]));\n return result.changes === expectedChanges;\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  export { PersistenceSchema } from '../../chunk-HY66GLM6.js';
2
2
  export { migrateSchema, readVersionedJson, writeVersionedJson } from '../../chunk-UCBJBJ27.js';
3
- export { applyWalWithFallback, containsCjk, isCorruptionError, openSqliteResilient, sanitizeFts5Query } from '../../chunk-CQ2TQ32Y.js';
3
+ export { containsCjk, sanitizeFts5Query } from '../../chunk-WE22OXQA.js';
4
+ export { applyWalWithFallback, isCorruptionError, openSqliteResilient } from '../../chunk-AMFXSENK.js';
4
5
  export { JsonlParseError, appendJsonl, loadJsonl, readJsonlIds, withFileLock } from '../../chunk-CV7XMBHP.js';
5
6
  export { withCwdMutex } from '../../chunk-Q5EWJPRY.js';
6
7
  export { atomicWriteJson, atomicWriteText, replaceFileAtomic } from '../../chunk-VF7EWVDG.js';