@williambeto/ai-workflow 2.6.4 → 2.7.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.
@@ -21,13 +21,13 @@ import {
21
21
  isTerminalFailure,
22
22
  readPackageFile,
23
23
  resolveWorkflowProfile
24
- } from "../chunk-UHLZX2EG.js";
24
+ } from "../chunk-W4RTQWVQ.js";
25
25
  import {
26
26
  EvidenceCollector,
27
27
  QualityGuard,
28
28
  ValidationPlanner
29
- } from "../chunk-6EMG6FAO.js";
30
- import "../chunk-2AOV2ATY.js";
29
+ } from "../chunk-BDZPUAEX.js";
30
+ import "../chunk-XW747GIG.js";
31
31
  import "../chunk-5WRI5ZAA.js";
32
32
 
33
33
  // src/cli/commands/init.ts
@@ -193,13 +193,14 @@ function isValidProfile(profile) {
193
193
  function getTemplateFiles(profile = "standard") {
194
194
  return PROFILE_FILES[profile] ?? PROFILE_FILES.standard;
195
195
  }
196
- function buildAiWorkflowConfig({ profile }) {
196
+ function buildAiWorkflowConfig({ profile, runtimes = ["opencode"] }) {
197
197
  const version = getPackageVersion();
198
198
  const now = (/* @__PURE__ */ new Date()).toISOString();
199
+ const primaryRuntime = runtimes.join("+");
199
200
  return {
200
201
  package: "@williambeto/ai-workflow",
201
202
  version,
202
- runtime: "opencode",
203
+ runtime: primaryRuntime,
203
204
  profile,
204
205
  installMode: "project-local",
205
206
  generatedAt: now,
@@ -779,7 +780,7 @@ Every task completion MUST provide the following payload:
779
780
  * - Skills -> .agents/skills/
780
781
  * - Commands -> .codex/prompts/
781
782
  */
782
- async deploy(installRoot = ".ai-workflow") {
783
+ async deploy(installRoot = ".ai-workflow", force) {
783
784
  const sourceAgentsDir = path7.join(this.cwd, installRoot, "opencode/agents");
784
785
  const sourceSkillsDir = path7.join(this.cwd, installRoot, "opencode/skills");
785
786
  const sourceCommandsDir = path7.join(this.cwd, installRoot, "opencode/commands");
@@ -791,7 +792,7 @@ Every task completion MUST provide the following payload:
791
792
  const transformed = await this.transformAgent(path7.join(sourceAgentsDir, file));
792
793
  await fs5.writeFile(path7.join(codexAgentsDir, `${transformed.name}.md`), transformed.content);
793
794
  }
794
- const codexSkillsDir = path7.join(this.cwd, ".agents/skills");
795
+ const codexSkillsDir = path7.join(this.cwd, ".codex/skills");
795
796
  await fs5.mkdir(codexSkillsDir, { recursive: true });
796
797
  const skillFolders = await fs5.readdir(sourceSkillsDir).catch(() => []);
797
798
  for (const folder of skillFolders) {
@@ -812,7 +813,7 @@ Every task completion MUST provide the following payload:
812
813
  const content = await fs5.readFile(path7.join(sourceCommandsDir, file), "utf8");
813
814
  await fs5.writeFile(path7.join(codexPromptsDir, targetFileName), content);
814
815
  }
815
- const codexPoliciesDir = path7.join(this.cwd, ".agents", "docs", "policies");
816
+ const codexPoliciesDir = path7.join(this.cwd, ".codex", "docs", "policies");
816
817
  await fs5.mkdir(codexPoliciesDir, { recursive: true });
817
818
  const sourcePoliciesDir = path7.join(this.cwd, installRoot, "opencode", "docs", "policies");
818
819
  const policyFiles = await fs5.readdir(sourcePoliciesDir).catch(() => []);
@@ -821,6 +822,70 @@ Every task completion MUST provide the following payload:
821
822
  const policyContent = await fs5.readFile(path7.join(sourcePoliciesDir, file), "utf8");
822
823
  await fs5.writeFile(path7.join(codexPoliciesDir, file), policyContent);
823
824
  }
825
+ await this.deployRootCopilotInstructions();
826
+ await this.deployRootCodexMd(force);
827
+ await this.deployRootAgentsMd(force);
828
+ }
829
+ async deployRootCopilotInstructions() {
830
+ const githubDir = path7.join(this.cwd, ".github");
831
+ await fs5.mkdir(githubDir, { recursive: true });
832
+ const targetPath = path7.join(githubDir, "copilot-instructions.md");
833
+ const instructions = `# AI Workflow Kit Governance
834
+
835
+ This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
836
+
837
+ ## Mandate: Atlas Authority Protocol
838
+ All architectural changes must align with the specifications issued by the **Specification Authority**.
839
+
840
+ ## Rules & Personas
841
+ - Always follow the **Branch Gate** policy (never work on \`main\`).
842
+ - Always follow the **SDD Workflow** (Spec -> Plan -> PR -> Evidence).
843
+ - When acting as an agent, consult the instructions in \`.github/agents/\`.
844
+ - When utilizing skills, consult the files in \`.codex/skills/\`.
845
+
846
+ ## Completion Contract
847
+ Every task completion MUST follow the payload format defined in the individual agent files.
848
+ `;
849
+ await fs5.writeFile(targetPath, instructions);
850
+ }
851
+ async deployRootCodexMd(force) {
852
+ const targetPath = path7.join(this.cwd, "CODEX.md");
853
+ if (!force && await this.exists(targetPath)) return;
854
+ const templatePath = path7.join(this.cwd, ".ai-workflow", "templates", "CODEX.md.template");
855
+ if (await this.exists(templatePath)) {
856
+ const content = await fs5.readFile(templatePath, "utf8");
857
+ await fs5.writeFile(targetPath, content);
858
+ return;
859
+ }
860
+ const instructions = `# AI Workflow Kit Governance
861
+
862
+ This repository uses the **AI Workflow Kit** (OpenCode-first) workflow.
863
+
864
+ ## Mandate: Atlas Authority Protocol
865
+ All architectural changes must align with the specifications issued by the **Specification Authority**.
866
+
867
+ ## Rules & Personas
868
+ - Always follow the **Branch Gate** policy (never work on \`main\`).
869
+ - Always follow the **SDD Workflow** (Spec -> Plan -> PR -> Evidence).
870
+ - When acting as an agent, consult the instructions in \`.github/agents/\`.
871
+ - When utilizing skills, consult the files in \`.codex/skills/\`.
872
+
873
+ ## Completion Contract
874
+ Every task completion MUST follow the payload format defined in the individual agent files.
875
+ `;
876
+ await fs5.writeFile(targetPath, instructions);
877
+ }
878
+ async deployRootAgentsMd(force) {
879
+ const targetPath = path7.join(this.cwd, "AGENTS.md");
880
+ if (!force && await this.exists(targetPath)) return;
881
+ const templatePath = path7.join(this.cwd, ".ai-workflow", "AGENTS.md");
882
+ if (await this.exists(templatePath)) {
883
+ let content = await fs5.readFile(templatePath, "utf8");
884
+ content = content.replace(/\.agents\/skills/g, ".codex/skills");
885
+ content = content.replace(/opencode\/commands/g, ".codex/prompts");
886
+ content = content.replace(/opencode\/docs\/policies/g, ".codex/docs/policies");
887
+ await fs5.writeFile(targetPath, content);
888
+ }
824
889
  }
825
890
  async exists(p) {
826
891
  try {
@@ -1256,7 +1321,7 @@ function resolveProfile(profile) {
1256
1321
  }
1257
1322
  return selectedProfile;
1258
1323
  }
1259
- async function writeInstallFiles(cwd, actions, selectedProfile, force, noOverwrite, backupRoot = ".ai-workflow-backups") {
1324
+ async function writeInstallFiles(cwd, actions, selectedProfile, platforms, force, noOverwrite, backupRoot = ".ai-workflow-backups") {
1260
1325
  const skipped = [];
1261
1326
  const backups = [];
1262
1327
  for (const action of actions) {
@@ -1272,7 +1337,7 @@ async function writeInstallFiles(cwd, actions, selectedProfile, force, noOverwri
1272
1337
  });
1273
1338
  backups.push(backupPath);
1274
1339
  }
1275
- const content = action.relativePath === ".ai-workflow/config.json" ? `${JSON.stringify(buildAiWorkflowConfig({ profile: selectedProfile }), null, 2)}
1340
+ const content = action.relativePath === ".ai-workflow/config.json" ? `${JSON.stringify(buildAiWorkflowConfig({ profile: selectedProfile, runtimes: platforms.length > 0 ? platforms : ["opencode"] }), null, 2)}
1276
1341
  ` : action.content;
1277
1342
  await writeFileSafe(action.absolutePath, content || "");
1278
1343
  }
@@ -1319,7 +1384,7 @@ async function writeSymlinks(cwd, linkEntries, force, backupRoot = ".ai-workflow
1319
1384
  }
1320
1385
  return { linkBackups, linkCreated };
1321
1386
  }
1322
- async function executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity) {
1387
+ async function executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity, force) {
1323
1388
  if (platforms.length === 0) return;
1324
1389
  console.log("ai-workflow: migrating to native platforms...");
1325
1390
  const agentsDir = path9.join(cwd, installRoot, "opencode/agents");
@@ -1332,7 +1397,7 @@ async function executePlatformMigrations(cwd, installRoot, platforms, claude, co
1332
1397
  }
1333
1398
  if (codex) {
1334
1399
  const adapter = new CodexAdapter({ cwd });
1335
- await adapter.deploy(installRoot);
1400
+ await adapter.deploy(installRoot, force);
1336
1401
  console.log("- codex: deployed AI Workflow Kit agents to .agents/, .codex/, and .github/ (native discovery enabled)");
1337
1402
  }
1338
1403
  if (antigravity) {
@@ -1433,7 +1498,7 @@ async function runInit({
1433
1498
  }
1434
1499
  return;
1435
1500
  }
1436
- const { skipped, backups } = await writeInstallFiles(cwd, actions, selectedProfile, force, noOverwrite, backupRoot);
1501
+ const { skipped, backups } = await writeInstallFiles(cwd, actions, selectedProfile, platforms, force, noOverwrite, backupRoot);
1437
1502
  const { linkBackups, linkCreated } = await writeSymlinks(cwd, linkEntries, force, backupRoot);
1438
1503
  await setupInternalSymlinks(cwd, installRoot);
1439
1504
  const opencodeResult = await mergeOpencodeConfig(cwd, { force, backupRoot });
@@ -1457,7 +1522,7 @@ async function runInit({
1457
1522
  }
1458
1523
  const gitignoreResult = await upsertGitignoreBlock(cwd, generatedIgnoreEntries);
1459
1524
  const scriptInjected = await injectScripts(cwd);
1460
- await executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity);
1525
+ await executePlatformMigrations(cwd, installRoot, platforms, claude, codex, antigravity, force);
1461
1526
  printInstallationSummary({
1462
1527
  selectedProfile,
1463
1528
  opencodeResult,
@@ -1486,11 +1551,7 @@ async function isSymlink(filePath) {
1486
1551
  return false;
1487
1552
  }
1488
1553
  }
1489
- async function runDoctor({ cwd }) {
1490
- let hasFailure = false;
1491
- let hasWarning = false;
1492
- let managedBlocks = [];
1493
- console.log("Doctor report:");
1554
+ async function checkOpenCodeRuntime(cwd, state) {
1494
1555
  const adapter = new OpenCodeAdapter({ cwd });
1495
1556
  const inspection = await adapter.inspect();
1496
1557
  if (inspection.available) {
@@ -1500,22 +1561,26 @@ async function runDoctor({ cwd }) {
1500
1561
  if (inspection.supports.agent) {
1501
1562
  console.log("PASS opencode --agent selection is supported");
1502
1563
  } else {
1503
- hasWarning = true;
1564
+ state.hasWarning = true;
1504
1565
  console.log("WARN opencode --agent selection is not supported; prompt fallback is available");
1505
1566
  }
1506
1567
  } else {
1507
- hasFailure = true;
1568
+ state.hasFailure = true;
1508
1569
  console.log("FAIL opencode CLI is not installed or not available in PATH");
1509
1570
  }
1571
+ }
1572
+ async function checkRequiredFiles(cwd, state) {
1510
1573
  for (const relativePath of REQUIRED_FILES) {
1511
1574
  const ok = await exists(path10.join(cwd, relativePath));
1512
1575
  if (ok) {
1513
1576
  console.log(`PASS ${relativePath}`);
1514
1577
  } else {
1515
- hasFailure = true;
1578
+ state.hasFailure = true;
1516
1579
  console.log(`FAIL ${relativePath} missing`);
1517
1580
  }
1518
1581
  }
1582
+ }
1583
+ async function checkAiWorkflowConfig(cwd, state) {
1519
1584
  const configPathV2 = path10.join(cwd, ".ai-workflow/config.json");
1520
1585
  const configPathLegacy = path10.join(cwd, ".ai-workflow.json");
1521
1586
  let configPath = null;
@@ -1524,122 +1589,172 @@ async function runDoctor({ cwd }) {
1524
1589
  } else if (await exists(configPathLegacy)) {
1525
1590
  configPath = configPathLegacy;
1526
1591
  }
1527
- if (configPath) {
1528
- try {
1529
- const config = await readJson(configPath);
1530
- if (config.installMode === "project-local" || config.mode === "standalone") {
1531
- console.log("PASS install mode is project-local/standalone");
1532
- } else {
1533
- hasWarning = true;
1534
- console.log(`WARN install mode is not project-local/standalone in ${path10.basename(configPath)}`);
1535
- }
1536
- if (config.profile) {
1537
- console.log(`PASS profile detected: ${config.profile}`);
1538
- } else {
1539
- hasWarning = true;
1540
- console.log(`WARN profile is missing in ${path10.basename(configPath)}`);
1541
- }
1542
- if (config.runtime) {
1543
- console.log(`PASS runtime: ${config.runtime}`);
1544
- } else {
1545
- console.log(`NOTE runtime field not present in ${path10.basename(configPath)}`);
1546
- }
1547
- if (Array.isArray(config.managedBlocks)) {
1548
- managedBlocks = config.managedBlocks;
1549
- } else {
1550
- console.log(`NOTE managedBlocks not present in ${path10.basename(configPath)} (legacy field)`);
1551
- }
1552
- if (Array.isArray(config.managedFiles)) {
1553
- for (const relativePath of config.managedFiles) {
1554
- const fileExists = await exists(path10.join(cwd, relativePath));
1555
- if (!fileExists) {
1556
- hasWarning = true;
1557
- console.log(`WARN managed file missing: ${relativePath}`);
1558
- }
1592
+ if (!configPath) return;
1593
+ try {
1594
+ const config = await readJson(configPath);
1595
+ const basename = path10.basename(configPath);
1596
+ if (config.installMode === "project-local" || config.mode === "standalone") {
1597
+ console.log("PASS install mode is project-local/standalone");
1598
+ } else {
1599
+ state.hasWarning = true;
1600
+ console.log(`WARN install mode is not project-local/standalone in ${basename}`);
1601
+ }
1602
+ if (config.profile) {
1603
+ console.log(`PASS profile detected: ${config.profile}`);
1604
+ } else {
1605
+ state.hasWarning = true;
1606
+ console.log(`WARN profile is missing in ${basename}`);
1607
+ }
1608
+ if (config.runtime) {
1609
+ console.log(`PASS runtime: ${config.runtime}`);
1610
+ state.runtimes = config.runtime.split("+");
1611
+ } else {
1612
+ console.log(`NOTE runtime field not present in ${basename}`);
1613
+ state.runtimes = ["opencode"];
1614
+ }
1615
+ if (Array.isArray(config.managedBlocks)) {
1616
+ state.managedBlocks = config.managedBlocks;
1617
+ } else {
1618
+ console.log(`NOTE managedBlocks not present in ${basename} (legacy field)`);
1619
+ }
1620
+ if (Array.isArray(config.managedFiles)) {
1621
+ for (const relativePath of config.managedFiles) {
1622
+ const fileExists = await exists(path10.join(cwd, relativePath));
1623
+ if (!fileExists) {
1624
+ state.hasWarning = true;
1625
+ console.log(`WARN managed file missing: ${relativePath}`);
1559
1626
  }
1560
1627
  }
1561
- if (Array.isArray(config.managedLinks)) {
1562
- for (const relativePath of config.managedLinks) {
1563
- const absolutePath = path10.join(cwd, relativePath);
1564
- const linkExists = await exists(absolutePath);
1565
- if (!linkExists) {
1566
- hasWarning = true;
1567
- console.log(`WARN managed link missing: ${relativePath}`);
1568
- continue;
1569
- }
1570
- const isLink = await isSymlink(absolutePath);
1571
- if (!isLink) {
1572
- hasWarning = true;
1573
- console.log(`WARN managed link is not a symlink: ${relativePath}`);
1574
- }
1628
+ }
1629
+ if (Array.isArray(config.managedLinks)) {
1630
+ for (const relativePath of config.managedLinks) {
1631
+ const absolutePath = path10.join(cwd, relativePath);
1632
+ if (!await exists(absolutePath)) {
1633
+ state.hasWarning = true;
1634
+ console.log(`WARN managed link missing: ${relativePath}`);
1635
+ continue;
1636
+ }
1637
+ if (!await isSymlink(absolutePath)) {
1638
+ state.hasWarning = true;
1639
+ console.log(`WARN managed link is not a symlink: ${relativePath}`);
1575
1640
  }
1576
1641
  }
1577
- } catch {
1578
- hasFailure = true;
1579
- console.log(`FAIL ${path10.basename(configPath)} is not valid JSON`);
1580
1642
  }
1643
+ } catch {
1644
+ state.hasFailure = true;
1645
+ console.log(`FAIL ${path10.basename(configPath)} is not valid JSON`);
1581
1646
  }
1647
+ }
1648
+ async function checkOpencodeJson(cwd, state) {
1582
1649
  const opencodePath = path10.join(cwd, "opencode.jsonc");
1583
1650
  if (!await exists(opencodePath)) {
1584
- hasFailure = true;
1651
+ state.hasFailure = true;
1585
1652
  console.log("FAIL opencode.jsonc missing");
1586
- } else {
1587
- try {
1588
- const opencodeConfig = await readJsonc(opencodePath);
1589
- console.log("PASS opencode.jsonc is valid JSONC");
1590
- const needsAgentDefault = managedBlocks.includes("opencode.jsonc:agent.default");
1591
- if (!needsAgentDefault || opencodeConfig.agent?.default) {
1592
- console.log("PASS opencode agent.default available");
1593
- } else {
1594
- hasFailure = true;
1595
- console.log("FAIL opencode agent.default missing");
1596
- }
1597
- const requiredCommands = managedBlocks.filter((block) => block.startsWith("opencode.jsonc:command.")).map((block) => block.replace("opencode.jsonc:command.", ""));
1598
- if (requiredCommands.length === 0) {
1599
- console.log("PASS no managed opencode commands required");
1653
+ return;
1654
+ }
1655
+ try {
1656
+ const opencodeConfig = await readJsonc(opencodePath);
1657
+ console.log("PASS opencode.jsonc is valid JSONC");
1658
+ const needsAgentDefault = state.managedBlocks.includes("opencode.jsonc:agent.default");
1659
+ if (!needsAgentDefault || opencodeConfig.agent?.default) {
1660
+ console.log("PASS opencode agent.default available");
1661
+ } else {
1662
+ state.hasFailure = true;
1663
+ console.log("FAIL opencode agent.default missing");
1664
+ }
1665
+ const requiredCommands = state.managedBlocks.filter((block) => block.startsWith("opencode.jsonc:command.")).map((block) => block.replace("opencode.jsonc:command.", ""));
1666
+ if (requiredCommands.length === 0) {
1667
+ console.log("PASS no managed opencode commands required");
1668
+ } else {
1669
+ const missingCommands = requiredCommands.filter((command) => !opencodeConfig.command?.[command]);
1670
+ if (missingCommands.length === 0) {
1671
+ console.log(`PASS opencode managed commands available (${requiredCommands.length})`);
1600
1672
  } else {
1601
- const missingCommands = requiredCommands.filter((command) => !opencodeConfig.command?.[command]);
1602
- if (missingCommands.length === 0) {
1603
- console.log(`PASS opencode managed commands available (${requiredCommands.length})`);
1604
- } else {
1605
- hasFailure = true;
1606
- console.log(`FAIL opencode command entries missing: ${missingCommands.join(", ")}`);
1607
- }
1673
+ state.hasFailure = true;
1674
+ console.log(`FAIL opencode command entries missing: ${missingCommands.join(", ")}`);
1608
1675
  }
1609
- const requiredAgents = managedBlocks.filter((block) => block.startsWith("opencode.jsonc:agent.") && !block.endsWith(".default")).map((block) => block.replace("opencode.jsonc:agent.", ""));
1610
- if (requiredAgents.length === 0) {
1611
- console.log("PASS no managed opencode agents required");
1676
+ }
1677
+ const requiredAgents = state.managedBlocks.filter((block) => block.startsWith("opencode.jsonc:agent.") && !block.endsWith(".default")).map((block) => block.replace("opencode.jsonc:agent.", ""));
1678
+ if (requiredAgents.length === 0) {
1679
+ console.log("PASS no managed opencode agents required");
1680
+ } else {
1681
+ const missingAgents = requiredAgents.filter((agent) => !opencodeConfig.agent?.[agent]);
1682
+ if (missingAgents.length === 0) {
1683
+ console.log(`PASS opencode managed agents available (${requiredAgents.length})`);
1612
1684
  } else {
1613
- const missingAgents = requiredAgents.filter((agent) => !opencodeConfig.agent?.[agent]);
1614
- if (missingAgents.length === 0) {
1615
- console.log(`PASS opencode managed agents available (${requiredAgents.length})`);
1616
- } else {
1617
- hasFailure = true;
1618
- console.log(`FAIL opencode agent entries missing: ${missingAgents.join(", ")}`);
1619
- }
1685
+ state.hasFailure = true;
1686
+ console.log(`FAIL opencode agent entries missing: ${missingAgents.join(", ")}`);
1620
1687
  }
1621
- } catch {
1622
- hasFailure = true;
1623
- console.log("FAIL opencode.jsonc is not valid JSONC");
1624
1688
  }
1689
+ } catch {
1690
+ state.hasFailure = true;
1691
+ console.log("FAIL opencode.jsonc is not valid JSONC");
1625
1692
  }
1693
+ }
1694
+ async function checkPackageJson(cwd, state) {
1626
1695
  const packageJsonPath = path10.join(cwd, "package.json");
1627
1696
  if (await exists(packageJsonPath)) {
1628
1697
  try {
1629
1698
  const packageJson = await readJson(packageJsonPath);
1630
1699
  const scripts = packageJson.scripts ?? {};
1631
1700
  if (!scripts.validate) {
1632
- hasWarning = true;
1701
+ state.hasWarning = true;
1633
1702
  console.log("WARN package.json has no validate script");
1634
1703
  }
1635
1704
  } catch {
1636
- hasFailure = true;
1705
+ state.hasFailure = true;
1637
1706
  console.log("FAIL package.json is not valid JSON");
1638
1707
  }
1639
1708
  }
1640
- const finalStatus = hasFailure ? "FAIL" : hasWarning ? "PASS_WITH_NOTES" : "PASS";
1709
+ }
1710
+ async function checkCodexRuntime(cwd, state) {
1711
+ const checks = [
1712
+ { name: "root AGENTS.md exists", path: "AGENTS.md" },
1713
+ { name: ".codex/skills exists", path: ".codex/skills" },
1714
+ { name: ".codex prompts detected", path: ".codex/prompts" },
1715
+ { name: ".codex policies detected", path: ".codex/docs/policies" }
1716
+ ];
1717
+ for (const check of checks) {
1718
+ const ok = await exists(path10.join(cwd, check.path));
1719
+ if (ok) {
1720
+ console.log(`PASS ${check.name}`);
1721
+ } else {
1722
+ state.hasFailure = true;
1723
+ console.log(`FAIL ${check.name}`);
1724
+ }
1725
+ }
1726
+ const codexMdOk = await exists(path10.join(cwd, "CODEX.md"));
1727
+ if (codexMdOk) {
1728
+ console.log(`PASS root CODEX.md exists`);
1729
+ }
1730
+ if (state.runtimes.includes("codex")) {
1731
+ console.log("PASS config runtime includes codex");
1732
+ } else {
1733
+ state.hasFailure = true;
1734
+ console.log("FAIL config runtime includes codex");
1735
+ }
1736
+ }
1737
+ async function runDoctor({ cwd }) {
1738
+ const state = {
1739
+ hasFailure: false,
1740
+ hasWarning: false,
1741
+ managedBlocks: [],
1742
+ runtimes: ["opencode"]
1743
+ };
1744
+ console.log("Doctor report:");
1745
+ await checkRequiredFiles(cwd, state);
1746
+ await checkAiWorkflowConfig(cwd, state);
1747
+ if (state.runtimes.includes("opencode")) {
1748
+ await checkOpenCodeRuntime(cwd, state);
1749
+ await checkOpencodeJson(cwd, state);
1750
+ }
1751
+ if (state.runtimes.includes("codex")) {
1752
+ await checkCodexRuntime(cwd, state);
1753
+ }
1754
+ await checkPackageJson(cwd, state);
1755
+ const finalStatus = state.hasFailure ? "FAIL" : state.hasWarning ? "PASS_WITH_NOTES" : "PASS";
1641
1756
  console.log(`Final status: ${finalStatus}`);
1642
- if (hasFailure) {
1757
+ if (state.hasFailure) {
1643
1758
  process.exitCode = 1;
1644
1759
  }
1645
1760
  }
@@ -2288,6 +2403,71 @@ async function generateFinalHandoff(plan, taskSlug, cwd, finalState, evidence) {
2288
2403
  `);
2289
2404
  return handoffPath;
2290
2405
  }
2406
+ async function runFastTrackValidation(cwd, result, checkBranchSafety) {
2407
+ console.log(`
2408
+ --- [FAST-TRACK] Running Lightweight Validation Gates ---`);
2409
+ checkBranchSafety();
2410
+ const changedFiles = result.evidence?.changedFiles || [];
2411
+ console.log(`[FAST-TRACK] Changed files detected:`, changedFiles);
2412
+ const evidencePath = path14.join(cwd, "EVIDENCE.json");
2413
+ const hasEvidence = await fs12.access(evidencePath).then(() => true).catch(() => false);
2414
+ if (hasEvidence) {
2415
+ const { EvidenceValidator } = await import("../evidence-validator-76ZQQYDU.js");
2416
+ const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
2417
+ const evidenceValidator = new EvidenceValidator({ cwd, skipFileCheck: isTest, skipGitCheck: isTest });
2418
+ const valResult = await evidenceValidator.validate();
2419
+ if (!valResult.valid) {
2420
+ console.error(`
2421
+ [FAST-TRACK BLOCKED] EVIDENCE.json validation failed: ${valResult.reason}`);
2422
+ return "FAIL_QUALITY_GATE";
2423
+ }
2424
+ }
2425
+ return result.overallStatus;
2426
+ }
2427
+ async function runFidelityValidation(naturalRequest, result, finalizer) {
2428
+ const fidelityResult = await finalizer.verifyFidelity({
2429
+ userRequest: naturalRequest,
2430
+ projectContext: result.evidence?.deliveryDecision?.projectContext || null,
2431
+ deliveryDecision: result.evidence?.deliveryDecision || null,
2432
+ changedFiles: result.evidence?.changedFiles || [],
2433
+ evidence: result.evidence || null,
2434
+ explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
2435
+ });
2436
+ if (!fidelityResult.passed) {
2437
+ console.log(`
2438
+ [FINALIZER BLOCKED] BLOCKED: Artifact fidelity check failed!`);
2439
+ console.log(`Reason: ${fidelityResult.reason}`);
2440
+ return "FAIL_QUALITY_GATE";
2441
+ }
2442
+ return result.overallStatus;
2443
+ }
2444
+ function computeFinalState(finalStatus) {
2445
+ if (finalStatus === "PASS") return "COMPLETED";
2446
+ if (finalStatus === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
2447
+ return "BLOCKED";
2448
+ }
2449
+ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack) {
2450
+ await runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachine, fastTrack);
2451
+ stateMachine.transitionTo("IMPLEMENTED");
2452
+ stateMachine.transitionTo("VALIDATING");
2453
+ stateMachine.transitionTo("COMPLETED");
2454
+ console.log("\n--- Final Handoff ---");
2455
+ console.log(`
2456
+ [AI WORKFLOW COMPLETE] COMPLETED`);
2457
+ console.log(`Handoff Packet: [IN-MEMORY] (No handoff file written in read-only mode)
2458
+ `);
2459
+ return {
2460
+ overallStatus: "PASS",
2461
+ evidence: {
2462
+ internalStatus: "PASS",
2463
+ commandsRun: []
2464
+ },
2465
+ quality: {
2466
+ overallStatus: "PASS"
2467
+ },
2468
+ stateHistory: stateMachine.getHistory()
2469
+ };
2470
+ }
2291
2471
  async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
2292
2472
  if (!naturalRequest || !naturalRequest.trim()) {
2293
2473
  throw new Error("Missing request. Please provide a natural request string via positional arguments or --request flag.");
@@ -2303,16 +2483,15 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
2303
2483
  const stateMachine = new WorkflowStateMachine();
2304
2484
  const { plan: planObj, classification } = await classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegationController, stateMachine);
2305
2485
  plan = planObj;
2306
- const fastTrack = plan.riskLevel === "low" && (plan.branchNeeded ? plan.evidencePolicy === "optional" : true);
2486
+ const fastTrack = plan.riskLevel === "low" && (!plan.branchNeeded || plan.evidencePolicy === "optional");
2307
2487
  const branchGate = new BranchGate({ memoryDir: path14.join(cwd, ".ai-workflow"), cwd });
2308
2488
  const gateResult = runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate);
2309
2489
  const checkBranchSafety = () => {
2310
- if (plan.branchNeeded) {
2311
- const currentBranch = branchGate.getCurrentBranch();
2312
- if (branchGate.protectedBranches.includes(currentBranch)) {
2313
- stateMachine.transitionTo("BLOCKED");
2314
- throw new Error(`[WORKFLOW BLOCKED] Security violation: current branch is protected '${currentBranch}'!`);
2315
- }
2490
+ if (!planObj.branchNeeded) return;
2491
+ const currentBranch = branchGate.getCurrentBranch();
2492
+ if (branchGate.protectedBranches.includes(currentBranch)) {
2493
+ stateMachine.transitionTo("BLOCKED");
2494
+ throw new Error(`[WORKFLOW BLOCKED] Security violation: current branch is protected '${currentBranch}'!`);
2316
2495
  }
2317
2496
  };
2318
2497
  await createSpecIfRequired(plan, classification, taskSlug, cwd);
@@ -2321,27 +2500,8 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
2321
2500
  readOnlyStateBefore = await captureReadOnlyState(cwd, fastTrack);
2322
2501
  }
2323
2502
  await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
2324
- if (!plan.branchNeeded) {
2325
- await runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachine, fastTrack);
2326
- stateMachine.transitionTo("IMPLEMENTED");
2327
- stateMachine.transitionTo("VALIDATING");
2328
- stateMachine.transitionTo("COMPLETED");
2329
- console.log("\n--- Final Handoff ---");
2330
- console.log(`
2331
- [AI WORKFLOW COMPLETE] COMPLETED`);
2332
- console.log(`Handoff Packet: [IN-MEMORY] (No handoff file written in read-only mode)
2333
- `);
2334
- return {
2335
- overallStatus: "PASS",
2336
- evidence: {
2337
- internalStatus: "PASS",
2338
- commandsRun: []
2339
- },
2340
- quality: {
2341
- overallStatus: "PASS"
2342
- },
2343
- stateHistory: stateMachine.getHistory()
2344
- };
2503
+ if (!plan.branchNeeded && readOnlyStateBefore) {
2504
+ return await executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack);
2345
2505
  }
