@wkronmiller/lisa 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +407 -0
  3. package/bin/lisa-runtime.js +8797 -0
  4. package/bin/lisa.js +21 -0
  5. package/completion.ts +58 -0
  6. package/install.ps1 +51 -0
  7. package/install.sh +93 -0
  8. package/lisa.ts +6 -0
  9. package/package.json +66 -0
  10. package/skills/README.md +28 -0
  11. package/skills/claude-code/CLAUDE.md +151 -0
  12. package/skills/codex/AGENTS.md +151 -0
  13. package/skills/gemini/GEMINI.md +151 -0
  14. package/skills/opencode/AGENTS.md +152 -0
  15. package/src/cli.ts +85 -0
  16. package/src/harness/base-adapter.ts +47 -0
  17. package/src/harness/claude-code.ts +106 -0
  18. package/src/harness/codex.ts +80 -0
  19. package/src/harness/command.ts +173 -0
  20. package/src/harness/gemini.ts +74 -0
  21. package/src/harness/opencode.ts +84 -0
  22. package/src/harness/registry.ts +29 -0
  23. package/src/harness/runner.ts +19 -0
  24. package/src/harness/types.ts +73 -0
  25. package/src/output-mode.ts +32 -0
  26. package/src/skill/artifacts.ts +174 -0
  27. package/src/skill/cli.ts +29 -0
  28. package/src/skill/install.ts +317 -0
  29. package/src/spec/agent-guidance.ts +466 -0
  30. package/src/spec/cli.ts +151 -0
  31. package/src/spec/commands/check.ts +1 -0
  32. package/src/spec/commands/config.ts +146 -0
  33. package/src/spec/commands/diff.ts +1 -0
  34. package/src/spec/commands/generate.ts +1 -0
  35. package/src/spec/commands/guide.ts +1 -0
  36. package/src/spec/commands/harness-list.ts +36 -0
  37. package/src/spec/commands/implement.ts +1 -0
  38. package/src/spec/commands/import.ts +1 -0
  39. package/src/spec/commands/init.ts +1 -0
  40. package/src/spec/commands/status.ts +87 -0
  41. package/src/spec/config.ts +63 -0
  42. package/src/spec/diff.ts +791 -0
  43. package/src/spec/extensions/benchmark.ts +347 -0
  44. package/src/spec/extensions/registry.ts +59 -0
  45. package/src/spec/extensions/types.ts +56 -0
  46. package/src/spec/grammar/index.ts +14 -0
  47. package/src/spec/grammar/parser.ts +443 -0
  48. package/src/spec/grammar/types.ts +70 -0
  49. package/src/spec/grammar/validator.ts +104 -0
  50. package/src/spec/loader.ts +174 -0
  51. package/src/spec/local-config.ts +59 -0
  52. package/src/spec/parser.ts +226 -0
  53. package/src/spec/path-utils.ts +73 -0
  54. package/src/spec/planner.ts +299 -0
  55. package/src/spec/prompt-renderer.ts +318 -0
  56. package/src/spec/skill-content.ts +119 -0
  57. package/src/spec/types.ts +239 -0
  58. package/src/spec/validator.ts +443 -0
  59. package/src/spec/workflows/check.ts +1534 -0
  60. package/src/spec/workflows/diff.ts +209 -0
  61. package/src/spec/workflows/generate.ts +1270 -0
  62. package/src/spec/workflows/guide.ts +190 -0
  63. package/src/spec/workflows/implement.ts +797 -0
  64. package/src/spec/workflows/import.ts +986 -0
  65. package/src/spec/workflows/init.ts +548 -0
  66. package/src/spec/workflows/status.ts +22 -0
  67. package/src/spec/workspace.ts +541 -0
  68. package/uninstall.ps1 +21 -0
  69. package/uninstall.sh +22 -0
