@williambeto/ai-workflow 2.4.8 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/bin/ai-workflow.d.ts +3 -0
  2. package/bin/ai-workflow.js +2669 -1
  3. package/bin/ai-workflow.js.map +1 -0
  4. package/chunk-2AOV2ATY.js +749 -0
  5. package/chunk-5WRI5ZAA.js +31 -0
  6. package/chunk-6EMG6FAO.js +734 -0
  7. package/chunk-UHLZX2EG.js +1827 -0
  8. package/core/index.d.ts +440 -0
  9. package/core/index.js +32 -0
  10. package/core/index.js.map +1 -0
  11. package/dist-assets/AGENTS.md +23 -6
  12. package/dist-assets/agents/atlas.md +8 -8
  13. package/dist-assets/agents/nexus.md +4 -4
  14. package/dist-assets/agents/orion.md +1 -1
  15. package/dist-assets/agents/phoenix.md +1 -1
  16. package/dist-assets/agents/sage.md +1 -1
  17. package/dist-assets/commands/implement.md +1 -1
  18. package/dist-assets/docs/QUICKSTART.md +8 -8
  19. package/dist-assets/docs/cli-reference.md +7 -7
  20. package/dist-assets/docs/compatibility/provider-usage.md +1 -9
  21. package/dist-assets/docs/compatibility/runtime-matrix.md +5 -6
  22. package/dist-assets/docs/consumer-onboarding.md +2 -2
  23. package/dist-assets/docs/design-patterns-policy.md +1 -1
  24. package/dist-assets/docs/full-documentation.md +0 -2
  25. package/dist-assets/docs/npm-consumer-quickstart.md +70 -109
  26. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  27. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  28. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  29. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  30. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  31. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  32. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  33. package/dist-assets/docs/profiles/README.md +2 -2
  34. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  35. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  36. package/dist-assets/docs/visual-validation-guide.md +65 -3
  37. package/dist-assets/prompts/01-create-requirement.md +2 -2
  38. package/dist-assets/prompts/02-create-spec.md +2 -2
  39. package/dist-assets/prompts/03-create-tech-plan.md +4 -4
  40. package/dist-assets/prompts/04-breakdown-prs.md +4 -4
  41. package/dist-assets/prompts/05-implement-pr.md +5 -5
  42. package/dist-assets/prompts/06-review-and-fix.md +3 -3
  43. package/dist-assets/prompts/07-apply-design.md +2 -2
  44. package/dist-assets/prompts/08-validate.md +5 -5
  45. package/dist-assets/prompts/09-deploy.md +5 -5
  46. package/dist-assets/prompts/commands/implement.md +1 -1
  47. package/dist-assets/prompts/commands/tech-plan.md +2 -2
  48. package/dist-assets/prompts/commands/validate.md +2 -2
  49. package/dist-assets/runbooks/deploy-checklist.md +5 -5
  50. package/dist-assets/runbooks/how-to-use-skills.md +10 -0
  51. package/dist-assets/runbooks/validation-checklist.md +5 -5
  52. package/dist-assets/schemas/evidence.schema.json +109 -0
  53. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  54. package/dist-assets/templates/CLAUDE.md.template +7 -5
  55. package/dist-assets/templates/CODEX.md.template +7 -5
  56. package/evidence-validator-76ZQQYDU.js +6661 -0
  57. package/package.json +7 -65
  58. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  59. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  60. package/AGENTS.md +0 -34
  61. package/CHANGELOG.md +0 -237
  62. package/LICENSE +0 -21
  63. package/PUBLISH_MANIFEST.json +0 -34
  64. package/README.md +0 -129
  65. package/dist-assets/runbooks/use-linear-for-operational-planning.md +0 -45
  66. package/dist-assets/runbooks/use-napkin-project-memory.md +0 -33
  67. package/dist-assets/templates/.geminiignore.template +0 -8
  68. package/dist-assets/templates/GEMINI.md.template +0 -20
  69. package/docs/compatibility/provider-usage.md +0 -46
  70. package/docs/compatibility/runtime-matrix.md +0 -31
  71. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  72. package/docs/getting-started/quickstart.md +0 -117
  73. package/docs/getting-started/troubleshooting.md +0 -123
  74. package/docs/getting-started/upgrading-to-v2.md +0 -62
  75. package/read_only_safety_verification.md +0 -48
  76. package/src/adapters/index.js +0 -4
  77. package/src/adapters/platforms/antigravity.js +0 -382
  78. package/src/adapters/platforms/claude.js +0 -126
  79. package/src/adapters/platforms/codex.js +0 -113
  80. package/src/adapters/platforms/gemini.js +0 -245
  81. package/src/cli.js +0 -155
  82. package/src/commands/clean.js +0 -106
  83. package/src/commands/collect-evidence.js +0 -25
  84. package/src/commands/doctor.js +0 -202
  85. package/src/commands/execute.js +0 -382
  86. package/src/commands/init.js +0 -359
  87. package/src/commands/run.js +0 -112
  88. package/src/core/backup.js +0 -65
  89. package/src/core/completion-contract.js +0 -35
  90. package/src/core/delegation-controller.js +0 -193
  91. package/src/core/evidence/evidence-ledger.js +0 -98
  92. package/src/core/execution-planner.js +0 -62
  93. package/src/core/filesystem.js +0 -141
  94. package/src/core/finalization/finalizer.js +0 -165
  95. package/src/core/finalization/workspace-snapshot.js +0 -110
  96. package/src/core/gates/branch-gate.js +0 -134
  97. package/src/core/gates/merge-gate.js +0 -74
  98. package/src/core/handoff/handoff-engine.js +0 -104
  99. package/src/core/healing/cli-remediation-executor.js +0 -153
  100. package/src/core/healing/healer-engine.js +0 -212
  101. package/src/core/healing/runtime-remediation-executor.js +0 -136
  102. package/src/core/identity.js +0 -43
  103. package/src/core/install-plan.js +0 -34
  104. package/src/core/opencode-merge.js +0 -150
  105. package/src/core/package-assets.js +0 -141
  106. package/src/core/request-classifier.js +0 -73
  107. package/src/core/request-router.js +0 -289
  108. package/src/core/runtime/opencode-adapter.js +0 -202
  109. package/src/core/sdd/validator.js +0 -67
  110. package/src/core/statuses.js +0 -29
  111. package/src/core/symlink-layout.js +0 -93
  112. package/src/core/templates.js +0 -229
  113. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  114. package/src/core/validation/canonical-finalization.js +0 -43
  115. package/src/core/validation/delivery-decision-engine.js +0 -344
  116. package/src/core/validation/evidence-collector.js +0 -245
  117. package/src/core/validation/quality-guard.js +0 -251
  118. package/src/core/validation/stack-detector.js +0 -65
  119. package/src/core/validation/validation-planner.js +0 -134
  120. package/src/core/validation/visual-verifier.js +0 -175
  121. package/src/core/workflow-profiles.js +0 -107
  122. package/src/core/workflow-state-machine.js +0 -46
