@lota-sdk/core 0.4.8 → 0.4.9

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 (259) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +94 -22
  3. package/src/ai-gateway/ai-gateway.ts +738 -223
  4. package/src/config/agent-defaults.ts +176 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/constants.ts +8 -2
  7. package/src/config/logger.ts +286 -19
  8. package/src/config/thread-defaults.ts +33 -21
  9. package/src/create-runtime.ts +725 -387
  10. package/src/db/base.service.ts +52 -28
  11. package/src/db/cursor-pagination.ts +71 -30
  12. package/src/db/memory-store.helpers.ts +4 -7
  13. package/src/db/memory-store.ts +856 -598
  14. package/src/db/memory.ts +398 -275
  15. package/src/db/record-id.ts +32 -10
  16. package/src/db/schema-fingerprint.ts +30 -12
  17. package/src/db/service-normalization.ts +255 -0
  18. package/src/db/service.ts +726 -761
  19. package/src/db/startup.ts +140 -66
  20. package/src/db/transaction-conflict.ts +15 -0
  21. package/src/effect/awaitable-effect.ts +87 -0
  22. package/src/effect/errors.ts +121 -0
  23. package/src/effect/helpers.ts +98 -0
  24. package/src/effect/index.ts +22 -0
  25. package/src/effect/layers.ts +228 -0
  26. package/src/effect/runtime-ref.ts +25 -0
  27. package/src/effect/runtime.ts +31 -0
  28. package/src/effect/services.ts +57 -0
  29. package/src/effect/zod.ts +43 -0
  30. package/src/embeddings/provider.ts +122 -76
  31. package/src/index.ts +46 -1
  32. package/src/openrouter/direct-provider.ts +11 -35
  33. package/src/queues/autonomous-job.queue.ts +130 -74
  34. package/src/queues/context-compaction.queue.ts +60 -15
  35. package/src/queues/delayed-node-promotion.queue.ts +52 -15
  36. package/src/queues/document-processor.queue.ts +52 -77
  37. package/src/queues/memory-consolidation.queue.ts +47 -32
  38. package/src/queues/organization-learning.queue.ts +13 -4
  39. package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
  40. package/src/queues/plan-scheduler.queue.ts +107 -31
  41. package/src/queues/post-chat-memory.queue.ts +66 -24
  42. package/src/queues/queue-factory.ts +142 -52
  43. package/src/queues/standalone-worker.ts +39 -0
  44. package/src/queues/title-generation.queue.ts +54 -9
  45. package/src/redis/connection.ts +84 -32
  46. package/src/redis/index.ts +6 -8
  47. package/src/redis/org-memory-lock.ts +60 -27
  48. package/src/redis/redis-lease-lock.ts +200 -121
  49. package/src/redis/runtime-connection.ts +10 -0
  50. package/src/redis/stream-context.ts +84 -46
  51. package/src/runtime/agent-identity-overrides.ts +2 -2
  52. package/src/runtime/agent-runtime-policy.ts +4 -1
  53. package/src/runtime/agent-stream-helpers.ts +20 -9
  54. package/src/runtime/chat-run-orchestration.ts +102 -19
  55. package/src/runtime/chat-run-registry.ts +36 -2
  56. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  57. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
  58. package/src/runtime/execution-plan-visibility.ts +2 -2
  59. package/src/runtime/execution-plan.ts +42 -15
  60. package/src/runtime/graph-designer.ts +11 -7
  61. package/src/runtime/helper-model.ts +135 -48
  62. package/src/runtime/index.ts +7 -7
  63. package/src/runtime/indexed-repositories-policy.ts +3 -3
  64. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
  65. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  66. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
  67. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  68. package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
  69. package/src/runtime/plugin-resolution.ts +144 -24
  70. package/src/runtime/plugin-types.ts +9 -1
  71. package/src/runtime/post-turn-side-effects.ts +197 -130
  72. package/src/runtime/retrieval-adapters.ts +38 -4
  73. package/src/runtime/runtime-config.ts +150 -61
  74. package/src/runtime/runtime-extensions.ts +21 -34
  75. package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
  76. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
  77. package/src/runtime/social-chat/social-chat.ts +594 -0
  78. package/src/runtime/specialist-runner.ts +36 -10
  79. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
  80. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  81. package/src/runtime/thread-chat-helpers.ts +2 -2
  82. package/src/runtime/thread-plan-turn.ts +2 -1
  83. package/src/runtime/thread-turn-context.ts +172 -94
  84. package/src/runtime/turn-lifecycle.ts +93 -27
  85. package/src/services/agent-activity.service.ts +287 -203
  86. package/src/services/agent-executor.service.ts +329 -217
  87. package/src/services/artifact.service.ts +225 -148
  88. package/src/services/attachment.service.ts +137 -115
  89. package/src/services/autonomous-job.service.ts +888 -491
  90. package/src/services/chat-run-registry.service.ts +11 -1
  91. package/src/services/context-compaction.service.ts +136 -86
  92. package/src/services/document-chunk.service.ts +162 -90
  93. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  94. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  95. package/src/services/execution-plan/execution-plan-graph.ts +256 -0
  96. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  97. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  98. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  99. package/src/services/feedback-loop.service.ts +132 -76
  100. package/src/services/global-orchestrator.service.ts +80 -170
  101. package/src/services/graph-full-routing.ts +182 -0
  102. package/src/services/index.ts +18 -21
  103. package/src/services/institutional-memory.service.ts +220 -123
  104. package/src/services/learned-skill.service.ts +364 -259
  105. package/src/services/memory/memory-conversation.ts +95 -0
  106. package/src/services/memory/memory-org-memory.ts +39 -0
  107. package/src/services/memory/memory-preseeded.ts +80 -0
  108. package/src/services/memory/memory-rerank.ts +297 -0
  109. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
  110. package/src/services/memory/memory.service.ts +692 -0
  111. package/src/services/memory/rerank.service.ts +209 -0
  112. package/src/services/monitoring-window.service.ts +92 -70
  113. package/src/services/mutating-approval.service.ts +62 -53
  114. package/src/services/node-workspace.service.ts +141 -98
  115. package/src/services/notification.service.ts +17 -16
  116. package/src/services/organization-member.service.ts +120 -66
  117. package/src/services/organization.service.ts +144 -51
  118. package/src/services/ownership-dispatcher.service.ts +415 -264
  119. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  120. package/src/services/plan/plan-agent-query.service.ts +322 -0
  121. package/src/services/plan/plan-approval.service.ts +102 -0
  122. package/src/services/plan/plan-artifact.service.ts +60 -0
  123. package/src/services/plan/plan-builder.service.ts +76 -0
  124. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  125. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  126. package/src/services/plan/plan-completion-side-effects.ts +175 -0
  127. package/src/services/plan/plan-coordination.service.ts +181 -0
  128. package/src/services/plan/plan-cycle.service.ts +398 -0
  129. package/src/services/plan/plan-deadline.service.ts +547 -0
  130. package/src/services/plan/plan-event-delivery.service.ts +261 -0
  131. package/src/services/plan/plan-executor-context.ts +35 -0
  132. package/src/services/plan/plan-executor-graph.ts +475 -0
  133. package/src/services/plan/plan-executor-helpers.ts +322 -0
  134. package/src/services/plan/plan-executor-persistence.ts +209 -0
  135. package/src/services/plan/plan-executor.service.ts +1654 -0
  136. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  137. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  138. package/src/services/plan/plan-run-serialization.ts +15 -0
  139. package/src/services/plan/plan-run.service.ts +644 -0
  140. package/src/services/plan/plan-scheduler.service.ts +385 -0
  141. package/src/services/plan/plan-template.service.ts +224 -0
  142. package/src/services/plan/plan-transaction-events.ts +33 -0
  143. package/src/services/plan/plan-validator.service.ts +907 -0
  144. package/src/services/plan/plan-workspace.service.ts +125 -0
  145. package/src/services/plugin-executor.service.ts +97 -68
  146. package/src/services/quality-metrics.service.ts +112 -94
  147. package/src/services/queue-job.service.ts +296 -230
  148. package/src/services/recent-activity-title.service.ts +65 -36
  149. package/src/services/recent-activity.service.ts +274 -259
  150. package/src/services/skill-resolver.service.ts +38 -12
  151. package/src/services/social-chat-history.service.ts +176 -125
  152. package/src/services/system-executor.service.ts +91 -61
  153. package/src/services/thread/thread-active-run.ts +203 -0
  154. package/src/services/thread/thread-bootstrap.ts +369 -0
  155. package/src/services/thread/thread-listing.ts +198 -0
  156. package/src/services/thread/thread-memory-block.ts +117 -0
  157. package/src/services/thread/thread-message.service.ts +363 -0
  158. package/src/services/thread/thread-record-store.ts +155 -0
  159. package/src/services/thread/thread-title.service.ts +74 -0
  160. package/src/services/thread/thread-turn-execution.ts +280 -0
  161. package/src/services/thread/thread-turn-message-context.ts +73 -0
  162. package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
  163. package/src/services/thread/thread-turn-streaming.ts +402 -0
  164. package/src/services/thread/thread-turn-tracing.ts +35 -0
  165. package/src/services/thread/thread-turn.ts +343 -0
  166. package/src/services/thread/thread.service.ts +335 -0
  167. package/src/services/user.service.ts +82 -32
  168. package/src/services/write-intent-validator.service.ts +63 -51
  169. package/src/storage/attachment-parser.ts +69 -27
  170. package/src/storage/attachment-storage.service.ts +331 -275
  171. package/src/storage/generated-document-storage.service.ts +66 -34
  172. package/src/system-agents/agent-result.ts +3 -1
  173. package/src/system-agents/context-compaction.agent.ts +2 -2
  174. package/src/system-agents/delegated-agent-factory.ts +159 -90
  175. package/src/system-agents/memory-reranker.agent.ts +2 -2
  176. package/src/system-agents/memory.agent.ts +2 -2
  177. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  178. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  179. package/src/system-agents/skill-extractor.agent.ts +2 -2
  180. package/src/system-agents/skill-manager.agent.ts +2 -2
  181. package/src/system-agents/thread-router.agent.ts +157 -113
  182. package/src/system-agents/title-generator.agent.ts +2 -2
  183. package/src/tools/execution-plan.tool.ts +220 -161
  184. package/src/tools/fetch-webpage.tool.ts +21 -17
  185. package/src/tools/firecrawl-client.ts +16 -6
  186. package/src/tools/index.ts +1 -0
  187. package/src/tools/memory-block.tool.ts +14 -6
  188. package/src/tools/plan-approval.tool.ts +49 -47
  189. package/src/tools/read-file-parts.tool.ts +44 -33
  190. package/src/tools/remember-memory.tool.ts +65 -45
  191. package/src/tools/search-web.tool.ts +26 -22
  192. package/src/tools/search.tool.ts +41 -29
  193. package/src/tools/team-think.tool.ts +124 -83
  194. package/src/tools/user-questions.tool.ts +4 -3
  195. package/src/tools/web-tool-shared.ts +6 -0
  196. package/src/utils/async.ts +17 -23
  197. package/src/utils/crypto.ts +21 -0
  198. package/src/utils/date-time.ts +40 -1
  199. package/src/utils/errors.ts +95 -16
  200. package/src/utils/hono-error-handler.ts +24 -39
  201. package/src/utils/index.ts +2 -1
  202. package/src/utils/null-proto-record.ts +41 -0
  203. package/src/utils/sse-keepalive.ts +124 -21
  204. package/src/workers/bootstrap.ts +186 -51
  205. package/src/workers/memory-consolidation.worker.ts +325 -237
  206. package/src/workers/organization-learning.worker.ts +50 -16
  207. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  208. package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
  209. package/src/workers/skill-extraction.runner.ts +176 -93
  210. package/src/workers/utils/file-section-chunker.ts +8 -10
  211. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  212. package/src/workers/utils/repomix-file-sections.ts +2 -2
  213. package/src/workers/utils/thread-message-query.ts +97 -38
  214. package/src/workers/worker-utils.ts +56 -31
  215. package/src/config/debug-logger.ts +0 -47
  216. package/src/redis/connection-accessor.ts +0 -26
  217. package/src/runtime/context-compaction-runtime.ts +0 -87
  218. package/src/runtime/social-chat-agent-runner.ts +0 -118
  219. package/src/runtime/social-chat.ts +0 -516
  220. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  221. package/src/services/adaptive-playbook.service.ts +0 -152
  222. package/src/services/artifact-provenance.service.ts +0 -172
  223. package/src/services/chat-attachments.service.ts +0 -17
  224. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  225. package/src/services/execution-plan.service.ts +0 -1118
  226. package/src/services/memory.service.ts +0 -914
  227. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  228. package/src/services/plan-agent-query.service.ts +0 -267
  229. package/src/services/plan-approval.service.ts +0 -83
  230. package/src/services/plan-artifact.service.ts +0 -50
  231. package/src/services/plan-builder.service.ts +0 -67
  232. package/src/services/plan-checkpoint.service.ts +0 -81
  233. package/src/services/plan-completion-side-effects.ts +0 -80
  234. package/src/services/plan-coordination.service.ts +0 -157
  235. package/src/services/plan-cycle.service.ts +0 -284
  236. package/src/services/plan-deadline.service.ts +0 -430
  237. package/src/services/plan-event-delivery.service.ts +0 -166
  238. package/src/services/plan-executor.service.ts +0 -1950
  239. package/src/services/plan-run.service.ts +0 -515
  240. package/src/services/plan-scheduler.service.ts +0 -240
  241. package/src/services/plan-template.service.ts +0 -177
  242. package/src/services/plan-validator.service.ts +0 -818
  243. package/src/services/plan-workspace.service.ts +0 -83
  244. package/src/services/rerank.service.ts +0 -156
  245. package/src/services/thread-message.service.ts +0 -275
  246. package/src/services/thread-plan-registry.service.ts +0 -22
  247. package/src/services/thread-title.service.ts +0 -39
  248. package/src/services/thread-turn-preparation.service.ts +0 -1147
  249. package/src/services/thread-turn.ts +0 -172
  250. package/src/services/thread.service.ts +0 -869
  251. package/src/utils/env.ts +0 -8
  252. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  253. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  254. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  255. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  256. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  257. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  258. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  259. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -1,6 +1,3 @@
