@scanton/phase2s 0.25.0 → 0.26.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.
package/README.md CHANGED
@@ -1,21 +1,12 @@
1
1
  # Phase2S
2
2
 
3
- Phase2S is a personal AI coding assistant you run in your terminal. You type questions about your code, ask it to review files, debug problems, or implement a feature — and it answers using your ChatGPT subscription, OpenAI API key, Anthropic API key, or a local Ollama model.
3
+ Three things Phase2S does that most AI coding tools don't:
4
4
 
5
- Think of it as a slash-command layer on top of AI. Instead of typing "please review this file for security issues and flag each problem with a severity level", you type `/review src/core/auth.ts` and get a structured, consistent answer every time.
5
+ 1. **Runs on your ChatGPT subscription** If you pay for ChatGPT Plus or Pro, you can use that same subscription to power a full coding assistant in your terminal. No API key, no per-token billing. The $20/month you're already paying starts pulling its weight.
6
6
 
7
- ```
8
- you > /review src/core/agent.ts
9
- assistant > Reviewing src/core/agent.ts...
10
-
11
- CRIT: The `maxTurns` check runs after tool execution, not before.
12
- An LLM that loops tool calls can exceed the limit by one turn.
13
-
14
- WARN: `getConversation()` returns the live object, not a copy.
15
- Callers that mutate it will corrupt the conversation state.
7
+ 2. **Gives Claude Code a second opinion** — If you use Claude Code as your daily driver, Phase2S plugs in as an MCP server and gives Claude a tool to call that runs your plan through GPT from a completely different model with completely different training. Before Claude executes anything big, it gets challenged by a second AI that has no stake in agreeing with it.
16
8
 
17
- NIT: Inline comment on line 47 is stale describes old batch behavior.
18
- ```
9
+ 3. **Executes specs autonomously** Write a spec describing what you want built and how you'll know it's done. Run `phase2s goal your-spec.md`. Phase2S breaks it into sub-tasks, implements each one, runs your tests, checks whether the results match your criteria, and retries with failure analysis if anything falls short. You come back when it's done.
19
10
 
20
11
  ---
21
12
 
@@ -23,9 +14,7 @@ assistant > Reviewing src/core/agent.ts...
23
14
 