@@ -1,202 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { exists, readJson, readJsonc } from "../core/filesystem.js";
4
- import { OpenCodeAdapter } from "../core/runtime/opencode-adapter.js";
5
-
6
- const REQUIRED_FILES = [
7
- ".ai-workflow",
8
- "opencode/README.md"
9
- ];
10
-
11
- async function isSymlink(filePath) {
12
- try {
13
- const stat = await fs.lstat(filePath);
14
- return stat.isSymbolicLink();
15
- } catch {
16
- return false;
17
- }
18
- }
19
-
20
- export async function runDoctor({ cwd }) {
21
- let hasFailure = false;
22
- let hasWarning = false;
23
- let managedBlocks = [];
24
-
25
- console.log("Doctor report:");
26
-
27
- // Check OpenCode runtime
28
- const adapter = new OpenCodeAdapter({ cwd });
29
- const inspection = await adapter.inspect();
30
- if (inspection.available) {
31
- console.log("PASS opencode CLI is available");
32
- const supportedStr = Object.entries(inspection.supports)
33
- .filter(([_, v]) => v)
34
- .map(([k, _]) => k)
35
- .join(", ");
36
- console.log(`PASS opencode capabilities detected: ${supportedStr || "none"}`);
37
- } else {
38
- hasFailure = true;
39
- console.log("FAIL opencode CLI is not installed or not available in PATH");
40
- }
41
-
42
- for (const relativePath of REQUIRED_FILES) {
43
- const ok = await exists(path.join(cwd, relativePath));
44
-
45
- if (ok) {
46
- console.log(`PASS ${relativePath}`);
47
- } else {
48
- hasFailure = true;
49
- console.log(`FAIL ${relativePath} missing`);
50
- }
51
- }
52
-
53
- const configPathV2 = path.join(cwd, ".ai-workflow/config.json");
54
- const configPathLegacy = path.join(cwd, ".ai-workflow.json");
55
- let configPath = null;
56
-
57
- if (await exists(configPathV2)) {
58
- configPath = configPathV2;
59
- } else if (await exists(configPathLegacy)) {
60
- configPath = configPathLegacy;
61
- }
62
-
63
- if (configPath) {
64
- try {
65
- const config = await readJson(configPath);
66
-
67
- if (config.installMode === "project-local" || config.mode === "standalone") {
68
- console.log("PASS install mode is project-local/standalone");
69
- } else {
70
- hasWarning = true;
71
- console.log(`WARN install mode is not project-local/standalone in ${path.basename(configPath)}`);
72
- }
73
-
74
- if (config.profile) {
75
- console.log(`PASS profile detected: ${config.profile}`);
76
- } else {
77
- hasWarning = true;
78
- console.log(`WARN profile is missing in ${path.basename(configPath)}`);
79
- }
80
-
81
- if (config.runtime) {
82
- console.log(`PASS runtime: ${config.runtime}`);
83
- } else {
84
- console.log(`NOTE runtime field not present in ${path.basename(configPath)}`);
85
- }
86
-
87
- if (Array.isArray(config.managedBlocks)) {
88
- managedBlocks = config.managedBlocks;
89
- } else {
90
- console.log(`NOTE managedBlocks not present in ${path.basename(configPath)} (legacy field)`);
91
- }
92
-
93
- if (Array.isArray(config.managedFiles)) {
94
- for (const relativePath of config.managedFiles) {
95
- const fileExists = await exists(path.join(cwd, relativePath));
96
- if (!fileExists) {
97
- hasWarning = true;
98
- console.log(`WARN managed file missing: ${relativePath}`);
99
- }
100
- }
101
- }
102
-
103
- if (Array.isArray(config.managedLinks)) {
104
- for (const relativePath of config.managedLinks) {
105
- const absolutePath = path.join(cwd, relativePath);
106
- const linkExists = await exists(absolutePath);
107
- if (!linkExists) {
108
- hasWarning = true;
109
- console.log(`WARN managed link missing: ${relativePath}`);
110
- continue;
111
- }
112
-
113
- const isLink = await isSymlink(absolutePath);
114
- if (!isLink) {
115
- hasWarning = true;
116
- console.log(`WARN managed link is not a symlink: ${relativePath}`);
117
- }
118
- }
119
- }
120
- } catch {
121
- hasFailure = true;
122
- console.log(`FAIL ${path.basename(configPath)} is not valid JSON`);
123
- }
124
- }
125
-
126
- const opencodePath = path.join(cwd, "opencode.jsonc");
127
- if (!(await exists(opencodePath))) {
128
- hasFailure = true;
129
- console.log("FAIL opencode.jsonc missing");
130
- } else {
131
- try {
132
- const opencodeConfig = await readJsonc(opencodePath);
133
- console.log("PASS opencode.jsonc is valid JSONC");
134
-
135
- const needsAgentDefault = managedBlocks.includes("opencode.jsonc:agent.default");
136
- if (!needsAgentDefault || opencodeConfig.agent?.default) {
137
- console.log("PASS opencode agent.default available");
138
- } else {
139
- hasFailure = true;
140
- console.log("FAIL opencode agent.default missing");
141
- }
142
-
143
- const requiredCommands = managedBlocks
144
- .filter((block) => block.startsWith("opencode.jsonc:command."))
145
- .map((block) => block.replace("opencode.jsonc:command.", ""));
146
-
147
- if (requiredCommands.length === 0) {
148
- console.log("PASS no managed opencode commands required");
149
- } else {
150
- const missingCommands = requiredCommands.filter((command) => !opencodeConfig.command?.[command]);
151
- if (missingCommands.length === 0) {
152
- console.log(`PASS opencode managed commands available (${requiredCommands.length})`);
153
- } else {
154
- hasFailure = true;
155
- console.log(`FAIL opencode command entries missing: ${missingCommands.join(", ")}`);
156
- }
157
- }
158
-
159
- const requiredAgents = managedBlocks
160
- .filter((block) => block.startsWith("opencode.jsonc:agent.") && !block.endsWith(".default"))
161
- .map((block) => block.replace("opencode.jsonc:agent.", ""));
162
-
163
- if (requiredAgents.length === 0) {
164
- console.log("PASS no managed opencode agents required");
165
- } else {
166
- const missingAgents = requiredAgents.filter((agent) => !opencodeConfig.agent?.[agent]);
167
- if (missingAgents.length === 0) {
168
- console.log(`PASS opencode managed agents available (${requiredAgents.length})`);
169
- } else {
170
- hasFailure = true;
171
- console.log(`FAIL opencode agent entries missing: ${missingAgents.join(", ")}`);
172
- }
173
- }
174
- } catch {
175
- hasFailure = true;
176
- console.log("FAIL opencode.jsonc is not valid JSONC");
177
- }
178
- }
179
-
180
- const packageJsonPath = path.join(cwd, "package.json");
181
- if (await exists(packageJsonPath)) {
182
- try {
183
- const packageJson = await readJson(packageJsonPath);
184
- const scripts = packageJson.scripts ?? {};
185
-
186
- if (!scripts.validate) {
187
- hasWarning = true;
188
- console.log("WARN package.json has no validate script");
189
- }
190
- } catch {
191
- hasFailure = true;
192
- console.log("FAIL package.json is not valid JSON");
193
- }
194
- }
195
-
196
- const finalStatus = hasFailure ? "FAIL" : hasWarning ? "PASS_WITH_NOTES" : "PASS";
197
- console.log(`Final status: ${finalStatus}`);
198
-
199
- if (hasFailure) {
200
- process.exitCode = 1;
201
- }
202
- }
@@ -1,382 +0,0 @@
1
- import { RequestClassifier } from "../core/request-classifier.js";
2
- import { ExecutionPlanner } from "../core/execution-planner.js";
3
- import { WorkflowStateMachine } from "../core/workflow-state-machine.js";
4
- import { BranchGate } from "../core/gates/branch-gate.js";
5
- import { EvidenceLedger } from "../core/evidence/evidence-ledger.js";
6
- import { DelegationController } from "../core/delegation-controller.js";
7
- import { OpenCodeAdapter } from "../core/runtime/opencode-adapter.js";
8
- import { runCollectEvidence } from "./collect-evidence.js";
9
- import { QualityGuard } from "../core/validation/quality-guard.js";
10
- import { HandoffEngine } from "../core/handoff/handoff-engine.js";
11
- import { HealerEngine } from "../core/healing/healer-engine.js";
12
- import { createRuntimeRemediationExecutor } from "../core/healing/runtime-remediation-executor.js";
13
- import { isRecoverableGateFailure, isTerminalFailure } from "../core/statuses.js";
14
- import { Finalizer } from "../core/finalization/finalizer.js";
15
- import fs from "node:fs/promises";
16
- import path from "node:path";
17
- import { execSync } from "node:child_process";
18
-
19
- async function captureReadOnlyState(cwd) {
20
- const finalizer = new Finalizer({ cwd });
21
- const snapshot = await finalizer.snapshotManager.capture();
22
-
23
- const execGit = (args) => {
24
- try {
25
- return execSync(`git ${args}`, { cwd, encoding: "utf8", stdio: "pipe" }).trim();
26
- } catch {
27
- return "";
28
- }
29
- };
30
-
31
- const head = execGit("rev-parse HEAD");
32
- const branch = execGit("rev-parse --abbrev-ref HEAD");
33
- const indexTree = execGit("write-tree");
34
- const porcelainStatus = execGit("status --porcelain");
35
-
36
- return {
37
- snapshot,
38
- head,
39
- branch,
40
- indexTree,
41
- porcelainStatus
42
- };
43
- }
44
-
45
- function compareReadOnlyState(before, after) {
46
- const violations = [];
47
-
48
- // 1. Compare branch
49
- if (before.branch !== after.branch) {
50
- violations.push(`Branch changed from '${before.branch}' to '${after.branch}'`);
51
- }
52
-
53
- // 2. Compare HEAD
54
- if (before.head !== after.head) {
55
- violations.push(`Git HEAD changed from '${before.head}' to '${after.head}' (commit or reset made inside read-only task)`);
56
- }
57
-
58
- // 3. Compare index tree
59
- if (before.indexTree !== after.indexTree) {
60
- violations.push(`Git index tree changed from '${before.indexTree}' to '${after.indexTree}'`);
61
- }
62
-
63
- // 4. Compare porcelain status
64
- if (before.porcelainStatus !== after.porcelainStatus) {
65
- violations.push(`Git status changed. Before:\n${before.porcelainStatus}\nAfter:\n${after.porcelainStatus}`);
66
- }
67
-
68
- // 5. Compare workspace files (SHA-256 hashes)
69
- const beforeFiles = before.snapshot.files || {};
70
- const afterFiles = after.snapshot.files || {};
71
-
72
- for (const [file, info] of Object.entries(afterFiles)) {
73
- const prevInfo = beforeFiles[file];
74
- if (!prevInfo) {
75
- violations.push(`File added: ${file}`);
76
- } else if (prevInfo.sha256 !== info.sha256) {
77
- violations.push(`File content modified: ${file}`);
78
- }
79
- }
80
- for (const file of Object.keys(beforeFiles)) {
81
- if (!afterFiles[file]) {
82
- violations.push(`File deleted: ${file}`);
83
- }
84
- }
85
-
86
- return violations;
87
- }
88
-
89
- function slugify(text) {
90
- return text
91
- .toLowerCase()
92
- .replace(/[^a-z0-9]+/g, "-")
93
- .replace(/^-+|-+$/g, "")
94
- .slice(0, 32) || "task";
95
- }
96
-
97
- function combineValidation(evidence, quality) {
98
- const statuses = [evidence.internalStatus, quality.overallStatus];
99
- let overallStatus = "PASS";
100
- if (statuses.includes("BLOCKED")) overallStatus = "BLOCKED";
101
- else if (statuses.includes("FAIL")) overallStatus = "FAIL";
102
- else if (statuses.includes("FAIL_DELEGATION_GATE")) overallStatus = "FAIL_DELEGATION_GATE";
103
- else if (statuses.includes("FAIL_QUALITY_GATE")) overallStatus = "FAIL_QUALITY_GATE";
104
- else if (statuses.includes("PASS_WITH_NOTES")) overallStatus = "PASS_WITH_NOTES";
105
- return { overallStatus, evidence, quality };
106
- }
107
-
108
- /**
109
- * runExecute - Coordinators natural request execution.
110
- */
111
- export async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
112
- if (!naturalRequest || !naturalRequest.trim()) {
113
- throw new Error("Missing request. Please provide a natural request string via positional arguments or --request flag.");
114
- }
115
-
116
- const taskSlug = taskSlugOverride || slugify(naturalRequest);
117
- console.log(`\n[AI WORKFLOW] Executing natural request intake [slug: ${taskSlug}]...\n`);
118
-
119
- const ledger = new EvidenceLedger({ cwd, workflowId: taskSlug });
120
- const delegationController = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
121
-
122
- let plan = null;
123
- try {
124
- const stateMachine = new WorkflowStateMachine();
125
-
126
- // 1. Classification
127
- const classifier = new RequestClassifier();
128
- const classification = classifier.classify(naturalRequest, { cwd });
129
- console.log(`[CLASSIFIED] Intent: ${classification.intent}, Mode: ${classification.mode}, Profile: ${classification.profile}`);
130
- stateMachine.transitionTo("CLASSIFIED");
131
-
132
- await delegationController.route(naturalRequest, classification);
133
-
134
- const routingDecision = classification.routingDecision || {};
135
- if (routingDecision.permissionDecision === "blocked") {
136
- stateMachine.transitionTo("BLOCKED");
137
- throw new Error(`[ROUTE BLOCKED] ${routingDecision.reason}`);
138
- }
139
- if (routingDecision.permissionDecision === "rerouted") {
140
- console.log(`[REROUTED] ${routingDecision.reason}`);
141
- }
142
-
143
- // 2. Planning
144
- const planner = new ExecutionPlanner({ cwd });
145
- plan = planner.plan(classification, taskSlug);
146
- console.log(`[PLANNED] Owner: ${plan.owner}, Remediation limit: ${plan.remediationLimit}`);
147
- stateMachine.transitionTo("PLANNED");
148
-
149
- // 3. Branch Gate
150
- const branchGate = new BranchGate({ memoryDir: path.join(cwd, ".ai-workflow"), cwd });
151
- const gateResult = branchGate.check(undefined, {
152
- autoRecover: true,
153
- taskSlug,
154
- readOnly: !plan.branchNeeded
155
- });
156
-
157
- if (gateResult.blocked) {
158
- stateMachine.transitionTo("BLOCKED");
159
- throw new Error(`[GATE BLOCKED] ${gateResult.reason}`);
160
- }
161
- console.log(`[PASS] Branch Gate: ${gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : `${gateResult.branch} is authorized`}.`);
162
- stateMachine.transitionTo("BRANCH_READY");
163
-
164
- const checkBranchSafety = () => {
165
- if (plan.branchNeeded) {
166
- const currentBranch = branchGate.getCurrentBranch();
167
- if (branchGate.protectedBranches.includes(currentBranch)) {
168
- stateMachine.transitionTo("BLOCKED");
169
- throw new Error(`[WORKFLOW BLOCKED] Security violation: current branch is protected '${currentBranch}'!`);
170
- }
171
- }
172
- };
173
-
174
- // 4. Spec template creation (for full/deep mode)
175
- if (plan.specPath) {
176
- const fullSpecPath = path.join(cwd, plan.specPath);
177
- const specExists = await fs.access(fullSpecPath).then(() => true).catch(() => false);
178
- if (!specExists) {
179
- await fs.mkdir(path.dirname(fullSpecPath), { recursive: true });
180
- const specTemplatePath = path.join(cwd, ".ai-workflow/templates/specs/standard.md");
181
- const templateContent = await fs.readFile(specTemplatePath, "utf8").catch(() => {
182
- return `# [STANDARD] Specification: ${classification.request}\n\n## Metadata\n\n- ID: SPEC-${taskSlug}\n- Author: Spec-Engineer\n- Status: DRAFT\n- Date: ${new Date().toISOString().split("T")[0]}\n\n## Functional Requirements\n\n- ${classification.request}\n\n## Technical Implementation Plan\n\n### Files to Create/Modify\n\n- \`src/index.js\`\n\n## Acceptance Criteria\n\n- [ ] Implemented successfully\n\n## Testing Strategy\n\n- [ ] Behavior tests pass`;
183
- });
184
- await fs.writeFile(fullSpecPath, templateContent);
185
- console.log(`[EXECUTE] Created DRAFT specification template at: ${plan.specPath}`);
186
- }
187
- }
188
-
189
- // 5. Delegation
190
- stateMachine.transitionTo("DELEGATED");
191
- stateMachine.transitionTo("IMPLEMENTING");
192
-
193
- let promptMsg = classification.request;
194
- if (plan.specPath) {
195
- promptMsg = `Please review and fill in the specification file at: ${plan.specPath}. Make sure to change the Status field to 'APPROVED' in the Metadata section, and then implement the behavior described.`;
196
- }
197
-
198
- let readOnlyStateBefore = null;
199
- if (!plan.branchNeeded) {
200
- readOnlyStateBefore = await captureReadOnlyState(cwd);
201
- }
202
-
203
- const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded });
204
-
205
- if (!runResult.success) {
206
- console.error(`[EXECUTE] OpenCode adapter execution reported failure: ${runResult.error || "Unknown error"}`);
207
- stateMachine.transitionTo("BLOCKED");
208
- throw new Error(`[WORKFLOW BLOCKED] OpenCode runtime execution failed: ${runResult.error || "Unknown error"}`);
209
- }
210
-
211
- if (!plan.branchNeeded) {
212
- const readOnlyStateAfter = await captureReadOnlyState(cwd);
213
- const violations = compareReadOnlyState(readOnlyStateBefore, readOnlyStateAfter);
214
- if (violations.length > 0) {
215
- console.error(`[EXECUTE] Read-only confinement violation detected:`);
216
- for (const v of violations) {
217
- console.error(` - ${v}`);
218
- }
219
- stateMachine.transitionTo("BLOCKED");
220
- throw new Error(`[WORKFLOW BLOCKED] Read-only confinement violation: files were written/modified, index or commits modified: \n${violations.join("\n")}`);
221
- }
222
-
223
- stateMachine.transitionTo("IMPLEMENTED");
224
- stateMachine.transitionTo("VALIDATING");
225
- stateMachine.transitionTo("COMPLETED");
226
-
227
- console.log("\n--- Final Handoff ---");
228
- console.log(`\n[AI WORKFLOW COMPLETE] COMPLETED`);
229
- console.log(`Handoff Packet: [IN-MEMORY] (No handoff file written in read-only mode)\n`);
230
-
231
- return {
232
- overallStatus: "PASS",
233
- evidence: {
234
- internalStatus: "PASS",
235
- commandsRun: []
236
- },
237
- quality: {
238
- overallStatus: "PASS"
239
- },
240
- stateHistory: stateMachine.getHistory()
241
- };
242
- }
243
-
244
- stateMachine.transitionTo("IMPLEMENTED");
245
- checkBranchSafety();
246
-
247
- // Capture snapshot immediately after last implementation write
248
- const finalizer = new Finalizer({ cwd });
249
- let lastWriteSnapshot = await finalizer.snapshotManager.capture();
250
-
251
- // 6. Validation
252
- stateMachine.transitionTo("VALIDATING");
253
- const validateWorkflow = async () => {
254
- const evidence = await runCollectEvidence({
255
- cwd,
256
- exitOnError: false,
257
- taskSlug,
258
- mode: plan.mode,
259
- profile: plan.profile,
260
- branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
261
- userRequest: naturalRequest,
262
- explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map(a => a.trim()) : []
263
- });
264
- const quality = await new QualityGuard({ cwd, taskSlug, mode: plan.mode }).verify();
265
- return combineValidation(evidence, quality);
266
- };
267
-
268
- let result = await delegationController.validate(taskSlug, plan.mode, plan.profile, validateWorkflow);
269
-
270
- // 7. Bounded Remediation
271
- if (isRecoverableGateFailure(result.overallStatus)) {
272
- console.log(`\n[REMEDIATION REQUIRED] ${result.overallStatus}. Starting bounded ${plan.mode} remediation.`);
273
- const executor = createRuntimeRemediationExecutor(cwd, ledger);
274
- const healer = new HealerEngine({ cwd, mode: plan.mode, taskSlug, ledger });
275
-
276
- result = await healer.run({
277
- initialResult: result,
278
- validate: async () => {
279
- stateMachine.transitionTo("REVALIDATING");
280
- const res = await delegationController.validate(taskSlug, plan.mode, plan.profile, validateWorkflow);
281
- return res;
282
- },
283
- remediate: async (remedCtx) => {
284
- stateMachine.transitionTo("REMEDIATING");
285
- const remRes = await executor(remedCtx);
286
- if (remRes.applied) {
287
- checkBranchSafety();
288
- // Update snapshot because remediation wrote new changes
289
- lastWriteSnapshot = await finalizer.snapshotManager.capture();
290
- }
291
- return remRes;
292
- }
293
- });
294
- }
295
-
296
- if (isTerminalFailure(result.overallStatus)) {
297
- stateMachine.transitionTo("BLOCKED");
298
- throw new Error(`[WORKFLOW BLOCKED] ${result.overallStatus}: Validation could not be resolved safely.`);
299
- }
300
-
301
- checkBranchSafety();
302
-
303
- // 8. Finalizer / Stabilization Loop
304
- let integrity = await finalizer.verifyIntegrity(lastWriteSnapshot);
305
- let revalidationCount = 0;
306
- const maxRevalidations = 2;
307
-
308
- while (!integrity.valid && revalidationCount < maxRevalidations) {
309
- console.log(`\n[FINALIZER WARNING] Workspace mutated during validation! Re-running validation to stabilize.`);
310
- console.log(`Changes detected:`, integrity.changes);
311
-
312
- checkBranchSafety();
313
-
314
- // Update snapshot to current workspace state before validation runs
315
- lastWriteSnapshot = await finalizer.snapshotManager.capture();
316
- revalidationCount++;
317
-
318
- // Re-run validation
319
- result = await delegationController.validate(taskSlug, plan.mode, plan.profile, validateWorkflow);
320
-
321
- checkBranchSafety();
322
-
323
- // Verify integrity against the snapshot taken before this validation iteration
324
- integrity = await finalizer.verifyIntegrity(lastWriteSnapshot);
325
- }
326
-
327
- let finalStatus = result.overallStatus;
328
- if (!integrity.valid) {
329
- console.log(`\n[FINALIZER BLOCKED] BLOCKED: workspace did not stabilize`);
330
- console.log(`Final changes detected:`, integrity.changes);
331
- finalStatus = "FAIL_QUALITY_GATE";
332
- } else {
333
- // Workspace stabilized, now run Artifact Fidelity Gate
334
- const fidelityResult = await finalizer.verifyFidelity({
335
- userRequest: naturalRequest,
336
- projectContext: result.evidence?.deliveryDecision?.projectContext || null,
337
- deliveryDecision: result.evidence?.deliveryDecision || null,
338
- changedFiles: result.evidence?.changedFiles || [],
339
- evidence: result.evidence || null,
340
- explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map(a => a.trim()) : []
341
- });
342
- if (!fidelityResult.passed) {
343
- console.log(`\n[FINALIZER BLOCKED] BLOCKED: Artifact fidelity check failed!`);
344
- console.log(`Reason: ${fidelityResult.reason}`);
345
- finalStatus = "FAIL_QUALITY_GATE";
346
- }
347
- }
348
-
349
- const finalState = finalStatus === "PASS"
350
- ? "COMPLETED"
351
- : finalStatus === "PASS_WITH_NOTES"
352
- ? "COMPLETED_WITH_NOTES"
353
- : "BLOCKED";
354
-
355
- stateMachine.transitionTo(finalState);
356
-
357
- console.log("\n--- Final Handoff ---");
358
- const handoffEngine = new HandoffEngine({ cwd });
359
- const handoffPath = await handoffEngine.generate({
360
- taskId: taskSlug,
361
- status: finalState,
362
- specPaths: plan.specPath ? [plan.specPath] : [],
363
- evidence: result.evidence,
364
- nextActions: `Profile ${plan.profile}. Observed execution completed with status ${finalState}.`
365
- });
366
-
367
- console.log(`\n[AI WORKFLOW COMPLETE] ${finalState}`);
368
- console.log(`Handoff Packet: ${path.relative(cwd, handoffPath)}\n`);
369
-
370
- return { ...result, overallStatus: finalStatus, stateHistory: stateMachine.getHistory() };
371
- } finally {
372
- if (typeof plan !== "undefined" && plan && !plan.branchNeeded) {
373
- console.log(`\n[READ-ONLY LEDGER] (In-Memory/Stdout)\n${JSON.stringify(ledger.getEvents(), null, 2)}\n`);
374
- } else {
375
- try {
376
- await ledger.save(`.ai-workflow/history/${taskSlug}-ledger.json`);
377
- } catch (err) {
378
- // ignore or log
379
- }
380
- }
381
- }
382
- }