@williambeto/ai-workflow 2.8.2 โ†’ 2.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  ## [unreleased]
2
2
 
3
+ ## [2.9.0] - 2026-07-15
4
+
5
+ ### ๐Ÿš€ Features
6
+
7
+ - Add a Codex-native project installation with custom agents under
8
+ `.codex/agents/` and reusable skills, including commands, under
9
+ `.codex/skills/`
10
+ - Introduce runtime adapters and explicit `--runtime=opencode|codex` selection
11
+ while preserving OpenCode as the compatibility default
12
+ - Add Codex-aware doctor checks for trusted projects, native agents, skills,
13
+ policies, and CLI capabilities
14
+
15
+ ### ๐Ÿ› Bug Fixes
16
+
17
+ - Replace legacy Codex prompt and GitHub Copilot paths with documented native
18
+ Codex assets
19
+ - Preserve consumer-owned `AGENTS.md` files and install a Codex workflow
20
+ contract only when the root instructions are AIWK-managed or absent
21
+ - Block non-interactive Codex delivery when the runtime cannot prove custom
22
+ agent ownership instead of reporting unobserved Astra execution
23
+ - Keep runtime selection, delegation evidence, and bounded remediation aligned
24
+ across the OpenCode and Codex adapters
25
+
26
+ ### ๐Ÿงช Testing
27
+
28
+ - Add unit coverage for Codex agent conversion, command skills, runtime
29
+ selection, JSON event parsing, trust checks, and fail-closed execution
30
+ - Add a clean-consumer Codex init and doctor smoke test without generating
31
+ legacy prompt paths
32
+ - Validate the local release tarball in clean repositories with GPT-5.6 Sol
33
+ Medium and GPT-5.3 Codex Spark; qualify Sol for direct delivery and mark
34
+ Spark unsupported for workspace-write delivery after repeated code-only
35
+ responses without observed edits or validation
36
+
37
+ ### ๐Ÿ“š Documentation
38
+
39
+ - Reclassify Codex as experimental until an app-server E2E can prove native
40
+ delegation, workspace delivery, and observed validation
41
+ - Document project trust, native Codex asset locations, runtime selection, and
42
+ model-specific delivery limits
43
+
3
44
  ## [2.8.1] - 2026-07-14
4
45
 
5
46
  ### ๐Ÿ› Bug Fixes
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  COMPLETION_STATUS_TEXT,
4
+ CodexRuntimeAdapter,
4
5
  DelegationController,
5
6
  EvidenceLedger,
6
7
  ExecutionPlanner,
