@astrale-os/cli 1.0.0-beta.3 → 1.0.0-beta.30

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 (343) hide show
  1. package/README.md +80 -6
  2. package/dist/astrale.js +55471 -58674
  3. package/dist/public/connect-core.js +2158 -30706
  4. package/dist/public/keys/index.js +1016 -29541
  5. package/dist/public/paths/index.js +370 -28654
  6. package/dist/types/admin/contract.d.ts +26 -0
  7. package/dist/types/admin/instance/client.d.ts +20 -0
  8. package/dist/types/admin/instance/index.d.ts +2 -0
  9. package/dist/types/admin/instance/model.d.ts +6 -10
  10. package/dist/types/connection/auth.d.ts +3 -0
  11. package/dist/types/connection/call.d.ts +3 -0
  12. package/dist/types/connection/command.d.ts +27 -0
  13. package/dist/types/connection/credential.d.ts +14 -0
  14. package/dist/types/connection/errors.d.ts +1 -0
  15. package/dist/types/connection/exchange.d.ts +9 -0
  16. package/dist/types/connection/failure/classify.d.ts +2 -0
  17. package/dist/types/connection/failure/index.d.ts +5 -0
  18. package/dist/types/connection/failure/model.d.ts +25 -0
  19. package/dist/types/connection/failure/render.d.ts +3 -0
  20. package/dist/types/connection/index.d.ts +8 -0
  21. package/dist/types/connection/reasons.d.ts +37 -0
  22. package/dist/types/connection/self.d.ts +37 -0
  23. package/dist/types/connection/session.d.ts +31 -0
  24. package/dist/types/connection/target.d.ts +38 -0
  25. package/dist/types/errors.d.ts +1 -1
  26. package/dist/types/lib/admin-instance.d.ts +17 -0
  27. package/dist/types/lib/admin-target.d.ts +11 -36
  28. package/dist/types/lib/config.d.ts +13 -59
  29. package/dist/types/lib/failure-debug.d.ts +1 -0
  30. package/dist/types/lib/idp.d.ts +25 -173
  31. package/dist/types/lib/instance-target.d.ts +1 -1
  32. package/dist/types/lib/instance.d.ts +30 -88
  33. package/dist/types/lib/log.d.ts +5 -3
  34. package/dist/types/lib/output.d.ts +1 -1
  35. package/dist/types/lib/proc.d.ts +43 -0
  36. package/dist/types/lib/self.d.ts +9 -0
  37. package/dist/types/lib/update.d.ts +109 -0
  38. package/dist/types/lib/validation.d.ts +4 -1
  39. package/dist/types/state/exchange-credentials.d.ts +6 -2
  40. package/dist/types/state/files.d.ts +2 -0
  41. package/dist/types/state/index.d.ts +3 -2
  42. package/dist/types/state/paths.d.ts +2 -0
  43. package/dist/types/state/session-routes.d.ts +10 -0
  44. package/package.json +16 -19
  45. package/src/__tests__/fixtures/publication.ts +20 -0
  46. package/src/admin/.spec/architecture.md +12 -5
  47. package/src/admin/__tests__/fixture.ts +25 -0
  48. package/src/admin/catalog/.spec/api.d.ts +1 -3
  49. package/src/admin/catalog/.spec/architecture.md +5 -4
  50. package/src/admin/catalog/__tests__/client.test.ts +143 -69
  51. package/src/admin/catalog/client.ts +40 -54
  52. package/src/admin/catalog/model.ts +3 -4
  53. package/src/admin/contract.ts +47 -0
  54. package/src/admin/graph/.spec/api.d.ts +4 -10
  55. package/src/admin/graph/nodes.ts +1 -1
  56. package/src/admin/instance/.spec/api.d.ts +5 -9
  57. package/src/admin/instance/.spec/architecture.md +7 -7
  58. package/src/admin/instance/__tests__/client.test.ts +157 -175
  59. package/src/admin/instance/client.ts +39 -128
  60. package/src/admin/instance/index.ts +1 -2
  61. package/src/admin/instance/model.ts +12 -18
  62. package/src/commands/__tests__/admin-instance.test.ts +11 -8
  63. package/src/commands/__tests__/call-describe.test.ts +46 -33
  64. package/src/commands/__tests__/call.test.ts +34 -0
  65. package/src/commands/__tests__/domain-install-operation.test.ts +30 -22
  66. package/src/commands/__tests__/domain-install-owned.test.ts +1 -1
  67. package/src/commands/__tests__/domain-list.test.ts +5 -25
  68. package/src/commands/__tests__/install-direct.test.ts +115 -8
  69. package/src/commands/__tests__/install-identity-override.test.ts +6 -17
  70. package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
  71. package/src/commands/__tests__/instance-status.test.ts +139 -0
  72. package/src/commands/__tests__/introspect-parse.test.ts +80 -1
  73. package/src/commands/__tests__/logs.test.ts +250 -2
  74. package/src/commands/__tests__/read-commands.test.ts +118 -10
  75. package/src/commands/__tests__/token-ttl.test.ts +49 -4
  76. package/src/commands/__tests__/update.test.ts +25 -3
  77. package/src/commands/__tests__/view.test.ts +50 -2
  78. package/src/commands/auth/logout.ts +2 -1
  79. package/src/commands/browser.ts +29 -0
  80. package/src/commands/call-describe.ts +60 -68
  81. package/src/commands/call.ts +71 -59
  82. package/src/commands/domain/install.ts +73 -46
  83. package/src/commands/domain/list.ts +1 -1
  84. package/src/commands/domain/publish.ts +14 -11
  85. package/src/commands/domain/uninstall.ts +2 -2
  86. package/src/commands/get.ts +15 -16
  87. package/src/commands/identity/__tests__/register.test.ts +3 -3
  88. package/src/commands/identity/register.ts +8 -8
  89. package/src/commands/instance/create.ts +8 -16
  90. package/src/commands/instance/delete.ts +12 -9
  91. package/src/commands/instance/list.ts +2 -2
  92. package/src/commands/instance/status.ts +66 -20
  93. package/src/commands/instance/use.ts +16 -12
  94. package/src/commands/introspect.ts +53 -41
  95. package/src/commands/logs.ts +123 -36
  96. package/src/commands/mutate.ts +6 -5
  97. package/src/commands/query.ts +35 -32
  98. package/src/commands/session/analyze.ts +26 -4
  99. package/src/commands/token.ts +42 -27
  100. package/src/commands/ui/__tests__/commands.test.ts +152 -0
  101. package/src/commands/ui/add.ts +51 -0
  102. package/src/commands/ui/doctor.ts +21 -0
  103. package/src/commands/ui/init.ts +38 -0
  104. package/src/commands/ui/list.ts +26 -0
  105. package/src/commands/ui/preset-apply.ts +19 -0
  106. package/src/commands/ui/preset-list.ts +12 -0
  107. package/src/commands/ui/shared.ts +25 -0
  108. package/src/commands/update.ts +89 -48
  109. package/src/commands/view.ts +82 -37
  110. package/src/connection/.spec/api.d.ts +7 -8
  111. package/src/connection/.spec/architecture.md +14 -5
  112. package/src/connection/.spec/flows/session.ts +2 -2
  113. package/src/connection/.spec/laws/connection.ts +6 -2
  114. package/src/connection/.spec/layout.ts +1 -0
  115. package/src/connection/__tests__/auth.test.ts +1 -0
  116. package/src/connection/__tests__/credential.test.ts +21 -1
  117. package/src/connection/__tests__/errors.test.ts +27 -20
  118. package/src/connection/__tests__/exchange.test.ts +64 -11
  119. package/src/connection/__tests__/failure-fixtures.ts +39 -0
  120. package/src/connection/__tests__/failure-safety.test.ts +75 -0
  121. package/src/connection/__tests__/self.test.ts +31 -11
  122. package/src/connection/__tests__/session.test.ts +8 -4
  123. package/src/connection/__tests__/target.test.ts +2 -0
  124. package/src/connection/auth.ts +13 -0
  125. package/src/connection/call.ts +1 -1
  126. package/src/connection/credential.ts +2 -2
  127. package/src/connection/errors.ts +1 -355
  128. package/src/connection/exchange.ts +110 -61
  129. package/src/connection/failure/classify.ts +110 -0
  130. package/src/connection/failure/index.ts +18 -0
  131. package/src/connection/failure/model.ts +28 -0
  132. package/src/connection/failure/render.ts +116 -0
  133. package/src/connection/reasons.ts +16 -0
  134. package/src/connection/self.ts +39 -21
  135. package/src/connection/session.ts +27 -9
  136. package/src/errors.ts +3 -2
  137. package/src/graph/.spec/api.d.ts +12 -5
  138. package/src/graph/.spec/layout.ts +9 -1
  139. package/src/graph/__tests__/mutation.test.ts +2 -2
  140. package/src/graph/__tests__/query.test.ts +25 -6
  141. package/src/graph/class.ts +30 -0
  142. package/src/graph/index.ts +1 -0
  143. package/src/graph/query.ts +21 -32
  144. package/src/identity/.spec/api.d.ts +2 -2
  145. package/src/identity/__tests__/fixtures/registry-journey.ts +13 -1
  146. package/src/identity/__tests__/registration.test.ts +1 -1
  147. package/src/identity/__tests__/registry.test.ts +4 -0
  148. package/src/identity/registration.ts +3 -3
  149. package/src/identity/registry.ts +2 -0
  150. package/src/keys/__tests__/keys.test.ts +4 -0
  151. package/src/keys/credential.ts +1 -0
  152. package/src/lib/__tests__/admin-target.test.ts +12 -0
  153. package/src/lib/__tests__/binary.test.ts +16 -1
  154. package/src/lib/__tests__/browser-retention.test.ts +212 -0
  155. package/src/lib/__tests__/command-dx.test.ts +1 -1
  156. package/src/lib/__tests__/config.test.ts +27 -0
  157. package/src/lib/__tests__/domain-publication.test.ts +10 -16
  158. package/src/lib/__tests__/idp.test.ts +34 -4
  159. package/src/lib/__tests__/instance-candidates.test.ts +1 -3
  160. package/src/lib/__tests__/instance-target.test.ts +7 -39
  161. package/src/lib/__tests__/log-errors.test.ts +64 -0
  162. package/src/lib/__tests__/skills.test.ts +809 -0
  163. package/src/lib/__tests__/studio-server-deps.test.ts +1 -1
  164. package/src/lib/__tests__/update.test.ts +264 -11
  165. package/src/lib/__tests__/view-assets.test.ts +25 -0
  166. package/src/lib/__tests__/view-external-open-intent.test.ts +150 -0
  167. package/src/lib/__tests__/view-external-open-origins.test.ts +27 -0
  168. package/src/lib/__tests__/view-open-intent.test.ts +0 -2
  169. package/src/lib/__tests__/view-server.test.ts +26 -3
  170. package/src/lib/__tests__/view-session.test.ts +2 -1
  171. package/src/lib/admin-instance.ts +14 -8
  172. package/src/lib/admin-target.ts +21 -2
  173. package/src/lib/binary.ts +22 -5
  174. package/src/lib/browser-retention.ts +210 -0
  175. package/src/lib/command-dx.ts +9 -9
  176. package/src/lib/config.ts +12 -1
  177. package/src/lib/domain-publication.ts +9 -7
  178. package/src/lib/failure-debug.ts +26 -0
  179. package/src/lib/idp.ts +34 -3
  180. package/src/lib/instance-candidates.ts +2 -2
  181. package/src/lib/instance-target.ts +3 -14
  182. package/src/lib/instance.ts +16 -11
  183. package/src/lib/log.ts +16 -9
  184. package/src/lib/output.ts +1 -1
  185. package/src/lib/proc.ts +7 -2
  186. package/src/lib/provision-instance.ts +34 -57
  187. package/src/lib/skills/lock.ts +117 -0
  188. package/src/lib/skills/sync.ts +814 -0
  189. package/src/lib/skills.ts +1 -52
  190. package/src/lib/update.ts +298 -45
  191. package/src/lib/validation.ts +2 -2
  192. package/src/lib/view/assets.ts +11 -2
  193. package/src/lib/view/external-open-intent.ts +41 -0
  194. package/src/lib/view/external-open-origins.ts +37 -0
  195. package/src/lib/view/host-capabilities.ts +17 -0
  196. package/src/lib/view/resolve.ts +1 -1
  197. package/src/lib/view/server.ts +18 -3
  198. package/src/lib/view/session.ts +2 -0
  199. package/src/program/.spec/api.d.ts +1 -0
  200. package/src/program/__tests__/program.test.ts +163 -6
  201. package/src/program/argv.ts +14 -0
  202. package/src/program/build.ts +26 -4
  203. package/src/program/command.ts +1 -0
  204. package/src/program/index.ts +1 -0
  205. package/src/program/registry.ts +2 -1
  206. package/src/setup/__tests__/instance-step.test.ts +27 -10
  207. package/src/setup/steps/instance.ts +22 -15
  208. package/src/setup/steps/skills.ts +40 -38
  209. package/src/state/.spec/api.d.ts +21 -2
  210. package/src/state/.spec/architecture.md +18 -4
  211. package/src/state/.spec/layout.ts +1 -0
  212. package/src/state/__tests__/exchange-credentials.test.ts +88 -42
  213. package/src/state/__tests__/files.test.ts +24 -1
  214. package/src/state/__tests__/fixtures/session-route-process.ts +93 -0
  215. package/src/state/__tests__/paths.test.ts +1 -0
  216. package/src/state/__tests__/session-routes.test.ts +138 -0
  217. package/src/state/exchange-credentials.ts +22 -9
  218. package/src/state/files.ts +41 -0
  219. package/src/state/index.ts +3 -1
  220. package/src/state/paths.ts +3 -0
  221. package/src/state/session-routes.ts +34 -0
  222. package/src/telemetry/__tests__/analyze-log.test.ts +38 -0
  223. package/src/telemetry/__tests__/recorder.test.ts +1 -0
  224. package/src/telemetry/__tests__/retention.test.ts +267 -0
  225. package/src/telemetry/__tests__/settings.test.ts +102 -0
  226. package/src/telemetry/__tests__/store-scan.test.ts +128 -0
  227. package/src/telemetry/__tests__/trigger.test.ts +33 -4
  228. package/src/telemetry/analyze.ts +24 -2
  229. package/src/telemetry/recorder.ts +15 -4
  230. package/src/telemetry/retention.ts +176 -0
  231. package/src/telemetry/session.ts +18 -12
  232. package/src/telemetry/settings.ts +64 -11
  233. package/src/telemetry/store.ts +92 -9
  234. package/src/telemetry/trigger.ts +16 -28
  235. package/src/ui/.spec/api.d.ts +14 -0
  236. package/src/ui/.spec/architecture.md +14 -0
  237. package/src/ui/.spec/laws.ts +50 -0
  238. package/src/ui/.spec/layout.ts +16 -0
  239. package/src/ui/__tests__/ui.test.ts +1742 -0
  240. package/src/ui/index.ts +13 -0
  241. package/src/ui/lock.ts +89 -0
  242. package/src/ui/model.ts +83 -0
  243. package/src/ui/operations.ts +1044 -0
  244. package/src/ui/project.ts +333 -0
  245. package/src/ui/release.ts +398 -0
  246. package/src/ui/runner.ts +18 -0
  247. package/studio/client/dist/assets/elk-api-DjmKsHXc.js +1 -0
  248. package/studio/client/dist/assets/index-BFVFs_8x.js +81 -0
  249. package/studio/client/dist/assets/index-DuB9iUdu.css +2 -0
  250. package/studio/client/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
  251. package/studio/client/dist/assets/schema-studio--a0kX3QU.css +1 -0
  252. package/studio/client/dist/assets/schema-studio-DH6oEWME.js +8 -0
  253. package/studio/client/dist/index.html +4 -3
  254. package/studio/package.json +8 -14
  255. package/studio/server/agent/ask.test.ts +1 -1
  256. package/studio/server/agent/bridge/grant.test.ts +1 -1
  257. package/studio/server/agent/bridge/routes.test.ts +1 -1
  258. package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
  259. package/studio/server/agent/harness/gateway/token.test.ts +1 -1
  260. package/studio/server/agent/harness/gateway/token.ts +3 -3
  261. package/studio/server/agent/prompts/anchors.test.ts +41 -32
  262. package/studio/server/agent/prompts/anchors.ts +23 -51
  263. package/studio/server/agent/prompts/system.test.ts +4 -4
  264. package/studio/server/agent/prompts/system.ts +5 -5
  265. package/studio/server/agent/routes.test.ts +1 -1
  266. package/studio/server/agent/run/coordinator.test.ts +1 -1
  267. package/studio/server/agent/run/preparation.ts +1 -1
  268. package/studio/server/api/canvas.ts +0 -1
  269. package/studio/server/api/context.ts +1 -1
  270. package/studio/server/api/deployment.ts +1 -1
  271. package/studio/server/api/views.ts +2 -2
  272. package/studio/server/api/workspace.ts +1 -1
  273. package/studio/server/api-agent-loadout.test.ts +1 -1
  274. package/studio/server/api.test.ts +1 -1
  275. package/studio/server/cache.test.ts +24 -18
  276. package/studio/server/cache.ts +10 -54
  277. package/studio/server/cli-consumers.test.ts +31 -3
  278. package/studio/server/domain.test.ts +50 -68
  279. package/studio/server/domain.ts +82 -63
  280. package/studio/server/handoff/copy.ts +2 -2
  281. package/studio/server/index.ts +1 -1
  282. package/studio/server/instances/probe.test.ts +1 -1
  283. package/studio/server/introspect/anatomy/views/routes.ts +46 -37
  284. package/studio/server/introspect/anatomy/views.ts +10 -16
  285. package/studio/server/introspect/anatomy-extras.test.ts +29 -56
  286. package/studio/server/introspect/anatomy.test.ts +22 -14
  287. package/studio/server/introspect/anatomy.ts +6 -9
  288. package/studio/server/introspect/bundle.ts +0 -3
  289. package/studio/server/introspect/canonical-schema.test.ts +155 -424
  290. package/studio/server/introspect/canonical-schema.ts +248 -601
  291. package/studio/server/introspect/core.ts +14 -22
  292. package/studio/server/introspect/diff.test.ts +21 -9
  293. package/studio/server/introspect/diff.ts +19 -64
  294. package/studio/server/introspect/extractor.ts +23 -122
  295. package/studio/server/introspect/overlay-tsmorph.test.ts +116 -241
  296. package/studio/server/introspect/overlay-tsmorph.ts +0 -1
  297. package/studio/server/introspect/overlay.ts +3 -51
  298. package/studio/server/introspect/revision.test.ts +24 -28
  299. package/studio/server/introspect/revision.ts +11 -22
  300. package/studio/server/introspect/runtime.test.ts +77 -195
  301. package/studio/server/introspect/runtime.ts +5 -67
  302. package/studio/server/introspect/schema-ir-json.test.ts +81 -0
  303. package/studio/server/introspect/schema-ir-json.ts +39 -69
  304. package/studio/server/introspect/schema-refs.test.ts +43 -88
  305. package/studio/server/introspect/schema-refs.ts +14 -49
  306. package/studio/server/introspect/source-overlay/handlers.ts +116 -636
  307. package/studio/server/introspect/source-overlay/kernel-calls.ts +0 -3
  308. package/studio/server/introspect/source-overlay/spans.ts +15 -45
  309. package/studio/server/lifecycle.ts +4 -1
  310. package/studio/server/state/baseline.test.ts +14 -9
  311. package/studio/server/state/baseline.ts +3 -5
  312. package/studio/server/state/documents.test.ts +69 -0
  313. package/studio/server/state/documents.ts +57 -10
  314. package/studio/server/state/visibility.ts +1 -5
  315. package/studio/server/views/model.ts +3 -3
  316. package/studio/server/views/target.test.ts +18 -76
  317. package/studio/server/views/target.ts +24 -50
  318. package/studio/server/watch.test.ts +11 -11
  319. package/studio/server/watch.ts +8 -4
  320. package/studio/server/workspace/updates.ts +23 -1
  321. package/studio/server/workspace-watch.ts +2 -2
  322. package/studio/shared/contracts/runtime.ts +4 -0
  323. package/studio/shared/contracts/schema.ts +44 -131
  324. package/studio/shared/contracts/surface.test.ts +15 -17
  325. package/studio/shared/contracts/workspace.ts +5 -11
  326. package/studio/shared/schema/identity.test.ts +20 -44
  327. package/studio/shared/schema/identity.ts +9 -24
  328. package/studio/tsconfig.json +2 -1
  329. package/tsconfig.json +1 -1
  330. package/viewer/dist/index.html +21 -0
  331. package/viewer/dist/main.js +41 -62
  332. package/.check-workspace.cjs +0 -40
  333. package/src/admin/binding.ts +0 -168
  334. package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
  335. package/studio/client/dist/assets/elk-api-lwhFo7vB.js +0 -1
  336. package/studio/client/dist/assets/index-B-c-smo9.js +0 -8
  337. package/studio/client/dist/assets/index-BckHuAWk.js +0 -81
  338. package/studio/client/dist/assets/index-C4aNQ6dz.css +0 -1
  339. package/studio/client/dist/assets/index-ChOr3yP0.css +0 -1
  340. package/studio/server/introspect/anatomy/views/legacy.ts +0 -232
  341. package/studio/server/introspect/core-extractor.ts +0 -139
  342. package/studio/server/introspect/overlay.test.ts +0 -72
  343. package/studio/server/introspect/source-overlay/annotations.ts +0 -14