@@ -0,0 +1,151 @@
1
+ # Lisa for Gemini CLI
2
+
3
+ Use this skill when working on Lisa from a Gemini CLI session.
4
+
5
+ ## What Lisa is
6
+
7
+ Lisa is a spec-driven agent orchestration CLI. Treat `.specs/` as the source of truth for intended behavior.
8
+
9
+ Core workflow:
10
+
11
+ ```text
12
+ specs -> git diff -> structured deltas -> plan -> single writer harness -> tests/benchmarks -> parallel read-only verification -> reports
13
+ ```
14
+
15
+ ## Working in this repo
16
+
17
+ - Start with `lisa spec status` to inspect the workspace and harness availability.
18
+ - Review active specs in `.specs/backend/` and `.specs/frontend/` before changing behavior.
19
+ - Prefer repo-local Lisa commands over ad hoc workflows that bypass the spec-first model.
20
+ - When behavior changes, update the relevant spec, code, tests, and checked-in docs or skills together.
21
+
22
+ If the local `lisa` command is not installed yet, run the same workflows from the checkout with `bun run lisa.ts spec ...`.
23
+
24
+ ## Current CLI surface
25
+
26
+ ```text
27
+ lisa skill install --global|--local
28
+ lisa spec init
29
+ lisa spec config [options]
30
+ lisa spec status
31
+ lisa spec harness list
32
+ lisa spec guide [backend|frontend] [name] [options]
33
+ lisa spec generate [backend|frontend] [name] [options]
34
+ lisa spec implement [options]
35
+ lisa spec check --changed|--all [options]
36
+ lisa spec import <path...> [options]
37
+ lisa spec diff [options]
38
+ ```
39
+
40
+ Common repo-local commands:
41
+
42
+ ```bash
43
+ lisa skill install --local
44
+ lisa skill install --global
45
+ lisa spec config --harness gemini
46
+ lisa spec status
47
+ lisa spec harness list
48
+ lisa spec guide backend checkout-flow
49
+ lisa spec generate backend checkout-flow
50
+ lisa spec diff
51
+ lisa spec implement
52
+ lisa spec check --changed
53
+ lisa spec check --all
54
+ ```
55
+
56
+ ## Installing Lisa guidance
57
+
58
+ ```bash
59
+ lisa skill install --local
60
+ lisa skill install --global
61
+ ```
62
+
63
+ - Local install target: `GEMINI.md`
64
+ - Global install target: `~/.gemini/GEMINI.md`
65
+
66
+ ## Seed guide workflow
67
+
68
+ - Run `lisa spec guide backend checkout-flow` when you want Lisa to create or reuse a draft seed spec and point you at the right harness-specific skill artifact.
69
+ - Keep editing the seed spec in `.specs/backend/` or `.specs/frontend/` until the placeholders are replaced with real Summary, Use Cases, Invariants, Failure Modes, Acceptance Criteria, and Out of Scope content.
70
+ - After the spec is ready, return to `lisa spec diff`, `lisa spec implement`, and `lisa spec check --changed`.
71
+
72
+ ## Spec layout
73
+
74
+ ```text
75
+ .specs/
76
+ backend/
77
+ <spec-name>.md
78
+ <spec-name>.bench.<environment>.md
79
+ frontend/
80
+ <spec-name>.md
81
+ <spec-name>.bench.<environment>.md
82
+ environments/
83
+ <environment>.yaml
84
+ config.yaml
85
+ ```
86
+
87
+ Create backend specs in `.specs/backend/` and frontend specs in `.specs/frontend/`.
88
+ Keep environment definitions in `.specs/environments/` and workspace harness settings in `.specs/config.yaml`.
89
+
90
+ ## Spec editing rules
91
+
92
+ Preserve YAML frontmatter and required sections when creating or updating an active base spec.
93
+
94
+ ```md
95
+ ---
96
+ id: backend.example-spec
97
+ status: active
98
+ code_paths:
99
+ - src/example/**
100
+ test_paths:
101
+ - tests/example/**
102
+ test_commands:
103
+ - bun test ./tests
104
+ ---
105
+
106
+ # Summary
107
+
108
+ ## Use Cases
109
+
110
+ ## Invariants
111
+
112
+ ## Failure Modes
113
+
114
+ ## Acceptance Criteria
115
+
116
+ ## Out of Scope
117
+ ```
118
+
119
+ Notes:
120
+
121
+ - `id`, `status`, and `code_paths` are required in frontmatter.
122
+ - Include at least one of `test_paths` or `test_commands`.
123
+ - `# Summary`, `## Use Cases`, `## Invariants`, `## Acceptance Criteria`, and `## Out of Scope` are required for active base specs.
124
+ - `lisa spec generate` also emits `## Failure Modes`, and planning uses it, so keep that section intact when present.
125
+ - Benchmark sidecars belong next to the base spec as `<spec-name>.bench.<environment>.md`.
126
+
127
+ ## Guardrails
128
+
129
+ - Do not treat spec deletion as permission to remove code silently.
130
+ - Keep edits inside the mapped repo paths unless a small supporting change is required.
131
+ - Prefer the checked-in workflow in this repository over global harness-specific shortcuts.
132
+
133
+ ## Aborting Operations
134
+
135
+ If you detect that you cannot complete a task due to missing tools or capabilities (e.g., no web browser for UI changes, no file system access for file modifications), you should abort gracefully rather than looping on a task you cannot complete.
136
+
137
+ To abort, output one of these formats as your final response:
138
+
139
+ ```
140
+ LISA_ABORT: <reason>
141
+ ```
142
+
143
+ Or for multi-line reasons:
144
+
145
+ ```
146
+ LISA_ABORT_START
147
+ <reason>
148
+ LISA_ABORT_END
149
+ ```
150
+
151
+ The workflow will detect the abort signal, stop execution, and report the reason to the user. This prevents wasted time and provides clear feedback about why the operation could not be completed.
@@ -0,0 +1,152 @@
1
+ # Lisa for OpenCode
2
+
3
+ Use this skill when working on Lisa from an OpenCode session.
4
+
5
+ ## What Lisa is
6
+
7
+ Lisa is a spec-driven agent orchestration CLI. Treat `.specs/` as the source of truth for intended behavior.
8
+
9
+ Core workflow:
10
+
11
+ ```text
12
+ specs -> git diff -> structured deltas -> plan -> single writer harness -> tests/benchmarks -> parallel read-only verification -> reports
13
+ ```
14
+
15
+ ## Working in this repo
16
+
17
+ - Start with `lisa spec status` to inspect the workspace and harness availability.
18
+ - Review active specs in `.specs/backend/` and `.specs/frontend/` before changing behavior.
19
+ - Prefer repo-local Lisa commands over ad hoc workflows that bypass the spec-first model.
20
+ - When behavior changes, update the relevant spec, code, tests, and checked-in docs or skills together.
21
+
22
+ If the local `lisa` command is not installed yet, run the same workflows from the checkout with `bun run lisa.ts spec ...`.
23
+
24
+ ## Current CLI surface
25
+
26
+ ```text
27
+ lisa skill install --global|--local
28
+ lisa spec init
29
+ lisa spec config [options]
30
+ lisa spec status
31
+ lisa spec harness list
32
+ lisa spec guide [backend|frontend] [name] [options]
33
+ lisa spec generate [backend|frontend] [name] [options]
34
+ lisa spec implement [options]
35
+ lisa spec check --changed|--all [options]
36
+ lisa spec import <path...> [options]
37
+ lisa spec diff [options]
38
+ ```
39
+
40
+ Common repo-local commands:
41
+
42
+ ```bash
43
+ lisa skill install --local
44
+ lisa skill install --global
45
+ lisa spec config --harness opencode
46
+ lisa spec status
47
+ lisa spec harness list
48
+ lisa spec guide backend checkout-flow
49
+ lisa spec generate backend checkout-flow
50
+ lisa spec diff
51
+ lisa spec implement
52
+ lisa spec check --changed
53
+ lisa spec check --all
54
+ ```
55
+
56
+ ## Installing Lisa guidance
57
+
58
+ ```bash
59
+ lisa skill install --local
60
+ lisa skill install --global
61
+ ```
62
+
63
+ - Local install target: `.opencode/skills/lisa/SKILL.md`
64
+ - Global install target: `${XDG_CONFIG_HOME:-~/.config}/opencode/skills/lisa/SKILL.md`
65
+ - The installed `SKILL.md` wraps the checked-in Lisa guidance with the small OpenCode skill frontmatter.
66
+
67
+ ## Seed guide workflow
68
+
69
+ - Run `lisa spec guide backend checkout-flow` when you want Lisa to create or reuse a draft seed spec and point you at the right harness-specific skill artifact.
70
+ - Keep editing the seed spec in `.specs/backend/` or `.specs/frontend/` until the placeholders are replaced with real Summary, Use Cases, Invariants, Failure Modes, Acceptance Criteria, and Out of Scope content.
71
+ - After the spec is ready, return to `lisa spec diff`, `lisa spec implement`, and `lisa spec check --changed`.
72
+
73
+ ## Spec layout
74
+
75
+ ```text
76
+ .specs/
77
+ backend/
78
+ <spec-name>.md
79
+ <spec-name>.bench.<environment>.md
80
+ frontend/
81
+ <spec-name>.md
82
+ <spec-name>.bench.<environment>.md
83
+ environments/
84
+ <environment>.yaml
85
+ config.yaml
86
+ ```
87
+
88
+ Create backend specs in `.specs/backend/` and frontend specs in `.specs/frontend/`.
89
+ Keep environment definitions in `.specs/environments/` and workspace harness settings in `.specs/config.yaml`.
90
+
91
+ ## Spec editing rules
92
+
93
+ Preserve YAML frontmatter and required sections when creating or updating an active base spec.
94
+
95
+ ```md
96
+ ---
97
+ id: backend.example-spec
98
+ status: active
99
+ code_paths:
100
+ - src/example/**
101
+ test_paths:
102
+ - tests/example/**
103
+ test_commands:
104
+ - bun test ./tests
105
+ ---
106
+
107
+ # Summary
108
+
109
+ ## Use Cases
110
+
111
+ ## Invariants
112
+
113
+ ## Failure Modes
114
+
115
+ ## Acceptance Criteria
116
+
117
+ ## Out of Scope
118
+ ```
119
+
120
+ Notes:
121
+
122
+ - `id`, `status`, and `code_paths` are required in frontmatter.
123
+ - Include at least one of `test_paths` or `test_commands`.
124
+ - `# Summary`, `## Use Cases`, `## Invariants`, `## Acceptance Criteria`, and `## Out of Scope` are required for active base specs.
125
+ - `lisa spec generate` also emits `## Failure Modes`, and planning uses it, so keep that section intact when present.
126
+ - Benchmark sidecars belong next to the base spec as `<spec-name>.bench.<environment>.md`.
127
+
128
+ ## Guardrails
129
+
130
+ - Do not treat spec deletion as permission to remove code silently.
131
+ - Keep edits inside the mapped repo paths unless a small supporting change is required.
132
+ - Prefer the checked-in workflow in this repository over global harness-specific shortcuts.
133
+
134
+ ## Aborting Operations
135
+
136
+ If you detect that you cannot complete a task due to missing tools or capabilities (e.g., no web browser for UI changes, no file system access for file modifications), you should abort gracefully rather than looping on a task you cannot complete.
137
+
138
+ To abort, output one of these formats as your final response:
139
+
140
+ ```
141
+ LISA_ABORT: <reason>
142
+ ```
143
+
144
+ Or for multi-line reasons:
145
+
146
+ ```
147
+ LISA_ABORT_START
148
+ <reason>
149
+ LISA_ABORT_END
150
+ ```
151
+
152
+ The workflow will detect the abort signal, stop execution, and report the reason to the user. This prevents wasted time and provides clear feedback about why the operation could not be completed.
package/src/cli.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { resolveOutputMode } from "./output-mode";
2
+ import { runSkillCli } from "./skill/cli";
3
+ import { runSpecCli } from "./spec/cli";
4
+
5
+ const VERSION = "1.2.2";
6
+
7
+ function printLisaHelp(): void {
8
+ const outputMode = resolveOutputMode();
9
+ const interactive = outputMode === "interactive";
10
+
11
+ const header = interactive
12
+ ? `Lisa turns markdown specs into implementation work, then verifies code, tests, and benchmark budgets against those specs.
13
+
14
+ Workflow:
15
+ 1. init Scaffold a .specs workspace for the repo
16
+ 2. generate or import Create or draft specs for the behavior you want
17
+ 3. implement Apply code and test changes from changed specs
18
+ 4. check Verify specs against code, tests, and benchmarks
19
+
20
+ Override output mode with LISA_OUTPUT_MODE=interactive or LISA_OUTPUT_MODE=concise.
21
+
22
+ `
23
+ : "Lisa - spec-driven agent orchestration\n\n";
24
+
25
+ console.log(`${header}Usage:
26
+ lisa <command>
27
+
28
+ Commands:
29
+ skill Install Lisa guidance for supported harnesses
30
+ spec Run Lisa spec workflows
31
+
32
+ Top-level options:
33
+ --help, -h Show Lisa help
34
+ --version, -v Show Lisa version
35
+
36
+ Environment:
37
+ LISA_OUTPUT_MODE=interactive|concise Override help/install copy mode
38
+
39
+ ${interactive ? `Typical flow:
40
+ 1. lisa spec init
41
+ Create the .specs workspace for this repo.
42
+ 2. lisa skill install --local
43
+ Optionally install repo-local Lisa guidance for supported harnesses.
44
+ 3. lisa spec generate backend <name>
45
+ Write a new spec for planned work.
46
+ or lisa spec import <path...>
47
+ Draft specs from existing code.
48
+ 4. lisa spec implement
49
+ Turn changed specs into code and test updates.
50
+ 5. lisa spec check --changed
51
+ Verify mapped tests, benchmarks, and spec compliance.
52
+ ` : ""}
53
+
54
+ Examples:
55
+ lisa skill install --local
56
+ lisa skill install --global
57
+ lisa spec status
58
+ lisa spec harness list
59
+ lisa spec guide backend checkout-flow
60
+ `);
61
+ }
62
+
63
+ export async function runCli(args: string[], cwd = process.cwd()): Promise<number> {
64
+ if (args.includes("--version") || args.includes("-v")) {
65
+ console.log(`lisa ${VERSION}`);
66
+ return 0;
67
+ }
68
+
69
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h" || args[0] === "help") {
70
+ printLisaHelp();
71
+ return 0;
72
+ }
73
+
74
+ if (args[0] === "spec") {
75
+ return runSpecCli(args.slice(1), cwd);
76
+ }
77
+
78
+ if (args[0] === "skill") {
79
+ return runSkillCli(args.slice(1), cwd);
80
+ }
81
+
82
+ console.error(`Unknown command: ${args[0]}`);
83
+ printLisaHelp();
84
+ return 1;
85
+ }
@@ -0,0 +1,47 @@
1
+ import { readFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ import { installSkillArtifact, resolveLocalSkillWorkspace, resolveSkillArtifactPath } from "../skill/artifacts";
5
+ import { detectHarnessCommand } from "./command";
6
+ import type { HarnessAvailability, HarnessSkillInstallDefinition, SkillInstallScope } from "./types";
7
+
8
+ export interface BaseAdapterConfig {
9
+ harnessId: string;
10
+ commandName: string;
11
+ envVars: string[];
12
+ artifactName: string;
13
+ resolveGlobalRoot: () => string;
14
+ resolveGlobalDestination?: () => string;
15
+ resolveLocalDestination?: (cwd: string) => string;
16
+ renderContent?: (content: string) => string;
17
+ }
18
+
19
+ export function createBaseAdapterHelpers(config: BaseAdapterConfig, cwd = process.cwd()) {
20
+ function getSkillInstallDefinition(scope: SkillInstallScope): HarnessSkillInstallDefinition {
21
+ return {
22
+ scope,
23
+ sourcePath: resolveSkillArtifactPath(config.harnessId, config.artifactName),
24
+ destinationPath: scope === "global"
25
+ ? config.resolveGlobalDestination ? config.resolveGlobalDestination() : join(config.resolveGlobalRoot(), config.artifactName)
26
+ : config.resolveLocalDestination ? config.resolveLocalDestination(cwd) : join(resolveLocalSkillWorkspace(cwd), config.artifactName),
27
+ };
28
+ }
29
+
30
+ return {
31
+ async detect(): Promise<HarnessAvailability> {
32
+ return detectHarnessCommand(config.commandName, config.envVars);
33
+ },
34
+ async getSkillInstallDefinition(scope: SkillInstallScope): Promise<HarnessSkillInstallDefinition> {
35
+ return getSkillInstallDefinition(scope);
36
+ },
37
+ async installSkill(scope: SkillInstallScope) {
38
+ const definition = getSkillInstallDefinition(scope);
39
+ const rawContent = readFileSync(definition.sourcePath, "utf8");
40
+ const content = config.renderContent ? config.renderContent(rawContent) : undefined;
41
+ return installSkillArtifact(definition, content, {
42
+ rootPath: scope === "local" ? resolveLocalSkillWorkspace(cwd) : config.resolveGlobalRoot(),
43
+ scope,
44
+ });
45
+ }
46
+ };
47
+ }
@@ -0,0 +1,106 @@
1
+ import { join } from "path";
2
+ import { resolveHomeDirectory } from "../skill/artifacts";
3
+ import { createBaseAdapterHelpers } from "./base-adapter";
4
+ import { resolveHarnessCommand, runHarnessCommand } from "./command";
5
+ import type {
6
+ HarnessAdapter,
7
+ HarnessCapabilities,
8
+ HarnessRequest,
9
+ HarnessResult,
10
+ Stage,
11
+ } from "./types";
12
+
13
+ const SUPPORTED_STAGES: Stage[] = ["generate", "implement", "check", "import"];
14
+ const CLAUDE_ENV_VARS = ["LISA_CLAUDE_BINARY", "RALPH_CLAUDE_BINARY"];
15
+
16
+ function getClaudeCodeCapabilities(): HarnessCapabilities {
17
+ return {
18
+ canEditFiles: true,
19
+ canResumeSessions: false,
20
+ supportsStructuredOutput: true,
21
+ supportsStreaming: false,
22
+ supportsModelSelection: true,
23
+ supportsReadOnlyMode: true,
24
+ };
25
+ }
26
+
27
+ function buildClaudeCodeArgs(request: HarnessRequest): string[] {
28
+ const args = ["-p", request.prompt];
29
+
30
+ if (request.model) {
31
+ args.push("--model", request.model);
32
+ }
33
+
34
+ if (request.outputSchema) {
35
+ args.push("--output-format", "json", "--json-schema", JSON.stringify(request.outputSchema));
36
+ }
37
+
38
+ if (request.allowEdits) {
39
+ args.push("--permission-mode", "bypassPermissions", "--dangerously-skip-permissions");
40
+ } else {
41
+ args.push("--permission-mode", "plan");
42
+ }
43
+
44
+ if (request.extraArgs?.length) {
45
+ args.push(...request.extraArgs);
46
+ }
47
+
48
+ return args;
49
+ }
50
+
51
+ export function createClaudeCodeHarnessAdapter(cwd = process.cwd()): HarnessAdapter {
52
+ const baseHelpers = createBaseAdapterHelpers(
53
+ {
54
+ harnessId: "claude-code",
55
+ commandName: "claude",
56
+ envVars: CLAUDE_ENV_VARS,
57
+ artifactName: "CLAUDE.md",
58
+ resolveGlobalRoot: () => join(resolveHomeDirectory(), ".claude"),
59
+ },
60
+ cwd
61
+ );
62
+
63
+ return {
64
+ id: "claude-code",
65
+ displayName: "Claude Code",
66
+ description: "Direct adapter for the Claude Code CLI.",
67
+ supportedStages: SUPPORTED_STAGES,
68
+ ...baseHelpers,
69
+ async capabilities(): Promise<HarnessCapabilities> {
70
+ return getClaudeCodeCapabilities();
71
+ },
72
+ async run(request: HarnessRequest): Promise<HarnessResult> {
73
+ const command = request.env?.LISA_HARNESS_COMMAND ?? resolveHarnessCommand("claude", CLAUDE_ENV_VARS);
74
+ if (!command) {
75
+ return {
76
+ status: "failed",
77
+ finalText: "Claude Code CLI not found.",
78
+ };
79
+ }
80
+
81
+ const result = runHarnessCommand(command, buildClaudeCodeArgs(request), request);
82
+ if (!request.outputSchema || result.status !== "success" || !result.finalText?.trim()) {
83
+ return result;
84
+ }
85
+
86
+ try {
87
+ const stdout = typeof result.rawEvents?.[0] === "object" && result.rawEvents[0] !== null && "stdout" in result.rawEvents[0]
88
+ ? (result.rawEvents[0] as { stdout?: unknown }).stdout
89
+ : undefined;
90
+ const rawJson = typeof stdout === "string" ? stdout.trim() : result.finalText;
91
+ return {
92
+ ...result,
93
+ structuredOutput: JSON.parse(rawJson),
94
+ };
95
+ } catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ const details = `Failed to parse structured output: ${message}`;
98
+ return {
99
+ ...result,
100
+ status: "failed",
101
+ finalText: [result.finalText?.trim(), details].filter((entry): entry is string => Boolean(entry)).join("\n\n"),
102
+ };
103
+ }
104
+ },
105
+ };
106
+ }
@@ -0,0 +1,80 @@
1
+ import { resolveCodexHome } from "../skill/artifacts";
2
+ import { createBaseAdapterHelpers } from "./base-adapter";
3
+ import { resolveHarnessCommand, runHarnessCommand } from "./command";
4
+ import type {
5
+ HarnessAdapter,
6
+ HarnessCapabilities,
7
+ HarnessRequest,
8
+ HarnessResult,
9
+ Stage,
10
+ } from "./types";
11
+
12
+ const SUPPORTED_STAGES: Stage[] = ["generate", "implement", "check", "import"];
13
+ const CODEX_ENV_VARS = ["LISA_CODEX_BINARY", "RALPH_CODEX_BINARY"];
14
+
15
+ function getCodexCapabilities(): HarnessCapabilities {
16
+ return {
17
+ canEditFiles: true,
18
+ canResumeSessions: false,
19
+ supportsStructuredOutput: false,
20
+ supportsStreaming: false,
21
+ supportsModelSelection: true,
22
+ supportsReadOnlyMode: true,
23
+ };
24
+ }
25
+
26
+ function buildCodexArgs(request: HarnessRequest): string[] {
27
+ const args = ["exec"];
28
+
29
+ if (request.model) {
30
+ args.push("--model", request.model);
31
+ }
32
+
33
+ if (request.allowEdits) {
34
+ args.push("--approval-mode", "full-auto", "--sandbox", "workspace-write");
35
+ } else {
36
+ args.push("--sandbox", "read-only");
37
+ }
38
+
39
+ if (request.extraArgs?.length) {
40
+ args.push(...request.extraArgs);
41
+ }
42
+
43
+ args.push(request.prompt);
44
+ return args;
45
+ }
46
+
47
+ export function createCodexHarnessAdapter(cwd = process.cwd()): HarnessAdapter {
48
+ const baseHelpers = createBaseAdapterHelpers(
49
+ {
50
+ harnessId: "codex",
51
+ commandName: "codex",
52
+ envVars: CODEX_ENV_VARS,
53
+ artifactName: "AGENTS.md",
54
+ resolveGlobalRoot: () => resolveCodexHome(),
55
+ },
56
+ cwd
57
+ );
58
+
59
+ return {
60
+ id: "codex",
61
+ displayName: "Codex",
62
+ description: "Direct adapter for the OpenAI Codex CLI.",
63
+ supportedStages: SUPPORTED_STAGES,
64
+ ...baseHelpers,
65
+ async capabilities(): Promise<HarnessCapabilities> {
66
+ return getCodexCapabilities();
67
+ },
68
+ async run(request: HarnessRequest): Promise<HarnessResult> {
69
+ const command = request.env?.LISA_HARNESS_COMMAND ?? resolveHarnessCommand("codex", CODEX_ENV_VARS);
70
+ if (!command) {
71
+ return {
72
+ status: "failed",
73
+ finalText: "Codex CLI not found.",
74
+ };
75
+ }
76
+
77
+ return runHarnessCommand(command, buildCodexArgs(request), request);
78
+ },
79
+ };
80
+ }