2346
2506
  stateMachine.transitionTo("IMPLEMENTED");
2347
2507
  checkBranchSafety();
@@ -2369,7 +2529,6 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
2369
2529
  }
2370
2530
  checkBranchSafety();
2371
2531
  let integrity = { valid: true, changes: { added: [], modified: [], deleted: [] } };
2372
- let stabResult = result;
2373
2532
  if (!fastTrack) {
2374
2533
  const stab = await runFinalizerStabilization(
2375
2534
  plan,
@@ -2382,54 +2541,23 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
2382
2541
  snapshotRef,
2383
2542
  checkBranchSafety
2384
2543
  );
2385
- stabResult = stab.result;
2544
+ result = stab.result;
2386
2545
  integrity = stab.integrity;
2387
2546
  }
2388
- result = stabResult;
2389
2547
  let finalStatus = result.overallStatus;
2390
2548
  if (fastTrack) {
2391
- console.log(`
2392
- --- [FAST-TRACK] Running Lightweight Validation Gates ---`);
2393
- checkBranchSafety();
2394
- const changedFiles = result.evidence?.changedFiles || [];
2395
- console.log(`[FAST-TRACK] Changed files detected:`, changedFiles);
2396
- const evidencePath = path14.join(cwd, "EVIDENCE.json");
2397
- const hasEvidence = await fs12.access(evidencePath).then(() => true).catch(() => false);
2398
- if (hasEvidence) {
2399
- const { EvidenceValidator } = await import("../evidence-validator-76ZQQYDU.js");
2400
- const isTest = process.env.NODE_ENV === "test" || process.env.VITEST === "true";
2401
- const evidenceValidator = new EvidenceValidator({ cwd, skipFileCheck: isTest, skipGitCheck: isTest });
2402
- const valResult2 = await evidenceValidator.validate();
2403
- if (!valResult2.valid) {
2404
- console.error(`
2405
- [FAST-TRACK BLOCKED] EVIDENCE.json validation failed: ${valResult2.reason}`);
2406
- finalStatus = "FAIL_QUALITY_GATE";
2407
- }
2408
- }
2549
+ finalStatus = await runFastTrackValidation(cwd, result, checkBranchSafety);
2409
2550
  } else if (!integrity.valid) {
2410
2551
  console.log(`
2411
2552
  [FINALIZER BLOCKED] BLOCKED: workspace did not stabilize`);
2412
2553
  console.log(`Final changes detected:`, integrity.changes);
2413
2554
  finalStatus = "FAIL_QUALITY_GATE";
2414
2555
  } else {
2415
- const fidelityResult = await finalizer.verifyFidelity({
2416
- userRequest: naturalRequest,
2417
- projectContext: result.evidence?.deliveryDecision?.projectContext || null,
2418
- deliveryDecision: result.evidence?.deliveryDecision || null,
2419
- changedFiles: result.evidence?.changedFiles || [],
2420
- evidence: result.evidence || null,
2421
- explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
2422
- });
2423
- if (!fidelityResult.passed) {
2424
- console.log(`
2425
- [FINALIZER BLOCKED] BLOCKED: Artifact fidelity check failed!`);
2426
- console.log(`Reason: ${fidelityResult.reason}`);
2427
- finalStatus = "FAIL_QUALITY_GATE";
2428
- }
2556
+ finalStatus = await runFidelityValidation(naturalRequest, result, finalizer);
2429
2557
  }
