fixo-cli 1.0.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 (303) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +530 -0
  3. package/dist/agent/agent-client.d.ts +108 -0
  4. package/dist/agent/agent-client.d.ts.map +1 -0
  5. package/dist/agent/agent-client.js +1247 -0
  6. package/dist/agent/agent-client.js.map +1 -0
  7. package/dist/agent/agent-pool.d.ts +20 -0
  8. package/dist/agent/agent-pool.d.ts.map +1 -0
  9. package/dist/agent/agent-pool.js +217 -0
  10. package/dist/agent/agent-pool.js.map +1 -0
  11. package/dist/agent/background-awareness.d.ts +55 -0
  12. package/dist/agent/background-awareness.d.ts.map +1 -0
  13. package/dist/agent/background-awareness.js +104 -0
  14. package/dist/agent/background-awareness.js.map +1 -0
  15. package/dist/agent/command-parser.d.ts +33 -0
  16. package/dist/agent/command-parser.d.ts.map +1 -0
  17. package/dist/agent/command-parser.js +120 -0
  18. package/dist/agent/command-parser.js.map +1 -0
  19. package/dist/agent/context-budget.d.ts +91 -0
  20. package/dist/agent/context-budget.d.ts.map +1 -0
  21. package/dist/agent/context-budget.js +219 -0
  22. package/dist/agent/context-budget.js.map +1 -0
  23. package/dist/agent/conversation.d.ts +190 -0
  24. package/dist/agent/conversation.d.ts.map +1 -0
  25. package/dist/agent/conversation.js +547 -0
  26. package/dist/agent/conversation.js.map +1 -0
  27. package/dist/agent/hooks.d.ts +72 -0
  28. package/dist/agent/hooks.d.ts.map +1 -0
  29. package/dist/agent/hooks.js +214 -0
  30. package/dist/agent/hooks.js.map +1 -0
  31. package/dist/agent/mcp-bridge.d.ts +13 -0
  32. package/dist/agent/mcp-bridge.d.ts.map +1 -0
  33. package/dist/agent/mcp-bridge.js +86 -0
  34. package/dist/agent/mcp-bridge.js.map +1 -0
  35. package/dist/agent/mcp-client.d.ts +24 -0
  36. package/dist/agent/mcp-client.d.ts.map +1 -0
  37. package/dist/agent/mcp-client.js +146 -0
  38. package/dist/agent/mcp-client.js.map +1 -0
  39. package/dist/agent/mcp-manager.d.ts +13 -0
  40. package/dist/agent/mcp-manager.d.ts.map +1 -0
  41. package/dist/agent/mcp-manager.js +84 -0
  42. package/dist/agent/mcp-manager.js.map +1 -0
  43. package/dist/agent/mcp-registry.d.ts +45 -0
  44. package/dist/agent/mcp-registry.d.ts.map +1 -0
  45. package/dist/agent/mcp-registry.js +98 -0
  46. package/dist/agent/mcp-registry.js.map +1 -0
  47. package/dist/agent/orchestrator.d.ts +14 -0
  48. package/dist/agent/orchestrator.d.ts.map +1 -0
  49. package/dist/agent/orchestrator.js +118 -0
  50. package/dist/agent/orchestrator.js.map +1 -0
  51. package/dist/agent/parser-adapter.d.ts +120 -0
  52. package/dist/agent/parser-adapter.d.ts.map +1 -0
  53. package/dist/agent/parser-adapter.js +265 -0
  54. package/dist/agent/parser-adapter.js.map +1 -0
  55. package/dist/agent/parsers/imports.d.ts +11 -0
  56. package/dist/agent/parsers/imports.d.ts.map +1 -0
  57. package/dist/agent/parsers/imports.js +94 -0
  58. package/dist/agent/parsers/imports.js.map +1 -0
  59. package/dist/agent/parsers/shell.d.ts +23 -0
  60. package/dist/agent/parsers/shell.d.ts.map +1 -0
  61. package/dist/agent/parsers/shell.js +200 -0
  62. package/dist/agent/parsers/shell.js.map +1 -0
  63. package/dist/agent/parsers/symbols.d.ts +17 -0
  64. package/dist/agent/parsers/symbols.d.ts.map +1 -0
  65. package/dist/agent/parsers/symbols.js +103 -0
  66. package/dist/agent/parsers/symbols.js.map +1 -0
  67. package/dist/agent/permissions.d.ts +65 -0
  68. package/dist/agent/permissions.d.ts.map +1 -0
  69. package/dist/agent/permissions.js +219 -0
  70. package/dist/agent/permissions.js.map +1 -0
  71. package/dist/agent/predictive-gate.d.ts +69 -0
  72. package/dist/agent/predictive-gate.d.ts.map +1 -0
  73. package/dist/agent/predictive-gate.js +128 -0
  74. package/dist/agent/predictive-gate.js.map +1 -0
  75. package/dist/agent/provider-cooldown.d.ts +144 -0
  76. package/dist/agent/provider-cooldown.d.ts.map +1 -0
  77. package/dist/agent/provider-cooldown.js +300 -0
  78. package/dist/agent/provider-cooldown.js.map +1 -0
  79. package/dist/agent/providers-manager.d.ts +109 -0
  80. package/dist/agent/providers-manager.d.ts.map +1 -0
  81. package/dist/agent/providers-manager.js +464 -0
  82. package/dist/agent/providers-manager.js.map +1 -0
  83. package/dist/agent/repo-map.d.ts +6 -0
  84. package/dist/agent/repo-map.d.ts.map +1 -0
  85. package/dist/agent/repo-map.js +221 -0
  86. package/dist/agent/repo-map.js.map +1 -0
  87. package/dist/agent/retry.d.ts +103 -0
  88. package/dist/agent/retry.d.ts.map +1 -0
  89. package/dist/agent/retry.js +276 -0
  90. package/dist/agent/retry.js.map +1 -0
  91. package/dist/agent/search/index.d.ts +61 -0
  92. package/dist/agent/search/index.d.ts.map +1 -0
  93. package/dist/agent/search/index.js +314 -0
  94. package/dist/agent/search/index.js.map +1 -0
  95. package/dist/agent/single-agent.d.ts +76 -0
  96. package/dist/agent/single-agent.d.ts.map +1 -0
  97. package/dist/agent/single-agent.js +697 -0
  98. package/dist/agent/single-agent.js.map +1 -0
  99. package/dist/agent/skills.d.ts +22 -0
  100. package/dist/agent/skills.d.ts.map +1 -0
  101. package/dist/agent/skills.js +139 -0
  102. package/dist/agent/skills.js.map +1 -0
  103. package/dist/agent/stream-glue.d.ts +85 -0
  104. package/dist/agent/stream-glue.d.ts.map +1 -0
  105. package/dist/agent/stream-glue.js +120 -0
  106. package/dist/agent/stream-glue.js.map +1 -0
  107. package/dist/agent/subagent.d.ts +72 -0
  108. package/dist/agent/subagent.d.ts.map +1 -0
  109. package/dist/agent/subagent.js +193 -0
  110. package/dist/agent/subagent.js.map +1 -0
  111. package/dist/agent/telemetry.d.ts +192 -0
  112. package/dist/agent/telemetry.d.ts.map +1 -0
  113. package/dist/agent/telemetry.js +400 -0
  114. package/dist/agent/telemetry.js.map +1 -0
  115. package/dist/agent/tokenizer.d.ts +42 -0
  116. package/dist/agent/tokenizer.d.ts.map +1 -0
  117. package/dist/agent/tokenizer.js +107 -0
  118. package/dist/agent/tokenizer.js.map +1 -0
  119. package/dist/agent/tool-executor.d.ts +289 -0
  120. package/dist/agent/tool-executor.d.ts.map +1 -0
  121. package/dist/agent/tool-executor.js +2519 -0
  122. package/dist/agent/tool-executor.js.map +1 -0
  123. package/dist/agent/web-impl.d.ts +2 -0
  124. package/dist/agent/web-impl.d.ts.map +1 -0
  125. package/dist/agent/web-impl.js +34 -0
  126. package/dist/agent/web-impl.js.map +1 -0
  127. package/dist/agent/web.d.ts +8 -0
  128. package/dist/agent/web.d.ts.map +1 -0
  129. package/dist/agent/web.js +8 -0
  130. package/dist/agent/web.js.map +1 -0
  131. package/dist/agent/worker-agent.d.ts +27 -0
  132. package/dist/agent/worker-agent.d.ts.map +1 -0
  133. package/dist/agent/worker-agent.js +503 -0
  134. package/dist/agent/worker-agent.js.map +1 -0
  135. package/dist/config.d.ts +162 -0
  136. package/dist/config.d.ts.map +1 -0
  137. package/dist/config.js +138 -0
  138. package/dist/config.js.map +1 -0
  139. package/dist/context/fixo-md-watcher.d.ts +42 -0
  140. package/dist/context/fixo-md-watcher.d.ts.map +1 -0
  141. package/dist/context/fixo-md-watcher.js +126 -0
  142. package/dist/context/fixo-md-watcher.js.map +1 -0
  143. package/dist/context/fixo-md.d.ts +50 -0
  144. package/dist/context/fixo-md.d.ts.map +1 -0
  145. package/dist/context/fixo-md.js +118 -0
  146. package/dist/context/fixo-md.js.map +1 -0
  147. package/dist/context/todo.d.ts +65 -0
  148. package/dist/context/todo.d.ts.map +1 -0
  149. package/dist/context/todo.js +194 -0
  150. package/dist/context/todo.js.map +1 -0
  151. package/dist/git/git-manager.d.ts +33 -0
  152. package/dist/git/git-manager.d.ts.map +1 -0
  153. package/dist/git/git-manager.js +293 -0
  154. package/dist/git/git-manager.js.map +1 -0
  155. package/dist/git/git-ops.d.ts +10 -0
  156. package/dist/git/git-ops.d.ts.map +1 -0
  157. package/dist/git/git-ops.js +131 -0
  158. package/dist/git/git-ops.js.map +1 -0
  159. package/dist/index.d.ts +3 -0
  160. package/dist/index.d.ts.map +1 -0
  161. package/dist/index.js +352 -0
  162. package/dist/index.js.map +1 -0
  163. package/dist/indexer.d.ts +30 -0
  164. package/dist/indexer.d.ts.map +1 -0
  165. package/dist/indexer.js +273 -0
  166. package/dist/indexer.js.map +1 -0
  167. package/dist/lsp/lsp-client.d.ts +24 -0
  168. package/dist/lsp/lsp-client.d.ts.map +1 -0
  169. package/dist/lsp/lsp-client.js +205 -0
  170. package/dist/lsp/lsp-client.js.map +1 -0
  171. package/dist/lsp/lsp-manager.d.ts +17 -0
  172. package/dist/lsp/lsp-manager.d.ts.map +1 -0
  173. package/dist/lsp/lsp-manager.js +154 -0
  174. package/dist/lsp/lsp-manager.js.map +1 -0
  175. package/dist/lsp/lsp-pre-save.d.ts +137 -0
  176. package/dist/lsp/lsp-pre-save.d.ts.map +1 -0
  177. package/dist/lsp/lsp-pre-save.js +245 -0
  178. package/dist/lsp/lsp-pre-save.js.map +1 -0
  179. package/dist/lsp/syntax-fallback.d.ts +83 -0
  180. package/dist/lsp/syntax-fallback.d.ts.map +1 -0
  181. package/dist/lsp/syntax-fallback.js +275 -0
  182. package/dist/lsp/syntax-fallback.js.map +1 -0
  183. package/dist/model-outcomes.d.ts +12 -0
  184. package/dist/model-outcomes.d.ts.map +1 -0
  185. package/dist/model-outcomes.js +46 -0
  186. package/dist/model-outcomes.js.map +1 -0
  187. package/dist/planner.d.ts +32 -0
  188. package/dist/planner.d.ts.map +1 -0
  189. package/dist/planner.js +163 -0
  190. package/dist/planner.js.map +1 -0
  191. package/dist/project-memory.d.ts +29 -0
  192. package/dist/project-memory.d.ts.map +1 -0
  193. package/dist/project-memory.js +349 -0
  194. package/dist/project-memory.js.map +1 -0
  195. package/dist/review.d.ts +2 -0
  196. package/dist/review.d.ts.map +1 -0
  197. package/dist/review.js +61 -0
  198. package/dist/review.js.map +1 -0
  199. package/dist/runtime/background-jobs.d.ts +97 -0
  200. package/dist/runtime/background-jobs.d.ts.map +1 -0
  201. package/dist/runtime/background-jobs.js +331 -0
  202. package/dist/runtime/background-jobs.js.map +1 -0
  203. package/dist/runtime/credential-vault.d.ts +124 -0
  204. package/dist/runtime/credential-vault.d.ts.map +1 -0
  205. package/dist/runtime/credential-vault.js +184 -0
  206. package/dist/runtime/credential-vault.js.map +1 -0
  207. package/dist/runtime/loop-trap.d.ts +197 -0
  208. package/dist/runtime/loop-trap.d.ts.map +1 -0
  209. package/dist/runtime/loop-trap.js +420 -0
  210. package/dist/runtime/loop-trap.js.map +1 -0
  211. package/dist/runtime/policy.d.ts +15 -0
  212. package/dist/runtime/policy.d.ts.map +1 -0
  213. package/dist/runtime/policy.js +60 -0
  214. package/dist/runtime/policy.js.map +1 -0
  215. package/dist/runtime/redaction.d.ts +66 -0
  216. package/dist/runtime/redaction.d.ts.map +1 -0
  217. package/dist/runtime/redaction.js +155 -0
  218. package/dist/runtime/redaction.js.map +1 -0
  219. package/dist/runtime/session-snapshots.d.ts +76 -0
  220. package/dist/runtime/session-snapshots.d.ts.map +1 -0
  221. package/dist/runtime/session-snapshots.js +166 -0
  222. package/dist/runtime/session-snapshots.js.map +1 -0
  223. package/dist/runtime/staging.d.ts +205 -0
  224. package/dist/runtime/staging.d.ts.map +1 -0
  225. package/dist/runtime/staging.js +526 -0
  226. package/dist/runtime/staging.js.map +1 -0
  227. package/dist/runtime/task-session.d.ts +95 -0
  228. package/dist/runtime/task-session.d.ts.map +1 -0
  229. package/dist/runtime/task-session.js +263 -0
  230. package/dist/runtime/task-session.js.map +1 -0
  231. package/dist/runtime/worktree.d.ts +55 -0
  232. package/dist/runtime/worktree.d.ts.map +1 -0
  233. package/dist/runtime/worktree.js +175 -0
  234. package/dist/runtime/worktree.js.map +1 -0
  235. package/dist/setup-wizard.d.ts +8 -0
  236. package/dist/setup-wizard.d.ts.map +1 -0
  237. package/dist/setup-wizard.js +73 -0
  238. package/dist/setup-wizard.js.map +1 -0
  239. package/dist/shared/content.d.ts +43 -0
  240. package/dist/shared/content.d.ts.map +1 -0
  241. package/dist/shared/content.js +61 -0
  242. package/dist/shared/content.js.map +1 -0
  243. package/dist/shared/types.d.ts +217 -0
  244. package/dist/shared/types.d.ts.map +1 -0
  245. package/dist/shared/types.js +3 -0
  246. package/dist/shared/types.js.map +1 -0
  247. package/dist/test-runner.d.ts +5 -0
  248. package/dist/test-runner.d.ts.map +1 -0
  249. package/dist/test-runner.js +42 -0
  250. package/dist/test-runner.js.map +1 -0
  251. package/dist/types.d.ts +85 -0
  252. package/dist/types.d.ts.map +1 -0
  253. package/dist/types.js +2 -0
  254. package/dist/types.js.map +1 -0
  255. package/dist/ui/ascii.d.ts +23 -0
  256. package/dist/ui/ascii.d.ts.map +1 -0
  257. package/dist/ui/ascii.js +45 -0
  258. package/dist/ui/ascii.js.map +1 -0
  259. package/dist/ui/colors.d.ts +111 -0
  260. package/dist/ui/colors.d.ts.map +1 -0
  261. package/dist/ui/colors.js +166 -0
  262. package/dist/ui/colors.js.map +1 -0
  263. package/dist/ui/image-attach.d.ts +27 -0
  264. package/dist/ui/image-attach.d.ts.map +1 -0
  265. package/dist/ui/image-attach.js +100 -0
  266. package/dist/ui/image-attach.js.map +1 -0
  267. package/dist/ui/index.d.ts +18 -0
  268. package/dist/ui/index.d.ts.map +1 -0
  269. package/dist/ui/index.js +18 -0
  270. package/dist/ui/index.js.map +1 -0
  271. package/dist/ui/markdown-stream.d.ts +91 -0
  272. package/dist/ui/markdown-stream.d.ts.map +1 -0
  273. package/dist/ui/markdown-stream.js +524 -0
  274. package/dist/ui/markdown-stream.js.map +1 -0
  275. package/dist/ui/plan-renderer.d.ts +36 -0
  276. package/dist/ui/plan-renderer.d.ts.map +1 -0
  277. package/dist/ui/plan-renderer.js +79 -0
  278. package/dist/ui/plan-renderer.js.map +1 -0
  279. package/dist/ui/prompt.d.ts +11 -0
  280. package/dist/ui/prompt.d.ts.map +1 -0
  281. package/dist/ui/prompt.js +1960 -0
  282. package/dist/ui/prompt.js.map +1 -0
  283. package/dist/ui/render-primitives.d.ts +117 -0
  284. package/dist/ui/render-primitives.d.ts.map +1 -0
  285. package/dist/ui/render-primitives.js +322 -0
  286. package/dist/ui/render-primitives.js.map +1 -0
  287. package/dist/ui/render.d.ts +133 -0
  288. package/dist/ui/render.d.ts.map +1 -0
  289. package/dist/ui/render.js +547 -0
  290. package/dist/ui/render.js.map +1 -0
  291. package/dist/ui/session-header.d.ts +30 -0
  292. package/dist/ui/session-header.d.ts.map +1 -0
  293. package/dist/ui/session-header.js +74 -0
  294. package/dist/ui/session-header.js.map +1 -0
  295. package/dist/workspace-guard.d.ts +68 -0
  296. package/dist/workspace-guard.d.ts.map +1 -0
  297. package/dist/workspace-guard.js +168 -0
  298. package/dist/workspace-guard.js.map +1 -0
  299. package/dist/workspace-lock.d.ts +27 -0
  300. package/dist/workspace-lock.d.ts.map +1 -0
  301. package/dist/workspace-lock.js +95 -0
  302. package/dist/workspace-lock.js.map +1 -0
  303. package/package.json +63 -0