24
15
  Requires [Node.js](https://nodejs.org) >= 20.
25
16
 
26
- **Option A: ChatGPT Plus or Pro subscription (recommended)**
27
-
28
- No API key, no per-token billing. All 29 skills work.
17
+ **If you have ChatGPT Plus or Pro (recommended)**
29
18
 
30
19
  ```bash
31
20
  npm install -g @openai/codex @scanton/phase2s
@@ -33,9 +22,9 @@ codex auth
33
22
  phase2s
34
23
  ```
35
24
 
36
- **Option B: OpenAI API key**
25
+ `codex auth` opens a browser window and logs into your ChatGPT account. You do it once. After that, `phase2s` uses your subscription automatically — no API keys, no credits to manage.
37
26
 
38
- Unlocks token-by-token streaming and model-per-skill routing.
27
+ **If you have an OpenAI API key**
39
28
 
40
29
  ```bash
41
30
  npm install -g @scanton/phase2s
@@ -44,9 +33,7 @@ export PHASE2S_PROVIDER=openai-api
44
33
  phase2s
45
34
  ```
46
35
 
47
- **Option C: Anthropic API key**
48
-
49
- Run all 29 skills on Claude 3.5 Sonnet (or any Anthropic model).
36
+ **If you have an Anthropic API key**
50
37
 
51
38
  ```bash
52
39
  npm install -g @scanton/phase2s
@@ -55,9 +42,7 @@ export PHASE2S_PROVIDER=anthropic
55
42
  phase2s
56
43
  ```
57
44
 
58
- **Option D: Local Ollama (free, private, offline)**
59
-
60
- No API keys. Runs entirely on your machine after the initial model pull.
45
+ **If you want to run everything locally (free, private, no internet)**
61
46
 
62
47
  ```bash
63
48
  npm install -g @scanton/phase2s
@@ -68,119 +53,207 @@ phase2s
68
53
 
69
54
  ---
70
55
 
71
- ## Quick start
56
+ ## Feature 1: Your ChatGPT subscription, in your terminal
72
57
 
73
- Once you're at the `you >` prompt:
58
+ Most people who pay for ChatGPT Plus use it by opening a browser tab and typing. Phase2S turns it into a programmable coding tool you can use from the command line, from scripts, and from inside Claude Code.
74
59
 
75
60
  ```
76
- you > /review src/core/agent.ts — code review with CRIT/WARN/NIT tagging
77
- you > /diff — review all uncommitted changes
78
- you > /satori add rate limiting — implement + test + retry until green
79
- you > /health — code quality score (tests, types, lint)
80
- you > /remember — save a project convention to memory
61
+ you > /review src/core/auth.ts
62
+
63
+ CRIT: session.destroy() is called without await on line 83.
64
+ If it rejects (Redis timeout, etc.), the error is silently dropped
65
+ and the response goes out before the session is actually cleared.
66
+
67
+ WARN: The JWT expiry check on line 47 uses Date.now() directly.
68
+ Clock skew between your server and the token issuer can cause
69
+ valid tokens to fail. Use a small leeway (±30s) instead.
70
+
71
+ NIT: The error message on line 91 says "auth failed" but should say
72
+ "session_expired" to match the error codes in api-errors.ts.
81
73
  ```
82
74
 
83
- Goal execution (dark factory mode):
75
+ ```
76
+ you > /satori add rate limiting to the API
84
77
 
85
- ```bash
86
- phase2s goal .phase2s/specs/2026-04-04-my-spec.md # run a spec to completion
87
- phase2s goal my-spec.md --max-attempts 5 # up to 5 retry loops
78
+ -- Attempt 1 --
79
+ [creates src/utils/rate-limiter.ts]
80
+ [creates src/middleware/rate-limit.ts]
81
+ [registers middleware in app.ts]
82
+ [writes tests in test/middleware/rate-limit.test.ts]
83
+ npm test: FAIL — bucket not clearing on window expiry
84
+
85
+ -- Attempt 2 --
86
+ [fixes resetAt logic in RateLimiter.check()]
87
+ npm test: PASS (23 tests)
88
+
89
+ Done in 2 attempts.
88
90
  ```
89
91
 
90
- One-shot mode (no REPL):
92
+ The 29 built-in skills cover the full development loop: specify, plan, implement, test, review, debug, ship, deploy. All of them run on your subscription.
93
+
94
+ [Full skill list →](docs/skills.md)
95
+
96
+ ---
97
+
98
+ ## Feature 2: Claude Code + Phase2S adversarial review
99
+
100
+ If you use Claude Code, here's the problem: one model reviewing its own work has blind spots. Claude agrees with Claude. The same training data, the same biases, the same failure modes.
101
+
102
+ Phase2S solves this by plugging into Claude Code as an MCP server. When Claude is about to execute a plan, it can call Phase2S — which runs the same plan through GPT using your ChatGPT subscription — and get back a structured challenge:
91
103
 
92
- ```bash
93
- phase2s run "/explain src/core/agent.ts" # routes through the explain skill
94
- phase2s run --dry-run "/satori add auth" # preview which skill + model, no execution
95
104
  ```
105
+ VERDICT: CHALLENGED
106
+ STRONGEST_CONCERN: The token bucket resets per-request rather than per-window.
107
+ OBJECTIONS:
108
+ 1. RateLimiter.check() increments the counter and checks it in the same call.
109
+ When the window expires, the bucket resets on the next request — meaning
110
+ a client can always make exactly one request immediately after the window
111
+ closes, even if they were throttled. The reset should happen on a fixed
112
+ schedule, not lazily on first request.
113
+ 2. The middleware is registered after the auth middleware in app.ts line 34.
114
+ Unauthenticated requests bypass rate limiting entirely.
115
+ APPROVE_IF: Fix the window reset logic and move middleware before auth.
116
+ ```
117
+
118
+ Claude gets specific, falsifiable objections from a model that wasn't involved in writing the plan. You see the verdict. You decide whether to proceed.
96
119
 
97
- Tab completion (bash/zsh):
120
+ **Setup takes about 2 minutes.** [Step-by-step guide →](docs/claude-code.md)
121
+
122
+ ---
123
+
124
+ ## Feature 3: The dark factory
125
+
126
+ Write a spec. Run one command. Come back when it's done.
127
+
128
+ `phase2s goal` reads your spec, breaks it into sub-tasks, implements each one through the `/satori` skill (which runs implement → test → retry until green), runs your eval command, checks whether your acceptance criteria actually passed, and if they didn't — analyzes what broke, figures out which sub-tasks need to be re-run, and tries again with that failure context.
129
+
130
+ It keeps going until all criteria pass or it runs out of attempts.
98
131
 
99
132
  ```bash
100
- eval "$(phase2s completion bash)" # add to ~/.bashrc for persistent completion
101
- phase2s run "/exp<TAB>" # completes to /explain
133
+ # Write the spec interactively
134
+ phase2s
135
+ you > /deep-specify add pagination to the search endpoint
136
+
137
+ # Execute it autonomously
138
+ phase2s goal .phase2s/specs/2026-04-04-11-00-pagination.md
102
139
  ```
103
140
 
104
- Resume your last session:
141
+ ```
142
+ Goal executor: Pagination for search endpoint
143
+ Sub-tasks: 3 | Eval: npm test | Max attempts: 3
105
144
 
106
- ```bash
107
- phase2s --resume
145
+ === Attempt 1/3 ===
146
+ Running sub-task: Cursor-based pagination logic
147
+ [satori: implement → test → retry until green]
148
+
149
+ Running sub-task: API response format update
150
+ [satori: implement → test → retry until green]
151
+
152
+ Running sub-task: Frontend page controls
153
+ [satori: implement → test → retry until green]
154
+
155
+ Running evaluation: npm test
156
+
157
+ Acceptance criteria:
158
+ ✗ Returns correct next_cursor on paginated results
159
+ ✓ Returns 20 items per page by default
160
+ ✓ next_cursor is null on last page
161
+
162
+ Retrying 1 sub-task(s): Cursor-based pagination logic
163
+
164
+ === Attempt 2/3 ===
165
+ Running sub-task: Cursor-based pagination logic
166
+ [satori: implement → test → retry until green]
167
+
168
+ Running evaluation: npm test
169
+
170
+ Acceptance criteria:
171
+ ✓ Returns correct next_cursor on paginated results
172
+ ✓ Returns 20 items per page by default
173
+ ✓ next_cursor is null on last page
174
+
175
+ ✓ All acceptance criteria met after 2 attempt(s).
108
176
  ```
109
177
 
110
- ---
178
+ This uses your ChatGPT subscription for all the implementation work. No API key needed.
111
179
 
112
- ## What's included
180
+ [Full dark factory guide →](docs/dark-factory.md)
113
181
 
114
- 29 built-in skills across 6 categories. A few highlights:
182
+ ---
115
183
 
116
- - `/satori` implement a task, run `npm test`, retry on failure (up to 3 times). Stops when tests are green, not when the model thinks it's done.
117
- - `/consensus-plan` — planner + architect + critic passes before producing a plan. Catches the errors that only show up in implementation.
118
- - `/deep-specify` — Socratic interview before writing code. Saves a 5-pillar spec (Problem Statement, Acceptance Criteria, Constraints, Decomposition, Eval Design) consumable by `phase2s goal`.
119
- - `/debug` — reproduce, isolate, fix, and verify a bug end-to-end.
120
- - `/remember` — save project conventions to persistent memory. Injected into every future session automatically.
121
- - `/skill` — create a new `/command` from inside Phase2S. Three questions, no YAML editing.
122
- - `/land-and-deploy` — push, open a PR, merge it, wait for CI, confirm the land. Picks up where `/ship` leaves off.
184
+ ## All 29 skills
123
185
 
124
- List everything:
186
+ ```
187
+ you > /review src/auth.ts — code review: CRIT / WARN / NIT
188
+ you > /diff — review all uncommitted changes
189
+ you > /satori add pagination — implement + test + retry until green
190
+ you > /deep-specify add OAuth — spec interview → 5-pillar spec file
191
+ you > /consensus-plan add OAuth — planner + architect + critic passes
192
+ you > /debug logout fails — reproduce, isolate, fix, verify
193
+ you > /investigate why 500s — evidence trail to root cause
194
+ you > /health — code quality score (tests, types, lint)
195
+ you > /audit — secrets scan, dependency CVEs, injection
196
+ you > /ship — diff review + commit message
197
+ you > /land-and-deploy — push, PR, CI wait, merge
198
+ you > /remember — save a project convention to memory
199
+ you > /retro — weekly velocity and pattern analysis
200
+ ```
125
201
 
126
202
  ```bash
127
- phase2s skills
203
+ phase2s skills # full list with model tier badges
204
+ phase2s skills --json # machine-readable for scripts
128
205
  ```
129
206
 
207
+ [Skills reference →](docs/skills.md)
208
+
130
209
  ---
131
210
 
132
211
  ## Docs
133
212
 
134
- - [GitHub Actions](docs/github-action.md) — `uses: scanton/phase2s@v1` reference, inputs, outputs, examples
135
- - [Getting started](docs/getting-started.md) — full setup walkthrough, first session, first skill call
136
- - [Skills reference](docs/skills.md) — all 29 skills with examples and arguments
137
- - [Workflows](docs/workflows.md) — real development sessions: feature, debug, review, weekly rhythm
138
- - [Memory and persistence](docs/memory.md) — session resume, `/remember`, what Phase2S writes to disk
139
- - [Writing custom skills](docs/writing-skills.md) — SKILL.md format, frontmatter fields, examples
140
- - [Advanced](docs/advanced.md) — streaming, tool loop, model routing (requires API key)
141
- - [Claude Code integration](docs/claude-code.md) — MCP server setup, cross-model adversarial review
142
- - [Configuration](docs/configuration.md) — `.phase2s.yaml` reference, environment variables
213
+ - [Getting started](docs/getting-started.md) — first install, first session, all four provider options
214
+ - [Dark factory](docs/dark-factory.md) — write a spec, run `phase2s goal`, get a feature
215
+ - [Claude Code integration](docs/claude-code.md) — MCP setup, adversarial review, CLAUDE.md routing rules
216
+ - [Skills reference](docs/skills.md) — all 29 skills with examples
217
+ - [Workflows](docs/workflows.md) — real development sessions end to end
218
+ - [Memory and persistence](docs/memory.md) — session resume, `/remember`, what gets saved
219
+ - [Writing custom skills](docs/writing-skills.md) — create your own `/commands`
220
+ - [GitHub Action](docs/github-action.md) — `uses: scanton/phase2s@v1` for CI (requires API key)
221
+ - [Advanced](docs/advanced.md) — streaming, model routing, tool allow/deny
222
+ - [Configuration](docs/configuration.md) — `.phase2s.yaml` and environment variables
143
223
 
144
224
  ---
145
225
 
146
226
  ## Roadmap
147
227
 
148
- - [x] Codex CLI provider (uses ChatGPT subscription, no API key required)
228
+ - [x] Codex CLI provider (ChatGPT subscription, no API key required)
149
229
  - [x] 29 built-in skills across 6 categories
150
- - [x] SKILL.md compatibility with `~/.codex/skills/`
151
- - [x] Smart skill argument parsing (file paths vs. context strings)
152
- - [x] File sandbox: tools reject paths outside the project directory, including symlink escapes
153
- - [x] 389 tests covering all tools, core modules, agent integration, goal executor, and the GitHub Action (`npm test`)
154
- - [x] CI: runs `npm test` on every push and PR (GitHub Actions, Node.js 22)
155
- - [x] Direct OpenAI API provider with live tool calling
156
- - [x] Anthropic API provider — Claude 3.5 Sonnet and family, all 29 skills
157
- - [x] Ollama providerlocal models, offline, no API keys required
158
- - [x] Streaming outputresponses stream token-by-token
159
- - [x] `npm install -g @scanton/phase2s`
160
- - [x] Session persistenceauto-save after each turn, `--resume` to continue
161
- - [x] Model-per-skill routing`fast_model` / `smart_model` tiers in `.phase2s.yaml`
162
- - [x] Satori persistent execution retry loop with shell verification, context snapshots, attempt logs
163
- - [x] Consensus planningplanner + architect + critic passes
164
- - [x] Claude Code MCP integration — all skills available as Claude Code tools via `phase2s mcp`
165
- - [x] `/adversarial` skill cross-model adversarial review with structured output
166
- - [x] Persistent memory `/remember` saves learnings to `.phase2s/memory/learnings.jsonl`
167
- - [x] `/skill` meta-skill create new skills from inside Phase2S
168
- - [x] Session file security session files written with `mode: 0o600` (owner-only)
169
- - [x] `/land-and-deploy` skill — push, PR, CI wait, merge, deploy confirmation via `gh` CLI
170
- - [x] Model tier badges in `phase2s skills` output — `[fast]` / `[smart]` per skill at a glance
171
- - [x] `phase2s run --dry-run "/explain foo"` — preview skill routing without executing
172
- - [x] Typed input hints in REPL — boolean shows `(yes/no)`, enum shows `[option1/option2]`
173
- - [x] `phase2s skills --json` machine-readable skill list for scripts and tooling
174
- - [x] Clean installno deprecation warnings from `npm install -g @scanton/phase2s`
175
- - [x] npm publish`@scanton/phase2s` on npm, `npm install -g @scanton/phase2s`
176
- - [x] Multi-turn skills `{{ASK: question?}}` inline prompts in SKILL.md templates
177
- - [x] Shell completion — `eval "$(phase2s completion bash)"` for tab-complete in bash/zsh
178
- - [x] Tool allow/deny — `tools:` and `deny:` in `.phase2s.yaml` restrict agent tool access
179
- - [x] Headless browser tool — navigate, click, type, screenshot, evaluate JS via Playwright (opt-in: `browser: true`)
180
- - [x] GitHub Action — `uses: scanton/phase2s@v1` runs any skill in CI, posts results as PR comments and Step Summaries (requires Anthropic or OpenAI API key — ChatGPT subscription doesn't work in CI)
181
- - [x] `phase2s goal <spec.md>` — dark factory mode: give Phase2S a spec, have it execute sub-tasks, run evals, and retry until acceptance criteria pass (uses ChatGPT subscription)
182
- - [x] 5-pillar spec format — `/deep-specify` outputs structured specs consumable by `phase2s goal`
183
- - [ ] Real Codex streaming (JSONL stdout parsing)
230
+ - [x] File sandbox: tools reject paths outside project directory, including symlink escapes
231
+ - [x] 389 tests covering all tools, core modules, agent integration, and goal executor
232
+ - [x] CI: runs `npm test` on every push and PR
233
+ - [x] OpenAI API provider with live tool calling
234
+ - [x] Anthropic API provider Claude 3.5 Sonnet and family
235
+ - [x] Ollama provider local models, offline, no API keys
236
+ - [x] Streaming output
237
+ - [x] Session persistenceauto-save + `--resume`
238
+ - [x] Model-per-skill routing`fast_model` / `smart_model` tiers
239
+ - [x] Satori persistent execution — retry loop with shell verification
240
+ - [x] Consensus planningplanner + architect + critic
241
+ - [x] Claude Code MCP integration all skills as Claude Code tools
242
+ - [x] `/adversarial`cross-model adversarial review
243
+ - [x] Persistent memory`/remember` + auto-inject into sessions
244
+ - [x] `/skill` create new skills from inside Phase2S
245
+ - [x] `/land-and-deploy` — push, PR, CI wait, merge
246
+ - [x] Model tier badges in `phase2s skills` output
247
+ - [x] `--dry-run` for skill routing preview
248
+ - [x] Typed input hints in REPL
249
+ - [x] `phase2s skills --json`
250
+ - [x] Shell completion `eval "$(phase2s completion bash)"`
251
+ - [x] Tool allow/deny in `.phase2s.yaml`
252
+ - [x] Headless browser tool via Playwright
253
+ - [x] GitHub Action`uses: scanton/phase2s@v1`
254
+ - [x] `phase2s goal <spec.md>` dark factory: spec in, feature out
255
+ - [x] 5-pillar spec format `/deep-specify` output feeds directly into `phase2s goal`
256
+ - [x] Real Codex streaming (JSONL stdout parsing) step-by-step feedback for multi-step tasks
184
257
 
185
258
  ---
186
259
 
@@ -5,24 +5,21 @@ import type { OpenAIFunctionDef } from "../tools/types.js";
5
5
  * Codex CLI provider.
6
6
  *
7
7
  * Uses `codex exec` in fully non-interactive scripting mode:
8
- * --json suppresses the terminal UI (outputs JSONL instead)
9
- * --output-last-message writes the final response to a temp file
8
+ * --json suppresses the terminal UI, outputs JSONL events on stdout
10
9
  *
11
- * This means codex never needs to open /dev/tty, so it cannot corrupt
12
- * the parent process's terminal/readline session.
10
+ * Each `item.completed` event with `type: "agent_message"` is yielded immediately
11
+ * as a `{ type: "text" }` ProviderEvent. Multi-step tasks (with tool calls) produce
12
+ * multiple agent_message items, so callers see real-time step-by-step progress rather
13
+ * than waiting for the entire run to finish.
13
14
  *
14
- * Real Codex streaming is deferred — the JSONL stdout format is undocumented.
15
- * `chatStream()` wraps `_chat()` in a passthrough single-event generator
16
- * (same batch UX as before, but through the Provider streaming interface).
17
- * Tool calling is not supported via the --output-last-message mechanism;
18
- * toolCalls is always [].
15
+ * Malformed JSONL lines are silently skipped.
16
+ * Tool calling is not surfaced via this mechanism; toolCalls is always [].
19
17
  */
20
18
  export declare class CodexProvider implements Provider {
21
19
  name: string;
22
20
  private codexPath;
23
21
  private model;
24
22
  constructor(config: Config);
25
- chatStream(messages: Message[], tools: OpenAIFunctionDef[], options?: import("./types.js").ChatStreamOptions): AsyncIterable<ProviderEvent>;
26
- private _chat;
23
+ chatStream(messages: Message[], _tools: OpenAIFunctionDef[], options?: import("./types.js").ChatStreamOptions): AsyncIterable<ProviderEvent>;
27
24
  }
28
25
  //# sourceMappingURL=codex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAY,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAsC3D;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,IAAI,SAAe;IACnB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM;IAKnB,UAAU,CACf,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,iBAAiB,EAAE,EAC1B,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,iBAAiB,GAC/C,aAAa,CAAC,aAAa,CAAC;YAejB,KAAK;CAmGpB"}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAkD3D;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAc,YAAW,QAAQ;IAC5C,IAAI,SAAe;IACnB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAEV,MAAM,EAAE,MAAM;IAKnB,UAAU,CACf,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,iBAAiB,EAAE,EAC3B,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,iBAAiB,GAC/C,aAAa,CAAC,aAAa,CAAC;CAuIhC"}
@@ -1,56 +1,20 @@
1
1
  import { spawn } from "node:child_process";
2
- import { rmSync } from "node:fs";
3
- import { mkdtemp, readFile, rm } from "node:fs/promises";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- /** Track all temp dirs created this process so we can clean up on crash/exit. */
7
- const activeTempDirs = new Set();
8
- function cleanupTempDirs() {
9
- // Synchronous cleanup — rmSync is available here (unlike the async rm).
10
- for (const dir of activeTempDirs) {
11
- try {
12
- rmSync(dir, { recursive: true, force: true });
13
- }
14
- catch {
15
- // Best-effort — ignore errors (e.g. already deleted by normal path)
16
- }
17
- }
18
- }
19
- /**
20
- * Guard against double-registration if the module is evaluated multiple times.
21
- * In vitest environments, modules may be re-evaluated between test files, which
22
- * would register duplicate handlers and trigger MaxListenersExceededWarning.
23
- */
24
- let _signalHandlersRegistered = false;
25
- if (!_signalHandlersRegistered) {
26
- _signalHandlersRegistered = true;
27
- process.on("exit", cleanupTempDirs);
28
- // SIGTERM and SIGINT don't trigger "exit" automatically — register them explicitly
29
- // so that temp dirs (which may contain prompt text) are cleaned up on Ctrl+C and kill.
30
- process.on("SIGTERM", () => {
31
- cleanupTempDirs();
32
- process.exit(0);
33
- });
34
- process.on("SIGINT", () => {
35
- cleanupTempDirs();
36
- process.exit(0);
37
- });
2
+ function isAgentMessage(item) {
3
+ return item.type === "agent_message" && "text" in item;
38
4
  }
39
5
  /**
40
6
  * Codex CLI provider.
41
7
  *
42
8
  * Uses `codex exec` in fully non-interactive scripting mode:
43
- * --json suppresses the terminal UI (outputs JSONL instead)
44
- * --output-last-message writes the final response to a temp file
9
+ * --json suppresses the terminal UI, outputs JSONL events on stdout
45
10
  *
46
- * This means codex never needs to open /dev/tty, so it cannot corrupt
47
- * the parent process's terminal/readline session.
11
+ * Each `item.completed` event with `type: "agent_message"` is yielded immediately
12
+ * as a `{ type: "text" }` ProviderEvent. Multi-step tasks (with tool calls) produce
13
+ * multiple agent_message items, so callers see real-time step-by-step progress rather
14
+ * than waiting for the entire run to finish.
48
15
  *
49
- * Real Codex streaming is deferred — the JSONL stdout format is undocumented.
50
- * `chatStream()` wraps `_chat()` in a passthrough single-event generator
51
- * (same batch UX as before, but through the Provider streaming interface).
52
- * Tool calling is not supported via the --output-last-message mechanism;
53
- * toolCalls is always [].
16
+ * Malformed JSONL lines are silently skipped.
17
+ * Tool calling is not surfaced via this mechanism; toolCalls is always [].
54
18
  */
55
19
  export class CodexProvider {
56
20
  name = "codex-cli";
@@ -60,22 +24,8 @@ export class CodexProvider {
60
24
  this.codexPath = config.codexPath;
61
25
  this.model = config.model;
62
26
  }
63
- async *chatStream(messages, tools, options) {
64
- const result = await this._chat(messages, tools, options?.model);
65
- if (result.text) {
66
- yield { type: "text", content: result.text };
67
- }
68
- // Codex provider currently always returns toolCalls: [] — tool calling
69
- // is not supported via the --output-last-message mechanism.
70
- if (result.toolCalls.length > 0) {
71
- yield { type: "tool_calls", calls: result.toolCalls };
72
- yield { type: "done", stopReason: "tool_calls" };
73
- }
74
- else {
75
- yield { type: "done", stopReason: "stop" };
76
- }
77
- }
78
- async _chat(messages, _tools, modelOverride) {
27
+ async *chatStream(messages, _tools, options) {
28
+ const model = options?.model ?? this.model;
79
29
  // Build the full prompt: system context + conversation history
80
30
  const parts = [];
81
31
  const systemMessages = messages.filter((m) => m.role === "system");
@@ -83,8 +33,7 @@ export class CodexProvider {
83
33
  parts.push(systemMessages.map((m) => m.content).join("\n"));
84
34
  parts.push("---");
85
35
  }
86
- const nonSystem = messages.filter((m) => m.role !== "system");
87
- for (const msg of nonSystem) {
36
+ for (const msg of messages.filter((m) => m.role !== "system")) {
88
37
  if (msg.role === "user") {
89
38
  parts.push(`User: ${msg.content}`);
90
39
  }
@@ -93,70 +42,118 @@ export class CodexProvider {
93
42
  }
94
43
  }
95
44
  const prompt = parts.join("\n\n");
96
- // Use a temp dir for the output file so we don't pollute the project
97
- const tmpDir = await mkdtemp(join(tmpdir(), "phase2s-"));
98
- activeTempDirs.add(tmpDir);
99
- const outputFile = join(tmpDir, "last-message.txt");
100
- // codex exec --json suppresses the interactive UI (no /dev/tty access)
101
- // --output-last-message writes the final response to a file we control
102
- //
103
- // The "--" separator signals end-of-flags to codex's own arg parser.
104
- // Without it, a prompt beginning with "--" (e.g. "--help" or "--flags")
105
- // would be misinterpreted as a codex CLI flag rather than as the prompt.
106
- // spawn() with an array is NOT shell-injected, so this is the only risk.
45
+ // codex exec --json suppresses the interactive UI and outputs JSONL on stdout.
46
+ // The "--" separator signals end-of-flags so prompts beginning with "--" are safe.
47
+ // spawn() with an array is NOT shell-injected, so this is the only prompt-injection risk.
107
48
  const args = [
108
49
  "exec",
109
- "-m", modelOverride ?? this.model,
50
+ "-m", model,
110
51
  "--full-auto",
111
52
  "-C", process.cwd(),
112
53
  "--json",
113
- "--output-last-message", outputFile,
114
54
  "--",
115
55
  prompt,
116
56
  ];
117
- return new Promise((resolve, reject) => {
118
- const proc = spawn(this.codexPath, args, {
119
- stdio: ["ignore", "pipe", "pipe"],
120
- env: {
121
- ...process.env,
122
- NO_COLOR: "1",
123
- FORCE_COLOR: "0",
124
- },
125
- });
126
- let stderr = "";
127
- // Consume stdout (JSONL events) so the pipe buffer never fills and
128
- // blocks codex. We don't parse it — we use --output-last-message instead.
129
- proc.stdout.resume();
130
- proc.stderr.on("data", (data) => {
131
- stderr += data.toString();
132
- });
133
- proc.on("close", async (code) => {
134
- // Try reading the output file first (most reliable)
135
- try {
136
- const text = await readFile(outputFile, "utf-8");
137
- await rm(tmpDir, { recursive: true }).catch(() => { });
138
- activeTempDirs.delete(tmpDir);
139
- resolve({ text: text.trim(), toolCalls: [] });
140
- return;
141
- }
142
- catch {
143
- // Output file missing — fall through to error handling
144
- }
145
- await rm(tmpDir, { recursive: true }).catch(() => { });
146
- activeTempDirs.delete(tmpDir);
147
- if (code !== 0) {
148
- reject(new Error(`Codex exited with code ${code}: ${stderr.trim()}`));
149
- return;
150
- }
151
- // Unexpected: exited 0 but no output file
152
- reject(new Error("Codex produced no output"));
153
- });
154
- proc.on("error", async (err) => {
155
- await rm(tmpDir, { recursive: true }).catch(() => { });
156
- activeTempDirs.delete(tmpDir);
157
- reject(new Error(`Failed to spawn codex: ${err.message}`));
158
- });
57
+ const proc = spawn(this.codexPath, args, {
58
+ stdio: ["ignore", "pipe", "pipe"],
59
+ env: {
60
+ ...process.env,
61
+ NO_COLOR: "1",
62
+ FORCE_COLOR: "0",
63
+ },
159
64
  });
65
+ // -------------------------------------------------------------------------
66
+ // Async queue: bridges the event-emitter world → async generator.
67
+ //
68
+ // We can't `yield` from inside event handlers, so we push events into a
69
+ // queue and use a one-shot resolve callback to wake the generator loop.
70
+ // -------------------------------------------------------------------------
71
+ const pendingEvents = [];
72
+ let finished = false;
73
+ let finishError = null;
74
+ let wakeUp = null;
75
+ let hasProducedText = false;
76
+ const push = (event) => {
77
+ pendingEvents.push(event);
78
+ wakeUp?.();
79
+ wakeUp = null;
80
+ };
81
+ const finish = (err) => {
82
+ if (finished)
83
+ return; // Guard: called at most once
84
+ finished = true;
85
+ finishError = err ?? null;
86
+ wakeUp?.();
87
+ wakeUp = null;
88
+ };
89
+ // JSONL line parser — called for each complete line from stdout
90
+ const processLine = (line) => {
91
+ if (!line)
92
+ return;
93
+ let evt;
94
+ try {
95
+ evt = JSON.parse(line);
96
+ }
97
+ catch {
98
+ // Silent fallback: malformed JSONL line, skip it
99
+ return;
100
+ }
101
+ if (evt.type === "item.completed" && isAgentMessage(evt.item)) {
102
+ hasProducedText = true;
103
+ push({ type: "text", content: evt.item.text });
104
+ }
105
+ else if (evt.type === "error") {
106
+ finish(new Error(`Codex error: ${evt.message}`));
107
+ }
108
+ };
109
+ // Stream stdout, splitting on newlines as chunks arrive
110
+ let lineBuffer = "";
111
+ proc.stdout.on("data", (chunk) => {
112
+ lineBuffer += chunk.toString();
113
+ let newlineIdx;
114
+ while ((newlineIdx = lineBuffer.indexOf("\n")) !== -1) {
115
+ processLine(lineBuffer.slice(0, newlineIdx).trim());
116
+ lineBuffer = lineBuffer.slice(newlineIdx + 1);
117
+ }
118
+ });
119
+ // Consume stderr so the pipe buffer never fills and blocks codex.
120
+ proc.stderr.resume();
121
+ proc.on("close", (code) => {
122
+ if (finished)
123
+ return;
124
+ // Flush any remaining content without a trailing newline
125
+ const remaining = lineBuffer.trim();
126
+ if (remaining)
127
+ processLine(remaining);
128
+ if (!hasProducedText && code !== 0) {
129
+ finish(new Error(`Codex exited with code ${code ?? "null"}`));
130
+ }
131
+ else if (!hasProducedText) {
132
+ finish(new Error("Codex produced no output"));
133
+ }
134
+ else {
135
+ finish();
136
+ }
137
+ });
138
+ proc.on("error", (err) => {
139
+ finish(new Error(`Failed to spawn codex: ${err.message}`));
140
+ });
141
+ // Generator loop: drain the queue, wait for more, repeat until done
142
+ while (true) {
143
+ while (pendingEvents.length > 0) {
144
+ yield pendingEvents.shift();
145
+ }
146
+ if (finished) {
147
+ if (finishError)
148
+ throw finishError;
149
+ yield { type: "done", stopReason: "stop" };
150
+ return;
151
+ }
152
+ // Wait for the next push() or finish() call
153
+ await new Promise((resolve) => {
154
+ wakeUp = resolve;
155
+ });
156
+ }
160
157
  }
161
158
  }
162
159
  //# sourceMappingURL=codex.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../../src/providers/codex.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scanton/phase2s",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "AI programming harness with multi-model support",
5
5
  "type": "module",
6
6
  "files": [