@@ -11,6 +12,7 @@ import {
11
12
  RequestClassifier,
12
13
  SpecValidator,
13
14
  WorkflowStateMachine,
15
+ createRuntimeAdapter,
14
16
  discoverPackageFiles,
15
17
  getFullAgentContent,
16
18
  getFullSkillFiles,
@@ -21,7 +23,7 @@ import {
21
23
  isTerminalFailure,
22
24
  readPackageFile,
23
25
  resolveWorkflowProfile
24
- } from "../chunk-4FI5ODAM.js";
26
+ } from "../chunk-JDSEOEYW.js";
25
27
  import {
26
28
  EvidenceValidator,
27
29
  require_ajv,
@@ -532,6 +534,7 @@ function buildAiWorkflowConfig({ profile, runtimes = ["opencode"] }) {
532
534
  const version = getPackageVersion();
533
535
  const now = (/* @__PURE__ */ new Date()).toISOString();
534
536
  const primaryRuntime = runtimes.join("+");
537
+ const codexOnly = runtimes.length === 1 && runtimes[0] === "codex";
535
538
  return {
536
539
  package: "@williambeto/ai-workflow",
537
540
  version,
@@ -540,10 +543,10 @@ function buildAiWorkflowConfig({ profile, runtimes = ["opencode"] }) {
540
543
  installMode: "project-local",
541
544
  generatedAt: now,
542
545
  paths: {
543
- agents: ".ai-workflow/opencode/agents",
544
- commands: ".ai-workflow/opencode/commands",
545
- skills: ".ai-workflow/opencode/skills",
546
- policies: ".ai-workflow/opencode/docs/policies",
546
+ agents: codexOnly ? ".codex/agents" : ".ai-workflow/opencode/agents",
547
+ commands: codexOnly ? ".codex/skills" : ".ai-workflow/opencode/commands",
548
+ skills: codexOnly ? ".codex/skills" : ".ai-workflow/opencode/skills",
549
+ policies: codexOnly ? ".codex/docs/policies" : ".ai-workflow/opencode/docs/policies",
547
550
  harness: ".ai-workflow/harness",
548
551
  schemas: ".ai-workflow/schemas"
549
552
  }
@@ -1057,167 +1060,156 @@ Every task completion MUST follow the payload format defined in the individual r
1057
1060
  // src/adapters/platforms/codex.ts
1058
1061
  import fs4 from "fs/promises";
1059
1062
  import path6 from "path";
1063
+ function parseMarkdownFrontmatter(content) {
1064
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
1065
+ if (!match) return { body: content };
1066
+ const metadata = match[1];
1067
+ const name = metadata.match(/^name:\s*["']?([^\n"']+)["']?\s*$/m)?.[1]?.trim();
1068
+ const description = metadata.match(/^description:\s*["']?([^\n"']+)["']?\s*$/m)?.[1]?.trim();
1069
+ return { body: content.slice(match[0].length), name, description };
1070
+ }
1071
+ function tomlBasicString(value) {
1072
+ return JSON.stringify(value.replace(/\r?\n/g, " "));
1073
+ }
1074
+ function tomlMultilineString(value) {
1075
+ return value.replace(/\r\n/g, "\n").replace(/"""/g, '\\"\\"\\"');
1076
+ }
1077
+ function yamlQuoted(value) {
1078
+ return JSON.stringify(value);
1079
+ }
1080
+ function commandSkillContent(content, commandName) {
1081
+ const parsed = parseMarkdownFrontmatter(content);
1082
+ const description = parsed.description || `Run the AI Workflow Kit ${commandName} command contract.`;
1083
+ return [
1084
+ "---",
1085
+ `name: ${yamlQuoted(`aiwk-command-${commandName}`)}`,
1086
+ `description: ${yamlQuoted(description)}`,
1087
+ "---",
1088
+ "",
1089
+ parsed.body.trim(),
1090
+ ""
1091
+ ].join("\n");
1092
+ }
1093
+ function codexRootInstructions() {
1094
+ return `# AI Workflow Kit for Codex
1095
+
1096
+ ## Purpose
1097
+
1098
+ Transform a natural request into a proportional, safe, and verifiable software delivery.
1099
+
1100
+ ## Required flow
1101
+
1102
+ Request \u2192 Planning \u2192 Branch Gate \u2192 Delegation when useful \u2192 Implementation \u2192 Validation \u2192 Evidence
1103
+
1104
+ ## Role ownership
1105
+
1106
+ - Atlas routes the request and coordinates the workflow.
1107
+ - Astra owns all requested workspace mutations, including work initiated from direct chat. Atlas must delegate write work to the native \`astra\` agent; it must not write on Astra's behalf.
1108
+ - Nexus owns discovery and specifications. Orion owns technical planning and release sequencing.
1109
+ - Sage performs independent validation only when risk or evidence policy requires it. Phoenix performs bounded remediation from observed findings.
1110
+ - Do not delegate simple read-only answers merely for ceremony.
1111
+
1112
+ ## Safety and completion
1113
+
1114
+ - Never implement directly on \`main\` or \`master\`.
1115
+ - Before changing files, inspect the branch and preserve unrelated dirty work.
1116
+ - Do not create a specification, evidence artifact, test, screenshot, or agent handoff unless the request or proportional policy requires it.
1117
+ - Run relevant validation after a workspace change. Do not report \`CHANGED\` or \`COMPLETED\` without observed validation evidence.
1118
+ - When \`ai-workflow execute\` is coordinating the task, its parent process owns finalization. A delegated agent must not run a second finalizer in that mode.
1119
+ - Report changed files, commands actually run, results, and limitations concisely. Do not print a full implementation in chat when it has been written to the workspace.
1120
+
1121
+ ## Native Codex resources
1122
+
1123
+ - Roles are defined in \`.codex/agents/\`.
1124
+ - Reusable skills, including AIWK commands, are defined in \`.codex/skills/\`.
1125
+ - Governance policies are available in \`.codex/docs/policies/\`.
1126
+ `;
1127
+ }
1128
+ function toCodexAgentToml(name, source) {
1129
+ const parsed = parseMarkdownFrontmatter(source);
1130
+ const agentName = (parsed.name || name).trim().toLowerCase();
1131
+ const description = parsed.description || `AI Workflow Kit ${agentName} agent.`;
1132
+ const developerInstructions = [
1133
+ parsed.body.trim(),
1134
+ "",
1135
+ "## Codex execution boundary",
1136
+ "- Follow the repository AGENTS.md and this role contract.",
1137
+ "- Report only observed commands, validations, and workspace effects.",
1138
+ "- Do not claim another role's work or finalize the parent AI Workflow execution."
1139
+ ].join("\n");
1140
+ return {
1141
+ name: agentName,
1142
+ description,
1143
+ content: [
1144
+ `name = ${tomlBasicString(agentName)}`,
1145
+ `description = ${tomlBasicString(description)}`,
1146
+ 'developer_instructions = """',
1147
+ tomlMultilineString(developerInstructions),
1148
+ '"""',
1149
+ ""
1150
+ ].join("\n")
1151
+ };
1152
+ }
1060
1153
  var CodexAdapter = class {
1061
1154
  cwd;
1062
1155
  constructor({ cwd }) {
1063
1156
  this.cwd = cwd;
1064
1157
  }
1065
- /**
1066
- * Transforms an OpenCode agent into a Codex prompt with completion contract.
1067
- */
1068
1158
  async transformAgent(filePath) {
1069
1159
  const content = await fs4.readFile(filePath, "utf8");
1070
- const fileName = path6.basename(filePath, ".md");
1071
- const hardenedContent = `${content}
1072
-
1073
- ## System Directives for Codex
1074
- - **No Conversational Filler**: Output only the requested artifact or analysis. Do not apologize or add filler text.
1075
- - **Explicit Paths**: Always specify the exact, absolute or relative file path before making edits.
1076
- - **Read-Only First**: Do not invent or assume file contents. Always read the workspace first.
1077
-
1078
- ## Completion Contract (Mandatory)
1079
- Every task completion MUST provide the following payload:
1080
- 1. **Status**: ${COMPLETION_STATUS_TEXT}
1081
- 2. **Scope reviewed**: Brief summary of what was analyzed.
1082
- 3. **Findings**: Evidence-based discoveries.
1083
- 4. **Files affected**: List of all concrete paths modified or created.
1084
- 5. **Validation/evidence**: Commands run and their results.
1085
- 6. **Risks/notes**: Any caveats or technical debt introduced.
1086
- 7. **Required manager action**: Clear next step for the orchestrator.
1087
- 8. **Can manager continue**: [yes/no]
1088
- `;
1089
- return {
1090
- content: hardenedContent,
1091
- name: fileName
1092
- };
1160
+ return toCodexAgentToml(path6.basename(filePath, ".md"), content);
1093
1161
  }
1094
- /**
1095
- * Deploys agents, skills, and commands to Codex-native directories.
1096
- * Mapping:
1097
- * - Agents -> .github/agents/
1098
- * - Skills -> .agents/skills/
1099
- * - Commands -> .codex/prompts/
1100
- */
1101
- async deploy(installRoot = ".ai-workflow", force) {
1102
- const sourceAgentsDir = path6.join(this.cwd, installRoot, "opencode/agents");
1103
- const sourceSkillsDir = path6.join(this.cwd, installRoot, "opencode/skills");
1104
- const sourceCommandsDir = path6.join(this.cwd, installRoot, "opencode/commands");
1105
- const codexAgentsDir = path6.join(this.cwd, ".github/agents");
1162
+ async deploy(installRoot = ".ai-workflow", force = false) {
1163
+ const sourceAgentsDir = path6.join(this.cwd, installRoot, "opencode", "agents");
1164
+ const sourceSkillsDir = path6.join(this.cwd, installRoot, "opencode", "skills");
1165
+ const sourceCommandsDir = path6.join(this.cwd, installRoot, "opencode", "commands");
1166
+ const codexAgentsDir = path6.join(this.cwd, ".codex", "agents");
1106
1167
  await fs4.mkdir(codexAgentsDir, { recursive: true });
1107
- const agentFiles = await fs4.readdir(sourceAgentsDir).catch(() => []);
1108
- for (const file of agentFiles) {
1168
+ for (const file of await fs4.readdir(sourceAgentsDir).catch(() => [])) {
1109
1169
  if (!file.endsWith(".md")) continue;
1110
1170
  const transformed = await this.transformAgent(path6.join(sourceAgentsDir, file));
1111
- await fs4.writeFile(path6.join(codexAgentsDir, `${transformed.name}.md`), transformed.content);
1171
+ await fs4.writeFile(path6.join(codexAgentsDir, `${transformed.name}.toml`), transformed.content);
1112
1172
  }
1113
- const codexSkillsDir = path6.join(this.cwd, ".codex/skills");
1173
+ const codexSkillsDir = path6.join(this.cwd, ".codex", "skills");
1114
1174
  await fs4.mkdir(codexSkillsDir, { recursive: true });
1115
- const skillFolders = await fs4.readdir(sourceSkillsDir).catch(() => []);
1116
- for (const folder of skillFolders) {
1175
+ for (const folder of await fs4.readdir(sourceSkillsDir).catch(() => [])) {
1117
1176
  const skillSource = path6.join(sourceSkillsDir, folder, "SKILL.md");
1118
- if (await this.exists(skillSource)) {
1119
- const targetDir = path6.join(codexSkillsDir, folder);
1120
- await fs4.mkdir(targetDir, { recursive: true });
1121
- const content = await fs4.readFile(skillSource, "utf8");
1122
- await fs4.writeFile(path6.join(targetDir, "SKILL.md"), content);
1123
- }
1124
- }
1125
- const codexPromptsDir = path6.join(this.cwd, ".codex/prompts");
1126
- await fs4.mkdir(codexPromptsDir, { recursive: true });
1127
- const commandFiles = await fs4.readdir(sourceCommandsDir).catch(() => []);
1128
- for (const file of commandFiles) {
1129
- if (!file.endsWith(".md") && !file.endsWith(".toml")) continue;
1130
- const targetFileName = file.endsWith(".toml") ? `${path6.basename(file, ".toml")}.md` : file;
1131
- const content = await fs4.readFile(path6.join(sourceCommandsDir, file), "utf8");
1132
- await fs4.writeFile(path6.join(codexPromptsDir, targetFileName), content);
1177
+ if (!await this.exists(skillSource)) continue;
1178
+ const targetDir = path6.join(codexSkillsDir, folder);
1179
+ await fs4.mkdir(targetDir, { recursive: true });
1180
+ await fs4.writeFile(path6.join(targetDir, "SKILL.md"), await fs4.readFile(skillSource, "utf8"));
1181
+ }
1182
+ for (const file of await fs4.readdir(sourceCommandsDir).catch(() => [])) {
1183
+ if (!file.endsWith(".md") || file.toLowerCase() === "readme.md") continue;
1184
+ const commandName = path6.basename(file, ".md");
1185
+ const targetDir = path6.join(codexSkillsDir, `aiwk-command-${commandName}`);
1186
+ await fs4.mkdir(targetDir, { recursive: true });
1187
+ const source = await fs4.readFile(path6.join(sourceCommandsDir, file), "utf8");
1188
+ await fs4.writeFile(path6.join(targetDir, "SKILL.md"), commandSkillContent(source, commandName));
1133
1189
  }
1134
1190
  const codexPoliciesDir = path6.join(this.cwd, ".codex", "docs", "policies");
1135
1191
  await fs4.mkdir(codexPoliciesDir, { recursive: true });
1136
1192
  const sourcePoliciesDir = path6.join(this.cwd, installRoot, "opencode", "docs", "policies");
1137
- const policyFiles = await fs4.readdir(sourcePoliciesDir).catch(() => []);
1138
- for (const file of policyFiles) {
1193
+ for (const file of await fs4.readdir(sourcePoliciesDir).catch(() => [])) {
1139
1194
  if (!file.endsWith(".md")) continue;
1140
- const policyContent = await fs4.readFile(path6.join(sourcePoliciesDir, file), "utf8");
1141
- await fs4.writeFile(path6.join(codexPoliciesDir, file), policyContent);
1195
+ await fs4.writeFile(
1196
+ path6.join(codexPoliciesDir, file),
1197
+ await fs4.readFile(path6.join(sourcePoliciesDir, file), "utf8")
1198
+ );
1142
1199
  }
1143
- await this.deployRootCopilotInstructions();
1144
- await this.deployRootCodexMd(force);
1145
1200
  await this.deployRootAgentsMd(force);
1146
1201
  }
1147
- async deployRootCopilotInstructions() {
1148
- const githubDir = path6.join(this.cwd, ".github");
1149
- await fs4.mkdir(githubDir, { recursive: true });
1150
- const targetPath = path6.join(githubDir, "copilot-instructions.md");
1151
- const instructions = `# AI Workflow Kit Governance
1152
-
1153
- This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
1154
-
1155
- ## Mandate: Atlas Authority Protocol
1156
- All architectural changes must align with the specifications issued by the **Specification Authority**.
1157
-
1158
- ## Rules & Personas
1159
- - Always follow the **Branch Gate** policy (never work on \`main\`).
1160
- - Always follow the **SDD Workflow** (Spec -> Plan -> PR -> Evidence).
1161
- - When acting as an agent, consult the instructions in \`.github/agents/\`.
1162
- - When utilizing skills, consult the files in \`.codex/skills/\`.
1163
-
1164
- ## System Boundaries
1165
- - **No Conversational Filler**: Do not apologize or add filler text.
1166
- - **Strict Evidence**: You must provide the Completion Contract payload when finishing a task.
1167
- - **Immutable Core**: Do not modify files inside \`.ai-workflow/\`, \`.agents/\`, \`.claude/\`, or \`.codex/\` unless explicitly requested.
1168
-
1169
- ## Completion Contract
1170
- Every task completion MUST follow the payload format defined in the individual agent files.
1171
- `;
1172
- await fs4.writeFile(targetPath, instructions);
1173
- }
1174
- async deployRootCodexMd(force) {
1175
- const targetPath = path6.join(this.cwd, "CODEX.md");
1176
- if (!force && await this.exists(targetPath)) return;
1177
- const templatePath = path6.join(this.cwd, ".ai-workflow", "templates", "CODEX.md.template");
1178
- if (await this.exists(templatePath)) {
1179
- const content = await fs4.readFile(templatePath, "utf8");
1180
- await fs4.writeFile(targetPath, content);
1181
- return;
1182
- }
1183
- const instructions = `# AI Workflow Kit Governance
1184
-
1185
- This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
1186
-
1187
- ## Mandate: Atlas Authority Protocol
1188
- All architectural changes must align with the specifications issued by the **Specification Authority**.
1189
-
1190
- ## Rules & Personas
1191
- - Always follow the **Branch Gate** policy (never work on \`main\`).
1192
- - Always follow the **SDD Workflow** (Spec -> Plan -> PR -> Evidence).
1193
- - When acting as an agent, consult the instructions in \`.github/agents/\`.
1194
- - When utilizing skills, consult the files in \`.codex/skills/\`.
1195
-
1196
- ## System Boundaries
1197
- - **No Conversational Filler**: Do not apologize or add filler text.
1198
- - **Strict Evidence**: You must provide the Completion Contract payload when finishing a task.
1199
- - **Immutable Core**: Do not modify files inside \`.ai-workflow/\`, \`.agents/\`, \`.claude/\`, or \`.codex/\` unless explicitly requested.
1200
-
1201
- ## Completion Contract
1202
- Every task completion MUST follow the payload format defined in the individual agent files.
1203
- `;
1204
- await fs4.writeFile(targetPath, instructions);
1205
- }
1206
- async deployRootAgentsMd(force) {
1202
+ async deployRootAgentsMd(force = false) {
1207
1203
  const targetPath = path6.join(this.cwd, "AGENTS.md");
1208
- if (!force && await this.exists(targetPath)) return;
1209
- const templatePath = path6.join(this.cwd, ".ai-workflow", "AGENTS.md");
1210
- if (await this.exists(templatePath)) {
1211
- let content = await fs4.readFile(templatePath, "utf8");
1212
- content = content.replace(/\.agents\/skills/g, ".codex/skills");
1213
- content = content.replace(/opencode\/commands/g, ".codex/prompts");
1214
- content = content.replace(/opencode\/docs\/policies/g, ".codex/docs/policies");
1215
- await fs4.writeFile(targetPath, content);
1204
+ if (!force && await this.exists(targetPath)) {
1205
+ const existing = await fs4.readFile(targetPath, "utf8");
1206
+ if (!existing.startsWith("# AI Workflow Kit contributor contract") && !existing.startsWith("# AI Workflow Kit for Codex")) return;
1216
1207
  }
1208
+ await fs4.writeFile(targetPath, codexRootInstructions());
1217
1209
  }
1218
- async exists(p) {
1210
+ async exists(candidate) {
1219
1211
  try {
1220
- await fs4.access(p);
1212
+ await fs4.access(candidate);
1221
1213
  return true;
1222
1214
  } catch {
1223
1215
  return false;
@@ -1726,7 +1718,7 @@ async function executePlatformMigrations(cwd, installRoot, platforms, claude, co
1726
1718
  if (codex) {
1727
1719
  const adapter = new CodexAdapter({ cwd });
1728
1720
  await adapter.deploy(installRoot, force);
1729
- console.log("- codex: deployed AI Workflow Kit agents to .agents/, .codex/, and .github/ (native discovery enabled)");
1721
+ console.log("- codex: deployed native agents to .codex/agents and skills (including commands) to .codex/skills");
1730
1722
  }
1731
1723
  if (antigravity) {
1732
1724
  const adapter = new AntigravityAdapter({ cwd });
@@ -2036,24 +2028,79 @@ async function checkPackageJson(cwd, state) {
2036
2028
  }
2037
2029
  }
2038
2030
  async function checkCodexRuntime(cwd, state) {
2039
- const checks = [
2040
- { name: "root AGENTS.md exists", path: "AGENTS.md" },
2041
- { name: ".codex/skills exists", path: ".codex/skills" },
2042
- { name: ".codex prompts detected", path: ".codex/prompts" },
2043
- { name: ".codex policies detected", path: ".codex/docs/policies" }
2044
- ];
2045
- for (const check of checks) {
2046
- const ok = await exists(path9.join(cwd, check.path));
2047
- if (ok) {
2048
- console.log(`PASS ${check.name}`);
2031
+ const rootAgentsPath = path9.join(cwd, "AGENTS.md");
2032
+ const rootAgents = await fs7.readFile(rootAgentsPath, "utf8").catch(() => "");
2033
+ if (!rootAgents) {
2034
+ state.hasFailure = true;
2035
+ console.log("FAIL root AGENTS.md missing");
2036
+ } else if (!rootAgents.includes("Astra owns all requested workspace mutations")) {
2037
+ state.hasFailure = true;
2038
+ console.log("FAIL root AGENTS.md lacks the Codex Astra ownership contract");
2039
+ } else {
2040
+ console.log("PASS root AGENTS.md contains the Codex workflow contract");
2041
+ }
2042
+ const agentsDir = path9.join(cwd, ".codex", "agents");
2043
+ const requiredAgents = ["atlas", "astra", "nexus", "orion", "sage", "phoenix"];
2044
+ for (const agent of requiredAgents) {
2045
+ const agentPath = path9.join(agentsDir, `${agent}.toml`);
2046
+ const content = await fs7.readFile(agentPath, "utf8").catch(() => "");
2047
+ const valid = new RegExp(`^name\\s*=\\s*["']${agent}["']\\s*$`, "m").test(content) && /^description\s*=\s*["']\S/m.test(content) && /^developer_instructions\s*=\s*"""/m.test(content) && /"""\s*$/m.test(content);
2048
+ if (valid) {
2049
+ console.log(`PASS native Codex agent: ${agent}`);
2050
+ } else {
2051
+ state.hasFailure = true;
2052
+ console.log(`FAIL native Codex agent invalid or missing: ${agent}`);
2053
+ }
2054
+ }
2055
+ const skillsDir = path9.join(cwd, ".codex", "skills");
2056
+ const skillFolders = await fs7.readdir(skillsDir).catch(() => []);
2057
+ if (skillFolders.length === 0) {
2058
+ state.hasFailure = true;
2059
+ console.log("FAIL .codex/skills missing or empty");
2060
+ } else {
2061
+ console.log(`PASS .codex/skills detected (${skillFolders.length})`);
2062
+ }
2063
+ const sourceCommandsDir = path9.join(cwd, ".ai-workflow", "opencode", "commands");
2064
+ for (const command of await fs7.readdir(sourceCommandsDir).catch(() => [])) {
2065
+ if (!command.endsWith(".md") || command.toLowerCase() === "readme.md") continue;
2066
+ const name = path9.basename(command, ".md");
2067
+ const commandSkillPath = path9.join(skillsDir, `aiwk-command-${name}`, "SKILL.md");
2068
+ const content = await fs7.readFile(commandSkillPath, "utf8").catch(() => "");
2069
+ if (content.includes(`name: "aiwk-command-${name}"`)) {
2070
+ console.log(`PASS Codex command skill: ${name}`);
2049
2071
  } else {
2050
2072
  state.hasFailure = true;
2051
- console.log(`FAIL ${check.name}`);
2073
+ console.log(`FAIL Codex command skill invalid or missing: ${name}`);
2052
2074
  }
2053
2075
  }
2054
- const codexMdOk = await exists(path9.join(cwd, "CODEX.md"));
2055
- if (codexMdOk) {
2056
- console.log(`PASS root CODEX.md exists`);
2076
+ if (await exists(path9.join(cwd, ".codex", "docs", "policies"))) {
2077
+ console.log("PASS .codex/docs/policies detected");
2078
+ } else {
2079
+ state.hasFailure = true;
2080
+ console.log("FAIL .codex/docs/policies missing");
2081
+ }
2082
+ if (await exists(path9.join(cwd, ".codex", "prompts"))) {
2083
+ state.hasWarning = true;
2084
+ console.log("WARN legacy .codex/prompts detected; AIWK commands now use .codex/skills");
2085
+ }
2086
+ const adapter = new CodexRuntimeAdapter({ cwd });
2087
+ const inspection = await adapter.inspect();
2088
+ if (!inspection.available) {
2089
+ state.hasFailure = true;
2090
+ console.log("FAIL Codex CLI is not installed or not available in PATH");
2091
+ } else if (!inspection.supports.formatJson) {
2092
+ state.hasFailure = true;
2093
+ console.log("FAIL Codex CLI lacks exec --json required for observed evidence");
2094
+ } else if (inspection.projectTrusted === false) {
2095
+ state.hasFailure = true;
2096
+ console.log("FAIL Codex project is not trusted; native agents are disabled until this repository is trusted in Codex");
2097
+ } else if (!inspection.supports.agent) {
2098
+ state.hasFailure = true;
2099
+ console.log("FAIL Codex native agents are incomplete");
2100
+ } else {
2101
+ state.hasWarning = true;
2102
+ console.log("PASS Codex CLI, JSON events, and native agents are available");
2103
+ console.log("WARN Codex CLI delivery is blocked because `codex exec` cannot prove custom-agent ownership; use interactive Codex");
2057
2104
  }
2058
2105
  if (state.runtimes.includes("codex")) {
2059
2106
  console.log("PASS config runtime includes codex");
@@ -2497,7 +2544,7 @@ async function findCompatibleLedger(cwd, specRelativePath) {
2497
2544
  }
2498
2545
  return matches;
2499
2546
  }
2500
- async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) {
2547
+ async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent, runtime) {
2501
2548
  if (!approvalPath) throw new Error("[SDD BLOCKED] DEEP resume requires --approval-path.");
2502
2549
  const approvalValidator = new ApprovalReceiptValidator({ cwd });
2503
2550
  const approval = await approvalValidator.validate(approvalPath, absoluteSpecPath);
@@ -2537,7 +2584,9 @@ async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPa
2537
2584
  throw new Error("[SDD BLOCKED] Ledger specification path does not match --spec-path.");
2538
2585
  }
2539
2586
  const draftHash = nexusTerminals[0]?.data?.artifactHash || null;
2540
- const controller = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
2587
+ const runtimeAdapter = await createRuntimeAdapter({ cwd, runtime });
2588
+ console.log(`[RUNTIME] ${runtimeAdapter.runtime}`);
2589
+ const controller = new DelegationController({ cwd, ledger, adapter: runtimeAdapter });
2541
2590
  const absolutePlanPath = path14.join(cwd, "docs/workflows", workflowId, "technical-plan.json");
2542
2591
  const relativePlanPath = repositoryRelative(cwd, absolutePlanPath);
2543
2592
  if (await fs12.access(absolutePlanPath).then(() => true).catch(() => false)) {
@@ -2645,7 +2694,7 @@ async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPa
2645
2694
  controller
2646
2695
  };
2647
2696
  }
2648
- async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null }) {
2697
+ async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null, runtime }) {
2649
2698
  if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
2650
2699
  throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
2651
2700
  }
@@ -2669,7 +2718,7 @@ async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, re
2669
2718
  const profileDefinition = getWorkflowProfile(workflowProfile);
2670
2719
  console.log(`[PASS] Specification: ${specTier.toUpperCase()} spec is APPROVED.`);
2671
2720
  console.log(`[PROFILE] ${workflowProfile} -> ${profileDefinition.owner}; skills: ${profileDefinition.skills.join(", ") || "none"}.`);
2672
- const deepContext = specTier === "deep" ? await runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent) : null;
2721
+ const deepContext = specTier === "deep" ? await runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPath, specContent, runtime) : null;
2673
2722
  const validateWorkflow = async () => {
2674
2723
  const evidence = await runCollectEvidence({
2675
2724
  cwd,
@@ -2735,8 +2784,7 @@ async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, re
2735
2784
  import fs13 from "fs/promises";
2736
2785
  import path15 from "path";
2737
2786
  import { execSync as execSync3 } from "child_process";
2738
- function createRuntimeRemediationExecutor(cwd, ledger = null) {
2739
- const adapter = new OpenCodeAdapter({ cwd });
2787
+ function createRuntimeRemediationExecutor(cwd, ledger = null, adapter = new OpenCodeAdapter({ cwd })) {
2740
2788
  function getChangedFiles2() {
2741
2789
  try {
2742
2790
  const output = execSync3("git status --short", { cwd, encoding: "utf8" });
@@ -2761,9 +2809,9 @@ function createRuntimeRemediationExecutor(cwd, ledger = null) {
2761
2809
  actor: "Phoenix",
2762
2810
  actorType: "runtime-agent",
2763
2811
  observed: true,
2764
- runtime: "opencode",
2812
+ runtime: adapter.runtime,
2765
2813
  eventType: "remediation_start",
2766
- provenance: "opencode-adapter",
2814
+ provenance: adapter.provenance,
2767
2815
  data: {
2768
2816
  attempt,
2769
2817
  affectedChecks,
@@ -3119,9 +3167,9 @@ async function runImplementation(plan, classification, delegationController, sta
3119
3167
  }
3120
3168
  const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded, fastTrack, orchestratedChild: true });
3121
3169
  if (!runResult.success) {
3122
- console.error(`[EXECUTE] OpenCode adapter execution reported failure: ${runResult.error || "Unknown error"}`);
3170
+ console.error(`[EXECUTE] Runtime execution reported failure: ${runResult.error || "Unknown error"}`);
3123
3171
  stateMachine.transitionTo("BLOCKED");
3124
- throw new Error(`[WORKFLOW BLOCKED] OpenCode runtime execution failed: ${runResult.error || "Unknown error"}`);
3172
+ throw new Error(`[WORKFLOW BLOCKED] Runtime execution failed: ${runResult.error || "Unknown error"}`);
3125
3173
  }
3126
3174
  return runResult;
3127
3175
  }
@@ -3170,13 +3218,13 @@ async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, cl
3170
3218
  const result = await delegationController.validate(taskSlug, plan.profile, validateWorkflow, plan.evidencePolicy);
3171
3219
  return { result, validateWorkflow };
3172
3220
  }
3173
- async function runBoundedRemediation(plan, taskSlug, cwd, initialResult, validateWorkflow, ledger, delegationController, stateMachine, checkBranchSafety, updateSnapshot) {
3221
+ async function runBoundedRemediation(plan, taskSlug, cwd, initialResult, validateWorkflow, ledger, delegationController, runtimeAdapter, stateMachine, checkBranchSafety, updateSnapshot) {
3174
3222
  if (!isRecoverableGateFailure(initialResult.overallStatus)) {
3175
3223
  return initialResult;
3176
3224
  }
3177
3225
  console.log(`
3178
3226
  [REMEDIATION REQUIRED] ${initialResult.overallStatus}. Starting bounded remediation (limit: ${plan.remediationLimit}).`);
3179
- const executor = createRuntimeRemediationExecutor(cwd, ledger);
3227
+ const executor = createRuntimeRemediationExecutor(cwd, ledger, runtimeAdapter);
3180
3228
  const healer = new HealerEngine({
3181
3229
  cwd,
3182
3230
  remediationLimit: plan.remediationLimit,
@@ -3303,7 +3351,7 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
3303
3351
  stateHistory: stateMachine.getHistory()
3304
3352
  };
3305
3353
  }
3306
- async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3354
+ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride, runtime }) {
3307
3355
  if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
3308
3356
  throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
3309
3357
  }
@@ -3315,7 +3363,9 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3315
3363
  [AI WORKFLOW] Executing natural request intake [slug: ${taskSlug}]...
3316
3364
  `);
3317
3365
  const ledger = new EvidenceLedger({ cwd, workflowId: taskSlug });
3318
- const delegationController = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
3366
+ const runtimeAdapter = await createRuntimeAdapter({ cwd, runtime });
3367
+ console.log(`[RUNTIME] ${runtimeAdapter.runtime}`);
3368
+ const delegationController = new DelegationController({ cwd, ledger, adapter: runtimeAdapter });
3319
3369
  let plan = null;
3320
3370
  let branchAuthorized = false;
3321
3371
  try {
@@ -3380,6 +3430,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
3380
3430
  validateWorkflow,
3381
3431
  ledger,
3382
3432
  delegationController,
3433
+ runtimeAdapter,
3383
3434
  stateMachine,
3384
3435
  checkBranchSafety,
3385
3436
  async () => {
@@ -3547,8 +3598,8 @@ function printHelp() {
3547
3598
  console.log(`ai-workflow
3548
3599
 
3549
3600
  Usage:
3550
- ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"]
3551
- ai-workflow run --spec-path=<path> [--approval-path=<path>]
3601
+ ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"] [--runtime=<opencode|codex>]
3602
+ ai-workflow run --spec-path=<path> [--approval-path=<path>] [--runtime=<opencode|codex>]
3552
3603
  ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--claude] [--codex] [--antigravity] [--profile=<profile>]
3553
3604
  ai-workflow validate [--a11y] [--visual-dist=<path>] [--port=<port>]
3554
3605
  ai-workflow collect-evidence [--task=<slug>] [--evidence-policy=<optional|required>] [--mode=<quick|standard|full>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
@@ -3578,6 +3629,7 @@ function parseFlags(args) {
3578
3629
  const requestArg = args.find((arg) => arg.startsWith("--request="));
3579
3630
  const visualDistArg = args.find((arg) => arg.startsWith("--visual-dist="));
3580
3631
  const portArg = args.find((arg) => arg.startsWith("--port="));
3632
+ const runtimeArg = args.find((arg) => arg.startsWith("--runtime="));
3581
3633
  const profileIdx = args.indexOf("--profile");
3582
3634
  const profileVal = profileEqArg ? profileEqArg.replace("--profile=", "") : profileIdx >= 0 && profileIdx + 1 < args.length && !args[profileIdx + 1].startsWith("--") ? args[profileIdx + 1] : void 0;
3583
3635
  return {
@@ -3599,6 +3651,7 @@ function parseFlags(args) {
3599
3651
  request: requestArg ? requestArg.replace("--request=", "") : void 0,
3600
3652
  visualDist: visualDistArg ? visualDistArg.replace("--visual-dist=", "") : void 0,
3601
3653
  port: portArg ? portArg.replace("--port=", "") : void 0,
3654
+ runtime: runtimeArg ? runtimeArg.replace("--runtime=", "") : void 0,
3602
3655
  a11y: args.includes("--a11y"),
3603
3656
  purgeAgents: args.includes("--purge-agents") || args.includes("--purge")
3604
3657
  };
@@ -3610,7 +3663,8 @@ var commandMap = {
3610
3663
  const result = await runExecute({
3611
3664
  cwd: process.cwd(),
3612
3665
  naturalRequest: request,
3613
- taskSlug: flags.taskSlug
3666
+ taskSlug: flags.taskSlug,
3667
+ runtime: flags.runtime
3614
3668
  });
3615
3669
  if (result && (result.overallStatus === "FAIL_QUALITY_GATE" || result.overallStatus === "BLOCKED" || result.overallStatus === "FAIL")) {
3616
3670
  process.exitCode = result.exitCode === 2 ? 2 : 1;