@@ -5,8 +5,14 @@
5
5
  * .analyzed AnalyzedMarker, written by the analyzer (any outcome)
6
6
  * report.md analyzer output
7
7
  * A session is CLOSED when events.jsonl's mtime is older than IDLE_WINDOW_MS.
8
+ *
9
+ * Two ways to read it, deliberately kept apart. scanSessions() answers the
10
+ * questions the CLI's start path asks — how old, open or closed, analyzed yet —
11
+ * from two stats per session and no file reads. listSessions() adds the parsed
12
+ * meta.json and marker, which only `session list` and the analyzer need. The
13
+ * start path runs before EVERY command, so the difference is not academic.
8
14
  */
9
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
15
+ import { type Dirent, existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
10
16
  import { join } from 'node:path'
11
17
 
12
18
  import type { AnalyzedMarker, SessionMeta } from './types'
@@ -42,6 +48,78 @@ export function reportPath(id: string): string {
42
48
  return join(sessionDir(id), 'report.md')
43
49
  }
44
50
 
51
+ /**
52
+ * The cheap facts about one session: enough to bucket an invocation, sweep the
53
+ * store, and pick an analysis target. `analyzed` records that a marker EXISTS,
54
+ * not what it says — a stat, not a read.
55
+ */
56
+ export type SessionScan = {
57
+ id: string
58
+ lastEventAt: Date | null
59
+ closed: boolean
60
+ analyzed: boolean
61
+ }
62
+
63
+ /** Session directory names, newest-first ordering applied by the callers. */
64
+ function sessionIds(): string[] {
65
+ try {
66
+ return readdirSync(sessionsRoot()).filter((name) => !name.startsWith('.'))
67
+ } catch {
68
+ return []
69
+ }
70
+ }
71
+
72
+ /** Newest activity first. Two stats per session, no file reads, no parsing. */
73
+ export function scanSessions(now = Date.now()): SessionScan[] {
74
+ return sessionIds()
75
+ .map((id) => {
76
+ let lastEventAt: Date | null = null
77
+ try {
78
+ lastEventAt = statSync(eventsPath(id)).mtime
79
+ } catch {
80
+ /* no events yet */
81
+ }
82
+ return {
83
+ id,
84
+ lastEventAt,
85
+ closed: lastEventAt !== null && now - lastEventAt.getTime() > IDLE_WINDOW_MS,
86
+ analyzed: existsSync(markerPath(id)),
87
+ }
88
+ })
89
+ .sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0))
90
+ }
91
+
92
+ /** Bytes on disk under `dir`. Unreadable entries count as zero — a directory
93
+ * racing away mid-walk is normal, not an error worth propagating. */
94
+ function directoryBytes(dir: string): number {
95
+ let entries: Dirent[]
96
+ try {
97
+ entries = readdirSync(dir, { withFileTypes: true })
98
+ } catch {
99
+ return 0
100
+ }
101
+ let total = 0
102
+ for (const entry of entries) {
103
+ const path = join(dir, entry.name)
104
+ if (entry.isDirectory()) {
105
+ total += directoryBytes(path)
106
+ } else if (entry.isFile()) {
107
+ try {
108
+ total += statSync(path).size
109
+ } catch {
110
+ /* raced away mid-walk */
111
+ }
112
+ }
113
+ }
114
+ return total
115
+ }
116
+
117
+ /** Bytes one session occupies. The analyzer runs with Write inside the session
118
+ * directory and may nest, so this walks rather than listing one level. */
119
+ export function sessionBytes(id: string): number {
120
+ return directoryBytes(sessionDir(id))
121
+ }
122
+
45
123
  export type SessionInfo = {
46
124
  id: string
47
125
  meta: SessionMeta | null
@@ -50,6 +128,16 @@ export type SessionInfo = {
50
128
  analyzed: AnalyzedMarker | null
51
129
  }
52
130
 
131
+ /** One session's meta.json, or null when absent or unparseable. */
132
+ export function readMeta(id: string): SessionMeta | null {
133
+ return readJsonSafe<SessionMeta>(metaPath(id))
134
+ }
135
+
136
+ /** One session's analyzer marker, or null when absent or unparseable. */
137
+ export function readMarker(id: string): AnalyzedMarker | null {
138
+ return readJsonSafe<AnalyzedMarker>(markerPath(id))
139
+ }
140
+
53
141
  function readJsonSafe<T>(path: string): T | null {
54
142
  try {
55
143
  return JSON.parse(readFileSync(path, 'utf-8')) as T
@@ -76,15 +164,10 @@ export function inspectSession(id: string, now = Date.now()): SessionInfo | null
76
164
  }
77
165
  }
78
166
 
79
- /** All sessions, newest activity first. Missing store dir → empty list. */
167
+ /** All sessions, newest activity first, fully parsed. Missing store dir → empty
168
+ * list. Prefer scanSessions() anywhere latency matters — see the file header. */
80
169
  export function listSessions(now = Date.now()): SessionInfo[] {
81
- let ids: string[]
82
- try {
83
- ids = readdirSync(sessionsRoot()).filter((n) => !n.startsWith('.'))
84
- } catch {
85
- return []
86
- }
87
- return ids
170
+ return sessionIds()
88
171
  .map((id) => inspectSession(id, now))
89
172
  .filter((s): s is SessionInfo => s !== null)
90
173
  .sort((a, b) => (b.lastEventAt?.getTime() ?? 0) - (a.lastEventAt?.getTime() ?? 0))
@@ -2,36 +2,18 @@
2
2
  * Opportunistic analysis trigger — the `git gc --auto` model. Each CLI start
3
3
  * cheaply scans for a closed, unanalyzed session and spawns ONE detached
4
4
  * analyzer for it. No daemon, no cron; a lockfile keeps it single-flight.
5
+ * The same scan pays for the age sweep (see retention.ts), which is why the
6
+ * store stays bounded without anything resembling a background job.
5
7
  */
6
8
  import { spawn } from 'node:child_process'
7
- import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs'
9
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
8
10
  import { join } from 'node:path'
9
11
 
12
+ import { MAX_REMOVALS_ON_START, sweepByAge } from './retention'
10
13
  import { telemetryEnabled } from './settings'
11
- import { listSessions, sessionDir, sessionsRoot, type SessionInfo } from './store'
14
+ import { scanSessions, sessionsRoot, type SessionScan } from './store'
12
15
 
13
16
  const LOCK_STALE_MS = 30 * 60 * 1000
14
- const GC_AGE_MS = 30 * 24 * 60 * 60 * 1000
15
- const GC_MAX_PER_RUN = 20
16
-
17
- /** Opportunistic GC: analyzed sessions idle past the retention age are removed
18
- * (capped per invocation) so the store — and the per-invocation scan — stays
19
- * bounded. Evidence worth keeping has left via reports or filed issues. */
20
- function gcOldSessions(sessions: SessionInfo[]): void {
21
- try {
22
- const cutoff = Date.now() - GC_AGE_MS
23
- let removed = 0
24
- for (const s of sessions) {
25
- if (removed >= GC_MAX_PER_RUN) break
26
- if (s.analyzed !== null && s.lastEventAt !== null && s.lastEventAt.getTime() < cutoff) {
27
- rmSync(sessionDir(s.id), { recursive: true, force: true })
28
- removed++
29
- }
30
- }
31
- } catch {
32
- /* best effort */
33
- }
34
- }
35
17
 
36
18
  function lockPath(): string {
37
19
  return join(sessionsRoot(), '.analyzer.lock')
@@ -76,16 +58,22 @@ export function releaseLock(): void {
76
58
  * the most recently closed unanalyzed session, if any. Never throws, never
77
59
  * blocks — worst case a few ms of directory stats.
78
60
  */
79
- export function maybeTriggerAnalysis(argv: string[]): void {
61
+ export function maybeTriggerAnalysis(argv: string[], scan?: readonly SessionScan[]): void {
80
62
  try {
81
- if (!telemetryEnabled()) return
82
63
  if (process.env.ASTRALE_TELEMETRY_NO_TRIGGER === '1') return
64
+
65
+ const sessions = scan ?? scanSessions()
66
+ // Retention runs ahead of the kill-switch check and on `session` commands
67
+ // too — turning telemetry off must still drain what is already on disk.
68
+ const swept = new Set(sweepByAge(sessions, { limit: MAX_REMOVALS_ON_START }).removed)
69
+
70
+ if (!telemetryEnabled()) return
83
71
  // Never cascade off the session commands themselves.
84
72
  if (argv[2] === 'session') return
85
73
 
86
- const sessions = listSessions()
87
- gcOldSessions(sessions)
88
- const target = sessions.find((s) => s.closed && s.analyzed === null && s.lastEventAt !== null)
74
+ const target = sessions.find(
75
+ (s) => s.closed && !s.analyzed && s.lastEventAt !== null && !swept.has(s.id),
76
+ )
89
77
  if (!target) return
90
78
  if (!claimLock()) return
91
79
 
@@ -0,0 +1,14 @@
1
+ export {
2
+ addUi,
3
+ applyPreset,
4
+ diffUi,
5
+ doctorUi,
6
+ initUi,
7
+ listUi,
8
+ viewUi,
9
+ type InitUiOptions,
10
+ } from '../operations.js'
11
+ export { UI_PRESETS, UiError, type UiLock, type UiPreset } from '../model.js'
12
+ export { discoverUiProject, type UiProject } from '../project.js'
13
+ export { resolveUiRelease } from '../release.js'
14
+ export { shadcnInvocation, type UiRunner } from '../runner.js'
@@ -0,0 +1,14 @@
1
+ # UI local tooling
2
+
3
+ This private CLI owner manages an existing local React project. It does not connect to a Kernel and
4
+ does not add React, Base UI, Tailwind, shadcn, or the UI runtime to the installed CLI dependency
5
+ graph.
6
+
7
+ One registry operation resolves one immutable UI commit. During the V1 prerelease, operations
8
+ without an explicit version resolve the npm `beta` dist-tag; the legacy `latest` channel is not a V1
9
+ source. Package compatibility and registry reads use that commit. Patterns and blocks invoke the
10
+ release-qualified shadcn CLI on demand through the detected package manager. Released themes copy
11
+ the exact admitted embedded CSS directly, while a local playground export is admitted and copied
12
+ without registry or shadcn. Both theme paths install consumer-owned source and activate it through
13
+ one project-relative CSS import, and complete application writes before the lock advances. Dry-run,
14
+ list, and doctor do not claim a file mutation.
@@ -0,0 +1,50 @@
1
+ import { defineLaw } from '@astrale-os/spec/authoring'
2
+
3
+ export const CLI_UI_BETA_DEFAULT = defineLaw({
4
+ id: 'CLI-UI-BETA-DEFAULT',
5
+ statement:
6
+ 'Until the UI V1 channel is promoted, an operation without an explicit version resolves the npm beta dist-tag and never the legacy latest dist-tag.',
7
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-BETA-DEFAULT' }],
8
+ })
9
+
10
+ export const CLI_UI_ONE_SNAPSHOT = defineLaw({
11
+ id: 'CLI-UI-ONE-SNAPSHOT',
12
+ statement:
13
+ 'Each UI operation resolves one immutable repository commit and reads compatibility, registry, and item source only from that snapshot.',
14
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-ONE-SNAPSHOT' }],
15
+ })
16
+
17
+ export const CLI_UI_LOCK_AFTER_SUCCESS = defineLaw({
18
+ id: 'CLI-UI-LOCK-AFTER-SUCCESS',
19
+ statement:
20
+ 'Dry runs and failed package or source operations do not advance astrale-ui.lock.json.',
21
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-LOCK-AFTER-SUCCESS' }],
22
+ })
23
+
24
+ export const CLI_UI_BOUNDED_REMOTE_DOCUMENTS = defineLaw({
25
+ id: 'CLI-UI-BOUNDED-REMOTE-DOCUMENTS',
26
+ statement:
27
+ 'Release metadata and registry documents are size-bounded, include-bounded, and normalized to stable UI registry errors before any project mutation.',
28
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-BOUNDED-REMOTE-DOCUMENTS' }],
29
+ })
30
+
31
+ export const CLI_UI_EXACT_ITEM_SOURCE = defineLaw({
32
+ id: 'CLI-UI-EXACT-ITEM-SOURCE',
33
+ statement:
34
+ 'An add operation admits the exact built item document against the release index and records its content-bearing source digest before any consumer mutation; only patterns and blocks invoke project tooling.',
35
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-EXACT-ITEM-SOURCE' }],
36
+ })
37
+
38
+ export const CLI_UI_THEME_OWNERSHIP = defineLaw({
39
+ id: 'CLI-UI-THEME-OWNERSHIP',
40
+ statement:
41
+ 'A released or locally exported theme becomes one project-contained consumer-owned CSS file, is activated by one relative host stylesheet import, and rolls back file, import, and lock together on failure.',
42
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-THEME-OWNERSHIP' }],
43
+ })
44
+
45
+ export const CLI_UI_SEMANTIC_DIFF = defineLaw({
46
+ id: 'CLI-UI-SEMANTIC-DIFF',
47
+ statement:
48
+ 'Diff is read-only, path-contained, and classifies locked source and files as upstream changed, unchanged, modified, or deleted without delegating truth to tool output.',
49
+ tests: [{ file: '../__tests__/ui.test.ts', id: 'TEST-CLI-UI-SEMANTIC-DIFF' }],
50
+ })
@@ -0,0 +1,16 @@
1
+ import { defineLayout } from '@astrale-os/spec/authoring'
2
+
3
+ export default defineLayout({
4
+ entries: [
5
+ '.spec/',
6
+ '__tests__/',
7
+ 'index.ts',
8
+ 'lock.ts',
9
+ 'model.ts',
10
+ 'operations.ts',
11
+ 'project.ts',
12
+ 'release.ts',
13
+ 'runner.ts',
14
+ ],
15
+ exact: true,
16
+ })