@visual-z/xeno 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +31 -746
  3. package/bin/xeno.exe +26 -0
  4. package/install.cjs +88 -0
  5. package/package.json +18 -48
  6. package/docs/app-server.md +0 -115
  7. package/docs/foundations.md +0 -88
  8. package/docs/install.md +0 -244
  9. package/docs/providers.md +0 -172
  10. package/docs/research/foundation-clients-2026-10-09.md +0 -27
  11. package/docs/research/foundation-editing-2026-10-09.md +0 -56
  12. package/docs/research/foundation-network-2026-10-09.md +0 -37
  13. package/docs/research/foundation-workflow-2026-10-09.md +0 -37
  14. package/src/apps/acp-server.ts +0 -41
  15. package/src/apps/agent-profiles.ts +0 -19
  16. package/src/apps/app-server.ts +0 -30
  17. package/src/apps/approve.ts +0 -49
  18. package/src/apps/cli.ts +0 -99
  19. package/src/apps/commands.ts +0 -319
  20. package/src/apps/completions.ts +0 -87
  21. package/src/apps/control-transport.ts +0 -90
  22. package/src/apps/export-cli.ts +0 -24
  23. package/src/apps/goal-flow.ts +0 -70
  24. package/src/apps/image-draft.ts +0 -46
  25. package/src/apps/input.ts +0 -312
  26. package/src/apps/model-catalog.ts +0 -69
  27. package/src/apps/preferences.ts +0 -20
  28. package/src/apps/project-trust.ts +0 -9
  29. package/src/apps/provider-connect.ts +0 -346
  30. package/src/apps/question-ui.ts +0 -27
  31. package/src/apps/render.ts +0 -181
  32. package/src/apps/resources.ts +0 -66
  33. package/src/apps/review.ts +0 -109
  34. package/src/apps/session-hub.ts +0 -96
  35. package/src/apps/session-lifecycle.ts +0 -64
  36. package/src/apps/shell.ts +0 -2071
  37. package/src/apps/startup-input.ts +0 -135
  38. package/src/apps/startup.ts +0 -1677
  39. package/src/apps/ui/app.ts +0 -1818
  40. package/src/apps/ui/control-model.ts +0 -43
  41. package/src/apps/ui/extension-host.ts +0 -194
  42. package/src/apps/ui/image-cache.ts +0 -121
  43. package/src/apps/ui/palette.ts +0 -88
  44. package/src/apps/ui/project.ts +0 -127
  45. package/src/apps/ui/standalone.ts +0 -148
  46. package/src/apps/ui/status-model.ts +0 -68
  47. package/src/apps/ui/surface.ts +0 -91
  48. package/src/apps/ui/theme.ts +0 -86
  49. package/src/apps/update-check.ts +0 -112
  50. package/src/apps/workflow.ts +0 -58
  51. package/src/core/acceptance.ts +0 -139
  52. package/src/core/action-defaults.ts +0 -58
  53. package/src/core/activity-status.ts +0 -52
  54. package/src/core/agent-profile.ts +0 -130
  55. package/src/core/attachment-input.ts +0 -17
  56. package/src/core/blind-checks.ts +0 -72
  57. package/src/core/boundary.ts +0 -174
  58. package/src/core/branch-receipt.ts +0 -26
  59. package/src/core/catalog-merge.ts +0 -19
  60. package/src/core/checkpoints.ts +0 -85
  61. package/src/core/churn.ts +0 -61
  62. package/src/core/command-artifacts.ts +0 -94
  63. package/src/core/command-contract.ts +0 -151
  64. package/src/core/completion-action.ts +0 -20
  65. package/src/core/completion-review.ts +0 -98
  66. package/src/core/context-detail.ts +0 -37
  67. package/src/core/context-policy.ts +0 -133
  68. package/src/core/context-window.ts +0 -54
  69. package/src/core/continuation.ts +0 -130
  70. package/src/core/control-protocol.ts +0 -69
  71. package/src/core/control-view.ts +0 -39
  72. package/src/core/cycle.ts +0 -17
  73. package/src/core/data-query.ts +0 -201
  74. package/src/core/deadline.ts +0 -40
  75. package/src/core/derive.ts +0 -516
  76. package/src/core/draft-images.ts +0 -56
  77. package/src/core/edit-plan.ts +0 -200
  78. package/src/core/environment.ts +0 -181
  79. package/src/core/event-validation.ts +0 -220
  80. package/src/core/events.ts +0 -670
  81. package/src/core/evidence-ladder.ts +0 -62
  82. package/src/core/evidence-links.ts +0 -68
  83. package/src/core/evidence.ts +0 -240
  84. package/src/core/execution.ts +0 -18
  85. package/src/core/exhaustive.ts +0 -18
  86. package/src/core/export.ts +0 -179
  87. package/src/core/extension-flags.ts +0 -14
  88. package/src/core/external-effects.ts +0 -488
  89. package/src/core/failure-recovery.ts +0 -173
  90. package/src/core/file-precondition.ts +0 -78
  91. package/src/core/file-references.ts +0 -35
  92. package/src/core/frontmatter.ts +0 -213
  93. package/src/core/git-status.ts +0 -29
  94. package/src/core/goal-view.ts +0 -64
  95. package/src/core/goal.ts +0 -200
  96. package/src/core/handoff.ts +0 -479
  97. package/src/core/history-query.ts +0 -139
  98. package/src/core/history.ts +0 -237
  99. package/src/core/image-limits.ts +0 -72
  100. package/src/core/images.ts +0 -46
  101. package/src/core/integration-health.ts +0 -34
  102. package/src/core/json-event.ts +0 -131
  103. package/src/core/json-pointer.ts +0 -46
  104. package/src/core/keymap.ts +0 -153
  105. package/src/core/lsp-framing.ts +0 -34
  106. package/src/core/mcp-config.ts +0 -68
  107. package/src/core/model-picker.ts +0 -64
  108. package/src/core/model-preferences.ts +0 -28
  109. package/src/core/model-replay.ts +0 -22
  110. package/src/core/model-scope.ts +0 -53
  111. package/src/core/note-guidance.ts +0 -34
  112. package/src/core/oauth-fields.ts +0 -44
  113. package/src/core/observation-view.ts +0 -152
  114. package/src/core/outcome.ts +0 -44
  115. package/src/core/overflow.ts +0 -59
  116. package/src/core/packages.ts +0 -47
  117. package/src/core/patch.ts +0 -130
  118. package/src/core/path-suggestions.ts +0 -59
  119. package/src/core/permissions.ts +0 -410
  120. package/src/core/process-receipt.ts +0 -105
  121. package/src/core/process-state.ts +0 -37
  122. package/src/core/program-status.ts +0 -40
  123. package/src/core/progress.ts +0 -150
  124. package/src/core/project.ts +0 -431
  125. package/src/core/prompt-policy.ts +0 -46
  126. package/src/core/prompts.ts +0 -111
  127. package/src/core/question.ts +0 -94
  128. package/src/core/queue.ts +0 -78
  129. package/src/core/read-bounds.ts +0 -24
  130. package/src/core/read-observation.ts +0 -106
  131. package/src/core/recovery-sources.ts +0 -74
  132. package/src/core/regex-dialect.ts +0 -25
  133. package/src/core/request-budget.ts +0 -23
  134. package/src/core/request-progress.ts +0 -87
  135. package/src/core/request-state.ts +0 -273
  136. package/src/core/resource-selection.ts +0 -36
  137. package/src/core/response-recovery.ts +0 -59
  138. package/src/core/resume.ts +0 -59
  139. package/src/core/review.ts +0 -68
  140. package/src/core/ripgrep-release.ts +0 -64
  141. package/src/core/runtime-target.ts +0 -94
  142. package/src/core/scheduling.ts +0 -8
  143. package/src/core/search-observation.ts +0 -97
  144. package/src/core/secrets.ts +0 -188
  145. package/src/core/session-archive.ts +0 -4
  146. package/src/core/session-browser.ts +0 -82
  147. package/src/core/session-entry.ts +0 -61
  148. package/src/core/session-exit.ts +0 -42
  149. package/src/core/session-format.ts +0 -169
  150. package/src/core/session-position.ts +0 -26
  151. package/src/core/session-tree.ts +0 -147
  152. package/src/core/settings.ts +0 -192
  153. package/src/core/skills.ts +0 -165
  154. package/src/core/sql-query.ts +0 -211
  155. package/src/core/stall.ts +0 -128
  156. package/src/core/stop-report.ts +0 -68
  157. package/src/core/text-normalize.ts +0 -81
  158. package/src/core/theme.ts +0 -166
  159. package/src/core/thinking.ts +0 -26
  160. package/src/core/tokens.ts +0 -151
  161. package/src/core/tool-contract.ts +0 -153
  162. package/src/core/tool-discovery.ts +0 -87
  163. package/src/core/tool-failure.ts +0 -81
  164. package/src/core/tool-input.ts +0 -44
  165. package/src/core/transcript.ts +0 -108
  166. package/src/core/tui-mode.ts +0 -29
  167. package/src/core/update-check.ts +0 -157
  168. package/src/core/version.ts +0 -55
  169. package/src/core/web-content.ts +0 -80
  170. package/src/core/work-state.ts +0 -154
  171. package/src/core/workflow-mode.ts +0 -34
  172. package/src/core/working-set.ts +0 -119
  173. package/src/core/yaml.ts +0 -282
  174. package/src/runtime/acp-agent.ts +0 -289
  175. package/src/runtime/agent-profiles.ts +0 -21
  176. package/src/runtime/auth.ts +0 -367
  177. package/src/runtime/blind-check.ts +0 -36
  178. package/src/runtime/branch-jobs.ts +0 -36
  179. package/src/runtime/catalog.ts +0 -285
  180. package/src/runtime/clipboard.ts +0 -125
  181. package/src/runtime/command-artifacts.ts +0 -64
  182. package/src/runtime/connection-reuse.ts +0 -40
  183. package/src/runtime/control-client.ts +0 -149
  184. package/src/runtime/control-connection.ts +0 -139
  185. package/src/runtime/counter-check.ts +0 -74
  186. package/src/runtime/environment-tools.ts +0 -60
  187. package/src/runtime/evidence-notes.ts +0 -35
  188. package/src/runtime/exec-sessions.ts +0 -456
  189. package/src/runtime/execution-control.ts +0 -40
  190. package/src/runtime/extension-loader.ts +0 -37
  191. package/src/runtime/extension-ui.ts +0 -47
  192. package/src/runtime/extensions.ts +0 -370
  193. package/src/runtime/formatter-tool.ts +0 -38
  194. package/src/runtime/formatter.ts +0 -50
  195. package/src/runtime/foundation.ts +0 -116
  196. package/src/runtime/history-cache.ts +0 -57
  197. package/src/runtime/history-tools.ts +0 -168
  198. package/src/runtime/images.ts +0 -80
  199. package/src/runtime/integrations.ts +0 -216
  200. package/src/runtime/loop.ts +0 -3108
  201. package/src/runtime/lsp-client.ts +0 -201
  202. package/src/runtime/mcp-client.ts +0 -264
  203. package/src/runtime/mcp-oauth.ts +0 -168
  204. package/src/runtime/mcp-schema.ts +0 -51
  205. package/src/runtime/model-config.ts +0 -232
  206. package/src/runtime/model-data.LICENSE +0 -27
  207. package/src/runtime/model-data.ts +0 -21
  208. package/src/runtime/model-discovery.ts +0 -208
  209. package/src/runtime/model-replay.ts +0 -10
  210. package/src/runtime/model-scope.ts +0 -20
  211. package/src/runtime/model-sources.ts +0 -151
  212. package/src/runtime/model.ts +0 -686
  213. package/src/runtime/models-fetch.ts +0 -57
  214. package/src/runtime/oauth-device.ts +0 -86
  215. package/src/runtime/oauth.ts +0 -853
  216. package/src/runtime/open-image.ts +0 -42
  217. package/src/runtime/openai-responses.ts +0 -304
  218. package/src/runtime/openai.ts +0 -697
  219. package/src/runtime/output-schema.ts +0 -26
  220. package/src/runtime/package-manager.ts +0 -125
  221. package/src/runtime/patch-tool.ts +0 -38
  222. package/src/runtime/permission-resources.ts +0 -18
  223. package/src/runtime/pi-provider.ts +0 -423
  224. package/src/runtime/program-status.ts +0 -38
  225. package/src/runtime/progress.ts +0 -26
  226. package/src/runtime/provider-access.ts +0 -21
  227. package/src/runtime/provider-connect.ts +0 -109
  228. package/src/runtime/provider-presets.ts +0 -98
  229. package/src/runtime/provider-protocols.ts +0 -29
  230. package/src/runtime/provider-request.ts +0 -87
  231. package/src/runtime/provider-tool-policy.ts +0 -33
  232. package/src/runtime/provider.ts +0 -580
  233. package/src/runtime/question-tool.ts +0 -62
  234. package/src/runtime/retry.ts +0 -447
  235. package/src/runtime/review-tool.ts +0 -48
  236. package/src/runtime/rich-text.ts +0 -36
  237. package/src/runtime/runtime-target.ts +0 -149
  238. package/src/runtime/secrets.ts +0 -79
  239. package/src/runtime/self-update.ts +0 -74
  240. package/src/runtime/session-control.ts +0 -265
  241. package/src/runtime/session-hub.ts +0 -85
  242. package/src/runtime/stream-idle.ts +0 -80
  243. package/src/runtime/tool-broker.ts +0 -137
  244. package/src/runtime/tool-discovery.ts +0 -58
  245. package/src/runtime/tool-queue.ts +0 -77
  246. package/src/runtime/tools.ts +0 -1820
  247. package/src/runtime/turn-effects.ts +0 -31
  248. package/src/runtime/update-check.ts +0 -48
  249. package/src/runtime/usage.ts +0 -253
  250. package/src/runtime/version.ts +0 -12
  251. package/src/runtime/web-http.ts +0 -56
  252. package/src/runtime/web-tools.ts +0 -88
  253. package/src/runtime/working-notes.ts +0 -60
  254. package/src/runtime/workspace-lifecycle.ts +0 -141
  255. package/src/store/acceptance.ts +0 -15
  256. package/src/store/agent-profile-files.ts +0 -13
  257. package/src/store/atomic.ts +0 -31
  258. package/src/store/auth-store.ts +0 -50
  259. package/src/store/blind-checks.ts +0 -32
  260. package/src/store/blobs.ts +0 -143
  261. package/src/store/checkpoint-history.ts +0 -32
  262. package/src/store/command-artifacts.ts +0 -96
  263. package/src/store/command.ts +0 -111
  264. package/src/store/config.ts +0 -69
  265. package/src/store/context-files.ts +0 -436
  266. package/src/store/file-checkpoints.ts +0 -108
  267. package/src/store/file-index.ts +0 -174
  268. package/src/store/foundation-config.ts +0 -24
  269. package/src/store/git-checkpoints.ts +0 -67
  270. package/src/store/git-status.ts +0 -15
  271. package/src/store/history-search.ts +0 -19
  272. package/src/store/log.ts +0 -725
  273. package/src/store/lsp-document.ts +0 -30
  274. package/src/store/mcp-credentials.ts +0 -85
  275. package/src/store/models.ts +0 -60
  276. package/src/store/observations.ts +0 -224
  277. package/src/store/packages.ts +0 -98
  278. package/src/store/patch.ts +0 -92
  279. package/src/store/permission-paths.ts +0 -34
  280. package/src/store/process-receipts.ts +0 -34
  281. package/src/store/query-source.ts +0 -49
  282. package/src/store/recovery.ts +0 -44
  283. package/src/store/replay.ts +0 -76
  284. package/src/store/resource-index.ts +0 -170
  285. package/src/store/resources.ts +0 -285
  286. package/src/store/restore.ts +0 -110
  287. package/src/store/review.ts +0 -109
  288. package/src/store/runtime-target.ts +0 -64
  289. package/src/store/search.ts +0 -728
  290. package/src/store/secrets.ts +0 -44
  291. package/src/store/serial.ts +0 -13
  292. package/src/store/session-archive.ts +0 -165
  293. package/src/store/session-browser.ts +0 -68
  294. package/src/store/session-position.ts +0 -27
  295. package/src/store/sessions.ts +0 -525
  296. package/src/store/source-cancellation.ts +0 -6
  297. package/src/store/tool-binaries.ts +0 -90
  298. package/src/store/trust.ts +0 -82
  299. package/src/store/update-cache.ts +0 -36
  300. package/src/store/user-settings.ts +0 -30
  301. package/src/store/workspace-clone.ts +0 -42
  302. package/src/store/workspace-observation.ts +0 -113
  303. package/src/store/workspace.ts +0 -115
  304. package/src/store/worktree.ts +0 -278
  305. package/src/tui/ansi-spans.ts +0 -126
  306. package/src/tui/diff.ts +0 -160
  307. package/src/tui/exhaustive.ts +0 -14
  308. package/src/tui/fuzzy.ts +0 -75
  309. package/src/tui/style.ts +0 -208
  310. package/src/tui/ui/atomic-draft.ts +0 -121
  311. package/src/tui/ui/branch-panel.ts +0 -47
  312. package/src/tui/ui/canvas.ts +0 -96
  313. package/src/tui/ui/colors.ts +0 -70
  314. package/src/tui/ui/composer.ts +0 -199
  315. package/src/tui/ui/control-panel.ts +0 -25
  316. package/src/tui/ui/control-sidebar.ts +0 -49
  317. package/src/tui/ui/dialog.ts +0 -398
  318. package/src/tui/ui/draft.ts +0 -23
  319. package/src/tui/ui/duration.ts +0 -16
  320. package/src/tui/ui/edge.ts +0 -186
  321. package/src/tui/ui/image-size.ts +0 -44
  322. package/src/tui/ui/ink.ts +0 -210
  323. package/src/tui/ui/items.ts +0 -42
  324. package/src/tui/ui/keys.ts +0 -40
  325. package/src/tui/ui/layout.ts +0 -74
  326. package/src/tui/ui/list-model.ts +0 -86
  327. package/src/tui/ui/list-view.ts +0 -172
  328. package/src/tui/ui/mascot.ts +0 -67
  329. package/src/tui/ui/messages.ts +0 -518
  330. package/src/tui/ui/motion.ts +0 -28
  331. package/src/tui/ui/paths.ts +0 -10
  332. package/src/tui/ui/pictures.ts +0 -108
  333. package/src/tui/ui/prompt-history.ts +0 -60
  334. package/src/tui/ui/screen.ts +0 -579
  335. package/src/tui/ui/status.ts +0 -403
  336. package/src/tui/ui/styled.ts +0 -85
  337. package/src/tui/ui/tool-card.ts +0 -150
  338. package/src/tui/width.ts +0 -567
  339. package/src/vendor/pi-rich/LICENSE.upstream +0 -21
  340. package/src/vendor/pi-rich/UPSTREAM.json +0 -17
  341. package/src/vendor/pi-rich/clipboard-command.ts +0 -44
  342. package/src/vendor/pi-rich/exif-orientation.ts +0 -182
  343. package/src/vendor/pi-rich/html.ts +0 -51
  344. package/src/vendor/pi-rich/image-convert.ts +0 -49
  345. package/src/vendor/pi-rich/image-resize-core.ts +0 -164
  346. package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
  347. package/src/vendor/pi-rich/image-resize.ts +0 -123
  348. package/src/vendor/pi-rich/mime.ts +0 -116
  349. package/src/vendor/pi-rich/photon.ts +0 -139
  350. package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
