@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,344 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { StackDetector } from "./stack-detector.js";
4
-
5
- function parseRequest(request = "") {
6
- const req = request.toLowerCase();
7
-
8
- let targetStack = null;
9
- if (/\b(vue|nuxt|quasar)\b/.test(req)) targetStack = "vue";
10
- else if (/\b(react|next)\b/.test(req)) targetStack = "react";
11
- else if (/\b(angular|svelte)\b/.test(req)) targetStack = "svelte-or-angular";
12
- else if (/\b(laravel|django|rails|express|nest|fastify)\b/.test(req)) {
13
- if (/\blaravel\b/.test(req)) targetStack = "laravel";
14
- else targetStack = "backend";
15
- }
16
- else if (/\b(wordpress|wp)\b/.test(req)) targetStack = "wordpress";
17
- else if (/\b(cli|command|bin)\b/.test(req)) targetStack = "cli";
18
-
19
- let intent = "implementation";
20
- if (/\b(document|doc|readme)\b/.test(req)) intent = "docs";
21
- else if (/\b(audit|security|inspect|dependencies)\b/.test(req)) intent = "audit";
22
- else if (/\b(release|publish|deploy)\b/.test(req)) intent = "release";
23
- else if (/\b(prototype|standalone html)\b/.test(req)) intent = "prototype";
24
-
25
- return { targetStack, intent };
26
- }
27
-
28
- export class DeliveryDecisionEngine {
29
- constructor({ cwd = process.cwd() } = {}) {
30
- this.cwd = cwd;
31
- this.stackDetector = new StackDetector({ cwd });
32
- }
33
-
34
- async determineClass(changedFiles = []) {
35
- if (changedFiles.length === 0) {
36
- return "empty";
37
- }
38
-
39
- const isDocs = changedFiles.every(file =>
40
- file.endsWith(".md") || file.endsWith(".txt") || file.startsWith("docs/")
41
- );
42
- if (isDocs) {
43
- return "docs-only";
44
- }
45
-
46
- const stacks = await this.stackDetector.detect();
47
-
48
- let usesJsFramework = false;
49
- try {
50
- const pkgPath = path.join(this.cwd, "package.json");
51
- const pkgContent = await fs.readFile(pkgPath, "utf8");
52
- const pkg = JSON.parse(pkgContent);
53
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
54
-
55
- const frameworks = ["react", "next", "vue", "nuxt", "svelte", "solid-js", "preact", "quasar", "@angular/core"];
56
- usesJsFramework = frameworks.some(fw => deps[fw] !== undefined);
57
- } catch {
58
- // ignore
59
- }
60
-
61
- const isWordPress = await checkWordPressDirectRoot(this.cwd);
62
-
63
- const hasFrontendFiles = changedFiles.some(file =>
64
- /\.(html?|css|scss|sass|less|jsx|tsx|vue|svelte)$/i.test(file) ||
65
- (/\.js$/i.test(file) && !file.includes("config") && !file.includes("test"))
66
- );
67
-
68
- if (isWordPress) {
69
- return "wordpress-php";
70
- }
71
-
72
- if (usesJsFramework && hasFrontendFiles) {
73
- return "framework-native-frontend";
74
- }
75
-
76
- if (hasFrontendFiles) {
77
- return "standalone-web";
78
- }
79
-
80
- if (stacks.includes("python") || stacks.includes("php") || (stacks.includes("node") && !usesJsFramework)) {
81
- return "backend-service";
82
- }
83
-
84
- return "generic-code";
85
- }
86
-
87
- async detectContext() {
88
- const stacks = await this.stackDetector.detect();
89
-
90
- let usesJsFramework = null;
91
- let hasPackageJson = false;
92
- let isCli = false;
93
- try {
94
- const pkgPath = path.join(this.cwd, "package.json");
95
- const pkgContent = await fs.readFile(pkgPath, "utf8");
96
- hasPackageJson = true;
97
- const pkg = JSON.parse(pkgContent);
98
- const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
99
- if (pkg.bin || pkg.name === "@williambeto/ai-workflow") {
100
- isCli = true;
101
- }
102
-
103
- const frameworks = ["react", "next", "vue", "nuxt", "svelte", "solid-js", "preact", "quasar", "@angular/core"];
104
- for (const fw of frameworks) {
105
- if (deps[fw] !== undefined) {
106
- usesJsFramework = fw;
107
- break;
108
- }
109
- }
110
- } catch {}
111
-
112
- let isLaravel = false;
113
- let hasComposerJson = false;
114
- try {
115
- const composerPath = path.join(this.cwd, "composer.json");
116
- const composerContent = await fs.readFile(composerPath, "utf8");
117
- hasComposerJson = true;
118
- const composer = JSON.parse(composerContent);
119
- const reqs = { ...(composer.require || {}), ...(composer.requireDev || {}) };
120
- if (reqs["laravel/framework"]) {
121
- isLaravel = true;
122
- }
123
- } catch {}
124
-
125
- const isWordPress = await checkWordPressDirectRoot(this.cwd);
126
-
127
- let isEmpty = true;
128
- try {
129
- const entries = await fs.readdir(this.cwd);
130
- const meaningful = entries.filter(e => !e.startsWith(".") && !["node_modules", "EVIDENCE.json"].includes(e));
131
- if (meaningful.length > 0) {
132
- if (meaningful.length === 1 && meaningful[0] === "package.json") {
133
- try {
134
- const content = await fs.readFile(path.join(this.cwd, "package.json"), "utf8");
135
- const pkg = JSON.parse(content);
136
- if (!pkg.dependencies && !pkg.devDependencies) {
137
- isEmpty = true;
138
- } else {
139
- isEmpty = false;
140
- }
141
- } catch {
142
- isEmpty = true;
143
- }
144
- } else {
145
- isEmpty = false;
146
- }
147
- }
148
- } catch {
149
- isEmpty = true;
150
- }
151
-
152
- let projectState = isEmpty ? "empty" : "active";
153
- let detectedStackType = "unknown";
154
- let detectedStacks = [];
155
-
156
- if (isWordPress) {
157
- detectedStackType = "WordPress context";
158
- detectedStacks.push("wordpress");
159
- } else if (isLaravel) {
160
- detectedStackType = "backend context";
161
- detectedStacks.push("laravel");
162
- } else if (usesJsFramework) {
163
- detectedStackType = "existing matching stack";
164
- detectedStacks.push(usesJsFramework);
165
- } else if (isCli) {
166
- detectedStackType = "CLI package context";
167
- detectedStacks.push("node");
168
- } else if (stacks.includes("node")) {
169
- detectedStackType = "existing matching stack";
170
- detectedStacks.push("node");
171
- } else if (stacks.includes("python") || stacks.includes("php")) {
172
- detectedStackType = "backend context";
173
- detectedStacks.push(...stacks);
174
- } else if (projectState === "empty") {
175
- detectedStackType = "empty project";
176
- }
177
-
178
- return {
179
- projectState,
180
- detectedStackType,
181
- detectedStacks,
182
- isWordPress,
183
- isLaravel,
184
- usesJsFramework,
185
- isCli
186
- };
187
- }
188
-
189
- async determineDecision(input = {}) {
190
- const userRequest = input.userRequest || "";
191
- const explicitApprovals = input.explicitApprovals || [];
192
-
193
- const ctx = input.projectContext || await this.detectContext();
194
- const projectState = ctx.projectState || "active";
195
- const detectedStacks = ctx.detectedStacks || [];
196
- const isWordPress = ctx.isWordPress || false;
197
- const isLaravel = ctx.isLaravel || false;
198
- const isCli = ctx.isCli || false;
199
- const usesJsFramework = ctx.usesJsFramework || null;
200
-
201
- const parsed = parseRequest(userRequest);
202
- const targetStack = parsed.targetStack;
203
- const intent = parsed.intent;
204
-
205
- let decision = "IMPLEMENT_IN_EXISTING_CONTEXT";
206
- let allowedArtifactShape = "generic-code";
207
- let forbiddenSubstitutes = ["success-without-validation"];
208
- let validationRequired = ["build-or-equivalent"];
209
- let approvalRequired = false;
210
-
211
- if (intent === "audit") {
212
- decision = "READ_ONLY_AUDIT";
213
- allowedArtifactShape = "audit-report";
214
- forbiddenSubstitutes.push("any-code-mutation");
215
- validationRequired = [];
216
- }
217
- else if (intent === "docs") {
218
- decision = "DOCS_ONLY";
219
- allowedArtifactShape = "markdown";
220
- forbiddenSubstitutes.push("unjustified-code-mutation");
221
- validationRequired = [];
222
- }
223
- else if (intent === "release") {
224
- decision = "RELEASE_OR_DEPLOY_REQUIRES_APPROVAL";
225
- allowedArtifactShape = "package-tarball";
226
- forbiddenSubstitutes.push("publish-without-approval");
227
- validationRequired = ["release-readiness-or-equivalent"];
228
-
229
- const hasApproval = explicitApprovals.includes("npm-publish") || explicitApprovals.includes("deploy-production");
230
- if (hasApproval) {
231
- decision = "RELEASE_OR_DEPLOY_APPROVED";
232
- approvalRequired = true;
233
- }
234
- }
235
- else if (intent === "prototype") {
236
- decision = "CREATE_STANDALONE_PROTOTYPE";
237
- allowedArtifactShape = "standalone-html";
238
- validationRequired = [];
239
- }
240
- else if (projectState === "empty") {
241
- decision = "SCAFFOLD_AND_IMPLEMENT";
242
- if (targetStack === "vue" || targetStack === "react") {
243
- allowedArtifactShape = "runnable-framework-app";
244
- forbiddenSubstitutes.push("standalone-html-as-framework-delivery", "loose-framework-files-without-runtime");
245
- validationRequired.push("dependency-install-or-verification", "runtime-smoke-or-equivalent");
246
- }
247
- } else {
248
- const hasConflict = (targetStack === "vue" && detectedStacks.includes("react")) ||
249
- (targetStack === "react" && detectedStacks.includes("vue")) ||
250
- (targetStack === "laravel" && !detectedStacks.includes("laravel") && detectedStacks.length > 0) ||
251
- (targetStack === "wordpress" && !detectedStacks.includes("wordpress") && detectedStacks.length > 0);
252
-
253
- if (hasConflict) {
254
- decision = "BLOCK_STACK_CONFLICT";
255
- } else if (targetStack === "laravel" && !detectedStacks.includes("laravel")) {
256
- decision = "BLOCK_UNSUPPORTED_CONTEXT";
257
- } else if (targetStack === "wordpress" && !detectedStacks.includes("wordpress")) {
258
- decision = "BLOCK_UNSUPPORTED_CONTEXT";
259
- } else {
260
- if (targetStack === "vue" || targetStack === "react") {
261
- decision = "IMPLEMENT_IN_EXISTING_CONTEXT";
262
- allowedArtifactShape = "runnable-framework-app";
263
- forbiddenSubstitutes.push("standalone-html-as-framework-delivery", "loose-framework-files-without-runtime");
264
- validationRequired.push("dependency-install-or-verification", "runtime-smoke-or-equivalent");
265
- } else if (targetStack === "cli" || isCli) {
266
- decision = "CLI_COMMAND_INTEGRATION";
267
- allowedArtifactShape = "bin-entry";
268
- }
269
- }
270
- }
271
-
272
- if (userRequest && userRequest.trim().length < 3) {
273
- decision = "BLOCK_UNCLEAR_SCOPE";
274
- }
275
-
276
- return {
277
- intent: intent === "implementation" ? "implementation" : intent,
278
- requestedOutcome: targetStack === "vue" || targetStack === "react" ? "page" : (targetStack === "wordpress" ? "plugin-or-theme" : null),
279
- requestedStack: targetStack,
280
- requestedPlatform: targetStack === "wordpress" ? "wordpress" : null,
281
- projectState,
282
- detectedStacks,
283
- decision,
284
- allowedArtifactShape,
285
- forbiddenSubstitutes,
286
- validationRequired,
287
- approvalRequired: explicitApprovals.length > 0
288
- };
289
- }
290
- }
291
-
292
- async function checkWordPressDirectRoot(cwd) {
293
- // Check standard paths
294
- const hasWpConfig = await fs.access(path.join(cwd, "wp-config.php")).then(() => true).catch(() => false);
295
- const hasWpContent = await fs.access(path.join(cwd, "wp-content")).then(() => true).catch(() => false);
296
- if (hasWpConfig || hasWpContent) {
297
- return true;
298
- }
299
-
300
- // Check direct Theme root
301
- const hasStyleCss = await fs.access(path.join(cwd, "style.css")).then(() => true).catch(() => false);
302
- if (hasStyleCss) {
303
- try {
304
- const content = await fs.readFile(path.join(cwd, "style.css"), "utf8");
305
- if (/\bTheme Name\s*:/i.test(content) || /\bTheme URI\s*:/i.test(content)) {
306
- const otherIndicators = [
307
- "functions.php",
308
- "theme.json",
309
- "index.php",
310
- "front-page.php",
311
- "single.php",
312
- "page.php",
313
- "archive.php",
314
- "template-parts"
315
- ];
316
- for (const ind of otherIndicators) {
317
- const exists = await fs.access(path.join(cwd, ind)).then(() => true).catch(() => false);
318
- if (exists) {
319
- return true;
320
- }
321
- }
322
- }
323
- } catch {
324
- // ignore
325
- }
326
- }
327
-
328
- // Check direct Plugin root
329
- try {
330
- const files = await fs.readdir(cwd);
331
- for (const file of files) {
332
- if (file.endsWith(".php")) {
333
- const content = await fs.readFile(path.join(cwd, file), "utf8");
334
- if (/\bPlugin Name\s*:/i.test(content)) {
335
- return true;
336
- }
337
- }
338
- }
339
- } catch {
340
- // ignore
341
- }
342
-
343
- return false;
344
- }
@@ -1,245 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import fs from "node:fs/promises";
3
- import path from "node:path";
4
- import { QualityGuard } from "./quality-guard.js";
5
- import { DeliveryDecisionEngine } from "./delivery-decision-engine.js";
6
- import { ArtifactFidelityGate } from "./artifact-fidelity-gate.js";
7
- import { VisualVerifier } from "./visual-verifier.js";
8
-
9
- const VALIDATION_KINDS = new Set(["test", "build", "typecheck", "lint", "security", "smoke", "validate", "other"]);
10
-
11
- function publicStatus(status) {
12
- if (status === "PASS") return "COMPLETED";
13
- if (status === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
14
- return "BLOCKED";
15
- }
16
-
17
- function inferTaskKind(task = {}) {
18
- const explicit = String(task.kind || "").trim().toLowerCase();
19
- if (VALIDATION_KINDS.has(explicit)) return explicit;
20
-
21
- const value = `${task.name || ""} ${task.command || ""}`.toLowerCase();
22
- if (/\b(test|tests|vitest|jest|mocha|ava|tap|pytest|phpunit|rspec|cypress|playwright)\b/.test(value)) return "test";
23
- if (/\b(typecheck|tsc\b|mypy|pyright)\b/.test(value)) return "typecheck";
24
- if (/\b(lint|eslint|stylelint|ruff|flake8|pylint)\b/.test(value)) return "lint";
25
- if (/\b(build|bundle|compile)\b/.test(value)) return "build";
26
- if (/\b(audit|security|sast|scan)\b/.test(value)) return "security";
27
- if (/\b(smoke|curl|preview|serve)\b/.test(value)) return "smoke";
28
- if (/\bvalidate\b/.test(value)) return "validate";
29
- return "other";
30
- }
31
-
32
- export class EvidenceCollector {
33
- constructor({ cwd, maxLogLength = 2000, timeout = 60000, taskSlug = null, mode = null, profile = "generic", branchRecovery = "NOT_RECORDED", userRequest = null, explicitApprovals = [], visualDist = null, port = 8080 } = {}) {
34
- this.cwd = cwd;
35
- this.maxLogLength = maxLogLength;
36
- this.timeout = timeout;
37
- this.taskSlug = taskSlug;
38
- this.mode = mode;
39
- this.profile = profile;
40
- this.branchRecovery = branchRecovery;
41
- this.userRequest = userRequest;
42
- this.explicitApprovals = explicitApprovals;
43
- this.visualDist = visualDist;
44
- this.port = Number(port || 8080);
45
- }
46
-
47
- async findUserRequest() {
48
- if (this.userRequest) return this.userRequest;
49
-
50
- if (this.taskSlug) {
51
- const ledgerPath = path.join(this.cwd, `.ai-workflow/history/${this.taskSlug}-ledger.json`);
52
- try {
53
- const content = await fs.readFile(ledgerPath, "utf8");
54
- const events = JSON.parse(content);
55
- const routeEvent = events.find(e => e.eventType === "routing");
56
- if (routeEvent && routeEvent.data?.request) {
57
- return routeEvent.data.request;
58
- }
59
- const startEvent = events.find(e => e.eventType === "implementation_start");
60
- if (startEvent && startEvent.data?.prompt) {
61
- return startEvent.data.prompt;
62
- }
63
- } catch {}
64
- }
65
-
66
- try {
67
- const dir = path.join(this.cwd, ".ai-workflow/history");
68
- const files = await fs.readdir(dir);
69
- for (const file of files) {
70
- if (file.endsWith("-ledger.json")) {
71
- const content = await fs.readFile(path.join(dir, file), "utf8");
72
- const events = JSON.parse(content);
73
- const routeEvent = events.find(e => e.eventType === "routing");
74
- if (routeEvent && routeEvent.data?.request) {
75
- return routeEvent.data.request;
76
- }
77
- }
78
- }
79
- } catch {}
80
-
81
- try {
82
- const { execSync } = await import("node:child_process");
83
- const branch = execSync("git branch --show-current", { cwd: this.cwd, encoding: "utf8" }).trim();
84
- if (branch && branch !== "main" && branch !== "master" && branch.startsWith("feat/")) {
85
- return branch.replace("feat/", "").replaceAll("-", " ");
86
- }
87
- } catch {}
88
-
89
- return "";
90
- }
91
-
92
- runTask(task) {
93
- const kind = inferTaskKind(task);
94
- if (task.presetStatus) {
95
- return { name: task.name, command: task.command, kind, status: task.presetStatus, exitCode: null, summary: task.summary, output: "" };
96
- }
97
-
98
- let cmd = task.command;
99
- let args = [];
100
- if (Array.isArray(cmd)) {
101
- const [c, ...a] = cmd;
102
- cmd = c;
103
- args = a;
104
- } else if (typeof cmd === "string") {
105
- const parts = cmd.split(/\s+/);
106
- cmd = parts[0];
107
- args = parts.slice(1);
108
- }
109
-
110
- const isWin32 = process.platform === "win32";
111
- const isUncPath = typeof this.cwd === "string" && (this.cwd.startsWith("\\\\") || this.cwd.startsWith("//"));
112
- const shellOption = isWin32 && isUncPath ? "powershell.exe" : (isWin32 ? true : false);
113
-
114
- const result = spawnSync(cmd, args, {
115
- cwd: this.cwd,
116
- shell: shellOption,
117
- encoding: "utf8",
118
- timeout: this.timeout
119
- });
120
-
121
- let status = result.status === 0 ? "PASS" : "FAIL";
122
- if (result.error) {
123
- status = "FAIL";
124
- if (result.error.code === "ETIMEDOUT") {
125
- status = "BLOCKED";
126
- }
127
- }
128
-
129
- let summary = status === "PASS" ? "Command completed successfully." : "Command failed.";
130
- if (result.error?.code === "ETIMEDOUT") {
131
- summary = `Command timed out after ${this.timeout / 1000}s.`;
132
- } else if (result.error) {
133
- summary = `Command execution error: ${result.error.message}`;
134
- }
135
-
136
- const rawOutput = `${result.stdout || ""}${result.stderr || ""}`.trim();
137
- const output = rawOutput.length > this.maxLogLength ? `${rawOutput.slice(0, this.maxLogLength)}\n... [TRUNCATED]` : rawOutput;
138
- return { name: task.name, command: task.command, kind, status, exitCode: result.status, signal: result.signal, summary, output };
139
- }
140
-
141
- async collect(tasks, { writeArtifact = false } = {}) {
142
- const qualityGuard = new QualityGuard({ cwd: this.cwd, taskSlug: this.taskSlug, mode: this.mode, evidenceWillBePersisted: writeArtifact });
143
- const implementation = qualityGuard.isImplementationTask();
144
- const executableBehavior = await qualityGuard.hasExecutableBehaviorChanges();
145
- const results = tasks.map((task) => this.runTask(task));
146
- const policyValidation = await qualityGuard.verify();
147
- const behaviorTests = results.filter((result) => result.kind === "test");
148
- const passingBehaviorTest = behaviorTests.some((result) => result.status === "PASS");
149
-
150
- const userRequest = await this.findUserRequest();
151
- const engine = new DeliveryDecisionEngine({ cwd: this.cwd });
152
- const projectContext = await engine.detectContext();
153
- const deliveryDecision = await engine.determineDecision({
154
- userRequest,
155
- projectContext,
156
- explicitApprovals: this.explicitApprovals || []
157
- });
158
-
159
- const fidelityGate = new ArtifactFidelityGate({
160
- cwd: this.cwd,
161
- deliveryClass: deliveryDecision.allowedArtifactShape === "runnable-framework-app" ? "framework-native-frontend" : (deliveryDecision.allowedArtifactShape === "wordpress" ? "wordpress-php" : "generic-code"),
162
- userRequest,
163
- explicitApprovals: this.explicitApprovals || []
164
- });
165
-
166
- const changedFiles = policyValidation.git?.changedFiles || [];
167
- const artifactFidelityCheck = await fidelityGate.verifyRequestFidelity({
168
- userRequest,
169
- deliveryDecision,
170
- projectContext,
171
- changedFiles
172
- });
173
-
174
- let overallStatus = "PASS";
175
- if (implementation && executableBehavior && tasks.length === 0) overallStatus = "BLOCKED";
176
- else if (results.some((result) => ["FAIL", "BLOCKED", "FAIL_QUALITY_GATE"].includes(result.status))) overallStatus = "FAIL_QUALITY_GATE";
177
- else if (executableBehavior && !passingBehaviorTest) overallStatus = "BLOCKED";
178
- else if (["FAIL", "BLOCKED", "FAIL_QUALITY_GATE"].includes(policyValidation.overallStatus)) overallStatus = "FAIL_QUALITY_GATE";
179
- else if (results.some((result) => result.status === "PASS_WITH_NOTES") || policyValidation.overallStatus === "PASS_WITH_NOTES") overallStatus = "PASS_WITH_NOTES";
180
-
181
- if (!artifactFidelityCheck || !artifactFidelityCheck.passed) {
182
- overallStatus = "BLOCKED";
183
- } else if (implementation) {
184
- if (!deliveryDecision || !deliveryDecision.decision || deliveryDecision.decision.startsWith("BLOCK") || deliveryDecision.decision.includes("BLOCKED") || deliveryDecision.decision === "REQUIRE_EXPLICIT_AUTHORIZATION" || deliveryDecision.decision === "RELEASE_OR_DEPLOY_REQUIRES_APPROVAL") {
185
- overallStatus = "BLOCKED";
186
- } else if (results.length === 0 && executableBehavior && deliveryDecision.decision !== "DOCUMENTATION_ONLY" && deliveryDecision.decision !== "DOCS_ONLY" && deliveryDecision.decision !== "READ_ONLY_AUDIT") {
187
- overallStatus = "BLOCKED";
188
- }
189
- }
190
-
191
- const limitations = [];
192
- if (implementation && executableBehavior && tasks.length === 0) limitations.push("No meaningful validation command was available for executable implementation work.");
193
- if (executableBehavior && behaviorTests.length === 0) {
194
- limitations.push("Executable behavior changed without a proportional automated behavior test. Build, lint, typecheck, smoke, screenshots, and manual review do not replace behavior tests.");
195
- }
196
- for (const [name, check] of Object.entries(policyValidation.checks || {})) {
197
- if (check.status === "PASS_WITH_NOTES" && check.reason) limitations.push(`${name}: ${check.reason}`);
198
- }
199
-
200
- let visualEvidence = null;
201
- if (this.visualDist) {
202
- console.log(`\n--- Capturing Visual Evidence ---`);
203
- console.log(`Serving ${this.visualDist} on port ${this.port}...`);
204
- try {
205
- const verifier = new VisualVerifier({ cwd: this.cwd, visualDist: this.visualDist, port: this.port });
206
- const screenshots = await verifier.captureScreenshots();
207
- visualEvidence = {
208
- desktop: path.relative(this.cwd, screenshots[0]),
209
- mobile: path.relative(this.cwd, screenshots[1])
210
- };
211
- console.log(`Visual evidence captured successfully:`);
212
- console.log(`- Desktop: ${visualEvidence.desktop}`);
213
- console.log(`- Mobile: ${visualEvidence.mobile}`);
214
- } catch (err) {
215
- console.error(`Error capturing visual evidence: ${err.message}`);
216
- throw err;
217
- }
218
- }
219
-
220
- const evidence = {
221
- timestamp: new Date().toISOString(),
222
- taskSlug: this.taskSlug,
223
- executionMode: this.mode,
224
- workflowProfile: this.profile,
225
- branchRecovery: this.branchRecovery,
226
- branch: policyValidation.git?.branch || "unknown",
227
- changedFiles,
228
- status: publicStatus(overallStatus),
229
- internalStatus: overallStatus,
230
- commands: results,
231
- checks: policyValidation.checks,
232
- limitations,
233
- deliveryDecision,
234
- artifactFidelityCheck,
235
- visualEvidence
236
- };
237
-
238
- if (writeArtifact) {
239
- await fs.writeFile(path.join(this.cwd, "EVIDENCE.json"), JSON.stringify(evidence, null, 2));
240
- }
241
- return evidence;
242
- }
243
- }
244
-
245
- export { inferTaskKind };