2430
- const finalState = finalStatus === "PASS" ? "COMPLETED" : finalStatus === "PASS_WITH_NOTES" ? "COMPLETED_WITH_NOTES" : "BLOCKED";
2558
+ const finalState = computeFinalState(finalStatus);
2431
2559
  stateMachine.transitionTo(finalState);
2432
- const handoffPath = await generateFinalHandoff(plan, taskSlug, cwd, finalState, result.evidence);
2560
+ await generateFinalHandoff(plan, taskSlug, cwd, finalState, result.evidence);
2433
2561
  return { ...result, overallStatus: finalStatus, stateHistory: stateMachine.getHistory() };
2434
2562
  } finally {
2435
2563
  if (plan && !plan.branchNeeded) {
@@ -2589,19 +2717,9 @@ function parseFlags(args) {
2589
2717
  purgeAgents: args.includes("--purge-agents") || args.includes("--purge")
2590
2718
  };
2591
2719
  }
2592
- async function runCli(args) {
2593
- const [command] = args;
2594
- if (command === "--version" || command === "-v") {
2595
- console.log(getPackageVersion() || "unknown");
2596
- return;
2597
- }
2598
- if (!command || command === "--help" || command === "-h") {
2599
- printHelp();
2600
- return;
2601
- }
2602
- if (command === "execute") {
2603
- const flags = parseFlags(args.slice(1));
2604
- const positionals = args.slice(1).filter((arg) => !arg.startsWith("-"));
2720
+ var commandMap = {
2721
+ execute: async (args, flags) => {
2722
+ const positionals = args.filter((arg) => !arg.startsWith("-"));
2605
2723
  const request = flags.request || positionals.join(" ");
2606
2724
  const result = await runExecute({
2607
2725
  cwd: process.cwd(),
@@ -2611,25 +2729,23 @@ async function runCli(args) {
2611
2729
  if (result && (result.overallStatus === "FAIL_QUALITY_GATE" || result.overallStatus === "BLOCKED" || result.overallStatus === "FAIL")) {
2612
2730
  process.exit(1);
2613
2731
  }
2614
- return;
2615
- }
2616
- if (command === "run") {
2732
+ },
2733
+ run: async (_, flags) => {
2617
2734
  await runMasterOrchestrator({
2618
2735
  cwd: process.cwd(),
2619
- ...parseFlags(args.slice(1))
2736
+ ...flags
2620
2737
  });
2621
- return;
2622
- }
2623
- if (command === "init") {
2738
+ },
2739
+ init: async (_, flags) => {
2624
2740
  await runInit({
2625
2741
  cwd: process.cwd(),
2626
- ...parseFlags(args.slice(1))
2742
+ ...flags
2627
2743
  });
2628
- return;
2629
- }
2630
- if (command === "collect-evidence") {
2631
- const flags = parseFlags(args.slice(1));
2632
- if (flags.mode && !["quick", "standard", "full"].includes(flags.mode)) throw new Error("--mode must be quick, standard, or full");
2744
+ },
2745
+ "collect-evidence": async (_, flags) => {
2746
+ if (flags.mode && !["quick", "standard", "full"].includes(flags.mode)) {
2747
+ throw new Error("--mode must be quick, standard, or full");
2748
+ }
2633
2749
  await runCollectEvidence({
2634
2750
  cwd: process.cwd(),
2635
2751
  taskSlug: flags.taskSlug,
@@ -2639,11 +2755,9 @@ async function runCli(args) {
2639
2755
  visualDist: flags.visualDist,
2640
2756
  port: flags.port
2641
2757
  });
2642
- return;
2643
- }
2644
- if (command === "validate") {
2645
- const flags = parseFlags(args.slice(1));
2646
- const { runValidate } = await import("../validate-Q2NLLQ5G.js");
2758
+ },
2759
+ validate: async (_, flags) => {
2760
+ const { runValidate } = await import("../validate-A46WUBVZ.js");
2647
2761
  await runValidate({
2648
2762
  cwd: process.cwd(),
2649
2763
  taskSlug: flags.taskSlug,
@@ -2651,17 +2765,32 @@ async function runCli(args) {
2651
2765
  visualDist: flags.visualDist,
2652
2766
  port: flags.port
2653
2767
  });
2654
- return;
2655
- }
2656
- if (command === "doctor") {
2768
+ },
2769
+ doctor: async () => {
2657
2770
  await runDoctor({ cwd: process.cwd() });
2658
- return;
2659
- }
2660
- if (command === "clean") {
2771
+ },
2772
+ clean: async (_, flags) => {
2661
2773
  await runClean({
2662
2774
  cwd: process.cwd(),
2663
- ...parseFlags(args.slice(1))
2775
+ ...flags
2664
2776
  });
2777
+ }
2778
+ };
2779
+ async function runCli(args) {
2780
+ const [command] = args;
2781
+ if (command === "--version" || command === "-v") {
2782
+ console.log(getPackageVersion() || "unknown");
2783
+ return;
2784
+ }
2785
+ if (!command || command === "--help" || command === "-h") {
2786
+ printHelp();
2787
+ return;
2788
+ }
2789
+ const handler = commandMap[command];
2790
+ if (handler) {
2791
+ const argsSlice = args.slice(1);
2792
+ const flags = parseFlags(argsSlice);
2793
+ await handler(argsSlice, flags);
2665
2794
  return;
2666
2795
  }
2667
2796
  throw new Error(`unknown command: ${command}`);