spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -0,0 +1,1402 @@
1
+ // CIL[BEGIN chunk_id="cilingestionpage_module_header_1784193942" type="module_header" identity="module_header" parent="none"]
2
+ // CIL[DOC-MACRO purpose="Import dependencies and icons for the CIL ingestion page"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Provides React hooks, router, apiFetch, app path hook, UI components, and lucide icons used by the page"]
4
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/functions/_shared/principal.ts\",\"\\\"]"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="module imports" errors="none" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Sets up external dependencies and icon imports required by CILIngestionPage and child functions" notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="developer"]
10
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/functions/_shared/principal.ts, \."]
11
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
12
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
13
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
14
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
15
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
16
+ import { useEffect, useState, useCallback } from 'react'
17
+ import { useParams } from 'react-router-dom'
18
+ import { apiFetch } from '@core/lib/api'
19
+ import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card'
20
+ import { Button } from '@core/components/ui/button'
21
+ import { Badge } from '@core/components/ui/badge'
22
+ import { Skeleton } from '@core/components/ui/skeleton'
23
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@core/components/ui/tooltip'
24
+ import { Input } from '@core/components/ui/input'
25
+ import { Label } from '@core/components/ui/label'
26
+ import { Progress } from '@core/components/ui/progress'
27
+ import {
28
+ CheckCircle2,
29
+ ChevronRight,
30
+ ChevronDown,
31
+ Folder,
32
+ FolderOpen,
33
+ FileText,
34
+ RefreshCw,
35
+ Play,
36
+ FileCode2,
37
+ MousePointerClick,
38
+ Code2,
39
+ BookOpen,
40
+ Zap,
41
+ GitGraph,
42
+ Sparkles,
43
+ Layers,
44
+ Network,
45
+ Info,
46
+ Save,
47
+ Settings2,
48
+ Copy,
49
+ Check,
50
+ } from 'lucide-react'
51
+
52
+
53
+
54
+ import { PageInstructions } from '../../../components/admin/PageInstructions'
55
+ // CIL[END chunk_id="cilingestionpage_module_header_1784193942"]
56
+ // CIL[BEGIN chunk_id="cilingestionpage_cilfilestatus_1784193942" type="type" identity="CilFileStatus" parent="none"]
57
+ // CIL[DOC-MACRO purpose="Define the union of possible CIL file pipeline statuses"]
58
+ // CIL[DOC-RESPONSIBILITY responsibility="Declares the string-literal type for file ingestion pipeline stages"]
59
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
60
+ // CIL[DOC-CONTRACT inputs="none" outputs="CilFileStatus type alias" errors="none" side_effects="none"]
61
+ // CIL[DOC-MICRO logic_summary="Lists ordered statuses from 2-discovered through 10-mapped" notable_decisions="[]"]
62
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
63
+ // CIL[DOC-STABILITY stability="stable"]
64
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/IntegrationsPage.tsx, .framework/functions/_shared/schema-utils.ts, \."]
65
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
66
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
67
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
68
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
69
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
70
+ type CilFileStatus =
71
+ | '2-discovered'
72
+ | '3-selected'
73
+ | '4-chunked'
74
+ | '5-documented'
75
+ | '6-embedded'
76
+ | '7-mapped'
77
+ | '8-enriched'
78
+ | '9-embedded'
79
+ | '10-mapped'
80
+
81
+ // CIL[END chunk_id="cilingestionpage_cilfilestatus_1784193942"]
82
+ // CIL[BEGIN chunk_id="cilingestionpage_filetreenode_1784193942" type="interface" identity="FileTreeNode" parent="none"]
83
+ // CIL[DOC-MACRO purpose="Define the recursive file tree node data shape"]
84
+ // CIL[DOC-RESPONSIBILITY responsibility="Provides a TypeScript contract for files and directories in the project tree"]
85
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
86
+ // CIL[DOC-CONTRACT inputs="none" outputs="FileTreeNode interface definition" errors="none" side_effects="none"]
87
+ // CIL[DOC-MICRO logic_summary="Describes id, name, path, type, extension, optional status, and children array" notable_decisions="[]"]
88
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
89
+ // CIL[DOC-STABILITY stability="stable"]
90
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/IntegrationsPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, \."]
91
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
92
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
93
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
94
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
95
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
96
+ interface FileTreeNode {
97
+ id?: string | null
98
+ name: string
99
+ path: string
100
+ type: 'file' | 'directory'
101
+ extension?: string
102
+ status?: CilFileStatus
103
+ children?: FileTreeNode[]
104
+ }
105
+
106
+ // CIL[END chunk_id="cilingestionpage_filetreenode_1784193942"]
107
+ // CIL[BEGIN chunk_id="cilingestionpage_project_1784193942" type="interface" identity="Project" parent="none"]
108
+ // CIL[DOC-MACRO purpose="Define the CIL project record data shape"]
109
+ // CIL[DOC-RESPONSIBILITY responsibility="Declares the TypeScript contract for a project including repo path, globs, file tree, and statuses"]
110
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[\".framework/src/pages/admin/IntegrationsPage.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
111
+ // CIL[DOC-CONTRACT inputs="none" outputs="Project interface definition" errors="none" side_effects="none"]
112
+ // CIL[DOC-MICRO logic_summary="Contains id, title, and data object with repo_path, ingestion_status, counts, globs, file_tree, selected_paths, and file_statuses" notable_decisions="[]"]
113
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
114
+ // CIL[DOC-STABILITY stability="stable"]
115
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/IntegrationsPage.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, \."]
116
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
117
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
118
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
119
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
120
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
121
+ interface Project {
122
+ id: string
123
+ title: string
124
+ data: {
125
+ repo_path: string
126
+ ingestion_status?: string
127
+ file_count?: number
128
+ chunk_count?: number
129
+ total_ingestion_cost_usd?: number
130
+ last_ingested_at?: string | null
131
+ globs?: string[]
132
+ exclude_globs?: string[]
133
+ file_tree?: FileTreeNode
134
+ selected_paths?: string[]
135
+ file_statuses?: Record<string, string>
136
+ }
137
+ }
138
+
139
+ // CIL[END chunk_id="cilingestionpage_project_1784193942"]
140
+ // CIL[BEGIN chunk_id="cilingestionpage_projectconfig_1784193942" type="interface" identity="ProjectConfig" parent="none"]
141
+ // CIL[DOC-MACRO purpose="Define the editable project configuration data shape"]
142
+ // CIL[DOC-RESPONSIBILITY responsibility="Provides a TypeScript contract for repo path, include/exclude globs, and force reprocess flag"]
143
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[\".framework/src/pages/admin/IntegrationsPage.tsx\",\".framework/functions/_shared/principal.ts\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\".framework/functions/_shared/schema-utils.ts\",\"\\\"]"]
144
+ // CIL[DOC-CONTRACT inputs="none" outputs="ProjectConfig interface definition" errors="none" side_effects="none"]
145
+ // CIL[DOC-MICRO logic_summary="Declares repo_path, globs, exclude_globs, and force_reprocess fields" notable_decisions="[]"]
146
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
147
+ // CIL[DOC-STABILITY stability="stable"]
148
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: .framework/src/pages/admin/IntegrationsPage.tsx, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/functions/_shared/schema-utils.ts, \."]
149
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
150
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
151
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
152
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
153
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
154
+ interface ProjectConfig {
155
+ repo_path: string
156
+ globs: string
157
+ exclude_globs: string
158
+ force_reprocess: boolean
159
+ }
160
+
161
+ // CIL[END chunk_id="cilingestionpage_projectconfig_1784193942"]
162
+ // CIL[BEGIN chunk_id="cilingestionpage_pipelineprogress_1784193942" type="interface" identity="PipelineProgress" parent="none"]
163
+ // CIL[DOC-MACRO purpose="Define the pipeline progress summary data shape"]
164
+ // CIL[DOC-RESPONSIBILITY responsibility="Provides a TypeScript contract for total selected files, status counts, and total chunks"]
165
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
166
+ // CIL[DOC-CONTRACT inputs="none" outputs="PipelineProgress interface definition" errors="none" side_effects="none"]
167
+ // CIL[DOC-MICRO logic_summary="Declares total_selected, by_status record, and total_chunks" notable_decisions="[]"]
168
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
169
+ // CIL[DOC-STABILITY stability="stable"]
170
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
171
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
172
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
173
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
174
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
175
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
176
+ interface PipelineProgress {
177
+ total_selected: number
178
+ by_status: Record<string, number>
179
+ total_chunks: number
180
+ }
181
+
182
+
183
+
184
+
185
+
186
+ // CIL[END chunk_id="cilingestionpage_pipelineprogress_1784193942"]
187
+ // CIL[BEGIN chunk_id="cilingestionpage_status_color_1784193942" type="constant" identity="STATUS_COLOR" parent="none"]
188
+ // CIL[DOC-MACRO purpose="Map each CIL file status to a Tailwind CSS color class string"]
189
+ // CIL[DOC-RESPONSIBILITY responsibility="Supplies visual badge color classes for pipeline status labels"]
190
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
191
+ // CIL[DOC-CONTRACT inputs="none" outputs="STATUS_COLOR record" errors="none" side_effects="none"]
192
+ // CIL[DOC-MICRO logic_summary="Maps each CilFileStatus value to a background/text color combination used by status badges" notable_decisions="[]"]
193
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
194
+ // CIL[DOC-STABILITY stability="stable"]
195
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
196
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
197
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
198
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
199
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
200
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
201
+ const STATUS_COLOR: Record<CilFileStatus, string> = {
202
+ '2-discovered': 'bg-gray-100 text-gray-600',
203
+ '3-selected': 'bg-indigo-100 text-indigo-700',
204
+ '4-chunked': 'bg-blue-100 text-blue-700',
205
+ '5-documented': 'bg-purple-100 text-purple-700',
206
+ '6-embedded': 'bg-green-100 text-green-700',
207
+ '7-mapped': 'bg-teal-100 text-teal-700',
208
+ '8-enriched': 'bg-violet-100 text-violet-700',
209
+ '9-embedded': 'bg-emerald-100 text-emerald-700',
210
+ '10-mapped': 'bg-amber-100 text-amber-700',
211
+ }
212
+
213
+ // CIL[END chunk_id="cilingestionpage_status_color_1784193942"]
214
+ // CIL[BEGIN chunk_id="cilingestionpage_pipelinestep_1784193942" type="interface" identity="PipelineStep" parent="none"]
215
+ // CIL[DOC-MACRO purpose="Define the pipeline step descriptor data shape"]
216
+ // CIL[DOC-RESPONSIBILITY responsibility="Provides a TypeScript contract for a single CIL pipeline step"]
217
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
218
+ // CIL[DOC-CONTRACT inputs="none" outputs="PipelineStep interface definition" errors="none" side_effects="none"]
219
+ // CIL[DOC-MICRO logic_summary="Declares number, key, label, description, icon, agentInstruction, optional command, targetStatus, and optional sourceStatus" notable_decisions="[]"]
220
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
221
+ // CIL[DOC-STABILITY stability="stable"]
222
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
223
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
224
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
225
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
226
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
227
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
228
+ interface PipelineStep {
229
+ number: number
230
+ key: string
231
+ label: string
232
+ description: string
233
+ icon: React.ElementType
234
+ agentInstruction: string
235
+ command?: string
236
+ targetStatus: CilFileStatus
237
+ sourceStatus?: CilFileStatus
238
+ }
239
+
240
+ // CIL[END chunk_id="cilingestionpage_pipelinestep_1784193942"]
241
+ // CIL[BEGIN chunk_id="cilingestionpage_pipeline_steps_1784193942" type="constant" identity="PIPELINE_STEPS" parent="none"]
242
+ // CIL[DOC-MACRO purpose="List all pipeline steps in order for the CIL ingestion UI"]
243
+ // CIL[DOC-RESPONSIBILITY responsibility="Supplies step definitions, icons, labels, instructions, and target statuses used by the pipeline view"]
244
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
245
+ // CIL[DOC-CONTRACT inputs="none" outputs="PIPELINE_STEPS array" errors="none" side_effects="none"]
246
+ // CIL[DOC-MICRO logic_summary="Defines 10 steps from project creation through second-pass mapping with associated icons, commands, and target statuses" notable_decisions="[]"]
247
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
248
+ // CIL[DOC-STABILITY stability="stable"]
249
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
250
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
251
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
252
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
253
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
254
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
255
+ const PIPELINE_STEPS: PipelineStep[] = [
256
+ {
257
+ number: 1,
258
+ key: 'create',
259
+ label: 'Create Project',
260
+ description: 'Project and repo path configured',
261
+ icon: FileCode2,
262
+ agentInstruction: 'Create a new CIL project in Cortex with the repository path set.',
263
+ targetStatus: '0-discovered',
264
+ },
265
+ {
266
+ number: 2,
267
+ key: 'build_tree',
268
+ label: 'Build File Tree',
269
+ description: 'Scan repository, build complete file structure',
270
+ icon: Folder,
271
+ agentInstruction: 'Run Pass 1 to walk the repository and discover all files. Use the "Run Pass 1" button or tell Devin: "Scan the repo and build the file tree."',
272
+ targetStatus: '0-discovered',
273
+ },
274
+ {
275
+ number: 3,
276
+ key: 'select',
277
+ label: 'Select Files',
278
+ description: 'Choose which files to process',
279
+ icon: MousePointerClick,
280
+ agentInstruction: 'Select files in the tree on the right. Click individual files, directories, or use Devin: "Select all TypeScript files in .framework/cli/".',
281
+ targetStatus: '3-selected',
282
+ sourceStatus: '2-discovered',
283
+ },
284
+ {
285
+ number: 4,
286
+ key: 'chunk',
287
+ label: 'Chunk',
288
+ description: 'Strip comments, apply fresh CIL markers',
289
+ icon: Code2,
290
+ agentInstruction: 'Tell Devin: "Chunk the selected files." Devin will strip all existing comments, analyze the code structure, and apply clean CIL[BEGIN]/CIL[END] markers ensuring 100% line coverage.',
291
+ command: '/cil-chunk',
292
+ targetStatus: '4-chunked',
293
+ sourceStatus: '3-selected',
294
+ },
295
+ {
296
+ number: 5,
297
+ key: 'document',
298
+ label: 'Document',
299
+ description: 'Generate single-file context documentation',
300
+ icon: BookOpen,
301
+ agentInstruction: 'Tell Devin: "Document the chunked files." Devin will generate standardized inline documentation for each chunk using only single-file context.',
302
+ command: '/cil-document',
303
+ targetStatus: '5-documented',
304
+ sourceStatus: '4-chunked',
305
+ },
306
+ {
307
+ number: 6,
308
+ key: 'first_pass_embed',
309
+ label: 'Embed (1st pass)',
310
+ description: 'Generate 3 basic vectors per chunk',
311
+ icon: Zap,
312
+ agentInstruction: 'Run from the pipeline UI — this step is not Devin-triggered. Click the Run button on Step 6.',
313
+ targetStatus: '6-embedded',
314
+ sourceStatus: '5-documented',
315
+ },
316
+ {
317
+ number: 7,
318
+ key: 'first_pass_map',
319
+ label: 'Map (1st pass)',
320
+ description: 'Create basic relationship mappings',
321
+ icon: GitGraph,
322
+ agentInstruction: 'Run from the pipeline UI — this step is not Devin-triggered. Click the Run button on Step 7.',
323
+ targetStatus: '7-mapped',
324
+ sourceStatus: '6-embedded',
325
+ },
326
+ {
327
+ number: 8,
328
+ key: 'enrich',
329
+ label: 'Enrich',
330
+ description: 'RAG enrichment with multi-file context',
331
+ icon: Sparkles,
332
+ agentInstruction: 'Tell Devin: "Enrich the documented files." Devin will perform RAG queries to discover cross-file relationships and enrich documentation with multi-file context. Requires Steps 6 and 7 to be complete.',
333
+ command: '/cil-enrich',
334
+ targetStatus: '8-enriched',
335
+ sourceStatus: '7-mapped',
336
+ },
337
+ {
338
+ number: 9,
339
+ key: 'second_pass_embed',
340
+ label: 'Embed (2nd pass)',
341
+ description: 'Generate 9 specialized vectors per chunk',
342
+ icon: Layers,
343
+ agentInstruction: 'Run from the pipeline UI — this step is not Devin-triggered. Click the Run button on Step 9.',
344
+ targetStatus: '9-embedded',
345
+ sourceStatus: '8-enriched',
346
+ },
347
+ {
348
+ number: 10,
349
+ key: 'second_pass_map',
350
+ label: 'Map (2nd pass)',
351
+ description: 'Enhanced relationship mapping',
352
+ icon: Network,
353
+ agentInstruction: 'Run from the pipeline UI — this step is not Devin-triggered. Click the Run button on Step 10.',
354
+ targetStatus: '10-mapped',
355
+ sourceStatus: '9-embedded',
356
+ },
357
+ ]
358
+
359
+
360
+
361
+ // CIL[END chunk_id="cilingestionpage_pipeline_steps_1784193942"]
362
+ // CIL[BEGIN chunk_id="cilingestionpage_collectalldirpaths_1784193942" type="function" identity="collectAllDirPaths" parent="none"]
363
+ // CIL[DOC-MACRO purpose="Recursively collect the paths of all directories in the file tree"]
364
+ // CIL[DOC-RESPONSIBILITY responsibility="Returns a flat array of directory paths for initial expansion"]
365
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
366
+ // CIL[DOC-CONTRACT inputs="none" outputs="string[]" errors="none" side_effects="none"]
367
+ // CIL[DOC-MICRO logic_summary="If node is a directory, include its path, then recurse through children" notable_decisions="[]"]
368
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
369
+ // CIL[DOC-STABILITY stability="stable"]
370
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
371
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
372
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
373
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
374
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
375
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
376
+ function collectAllDirPaths(node: FileTreeNode): string[] {
377
+ const paths: string[] = node.type === 'directory' ? [node.path] : []
378
+ if (node.children) {
379
+ for (const child of node.children) paths.push(...collectAllDirPaths(child))
380
+ }
381
+ return paths
382
+ }
383
+
384
+ // CIL[END chunk_id="cilingestionpage_collectalldirpaths_1784193942"]
385
+ // CIL[BEGIN chunk_id="cilingestionpage_countfilesintree_1784193942" type="function" identity="countFilesInTree" parent="none"]
386
+ // CIL[DOC-MACRO purpose="Count all file leaves in a file tree recursively"]
387
+ // CIL[DOC-RESPONSIBILITY responsibility="Returns the total number of file nodes under the given tree node"]
388
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
389
+ // CIL[DOC-CONTRACT inputs="none" outputs="number" errors="none" side_effects="none"]
390
+ // CIL[DOC-MICRO logic_summary="Returns 1 for files and sums counts of children for directories" notable_decisions="[]"]
391
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
392
+ // CIL[DOC-STABILITY stability="stable"]
393
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
394
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
395
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
396
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
397
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
398
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
399
+ function countFilesInTree(node: FileTreeNode): number {
400
+ if (node.type === 'file') return 1
401
+ return (node.children || []).reduce((acc, c) => acc + countFilesInTree(c), 0)
402
+ }
403
+
404
+ // CIL[END chunk_id="cilingestionpage_countfilesintree_1784193942"]
405
+ // CIL[BEGIN chunk_id="cilingestionpage_getfilestatus_1784193942" type="function" identity="getFileStatus" parent="none"]
406
+ // CIL[DOC-MACRO purpose="Resolve the pipeline status for a single file path"]
407
+ // CIL[DOC-RESPONSIBILITY responsibility="Maps a file path to its explicit status, selected status, or discovered status"]
408
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
409
+ // CIL[DOC-CONTRACT inputs="none" outputs="CilFileStatus" errors="none" side_effects="none"]
410
+ // CIL[DOC-MICRO logic_summary="Returns explicit status if present, '3-selected' if path is selected, otherwise '2-discovered'" notable_decisions="[]"]
411
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
412
+ // CIL[DOC-STABILITY stability="stable"]
413
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
414
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
415
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
416
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
417
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
418
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
419
+ function getFileStatus(path: string, fileStatuses: Record<string, string>, selectedPaths: string[]): CilFileStatus {
420
+ const explicit = fileStatuses[path] as CilFileStatus | undefined
421
+ if (explicit) return explicit
422
+
423
+ if (selectedPaths.includes(path)) return '3-selected'
424
+ return '2-discovered'
425
+ }
426
+
427
+
428
+ // CIL[END chunk_id="cilingestionpage_getfilestatus_1784193942"]
429
+ // CIL[BEGIN chunk_id="cilingestionpage_countselectedleaves_1784193942" type="function" identity="countSelectedLeaves" parent="none"]
430
+ // CIL[DOC-MACRO purpose="Count selected file leaves under a tree node"]
431
+ // CIL[DOC-RESPONSIBILITY responsibility="Returns the number of selected file nodes in the subtree"]
432
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
433
+ // CIL[DOC-CONTRACT inputs="none" outputs="number" errors="none" side_effects="none"]
434
+ // CIL[DOC-MICRO logic_summary="For files checks inclusion in selectedPaths; for directories sums child counts" notable_decisions="[]"]
435
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
436
+ // CIL[DOC-STABILITY stability="stable"]
437
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
438
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
439
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
440
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
441
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
442
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
443
+ function countSelectedLeaves(node: FileTreeNode, selectedPaths: string[]): number {
444
+ if (node.type === 'file') return selectedPaths.includes(node.path) ? 1 : 0
445
+ return (node.children || []).reduce((sum, c) => sum + countSelectedLeaves(c, selectedPaths), 0)
446
+ }
447
+
448
+
449
+ // CIL[END chunk_id="cilingestionpage_countselectedleaves_1784193942"]
450
+ // CIL[BEGIN chunk_id="cilingestionpage_countleaves_1784193942" type="function" identity="countLeaves" parent="none"]
451
+ // CIL[DOC-MACRO purpose="Count all file leaves under a tree node"]
452
+ // CIL[DOC-RESPONSIBILITY responsibility="Returns the total number of file nodes in the subtree"]
453
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
454
+ // CIL[DOC-CONTRACT inputs="none" outputs="number" errors="none" side_effects="none"]
455
+ // CIL[DOC-MICRO logic_summary="Returns 1 for files and recursively sums child leaf counts for directories" notable_decisions="[]"]
456
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
457
+ // CIL[DOC-STABILITY stability="stable"]
458
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
459
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
460
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
461
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
462
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
463
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
464
+ function countLeaves(node: FileTreeNode): number {
465
+ if (node.type === 'file') return 1
466
+ return (node.children || []).reduce((sum, c) => sum + countLeaves(c), 0)
467
+ }
468
+
469
+
470
+
471
+ // CIL[END chunk_id="cilingestionpage_countleaves_1784193942"]
472
+ // CIL[BEGIN chunk_id="cilingestionpage_treenode_1784193942" type="function" identity="TreeNode" parent="none"]
473
+ // CIL[DOC-MACRO purpose="Render a single file tree node with selection, expansion, and status"]
474
+ // CIL[DOC-RESPONSIBILITY responsibility="Displays a directory or file row, handles expand/collapse, selection toggling, and status badges"]
475
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
476
+ // CIL[DOC-CONTRACT inputs="none" outputs="JSX.Element" errors="none" side_effects="Updates UI state via parent callbacks"]
477
+ // CIL[DOC-MICRO logic_summary="Computes selection and expansion state, renders folder/file icons, checkbox, name, status badge, and recursively renders children" notable_decisions="[]"]
478
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
479
+ // CIL[DOC-STABILITY stability="stable"]
480
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
481
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
482
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
483
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
484
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
485
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
486
+ function TreeNode({
487
+ node,
488
+ selectedPaths,
489
+ expandedPaths,
490
+ fileStatuses,
491
+ onToggleSelect,
492
+ onToggleExpand,
493
+ depth = 0,
494
+ }: {
495
+ node: FileTreeNode
496
+ selectedPaths: string[]
497
+ expandedPaths: Set<string>
498
+ fileStatuses: Record<string, string>
499
+ onToggleSelect: (path: string, isDir: boolean, children?: FileTreeNode[]) => void
500
+ onToggleExpand: (path: string) => void
501
+ depth?: number
502
+ }) {
503
+ const isDir = node.type === 'directory'
504
+ const isExpanded = expandedPaths.has(node.path)
505
+ const isSelected = selectedPaths.includes(node.path)
506
+ const hasChildren = isDir && (node.children?.length || 0) > 0
507
+
508
+
509
+ const selectedLeafCount = isDir ? countSelectedLeaves(node, selectedPaths) : 0
510
+ const totalLeafCount = isDir ? countLeaves(node) : 0
511
+ const isIndeterminate = isDir && !isSelected && selectedLeafCount > 0 && selectedLeafCount < totalLeafCount
512
+ const isAllSelected = isDir && !isSelected && totalLeafCount > 0 && selectedLeafCount === totalLeafCount
513
+
514
+ const fileStatus = !isDir ? getFileStatus(node.path, fileStatuses, selectedPaths) : undefined
515
+ const statusLabel = fileStatus && fileStatus !== '2-discovered' ? fileStatus : undefined
516
+ const statusColor = fileStatus ? STATUS_COLOR[fileStatus] : undefined
517
+
518
+ return (
519
+ <div>
520
+ <div
521
+ className="flex items-center gap-1 py-0.5 px-1 rounded hover:bg-muted/50"
522
+ style={{ paddingLeft: `${depth * 16 + 4}px` }}
523
+ >
524
+ {}
525
+ {hasChildren ? (
526
+ <button
527
+ onClick={e => { e.stopPropagation(); onToggleExpand(node.path) }}
528
+ className="p-0.5 rounded hover:bg-muted shrink-0"
529
+ >
530
+ {isExpanded ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
531
+ </button>
532
+ ) : (
533
+ <span className="w-4 shrink-0" />
534
+ )}
535
+
536
+ {}
537
+ <div
538
+ className="flex items-center gap-1.5 flex-1 min-w-0 cursor-pointer"
539
+ onClick={() => onToggleSelect(node.path, isDir, node.children)}
540
+ >
541
+ {}
542
+ <div className={`h-3.5 w-3.5 rounded border flex items-center justify-center shrink-0 ${
543
+ isSelected || isAllSelected
544
+ ? 'bg-indigo-500 border-indigo-500'
545
+ : isIndeterminate
546
+ ? 'border-indigo-500 bg-indigo-50'
547
+ : 'border-muted-foreground'
548
+ }`}>
549
+ {(isSelected || isAllSelected) && <CheckCircle2 className="h-2.5 w-2.5 text-white" />}
550
+ {isIndeterminate && <div className="h-1.5 w-1.5 bg-indigo-500 rounded-sm" />}
551
+ </div>
552
+
553
+ {}
554
+ {isDir
555
+ ? isExpanded
556
+ ? <FolderOpen className={`h-3.5 w-3.5 shrink-0 ${isSelected || isAllSelected || isIndeterminate ? 'text-indigo-500' : 'text-amber-500'}`} />
557
+ : <Folder className={`h-3.5 w-3.5 shrink-0 ${isSelected || isAllSelected || isIndeterminate ? 'text-indigo-500' : 'text-amber-500'}`} />
558
+ : <FileText className="h-3.5 w-3.5 text-slate-400 shrink-0" />
559
+ }
560
+
561
+ {}
562
+ <span className={`text-xs truncate ${isSelected || isAllSelected || isIndeterminate ? 'text-indigo-700 dark:text-indigo-300 font-medium' : ''}`}>
563
+ {node.name}
564
+ </span>
565
+
566
+ {}
567
+ {statusLabel && statusColor && (
568
+ <span className={`shrink-0 text-[10px] px-1.5 py-0.5 rounded-full ${statusColor}`}>
569
+ {statusLabel}
570
+ </span>
571
+ )}
572
+ </div>
573
+ </div>
574
+
575
+ {isExpanded && node.children?.map(child => (
576
+ <TreeNode
577
+ key={child.path}
578
+ node={child}
579
+ selectedPaths={selectedPaths}
580
+ expandedPaths={expandedPaths}
581
+ fileStatuses={fileStatuses}
582
+ onToggleSelect={onToggleSelect}
583
+ onToggleExpand={onToggleExpand}
584
+ depth={depth + 1}
585
+ />
586
+ ))}
587
+ </div>
588
+ )
589
+ }
590
+
591
+
592
+
593
+ // CIL[END chunk_id="cilingestionpage_treenode_1784193942"]
594
+ // CIL[BEGIN chunk_id="cilingestionpage_steprow_1784193942" type="function" identity="StepRow" parent="none"]
595
+ // CIL[DOC-MACRO purpose="Render a pipeline step row with status, command copy, and run action"]
596
+ // CIL[DOC-RESPONSIBILITY responsibility="Displays step metadata, completion/partial state, command copy button, and run button"]
597
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
598
+ // CIL[DOC-CONTRACT inputs="none" outputs="JSX.Element" errors="none" side_effects="Writes command text to clipboard and triggers onRun callback"]
599
+ // CIL[DOC-MICRO logic_summary="Determines done and partial states, renders step icon, label, progress badge, description, copyable command, info tooltip, and run button" notable_decisions="[]"]
600
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
601
+ // CIL[DOC-STABILITY stability="stable"]
602
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
603
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
604
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
605
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
606
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
607
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
608
+ function StepRow({
609
+ step,
610
+ fileCount,
611
+ activeCount,
612
+ isActive,
613
+ done,
614
+ onRun,
615
+ running,
616
+ byStatus,
617
+ totalChunks,
618
+ }: {
619
+ step: PipelineStep
620
+ fileCount: number
621
+ activeCount: number
622
+ isActive: boolean
623
+ done?: boolean
624
+ onRun?: () => void
625
+ running: boolean
626
+ byStatus?: Record<string, number>
627
+ totalChunks?: number
628
+ }) {
629
+ const Icon = step.icon
630
+ const [copied, setCopied] = useState(false)
631
+
632
+ const stepDone = done !== undefined ? done : (activeCount >= fileCount && fileCount > 0)
633
+ const partial = activeCount > 0 && activeCount < fileCount
634
+
635
+ const handleCopy = () => {
636
+ if (!step.command) return
637
+ navigator.clipboard.writeText(step.command)
638
+ setCopied(true)
639
+ setTimeout(() => setCopied(false), 1500)
640
+ }
641
+
642
+ return (
643
+ <div className={`flex items-start gap-3 p-3 rounded-lg border transition-colors ${
644
+ isActive
645
+ ? 'bg-indigo-50 border-indigo-200 dark:bg-indigo-950/20 dark:border-indigo-800'
646
+ : stepDone
647
+ ? 'bg-green-50/50 border-green-100'
648
+ : 'border-border bg-card'
649
+ }`}>
650
+ {}
651
+ <div className={`h-6 w-6 rounded-full flex items-center justify-center text-xs font-bold shrink-0 mt-0.5 ${
652
+ stepDone
653
+ ? 'bg-green-100 text-green-700'
654
+ : isActive
655
+ ? 'bg-indigo-100 text-indigo-700'
656
+ : 'bg-muted text-muted-foreground'
657
+ }`}>
658
+ {stepDone ? <CheckCircle2 className="h-3.5 w-3.5 text-green-600" /> : step.number}
659
+ </div>
660
+
661
+ {}
662
+ <div className="flex-1 min-w-0">
663
+ <div className="flex items-center gap-2">
664
+ <Icon className={`h-3.5 w-3.5 shrink-0 ${done ? 'text-green-600' : isActive ? 'text-indigo-600' : 'text-muted-foreground'}`} />
665
+ <span className="text-sm font-medium">
666
+ {step.label.includes('(')
667
+ ? <>{step.label.split('(')[0]}<span className="text-xs font-normal text-muted-foreground">({step.label.split('(')[1]}</span></>
668
+ : step.label}
669
+ </span>
670
+ {(isActive || partial || done) && fileCount > 0 && !(step.number === 2) && (
671
+ <Badge variant={partial ? "secondary" : done ? "secondary" : "outline"} className="text-[10px] h-4 px-1.5">
672
+ {partial
673
+ ? `${activeCount}/${fileCount}`
674
+ : step.number === 3
675
+ ? `${fileCount}`
676
+ : (step.number === 4 || step.number === 5)
677
+ ? `${totalChunks || 0} chunks`
678
+ : (step.number === 6 || step.number === 9)
679
+ ? `${(totalChunks || 0) * 3} vectors`
680
+ : `${fileCount} done`}
681
+ </Badge>
682
+ )}
683
+ </div>
684
+ <p className="text-xs text-muted-foreground mt-0.5">{step.description}</p>
685
+ {step.command && (
686
+ <button
687
+ onClick={handleCopy}
688
+ className="mt-1.5 flex items-center gap-1.5 w-fit rounded px-2 py-0.5 bg-muted hover:bg-muted/80 transition-colors group"
689
+ >
690
+ <code className="text-[10px] font-mono text-foreground/70">{step.command}</code>
691
+ {copied
692
+ ? <Check className="h-3 w-3 text-green-600 shrink-0" />
693
+ : <Copy className="h-3 w-3 text-muted-foreground shrink-0 opacity-0 group-hover:opacity-100 transition-opacity" />
694
+ }
695
+ </button>
696
+ )}
697
+ </div>
698
+
699
+ {}
700
+ <div className="flex items-center gap-1 shrink-0">
701
+ <Tooltip>
702
+ <TooltipTrigger asChild>
703
+ <button className="p-1 rounded hover:bg-muted">
704
+ <Info className="h-3.5 w-3.5 text-muted-foreground" />
705
+ </button>
706
+ </TooltipTrigger>
707
+ <TooltipContent side="left" className="max-w-xs text-xs">
708
+ <p className="font-semibold mb-1">Devin instruction:</p>
709
+ <p>{step.agentInstruction}</p>
710
+ </TooltipContent>
711
+ </Tooltip>
712
+ {onRun && (
713
+ <Button size="sm" variant={isActive ? 'default' : 'outline'} onClick={onRun} disabled={running} className="h-7 text-xs">
714
+ {running ? <RefreshCw className="h-3 w-3 animate-spin" /> : <Play className="h-3 w-3" />}
715
+ </Button>
716
+ )}
717
+ </div>
718
+ </div>
719
+ )
720
+ }
721
+
722
+
723
+
724
+ // CIL[END chunk_id="cilingestionpage_steprow_1784193942"]
725
+ // CIL[BEGIN chunk_id="cilingestionpage_cilingestionpage_1784193942" type="component" identity="CILIngestionPage" parent="none"]
726
+ // CIL[DOC-MACRO purpose="Render the CIL ingestion pipeline page for a project"]
727
+ // CIL[DOC-RESPONSIBILITY responsibility="Manage project config, file tree selection, pipeline progress, and run ingestion passes"]
728
+ // CIL[DOC-CONTEXT module="cortex:cil-ingestion" layer="presentation" related_files="[]"]
729
+ // CIL[DOC-CONTRACT inputs="none" outputs="JSX.Element" errors="Displays loading, not-found, and API error states" side_effects="Calls /api/cil-project, /api/cil-ingest, /api/cil-embeddings, and /api/cil-callmap endpoints; updates local state"]
730
+ // CIL[DOC-MICRO logic_summary="Loads project and progress, renders stats, settings, pipeline steps, and file tree; provides handlers to save config/selection and run pass 1, embed, and map passes" notable_decisions="[]"]
731
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
732
+ // CIL[DOC-STABILITY stability="stable"]
733
+ // CIL[DOC-VECTOR type="context" content="Layer: presentation. Direct callers: 0. Direct calls: 0. Related files: none."]
734
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
735
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
736
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
737
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
738
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
739
+ export default function CILIngestionPage() {
740
+ const { id: projectId } = useParams<{ id: string }>()
741
+ const appPath = (path: string) => '/spine-framework/admin' + (path.startsWith('/') ? path : '/' + path)
742
+
743
+ const [project, setProject] = useState<Project | null>(null)
744
+ const [loadingProject, setLoadingProject] = useState(true)
745
+
746
+ const [projectConfig, setProjectConfig] = useState<ProjectConfig>({
747
+ repo_path: '',
748
+ globs: '**/*.ts,**/*.tsx,**/*.sql',
749
+ exclude_globs: 'node_modules/**,.git/**,dist/**,build/**,.netlify/**,**/*.min.js,**/*.d.ts',
750
+ force_reprocess: false,
751
+ })
752
+ const [configOpen, setConfigOpen] = useState(false)
753
+ const [savingConfig, setSavingConfig] = useState(false)
754
+ const [configSaved, setConfigSaved] = useState(false)
755
+
756
+ const [fileTree, setFileTree] = useState<FileTreeNode | null>(null)
757
+ const [selectedPaths, setSelectedPaths] = useState<string[]>([])
758
+ const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set())
759
+ const [fileStatuses, setFileStatuses] = useState<Record<string, string>>({})
760
+ const [savingSelection, setSavingSelection] = useState(false)
761
+
762
+ const [pipelineProgress, setPipelineProgress] = useState<PipelineProgress | null>(null)
763
+ const [loadingProgress, setLoadingProgress] = useState(false)
764
+ const [runningPass, setRunningPass] = useState<string | null>(null)
765
+
766
+
767
+ useEffect(() => {
768
+ if (!projectId) return
769
+ setLoadingProject(true)
770
+ apiFetch(`/api/cil-project?action=get&id=${projectId}`)
771
+ .then(r => r.json())
772
+ .then(data => {
773
+ const p: Project = data?.project || data?.data?.project || null
774
+ setProject(p)
775
+ if (p?.data?.file_tree) {
776
+ setFileTree(p.data.file_tree)
777
+ setExpandedPaths(new Set(collectAllDirPaths(p.data.file_tree)))
778
+ }
779
+ setSelectedPaths(p?.data?.selected_paths || [])
780
+ setFileStatuses(p?.data?.file_statuses || {})
781
+
782
+ if (p?.data) {
783
+ setProjectConfig(c => ({
784
+ ...c,
785
+ repo_path: p.data.repo_path || '',
786
+ globs: p.data.globs?.join(',') || c.globs,
787
+ exclude_globs: p.data.exclude_globs?.join(',') || c.exclude_globs,
788
+ }))
789
+ }
790
+ })
791
+ .catch(() => {})
792
+ .finally(() => setLoadingProject(false))
793
+ }, [projectId])
794
+
795
+
796
+ const fetchProgress = useCallback(() => {
797
+ if (!projectId) return
798
+ setLoadingProgress(true)
799
+ apiFetch(`/api/cil-project?action=pipeline_progress&id=${projectId}`)
800
+ .then(r => r.json())
801
+ .then(data => {
802
+ const d = data?.data || data
803
+ const progress = d?.progress || null
804
+ setPipelineProgress(progress)
805
+
806
+ if (progress?.file_statuses) setFileStatuses(progress.file_statuses)
807
+
808
+ return apiFetch(`/api/cil-project?action=get&id=${projectId}`)
809
+ })
810
+ .then(r => r.json())
811
+ .then(projectData => {
812
+ const p = projectData?.project || projectData?.data?.project || null
813
+ if (p?.data?.file_statuses) {
814
+ setFileStatuses(p.data.file_statuses)
815
+ }
816
+ })
817
+ .catch(() => {})
818
+ .finally(() => setLoadingProgress(false))
819
+ }, [projectId])
820
+
821
+ useEffect(() => { fetchProgress() }, [fetchProgress])
822
+
823
+
824
+ const saveSelection = useCallback(async (paths: string[]) => {
825
+ if (!projectId) return
826
+ setSavingSelection(true)
827
+ try {
828
+
829
+
830
+
831
+ const updatedStatuses = { ...fileStatuses }
832
+ for (const p of paths) {
833
+ if (!updatedStatuses[p]) updatedStatuses[p] = '3-selected'
834
+ }
835
+ for (const p of Object.keys(updatedStatuses)) {
836
+ if (!paths.includes(p) && updatedStatuses[p] === '3-selected') {
837
+ delete updatedStatuses[p]
838
+ }
839
+ }
840
+ await apiFetch(`/api/cil-project?action=update&id=${projectId}`, {
841
+ method: 'PATCH',
842
+ headers: { 'Content-Type': 'application/json' },
843
+ body: JSON.stringify({ id: projectId, data: { selected_paths: paths, file_statuses: updatedStatuses } }),
844
+ })
845
+ setFileStatuses(updatedStatuses)
846
+ setProject(p => p ? { ...p, data: { ...p.data, selected_paths: paths, file_statuses: updatedStatuses } } : p)
847
+ } catch {
848
+
849
+ } finally {
850
+ setSavingSelection(false)
851
+ }
852
+ }, [projectId, fileStatuses])
853
+
854
+
855
+ const saveConfig = useCallback(async () => {
856
+ if (!projectId) return
857
+ setSavingConfig(true)
858
+ setConfigSaved(false)
859
+ try {
860
+ await apiFetch(`/api/cil-project?action=update&id=${projectId}`, {
861
+ method: 'PATCH',
862
+ headers: { 'Content-Type': 'application/json' },
863
+ body: JSON.stringify({
864
+ id: projectId,
865
+ data: {
866
+ repo_path: projectConfig.repo_path,
867
+ globs: projectConfig.globs.split(',').map(s => s.trim()).filter(Boolean),
868
+ exclude_globs: projectConfig.exclude_globs.split(',').map(s => s.trim()).filter(Boolean),
869
+ },
870
+ }),
871
+ })
872
+ setProject(p => p ? {
873
+ ...p,
874
+ data: {
875
+ ...p.data,
876
+ repo_path: projectConfig.repo_path,
877
+ globs: projectConfig.globs.split(',').map(s => s.trim()).filter(Boolean),
878
+ exclude_globs: projectConfig.exclude_globs.split(',').map(s => s.trim()).filter(Boolean),
879
+ },
880
+ } : p)
881
+ setConfigSaved(true)
882
+ setTimeout(() => setConfigSaved(false), 2000)
883
+ } catch {
884
+
885
+ } finally {
886
+ setSavingConfig(false)
887
+ }
888
+ }, [projectId, projectConfig])
889
+
890
+
891
+ const togglePath = useCallback((path: string, isDir: boolean, children?: FileTreeNode[]) => {
892
+ setSelectedPaths(prev => {
893
+ let next: string[]
894
+ if (isDir && children) {
895
+
896
+ const childPaths = children.map(c => c.path)
897
+ const allSelected = childPaths.every(cp => prev.includes(cp))
898
+ if (allSelected) {
899
+ next = prev.filter(p => !childPaths.includes(p))
900
+ } else {
901
+ next = [...new Set([...prev, ...childPaths])]
902
+ }
903
+ } else {
904
+ next = prev.includes(path) ? prev.filter(p => p !== path) : [...prev, path]
905
+ }
906
+ saveSelection(next)
907
+ return next
908
+ })
909
+ }, [saveSelection])
910
+
911
+ const toggleExpand = (path: string) => {
912
+ setExpandedPaths(prev => {
913
+ const next = new Set(prev)
914
+ if (next.has(path)) { next.delete(path) } else { next.add(path) }
915
+ return next
916
+ })
917
+ }
918
+
919
+
920
+ const runPass1 = async () => {
921
+ if (!projectId) return
922
+ setRunningPass('build_tree')
923
+ try {
924
+ const res = await apiFetch('/api/cil-ingest?action=pass1', {
925
+ method: 'POST',
926
+ headers: { 'Content-Type': 'application/json' },
927
+ body: JSON.stringify({
928
+ project_id: projectId,
929
+ globs: projectConfig.globs.split(',').map(s => s.trim()).filter(Boolean),
930
+ exclude_globs: projectConfig.exclude_globs.split(',').map(s => s.trim()).filter(Boolean),
931
+ force_reprocess: projectConfig.force_reprocess,
932
+ }),
933
+ })
934
+ const data = await res.json()
935
+ const d = data?.data || data
936
+ if (d?.file_tree) {
937
+ setFileTree(d.file_tree)
938
+ setExpandedPaths(new Set(collectAllDirPaths(d.file_tree)))
939
+ setProject(p => p ? { ...p, data: { ...p.data, file_tree: d.file_tree } } : p)
940
+ }
941
+ fetchProgress()
942
+ } catch {
943
+
944
+ } finally {
945
+ setRunningPass(null)
946
+ }
947
+ }
948
+
949
+
950
+ const runEmbedFirstPass = async () => {
951
+ if (!projectId) return
952
+ setRunningPass('first_pass_embed')
953
+ try {
954
+ let offset: number | null = 0
955
+ while (offset !== null) {
956
+ const res = await apiFetch('/api/cil-embeddings?action=embed_project', {
957
+ method: 'POST',
958
+ headers: { 'Content-Type': 'application/json' },
959
+ body: JSON.stringify({
960
+ project_id: projectId,
961
+ force_regenerate: projectConfig.force_reprocess,
962
+ offset,
963
+ }),
964
+ })
965
+ const data = await res.json()
966
+ const d = data?.data || data
967
+ offset = d?.next_offset ?? null
968
+ await fetchProgress()
969
+ }
970
+ } catch {
971
+
972
+ } finally {
973
+ setRunningPass(null)
974
+ }
975
+ }
976
+
977
+
978
+ const runMapFirstPass = async () => {
979
+ if (!projectId) return
980
+ setRunningPass('first_pass_map')
981
+ try {
982
+ let offset: number | null = 0
983
+ while (offset !== null) {
984
+ const res = await apiFetch('/api/cil-callmap?action=map_project', {
985
+ method: 'POST',
986
+ headers: { 'Content-Type': 'application/json' },
987
+ body: JSON.stringify({
988
+ project_id: projectId,
989
+ force_regenerate: projectConfig.force_reprocess,
990
+ offset,
991
+ }),
992
+ })
993
+ const data = await res.json()
994
+ const d = data?.data || data
995
+ offset = d?.next_offset ?? null
996
+ await fetchProgress()
997
+ }
998
+ } catch {
999
+
1000
+ } finally {
1001
+ setRunningPass(null)
1002
+ }
1003
+ }
1004
+
1005
+
1006
+ const runEmbedSecondPass = async () => {
1007
+ if (!projectId) return
1008
+ setRunningPass('second_pass_embed')
1009
+ try {
1010
+ let offset: number | null = 0
1011
+ while (offset !== null) {
1012
+ const res = await apiFetch('/api/cil-embeddings?action=embed_project', {
1013
+ method: 'POST',
1014
+ headers: { 'Content-Type': 'application/json' },
1015
+ body: JSON.stringify({
1016
+ project_id: projectId,
1017
+ force_regenerate: projectConfig.force_reprocess,
1018
+ second_pass: true,
1019
+ offset,
1020
+ }),
1021
+ })
1022
+ const data = await res.json()
1023
+ const d = data?.data || data
1024
+ offset = d?.next_offset ?? null
1025
+ await fetchProgress()
1026
+ }
1027
+ } catch {
1028
+
1029
+ } finally {
1030
+ setRunningPass(null)
1031
+ }
1032
+ }
1033
+
1034
+
1035
+ const runMapSecondPass = async () => {
1036
+ if (!projectId) return
1037
+ setRunningPass('second_pass_map')
1038
+ try {
1039
+ let offset: number | null = 0
1040
+ while (offset !== null) {
1041
+ const res = await apiFetch('/api/cil-callmap?action=map_project', {
1042
+ method: 'POST',
1043
+ headers: { 'Content-Type': 'application/json' },
1044
+ body: JSON.stringify({
1045
+ project_id: projectId,
1046
+ force_regenerate: projectConfig.force_reprocess,
1047
+ second_pass: true,
1048
+ offset,
1049
+ }),
1050
+ })
1051
+ const data = await res.json()
1052
+ const d = data?.data || data
1053
+ offset = d?.next_offset ?? null
1054
+ await fetchProgress()
1055
+ }
1056
+ } catch {
1057
+
1058
+ } finally {
1059
+ setRunningPass(null)
1060
+ }
1061
+ }
1062
+
1063
+
1064
+ const totalFiles = fileTree ? countFilesInTree(fileTree) : 0
1065
+ const selectedCount = selectedPaths.length
1066
+ const byStatus = pipelineProgress?.by_status || {}
1067
+ const totalChunks = pipelineProgress?.total_chunks || 0
1068
+
1069
+
1070
+
1071
+
1072
+ const statusOrder = ['3-selected','4-chunked','5-documented','6-embedded','7-mapped','8-enriched','9-embedded','10-mapped']
1073
+ const filesAtOrPast = (minStatus: string) => {
1074
+ const minIdx = statusOrder.indexOf(minStatus)
1075
+ return statusOrder.slice(minIdx).reduce((sum, s) => sum + (byStatus[s] || 0), 0)
1076
+ }
1077
+
1078
+ const activeStep = (() => {
1079
+
1080
+ if (!project?.data?.repo_path) return 1
1081
+
1082
+
1083
+ if (!fileTree) return 2
1084
+
1085
+
1086
+ if (selectedCount === 0) return 3
1087
+
1088
+
1089
+ if (filesAtOrPast('4-chunked') < selectedCount) return 4
1090
+
1091
+
1092
+ if (filesAtOrPast('5-documented') < selectedCount) return 5
1093
+
1094
+
1095
+ if (filesAtOrPast('6-embedded') < selectedCount) return 6
1096
+
1097
+
1098
+ if (filesAtOrPast('7-mapped') < selectedCount) return 7
1099
+
1100
+
1101
+ if (filesAtOrPast('8-enriched') < selectedCount) return 8
1102
+
1103
+
1104
+ if (filesAtOrPast('9-embedded') < selectedCount) return 9
1105
+
1106
+
1107
+ if (filesAtOrPast('10-mapped') < selectedCount) return 10
1108
+
1109
+ return 10
1110
+ })()
1111
+
1112
+ if (loadingProject) {
1113
+ return (
1114
+ <div className="max-w-6xl mx-auto p-6 space-y-4">
1115
+ <Skeleton className="h-8 w-64" />
1116
+ <div className="grid grid-cols-4 gap-3">
1117
+ {[1,2,3,4].map(i => <Skeleton key={i} className="h-20" />)}
1118
+ </div>
1119
+ <div className="grid grid-cols-3 gap-4">
1120
+ <Skeleton className="h-[500px]" />
1121
+ <Skeleton className="h-[500px] col-span-2" />
1122
+ </div>
1123
+ </div>
1124
+ )
1125
+ }
1126
+
1127
+ if (!project) {
1128
+ return (
1129
+ <div className="max-w-6xl mx-auto p-6">
1130
+ <p className="text-muted-foreground">Project not found.</p>
1131
+ </div>
1132
+ )
1133
+ }
1134
+
1135
+ return (
1136
+ <div className="max-w-6xl mx-auto p-6 space-y-5">
1137
+
1138
+ {}
1139
+ <div className="flex items-center justify-between">
1140
+ <div>
1141
+ <div className="flex items-center gap-2 text-xs text-muted-foreground mb-1">
1142
+ <a href={appPath('/cil')} className="hover:text-foreground">CIL</a>
1143
+ <ChevronRight className="h-3 w-3" />
1144
+ <a href={appPath(`/cil/${projectId}/dashboard`)} className="hover:text-foreground">{project.title}</a>
1145
+ <ChevronRight className="h-3 w-3" />
1146
+ <span className="text-foreground">Pipeline</span>
1147
+ </div>
1148
+ <h1 className="text-xl font-bold">{project.title}</h1>
1149
+ <p className="text-xs text-muted-foreground mt-0.5 font-mono">{project.data.repo_path}</p>
1150
+ </div>
1151
+ <div className="flex items-center gap-2">
1152
+ <Button size="sm" variant="outline" onClick={fetchProgress} disabled={loadingProgress} className="h-8">
1153
+ <RefreshCw className={`h-3.5 w-3.5 ${loadingProgress ? 'animate-spin' : ''}`} />
1154
+ </Button>
1155
+ <a href={appPath(`/cil/${projectId}/dashboard`)}>
1156
+ <Button size="sm" variant="outline" className="h-8 text-xs">Dashboard</Button>
1157
+ </a>
1158
+ <a href={appPath(`/cil/${projectId}/agent`)}>
1159
+ <Button size="sm" className="h-8 text-xs">Open Agent</Button>
1160
+ </a>
1161
+ </div>
1162
+ </div>
1163
+
1164
+ <PageInstructions
1165
+ description="Run and monitor the CIL ingestion pipeline for a project."
1166
+ sections={[
1167
+ { title: 'What this page is for', content: 'This page controls the multi-pass ingestion process that discovers files, chunks code, generates embeddings, documents contracts, maps calls, and extracts config.' },
1168
+ { title: 'How to read this page', content: 'The top cards show files discovered, files selected, first-pass complete, and fully ingested counts. The steps list shows which pass is active and how many files are done.' },
1169
+ { title: 'How to use this page', content: 'Expand Project Settings to set the repository path, include globs, exclude globs, and Force Reprocess. Save settings, then run the pipeline steps in order. Refresh the progress bar to track status.' },
1170
+ { title: 'Important notes', content: 'Ingestion can be expensive because it uses LLMs. Excluding node_modules, .git, and dist is strongly recommended. Force Reprocess re-runs passes even on already-processed files.' },
1171
+ ]}
1172
+ />
1173
+
1174
+ {}
1175
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
1176
+ <div className="rounded-lg border bg-card p-3">
1177
+ <div className="text-2xl font-bold tabular-nums">{totalFiles}</div>
1178
+ <div className="text-xs text-muted-foreground mt-0.5">Files discovered</div>
1179
+ </div>
1180
+ <div className="rounded-lg border bg-card p-3">
1181
+ <div className="text-2xl font-bold tabular-nums text-indigo-700">{selectedCount}</div>
1182
+ <div className="text-xs text-muted-foreground mt-0.5">Files selected</div>
1183
+ </div>
1184
+ <div className="rounded-lg border bg-card p-3">
1185
+ <div className="text-2xl font-bold tabular-nums text-green-700">
1186
+ {byStatus['7-mapped'] || 0}
1187
+ </div>
1188
+ <div className="text-xs text-muted-foreground mt-0.5">First pass complete</div>
1189
+ </div>
1190
+ <div className="rounded-lg border bg-card p-3">
1191
+ <div className="text-2xl font-bold tabular-nums text-amber-700">
1192
+ {byStatus['10-mapped'] || 0}
1193
+ </div>
1194
+ <div className="text-xs text-muted-foreground mt-0.5">Fully ingested</div>
1195
+ </div>
1196
+ </div>
1197
+
1198
+ {}
1199
+ <div className="rounded-lg border bg-card overflow-hidden">
1200
+ <button
1201
+ className="w-full flex items-center justify-between px-4 py-3 hover:bg-muted/40 transition-colors"
1202
+ onClick={() => setConfigOpen(o => !o)}
1203
+ >
1204
+ <div className="flex items-center gap-2">
1205
+ <Settings2 className="h-4 w-4 text-muted-foreground" />
1206
+ <span className="text-sm font-medium">Project Settings</span>
1207
+ <span className="text-xs text-muted-foreground">— repo path, globs, exclusions</span>
1208
+ </div>
1209
+ <ChevronDown className={`h-4 w-4 text-muted-foreground transition-transform ${configOpen ? 'rotate-180' : ''}`} />
1210
+ </button>
1211
+
1212
+ {configOpen && (
1213
+ <div className="px-4 pb-4 border-t pt-4 space-y-4">
1214
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
1215
+ {}
1216
+ <div className="sm:col-span-2 space-y-1.5">
1217
+ <Label className="text-xs font-semibold">Repository Path</Label>
1218
+ <Input
1219
+ value={projectConfig.repo_path}
1220
+ onChange={e => setProjectConfig(c => ({ ...c, repo_path: e.target.value }))}
1221
+ placeholder="/path/to/repo"
1222
+ className="font-mono text-xs"
1223
+ />
1224
+ <p className="text-xs text-muted-foreground">Absolute path to the repository root on disk.</p>
1225
+ </div>
1226
+
1227
+ {}
1228
+ <div className="space-y-1.5">
1229
+ <Label className="text-xs font-semibold">Include Globs</Label>
1230
+ <Input
1231
+ value={projectConfig.globs}
1232
+ onChange={e => setProjectConfig(c => ({ ...c, globs: e.target.value }))}
1233
+ placeholder="**/*.ts,**/*.tsx,**/*.sql"
1234
+ className="font-mono text-xs"
1235
+ />
1236
+ <p className="text-xs text-muted-foreground">Comma-separated glob patterns for files to include.</p>
1237
+ </div>
1238
+
1239
+ {}
1240
+ <div className="space-y-1.5">
1241
+ <Label className="text-xs font-semibold">Exclude Globs</Label>
1242
+ <Input
1243
+ value={projectConfig.exclude_globs}
1244
+ onChange={e => setProjectConfig(c => ({ ...c, exclude_globs: e.target.value }))}
1245
+ placeholder="node_modules/**,.git/**,dist/**"
1246
+ className="font-mono text-xs"
1247
+ />
1248
+ <p className="text-xs text-muted-foreground">Comma-separated patterns to exclude from all passes.</p>
1249
+ </div>
1250
+ </div>
1251
+
1252
+ {}
1253
+ <div className="flex items-center justify-between gap-4 pt-3 border-t">
1254
+ <div className="flex items-center gap-3">
1255
+ <input
1256
+ type="checkbox"
1257
+ id="force-reprocess"
1258
+ checked={projectConfig.force_reprocess}
1259
+ onChange={e => setProjectConfig(c => ({ ...c, force_reprocess: e.target.checked }))}
1260
+ className="h-4 w-4 rounded border-slate-300 accent-indigo-600 cursor-pointer"
1261
+ />
1262
+ <label htmlFor="force-reprocess" className="cursor-pointer select-none">
1263
+ <p className="text-xs font-semibold">Force Reprocess</p>
1264
+ <p className="text-xs text-muted-foreground">Re-run passes even if files are already processed.</p>
1265
+ </label>
1266
+ </div>
1267
+ <Button size="sm" onClick={saveConfig} disabled={savingConfig} className="h-8 text-xs shrink-0">
1268
+ {savingConfig
1269
+ ? <><RefreshCw className="h-3 w-3 mr-1.5 animate-spin" />Saving…</>
1270
+ : configSaved
1271
+ ? <><CheckCircle2 className="h-3 w-3 mr-1.5 text-green-600" />Saved</>
1272
+ : <><Save className="h-3 w-3 mr-1.5" />Save Settings</>
1273
+ }
1274
+ </Button>
1275
+ </div>
1276
+ </div>
1277
+ )}
1278
+ </div>
1279
+
1280
+ {}
1281
+ <div className="grid grid-cols-3 gap-4">
1282
+
1283
+ {}
1284
+ <div className="col-span-1 space-y-2">
1285
+ <div className="flex items-center justify-between mb-1">
1286
+ <h2 className="text-sm font-semibold">Pipeline Steps</h2>
1287
+ <Badge variant="outline" className="text-xs">
1288
+ Step {activeStep} active
1289
+ </Badge>
1290
+ </div>
1291
+
1292
+ {PIPELINE_STEPS.map(step => {
1293
+ const isActive = step.number === activeStep
1294
+ let activeCount = 0
1295
+ let fileCount = selectedCount
1296
+ let done = false
1297
+
1298
+
1299
+ if (step.number === 1) {
1300
+ done = !!(project?.data?.repo_path)
1301
+ activeCount = done ? 1 : 0
1302
+ fileCount = 1
1303
+ }
1304
+
1305
+ else if (step.number === 2) {
1306
+ done = !!fileTree
1307
+ activeCount = done ? 1 : 0
1308
+ fileCount = 1
1309
+ }
1310
+
1311
+ else if (step.number === 3) {
1312
+ done = selectedCount > 0
1313
+ activeCount = selectedCount
1314
+ fileCount = totalFiles
1315
+ }
1316
+
1317
+
1318
+ else if (step.targetStatus) {
1319
+ activeCount = filesAtOrPast(step.targetStatus)
1320
+ fileCount = selectedCount
1321
+ done = activeCount >= fileCount && fileCount > 0
1322
+ }
1323
+
1324
+
1325
+ let onRun: (() => void) | undefined
1326
+ if (step.number === 2) onRun = runPass1
1327
+ else if (step.number === 6) onRun = runEmbedFirstPass
1328
+ else if (step.number === 7) onRun = runMapFirstPass
1329
+ else if (step.number === 9) onRun = runEmbedSecondPass
1330
+ else if (step.number === 10) onRun = runMapSecondPass
1331
+
1332
+ return (
1333
+ <StepRow
1334
+ key={step.key}
1335
+ step={step}
1336
+ fileCount={fileCount}
1337
+ activeCount={activeCount}
1338
+ isActive={isActive}
1339
+ done={done}
1340
+ onRun={onRun}
1341
+ running={runningPass === step.key}
1342
+ byStatus={byStatus}
1343
+ totalChunks={totalChunks}
1344
+ />
1345
+ )
1346
+ })}
1347
+ </div>
1348
+
1349
+ {}
1350
+ <div className="col-span-2">
1351
+ <Card className="h-full">
1352
+ <CardHeader className="pb-2 border-b">
1353
+ <div className="flex items-center justify-between">
1354
+ <CardTitle className="text-sm">File Tree</CardTitle>
1355
+ <div className="flex items-center gap-2">
1356
+ {savingSelection && (
1357
+ <span className="flex items-center gap-1 text-xs text-muted-foreground">
1358
+ <Save className="h-3 w-3 animate-pulse" /> Saving…
1359
+ </span>
1360
+ )}
1361
+ {selectedCount > 0 && (
1362
+ <Badge variant="secondary" className="text-xs">
1363
+ {selectedCount} selected
1364
+ </Badge>
1365
+ )}
1366
+ </div>
1367
+ </div>
1368
+ </CardHeader>
1369
+ <CardContent className="pt-2 pb-3">
1370
+ {!fileTree ? (
1371
+ <div className="py-8 text-center">
1372
+ <Folder className="h-8 w-8 text-muted-foreground/30 mx-auto mb-2" />
1373
+ <p className="text-sm text-muted-foreground">No file tree yet.</p>
1374
+ <p className="text-xs text-muted-foreground mt-1">Run Step 2 to scan the repository.</p>
1375
+ <Button size="sm" className="mt-3" onClick={runPass1} disabled={runningPass === 'build_tree'}>
1376
+ {runningPass === 'build_tree'
1377
+ ? <><RefreshCw className="h-3 w-3 mr-1.5 animate-spin" />Scanning…</>
1378
+ : <><Play className="h-3 w-3 mr-1.5" />Scan Repository</>
1379
+ }
1380
+ </Button>
1381
+ </div>
1382
+ ) : (
1383
+ <div className="overflow-y-auto max-h-[calc(100vh-340px)]">
1384
+ <TreeNode
1385
+ node={fileTree}
1386
+ selectedPaths={selectedPaths}
1387
+ expandedPaths={expandedPaths}
1388
+ fileStatuses={fileStatuses}
1389
+ onToggleSelect={togglePath}
1390
+ onToggleExpand={toggleExpand}
1391
+ />
1392
+ </div>
1393
+ )}
1394
+ </CardContent>
1395
+ </Card>
1396
+ </div>
1397
+ </div>
1398
+ </div>
1399
+ )
1400
+ }
1401
+
1402
+ // CIL[END chunk_id="cilingestionpage_cilingestionpage_1784193942"]