@golba98/codexa 1.0.1 → 1.0.3

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 (234) hide show
  1. package/README.md +391 -94
  2. package/package.json +14 -8
  3. package/src/app.tsx +196 -63
  4. package/src/commands/handler.ts +9 -4
  5. package/src/config/buildInfo.ts +3 -0
  6. package/src/config/launchArgs.ts +10 -0
  7. package/src/config/layeredConfig.ts +0 -0
  8. package/src/config/persistence.ts +51 -0
  9. package/src/config/runtimeConfig.ts +4 -0
  10. package/src/config/settings.ts +6 -13
  11. package/src/config/toml-serialize.ts +0 -0
  12. package/src/config/trustStore.ts +0 -0
  13. package/src/config/updateCheckCache.ts +63 -0
  14. package/src/core/attachments.ts +0 -0
  15. package/src/core/auth/codexAuth.ts +0 -0
  16. package/src/core/channel.ts +23 -0
  17. package/src/core/cleanupFastFail.ts +0 -0
  18. package/src/core/clipboard.ts +0 -0
  19. package/src/core/codex.ts +0 -0
  20. package/src/core/codexExecArgs.ts +0 -0
  21. package/src/core/codexLaunch.ts +0 -0
  22. package/src/core/codexPrompt.ts +0 -0
  23. package/src/core/executables/claudeExecutable.ts +0 -0
  24. package/src/core/executables/codexExecutable.ts +1 -0
  25. package/src/core/executables/executableResolver.ts +61 -43
  26. package/src/core/executables/geminiExecutable.ts +0 -0
  27. package/src/core/githubDiagnostics.ts +0 -0
  28. package/src/core/hollowResponseFormat.ts +0 -0
  29. package/src/core/inputDebug.ts +0 -0
  30. package/src/core/launchContext.ts +33 -33
  31. package/src/core/models/codexCapabilities.ts +0 -0
  32. package/src/core/models/codexModelCapabilities.ts +0 -0
  33. package/src/core/models/modelSpecs.ts +0 -0
  34. package/src/core/perf/profiler.ts +0 -0
  35. package/src/core/perf/renderDebug.ts +0 -0
  36. package/src/core/planStorage.ts +0 -0
  37. package/src/core/process/CommandRunner.ts +0 -0
  38. package/src/core/process/processValidation.ts +9 -5
  39. package/src/core/projectInstructions.ts +0 -0
  40. package/src/core/providerLauncher/launcher.ts +59 -42
  41. package/src/core/providerLauncher/registry.ts +0 -0
  42. package/src/core/providerLauncher/types.ts +0 -0
  43. package/src/core/providerLauncher/workspaceConfig.ts +0 -0
  44. package/src/core/providerRuntime/anthropic.ts +0 -0
  45. package/src/core/providerRuntime/capabilityProfile.ts +0 -0
  46. package/src/core/providerRuntime/claudeCodeDiscovery.ts +0 -0
  47. package/src/core/providerRuntime/contextMetadata.ts +0 -0
  48. package/src/core/providerRuntime/gemini.ts +0 -0
  49. package/src/core/providerRuntime/lmstudio.ts +0 -0
  50. package/src/core/providerRuntime/local.ts +0 -0
  51. package/src/core/providerRuntime/models.ts +0 -0
  52. package/src/core/providerRuntime/reasoning.ts +0 -0
  53. package/src/core/providerRuntime/registry.ts +0 -0
  54. package/src/core/providerRuntime/types.ts +0 -0
  55. package/src/core/providers/codexJsonStream.ts +0 -0
  56. package/src/core/providers/codexSubprocess.ts +0 -0
  57. package/src/core/providers/codexTranscript.ts +0 -0
  58. package/src/core/providers/openaiNative.ts +0 -0
  59. package/src/core/providers/registry.ts +0 -0
  60. package/src/core/providers/types.ts +0 -0
  61. package/src/core/terminal/startupClear.ts +20 -0
  62. package/src/core/terminal/terminalCapabilities.ts +0 -0
  63. package/src/core/terminal/terminalControl.ts +0 -0
  64. package/src/core/terminal/terminalSanitize.ts +0 -0
  65. package/src/core/terminal/terminalSelection.ts +0 -0
  66. package/src/core/terminal/terminalTitle.ts +0 -0
  67. package/src/core/updateCheck.ts +188 -0
  68. package/src/core/workspaceActivity.ts +0 -0
  69. package/src/core/workspaceGuard.ts +0 -0
  70. package/src/core/workspaceRoot.ts +0 -0
  71. package/src/exec.ts +0 -0
  72. package/src/headless/execArgs.ts +1 -0
  73. package/src/headless/execRunner.ts +0 -0
  74. package/src/index.tsx +13 -9
  75. package/src/session/appSession.ts +0 -0
  76. package/src/session/chatLifecycle.ts +0 -0
  77. package/src/session/liveRenderScheduler.ts +0 -0
  78. package/src/session/planFlow.ts +0 -0
  79. package/src/session/planTranscript.ts +0 -0
  80. package/src/session/promptRunSchedule.ts +0 -0
  81. package/src/session/types.ts +2 -1
  82. package/src/test/runtimeTestUtils.ts +0 -0
  83. package/src/types/react-dom.d.ts +0 -0
  84. package/src/ui/ActionRequiredBlock.tsx +5 -5
  85. package/src/ui/ActivityBars.tsx +3 -3
  86. package/src/ui/ActivityIndicator.tsx +6 -6
  87. package/src/ui/AgentBlock.tsx +6 -6
  88. package/src/ui/AnimatedStatusText.tsx +1 -1
  89. package/src/ui/AppShell.tsx +13 -5
  90. package/src/ui/AttachmentImportPanel.tsx +8 -8
  91. package/src/ui/AuthPanel.tsx +20 -20
  92. package/src/ui/BackendPicker.tsx +0 -0
  93. package/src/ui/BottomComposer.tsx +117 -78
  94. package/src/ui/DashCard.tsx +3 -3
  95. package/src/ui/Markdown.tsx +17 -17
  96. package/src/ui/ModePicker.tsx +0 -0
  97. package/src/ui/ModelPicker.tsx +0 -0
  98. package/src/ui/ModelPickerScreen.tsx +10 -10
  99. package/src/ui/ModelReasoningPicker.tsx +14 -14
  100. package/src/ui/Panel.tsx +3 -3
  101. package/src/ui/PermissionsPanel.tsx +0 -0
  102. package/src/ui/PlanActionPicker.tsx +6 -6
  103. package/src/ui/PlanReviewPanel.tsx +8 -8
  104. package/src/ui/ProviderPicker.tsx +18 -18
  105. package/src/ui/ReasoningPicker.tsx +0 -0
  106. package/src/ui/RunFooter.tsx +3 -3
  107. package/src/ui/SelectionPanel.tsx +4 -4
  108. package/src/ui/SettingsPanel.tsx +8 -8
  109. package/src/ui/Spinner.tsx +1 -1
  110. package/src/ui/StaticIntroItem.tsx +0 -0
  111. package/src/ui/StaticTranscriptItem.tsx +0 -0
  112. package/src/ui/TextEntryPanel.tsx +10 -10
  113. package/src/ui/ThemePicker.tsx +0 -0
  114. package/src/ui/ThinkingBlock.tsx +8 -8
  115. package/src/ui/Timeline.tsx +21 -17
  116. package/src/ui/TopHeader.tsx +421 -257
  117. package/src/ui/TurnGroup.tsx +31 -31
  118. package/src/ui/UpdateAvailableCard.tsx +41 -0
  119. package/src/ui/UpdatePromptPanel.tsx +203 -0
  120. package/src/ui/busyStatusAnimation.ts +0 -0
  121. package/src/ui/commandNormalize.ts +0 -0
  122. package/src/ui/diffRenderer.ts +0 -0
  123. package/src/ui/focus.ts +3 -0
  124. package/src/ui/inputBuffer.ts +0 -0
  125. package/src/ui/layout.ts +1 -1
  126. package/src/ui/logoVariants.ts +107 -0
  127. package/src/ui/modeDisplay.ts +12 -12
  128. package/src/ui/outputPipeline.ts +0 -0
  129. package/src/ui/progressEntries.ts +0 -0
  130. package/src/ui/runActivityView.ts +0 -0
  131. package/src/ui/runtimeDisplay.ts +100 -0
  132. package/src/ui/slashCommands.ts +0 -0
  133. package/src/ui/terminalAnswerFormat.ts +0 -0
  134. package/src/ui/textLayout.ts +0 -0
  135. package/src/ui/theme.tsx +274 -395
  136. package/src/ui/themeFlow.ts +0 -0
  137. package/src/ui/timelineMeasure.ts +29 -19
  138. package/src/ui/useThrottledValue.ts +0 -0
  139. package/scripts/audit-codexa-capabilities.mjs +0 -466
  140. package/scripts/smoke-terminal-bench.mjs +0 -35
  141. package/src/appRenderStability.test.ts +0 -131
  142. package/src/commands/handler.test.ts +0 -643
  143. package/src/config/launchArgs.test.ts +0 -158
  144. package/src/config/layeredConfig.test.ts +0 -143
  145. package/src/config/persistence.test.ts +0 -110
  146. package/src/config/runtimeConfig.test.ts +0 -218
  147. package/src/config/settings.test.ts +0 -155
  148. package/src/config/trustStore.test.ts +0 -29
  149. package/src/core/attachments.test.ts +0 -155
  150. package/src/core/auth/codexAuth.test.ts +0 -68
  151. package/src/core/cleanupFastFail.test.ts +0 -76
  152. package/src/core/codexExecArgs.test.ts +0 -195
  153. package/src/core/codexLaunch.test.ts +0 -205
  154. package/src/core/codexPrompt.test.ts +0 -252
  155. package/src/core/executables/codexExecutable.test.ts +0 -212
  156. package/src/core/executables/executableResolver.test.ts +0 -129
  157. package/src/core/executables/geminiExecutable.test.ts +0 -116
  158. package/src/core/executables/pathSanityScan.test.ts +0 -47
  159. package/src/core/githubDiagnostics.test.ts +0 -92
  160. package/src/core/hollowResponseFormat.test.ts +0 -58
  161. package/src/core/launchContext.test.ts +0 -157
  162. package/src/core/models/codexCapabilities.test.ts +0 -45
  163. package/src/core/models/codexModelCapabilities.test.ts +0 -246
  164. package/src/core/models/modelSpecs.test.ts +0 -283
  165. package/src/core/perf/renderDebug.test.ts +0 -230
  166. package/src/core/planStorage.test.ts +0 -143
  167. package/src/core/process/CommandRunner.test.ts +0 -105
  168. package/src/core/projectInstructions.test.ts +0 -50
  169. package/src/core/providerLauncher/launcher.test.ts +0 -238
  170. package/src/core/providerLauncher/registry.test.ts +0 -324
  171. package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
  172. package/src/core/providerRuntime/anthropic.test.ts +0 -1120
  173. package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
  174. package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
  175. package/src/core/providerRuntime/gemini.test.ts +0 -437
  176. package/src/core/providerRuntime/lmstudio.test.ts +0 -168
  177. package/src/core/providerRuntime/local.test.ts +0 -787
  178. package/src/core/providerRuntime/registry.test.ts +0 -233
  179. package/src/core/providers/codexJsonStream.test.ts +0 -148
  180. package/src/core/providers/codexSubprocess.test.ts +0 -68
  181. package/src/core/providers/codexTranscript.test.ts +0 -284
  182. package/src/core/terminal/terminalCapabilities.test.ts +0 -93
  183. package/src/core/terminal/terminalControl.test.ts +0 -75
  184. package/src/core/terminal/terminalSanitize.test.ts +0 -22
  185. package/src/core/terminal/terminalSelection.test.ts +0 -42
  186. package/src/core/terminal/terminalTitle.test.ts +0 -328
  187. package/src/core/workspaceActivity.test.ts +0 -163
  188. package/src/core/workspaceGuard.test.ts +0 -151
  189. package/src/core/workspaceRoot.test.ts +0 -23
  190. package/src/exec.test.ts +0 -13
  191. package/src/headless/execArgs.test.ts +0 -147
  192. package/src/headless/execRunner.test.ts +0 -434
  193. package/src/index.test.tsx +0 -618
  194. package/src/session/appSession.test.ts +0 -897
  195. package/src/session/chatLifecycle.test.ts +0 -64
  196. package/src/session/liveRenderScheduler.test.ts +0 -201
  197. package/src/session/planFlow.test.ts +0 -103
  198. package/src/session/planTranscript.test.ts +0 -65
  199. package/src/session/promptRunSchedule.test.ts +0 -36
  200. package/src/ui/ActivityIndicator.test.tsx +0 -58
  201. package/src/ui/AgentBlock.test.ts +0 -6
  202. package/src/ui/AnimatedStatusText.test.ts +0 -16
  203. package/src/ui/AppShell.test.tsx +0 -1739
  204. package/src/ui/AttachmentImportPanel.test.tsx +0 -204
  205. package/src/ui/BottomComposer.test.ts +0 -674
  206. package/src/ui/CodexLogo.tsx +0 -55
  207. package/src/ui/Markdown.test.ts +0 -157
  208. package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
  209. package/src/ui/ModelPickerScreen.test.tsx +0 -99
  210. package/src/ui/ModelPickerState.test.tsx +0 -151
  211. package/src/ui/ModelReasoningPicker.test.tsx +0 -447
  212. package/src/ui/PlanReviewPanel.test.tsx +0 -267
  213. package/src/ui/PromptCardBorder.test.tsx +0 -161
  214. package/src/ui/ProviderPicker.test.tsx +0 -289
  215. package/src/ui/ProviderShortcut.test.tsx +0 -143
  216. package/src/ui/SettingsPanel.test.tsx +0 -233
  217. package/src/ui/Timeline.test.ts +0 -2067
  218. package/src/ui/TimelineNavigation.test.tsx +0 -201
  219. package/src/ui/TopHeader.test.tsx +0 -239
  220. package/src/ui/TurnGroup.test.tsx +0 -365
  221. package/src/ui/busyStatusAnimation.test.ts +0 -30
  222. package/src/ui/commandNormalize.test.ts +0 -142
  223. package/src/ui/diffRenderer.test.ts +0 -102
  224. package/src/ui/focusFlow.test.tsx +0 -1098
  225. package/src/ui/inputBuffer.test.ts +0 -151
  226. package/src/ui/layout.test.ts +0 -145
  227. package/src/ui/modeDisplay.test.ts +0 -42
  228. package/src/ui/runActivityView.test.ts +0 -89
  229. package/src/ui/runLifecycleView.test.tsx +0 -237
  230. package/src/ui/statusRenderIsolation.test.tsx +0 -654
  231. package/src/ui/terminalAnswerFormat.test.ts +0 -19
  232. package/src/ui/textLayout.test.ts +0 -18
  233. package/src/ui/themeFlow.test.ts +0 -53
  234. package/src/ui/timelineMeasureCache.test.ts +0 -986
