@golba98/codexa 1.0.2 → 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.
- package/README.md +391 -94
- package/package.json +12 -7
- package/src/app.tsx +149 -117
- package/src/commands/handler.ts +4 -4
- package/src/config/buildInfo.ts +2 -2
- package/src/config/persistence.ts +10 -0
- package/src/config/settings.ts +7 -15
- package/src/config/updateCheckCache.ts +19 -1
- package/src/core/channel.ts +23 -0
- package/src/core/executables/codexExecutable.ts +1 -0
- package/src/core/executables/executableResolver.ts +61 -43
- package/src/core/launchContext.ts +33 -33
- package/src/core/process/processValidation.ts +9 -5
- package/src/core/providerLauncher/launcher.ts +59 -42
- package/src/core/updateCheck.ts +111 -95
- package/src/session/types.ts +2 -1
- package/src/ui/ActionRequiredBlock.tsx +5 -5
- package/src/ui/ActivityBars.tsx +3 -3
- package/src/ui/ActivityIndicator.tsx +6 -6
- package/src/ui/AgentBlock.tsx +6 -6
- package/src/ui/AnimatedStatusText.tsx +1 -1
- package/src/ui/AppShell.tsx +9 -24
- package/src/ui/AttachmentImportPanel.tsx +8 -8
- package/src/ui/AuthPanel.tsx +20 -20
- package/src/ui/BottomComposer.tsx +117 -78
- package/src/ui/DashCard.tsx +3 -3
- package/src/ui/Markdown.tsx +17 -17
- package/src/ui/ModelPickerScreen.tsx +10 -10
- package/src/ui/ModelReasoningPicker.tsx +14 -14
- package/src/ui/Panel.tsx +3 -3
- package/src/ui/PlanActionPicker.tsx +6 -6
- package/src/ui/PlanReviewPanel.tsx +8 -8
- package/src/ui/ProviderPicker.tsx +18 -18
- package/src/ui/RunFooter.tsx +3 -3
- package/src/ui/SelectionPanel.tsx +4 -4
- package/src/ui/SettingsPanel.tsx +8 -8
- package/src/ui/Spinner.tsx +1 -1
- package/src/ui/TextEntryPanel.tsx +10 -10
- package/src/ui/ThinkingBlock.tsx +8 -8
- package/src/ui/Timeline.tsx +21 -17
- package/src/ui/TopHeader.tsx +421 -293
- package/src/ui/TurnGroup.tsx +31 -31
- package/src/ui/UpdateAvailableCard.tsx +41 -0
- package/src/ui/UpdatePromptPanel.tsx +203 -0
- package/src/ui/focus.ts +3 -0
- package/src/ui/layout.ts +1 -1
- package/src/ui/logoVariants.ts +107 -0
- package/src/ui/modeDisplay.ts +12 -12
- package/src/ui/runtimeDisplay.ts +100 -0
- package/src/ui/theme.tsx +274 -395
- package/src/ui/timelineMeasure.ts +29 -19
- package/scripts/audit-codexa-capabilities.mjs +0 -466
- package/scripts/gen-build-info.mjs +0 -33
- package/scripts/smoke-terminal-bench.mjs +0 -35
- package/src/appRenderStability.test.ts +0 -131
- package/src/commands/handler.test.ts +0 -655
- package/src/config/launchArgs.test.ts +0 -189
- package/src/config/layeredConfig.test.ts +0 -143
- package/src/config/persistence.test.ts +0 -114
- package/src/config/runtimeConfig.test.ts +0 -218
- package/src/config/settings.test.ts +0 -155
- package/src/config/trustStore.test.ts +0 -29
- package/src/core/attachments.test.ts +0 -155
- package/src/core/auth/codexAuth.test.ts +0 -68
- package/src/core/cleanupFastFail.test.ts +0 -76
- package/src/core/codexExecArgs.test.ts +0 -195
- package/src/core/codexLaunch.test.ts +0 -205
- package/src/core/codexPrompt.test.ts +0 -252
- package/src/core/executables/codexExecutable.test.ts +0 -212
- package/src/core/executables/executableResolver.test.ts +0 -129
- package/src/core/executables/geminiExecutable.test.ts +0 -116
- package/src/core/executables/pathSanityScan.test.ts +0 -47
- package/src/core/githubDiagnostics.test.ts +0 -92
- package/src/core/hollowResponseFormat.test.ts +0 -58
- package/src/core/launchContext.test.ts +0 -157
- package/src/core/models/codexCapabilities.test.ts +0 -45
- package/src/core/models/codexModelCapabilities.test.ts +0 -246
- package/src/core/models/modelSpecs.test.ts +0 -283
- package/src/core/perf/renderDebug.test.ts +0 -230
- package/src/core/planStorage.test.ts +0 -143
- package/src/core/process/CommandRunner.test.ts +0 -105
- package/src/core/projectInstructions.test.ts +0 -50
- package/src/core/providerLauncher/launcher.test.ts +0 -238
- package/src/core/providerLauncher/registry.test.ts +0 -324
- package/src/core/providerLauncher/workspaceConfig.test.ts +0 -638
- package/src/core/providerRuntime/anthropic.test.ts +0 -1120
- package/src/core/providerRuntime/capabilityProfile.test.ts +0 -311
- package/src/core/providerRuntime/contextMetadata.test.ts +0 -468
- package/src/core/providerRuntime/gemini.test.ts +0 -437
- package/src/core/providerRuntime/lmstudio.test.ts +0 -168
- package/src/core/providerRuntime/local.test.ts +0 -787
- package/src/core/providerRuntime/registry.test.ts +0 -233
- package/src/core/providers/codexJsonStream.test.ts +0 -148
- package/src/core/providers/codexSubprocess.test.ts +0 -68
- package/src/core/providers/codexTranscript.test.ts +0 -284
- package/src/core/terminal/startupClear.test.ts +0 -55
- package/src/core/terminal/terminalCapabilities.test.ts +0 -93
- package/src/core/terminal/terminalControl.test.ts +0 -75
- package/src/core/terminal/terminalSanitize.test.ts +0 -22
- package/src/core/terminal/terminalSelection.test.ts +0 -42
- package/src/core/terminal/terminalTitle.test.ts +0 -328
- package/src/core/updateCheck.test.ts +0 -194
- package/src/core/workspaceActivity.test.ts +0 -163
- package/src/core/workspaceGuard.test.ts +0 -151
- package/src/core/workspaceRoot.test.ts +0 -23
- package/src/exec.test.ts +0 -13
- package/src/headless/execArgs.test.ts +0 -147
- package/src/headless/execRunner.test.ts +0 -436
- package/src/index.test.tsx +0 -620
- package/src/session/appSession.test.ts +0 -897
- package/src/session/chatLifecycle.test.ts +0 -64
- package/src/session/liveRenderScheduler.test.ts +0 -201
- package/src/session/planFlow.test.ts +0 -103
- package/src/session/planTranscript.test.ts +0 -65
- package/src/session/promptRunSchedule.test.ts +0 -36
- package/src/ui/ActivityIndicator.test.tsx +0 -58
- package/src/ui/AgentBlock.test.ts +0 -6
- package/src/ui/AnimatedStatusText.test.ts +0 -16
- package/src/ui/AppShell.test.tsx +0 -1776
- package/src/ui/AttachmentImportPanel.test.tsx +0 -204
- package/src/ui/BottomComposer.test.ts +0 -674
- package/src/ui/CodexLogo.tsx +0 -55
- package/src/ui/Markdown.test.ts +0 -157
- package/src/ui/ModelPickerProviderScope.test.tsx +0 -411
- package/src/ui/ModelPickerScreen.test.tsx +0 -99
- package/src/ui/ModelPickerState.test.tsx +0 -151
- package/src/ui/ModelReasoningPicker.test.tsx +0 -447
- package/src/ui/PlanReviewPanel.test.tsx +0 -267
- package/src/ui/PromptCardBorder.test.tsx +0 -161
- package/src/ui/ProviderPicker.test.tsx +0 -289
- package/src/ui/ProviderShortcut.test.tsx +0 -143
- package/src/ui/SettingsPanel.test.tsx +0 -233
- package/src/ui/Timeline.test.ts +0 -2067
- package/src/ui/TimelineNavigation.test.tsx +0 -201
- package/src/ui/TopHeader.test.tsx +0 -254
- package/src/ui/TurnGroup.test.tsx +0 -365
- package/src/ui/busyStatusAnimation.test.ts +0 -30
- package/src/ui/commandNormalize.test.ts +0 -142
- package/src/ui/diffRenderer.test.ts +0 -102
- package/src/ui/focusFlow.test.tsx +0 -1098
- package/src/ui/inputBuffer.test.ts +0 -151
- package/src/ui/layout.test.ts +0 -146
- package/src/ui/modeDisplay.test.ts +0 -42
- package/src/ui/runActivityView.test.ts +0 -89
- package/src/ui/runLifecycleView.test.tsx +0 -237
- package/src/ui/statusRenderIsolation.test.tsx +0 -654
- package/src/ui/terminalAnswerFormat.test.ts +0 -19
- package/src/ui/textLayout.test.ts +0 -18
- package/src/ui/themeFlow.test.ts +0 -53
- 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
|
-
});
|