@@ -1,82 +0,0 @@
1
- /**
2
- * Project trust: whether to load `.xeno/settings.json` for a directory.
3
- *
4
- * Trust does not affect tool execution. Path tools stay confined to the
5
- * workspace root regardless, and bash still goes through the permission
6
- * gate. This file only records which directories may contribute project
7
- * settings. AGENTS.md is loaded whether the project is trusted or not.
8
- */
9
-
10
- import { dirname, join, resolve } from "node:path";
11
- import { configDir, readJsonFile, writeJsonFile } from "./config.ts";
12
- import { parseSettings } from "../core/settings.ts";
13
-
14
- export type TrustFile = { readonly [dir: string]: boolean };
15
-
16
- export function trustPath(): string {
17
- return join(configDir(), "trust.json");
18
- }
19
-
20
- export function projectSettingsPath(workspace: string): string {
21
- return join(workspace, ".xeno", "settings.json");
22
- }
23
-
24
- /**
25
- * Closest saved decision on `dir` or a parent. Undefined if none.
26
- *
27
- * A parent applies to its children so trusting a repo root covers the
28
- * packages underneath; a closer entry wins so a single untrusted nested
29
- * project can opt out.
30
- */
31
- export function lookupTrust(decisions: TrustFile, dir: string): boolean | undefined {
32
- let current = resolve(dir);
33
- for (;;) {
34
- const value = decisions[current];
35
- if (value !== undefined) return value;
36
- const parent = dirname(current);
37
- if (parent === current) return undefined;
38
- current = parent;
39
- }
40
- }
41
-
42
- export async function projectTrust(workspace: string, temporary?: boolean): Promise<{ trusted: boolean; saved?: { path: string; trusted: boolean }; policy: "ask" | "always" | "never" }> {
43
- const [saved, settings] = await Promise.all([readTrust(), readJsonFile<unknown>(join(configDir(), "settings.json"))]);
44
- const policy = parseSettings(settings.value).settings.defaultProjectTrust ?? "ask";
45
- let path = resolve(workspace);
46
- for (;;) {
47
- const decision = saved.value[path];
48
- if (decision !== undefined) return { trusted: temporary ?? decision, saved: { path, trusted: decision }, policy };
49
- const parent = dirname(path); if (parent === path) break; path = parent;
50
- }
51
- return { trusted: temporary ?? policy === "always", policy };
52
- }
53
-
54
- export async function readTrust(): Promise<{ value: TrustFile; error: string | null }> {
55
- const { value, error } = await readJsonFile<unknown>(trustPath());
56
- if (error) return { value: {}, error };
57
- if (value === null) return { value: {}, error: null };
58
- if (!isTrustFile(value)) {
59
- return { value: {}, error: `${trustPath()} is not a directory-to-boolean map` };
60
- }
61
- return { value, error: null };
62
- }
63
-
64
- export async function writeTrust(decisions: TrustFile): Promise<void> {
65
- await writeJsonFile(trustPath(), decisions, false);
66
- }
67
-
68
- export async function saveTrust(dir: string, trusted: boolean): Promise<void> {
69
- const { value } = await readTrust();
70
- const next: { [dir: string]: boolean } = { ...value };
71
- next[resolve(dir)] = trusted;
72
- await writeTrust(next);
73
- }
74
-
75
- function isTrustFile(value: unknown): value is TrustFile {
76
- if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
77
- const rec = value as { readonly [k: string]: unknown };
78
- for (const key of Object.keys(rec)) {
79
- if (typeof rec[key] !== "boolean") return false;
80
- }
81
- return true;
82
- }
@@ -1,36 +0,0 @@
1
- /**
2
- * The last update check, kept in the config directory (docs/tasks/feat-update-check.md).
3
- *
4
- * `{ checkedAt, latest? }` and nothing else: no identifiers, no history. A
5
- * missing or unreadable file means "never checked"; it is rewritten whole.
6
- */
7
- import { readFile } from "node:fs/promises";
8
- import { existsSync, realpathSync } from "node:fs";
9
- import { dirname, join } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
- import type { UpdateCache } from "../core/update-check.ts";
12
- import { atomicWrite } from "./atomic.ts";
13
- import { configDir } from "./config.ts";
14
-
15
- export function updateCachePath(): string { return join(configDir(), "update-check.json"); }
16
-
17
- export async function readUpdateCache(): Promise<UpdateCache | null> {
18
- try {
19
- const value = JSON.parse(await readFile(updateCachePath(), "utf8")) as Record<string, unknown>;
20
- if (typeof value["checkedAt"] !== "number") return null;
21
- return { checkedAt: value["checkedAt"], ...(typeof value["latest"] === "string" ? { latest: value["latest"] } : {}) };
22
- } catch { return null; }
23
- }
24
-
25
- export async function writeUpdateCache(cache: UpdateCache): Promise<void> {
26
- try { await atomicWrite(updateCachePath(), JSON.stringify(cache) + "\n"); } catch { /* a read-only config dir only means checking again next time */ }
27
- }
28
-
29
- /** This copy's package root, resolved through symlinks, and whether it is a git checkout. */
30
- export function packageLocation(): { root: string; git: boolean } {
31
- // src/store/update-cache.ts → the package root two directories up.
32
- const here = dirname(fileURLToPath(import.meta.url));
33
- let root = join(here, "..", "..");
34
- try { root = realpathSync(root); } catch { /* keep the unresolved path */ }
35
- return { root, git: existsSync(join(root, ".git")) };
36
- }
@@ -1,30 +0,0 @@
1
- /** One read/modify/replace owner prevents automatic selections from racing other settings (Q.25). */
2
- import { readFile } from "node:fs/promises";
3
- import { settingsPath } from "./config.ts";
4
- import { atomicWrite } from "./atomic.ts";
5
- import { serialized } from "./serial.ts";
6
-
7
- async function readSettings(path: string): Promise<Record<string, unknown>> {
8
- let text: string;
9
- try { text = await readFile(path, "utf8"); }
10
- catch (error) {
11
- if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return {};
12
- throw error;
13
- }
14
- let value: unknown;
15
- try { value = JSON.parse(text); }
16
- catch { throw new Error(`${path} is not valid JSON; repair it before saving settings`); }
17
- if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${path} must contain a settings object`);
18
- return value as Record<string, unknown>;
19
- }
20
-
21
- export function editUserSettings(edit: (previous: Record<string, unknown>) => Record<string, unknown> | null): Promise<void> {
22
- // Capture the destination before any await, including when another profile changes configDir().
23
- const path = settingsPath();
24
- // Packages and resource overrides already coordinate this file through serialized().
25
- return serialized(path, async () => {
26
- const next = edit(await readSettings(path));
27
- if (next === null) return;
28
- await atomicWrite(path, `${JSON.stringify(next, null, 2)}\n`);
29
- });
30
- }
@@ -1,42 +0,0 @@
1
- /**
2
- * A disposable copy of a workspace, for running a check somewhere it cannot
3
- * touch the real files (feat-counter-check).
4
- *
5
- * Copy-on-write where the filesystem offers it -- APFS clonefile through
6
- * `cp -c`, reflinks through `cp --reflink=auto` -- so a workspace with large
7
- * dependency trees costs metadata, not bytes. Where it does not, cp falls back
8
- * to a real copy, bounded by the deadline below.
9
- */
10
- import { mkdtemp, rm } from "node:fs/promises";
11
- import { tmpdir } from "node:os";
12
- import { join } from "node:path";
13
-
14
- /**
15
- * Assumed, not measured: a clone is metadata-only on APFS and reflink
16
- * filesystems; a full copy that takes longer than this is better reported as
17
- * unavailable than waited on.
18
- */
19
- export const CLONE_TIMEOUT_MS = 60_000;
20
-
21
- export type WorkspaceClone = { ok: true; path: string; dispose: () => Promise<void> } | { ok: false; reason: string };
22
-
23
- export async function cloneWorkspace(root: string, signal?: AbortSignal): Promise<WorkspaceClone> {
24
- const parent = await mkdtemp(join(tmpdir(), "xeno-counter-check-")), path = join(parent, "workspace");
25
- const dispose = () => rm(parent, { recursive: true, force: true });
26
- const attempts = process.platform === "darwin" ? [["cp", "-cR", root, path], ["cp", "-R", root, path]] : [["cp", "-a", "--reflink=auto", root, path]];
27
- let reason = "the workspace could not be copied";
28
- for (const argv of attempts) {
29
- if (signal?.aborted) { await dispose(); return { ok: false, reason: "cancelled" }; }
30
- await rm(path, { recursive: true, force: true });
31
- const child = Bun.spawn(argv, { stdout: "ignore", stderr: "pipe" });
32
- const timer = setTimeout(() => child.kill(), CLONE_TIMEOUT_MS);
33
- const abort = () => child.kill();
34
- signal?.addEventListener("abort", abort, { once: true });
35
- const [exit, stderr] = await Promise.all([child.exited, new Response(child.stderr).text()]);
36
- clearTimeout(timer); signal?.removeEventListener("abort", abort);
37
- if (exit === 0) return { ok: true, path, dispose };
38
- reason = child.signalCode ? `copying the workspace exceeded ${CLONE_TIMEOUT_MS / 1000}s` : `the workspace could not be copied: ${stderr.trim().split("\n").at(-1) ?? `exit ${exit}`}`;
39
- }
40
- await dispose();
41
- return { ok: false, reason };
42
- }
@@ -1,113 +0,0 @@
1
- /** P.3: explicit source inventory for verification and optional Git checkpoints. */
2
- import { readdir, lstat } from "node:fs/promises";
3
- import { join, relative, resolve } from "node:path";
4
- import { blobAddress, digestFile } from "./blobs.ts";
5
-
6
- // Conservative resource budgets, not measured performance targets. Exceeding them is unknown, never an empty workspace.
7
- const MAX_FILES = 20_000;
8
- const MAX_BYTES = 256 * 1024 * 1024;
9
- export const MAX_UNTRACKED_BYTES = 2 * 1024 * 1024;
10
- export const SOURCE_IGNORE = [".git", ".xeno", "node_modules", "target", ".venv", "venv", "__pycache__", ".pytest_cache", ".mypy_cache", ".ruff_cache", ".tox", ".gradle", ".next", ".turbo", ".cache"];
11
- const ignored = (path: string) => path.split("/").some(part => SOURCE_IGNORE.includes(part));
12
-
13
- /** Bounded subprocess; private-repository callers override only Git's storage, never the user's index. */
14
- export async function gitBytes(root: string, args: readonly string[], input?: Uint8Array, signal?: AbortSignal,
15
- environment: Record<string, string> = {}): Promise<Uint8Array> {
16
- if (signal?.aborted) throw new Error("Git observation cancelled");
17
- const env: Record<string, string | undefined> = { ...process.env, GIT_OPTIONAL_LOCKS: "0", ...environment };
18
- for (const key of ["GIT_INDEX_FILE", "GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"]) {
19
- if (!(key in environment)) delete env[key];
20
- }
21
- const child = Bun.spawn(["git", "--no-optional-locks", ...args], { cwd: root, env, stdin: input ?? "ignore", stdout: "pipe", stderr: "pipe" });
22
- const abort = () => child.kill();
23
- const timer = setTimeout(abort, 10_000);
24
- signal?.addEventListener("abort", abort, { once: true });
25
- const read = async (stream: ReadableStream<Uint8Array>) => {
26
- const reader = stream.getReader(), chunks: Uint8Array[] = [];
27
- let bytes = 0;
28
- for (;;) {
29
- const item = await reader.read();
30
- if (item.done) break;
31
- bytes += item.value.length;
32
- if (bytes > MAX_BYTES) { child.kill(); throw new Error("Git observation exceeds resource budget"); }
33
- chunks.push(item.value);
34
- }
35
- return Buffer.concat(chunks);
36
- };
37
- try {
38
- const [stdout, , code] = await Promise.all([read(child.stdout), read(child.stderr), child.exited]);
39
- if (code !== 0 || signal?.aborted) throw new Error("Git observation unavailable or cancelled");
40
- return stdout;
41
- } finally { clearTimeout(timer); signal?.removeEventListener("abort", abort); }
42
- }
43
- const text = (bytes: Uint8Array) => new TextDecoder("utf-8", { fatal: true }).decode(bytes);
44
-
45
- export async function sourceInventory(root: string, signal?: AbortSignal, requireGit = false, ownedPaths: readonly string[] = []): Promise<{ paths: string[]; git: boolean }> {
46
- const owned = ownedPaths.map(path => relative(resolve(root), resolve(path))).filter(path => path && !path.startsWith("../"));
47
- const excluded = (path: string) => ignored(path) || owned.some(directory => path === directory || path.startsWith(directory + "/"));
48
- let git = false;
49
- try { await gitBytes(root, ["rev-parse", "--show-toplevel"], undefined, signal); git = true; }
50
- catch { if (requireGit || signal?.aborted) throw new Error("Git checkpoints require a Git working directory"); }
51
- let candidates: { path: string; tracked: boolean }[] = [];
52
- if (git) {
53
- const tracked = text(await gitBytes(root, ["ls-files", "-z", "--cached"], undefined, signal)).split("\0").filter(Boolean);
54
- const untracked = text(await gitBytes(root, ["ls-files", "-z", "--others", "--exclude-standard"], undefined, signal)).split("\0").filter(Boolean);
55
- candidates = [...new Set(tracked)].map(path => ({ path, tracked: true }));
56
- const known = new Set(tracked);
57
- candidates.push(...untracked.filter(path => !known.has(path)).map(path => ({ path, tracked: false })));
58
- } else {
59
- let entries = 0;
60
- const walk = async (directory: string, prefix: string): Promise<void> => {
61
- if (signal?.aborted) throw new Error("workspace observation cancelled");
62
- for (const entry of await readdir(directory, { withFileTypes: true })) {
63
- if (++entries > MAX_FILES) throw new Error("source inventory exceeds file budget");
64
- const path = prefix + entry.name;
65
- if (excluded(path)) continue;
66
- if (entry.isDirectory()) await walk(join(directory, entry.name), path + "/");
67
- else if (entry.isFile()) candidates.push({ path, tracked: true });
68
- }
69
- };
70
- await walk(root, "");
71
- }
72
- candidates = candidates.filter(candidate => !excluded(candidate.path));
73
- if (candidates.length > MAX_FILES) throw new Error("source inventory exceeds file budget");
74
- const paths: string[] = [];
75
- let bytes = 0;
76
- for (const candidate of candidates.sort((a, b) => a.path < b.path ? -1 : 1)) {
77
- if (signal?.aborted) throw new Error("workspace observation cancelled");
78
- const path = candidate.path;
79
- if (excluded(path)) continue;
80
- const parts = path.split("/");
81
- if (parts.includes("..") || path.startsWith("/")) throw new Error("unsafe source inventory");
82
- let safe = true;
83
- for (let i = 1; i <= parts.length; i++) {
84
- try {
85
- const info = await lstat(join(root, ...parts.slice(0, i)));
86
- if (info.isSymbolicLink() || i === parts.length && !info.isFile()) { safe = false; break; }
87
- if (i === parts.length) {
88
- if (!candidate.tracked && info.size > MAX_UNTRACKED_BYTES) { safe = false; break; }
89
- bytes += info.size;
90
- }
91
- } catch (error) {
92
- if ((error as { code?: string }).code !== "ENOENT") throw error;
93
- safe = false; break;
94
- }
95
- }
96
- if (bytes > MAX_BYTES) throw new Error("source inventory exceeds byte budget");
97
- if (safe) paths.push(path);
98
- }
99
- return { paths, git };
100
- }
101
-
102
- export type WorkspaceObservation = { ok: true; fingerprint: string } | { ok: false; reason: string };
103
- export async function observeWorkspace(root: string, signal?: AbortSignal, ownedPaths: readonly string[] = []): Promise<WorkspaceObservation> {
104
- try {
105
- const inventory = await sourceInventory(root, signal, false, ownedPaths);
106
- const entries: [string, string, number][] = [];
107
- for (const path of inventory.paths) {
108
- if (signal?.aborted) throw new Error("workspace observation cancelled");
109
- entries.push([path, await digestFile(join(root, path)), (await lstat(join(root, path))).mode & 0o777]);
110
- }
111
- return { ok: true, fingerprint: blobAddress(JSON.stringify(["source-v1", inventory.git ? "git" : "files", entries])) };
112
- } catch (error) { return { ok: false, reason: error instanceof Error ? error.message : "workspace observation unavailable" }; }
113
- }
@@ -1,115 +0,0 @@
1
- /**
2
- * A bounded content scan of a workspace: relative path -> digest, and one
3
- * version digest over the whole set.
4
- *
5
- * Session file history no longer comes from per-turn scans. P.3 replaced them
6
- * with journaled checkpoints and source fingerprints (workspace-observation.ts,
7
- * WorkspaceLifecycle), and S.3 removed the compatibility mode that still
8
- * scanned. What remains is the scanner itself and the default ignore list the
9
- * search and file index share.
10
- */
11
-
12
- import { readdir, stat } from "node:fs/promises";
13
- import type { Dirent } from "node:fs";
14
- import { join, relative, sep } from "node:path";
15
- import { blobAddress, digestFile } from "./blobs.ts";
16
-
17
- export interface ScanLimits {
18
- maxFiles: number;
19
- maxFileBytes: number;
20
- maxTotalBytes: number;
21
- }
22
-
23
- /**
24
- * Hashing and blob capture stream regular files, so file size has no default
25
- * product cap. Callers may impose explicit resource limits. Exceeding one,
26
- * losing a directory, or meeting an unsupported file type fails the whole scan;
27
- * a partial listing could make a restore delete files it never recorded.
28
- */
29
- export const DEFAULT_SCAN_LIMITS: ScanLimits = {
30
- maxFiles: Infinity,
31
- maxFileBytes: Infinity,
32
- maxTotalBytes: Infinity,
33
- };
34
-
35
- /**
36
- * Directories never scanned, snapshotted or restored.
37
- *
38
- * One constant rather than a default repeated at each call site: the snapshot
39
- * and the restore have to agree exactly. A path the snapshot skipped is absent
40
- * from every target file set, and applyRestore deletes whatever is not in the
41
- * target -- so a restore using a shorter list than the snapshot would delete
42
- * the log, the git directory, or the user's dependencies.
43
- */
44
- export const DEFAULT_IGNORE: readonly string[] = [".xeno", ".git", "node_modules"];
45
-
46
- /** Relative path -> content digest. Same address space as the blob store. */
47
- export type FileSet = Record<string, string>;
48
-
49
- export function workspaceVersion(files: FileSet): string {
50
- return blobAddress(JSON.stringify(Object.entries(files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)));
51
- }
52
-
53
- export type ScanResult =
54
- | { ok: true; files: FileSet }
55
- | { ok: false; error: string };
56
-
57
- function isIgnored(rel: string, ignore: readonly string[]): boolean {
58
- const parts = rel.split(sep);
59
- return ignore.some((i) => parts.includes(i));
60
- }
61
-
62
- export async function scanWorkspace(
63
- root: string,
64
- ignore: readonly string[],
65
- limits: ScanLimits = DEFAULT_SCAN_LIMITS,
66
- ): Promise<ScanResult> {
67
- const files: FileSet = {};
68
- let count = 0;
69
- let total = 0;
70
-
71
- async function walk(dir: string): Promise<string | null> {
72
- let entries: Dirent[];
73
- try {
74
- entries = await readdir(dir, { withFileTypes: true });
75
- } catch (err) {
76
- return `cannot scan ${dir}: ${String(err)}`;
77
- }
78
- for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
79
- const abs = join(dir, entry.name);
80
- const rel = relative(root, abs);
81
- if (isIgnored(rel, ignore)) continue;
82
- if (entry.isDirectory()) {
83
- const err = await walk(abs);
84
- if (err) return err;
85
- continue;
86
- }
87
- // A symlink is not workspace content: it can point anywhere, and search
88
- // and the file index already refuse to follow one. It is left out of the
89
- // file set, so snapshots never read through it and a restore never
90
- // deletes or replaces it (/back does not restore symlinks). Anything
91
- // else that is not a regular file still fails the whole scan.
92
- if (entry.isSymbolicLink()) continue;
93
- if (!entry.isFile()) return `cannot snapshot unsupported file type: ${rel}`;
94
-
95
- const info = await stat(abs);
96
- if (info.size > limits.maxFileBytes) {
97
- return `${rel} is ${info.size} bytes, over maxFileBytes (${limits.maxFileBytes})`;
98
- }
99
- if (++count > limits.maxFiles) {
100
- return `workspace has more than maxFiles (${limits.maxFiles}) files`;
101
- }
102
- total += info.size;
103
- if (total > limits.maxTotalBytes) {
104
- return `workspace exceeds maxTotalBytes (${limits.maxTotalBytes})`;
105
- }
106
- files[rel.split(sep).join("/")] = await digestFile(abs);
107
- }
108
- return null;
109
- }
110
-
111
- try {
112
- const error = await walk(root);
113
- return error ? { ok: false, error } : { ok: true, files };
114
- } catch (error) { return { ok: false, error: `incomplete workspace scan: ${String(error)}` }; }
115
- }