clanka 0.2.19 → 0.2.21

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.
@@ -59,18 +59,21 @@ export class CodeChunker extends ServiceMap.Service<
59
59
  readonly path: string
60
60
  readonly chunkSize: number
61
61
  readonly chunkOverlap: number
62
+ readonly chunkMaxCharacters?: number | undefined
62
63
  }): Effect.Effect<ReadonlyArray<CodeChunk>>
63
64
  chunkFiles(options: {
64
65
  readonly root: string
65
66
  readonly paths: ReadonlyArray<string>
66
67
  readonly chunkSize: number
67
68
  readonly chunkOverlap: number
69
+ readonly chunkMaxCharacters?: number | undefined
68
70
  }): Stream.Stream<CodeChunk>
69
71
  chunkCodebase(options: {
70
72
  readonly root: string
71
73
  readonly maxFileSize?: string | undefined
72
74
  readonly chunkSize: number
73
75
  readonly chunkOverlap: number
76
+ readonly chunkMaxCharacters?: number | undefined
74
77
  }): Stream.Stream<CodeChunk>
75
78
  }
76
79
  >()("clanka/CodeChunker") {}
@@ -157,6 +160,7 @@ interface LineRange {
157
160
  interface ChunkSettings {
158
161
  readonly chunkSize: number
159
162
  readonly chunkOverlap: number
163
+ readonly chunkMaxCharacters: number
160
164
  }
161
165
 
162
166
  interface ChunkRange extends LineRange {
@@ -234,16 +238,23 @@ export const isMeaningfulFile = (path: string): boolean => {
234
238
  const resolveChunkSettings = (options: {
235
239
  readonly chunkSize: number
236
240
  readonly chunkOverlap: number
241
+ readonly chunkMaxCharacters?: number | undefined
237
242
  }): ChunkSettings => {
238
243
  const chunkSize = Math.max(1, options.chunkSize)
239
244
  const chunkOverlap = Math.max(
240
245
  0,
241
246
  Math.min(chunkSize - 1, options.chunkOverlap),
242
247
  )
248
+ const chunkMaxCharacters =
249
+ options.chunkMaxCharacters !== undefined &&
250
+ Number.isFinite(options.chunkMaxCharacters)
251
+ ? Math.max(1, Math.floor(options.chunkMaxCharacters))
252
+ : Number.POSITIVE_INFINITY
243
253
 
244
254
  return {
245
255
  chunkSize,
246
256
  chunkOverlap,
257
+ chunkMaxCharacters,
247
258
  }
248
259
  }
249
260
 
@@ -345,24 +356,76 @@ const normalizeLineRange = (
345
356
  }
346
357
  }
347
358
 
359
+ const lineLengthPrefixSums = (
360
+ lines: ReadonlyArray<string>,
361
+ ): ReadonlyArray<number> => {
362
+ const sums = [0] as Array<number>
363
+
364
+ for (let index = 0; index < lines.length; index++) {
365
+ sums.push(sums[index]! + lines[index]!.length)
366
+ }
367
+
368
+ return sums
369
+ }
370
+
371
+ const lineRangeCharacterLength = (
372
+ prefixSums: ReadonlyArray<number>,
373
+ range: LineRange,
374
+ ): number =>
375
+ prefixSums[range.endLine]! -
376
+ prefixSums[range.startLine - 1]! +
377
+ (range.endLine - range.startLine)
378
+
379
+ const resolveSegmentEndLine = (options: {
380
+ readonly startLine: number
381
+ readonly maxEndLine: number
382
+ readonly settings: ChunkSettings
383
+ readonly prefixSums: ReadonlyArray<number>
384
+ }): number => {
385
+ if (options.settings.chunkMaxCharacters === Number.POSITIVE_INFINITY) {
386
+ return options.maxEndLine
387
+ }
388
+
389
+ let endLine = options.maxEndLine
390
+ while (
391
+ endLine > options.startLine &&
392
+ lineRangeCharacterLength(options.prefixSums, {
393
+ startLine: options.startLine,
394
+ endLine,
395
+ }) > options.settings.chunkMaxCharacters
396
+ ) {
397
+ endLine--
398
+ }
399
+
400
+ return endLine
401
+ }
402
+
348
403
  const splitRange = (
349
404
  range: LineRange,
350
405
  settings: ChunkSettings,
406
+ prefixSums: ReadonlyArray<number>,
351
407
  ): ReadonlyArray<LineRange> => {
352
408
  const lineCount = range.endLine - range.startLine + 1
353
- if (lineCount <= settings.chunkSize) {
409
+ if (
410
+ lineCount <= settings.chunkSize &&
411
+ lineRangeCharacterLength(prefixSums, range) <= settings.chunkMaxCharacters
412
+ ) {
354
413
  return [range]
355
414
  }
356
-
357
- const step = settings.chunkSize - settings.chunkOverlap
358
415
  const out = [] as Array<LineRange>
359
416
 
360
- for (
361
- let startLine = range.startLine;
362
- startLine <= range.endLine;
363
- startLine += step
364
- ) {
365
- const endLine = Math.min(range.endLine, startLine + settings.chunkSize - 1)
417
+ for (let startLine = range.startLine; startLine <= range.endLine; ) {
418
+ const maxEndLine = Math.min(
419
+ range.endLine,
420
+ startLine + settings.chunkSize - 1,
421
+ )
422
+ const endLine = resolveSegmentEndLine({
423
+ startLine,
424
+ maxEndLine,
425
+ settings,
426
+ prefixSums,
427
+ })
428
+
366
429
  out.push({
367
430
  startLine,
368
431
  endLine,
@@ -371,6 +434,8 @@ const splitRange = (
371
434
  if (endLine >= range.endLine) {
372
435
  break
373
436
  }
437
+
438
+ startLine = Math.max(startLine + 1, endLine - settings.chunkOverlap + 1)
374
439
  }
375
440
 
376
441
  return out
@@ -648,6 +713,7 @@ const chunksFromRanges = (
648
713
 
649
714
  const out = [] as Array<CodeChunk>
650
715
  const seen = new Set<string>()
716
+ const prefixSums = lineLengthPrefixSums(lines)
651
717
 
652
718
  for (const range of ranges) {
653
719
  const normalizedRange = normalizeLineRange(range, lines.length)
@@ -655,7 +721,7 @@ const chunksFromRanges = (
655
721
  continue
656
722
  }
657
723
 
658
- const allSegments = splitRange(normalizedRange, settings)
724
+ const allSegments = splitRange(normalizedRange, settings, prefixSums)
659
725
  const segments =
660
726
  range.type === "class" &&
661
727
  allSegments.length > 1 &&
@@ -709,8 +775,8 @@ const chunkWithLineWindows = (
709
775
  lines: ReadonlyArray<string>,
710
776
  settings: ChunkSettings,
711
777
  ): ReadonlyArray<CodeChunk> => {
712
- const step = settings.chunkSize - settings.chunkOverlap
713
778
  const out = [] as Array<CodeChunk>
779
+ const prefixSums = lineLengthPrefixSums(lines)
714
780
 
715
781
  for (let index = 0; index < lines.length; ) {
716
782
  if (!isMeaningfulLine(lines[index]!)) {
@@ -718,25 +784,38 @@ const chunkWithLineWindows = (
718
784
  continue
719
785
  }
720
786
 
721
- const start = index
722
- const end = Math.min(lines.length, start + settings.chunkSize)
723
- const chunkLines = lines.slice(start, end)
787
+ const startLine = index + 1
788
+ const maxEndLine = Math.min(
789
+ lines.length,
790
+ startLine + settings.chunkSize - 1,
791
+ )
792
+ const endLine = resolveSegmentEndLine({
793
+ startLine,
794
+ maxEndLine,
795
+ settings,
796
+ prefixSums,
797
+ })
798
+ const chunkLines = lines.slice(startLine - 1, endLine)
724
799
 
725
800
  out.push({
726
801
  path,
727
- startLine: start + 1,
728
- endLine: end,
802
+ startLine,
803
+ endLine,
729
804
  name: undefined,
730
805
  type: undefined,
731
806
  parent: undefined,
732
807
  content: chunkLines.join("\n"),
733
808
  })
734
809
 
735
- index += step
736
-
737
- if (end >= lines.length) {
810
+ if (endLine >= lines.length) {
738
811
  break
739
812
  }
813
+
814
+ const nextStartLine = Math.max(
815
+ startLine + 1,
816
+ endLine - settings.chunkOverlap + 1,
817
+ )
818
+ index = nextStartLine - 1
740
819
  }
741
820
 
742
821
  return out
@@ -752,6 +831,7 @@ export const chunkFileContent = (
752
831
  options: {
753
832
  readonly chunkSize: number
754
833
  readonly chunkOverlap: number
834
+ readonly chunkMaxCharacters?: number | undefined
755
835
  },
756
836
  ): ReadonlyArray<CodeChunk> => {
757
837
  if (content.trim().length === 0 || isProbablyMinified(content)) {
@@ -869,6 +949,9 @@ export const layer: Layer.Layer<
869
949
  path,
870
950
  chunkSize: options.chunkSize,
871
951
  chunkOverlap: options.chunkOverlap,
952
+ ...(options.chunkMaxCharacters === undefined
953
+ ? {}
954
+ : { chunkMaxCharacters: options.chunkMaxCharacters }),
872
955
  }),
873
956
  Stream.fromArrayEffect,
874
957
  ),
@@ -891,6 +974,9 @@ export const layer: Layer.Layer<
891
974
  paths: files,
892
975
  chunkSize: options.chunkSize,
893
976
  chunkOverlap: options.chunkOverlap,
977
+ ...(options.chunkMaxCharacters === undefined
978
+ ? {}
979
+ : { chunkMaxCharacters: options.chunkMaxCharacters }),
894
980
  })
895
981
  }, Stream.unwrap)
896
982
 
@@ -169,6 +169,7 @@ describe("preprocessScript", () => {
169
169
  "patch9",
170
170
  "patch10",
171
171
  "patch11",
172
+ "patch12",
172
173
  ])("fixes broken %s", (fixture) => {
173
174
  const content = readFileSync(
174
175
  join(__dirname, "fixtures", `${fixture}-broken.txt`),
@@ -316,6 +316,127 @@ const findClosingParen = (text: string, openParen: number): number => {
316
316
  return -1
317
317
  }
318
318
 
319
+ const findClosingBrace = (text: string, openBrace: number): number => {
320
+ let depth = 1
321
+ let stringDelimiter: '"' | "'" | "`" | undefined
322
+
323
+ for (let i = openBrace + 1; i < text.length; i++) {
324
+ const char = text[i]!
325
+
326
+ if (stringDelimiter !== undefined) {
327
+ if (char === stringDelimiter && !isEscaped(text, i)) {
328
+ stringDelimiter = undefined
329
+ }
330
+ continue
331
+ }
332
+
333
+ if (char === '"' || char === "'" || char === "`") {
334
+ stringDelimiter = char
335
+ continue
336
+ }
337
+
338
+ if (char === "{") {
339
+ depth++
340
+ continue
341
+ }
342
+
343
+ if (char === "}") {
344
+ depth--
345
+ if (depth === 0) {
346
+ return i
347
+ }
348
+ }
349
+ }
350
+
351
+ return -1
352
+ }
353
+
354
+ const fixObjectLiteralTemplateValues = (text: string): string =>
355
+ text.replace(/\\{2,}(?=`|\$\{)/g, "\\")
356
+
357
+ const fixAssignedObjectTemplateValues = (
358
+ script: string,
359
+ variableName: string,
360
+ ): string => {
361
+ let out = script
362
+ let cursor = 0
363
+
364
+ while (cursor < out.length) {
365
+ const variableStart = findNextIdentifier(out, variableName, cursor)
366
+ if (variableStart === -1) {
367
+ break
368
+ }
369
+
370
+ let assignmentStart = skipWhitespace(
371
+ out,
372
+ variableStart + variableName.length,
373
+ )
374
+ if (out[assignmentStart] === ":") {
375
+ assignmentStart = findTypeAnnotationAssignment(out, assignmentStart + 1)
376
+ if (assignmentStart === -1) {
377
+ cursor = variableStart + variableName.length
378
+ continue
379
+ }
380
+ }
381
+
382
+ if (
383
+ out[assignmentStart] !== "=" ||
384
+ out[assignmentStart + 1] === "=" ||
385
+ out[assignmentStart + 1] === ">"
386
+ ) {
387
+ cursor = variableStart + variableName.length
388
+ continue
389
+ }
390
+
391
+ const objectStart = skipWhitespace(out, assignmentStart + 1)
392
+ if (out[objectStart] !== "{") {
393
+ cursor = objectStart + 1
394
+ continue
395
+ }
396
+
397
+ const objectEnd = findClosingBrace(out, objectStart)
398
+ if (objectEnd === -1) {
399
+ cursor = objectStart + 1
400
+ continue
401
+ }
402
+
403
+ const original = out.slice(objectStart, objectEnd + 1)
404
+ const escaped = fixObjectLiteralTemplateValues(original)
405
+ if (escaped !== original) {
406
+ out = `${out.slice(0, objectStart)}${escaped}${out.slice(objectEnd + 1)}`
407
+ cursor = objectEnd + (escaped.length - original.length) + 1
408
+ continue
409
+ }
410
+
411
+ cursor = objectEnd + 1
412
+ }
413
+
414
+ return out
415
+ }
416
+
417
+ const escapeRegExp = (text: string): string =>
418
+ text.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")
419
+
420
+ const collectObjectEntryMapSources = (
421
+ script: string,
422
+ valueIdentifier: string,
423
+ ): ReadonlySet<string> => {
424
+ const out = new Set<string>()
425
+ const pattern = new RegExp(
426
+ `Object\\.entries\\(\\s*([A-Za-z_$][A-Za-z0-9_$]*)\\s*\\)\\s*\\.map\\(\\s*(?:async\\s*)?\\(\\s*\\[\\s*[A-Za-z_$][A-Za-z0-9_$]*\\s*,\\s*${escapeRegExp(valueIdentifier)}\\s*\\]\\s*\\)\\s*=>`,
427
+ "g",
428
+ )
429
+
430
+ for (const match of script.matchAll(pattern)) {
431
+ const sourceIdentifier = match[1]
432
+ if (sourceIdentifier !== undefined) {
433
+ out.add(sourceIdentifier)
434
+ }
435
+ }
436
+
437
+ return out
438
+ }
439
+
319
440
  const findCallTemplateEnd = (
320
441
  text: string,
321
442
  templateStart: number,
@@ -641,10 +762,23 @@ const fixAssignedTemplatesForToolCalls = (script: string): string => {
641
762
  identifiers.add("patch")
642
763
  }
643
764
 
765
+ const objectTemplateIdentifiers = new Set<string>()
766
+ for (const identifier of identifiers) {
767
+ for (const sourceIdentifier of collectObjectEntryMapSources(
768
+ script,
769
+ identifier,
770
+ )) {
771
+ objectTemplateIdentifiers.add(sourceIdentifier)
772
+ }
773
+ }
774
+
644
775
  let out = script
645
776
  for (const identifier of identifiers) {
646
777
  out = fixAssignedTemplate(out, identifier)
647
778
  }
779
+ for (const identifier of objectTemplateIdentifiers) {
780
+ out = fixAssignedObjectTemplateValues(out, identifier)
781
+ }
648
782
  return out
649
783
  }
650
784
 
@@ -42,10 +42,13 @@ export class SemanticSearch extends ServiceMap.Service<
42
42
 
43
43
  const normalizePath = (path: string) => path.replace(/\\/g, "/")
44
44
 
45
- const chunkConfig = {
45
+ const resolveChunkConfig = (options: {
46
+ readonly chunkMaxCharacters?: number | undefined
47
+ }) => ({
46
48
  chunkSize: 30,
47
49
  chunkOverlap: 0,
48
- } as const
50
+ chunkMaxCharacters: options.chunkMaxCharacters ?? 10_000,
51
+ })
49
52
 
50
53
  export const makeEmbeddingResolver = (
51
54
  resolver: EmbeddingModel.Service["resolver"],
@@ -100,6 +103,7 @@ export const layer = (options: {
100
103
  readonly embeddingBatchSize?: number | undefined
101
104
  readonly embeddingRequestDelay?: Duration.Input | undefined
102
105
  readonly concurrency?: number | undefined
106
+ readonly chunkMaxCharacters?: number | undefined
103
107
  }): Layer.Layer<
104
108
  SemanticSearch,
105
109
  | SqlError.SqlError
@@ -121,6 +125,7 @@ export const layer = (options: {
121
125
  const root = pathService.resolve(options.directory)
122
126
  const resolver = makeEmbeddingResolver(embeddings.resolver, options)
123
127
  const concurrency = options.concurrency ?? 2000
128
+ const chunkConfig = resolveChunkConfig(options)
124
129
  const indexHandle = yield* FiberHandle.make()
125
130
  const console = yield* Console.Console
126
131
 
package/src/cli.ts ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ import * as Effect from "effect/Effect"
3
+ import * as Prompt from "effect/unstable/cli/Prompt"
4
+ import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient"
5
+ import * as NodeServices from "@effect/platform-node/NodeServices"
6
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
7
+ import * as NodeSocket from "@effect/platform-node/NodeSocket"
8
+ import * as Codex from "./Codex.ts"
9
+ import * as Copilot from "./Copilot.ts"
10
+ import * as Agent from "./Agent.ts"
11
+ import * as Stream from "effect/Stream"
12
+ import * as OutputFormatter from "./OutputFormatter.ts"
13
+ import * as Stdio from "effect/Stdio"
14
+ import { pipe } from "effect/Function"
15
+ import * as Layer from "effect/Layer"
16
+ import * as Path from "effect/Path"
17
+ import * as Config from "effect/Config"
18
+ import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"
19
+ import * as SemanticSearch from "./SemanticSearch.ts"
20
+ import * as Option from "effect/Option"
21
+ import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai"
22
+
23
+ const Kvs = Layer.unwrap(
24
+ Effect.gen(function* () {
25
+ const path = yield* Path.Path
26
+
27
+ const configHome = yield* Config.nonEmptyString("XDG_CONFIG_HOME").pipe(
28
+ Config.orElse(() =>
29
+ Config.nonEmptyString("HOME").pipe(
30
+ Config.map((home) => path.join(home, ".config")),
31
+ ),
32
+ ),
33
+ )
34
+ return KeyValueStore.layerFileSystem(path.join(configHome, "clanka"))
35
+ }),
36
+ ).pipe(Layer.provide(NodeServices.layer))
37
+
38
+ const Search = Layer.unwrap(
39
+ Effect.gen(function* () {
40
+ const apiKey = yield* Config.redacted("OPENAI_API_KEY").pipe(Config.option)
41
+
42
+ if (Option.isNone(apiKey)) {
43
+ yield* Effect.logWarning("OPENAI_API_KEY is not set")
44
+ return Layer.empty
45
+ }
46
+
47
+ const path = yield* Path.Path
48
+
49
+ return SemanticSearch.layer({
50
+ directory: process.cwd(),
51
+ database: path.join(".clanka", "search.sqlite"),
52
+ }).pipe(
53
+ Layer.provide(
54
+ OpenAiEmbeddingModel.model("text-embedding-3-small", {
55
+ dimensions: 1536,
56
+ }),
57
+ ),
58
+ Layer.provide(
59
+ OpenAiClient.layer({
60
+ apiKey: apiKey.value,
61
+ }),
62
+ ),
63
+ )
64
+ }),
65
+ ).pipe(Layer.provide([NodeServices.layer, NodeHttpClient.layerUndici]))
66
+
67
+ Effect.gen(function* () {
68
+ const stdio = yield* Stdio.Stdio
69
+
70
+ const provider = yield* Prompt.select({
71
+ message: "Select a provider",
72
+ choices: [
73
+ {
74
+ title: "openai",
75
+ value: "openai",
76
+ selected: true,
77
+ },
78
+ {
79
+ title: "copilot",
80
+ value: "copilot",
81
+ },
82
+ ],
83
+ })
84
+ const modelRaw = yield* Prompt.text({
85
+ message: "Enter a model",
86
+ default: "gpt-5.4/medium",
87
+ validate(value) {
88
+ const parts = value.split("/")
89
+ if (parts.length !== 2) {
90
+ return Effect.fail("Invalid model")
91
+ }
92
+ return Effect.succeed(value)
93
+ },
94
+ })
95
+ const semantic = yield* Prompt.confirm({
96
+ message: "Use semantic search? (uses OPENAI_API_KEY env var)",
97
+ })
98
+
99
+ const [model, reasoning] = modelRaw.split("/") as [string, string]
100
+ const Model =
101
+ provider === "openai"
102
+ ? Codex.modelWebSocket(model, {
103
+ reasoning: {
104
+ effort: reasoning as any,
105
+ },
106
+ }).pipe(
107
+ Layer.merge(
108
+ Agent.layerSubagentModel(
109
+ Codex.modelWebSocket("gpt-5.4-mini", {
110
+ reasoning: {
111
+ effort: "high",
112
+ },
113
+ }),
114
+ ),
115
+ ),
116
+ Layer.provide(Codex.layerClient),
117
+ )
118
+ : Copilot.model(model, {
119
+ reasoning: {
120
+ effort: reasoning,
121
+ },
122
+ }).pipe(
123
+ Layer.merge(
124
+ Agent.layerSubagentModel(
125
+ Copilot.model(model, {
126
+ reasoning: {
127
+ effort: "medium",
128
+ },
129
+ }),
130
+ ),
131
+ ),
132
+ Layer.provide(Copilot.layerClient),
133
+ )
134
+
135
+ return yield* Effect.gen(function* () {
136
+ const agent = yield* Agent.Agent
137
+
138
+ while (true) {
139
+ const prompt = yield* Prompt.text({
140
+ message: ">",
141
+ })
142
+
143
+ yield* pipe(
144
+ agent.send({ prompt }),
145
+ Stream.unwrap,
146
+ OutputFormatter.pretty({ outputTruncation: 30 }),
147
+ Stream.run(stdio.stdout()),
148
+ )
149
+
150
+ console.log("")
151
+ }
152
+ }).pipe(
153
+ Effect.provide([
154
+ Agent.layerLocal({
155
+ directory: process.cwd(),
156
+ }),
157
+ Model,
158
+ semantic ? Search : Layer.empty,
159
+ ]),
160
+ )
161
+ }).pipe(
162
+ Effect.provide([
163
+ NodeServices.layer,
164
+ Kvs,
165
+ NodeHttpClient.layerUndici,
166
+ NodeSocket.layerWebSocketConstructorWS,
167
+ ]),
168
+ NodeRuntime.runMain,
169
+ )
@@ -0,0 +1,16 @@
1
+ const files = {
2
+ '.changeset/green-seahorses-jump.md': `---\n"effect": minor\n---\n\nMigrate unstable \\`SqlError\\` to a reason-based shape (\\`new SqlError({ reason })\\`) with structured reason classes and retryable/classified reasons.`,
3
+ '.changeset/cool-islands-move.md': `---\n"@effect/sql-pg": minor\n---\n\nClassify PostgreSQL native/SQLSTATE failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
4
+ '.changeset/curly-buckets-argue.md': `---\n"@effect/sql-mysql2": minor\n---\n\nClassify mysql2 native errno failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
5
+ '.changeset/green-maps-think.md': `---\n"@effect/sql-mssql": minor\n---\n\nClassify MSSQL native error-number failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
6
+ '.changeset/loud-gorillas-grab.md': `---\n"@effect/sql-sqlite-node": minor\n---\n\nClassify sqlite-node native SQLite failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
7
+ '.changeset/late-brooms-decide.md': `---\n"@effect/sql-sqlite-bun": minor\n---\n\nClassify sqlite-bun native SQLite failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
8
+ '.changeset/giant-dryers-sing.md': `---\n"@effect/sql-sqlite-wasm": minor\n---\n\nClassify sqlite-wasm native SQLite failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
9
+ '.changeset/nice-walls-pull.md': `---\n"@effect/sql-sqlite-do": minor\n---\n\nClassify durable-object SQLite failures into structured \\`SqlError\\` reasons with Unknown fallback and migrate driver construction to reason-based \\`SqlError\\`.`,
10
+ '.changeset/flat-spoons-chew.md': `---\n"@effect/sql-sqlite-react-native": minor\n---\n\nClassify react-native SQLite failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`,
11
+ '.changeset/metal-geese-fly.md': `---\n"@effect/sql-d1": minor\n---\n\nMigrate D1 driver \\`SqlError\\` construction to the reason-based shape and classify native failures into structured reasons with Unknown fallback when SQLite codes are unavailable.`,
12
+ '.changeset/clean-lions-hear.md': `---\n"@effect/sql-libsql": minor\n---\n\nClassify libSQL native failures into structured \\`SqlError\\` reasons (using SQLite code mapping where available) and migrate driver construction to reason-based \\`SqlError\\`.`,
13
+ '.changeset/fresh-bears-trace.md': `---\n"@effect/sql-clickhouse": minor\n---\n\nClassify ClickHouse native failures into structured \\`SqlError\\` reasons and migrate driver construction to the reason-based \\`SqlError\\` shape.`
14
+ }
15
+ await Promise.all(Object.entries(files).map(([path, content]) => writeFile({ path, content })))
16
+ console.log('wrote', Object.keys(files).length, 'changesets')
@@ -0,0 +1,16 @@
1
+ const files = {
2
+ '.changeset/green-seahorses-jump.md': `---\n"effect": minor\n---\n\nMigrate unstable \`SqlError\` to a reason-based shape (\`new SqlError({ reason })\`) with structured reason classes and retryable/classified reasons.`,
3
+ '.changeset/cool-islands-move.md': `---\n"@effect/sql-pg": minor\n---\n\nClassify PostgreSQL native/SQLSTATE failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
4
+ '.changeset/curly-buckets-argue.md': `---\n"@effect/sql-mysql2": minor\n---\n\nClassify mysql2 native errno failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
5
+ '.changeset/green-maps-think.md': `---\n"@effect/sql-mssql": minor\n---\n\nClassify MSSQL native error-number failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
6
+ '.changeset/loud-gorillas-grab.md': `---\n"@effect/sql-sqlite-node": minor\n---\n\nClassify sqlite-node native SQLite failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
7
+ '.changeset/late-brooms-decide.md': `---\n"@effect/sql-sqlite-bun": minor\n---\n\nClassify sqlite-bun native SQLite failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
8
+ '.changeset/giant-dryers-sing.md': `---\n"@effect/sql-sqlite-wasm": minor\n---\n\nClassify sqlite-wasm native SQLite failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
9
+ '.changeset/nice-walls-pull.md': `---\n"@effect/sql-sqlite-do": minor\n---\n\nClassify durable-object SQLite failures into structured \`SqlError\` reasons with Unknown fallback and migrate driver construction to reason-based \`SqlError\`.`,
10
+ '.changeset/flat-spoons-chew.md': `---\n"@effect/sql-sqlite-react-native": minor\n---\n\nClassify react-native SQLite failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`,
11
+ '.changeset/metal-geese-fly.md': `---\n"@effect/sql-d1": minor\n---\n\nMigrate D1 driver \`SqlError\` construction to the reason-based shape and classify native failures into structured reasons with Unknown fallback when SQLite codes are unavailable.`,
12
+ '.changeset/clean-lions-hear.md': `---\n"@effect/sql-libsql": minor\n---\n\nClassify libSQL native failures into structured \`SqlError\` reasons (using SQLite code mapping where available) and migrate driver construction to reason-based \`SqlError\`.`,
13
+ '.changeset/fresh-bears-trace.md': `---\n"@effect/sql-clickhouse": minor\n---\n\nClassify ClickHouse native failures into structured \`SqlError\` reasons and migrate driver construction to the reason-based \`SqlError\` shape.`
14
+ }
15
+ await Promise.all(Object.entries(files).map(([path, content]) => writeFile({ path, content })))
16
+ console.log('wrote', Object.keys(files).length, 'changesets')