1
- import { readdir } from 'node:fs/promises'
2
- import path from 'node:path'
3
-
4
1
  import { RepositoryStructureArtifactSchema, RepositoryStructureSummarySchema } from '@lota-sdk/shared'
5
2
  import type {
6
3
  RepositoryStructureArtifact,
@@ -9,6 +6,11 @@ import type {
9
6
  RepositoryStructureSignal,
10
7
  RepositoryStructureSummary,
11
8
  } from '@lota-sdk/shared'
9
+ import type { Cause } from 'effect'
10
+ import { Effect } from 'effect'
11
+
12
+ import { effectTryPromise } from '../../effect/helpers'
13
+ import { nowIsoDateTimeString } from '../../utils/date-time'
12
14
 
13
15
  const EXTRACTOR_VERSION = 'repository-structure-extractor.v1'
14
16
  const IGNORED_DIR_NAMES = new Set([
@@ -54,6 +56,23 @@ type PackageJson = {
54
56
  devDependencies?: Record<string, string>
55
57
  }
56
58
 
59
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
60
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
61
+ }
62
+
63
+ function pickStringRecord(value: unknown): Record<string, string> | undefined {
64
+ if (!isPlainRecord(value)) return undefined
65
+
66
+ const result: Record<string, string> = {}
67
+ for (const [key, entry] of Object.entries(value)) {
68
+ if (typeof entry === 'string') {
69
+ result[key] = entry
70
+ }
71
+ }
72
+
73
+ return Object.keys(result).length > 0 ? result : undefined
74
+ }
75
+
57
76
  type ExtractParams = {
58
77
  rootDir: string
59
78
  repositoryKey: string
@@ -70,7 +89,35 @@ type ExtractResult = {
70
89
  }
71
90
 
72
91
  function normalizePath(value: string): string {
73
- return value.replaceAll(path.sep, '/')
92
+ return value.replaceAll('\\', '/').replaceAll(/\/+/g, '/')
93
+ }
94
+
95
+ function joinPath(...segments: string[]): string {
96
+ return normalizePath(
97
+ segments
98
+ .map((segment) => segment.trim())
99
+ .filter((segment) => segment.length > 0)
100
+ .join('/'),
101
+ )
102
+ }
103
+
104
+ function extname(filePath: string): string {
105
+ const base = basename(filePath)
106
+ const dotIndex = base.lastIndexOf('.')
107
+ return dotIndex > 0 ? base.slice(dotIndex) : ''
108
+ }
109
+
110
+ function dirname(filePath: string): string {
111
+ const normalized = normalizePath(filePath).replace(/\/+$/, '')
112
+ const slashIndex = normalized.lastIndexOf('/')
113
+ if (slashIndex <= 0) return '.'
114
+ return normalized.slice(0, slashIndex)
115
+ }
116
+
117
+ function basename(filePath: string): string {
118
+ const normalized = normalizePath(filePath).replace(/\/+$/, '')
119
+ const slashIndex = normalized.lastIndexOf('/')
120
+ return slashIndex >= 0 ? normalized.slice(slashIndex + 1) : normalized
74
121
  }
75
122
 
76
123
  function unique<T>(values: T[]): T[] {
@@ -99,31 +146,62 @@ function createSignal(params: {
99
146
  }
100
147
  }
101
148
 
102
- async function collectRelativeFilePaths(rootDir: string, currentDir = ''): Promise<string[]> {
103
- const absoluteDir = path.join(rootDir, currentDir)
104
- const entries = await readdir(absoluteDir, { withFileTypes: true })
105
- const filePaths: string[] = []
106
-
107
- for (const entry of entries) {
108
- const relativePath = normalizePath(path.join(currentDir, entry.name))
109
- if (entry.isDirectory()) {
110
- if (IGNORED_DIR_NAMES.has(entry.name)) continue
111
- filePaths.push(...(await collectRelativeFilePaths(rootDir, relativePath)))
112
- continue
149
+ function collectRelativeFilePathsEffect(
150
+ rootDir: string,
151
+ currentDir = '',
152
+ ): Effect.Effect<string[], Cause.UnknownError, never> {
153
+ return Effect.gen(function* () {
154
+ const absoluteDir = currentDir ? joinPath(rootDir, currentDir) : rootDir
155
+ const entries = yield* effectTryPromise(() =>
156
+ Array.fromAsync(new Bun.Glob('*').scan({ cwd: absoluteDir, onlyFiles: false, absolute: false })),
157
+ )
158
+
159
+ const filePaths: string[] = []
160
+ for (const entry of entries) {
161
+ const relativePath = currentDir ? joinPath(currentDir, entry) : normalizePath(entry)
162
+ const stats = yield* effectTryPromise(() => Bun.file(joinPath(rootDir, relativePath)).stat())
163
+
164
+ if (stats.isDirectory()) {
165
+ if (IGNORED_DIR_NAMES.has(entry)) continue
166
+ const nested = yield* collectRelativeFilePathsEffect(rootDir, relativePath)
167
+ filePaths.push(...nested)
168
+ continue
169
+ }
170
+
171
+ filePaths.push(relativePath)
113
172
  }
114
- filePaths.push(relativePath)
115
- }
116
173
 
117
- return filePaths
174
+ return filePaths
175
+ })
118
176
  }
119
177
 
120
- async function readPackageJson(rootDir: string, relativePath: string): Promise<PackageJson | null> {
121
- try {
122
- const raw = await Bun.file(path.join(rootDir, relativePath)).text()
123
- return JSON.parse(raw) as PackageJson
124
- } catch {
125
- return null
126
- }
178
+ function readPackageJsonEffect(
179
+ rootDir: string,
180
+ relativePath: string,
181
+ ): Effect.Effect<PackageJson | null, Cause.UnknownError, never> {
182
+ return effectTryPromise(() => Bun.file(joinPath(rootDir, relativePath)).text()).pipe(
183
+ Effect.map((raw) => {
184
+ try {
185
+ const parsed: unknown = JSON.parse(raw)
186
+ if (!isPlainRecord(parsed)) return null
187
+
188
+ const packageJson: PackageJson = {
189
+ ...(typeof parsed.name === 'string' ? { name: parsed.name } : {}),
190
+ ...(typeof parsed.private === 'boolean' ? { private: parsed.private } : {}),
191
+ }
192
+ const scripts = pickStringRecord(parsed.scripts)
193
+ if (scripts) packageJson.scripts = scripts
194
+ const dependencies = pickStringRecord(parsed.dependencies)
195
+ if (dependencies) packageJson.dependencies = dependencies
196
+ const devDependencies = pickStringRecord(parsed.devDependencies)
197
+ if (devDependencies) packageJson.devDependencies = devDependencies
198
+ return packageJson
199
+ } catch {
200
+ return null
201
+ }
202
+ }),
203
+ Effect.catch(() => Effect.succeed(null)),
204
+ )
127
205
  }
128
206
 
129
207
  function gatherTopLevelEntries(filePaths: string[]): string[] {
@@ -137,7 +215,7 @@ function gatherLanguageSignals(filePaths: string[]): RepositoryStructureSignal[]
137
215
  const languageEvidence = new Map<string, string[]>()
138
216
 
139
217
  for (const filePath of filePaths) {
140
- const language = LANGUAGE_BY_EXTENSION[path.extname(filePath).toLowerCase()]
218
+ const language = LANGUAGE_BY_EXTENSION[extname(filePath).toLowerCase()]
141
219
  if (!language) continue
142
220
  const entries = languageEvidence.get(language) ?? []
143
221
  entries.push(filePath)
@@ -354,7 +432,7 @@ function inferComponentLanguages(filePaths: string[], componentPath: string): st
354
432
  )
355
433
  return unique(
356
434
  componentFiles
357
- .map((filePath) => LANGUAGE_BY_EXTENSION[path.extname(filePath).toLowerCase()])
435
+ .map((filePath) => LANGUAGE_BY_EXTENSION[extname(filePath).toLowerCase()])
358
436
  .filter((value): value is string => Boolean(value)),
359
437
  ).slice(0, 20)
360
438
  }
@@ -398,245 +476,256 @@ function buildStructureMarkdown(params: {
398
476
  ].join('\n')
399
477
  }
400
478
 
401
- export async function extractRepositoryStructure(params: ExtractParams): Promise<ExtractResult> {
402
- const filePaths = await collectRelativeFilePaths(params.rootDir)
403
- const topLevelEntries = gatherTopLevelEntries(filePaths)
404
- const packageJsonPaths = filePaths.filter((filePath) => filePath.endsWith('package.json')).slice(0, 200)
405
- const packageJsons = (
406
- await Promise.all(
407
- packageJsonPaths.map(async (packageJsonPath) => {
408
- const data = await readPackageJson(params.rootDir, packageJsonPath)
409
- return data ? { path: packageJsonPath, data } : null
410
- }),
479
+ function extractRepositoryStructureEffect(
480
+ params: ExtractParams,
481
+ ): Effect.Effect<ExtractResult, Cause.UnknownError, never> {
482
+ return Effect.gen(function* () {
483
+ const filePaths = yield* collectRelativeFilePathsEffect(params.rootDir)
484
+ const topLevelEntries = gatherTopLevelEntries(filePaths)
485
+ const packageJsonPaths = filePaths.filter((filePath) => filePath.endsWith('package.json')).slice(0, 200)
486
+ const packageJsons = yield* Effect.forEach(packageJsonPaths, (packageJsonPath) =>
487
+ readPackageJsonEffect(params.rootDir, packageJsonPath).pipe(
488
+ Effect.map((data) => (data ? { path: packageJsonPath, data } : null)),
489
+ ),
490
+ )
491
+ const typedPackageJsons = packageJsons.filter(
492
+ (value): value is { path: string; data: PackageJson } => value !== null,
411
493
  )
412
- ).filter((value): value is { path: string; data: PackageJson } => value !== null)
413
-
414
- const packageManagers = inferPackageManagerSignals(filePaths)
415
- const components: RepositoryStructureComponent[] = packageJsons.map((packageJson) => {
416
- const componentPath = packageJson.path === 'package.json' ? '.' : normalizePath(path.dirname(packageJson.path))
417
- return {
418
- kind: inferComponentKind(componentPath, packageJson.data),
419
- name: packageJson.data.name?.trim() || path.basename(componentPath === '.' ? params.rootDir : componentPath),
420
- path: componentPath,
421
- packageManager: packageManagers[0]?.label,
422
- languages: inferComponentLanguages(filePaths, componentPath),
423
- frameworks: inferComponentFrameworks(packageJson.data),
424
- reason: 'Detected a package manifest in this directory.',
425
- evidencePaths: [packageJson.path],
426
- }
427
- })
428
494
 
429
- const languages = gatherLanguageSignals(filePaths)
430
- const frameworks = inferFrameworkSignals(packageJsons)
431
- const routeSignals = buildPathSignals({
432
- keyPrefix: 'route',
433
- label: 'Routes',
434
- reason: 'Detected route-related directories or files.',
435
- filePaths: truncate(
436
- findPaths(filePaths, (filePath) => /(^|\/)(routes|pages|app\/routes)\//.test(filePath)),
437
- 20,
438
- ),
439
- })
440
- const apiSignals = buildPathSignals({
441
- keyPrefix: 'api',
442
- label: 'API',
443
- reason: 'Detected API or server route surfaces.',
444
- filePaths: truncate(
445
- findPaths(filePaths, (filePath) => /(^|\/)(api|server|openapi|rpc)\//.test(filePath)),
446
- 20,
447
- ),
448
- })
449
- const databaseSignals = buildPathSignals({
450
- keyPrefix: 'database',
451
- label: 'Database',
452
- reason: 'Detected database schemas, migrations, or data-access directories.',
453
- filePaths: truncate(
454
- findPaths(filePaths, (filePath) => /(^|\/)(db|database|migrations|prisma|drizzle|schema)\b/.test(filePath)),
455
- 20,
456
- ),
457
- })
458
- const authSignals = buildPathSignals({
459
- keyPrefix: 'auth',
460
- label: 'Auth',
461
- reason: 'Detected authentication-related code or configuration.',
462
- filePaths: truncate(
463
- findPaths(filePaths, (filePath) => /(^|\/)(auth|better-auth|clerk|next-auth)\b/.test(filePath)),
464
- 20,
465
- ),
466
- })
467
- const jobSignals = buildPathSignals({
468
- keyPrefix: 'job',
469
- label: 'Jobs',
470
- reason: 'Detected jobs, queues, or worker execution surfaces.',
471
- filePaths: truncate(
472
- findPaths(filePaths, (filePath) => /(^|\/)(jobs|queues|workers?)\b/.test(filePath)),
473
- 20,
474
- ),
475
- })
476
- const envFiles = buildPathSignals({
477
- keyPrefix: 'env',
478
- label: 'Environment',
479
- reason: 'Detected environment variable files.',
480
- filePaths: truncate(
481
- findPaths(filePaths, (filePath) => /(^|\/)\.env(\.|$)/.test(filePath)),
482
- 20,
483
- ),
484
- })
485
- const dockerFiles = buildPathSignals({
486
- keyPrefix: 'docker',
487
- label: 'Docker',
488
- reason: 'Detected Docker build or compose files.',
489
- filePaths: truncate(
490
- findPaths(filePaths, (filePath) => /(Dockerfile|docker-compose|compose\.ya?ml|\.dockerignore)/i.test(filePath)),
491
- 20,
492
- ),
493
- })
494
- const ciCdFiles = buildPathSignals({
495
- keyPrefix: 'cicd',
496
- label: 'CI/CD',
497
- reason: 'Detected continuous integration or delivery workflow files.',
498
- filePaths: truncate(
499
- findPaths(filePaths, (filePath) => /^\.github\/workflows\/|\.gitlab-ci\.yml$|circleci\//.test(filePath)),
500
- 20,
501
- ),
502
- })
503
- const deploymentFiles = buildPathSignals({
504
- keyPrefix: 'deploy',
505
- label: 'Deployment',
506
- reason: 'Detected deployment platform or infrastructure descriptors.',
507
- filePaths: truncate(
508
- findPaths(filePaths, (filePath) =>
509
- /(vercel\.json|fly\.toml|render\.ya?ml|netlify\.toml|railway\.json|k8s|helm)/i.test(filePath),
495
+ const packageManagers = inferPackageManagerSignals(filePaths)
496
+ const components: RepositoryStructureComponent[] = typedPackageJsons.map((packageJson) => {
497
+ const componentPath = packageJson.path === 'package.json' ? '.' : normalizePath(dirname(packageJson.path))
498
+ return {
499
+ kind: inferComponentKind(componentPath, packageJson.data),
500
+ name: packageJson.data.name?.trim() || basename(componentPath === '.' ? params.rootDir : componentPath),
501
+ path: componentPath,
502
+ packageManager: packageManagers[0]?.label,
503
+ languages: inferComponentLanguages(filePaths, componentPath),
504
+ frameworks: inferComponentFrameworks(packageJson.data),
505
+ reason: 'Detected a package manifest in this directory.',
506
+ evidencePaths: [packageJson.path],
507
+ }
508
+ })
509
+
510
+ const languages = gatherLanguageSignals(filePaths)
511
+ const frameworks = inferFrameworkSignals(typedPackageJsons)
512
+ const routeSignals = buildPathSignals({
513
+ keyPrefix: 'route',
514
+ label: 'Routes',
515
+ reason: 'Detected route-related directories or files.',
516
+ filePaths: truncate(
517
+ findPaths(filePaths, (filePath) => /(^|\/)(routes|pages|app\/routes)\//.test(filePath)),
518
+ 20,
510
519
  ),
511
- 20,
512
- ),
513
- })
514
- const testSignals = buildPathSignals({
515
- keyPrefix: 'test',
516
- label: 'Tests',
517
- reason: 'Detected repository test files.',
518
- filePaths: truncate(
519
- findPaths(filePaths, (filePath) => /(^|\/)(__tests__|tests?)\/|(\.test\.|\.(spec)\.)/.test(filePath)),
520
- 20,
521
- ),
522
- })
523
- const lintSignals = buildPathSignals({
524
- keyPrefix: 'lint',
525
- label: 'Linting',
526
- reason: 'Detected linting configuration files.',
527
- filePaths: truncate(
528
- findPaths(
529
- filePaths,
530
- (filePath) => /(eslint|oxlint|biome|lint)/i.test(filePath) && /\.(json|js|ts|mjs|cjs|yaml|yml)$/.test(filePath),
520
+ })
521
+ const apiSignals = buildPathSignals({
522
+ keyPrefix: 'api',
523
+ label: 'API',
524
+ reason: 'Detected API or server route surfaces.',
525
+ filePaths: truncate(
526
+ findPaths(filePaths, (filePath) => /(^|\/)(api|server|openapi|rpc)\//.test(filePath)),
527
+ 20,
531
528
  ),
532
- 20,
533
- ),
534
- })
535
- const formatSignals = buildPathSignals({
536
- keyPrefix: 'format',
537
- label: 'Formatting',
538
- reason: 'Detected code-formatting configuration files.',
539
- filePaths: truncate(
540
- findPaths(
541
- filePaths,
542
- (filePath) => /(prettier|oxfmt|biome)/i.test(filePath) && /\.(json|js|ts|mjs|cjs|yaml|yml)$/.test(filePath),
529
+ })
530
+ const databaseSignals = buildPathSignals({
531
+ keyPrefix: 'database',
532
+ label: 'Database',
533
+ reason: 'Detected database schemas, migrations, or data-access directories.',
534
+ filePaths: truncate(
535
+ findPaths(filePaths, (filePath) => /(^|\/)(db|database|migrations|prisma|drizzle|schema)\b/.test(filePath)),
536
+ 20,
543
537
  ),
544
- 20,
545
- ),
546
- })
547
- const typecheckSignals = buildPathSignals({
548
- keyPrefix: 'typecheck',
549
- label: 'Typechecking',
550
- reason: 'Detected typechecking configuration files.',
551
- filePaths: truncate(
552
- findPaths(filePaths, (filePath) => /(tsconfig|mypy|pyright)/i.test(filePath)),
553
- 20,
554
- ),
555
- })
556
- const entryPoints = buildPathSignals({
557
- keyPrefix: 'entry',
558
- label: 'Entry point',
559
- reason: 'Detected likely application entry points.',
560
- filePaths: truncate(
561
- findPaths(filePaths, (filePath) => /(^|\/)(main|index|app|server|worker)\.(ts|tsx|js|jsx|py|go)$/.test(filePath)),
562
- 20,
563
- ),
564
- })
565
-
566
- const repoShape = inferRepoShape({ topLevelEntries, components, frameworks, filePaths })
567
-
568
- const repoStructure = RepositoryStructureArtifactSchema.parse({
569
- schemaVersion: 'repository-structure.v1',
570
- extractorVersion: EXTRACTOR_VERSION,
571
- generatedAt: new Date().toISOString(),
572
- repository: {
573
- repositoryKey: params.repositoryKey,
574
- repositoryFullName: params.repositoryFullName,
575
- defaultBranch: params.defaultBranch,
576
- indexBranch: params.indexBranch,
577
- commitSha: params.commitSha,
578
- },
579
- topLevelEntries,
580
- repoShape,
581
- packageManagers,
582
- languages,
583
- frameworks,
584
- components: truncate(components, 200),
585
- entryPoints,
586
- routeSignals,
587
- apiSignals,
588
- databaseSignals,
589
- authSignals,
590
- jobSignals,
591
- envFiles,
592
- dockerFiles,
593
- ciCdFiles,
594
- deploymentFiles,
595
- testSignals,
596
- lintSignals,
597
- formatSignals,
598
- typecheckSignals,
599
- unknowns: repoShape.kind === 'uncertain' ? ['Repository shape could not be confidently classified.'] : [],
600
- })
538
+ })
539
+ const authSignals = buildPathSignals({
540
+ keyPrefix: 'auth',
541
+ label: 'Auth',
542
+ reason: 'Detected authentication-related code or configuration.',
543
+ filePaths: truncate(
544
+ findPaths(filePaths, (filePath) => /(^|\/)(auth|better-auth|clerk|next-auth)\b/.test(filePath)),
545
+ 20,
546
+ ),
547
+ })
548
+ const jobSignals = buildPathSignals({
549
+ keyPrefix: 'job',
550
+ label: 'Jobs',
551
+ reason: 'Detected jobs, queues, or worker execution surfaces.',
552
+ filePaths: truncate(
553
+ findPaths(filePaths, (filePath) => /(^|\/)(jobs|queues|workers?)\b/.test(filePath)),
554
+ 20,
555
+ ),
556
+ })
557
+ const envFiles = buildPathSignals({
558
+ keyPrefix: 'env',
559
+ label: 'Environment',
560
+ reason: 'Detected environment variable files.',
561
+ filePaths: truncate(
562
+ findPaths(filePaths, (filePath) => /(^|\/)\.env(\.|$)/.test(filePath)),
563
+ 20,
564
+ ),
565
+ })
566
+ const dockerFiles = buildPathSignals({
567
+ keyPrefix: 'docker',
568
+ label: 'Docker',
569
+ reason: 'Detected Docker build or compose files.',
570
+ filePaths: truncate(
571
+ findPaths(filePaths, (filePath) => /(Dockerfile|docker-compose|compose\.ya?ml|\.dockerignore)/i.test(filePath)),
572
+ 20,
573
+ ),
574
+ })
575
+ const ciCdFiles = buildPathSignals({
576
+ keyPrefix: 'cicd',
577
+ label: 'CI/CD',
578
+ reason: 'Detected continuous integration or delivery workflow files.',
579
+ filePaths: truncate(
580
+ findPaths(filePaths, (filePath) => /^\.github\/workflows\/|\.gitlab-ci\.yml$|circleci\//.test(filePath)),
581
+ 20,
582
+ ),
583
+ })
584
+ const deploymentFiles = buildPathSignals({
585
+ keyPrefix: 'deploy',
586
+ label: 'Deployment',
587
+ reason: 'Detected deployment platform or infrastructure descriptors.',
588
+ filePaths: truncate(
589
+ findPaths(filePaths, (filePath) =>
590
+ /(vercel\.json|fly\.toml|render\.ya?ml|netlify\.toml|railway\.json|k8s|helm)/i.test(filePath),
591
+ ),
592
+ 20,
593
+ ),
594
+ })
595
+ const testSignals = buildPathSignals({
596
+ keyPrefix: 'test',
597
+ label: 'Tests',
598
+ reason: 'Detected repository test files.',
599
+ filePaths: truncate(
600
+ findPaths(filePaths, (filePath) => /(^|\/)(__tests__|tests?)\/|(\.test\.|\.(spec)\.)/.test(filePath)),
601
+ 20,
602
+ ),
603
+ })
604
+ const lintSignals = buildPathSignals({
605
+ keyPrefix: 'lint',
606
+ label: 'Linting',
607
+ reason: 'Detected linting configuration files.',
608
+ filePaths: truncate(
609
+ findPaths(
610
+ filePaths,
611
+ (filePath) =>
612
+ /(eslint|oxlint|biome|lint)/i.test(filePath) && /\.(json|js|ts|mjs|cjs|yaml|yml)$/.test(filePath),
613
+ ),
614
+ 20,
615
+ ),
616
+ })
617
+ const formatSignals = buildPathSignals({
618
+ keyPrefix: 'format',
619
+ label: 'Formatting',
620
+ reason: 'Detected code-formatting configuration files.',
621
+ filePaths: truncate(
622
+ findPaths(
623
+ filePaths,
624
+ (filePath) => /(prettier|oxfmt|biome)/i.test(filePath) && /\.(json|js|ts|mjs|cjs|yaml|yml)$/.test(filePath),
625
+ ),
626
+ 20,
627
+ ),
628
+ })
629
+ const typecheckSignals = buildPathSignals({
630
+ keyPrefix: 'typecheck',
631
+ label: 'Typechecking',
632
+ reason: 'Detected typechecking configuration files.',
633
+ filePaths: truncate(
634
+ findPaths(filePaths, (filePath) => /(tsconfig|mypy|pyright)/i.test(filePath)),
635
+ 20,
636
+ ),
637
+ })
638
+ const entryPoints = buildPathSignals({
639
+ keyPrefix: 'entry',
640
+ label: 'Entry point',
641
+ reason: 'Detected likely application entry points.',
642
+ filePaths: truncate(
643
+ findPaths(filePaths, (filePath) =>
644
+ /(^|\/)(main|index|app|server|worker)\.(ts|tsx|js|jsx|py|go)$/.test(filePath),
645
+ ),
646
+ 20,
647
+ ),
648
+ })
649
+
650
+ const repoShape = inferRepoShape({ topLevelEntries, components, frameworks, filePaths })
651
+
652
+ const repoStructure = RepositoryStructureArtifactSchema.parse({
653
+ schemaVersion: 'repository-structure.v1',
654
+ extractorVersion: EXTRACTOR_VERSION,
655
+ generatedAt: nowIsoDateTimeString(),
656
+ repository: {
657
+ repositoryKey: params.repositoryKey,
658
+ repositoryFullName: params.repositoryFullName,
659
+ defaultBranch: params.defaultBranch,
660
+ indexBranch: params.indexBranch,
661
+ commitSha: params.commitSha,
662
+ },
663
+ topLevelEntries,
664
+ repoShape,
665
+ packageManagers,
666
+ languages,
667
+ frameworks,
668
+ components: truncate(components, 200),
669
+ entryPoints,
670
+ routeSignals,
671
+ apiSignals,
672
+ databaseSignals,
673
+ authSignals,
674
+ jobSignals,
675
+ envFiles,
676
+ dockerFiles,
677
+ ciCdFiles,
678
+ deploymentFiles,
679
+ testSignals,
680
+ lintSignals,
681
+ formatSignals,
682
+ typecheckSignals,
683
+ unknowns: repoShape.kind === 'uncertain' ? ['Repository shape could not be confidently classified.'] : [],
684
+ })
685
+
686
+ const structureSummary = RepositoryStructureSummarySchema.parse({
687
+ schemaVersion: 'repository-structure-summary.v1',
688
+ extractorVersion: EXTRACTOR_VERSION,
689
+ generatedAt: repoStructure.generatedAt,
690
+ repository: repoStructure.repository,
691
+ repoShape: repoStructure.repoShape.kind,
692
+ topLevelEntries: truncate(repoStructure.topLevelEntries, 20),
693
+ packageManagers: summarizeSignals(repoStructure.packageManagers),
694
+ languages: summarizeSignals(repoStructure.languages),
695
+ frameworks: summarizeSignals(repoStructure.frameworks),
696
+ components: truncate(
697
+ repoStructure.components.map((component) => ({
698
+ kind: component.kind,
699
+ name: component.name,
700
+ path: component.path,
701
+ })),
702
+ 40,
703
+ ),
704
+ entryPoints: summarizeSignals(repoStructure.entryPoints),
705
+ routeSignals: summarizeSignals(repoStructure.routeSignals),
706
+ apiSignals: summarizeSignals(repoStructure.apiSignals),
707
+ databaseSignals: summarizeSignals(repoStructure.databaseSignals),
708
+ authSignals: summarizeSignals(repoStructure.authSignals),
709
+ jobSignals: summarizeSignals(repoStructure.jobSignals),
710
+ envFiles: summarizeSignals(repoStructure.envFiles),
711
+ dockerFiles: summarizeSignals(repoStructure.dockerFiles),
712
+ ciCdFiles: summarizeSignals(repoStructure.ciCdFiles),
713
+ testSignals: summarizeSignals(repoStructure.testSignals),
714
+ lintSignals: summarizeSignals(repoStructure.lintSignals),
715
+ formatSignals: summarizeSignals(repoStructure.formatSignals),
716
+ typecheckSignals: summarizeSignals(repoStructure.typecheckSignals),
717
+ deploymentSignals: summarizeSignals(repoStructure.deploymentFiles),
718
+ unknowns: repoStructure.unknowns.slice(0, 20),
719
+ })
601
720
 
602
- const structureSummary = RepositoryStructureSummarySchema.parse({
603
- schemaVersion: 'repository-structure-summary.v1',
604
- extractorVersion: EXTRACTOR_VERSION,
605
- generatedAt: repoStructure.generatedAt,
606
- repository: repoStructure.repository,
607
- repoShape: repoStructure.repoShape.kind,
608
- topLevelEntries: truncate(repoStructure.topLevelEntries, 20),
609
- packageManagers: summarizeSignals(repoStructure.packageManagers),
610
- languages: summarizeSignals(repoStructure.languages),
611
- frameworks: summarizeSignals(repoStructure.frameworks),
612
- components: truncate(
613
- repoStructure.components.map((component) => ({
614
- kind: component.kind,
615
- name: component.name,
616
- path: component.path,
617
- })),
618
- 40,
619
- ),
620
- entryPoints: summarizeSignals(repoStructure.entryPoints),
621
- routeSignals: summarizeSignals(repoStructure.routeSignals),
622
- apiSignals: summarizeSignals(repoStructure.apiSignals),
623
- databaseSignals: summarizeSignals(repoStructure.databaseSignals),
624
- authSignals: summarizeSignals(repoStructure.authSignals),
625
- jobSignals: summarizeSignals(repoStructure.jobSignals),
626
- envFiles: summarizeSignals(repoStructure.envFiles),
627
- dockerFiles: summarizeSignals(repoStructure.dockerFiles),
628
- ciCdFiles: summarizeSignals(repoStructure.ciCdFiles),
629
- testSignals: summarizeSignals(repoStructure.testSignals),
630
- lintSignals: summarizeSignals(repoStructure.lintSignals),
631
- formatSignals: summarizeSignals(repoStructure.formatSignals),
632
- typecheckSignals: summarizeSignals(repoStructure.typecheckSignals),
633
- deploymentSignals: summarizeSignals(repoStructure.deploymentFiles),
634
- unknowns: repoStructure.unknowns.slice(0, 20),
721
+ return {
722
+ repoStructure,
723
+ structureSummary,
724
+ structureMarkdown: buildStructureMarkdown({ repoStructure, structureSummary }),
725
+ }
635
726
  })
727
+ }
636
728
 
637
- return {
638
- repoStructure,
639
- structureSummary,
640
- structureMarkdown: buildStructureMarkdown({ repoStructure, structureSummary }),
641
- }
729
+ export function extractRepositoryStructure(params: ExtractParams): Promise<ExtractResult> {
730
+ return Effect.runPromise(extractRepositoryStructureEffect(params))
642
731
  }