@williambeto/ai-workflow 2.4.9 → 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 (104) 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/npm-consumer-quickstart.md +70 -109
  25. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  26. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  27. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  28. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  29. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  30. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  31. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  32. package/dist-assets/docs/profiles/README.md +2 -2
  33. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  34. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  35. package/dist-assets/docs/visual-validation-guide.md +3 -3
  36. package/dist-assets/schemas/evidence.schema.json +109 -0
  37. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  38. package/dist-assets/templates/CLAUDE.md.template +7 -5
  39. package/dist-assets/templates/CODEX.md.template +7 -5
  40. package/evidence-validator-76ZQQYDU.js +6661 -0
  41. package/package.json +7 -64
  42. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  43. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  44. package/AGENTS.md +0 -34
  45. package/CHANGELOG.md +0 -259
  46. package/LICENSE +0 -21
  47. package/PUBLISH_MANIFEST.json +0 -34
  48. package/README.md +0 -129
  49. package/dist-assets/templates/.geminiignore.template +0 -8
  50. package/dist-assets/templates/GEMINI.md.template +0 -20
  51. package/docs/compatibility/provider-usage.md +0 -46
  52. package/docs/compatibility/runtime-matrix.md +0 -31
  53. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  54. package/docs/getting-started/authoring-custom-skills.md +0 -80
  55. package/docs/getting-started/quickstart.md +0 -117
  56. package/docs/getting-started/troubleshooting.md +0 -123
  57. package/docs/getting-started/upgrading-to-v2.md +0 -62
  58. package/src/adapters/index.js +0 -4
  59. package/src/adapters/platforms/antigravity.js +0 -382
  60. package/src/adapters/platforms/claude.js +0 -126
  61. package/src/adapters/platforms/codex.js +0 -113
  62. package/src/adapters/platforms/gemini.js +0 -245
  63. package/src/cli.js +0 -155
  64. package/src/commands/clean.js +0 -106
  65. package/src/commands/collect-evidence.js +0 -25
  66. package/src/commands/doctor.js +0 -202
  67. package/src/commands/execute.js +0 -382
  68. package/src/commands/init.js +0 -359
  69. package/src/commands/run.js +0 -112
  70. package/src/core/backup.js +0 -65
  71. package/src/core/completion-contract.js +0 -35
  72. package/src/core/delegation-controller.js +0 -193
  73. package/src/core/evidence/evidence-ledger.js +0 -98
  74. package/src/core/execution-planner.js +0 -62
  75. package/src/core/filesystem.js +0 -141
  76. package/src/core/finalization/finalizer.js +0 -165
  77. package/src/core/finalization/workspace-snapshot.js +0 -110
  78. package/src/core/gates/branch-gate.js +0 -134
  79. package/src/core/gates/merge-gate.js +0 -74
  80. package/src/core/handoff/handoff-engine.js +0 -104
  81. package/src/core/healing/cli-remediation-executor.js +0 -153
  82. package/src/core/healing/healer-engine.js +0 -212
  83. package/src/core/healing/runtime-remediation-executor.js +0 -136
  84. package/src/core/identity.js +0 -43
  85. package/src/core/install-plan.js +0 -34
  86. package/src/core/opencode-merge.js +0 -150
  87. package/src/core/package-assets.js +0 -141
  88. package/src/core/request-classifier.js +0 -73
  89. package/src/core/request-router.js +0 -289
  90. package/src/core/runtime/opencode-adapter.js +0 -202
  91. package/src/core/sdd/validator.js +0 -67
  92. package/src/core/statuses.js +0 -29
  93. package/src/core/symlink-layout.js +0 -93
  94. package/src/core/templates.js +0 -229
  95. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  96. package/src/core/validation/canonical-finalization.js +0 -43
  97. package/src/core/validation/delivery-decision-engine.js +0 -344
  98. package/src/core/validation/evidence-collector.js +0 -245
  99. package/src/core/validation/quality-guard.js +0 -251
  100. package/src/core/validation/stack-detector.js +0 -65
  101. package/src/core/validation/validation-planner.js +0 -134
  102. package/src/core/validation/visual-verifier.js +0 -175
  103. package/src/core/workflow-profiles.js +0 -107
  104. 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 };