@@ -0,0 +1,162 @@
1
+ import type { PolicyProfile } from './runtime/policy.js';
2
+ export declare const DEFAULT_API_URL = "https://api.freellmapi.com/v1";
3
+ /** Stream-resume policy. */
4
+ export type StreamResumePolicy = 'auto' | 'never';
5
+ /**
6
+ * Context-budget policy.
7
+ * - `auto` — proactive enforcement is enabled and, if the enforcer
8
+ * asks for compaction, the agent summarises the oldest
9
+ * turns via an LLM call. (default)
10
+ * - `truncate` — proactive enforcement runs but the agent will NOT
11
+ * trigger LLM-based compaction. If even the enforcer
12
+ * cannot fit the budget, the request is sent anyway
13
+ * (and may 413).
14
+ * - `never` — kill-switch. No enforcement, no compaction. Useful
15
+ * when a user wants exact 1:1 historical behaviour.
16
+ */
17
+ export type ContextBudgetPolicy = 'auto' | 'truncate' | 'never';
18
+ /**
19
+ * Loop-trap configuration. Lives under `preferences.safety.loopTrap`
20
+ * (not `preferences.resilience`) so the safety and resilience
21
+ * concerns remain orthogonal.
22
+ *
23
+ * The defaults match the design decision: warn at 3 consecutive
24
+ * equivalent turns (acts as an intentional psychological disruptor
25
+ * to the LLM via system-prompt injection), hard-abort at 6.
26
+ */
27
+ export interface LoopTrapPolicy {
28
+ /** Number of consecutive equivalent turns that triggers a directive. */
29
+ triggerCount: number;
30
+ /** Number of consecutive equivalent turns that triggers a hard abort. */
31
+ hardAbortCount: number;
32
+ /** Maximum number of tool-result bytes to include in the fingerprint. */
33
+ toolResultTailBytes: number;
34
+ /** Hard cap on in-memory history to bound memory growth. */
35
+ maxHistory: number;
36
+ /** Master kill-switch; when false, the detector is never invoked. */
37
+ enabled: boolean;
38
+ }
39
+ /** Semantic loop-trap configuration. Tracks file-target frequency
40
+ * inside a sliding window so that an LLM which varies its search
41
+ * arguments but keeps hammering the same file still trips. */
42
+ export interface SemanticLoopTrapPolicy {
43
+ enabled: boolean;
44
+ windowSize: number;
45
+ triggerCount: number;
46
+ hardAbortCount: number;
47
+ }
48
+ /** Pre-save gate severity. */
49
+ export type LspPreSaveMode = 'off' | 'warn' | 'block' | 'sandbox-mock';
50
+ /** Safety preferences — Pillar 1, 2, 3 surface. Pillar 4 lives in the
51
+ * credential vault module, not in the user-facing config. */
52
+ export interface SafetyConfig {
53
+ /** Run file writes through the atomic shadow-staging pipeline. */
54
+ atomicStaging: boolean;
55
+ /** Staged writes older than this (ms) are eligible for auto-GC. */
56
+ stagingTtlMs: number;
57
+ /** How to react to LSP diagnostics on a staged write. */
58
+ lspPreSave: LspPreSaveMode;
59
+ /** Loop-trap thresholds. */
60
+ loopTrap: LoopTrapPolicy;
61
+ /** Semantic loop-trap thresholds (Pillar 2 of the refit). */
62
+ semanticLoopTrap: SemanticLoopTrapPolicy;
63
+ /** Maximum bytes a file may be before read_file is gated by the
64
+ * structural pre-scan rule. Defaults to 15 KiB. */
65
+ largeFileGateBytes: number;
66
+ /** Maximum line count before read_file is gated. Defaults to 350. */
67
+ largeFileGateLines: number;
68
+ /**
69
+ * Predictive context-budget gate (Phase 4). When set to a value in
70
+ * (0, 1], `read_file` projects the token cost of the read against
71
+ * the model's input window and defers if projected total exceeds
72
+ * this fraction. Default 0.85. Set to 1.0 to disable.
73
+ */
74
+ predictiveBudgetPct?: number;
75
+ }
76
+ /** Resilience preferences for the new withRetry + chatStreamWithResume paths. */
77
+ export interface ResilienceConfig {
78
+ /** When 'auto', mid-stream cuts are resumed transparently (default). */
79
+ streamResume: StreamResumePolicy;
80
+ /** Max additional resume attempts after a mid-stream cut. */
81
+ maxResumeAttempts: number;
82
+ /** When true, the new withRetry engine is used for non-streaming calls. */
83
+ useWithRetry: boolean;
84
+ /**
85
+ * Context-budget policy. When 'auto' or 'truncate', the
86
+ * {@link ContextBudgetEnforcer} runs before every LLM call and
87
+ * trims the conversation to fit the model's input window. When
88
+ * 'auto', the agent may also call `compact()` to summarise the
89
+ * oldest turns.
90
+ */
91
+ contextBudget: ContextBudgetPolicy;
92
+ /**
93
+ * Fraction of the model context window to use as the hard cap when
94
+ * enforcing the budget. 0.8 leaves 20% headroom for the response.
95
+ */
96
+ contextBudgetRatio: number;
97
+ }
98
+ /**
99
+ * Global configuration for the FixO CLI.
100
+ * Persisted at `~/.fixocli/config.json`.
101
+ */
102
+ export interface FreeLLMConfig {
103
+ freellmapi_api_key?: string;
104
+ apiUrl?: string;
105
+ defaultModel: string;
106
+ preferences: {
107
+ autoCommit: boolean;
108
+ streaming: boolean;
109
+ theme: 'dark' | 'light';
110
+ maxRetries: number;
111
+ policy: PolicyProfile;
112
+ telemetry: boolean;
113
+ /**
114
+ * Local NDJSON sink. Defaults to true. When false, no events are
115
+ * written to `~/.fixocli/telemetry.jsonl` — useful for users who
116
+ * want to keep their disk private but still want to send events
117
+ * to the remote sink.
118
+ */
119
+ telemetryLocal: boolean;
120
+ /**
121
+ * Remote HTTP sink (legacy). Defaults to false. When true, the
122
+ * legacy `logTelemetry` HTTP poster is re-enabled alongside the
123
+ * local sink. The free FixO API server collects anonymous usage
124
+ * stats so we can prioritise provider fixes.
125
+ */
126
+ telemetryRemote: boolean;
127
+ resilience: ResilienceConfig;
128
+ /**
129
+ * Safety preferences — orthogonal to `resilience`.
130
+ *
131
+ * - `resilience` keeps the system alive through network noise.
132
+ * - `safety` keeps the system from corrupting the user's
133
+ * workspace or leaking secrets.
134
+ *
135
+ * Pillar 1 (loop-trap), Pillar 2 (atomic staging), and Pillar 3
136
+ * (LSP pre-save) are wired up here. Pillar 4 (credential vault)
137
+ * is a programmatic-only surface and is not user-configurable.
138
+ */
139
+ safety: SafetyConfig;
140
+ };
141
+ _firstRunComplete: boolean;
142
+ }
143
+ /** Returns the FixO CLI config directory (`~/.fixocli/`). */
144
+ export declare function getConfigDir(): string;
145
+ /** Returns the full path to the config file (`~/.fixocli/config.json`). */
146
+ export declare function getConfigPath(): string;
147
+ /** Returns the full path to the prompt history log (`~/.fixocli/history.jsonl`). */
148
+ export declare function getHistoryPath(): string;
149
+ /** Returns a complete default configuration object. */
150
+ export declare function getDefaultConfig(): FreeLLMConfig;
151
+ /**
152
+ * Reads `~/.fixocli/config.json` and returns the parsed config.
153
+ * If the file doesn't exist or is unreadable, a default config is returned
154
+ * instead — the caller can then decide whether to run the setup wizard.
155
+ */
156
+ export declare function loadConfig(): FreeLLMConfig;
157
+ /**
158
+ * Persists the given config to `~/.fixocli/config.json`.
159
+ * Creates the config directory if it doesn't already exist.
160
+ */
161
+ export declare function saveConfig(config: FreeLLMConfig): void;
162
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,138 @@
1
+ import os from 'node:os';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ export const DEFAULT_API_URL = 'https://api.freellmapi.com/v1';
5
+ // ---------------------------------------------------------------------------
6
+ // Path helpers
7
+ // ---------------------------------------------------------------------------
8
+ /** Returns the FixO CLI config directory (`~/.fixocli/`). */
9
+ export function getConfigDir() {
10
+ return path.join(os.homedir(), '.fixocli');
11
+ }
12
+ /** Returns the full path to the config file (`~/.fixocli/config.json`). */
13
+ export function getConfigPath() {
14
+ return path.join(getConfigDir(), 'config.json');
15
+ }
16
+ /** Returns the full path to the prompt history log (`~/.fixocli/history.jsonl`). */
17
+ export function getHistoryPath() {
18
+ return path.join(getConfigDir(), 'history.jsonl');
19
+ }
20
+ // ---------------------------------------------------------------------------
21
+ // Config I/O
22
+ // ---------------------------------------------------------------------------
23
+ /** Returns a complete default configuration object. */
24
+ export function getDefaultConfig() {
25
+ return {
26
+ defaultModel: 'auto',
27
+ preferences: {
28
+ autoCommit: false,
29
+ streaming: true,
30
+ theme: 'dark',
31
+ maxRetries: 3,
32
+ policy: 'shell-confirm',
33
+ telemetry: true,
34
+ telemetryLocal: true,
35
+ telemetryRemote: false,
36
+ resilience: {
37
+ streamResume: 'auto',
38
+ maxResumeAttempts: 3,
39
+ useWithRetry: true,
40
+ contextBudget: 'auto',
41
+ contextBudgetRatio: 0.8,
42
+ },
43
+ safety: {
44
+ atomicStaging: true,
45
+ stagingTtlMs: 24 * 60 * 60 * 1000,
46
+ lspPreSave: 'warn',
47
+ loopTrap: {
48
+ triggerCount: 3,
49
+ hardAbortCount: 6,
50
+ toolResultTailBytes: 1024,
51
+ maxHistory: 64,
52
+ enabled: true,
53
+ },
54
+ semanticLoopTrap: {
55
+ enabled: true,
56
+ windowSize: 5,
57
+ triggerCount: 3,
58
+ hardAbortCount: 6,
59
+ },
60
+ largeFileGateBytes: 15 * 1024,
61
+ largeFileGateLines: 350,
62
+ },
63
+ },
64
+ _firstRunComplete: false,
65
+ };
66
+ }
67
+ /**
68
+ * Reads `~/.fixocli/config.json` and returns the parsed config.
69
+ * If the file doesn't exist or is unreadable, a default config is returned
70
+ * instead — the caller can then decide whether to run the setup wizard.
71
+ */
72
+ export function loadConfig() {
73
+ const configPath = getConfigPath();
74
+ try {
75
+ const raw = fs.readFileSync(configPath, 'utf-8');
76
+ const parsed = JSON.parse(raw);
77
+ const defaults = getDefaultConfig();
78
+ // Merge top-level keys while keeping nested `preferences` safe.
79
+ // `resilience` and `safety` are deep-merged so old configs that
80
+ // predate a new field still pick up the new default.
81
+ const parsedPreferences = parsed.preferences ?? {};
82
+ const parsedResilience = parsedPreferences.resilience ?? {};
83
+ const parsedSafety = parsedPreferences.safety ?? {};
84
+ const parsedLoopTrap = parsedSafety.loopTrap ?? {};
85
+ const parsedSemanticLoopTrap = parsedSafety
86
+ .semanticLoopTrap ?? {};
87
+ return {
88
+ ...defaults,
89
+ ...parsed,
90
+ preferences: {
91
+ ...defaults.preferences,
92
+ ...parsedPreferences,
93
+ resilience: {
94
+ ...defaults.preferences.resilience,
95
+ ...parsedResilience,
96
+ },
97
+ safety: {
98
+ ...defaults.preferences.safety,
99
+ ...parsedSafety,
100
+ loopTrap: {
101
+ ...defaults.preferences.safety.loopTrap,
102
+ ...parsedLoopTrap,
103
+ },
104
+ semanticLoopTrap: {
105
+ ...defaults.preferences.safety.semanticLoopTrap,
106
+ ...parsedSemanticLoopTrap,
107
+ },
108
+ },
109
+ },
110
+ };
111
+ }
112
+ catch {
113
+ // File missing, corrupt, or otherwise unreadable — use defaults.
114
+ return getDefaultConfig();
115
+ }
116
+ }
117
+ /**
118
+ * Persists the given config to `~/.fixocli/config.json`.
119
+ * Creates the config directory if it doesn't already exist.
120
+ */
121
+ export function saveConfig(config) {
122
+ const dir = getConfigDir();
123
+ if (!fs.existsSync(dir)) {
124
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
125
+ }
126
+ const configPath = getConfigPath();
127
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', {
128
+ encoding: 'utf-8',
129
+ mode: 0o600,
130
+ });
131
+ try {
132
+ fs.chmodSync(configPath, 0o600);
133
+ }
134
+ catch {
135
+ // Ignore OS limitations
136
+ }
137
+ }
138
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { type FixoMdLoadResult } from './fixo-md.js';
2
+ export type FixoMdWatchResult = {
3
+ kind: 'unchanged';
4
+ } | {
5
+ kind: 'created';
6
+ block: string;
7
+ result: FixoMdLoadResult;
8
+ } | {
9
+ kind: 'updated';
10
+ block: string;
11
+ result: FixoMdLoadResult;
12
+ } | {
13
+ kind: 'deleted';
14
+ previousPath: string;
15
+ };
16
+ export declare class FixoMdWatcher {
17
+ private readonly cwd;
18
+ /** The fingerprint we last surfaced (or captured at construction). */
19
+ private lastSeen;
20
+ constructor(cwd: string);
21
+ /**
22
+ * Compare the current on-disk state to the last surfaced
23
+ * fingerprint and return the delta. Internal state advances on
24
+ * non-`unchanged` results so a single change is announced once.
25
+ */
26
+ check(): FixoMdWatchResult;
27
+ /**
28
+ * Render a watch result into an injectable directive, or null
29
+ * when nothing should be surfaced. Matches the directive shape
30
+ * used by the safety alert and background-jobs awareness so the
31
+ * agent model already knows how to parse it.
32
+ */
33
+ formatDirective(watch: FixoMdWatchResult): string | null;
34
+ }
35
+ /**
36
+ * Convenience wrapper for tests + callers that don't need the
37
+ * persistent watcher state.
38
+ *
39
+ * @internal
40
+ */
41
+ export declare function readFixoMd(cwd: string): FixoMdLoadResult;
42
+ //# sourceMappingURL=fixo-md-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixo-md-watcher.d.ts","sourceRoot":"","sources":["../../src/context/fixo-md-watcher.ts"],"names":[],"mappings":"AAoBA,OAAO,EAIL,KAAK,gBAAgB,EAEtB,MAAM,cAAc,CAAC;AActB,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAgC9C,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAyB;gBAE7B,GAAG,EAAE,MAAM;IASvB;;;;OAIG;IACH,KAAK,IAAI,iBAAiB;IAkC1B;;;;;OAKG;IACH,eAAe,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI;CAqBzD;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAExD"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * fixo-md-watcher.ts — Phase 4: per-turn re-injection of FIXO.md
3
+ * changes.
4
+ *
5
+ * The system prompt bakes the FIXO.md content in at run start, so
6
+ * a long-running agent loop normally never sees later edits. This
7
+ * watcher closes that gap by stat-checking the active FIXO.md
8
+ * before each `chat()` call and emitting a delta when the file is
9
+ * created, updated, or removed mid-run.
10
+ *
11
+ * Why a delta model instead of always re-injecting? The full
12
+ * contents already live in the system prompt; re-sending them on
13
+ * every turn would double the token cost without any new signal.
14
+ * The watcher injects only when the on-disk file no longer matches
15
+ * what the prompt currently reflects.
16
+ *
17
+ * Pure read: stats, optional read, and an in-memory cursor. No
18
+ * `fs.watch`, no subprocesses, no globals.
19
+ */
20
+ import fs from 'node:fs';
21
+ import { buildProjectInstructionsBlock, findFixoMdPath, loadProjectInstructions, } from './fixo-md.js';
22
+ /* ──────────────────────── helpers ──────────────────────── */
23
+ function fingerprint(cwd) {
24
+ const found = findFixoMdPath(cwd);
25
+ if (found.path === null)
26
+ return null;
27
+ let stat;
28
+ try {
29
+ stat = fs.statSync(found.path);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ return {
35
+ path: found.path,
36
+ source: found.source,
37
+ mtimeMs: stat.mtimeMs,
38
+ bytes: stat.size,
39
+ };
40
+ }
41
+ function fingerprintsEqual(a, b) {
42
+ return (a.path === b.path &&
43
+ a.source === b.source &&
44
+ a.mtimeMs === b.mtimeMs &&
45
+ a.bytes === b.bytes);
46
+ }
47
+ /* ──────────────────────── watcher ──────────────────────── */
48
+ export class FixoMdWatcher {
49
+ cwd;
50
+ /** The fingerprint we last surfaced (or captured at construction). */
51
+ lastSeen;
52
+ constructor(cwd) {
53
+ this.cwd = cwd;
54
+ // Snapshot the baseline so the very first `check()` after the
55
+ // agent loop starts returns 'unchanged' for files that were
56
+ // already present. Those files are already baked into the
57
+ // system prompt by `buildSystemPrompt`.
58
+ this.lastSeen = fingerprint(cwd);
59
+ }
60
+ /**
61
+ * Compare the current on-disk state to the last surfaced
62
+ * fingerprint and return the delta. Internal state advances on
63
+ * non-`unchanged` results so a single change is announced once.
64
+ */
65
+ check() {
66
+ const current = fingerprint(this.cwd);
67
+ const previous = this.lastSeen;
68
+ // Both null → nothing on disk now or before. Quiet path.
69
+ if (current === null && previous === null) {
70
+ return { kind: 'unchanged' };
71
+ }
72
+ // File appeared (or moved into the chain) mid-run.
73
+ if (current !== null && previous === null) {
74
+ const { block, result } = buildProjectInstructionsBlock(this.cwd);
75
+ this.lastSeen = current;
76
+ return { kind: 'created', block, result };
77
+ }
78
+ // File disappeared mid-run.
79
+ if (current === null && previous !== null) {
80
+ const previousPath = previous.path;
81
+ this.lastSeen = null;
82
+ return { kind: 'deleted', previousPath };
83
+ }
84
+ // Both non-null. If fingerprint matches we're done.
85
+ if (current !== null && previous !== null && fingerprintsEqual(current, previous)) {
86
+ return { kind: 'unchanged' };
87
+ }
88
+ // Otherwise: content (or path / source) changed.
89
+ const { block, result } = buildProjectInstructionsBlock(this.cwd);
90
+ this.lastSeen = current;
91
+ return { kind: 'updated', block, result };
92
+ }
93
+ /**
94
+ * Render a watch result into an injectable directive, or null
95
+ * when nothing should be surfaced. Matches the directive shape
96
+ * used by the safety alert and background-jobs awareness so the
97
+ * agent model already knows how to parse it.
98
+ */
99
+ formatDirective(watch) {
100
+ if (watch.kind === 'unchanged')
101
+ return null;
102
+ if (watch.kind === 'deleted') {
103
+ return (`[Project Instructions]\n` +
104
+ `The FIXO.md previously loaded from \`${watch.previousPath}\` was ` +
105
+ `removed mid-run. Stop relying on its rules until a new one appears.`);
106
+ }
107
+ // Created or updated — surface the block verbatim under a
108
+ // labelled header so the LLM sees this is a refresh, not a
109
+ // duplicate of what's in the system prompt.
110
+ const verb = watch.kind === 'created' ? 'now available' : 'updated mid-run';
111
+ const sourceLine = watch.result.path ?? '(unknown path)';
112
+ return (`[Project Instructions]\n` +
113
+ `FIXO.md is ${verb}. Source: ${sourceLine}.\n` +
114
+ `Latest content:\n${watch.block.trim()}`);
115
+ }
116
+ }
117
+ /**
118
+ * Convenience wrapper for tests + callers that don't need the
119
+ * persistent watcher state.
120
+ *
121
+ * @internal
122
+ */
123
+ export function readFixoMd(cwd) {
124
+ return loadProjectInstructions(cwd);
125
+ }
126
+ //# sourceMappingURL=fixo-md-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixo-md-watcher.js","sourceRoot":"","sources":["../../src/context/fixo-md-watcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ export type FixoMdSource = 'project-fixo' | 'project-cwd' | 'global' | 'none';
2
+ export interface FixoMdLoadResult {
3
+ /** The path that won, or null when no FIXO.md was found. */
4
+ readonly source: FixoMdSource;
5
+ /** Absolute path of the file that was loaded, when source !== 'none'. */
6
+ readonly path: string | null;
7
+ /** Raw file content. Empty string when source === 'none'. */
8
+ readonly content: string;
9
+ /** Byte size of the loaded file. 0 when source === 'none'. */
10
+ readonly bytes: number;
11
+ }
12
+ /**
13
+ * Search the lookup chain and return the first match. Does not
14
+ * read the file — the caller decides whether to read it
15
+ * synchronously or stream it.
16
+ */
17
+ export declare function findFixoMdPath(cwd: string): {
18
+ source: FixoMdSource;
19
+ path: string | null;
20
+ };
21
+ /**
22
+ * Read the FIXO.md content from the first match in the lookup
23
+ * chain. Returns `{ source: 'none', ... }` when no file exists.
24
+ *
25
+ * Best-effort sandboxing: each candidate is read through the
26
+ * platform's `fs.readFileSync` and capped at 1 MiB so a runaway
27
+ * file cannot OOM the agent.
28
+ */
29
+ export declare function loadProjectInstructions(cwd: string): FixoMdLoadResult;
30
+ /**
31
+ * Build the `<project-instructions>` block that is appended to
32
+ * the agent's system prompt. The block is wrapped in clearly
33
+ * labelled fences so the LLM cannot confuse it with the
34
+ * platform-managed system prompt.
35
+ *
36
+ * The block is empty when no FIXO.md was found; callers can
37
+ * detect this by inspecting `source === 'none'`.
38
+ */
39
+ export declare function buildProjectInstructionsBlock(cwd: string): {
40
+ block: string;
41
+ result: FixoMdLoadResult;
42
+ };
43
+ /**
44
+ * Best-effort telemetry emission. Imported lazily so a missing
45
+ * `telemetry.js` (e.g. during early bootstrap) never blocks
46
+ * the loader. Errors are swallowed — telemetry must never
47
+ * break a tool call.
48
+ */
49
+ export declare function recordFixoMdLoad(result: FixoMdLoadResult): Promise<void>;
50
+ //# sourceMappingURL=fixo-md.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixo-md.d.ts","sourceRoot":"","sources":["../../src/context/fixo-md.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,YAAY,GAAG,cAAc,GAAG,aAAa,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAID;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAiBzF;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAkBrE;AAID;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,MAAM,GAAG;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAeA;AAID;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY9E"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * FIXO.md loader — Phase 2 deliverable.
3
+ *
4
+ * Convention: a single, optional `FIXO.md` (or `.fixo/FIXO.md`)
5
+ * provides project-local instructions for the agent. The
6
+ * loader reads it from a deterministic lookup chain and
7
+ * surfaces the loaded content to the system-prompt builder.
8
+ *
9
+ * Lookup order (first hit wins):
10
+ * 1. `<cwd>/.fixo/FIXO.md`
11
+ * 2. `<cwd>/FIXO.md`
12
+ * 3. `~/.fixocli/FIXO.md`
13
+ *
14
+ * The loader is *additive* — `AgentContext.systemPromptOverride`
15
+ * still wins if the caller already supplied one. The FIXO.md
16
+ * content is appended under a clearly-labelled
17
+ * `<project-instructions>` block.
18
+ */
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { getConfigDir } from '../config.js';
22
+ /* ──────────────────────── lookup chain ──────────────────────── */
23
+ /**
24
+ * Search the lookup chain and return the first match. Does not
25
+ * read the file — the caller decides whether to read it
26
+ * synchronously or stream it.
27
+ */
28
+ export function findFixoMdPath(cwd) {
29
+ const candidates = [
30
+ { source: 'project-fixo', p: path.join(cwd, '.fixo', 'FIXO.md') },
31
+ { source: 'project-cwd', p: path.join(cwd, 'FIXO.md') },
32
+ { source: 'global', p: path.join(getConfigDir(), 'FIXO.md') },
33
+ ];
34
+ for (const c of candidates) {
35
+ try {
36
+ const stat = fs.statSync(c.p);
37
+ if (stat.isFile() && stat.size > 0) {
38
+ return { source: c.source, path: c.p };
39
+ }
40
+ }
41
+ catch {
42
+ // ENOENT or permission denied — try the next candidate.
43
+ }
44
+ }
45
+ return { source: 'none', path: null };
46
+ }
47
+ /**
48
+ * Read the FIXO.md content from the first match in the lookup
49
+ * chain. Returns `{ source: 'none', ... }` when no file exists.
50
+ *
51
+ * Best-effort sandboxing: each candidate is read through the
52
+ * platform's `fs.readFileSync` and capped at 1 MiB so a runaway
53
+ * file cannot OOM the agent.
54
+ */
55
+ export function loadProjectInstructions(cwd) {
56
+ const found = findFixoMdPath(cwd);
57
+ if (found.path === null) {
58
+ return { source: 'none', path: null, content: '', bytes: 0 };
59
+ }
60
+ let content;
61
+ try {
62
+ content = fs.readFileSync(found.path, 'utf-8');
63
+ }
64
+ catch {
65
+ return { source: 'none', path: found.path, content: '', bytes: 0 };
66
+ }
67
+ const bytes = Buffer.byteLength(content, 'utf-8');
68
+ return {
69
+ source: found.source,
70
+ path: found.path,
71
+ content,
72
+ bytes,
73
+ };
74
+ }
75
+ /* ──────────────────────── prompt block ──────────────────────── */
76
+ /**
77
+ * Build the `<project-instructions>` block that is appended to
78
+ * the agent's system prompt. The block is wrapped in clearly
79
+ * labelled fences so the LLM cannot confuse it with the
80
+ * platform-managed system prompt.
81
+ *
82
+ * The block is empty when no FIXO.md was found; callers can
83
+ * detect this by inspecting `source === 'none'`.
84
+ */
85
+ export function buildProjectInstructionsBlock(cwd) {
86
+ const result = loadProjectInstructions(cwd);
87
+ if (result.source === 'none' || result.content.length === 0) {
88
+ return { block: '', result };
89
+ }
90
+ const header = result.source === 'global'
91
+ ? 'Global instructions (from ~/.fixocli/FIXO.md)'
92
+ : `Project instructions (from ${result.path ?? 'FIXO.md'})`;
93
+ const block = `\n\n<project-instructions source="${result.source}">\n` +
94
+ `## ${header}\n\n` +
95
+ `${result.content.trim()}\n` +
96
+ `</project-instructions>\n`;
97
+ return { block, result };
98
+ }
99
+ /* ──────────────────────── telemetry ──────────────────────── */
100
+ /**
101
+ * Best-effort telemetry emission. Imported lazily so a missing
102
+ * `telemetry.js` (e.g. during early bootstrap) never blocks
103
+ * the loader. Errors are swallowed — telemetry must never
104
+ * break a tool call.
105
+ */
106
+ export async function recordFixoMdLoad(result) {
107
+ try {
108
+ const { recordTelemetry, telemetry } = await import('../agent/telemetry.js');
109
+ recordTelemetry(telemetry.fixoMdLoaded({
110
+ source: result.source,
111
+ bytes: result.bytes,
112
+ }));
113
+ }
114
+ catch {
115
+ // ignore
116
+ }
117
+ }
118
+ //# sourceMappingURL=fixo-md.js.map