@@ -1,230 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, readFileSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- import test from "node:test";
6
- import {
7
- configureRenderDebug,
8
- getRenderDebugLogPath,
9
- traceBlankFrame,
10
- traceEvent,
11
- traceLayoutValidity,
12
- traceLifecycleEvent,
13
- traceLifecycleTransition,
14
- traceFlickerEvent,
15
- traceStatusTick,
16
- traceRender,
17
- traceTerminalWrite,
18
- } from "./renderDebug.js";
19
-
20
- function clean(path: string): void {
21
- rmSync(path, { force: true });
22
- }
23
-
24
- test("render debug stays quiet by default", () => {
25
- const logPath = join(tmpdir(), `codexa-render-debug-quiet-${process.pid}.jsonl`);
26
- clean(logPath);
27
-
28
- configureRenderDebug({ CODEXA_RENDER_DEBUG_FILE: logPath });
29
- traceEvent("test", "quiet");
30
- traceRender("QuietComponent", "test");
31
-
32
- assert.equal(existsSync(logPath), false);
33
- });
34
-
35
- test("render debug writes JSONL only when explicitly enabled", () => {
36
- const logPath = join(tmpdir(), `codexa-render-debug-enabled-${process.pid}.jsonl`);
37
- clean(logPath);
38
-
39
- try {
40
- configureRenderDebug({
41
- CODEXA_RENDER_DEBUG: "1",
42
- CODEXA_RENDER_DEBUG_FILE: logPath,
43
- });
44
- traceRender("EnabledComponent", "unit");
45
-
46
- assert.equal(getRenderDebugLogPath(), logPath);
47
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
48
- assert.equal(records[0]?.kind, "session");
49
- assert.equal(records[1]?.kind, "render");
50
- assert.equal(records[1]?.component, "EnabledComponent");
51
- assert.equal(records[1]?.reason, "unit");
52
- } finally {
53
- configureRenderDebug({});
54
- clean(logPath);
55
- }
56
- });
57
-
58
- test("render debug defaults to the repo-local diagnostic log path", () => {
59
- configureRenderDebug({});
60
- assert.equal(getRenderDebugLogPath(), join(process.cwd(), ".codexa-debug", "render-debug.log"));
61
- });
62
-
63
- test("render debug creates missing log directories", () => {
64
- const logPath = join(tmpdir(), `codexa-render-debug-nested-${process.pid}`, "render-debug.log");
65
- rmSync(join(tmpdir(), `codexa-render-debug-nested-${process.pid}`), { force: true, recursive: true });
66
-
67
- try {
68
- configureRenderDebug({
69
- CODEXA_RENDER_DEBUG: "1",
70
- CODEXA_RENDER_DEBUG_FILE: logPath,
71
- });
72
- assert.equal(existsSync(logPath), true);
73
- } finally {
74
- configureRenderDebug({});
75
- rmSync(join(tmpdir(), `codexa-render-debug-nested-${process.pid}`), { force: true, recursive: true });
76
- }
77
- });
78
-
79
- test("render debug records lifecycle, layout, and blank-frame diagnostics", () => {
80
- const logPath = join(tmpdir(), `codexa-render-diagnostics-${process.pid}.jsonl`);
81
- clean(logPath);
82
-
83
- try {
84
- configureRenderDebug({
85
- CODEXA_RENDER_DEBUG: "1",
86
- CODEXA_RENDER_DEBUG_FILE: logPath,
87
- });
88
- traceLifecycleEvent("Timeline", "mount", { viewportRows: 12 });
89
- traceLayoutValidity("Timeline", { viewportRows: 0, cols: 120 });
90
- traceBlankFrame("Timeline", { reason: "visible-rows-zero-with-events" });
91
-
92
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
93
- assert.equal(records[1]?.kind, "lifecycle");
94
- assert.equal(records[1]?.component, "Timeline");
95
- assert.equal(records[1]?.event, "mount");
96
- assert.equal(records[2]?.kind, "layout");
97
- assert.equal(records[2]?.event, "invalidLayout");
98
- assert.deepEqual(records[2]?.invalidValues, [{ key: "viewportRows", value: 0 }]);
99
- assert.equal(records[3]?.kind, "blankFrame");
100
- assert.equal(records[3]?.reason, "visible-rows-zero-with-events");
101
- } finally {
102
- configureRenderDebug({});
103
- clean(logPath);
104
- }
105
- });
106
-
107
- test("terminal writes are classified for clear and reset diagnosis", () => {
108
- const logPath = join(tmpdir(), `codexa-terminal-classification-${process.pid}.jsonl`);
109
- clean(logPath);
110
-
111
- try {
112
- configureRenderDebug({
113
- CODEXA_RENDER_DEBUG: "1",
114
- CODEXA_RENDER_DEBUG_FILE: logPath,
115
- });
116
- traceTerminalWrite(
117
- "stdout",
118
- "unit",
119
- "\x1b[2J\x1b[3J\x1b[H\x1bc\x1b[?1049h\x1b]0;CODEXA\x07\x1b[?2004h\x1b[?1000h",
120
- );
121
-
122
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
123
- assert.equal(records[1]?.kind, "stdout");
124
- assert.equal(records[1]?.containsViewportClear, true);
125
- assert.equal(records[1]?.containsScrollbackClear, true);
126
- assert.equal(records[1]?.containsCursorHome, true);
127
- assert.equal(records[1]?.containsTerminalReset, true);
128
- assert.equal(records[1]?.containsAlternateScreen, true);
129
- assert.equal(records[1]?.containsTitleSequence, true);
130
- assert.equal(records[1]?.containsBracketedPaste, true);
131
- assert.equal(records[1]?.containsMouseMode, true);
132
- } finally {
133
- configureRenderDebug({});
134
- clean(logPath);
135
- }
136
- });
137
-
138
- test("CODEXA_DEBUG_RENDER aliases render debug logging", () => {
139
- const logPath = join(tmpdir(), `codexa-debug-render-alias-${process.pid}.jsonl`);
140
- clean(logPath);
141
-
142
- try {
143
- configureRenderDebug({
144
- CODEXA_DEBUG_RENDER: "1",
145
- CODEXA_RENDER_DEBUG_FILE: logPath,
146
- });
147
- traceRender("AliasComponent", "unit");
148
-
149
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
150
- assert.equal(records[0]?.kind, "session");
151
- assert.equal(records[1]?.kind, "render");
152
- assert.equal(records[1]?.component, "AliasComponent");
153
- } finally {
154
- configureRenderDebug({});
155
- clean(logPath);
156
- }
157
- });
158
-
159
- test("render trace flag enables compact render diagnostics", () => {
160
- const logPath = join(tmpdir(), `codexa-render-trace-${process.pid}.jsonl`);
161
- clean(logPath);
162
-
163
- try {
164
- configureRenderDebug({
165
- CODEXA_DEBUG_RENDER_TRACE: "1",
166
- CODEXA_RENDER_DEBUG_FILE: logPath,
167
- });
168
- traceRender("TraceComponent", "unit");
169
- traceFlickerEvent("viewportSlice", { reason: "unit" });
170
-
171
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
172
- assert.equal(records[0]?.kind, "session");
173
- assert.equal(records[1]?.kind, "render");
174
- assert.equal(records[1]?.component, "TraceComponent");
175
- assert.equal(records[2]?.kind, "flicker");
176
- assert.equal(records[2]?.event, "viewportSlice");
177
- } finally {
178
- configureRenderDebug({});
179
- clean(logPath);
180
- }
181
- });
182
-
183
- test("lifecycle trace is gated by CODEXA_DEBUG_LIFECYCLE", () => {
184
- const logPath = join(tmpdir(), `codexa-lifecycle-debug-${process.pid}.jsonl`);
185
- clean(logPath);
186
-
187
- try {
188
- configureRenderDebug({
189
- CODEXA_DEBUG_LIFECYCLE: "1",
190
- CODEXA_RENDER_DEBUG_FILE: logPath,
191
- });
192
- traceLifecycleTransition({
193
- prevKind: "THINKING",
194
- nextKind: "IDLE",
195
- reason: "unit",
196
- });
197
-
198
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
199
- assert.equal(records.length, 1);
200
- assert.equal(records[0]?.kind, "lifecycle");
201
- assert.equal(records[0]?.reason, "unit");
202
- } finally {
203
- configureRenderDebug({});
204
- clean(logPath);
205
- }
206
- });
207
-
208
- test("flicker trace is gated by CODEXA_DEBUG_FLICKER", () => {
209
- const logPath = join(tmpdir(), `codexa-flicker-debug-${process.pid}.jsonl`);
210
- clean(logPath);
211
-
212
- try {
213
- configureRenderDebug({
214
- CODEXA_DEBUG_FLICKER: "1",
215
- CODEXA_RENDER_DEBUG_FILE: logPath,
216
- });
217
- traceFlickerEvent("timelineRender", { reason: "unit" });
218
- traceStatusTick({ owner: "Status", label: "Codex is thinking" });
219
-
220
- const records = readFileSync(logPath, "utf8").trim().split("\n").map((line) => JSON.parse(line));
221
- assert.equal(records.length, 2);
222
- assert.equal(records[0]?.kind, "flicker");
223
- assert.equal(records[0]?.event, "timelineRender");
224
- assert.equal(records[1]?.kind, "flicker");
225
- assert.equal(records[1]?.event, "statusTick");
226
- } finally {
227
- configureRenderDebug({});
228
- clean(logPath);
229
- }
230
- });
@@ -1,143 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test, { afterEach, beforeEach, describe } from "node:test";
3
- import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
4
- import { join } from "path";
5
- import { tmpdir } from "os";
6
- import { resolvePlanDir, savePlan, readPlan } from "./planStorage.js";
7
-
8
- describe("resolvePlanDir", () => {
9
- const savedEnv: Record<string, string | undefined> = {};
10
- const envKeys = ["CODEXA_PLAN_DIR", "LOCALAPPDATA", "APPDATA", "XDG_DATA_HOME"];
11
-
12
- beforeEach(() => {
13
- for (const key of envKeys) {
14
- savedEnv[key] = process.env[key];
15
- }
16
- });
17
-
18
- afterEach(() => {
19
- for (const key of envKeys) {
20
- if (savedEnv[key] === undefined) {
21
- delete process.env[key];
22
- } else {
23
- process.env[key] = savedEnv[key];
24
- }
25
- }
26
- });
27
-
28
- test("CODEXA_PLAN_DIR override takes priority", () => {
29
- process.env["CODEXA_PLAN_DIR"] = "/custom/plan/dir";
30
- assert.equal(resolvePlanDir(), "/custom/plan/dir");
31
- });
32
-
33
- test("Windows LOCALAPPDATA path resolution", () => {
34
- delete process.env["CODEXA_PLAN_DIR"];
35
- process.env["LOCALAPPDATA"] = "C:\\Users\\test\\AppData\\Local";
36
- const result = resolvePlanDir("win32");
37
- assert.ok(result.includes("Codexa"));
38
- assert.ok(result.includes("plans"));
39
- assert.ok(result.includes("AppData"));
40
- });
41
-
42
- test("Windows APPDATA fallback", () => {
43
- delete process.env["CODEXA_PLAN_DIR"];
44
- delete process.env["LOCALAPPDATA"];
45
- process.env["APPDATA"] = "C:\\Users\\test\\AppData\\Roaming";
46
- const result = resolvePlanDir("win32");
47
- assert.ok(result.includes("Codexa"));
48
- assert.ok(result.includes("plans"));
49
- assert.ok(result.includes("Roaming"));
50
- });
51
-
52
- test("macOS path", () => {
53
- delete process.env["CODEXA_PLAN_DIR"];
54
- const result = resolvePlanDir("darwin");
55
- assert.ok(result.includes("Library"));
56
- assert.ok(result.includes("Codexa"));
57
- assert.ok(result.includes("plans"));
58
- });
59
-
60
- test("Linux default path", () => {
61
- delete process.env["CODEXA_PLAN_DIR"];
62
- delete process.env["XDG_DATA_HOME"];
63
- const result = resolvePlanDir("linux");
64
- assert.ok(result.includes(".local"));
65
- assert.ok(result.includes("share"));
66
- assert.ok(result.includes("codexa"));
67
- assert.ok(result.includes("plans"));
68
- });
69
-
70
- test("Linux XDG_DATA_HOME override", () => {
71
- delete process.env["CODEXA_PLAN_DIR"];
72
- process.env["XDG_DATA_HOME"] = "/custom/xdg/data";
73
- const result = resolvePlanDir("linux");
74
- assert.ok(result.includes("custom"));
75
- assert.ok(result.includes("xdg"));
76
- assert.ok(result.includes("codexa"));
77
- assert.ok(result.includes("plans"));
78
- });
79
- });
80
-
81
- describe("savePlan", () => {
82
- let tempDir: string;
83
-
84
- beforeEach(() => {
85
- tempDir = join(tmpdir(), `planStorage-test-${Date.now()}`);
86
- mkdirSync(tempDir, { recursive: true });
87
- process.env["CODEXA_PLAN_DIR"] = tempDir;
88
- });
89
-
90
- afterEach(() => {
91
- delete process.env["CODEXA_PLAN_DIR"];
92
- rmSync(tempDir, { recursive: true, force: true });
93
- });
94
-
95
- test("safe filename format with timestamp and 8-hex-char hash", () => {
96
- const result = savePlan("# My Plan", "/some/workspace");
97
- assert.notEqual(result, null);
98
- const filename = result!.split(/[/\\]/).pop()!;
99
- assert.match(filename, /^plan-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}.*-[0-9a-f]{8}\.md$/);
100
- });
101
-
102
- test("written content can be read back", () => {
103
- const content = "# Test Plan\n\n- Step 1\n- Step 2\n";
104
- const result = savePlan(content, "/workspace");
105
- assert.notEqual(result, null);
106
- assert.ok(existsSync(result!));
107
- assert.equal(readPlan(result!), content);
108
- });
109
-
110
- test("write failure returns null", () => {
111
- process.env["CODEXA_PLAN_DIR"] = "/nonexistent\x00/bad/path";
112
- const result = savePlan("content", "/workspace");
113
- assert.equal(result, null);
114
- });
115
-
116
- test("does not write to process.cwd()", () => {
117
- const cwdPlanDir = join(process.cwd(), ".codexa");
118
- const beforeMarkdown = existsSync(cwdPlanDir)
119
- ? readdirSync(cwdPlanDir).filter((name) => name.endsWith(".md")).sort()
120
- : [];
121
- savePlan("# Plan", process.cwd());
122
- const afterMarkdown = existsSync(cwdPlanDir)
123
- ? readdirSync(cwdPlanDir).filter((name) => name.endsWith(".md")).sort()
124
- : [];
125
- assert.deepEqual(afterMarkdown, beforeMarkdown);
126
- });
127
- });
128
-
129
- describe("readPlan", () => {
130
- test("returns null for missing file", () => {
131
- assert.equal(readPlan("/nonexistent/file.md"), null);
132
- });
133
-
134
- test("reads existing file content", () => {
135
- const tempFile = join(tmpdir(), `plan-read-test-${Date.now()}.md`);
136
- writeFileSync(tempFile, "hello plan", "utf-8");
137
- try {
138
- assert.equal(readPlan(tempFile), "hello plan");
139
- } finally {
140
- rmSync(tempFile, { force: true });
141
- }
142
- });
143
- });
@@ -1,105 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { readFileSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import test from "node:test";
5
- import { runCommand, runShellCommand, summarizeCommandResult, type CommandResult } from "./CommandRunner.js";
6
-
7
- function makeResult(overrides: Partial<CommandResult> = {}): CommandResult {
8
- return {
9
- status: "completed",
10
- exitCode: 0,
11
- signal: null,
12
- stdout: "",
13
- stderr: "",
14
- startedAt: 1,
15
- endedAt: 2,
16
- durationMs: 1,
17
- userMessage: "Command completed.",
18
- ...overrides,
19
- };
20
- }
21
-
22
- test("summarizes ripgrep file listings without flooding the UI", () => {
23
- const result = makeResult({
24
- stdout: "src/app.tsx\nsrc/ui/BottomComposer.tsx\n",
25
- });
26
-
27
- assert.equal(summarizeCommandResult("rg --files", result), "Found 2 files.");
28
- });
29
-
30
- test("keeps a concise fallback summary for generic successful commands", () => {
31
- const result = makeResult({
32
- stdout: "alpha\nbeta\ngamma\n",
33
- });
34
-
35
- assert.equal(summarizeCommandResult("node script.js", result), "Produced 3 lines of output.");
36
- });
37
-
38
- test("preserves the failure message for unsuccessful commands", () => {
39
- const result = makeResult({
40
- status: "failed",
41
- exitCode: 1,
42
- userMessage: "git exited with code 1.",
43
- stderr: "fatal: not a git repository",
44
- });
45
-
46
- assert.equal(summarizeCommandResult("git status", result), "git exited with code 1.");
47
- });
48
-
49
- test("runCommand executes a direct executable with argument array", async () => {
50
- const runner = runCommand({
51
- executable: process.execPath,
52
- args: ["-e", "console.log(process.argv[1])", "direct-ok"],
53
- cwd: process.cwd(),
54
- });
55
-
56
- const result = await runner.result;
57
- assert.equal(result.status, "completed");
58
- assert.equal(result.exitCode, 0);
59
- assert.equal(result.stdout.trim(), "direct-ok");
60
- });
61
-
62
- test("runCommand rejects obvious executable injection", () => {
63
- assert.throws(
64
- () => runCommand({
65
- executable: "node & echo injected",
66
- args: ["--version"],
67
- cwd: process.cwd(),
68
- }),
69
- /single executable name|shell metacharacters/i,
70
- );
71
- });
72
-
73
- test("runShellCommand is the explicit shell execution path", async () => {
74
- const runner = runShellCommand("echo shell-ok", { cwd: process.cwd() });
75
- const result = await runner.result;
76
-
77
- assert.equal(result.status, "completed");
78
- assert.equal(result.exitCode, 0);
79
- assert.match(result.stdout, /shell-ok/);
80
- });
81
-
82
- test("command runner reports lifecycle boundaries for terminal title reassertion", () => {
83
- const source = readFileSync(fileURLToPath(new URL("./CommandRunner.ts", import.meta.url)), "utf8");
84
- const beforeSpawnIndex = source.indexOf('handlers.onProcessLifecycle?.("before-spawn")');
85
- const spawnIndex = source.indexOf("child = spawn(");
86
- const spawnedIndex = source.indexOf('handlers.onProcessLifecycle?.("spawned")', spawnIndex);
87
- const errorIndex = source.indexOf('child.once("error"', spawnIndex);
88
- const lifecycleErrorIndex = source.indexOf('handlers.onProcessLifecycle?.("error")', errorIndex);
89
- const closeIndex = source.indexOf('child.once("close"', spawnIndex);
90
- const exitIndex = source.indexOf('handlers.onProcessLifecycle?.("exit")', closeIndex);
91
- const cancelIndex = source.indexOf("cancel: () =>");
92
- const lifecycleCancelIndex = source.indexOf('handlers.onProcessLifecycle?.("cancel")', cancelIndex);
93
-
94
- assert.ok(beforeSpawnIndex >= 0 && beforeSpawnIndex < spawnIndex);
95
- assert.ok(spawnedIndex > spawnIndex);
96
- assert.ok(lifecycleErrorIndex > errorIndex);
97
- assert.ok(exitIndex > closeIndex);
98
- assert.ok(lifecycleCancelIndex > cancelIndex);
99
- });
100
-
101
- test("generic command runner does not expose shell mode", () => {
102
- const source = readFileSync(fileURLToPath(new URL("./CommandRunner.ts", import.meta.url)), "utf8");
103
- assert.equal(source.includes("shell?: boolean"), false);
104
- assert.equal(source.includes("spec.shell"), false);
105
- });
@@ -1,50 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import test from "node:test";
6
- import { loadProjectInstructions } from "./projectInstructions.js";
7
-
8
- function withTempWorkspace(run: (workspaceRoot: string) => void) {
9
- const workspaceRoot = mkdtempSync(join(tmpdir(), "codexa-instructions-"));
10
- try {
11
- run(workspaceRoot);
12
- } finally {
13
- rmSync(workspaceRoot, { recursive: true, force: true });
14
- }
15
- }
16
-
17
- test("loads AGENTS.md from the workspace root first", () => {
18
- withTempWorkspace((workspaceRoot) => {
19
- mkdirSync(join(workspaceRoot, ".codex"), { recursive: true });
20
- writeFileSync(join(workspaceRoot, "AGENTS.md"), "root instructions\n", "utf8");
21
- writeFileSync(join(workspaceRoot, ".codex", "AGENTS.md"), "nested instructions\n", "utf8");
22
-
23
- const result = loadProjectInstructions(workspaceRoot);
24
-
25
- assert.equal(result.status, "loaded");
26
- if (result.status !== "loaded") return;
27
- assert.equal(result.instructions.content, "root instructions");
28
- assert.match(result.instructions.path, /AGENTS\.md$/);
29
- });
30
- });
31
-
32
- test("falls back to .codex/AGENTS.md", () => {
33
- withTempWorkspace((workspaceRoot) => {
34
- mkdirSync(join(workspaceRoot, ".codex"), { recursive: true });
35
- writeFileSync(join(workspaceRoot, ".codex", "AGENTS.md"), "project instructions\n", "utf8");
36
-
37
- const result = loadProjectInstructions(workspaceRoot);
38
-
39
- assert.equal(result.status, "loaded");
40
- if (result.status !== "loaded") return;
41
- assert.equal(result.instructions.content, "project instructions");
42
- assert.match(result.instructions.path, /\.codex[\\/]AGENTS\.md$/);
43
- });
44
- });
45
-
46
- test("treats missing instruction files as non-fatal", () => {
47
- withTempWorkspace((workspaceRoot) => {
48
- assert.deepEqual(loadProjectInstructions(workspaceRoot), { status: "missing" });
49
- });
50
- });