@williambeto/ai-workflow 2.4.9 → 2.6.4

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 (103) hide show
  1. package/CHANGELOG.md +1138 -168
  2. package/README.md +203 -77
  3. package/bin/ai-workflow.d.ts +3 -0
  4. package/bin/ai-workflow.js +2669 -1
  5. package/bin/ai-workflow.js.map +1 -0
  6. package/chunk-2AOV2ATY.js +749 -0
  7. package/chunk-5WRI5ZAA.js +31 -0
  8. package/chunk-6EMG6FAO.js +734 -0
  9. package/chunk-UHLZX2EG.js +1827 -0
  10. package/core/index.d.ts +440 -0
  11. package/core/index.js +32 -0
  12. package/core/index.js.map +1 -0
  13. package/dist-assets/AGENTS.md +23 -6
  14. package/dist-assets/agents/atlas.md +8 -8
  15. package/dist-assets/agents/nexus.md +4 -4
  16. package/dist-assets/agents/orion.md +1 -1
  17. package/dist-assets/agents/phoenix.md +1 -1
  18. package/dist-assets/agents/sage.md +1 -1
  19. package/dist-assets/commands/implement.md +1 -1
  20. package/dist-assets/docs/QUICKSTART.md +8 -8
  21. package/dist-assets/docs/cli-reference.md +7 -7
  22. package/dist-assets/docs/compatibility/provider-usage.md +1 -9
  23. package/dist-assets/docs/compatibility/runtime-matrix.md +5 -6
  24. package/dist-assets/docs/consumer-onboarding.md +2 -2
  25. package/dist-assets/docs/design-patterns-policy.md +1 -1
  26. package/dist-assets/docs/npm-consumer-quickstart.md +70 -109
  27. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  28. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  29. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  30. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  31. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  32. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  33. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  34. package/dist-assets/docs/profiles/README.md +2 -2
  35. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  36. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  37. package/dist-assets/docs/visual-validation-guide.md +3 -3
  38. package/dist-assets/schemas/evidence.schema.json +109 -0
  39. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  40. package/dist-assets/templates/CLAUDE.md.template +7 -5
  41. package/dist-assets/templates/CODEX.md.template +7 -5
  42. package/evidence-validator-76ZQQYDU.js +6661 -0
  43. package/package.json +7 -64
  44. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  45. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  46. package/AGENTS.md +0 -34
  47. package/PUBLISH_MANIFEST.json +0 -34
  48. package/dist-assets/templates/.geminiignore.template +0 -8
  49. package/dist-assets/templates/GEMINI.md.template +0 -20
  50. package/docs/compatibility/provider-usage.md +0 -46
  51. package/docs/compatibility/runtime-matrix.md +0 -31
  52. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  53. package/docs/getting-started/authoring-custom-skills.md +0 -80
  54. package/docs/getting-started/quickstart.md +0 -117
  55. package/docs/getting-started/troubleshooting.md +0 -123
  56. package/docs/getting-started/upgrading-to-v2.md +0 -62
  57. package/src/adapters/index.js +0 -4
  58. package/src/adapters/platforms/antigravity.js +0 -382
  59. package/src/adapters/platforms/claude.js +0 -126
  60. package/src/adapters/platforms/codex.js +0 -113
  61. package/src/adapters/platforms/gemini.js +0 -245
  62. package/src/cli.js +0 -155
  63. package/src/commands/clean.js +0 -106
  64. package/src/commands/collect-evidence.js +0 -25
  65. package/src/commands/doctor.js +0 -202
  66. package/src/commands/execute.js +0 -382
  67. package/src/commands/init.js +0 -359
  68. package/src/commands/run.js +0 -112
  69. package/src/core/backup.js +0 -65
  70. package/src/core/completion-contract.js +0 -35
  71. package/src/core/delegation-controller.js +0 -193
  72. package/src/core/evidence/evidence-ledger.js +0 -98
  73. package/src/core/execution-planner.js +0 -62
  74. package/src/core/filesystem.js +0 -141
  75. package/src/core/finalization/finalizer.js +0 -165
  76. package/src/core/finalization/workspace-snapshot.js +0 -110
  77. package/src/core/gates/branch-gate.js +0 -134
  78. package/src/core/gates/merge-gate.js +0 -74
  79. package/src/core/handoff/handoff-engine.js +0 -104
  80. package/src/core/healing/cli-remediation-executor.js +0 -153
  81. package/src/core/healing/healer-engine.js +0 -212
  82. package/src/core/healing/runtime-remediation-executor.js +0 -136
  83. package/src/core/identity.js +0 -43
  84. package/src/core/install-plan.js +0 -34
  85. package/src/core/opencode-merge.js +0 -150
  86. package/src/core/package-assets.js +0 -141
  87. package/src/core/request-classifier.js +0 -73
  88. package/src/core/request-router.js +0 -289
  89. package/src/core/runtime/opencode-adapter.js +0 -202
  90. package/src/core/sdd/validator.js +0 -67
  91. package/src/core/statuses.js +0 -29
  92. package/src/core/symlink-layout.js +0 -93
  93. package/src/core/templates.js +0 -229
  94. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  95. package/src/core/validation/canonical-finalization.js +0 -43
  96. package/src/core/validation/delivery-decision-engine.js +0 -344
  97. package/src/core/validation/evidence-collector.js +0 -245
  98. package/src/core/validation/quality-guard.js +0 -251
  99. package/src/core/validation/stack-detector.js +0 -65
  100. package/src/core/validation/validation-planner.js +0 -134
  101. package/src/core/validation/visual-verifier.js +0 -175
  102. package/src/core/workflow-profiles.js +0 -107
  103. package/src/core/workflow-state-machine.js +0 -46
@@ -0,0 +1,734 @@
1
+ import {
2
+ ArtifactFidelityGate,
3
+ DeliveryDecisionEngine,
4
+ MergeGate,
5
+ StackDetector
6
+ } from "./chunk-2AOV2ATY.js";
7
+
8
+ // src/core/validation/quality-guard.ts
9
+ import { execSync } from "child_process";
10
+ import fs from "fs/promises";
11
+ import path from "path";
12
+ var PROTECTED_BRANCHES = /* @__PURE__ */ new Set(["main", "master"]);
13
+ function normalizePath(file) {
14
+ return String(file || "").trim().replaceAll("\\", "/");
15
+ }
16
+ function parseStatusLine(line) {
17
+ const value = String(line || "");
18
+ const match = value.match(/^(?:[ MADRCU?!]{2}\s+|[MADRCU?!]\s+)(.+)$/);
19
+ if (!match) return "";
20
+ const candidate = match[1].trim();
21
+ const renamed = candidate.includes(" -> ") ? candidate.split(" -> ").at(-1) : candidate;
22
+ return normalizePath(renamed);
23
+ }
24
+ function isManagedChangePath(file = "") {
25
+ return /(^|\/)(node_modules|vendor|dist|coverage|\.cache)(\/|$)/.test(normalizePath(file));
26
+ }
27
+ async function exists(target) {
28
+ try {
29
+ await fs.access(target);
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ var QualityGuard = class {
36
+ cwd;
37
+ taskSlug;
38
+ evidenceWillBePersisted;
39
+ evidencePolicy;
40
+ riskLevel;
41
+ _changedFiles;
42
+ _hasHead;
43
+ constructor({
44
+ cwd,
45
+ taskSlug = null,
46
+ evidenceWillBePersisted = false,
47
+ evidencePolicy = "optional",
48
+ riskLevel = "medium"
49
+ }) {
50
+ this.cwd = cwd;
51
+ this.taskSlug = taskSlug;
52
+ this.evidencePolicy = evidencePolicy || "optional";
53
+ this.riskLevel = riskLevel || "medium";
54
+ this._changedFiles = null;
55
+ this._hasHead = null;
56
+ this.evidenceWillBePersisted = evidenceWillBePersisted;
57
+ }
58
+ runGit(command) {
59
+ return execSync(command, {
60
+ cwd: this.cwd,
61
+ encoding: "utf8",
62
+ stdio: ["ignore", "pipe", "pipe"]
63
+ }).trim();
64
+ }
65
+ hasGitHead() {
66
+ if (this._hasHead !== null) return this._hasHead;
67
+ try {
68
+ this.runGit("git rev-parse --verify HEAD");
69
+ this._hasHead = true;
70
+ } catch {
71
+ this._hasHead = false;
72
+ }
73
+ return this._hasHead;
74
+ }
75
+ getCurrentBranch() {
76
+ try {
77
+ return this.runGit("git branch --show-current") || "unknown";
78
+ } catch {
79
+ return "unknown";
80
+ }
81
+ }
82
+ getChangedFiles() {
83
+ if (this._changedFiles) return this._changedFiles;
84
+ const files = /* @__PURE__ */ new Set();
85
+ try {
86
+ if (this.hasGitHead()) {
87
+ this.runGit("git diff --name-only HEAD").split("\n").map(normalizePath).filter(Boolean).forEach((file) => files.add(file));
88
+ }
89
+ this.runGit("git status --short --untracked-files=all").split("\n").map(parseStatusLine).filter(Boolean).filter((file) => !isManagedChangePath(file)).forEach((file) => files.add(file));
90
+ } catch {
91
+ }
92
+ this._changedFiles = [...files].sort();
93
+ return this._changedFiles;
94
+ }
95
+ isDocsTask() {
96
+ const files = this.getChangedFiles();
97
+ return files.length > 0 && files.every((file) => file.endsWith(".md") || file.endsWith(".txt") || file.startsWith("docs/"));
98
+ }
99
+ isUiTask() {
100
+ return this.getChangedFiles().some(
101
+ (file) => /\.(tsx|jsx|vue|html|css|scss|less|blade\.php)$/.test(file) || /(^|\/)(components|pages|views)\//.test(file)
102
+ );
103
+ }
104
+ isImplementationTask() {
105
+ const files = this.getChangedFiles();
106
+ if (files.length === 0 || this.isDocsTask()) return false;
107
+ return files.some(
108
+ (file) => file === "package.json" || /^(src|app|pages|components|server|api)\//.test(file) || /\.(js|jsx|ts|tsx|vue|php|py|rb|go|java|css|scss|html)$/.test(file)
109
+ );
110
+ }
111
+ async hasExecutableBehaviorChanges() {
112
+ const files = this.getChangedFiles().filter((file) => !isManagedChangePath(file));
113
+ const executableExtensions = /\.(js|jsx|mjs|cjs|ts|tsx|vue|php|py|rb|go|java|cs|rs|swift|kt|kts|sh|bash)$/i;
114
+ if (files.some((file) => executableExtensions.test(file))) return true;
115
+ for (const file of files.filter((item) => /\.(html?|xhtml)$/i.test(item))) {
116
+ try {
117
+ const content = await fs.readFile(path.join(this.cwd, file), "utf8");
118
+ if (/<script\b|\son(?:click|change|input|submit|keydown|keyup|load|error)\s*=|javascript:/i.test(content)) return true;
119
+ } catch {
120
+ }
121
+ }
122
+ return false;
123
+ }
124
+ verifyBranchSafety() {
125
+ const branch = this.getCurrentBranch();
126
+ const implementation = this.isImplementationTask();
127
+ if (!this.hasGitHead()) {
128
+ return { status: "PASS_WITH_NOTES", branch, reason: "Git HEAD unavailable; branch safety could not be verified." };
129
+ }
130
+ if (implementation && PROTECTED_BRANCHES.has(branch)) {
131
+ return { status: "FAIL_QUALITY_GATE", branch, reason: `Implementation changes are present on protected branch '${branch}'.` };
132
+ }
133
+ if (this.riskLevel === "high") {
134
+ const mergeGate = new MergeGate({ cwd: this.cwd });
135
+ const mergeResult = mergeGate.check(branch);
136
+ if (mergeResult.status === "FAIL_QUALITY_GATE") {
137
+ return { status: "FAIL_QUALITY_GATE", branch, reason: mergeResult.reason };
138
+ }
139
+ }
140
+ return { status: "PASS", branch };
141
+ }
142
+ async verifyDocumentationNeed() {
143
+ if (!this.isImplementationTask()) return { status: "NOT_RUN", reason: "No implementation changes detected." };
144
+ const files = this.getChangedFiles();
145
+ const docsChanged = files.some((file) => file === "README.md" || file.startsWith("docs/") || file.endsWith(".md"));
146
+ const publicSurfaceChanged = files.some(
147
+ (file) => file === "package.json" || /(^|\/)(routes?|api|cli|commands?|config|public)\//.test(file) || /README|CHANGELOG/.test(file)
148
+ );
149
+ if (publicSurfaceChanged && !docsChanged) {
150
+ return { status: "PASS_WITH_NOTES", reason: "Public behavior may have changed without a documentation update." };
151
+ }
152
+ return { status: "PASS" };
153
+ }
154
+ async verifyUiEvidence() {
155
+ if (!this.isUiTask()) return { status: "NOT_RUN", reason: "Non-UI task." };
156
+ const candidates = ["docs", "screenshots", "evidence"];
157
+ const found = [];
158
+ for (const dir of candidates) {
159
+ const root = path.join(this.cwd, dir);
160
+ if (!await exists(root)) continue;
161
+ const entries = await fs.readdir(root, { recursive: true }).catch(() => []);
162
+ for (const entry of entries) {
163
+ if (/\.(png|jpe?g|webp)$/i.test(String(entry))) found.push(normalizePath(path.join(dir, String(entry))));
164
+ }
165
+ }
166
+ return found.length ? { status: "PASS", screenshots: found.slice(0, 20) } : { status: "PASS_WITH_NOTES", reason: "UI changed without persisted screenshots; rely on the stated browser/manual review only if it actually occurred." };
167
+ }
168
+ async verifyPersistedEvidence() {
169
+ if (this.evidencePolicy !== "required") return { status: "NOT_RUN", reason: "Persisted evidence is optional under optional evidence policy." };
170
+ if (this.evidenceWillBePersisted) return { status: "PASS", reason: "Collector will persist observed evidence." };
171
+ const candidates = [
172
+ path.join(this.cwd, "EVIDENCE.json"),
173
+ this.taskSlug ? path.join(this.cwd, "docs/workflows", this.taskSlug, "evidence.json") : null
174
+ ].filter(Boolean);
175
+ for (const file of candidates) {
176
+ if (await exists(file)) return { status: "PASS", file: path.relative(this.cwd, file) };
177
+ }
178
+ return { status: "FAIL_QUALITY_GATE", reason: "Persisted evidence is required under required evidence policy." };
179
+ }
180
+ verifyTruthfulnessBoundary() {
181
+ if (!this.isUiTask()) return { status: "NOT_RUN", reason: "Non-UI task." };
182
+ return {
183
+ status: "NOT_RUN",
184
+ reason: "Product truthfulness requires semantic review; keyword matching is intentionally not used as proof."
185
+ };
186
+ }
187
+ async verify() {
188
+ const branchSafety = this.verifyBranchSafety();
189
+ const documentation = await this.verifyDocumentationNeed();
190
+ const uiEvidence = await this.verifyUiEvidence();
191
+ const persistedEvidence = await this.verifyPersistedEvidence();
192
+ const productTruthfulness = this.verifyTruthfulnessBoundary();
193
+ const checks = [branchSafety, documentation, uiEvidence, persistedEvidence];
194
+ let overallStatus = "PASS";
195
+ if (checks.some((check) => ["FAIL", "FAIL_QUALITY_GATE", "BLOCKED"].includes(check.status))) {
196
+ overallStatus = "FAIL_QUALITY_GATE";
197
+ } else if (checks.some((check) => check.status === "PASS_WITH_NOTES")) {
198
+ overallStatus = "PASS_WITH_NOTES";
199
+ }
200
+ return {
201
+ overallStatus,
202
+ isUiTask: this.isUiTask(),
203
+ isDocsTask: this.isDocsTask(),
204
+ isImplementationTask: this.isImplementationTask(),
205
+ git: {
206
+ hasHead: this.hasGitHead(),
207
+ branch: this.getCurrentBranch(),
208
+ changedFiles: this.getChangedFiles()
209
+ },
210
+ checks: {
211
+ branchSafety,
212
+ documentation,
213
+ uiEvidence,
214
+ persistedEvidence,
215
+ productTruthfulness
216
+ }
217
+ };
218
+ }
219
+ };
220
+
221
+ // src/core/validation/visual-verifier.ts
222
+ import http from "http";
223
+ import fs2 from "fs/promises";
224
+ import path2 from "path";
225
+ var VisualVerifier = class {
226
+ cwd;
227
+ visualDist;
228
+ port;
229
+ server;
230
+ constructor({ cwd = process.cwd(), visualDist = null, port = 8080 } = {}) {
231
+ this.cwd = cwd;
232
+ this.visualDist = visualDist;
233
+ this.port = Number(port || 8080);
234
+ this.server = null;
235
+ }
236
+ async startServer() {
237
+ if (!this.visualDist) {
238
+ throw new Error("No --visual-dist directory specified. Cannot start visual server.");
239
+ }
240
+ const distPath = path2.resolve(this.cwd, this.visualDist);
241
+ this.server = http.createServer(async (req, res) => {
242
+ try {
243
+ const urlPath = req.url?.split("?")[0] || "/";
244
+ const filePath = path2.join(distPath, urlPath === "/" ? "index.html" : urlPath);
245
+ const content = await fs2.readFile(filePath);
246
+ const ext = path2.extname(filePath).toLowerCase();
247
+ let contentType = "text/html";
248
+ if (ext === ".js" || ext === ".mjs") contentType = "application/javascript";
249
+ else if (ext === ".css") contentType = "text/css";
250
+ else if (ext === ".png") contentType = "image/png";
251
+ else if (ext === ".jpg" || ext === ".jpeg") contentType = "image/jpeg";
252
+ else if (ext === ".svg") contentType = "image/svg+xml";
253
+ res.writeHead(200, { "Content-Type": contentType });
254
+ res.end(content);
255
+ } catch (err) {
256
+ res.writeHead(404, { "Content-Type": "text/plain" });
257
+ res.end("Not Found");
258
+ }
259
+ });
260
+ await new Promise((resolve, reject) => {
261
+ this.server.listen(this.port, "127.0.0.1", () => {
262
+ resolve();
263
+ });
264
+ this.server.on("error", (err) => {
265
+ reject(err);
266
+ });
267
+ });
268
+ }
269
+ async closeServer() {
270
+ if (this.server) {
271
+ await new Promise((resolve) => this.server.close(() => resolve()));
272
+ this.server = null;
273
+ }
274
+ }
275
+ async captureScreenshots() {
276
+ if (!this.visualDist) return [];
277
+ let playwright;
278
+ try {
279
+ playwright = await import("playwright");
280
+ } catch {
281
+ throw new Error(
282
+ "Playwright is required to capture visual evidence. Please install it using 'npm install -D playwright' and initialize browsers using 'npx playwright install'."
283
+ );
284
+ }
285
+ await this.startServer();
286
+ let browser;
287
+ try {
288
+ browser = await playwright.chromium.launch();
289
+ } catch (err) {
290
+ await this.closeServer();
291
+ throw new Error(
292
+ `Failed to launch Playwright Chromium. Please ensure browsers are installed using 'npx playwright install chromium'. Details: ${err.message}`
293
+ );
294
+ }
295
+ const page = await browser.newPage();
296
+ const outputDir = path2.join(this.cwd, ".evidence/visual");
297
+ await fs2.mkdir(outputDir, { recursive: true });
298
+ const desktopPath = path2.join(outputDir, "desktop.png");
299
+ const mobilePath = path2.join(outputDir, "mobile.png");
300
+ try {
301
+ await page.goto(`http://127.0.0.1:${this.port}`);
302
+ await page.setViewportSize({ width: 1440, height: 900 });
303
+ await page.screenshot({ path: desktopPath, fullPage: true });
304
+ await page.setViewportSize({ width: 390, height: 844 });
305
+ await page.screenshot({ path: mobilePath, fullPage: true });
306
+ } finally {
307
+ await browser.close();
308
+ await this.closeServer();
309
+ }
310
+ return [desktopPath, mobilePath];
311
+ }
312
+ async runAccessibilityCheck() {
313
+ if (!this.visualDist) {
314
+ throw new Error("Accessibility check requires a target build directory specified via --visual-dist.");
315
+ }
316
+ let playwright;
317
+ try {
318
+ playwright = await import("playwright");
319
+ } catch {
320
+ throw new Error(
321
+ "Playwright is required to run accessibility checks. Please install it using 'npm install -D playwright' and initialize browsers using 'npx playwright install'."
322
+ );
323
+ }
324
+ await this.startServer();
325
+ let browser;
326
+ try {
327
+ browser = await playwright.chromium.launch();
328
+ } catch (err) {
329
+ await this.closeServer();
330
+ throw new Error(
331
+ `Failed to launch Playwright Chromium. Please ensure browsers are installed using 'npx playwright install chromium'. Details: ${err.message}`
332
+ );
333
+ }
334
+ const page = await browser.newPage();
335
+ const outputDir = path2.join(this.cwd, ".evidence/a11y");
336
+ await fs2.mkdir(outputDir, { recursive: true });
337
+ try {
338
+ await page.goto(`http://127.0.0.1:${this.port}`);
339
+ let axeScript = "";
340
+ try {
341
+ const axePath = path2.join(this.cwd, "node_modules/axe-core/axe.min.js");
342
+ axeScript = await fs2.readFile(axePath, "utf8");
343
+ } catch {
344
+ try {
345
+ const mainAxePath = path2.resolve(import.meta.dirname, "../../../node_modules/axe-core/axe.min.js");
346
+ axeScript = await fs2.readFile(mainAxePath, "utf8");
347
+ } catch {
348
+ throw new Error("axe-core package is not installed. Please install axe-core using 'npm install -D axe-core'.");
349
+ }
350
+ }
351
+ await page.evaluate(axeScript);
352
+ const axeResults = await page.evaluate(() => {
353
+ return window.axe.run();
354
+ });
355
+ const reportPath = path2.join(outputDir, "report.json");
356
+ await fs2.writeFile(reportPath, JSON.stringify(axeResults, null, 2));
357
+ const criticalViolations = axeResults.violations.filter((v) => v.impact === "critical");
358
+ const seriousViolations = axeResults.violations.filter((v) => v.impact === "serious");
359
+ return {
360
+ passed: criticalViolations.length === 0,
361
+ reportPath,
362
+ violations: axeResults.violations,
363
+ critical: criticalViolations,
364
+ serious: seriousViolations
365
+ };
366
+ } finally {
367
+ await browser.close();
368
+ await this.closeServer();
369
+ }
370
+ }
371
+ };
372
+
373
+ // src/core/validation/evidence-collector.ts
374
+ import { spawnSync } from "child_process";
375
+ import fs3 from "fs/promises";
376
+ import path3 from "path";
377
+ var VALIDATION_KINDS = /* @__PURE__ */ new Set(["test", "build", "typecheck", "lint", "security", "smoke", "validate", "other"]);
378
+ function publicStatus(status) {
379
+ if (status === "PASS") return "COMPLETED";
380
+ if (status === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
381
+ return "BLOCKED";
382
+ }
383
+ function inferTaskKind(task = {}) {
384
+ const explicit = String(task.kind || "").trim().toLowerCase();
385
+ if (VALIDATION_KINDS.has(explicit)) return explicit;
386
+ const value = `${task.name || ""} ${task.command || ""}`.toLowerCase();
387
+ if (/\b(test|tests|vitest|jest|mocha|ava|tap|pytest|phpunit|rspec|cypress|playwright)\b/.test(value)) return "test";
388
+ if (/\b(typecheck|tsc\b|mypy|pyright)\b/.test(value)) return "typecheck";
389
+ if (/\b(lint|eslint|stylelint|ruff|flake8|pylint)\b/.test(value)) return "lint";
390
+ if (/\b(build|bundle|compile)\b/.test(value)) return "build";
391
+ if (/\b(audit|security|sast|scan)\b/.test(value)) return "security";
392
+ if (/\b(smoke|curl|preview|serve)\b/.test(value)) return "smoke";
393
+ if (/\bvalidate\b/.test(value)) return "validate";
394
+ return "other";
395
+ }
396
+ var EvidenceCollector = class {
397
+ cwd;
398
+ maxLogLength;
399
+ timeout;
400
+ taskSlug;
401
+ /** @deprecated use evidencePolicy instead */
402
+ mode;
403
+ evidencePolicy;
404
+ profile;
405
+ branchRecovery;
406
+ userRequest;
407
+ explicitApprovals;
408
+ visualDist;
409
+ port;
410
+ constructor({
411
+ cwd,
412
+ maxLogLength = 2e3,
413
+ timeout = 6e4,
414
+ taskSlug = null,
415
+ mode = null,
416
+ evidencePolicy = null,
417
+ profile = "generic",
418
+ branchRecovery = "NOT_RECORDED",
419
+ userRequest = null,
420
+ explicitApprovals = [],
421
+ visualDist = null,
422
+ port = 8080
423
+ }) {
424
+ this.cwd = cwd;
425
+ this.maxLogLength = maxLogLength;
426
+ this.timeout = timeout;
427
+ this.taskSlug = taskSlug;
428
+ this.evidencePolicy = evidencePolicy || (mode === "full" ? "required" : "optional");
429
+ this.mode = this.evidencePolicy === "required" ? "full" : "standard";
430
+ this.profile = profile;
431
+ this.branchRecovery = branchRecovery;
432
+ this.userRequest = userRequest;
433
+ this.explicitApprovals = explicitApprovals;
434
+ this.visualDist = visualDist;
435
+ this.port = Number(port || 8080);
436
+ }
437
+ async findUserRequest() {
438
+ if (this.userRequest) return this.userRequest;
439
+ if (this.taskSlug) {
440
+ const ledgerPath = path3.join(this.cwd, `.ai-workflow/history/${this.taskSlug}-ledger.json`);
441
+ try {
442
+ const content = await fs3.readFile(ledgerPath, "utf8");
443
+ const events = JSON.parse(content);
444
+ const routeEvent = events.find((e) => e.eventType === "routing");
445
+ if (routeEvent && routeEvent.data?.request) {
446
+ return routeEvent.data.request;
447
+ }
448
+ const startEvent = events.find((e) => e.eventType === "implementation_start");
449
+ if (startEvent && startEvent.data?.prompt) {
450
+ return startEvent.data.prompt;
451
+ }
452
+ } catch {
453
+ }
454
+ }
455
+ try {
456
+ const dir = path3.join(this.cwd, ".ai-workflow/history");
457
+ const files = await fs3.readdir(dir);
458
+ for (const file of files) {
459
+ if (file.endsWith("-ledger.json")) {
460
+ const content = await fs3.readFile(path3.join(dir, file), "utf8");
461
+ const events = JSON.parse(content);
462
+ const routeEvent = events.find((e) => e.eventType === "routing");
463
+ if (routeEvent && routeEvent.data?.request) {
464
+ return routeEvent.data.request;
465
+ }
466
+ }
467
+ }
468
+ } catch {
469
+ }
470
+ try {
471
+ const { execSync: execSync2 } = await import("child_process");
472
+ const branch = execSync2("git branch --show-current", { cwd: this.cwd, encoding: "utf8" }).trim();
473
+ if (branch && branch !== "main" && branch !== "master" && branch.startsWith("feat/")) {
474
+ return branch.replace("feat/", "").replaceAll("-", " ");
475
+ }
476
+ } catch {
477
+ }
478
+ return "";
479
+ }
480
+ runTask(task) {
481
+ const kind = inferTaskKind(task);
482
+ if (task.presetStatus) {
483
+ return { name: task.name, command: task.command, kind, status: task.presetStatus, exitCode: null, summary: task.summary || "", output: "" };
484
+ }
485
+ let cmd = task.command;
486
+ let args = [];
487
+ if (Array.isArray(cmd)) {
488
+ const [c, ...a] = cmd;
489
+ cmd = c;
490
+ args = a;
491
+ } else if (typeof cmd === "string") {
492
+ const parts = cmd.split(/\s+/);
493
+ cmd = parts[0];
494
+ args = parts.slice(1);
495
+ }
496
+ const isWin32 = process.platform === "win32";
497
+ const isUncPath = typeof this.cwd === "string" && (this.cwd.startsWith("\\\\") || this.cwd.startsWith("//"));
498
+ const shellOption = isWin32 && isUncPath ? "powershell.exe" : isWin32 ? true : false;
499
+ const result = spawnSync(cmd, args, {
500
+ cwd: this.cwd,
501
+ shell: shellOption,
502
+ encoding: "utf8",
503
+ timeout: this.timeout
504
+ });
505
+ let status = result.status === 0 ? "PASS" : "FAIL";
506
+ if (result.error) {
507
+ status = "FAIL";
508
+ if (result.error.code === "ETIMEDOUT") {
509
+ status = "BLOCKED";
510
+ }
511
+ }
512
+ let summary = status === "PASS" ? "Command completed successfully." : "Command failed.";
513
+ if (result.error?.code === "ETIMEDOUT") {
514
+ summary = `Command timed out after ${this.timeout / 1e3}s.`;
515
+ } else if (result.error) {
516
+ summary = `Command execution error: ${result.error.message}`;
517
+ }
518
+ const rawOutput = `${result.stdout || ""}${result.stderr || ""}`.trim();
519
+ const output = rawOutput.length > this.maxLogLength ? `${rawOutput.slice(0, this.maxLogLength)}
520
+ ... [TRUNCATED]` : rawOutput;
521
+ return { name: task.name, command: task.command, kind, status, exitCode: result.status, signal: result.signal, summary, output };
522
+ }
523
+ async collect(tasks, { writeArtifact = false } = {}) {
524
+ const qualityGuard = new QualityGuard({ cwd: this.cwd, taskSlug: this.taskSlug, evidencePolicy: this.evidencePolicy, riskLevel: this.evidencePolicy === "required" ? "high" : "medium", evidenceWillBePersisted: writeArtifact });
525
+ const implementation = qualityGuard.isImplementationTask();
526
+ const executableBehavior = await qualityGuard.hasExecutableBehaviorChanges();
527
+ const results = tasks.map((task) => this.runTask(task));
528
+ const policyValidation = await qualityGuard.verify();
529
+ const behaviorTests = results.filter((result) => result.kind === "test");
530
+ const passingBehaviorTest = behaviorTests.some((result) => result.status === "PASS");
531
+ const userRequest = await this.findUserRequest();
532
+ const engine = new DeliveryDecisionEngine({ cwd: this.cwd });
533
+ const projectContext = await engine.detectContext();
534
+ const deliveryDecision = await engine.determineDecision({
535
+ userRequest,
536
+ projectContext,
537
+ explicitApprovals: this.explicitApprovals || []
538
+ });
539
+ const fidelityGate = new ArtifactFidelityGate({
540
+ cwd: this.cwd,
541
+ deliveryClass: deliveryDecision.allowedArtifactShape === "runnable-framework-app" ? "framework-native-frontend" : deliveryDecision.allowedArtifactShape === "wordpress" ? "wordpress-php" : "generic-code",
542
+ userRequest,
543
+ explicitApprovals: this.explicitApprovals || []
544
+ });
545
+ const changedFiles = policyValidation.git?.changedFiles || [];
546
+ const artifactFidelityCheck = await fidelityGate.verifyRequestFidelity({
547
+ userRequest,
548
+ deliveryDecision,
549
+ projectContext,
550
+ changedFiles
551
+ });
552
+ let overallStatus = "PASS";
553
+ if (implementation && executableBehavior && tasks.length === 0) overallStatus = "BLOCKED";
554
+ else if (results.some((result) => ["FAIL", "BLOCKED", "FAIL_QUALITY_GATE"].includes(result.status))) overallStatus = "FAIL_QUALITY_GATE";
555
+ else if (executableBehavior && !passingBehaviorTest) overallStatus = "BLOCKED";
556
+ else if (["FAIL", "BLOCKED", "FAIL_QUALITY_GATE"].includes(policyValidation.overallStatus)) overallStatus = "FAIL_QUALITY_GATE";
557
+ else if (results.some((result) => result.status === "PASS_WITH_NOTES") || policyValidation.overallStatus === "PASS_WITH_NOTES") overallStatus = "PASS_WITH_NOTES";
558
+ if (!artifactFidelityCheck || !artifactFidelityCheck.passed) {
559
+ overallStatus = "BLOCKED";
560
+ } else if (implementation) {
561
+ if (!deliveryDecision || !deliveryDecision.decision || deliveryDecision.decision.startsWith("BLOCK") || deliveryDecision.decision.includes("BLOCKED") || deliveryDecision.decision === "REQUIRE_EXPLICIT_AUTHORIZATION" || deliveryDecision.decision === "RELEASE_OR_DEPLOY_REQUIRES_APPROVAL") {
562
+ overallStatus = "BLOCKED";
563
+ } else if (results.length === 0 && executableBehavior && deliveryDecision.decision !== "DOCUMENTATION_ONLY" && deliveryDecision.decision !== "DOCS_ONLY" && deliveryDecision.decision !== "READ_ONLY_AUDIT") {
564
+ overallStatus = "BLOCKED";
565
+ }
566
+ }
567
+ const limitations = [];
568
+ if (implementation && executableBehavior && tasks.length === 0) limitations.push("No meaningful validation command was available for executable implementation work.");
569
+ if (executableBehavior && behaviorTests.length === 0) {
570
+ limitations.push("Executable behavior changed without a proportional automated behavior test. Build, lint, typecheck, smoke, screenshots, and manual review do not replace behavior tests.");
571
+ }
572
+ for (const [name, check] of Object.entries(policyValidation.checks || {})) {
573
+ if (check.status === "PASS_WITH_NOTES" && check.reason) limitations.push(`${name}: ${check.reason}`);
574
+ }
575
+ let visualEvidence = null;
576
+ if (this.visualDist) {
577
+ console.log(`
578
+ --- Capturing Visual Evidence ---`);
579
+ console.log(`Serving ${this.visualDist} on port ${this.port}...`);
580
+ try {
581
+ const verifier = new VisualVerifier({ cwd: this.cwd, visualDist: this.visualDist, port: this.port });
582
+ const screenshots = await verifier.captureScreenshots();
583
+ visualEvidence = {
584
+ desktop: path3.relative(this.cwd, screenshots[0]),
585
+ mobile: path3.relative(this.cwd, screenshots[1])
586
+ };
587
+ console.log(`Visual evidence captured successfully:`);
588
+ console.log(`- Desktop: ${visualEvidence.desktop}`);
589
+ console.log(`- Mobile: ${visualEvidence.mobile}`);
590
+ } catch (err) {
591
+ console.error(`Error capturing visual evidence: ${err.message}`);
592
+ throw err;
593
+ }
594
+ }
595
+ const evidence = {
596
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
597
+ taskSlug: this.taskSlug,
598
+ executionMode: this.mode,
599
+ workflowProfile: this.profile,
600
+ branchRecovery: this.branchRecovery,
601
+ branch: policyValidation.git?.branch || "unknown",
602
+ changedFiles,
603
+ status: publicStatus(overallStatus),
604
+ internalStatus: overallStatus,
605
+ commands: results,
606
+ checks: policyValidation.checks,
607
+ limitations,
608
+ deliveryDecision,
609
+ artifactFidelityCheck,
610
+ visualEvidence
611
+ };
612
+ if (writeArtifact) {
613
+ await fs3.writeFile(path3.join(this.cwd, "EVIDENCE.json"), JSON.stringify(evidence, null, 2));
614
+ }
615
+ return evidence;
616
+ }
617
+ };
618
+
619
+ // src/core/validation/validation-planner.ts
620
+ import fs4 from "fs/promises";
621
+ import path4 from "path";
622
+ var ValidationPlanner = class {
623
+ cwd;
624
+ qualityGuard;
625
+ stackDetector;
626
+ constructor({ cwd = process.cwd(), qualityGuard }) {
627
+ this.cwd = cwd;
628
+ this.qualityGuard = qualityGuard;
629
+ this.stackDetector = new StackDetector({ cwd });
630
+ }
631
+ async plan(profile = "generic") {
632
+ const stacks = await this.stackDetector.detect();
633
+ const tasks = [];
634
+ const changedFiles = this.qualityGuard.getChangedFiles().filter((file) => {
635
+ return !/(^|\/)(node_modules|vendor|dist|coverage|\.cache)(\/|$)/.test(String(file).replaceAll("\\", "/"));
636
+ });
637
+ const executableBehavior = await this.qualityGuard.hasExecutableBehaviorChanges();
638
+ if (stacks.includes("node")) {
639
+ try {
640
+ const pkg = JSON.parse(await fs4.readFile(path4.join(this.cwd, "package.json"), "utf8"));
641
+ const scripts = pkg.scripts || {};
642
+ this.addNodeScriptTask(tasks, scripts, "lint", ["npm", "run", "lint"]);
643
+ this.addNodeScriptTask(tasks, scripts, "test", ["npm", "test"], { rejectNoOp: executableBehavior });
644
+ this.addNodeScriptTask(tasks, scripts, "build", ["npm", "run", "build"]);
645
+ this.addNodeScriptTask(tasks, scripts, "typecheck", ["npm", "run", "typecheck"]);
646
+ if (scripts.validate && !String(scripts.validate).includes("ai-workflow collect-evidence")) {
647
+ this.addNodeScriptTask(tasks, scripts, "validate", ["npm", "run", "validate"]);
648
+ }
649
+ const hasTs = changedFiles.some((file) => /\.(ts|tsx)$/.test(file)) || await fs4.access(path4.join(this.cwd, "tsconfig.json")).then(() => true).catch(() => false);
650
+ if (hasTs && !scripts.typecheck) {
651
+ tasks.push({
652
+ name: "typecheck",
653
+ command: ["npm", "run", "typecheck"],
654
+ kind: "typecheck",
655
+ presetStatus: "FAIL_QUALITY_GATE",
656
+ summary: "TypeScript detected but no typecheck script is configured."
657
+ });
658
+ }
659
+ } catch {
660
+ }
661
+ }
662
+ if (stacks.includes("python")) {
663
+ const pythonFiles = changedFiles.filter((f) => f.endsWith(".py"));
664
+ if (pythonFiles.length > 0) {
665
+ tasks.push({
666
+ name: "python-syntax",
667
+ command: ["python", "-m", "py_compile", ...pythonFiles],
668
+ kind: "lint"
669
+ });
670
+ }
671
+ if (executableBehavior) {
672
+ const hasPytestConfig = await fs4.access(path4.join(this.cwd, "pytest.ini")).then(() => true).catch(() => false) || await fs4.access(path4.join(this.cwd, "pyproject.toml")).then(() => true).catch(() => false);
673
+ if (hasPytestConfig || pythonFiles.length > 0) {
674
+ tasks.push({
675
+ name: "python-test",
676
+ command: ["pytest"],
677
+ kind: "test"
678
+ });
679
+ }
680
+ }
681
+ }
682
+ if (stacks.includes("php")) {
683
+ const phpFiles = changedFiles.filter((f) => f.endsWith(".php") || f.endsWith(".inc"));
684
+ if (phpFiles.length > 0) {
685
+ for (const file of phpFiles) {
686
+ tasks.push({
687
+ name: `php-syntax:${file}`,
688
+ command: ["php", "-l", file],
689
+ kind: "lint"
690
+ });
691
+ }
692
+ }
693
+ if (executableBehavior) {
694
+ const hasPhpUnitXml = await fs4.access(path4.join(this.cwd, "phpunit.xml")).then(() => true).catch(() => false) || await fs4.access(path4.join(this.cwd, "phpunit.xml.dist")).then(() => true).catch(() => false);
695
+ if (hasPhpUnitXml || phpFiles.length > 0) {
696
+ tasks.push({
697
+ name: "php-test",
698
+ command: ["vendor/bin/phpunit"],
699
+ kind: "test"
700
+ });
701
+ }
702
+ }
703
+ }
704
+ return tasks;
705
+ }
706
+ addNodeScriptTask(tasks, scripts, name, command, { rejectNoOp = false } = {}) {
707
+ const script = scripts[name];
708
+ if (!script) return;
709
+ if (!rejectNoOp && name === "test" && this.isNoOpScript(script)) return;
710
+ if (rejectNoOp && this.isNoOpScript(script)) {
711
+ tasks.push({
712
+ name,
713
+ command,
714
+ kind: name === "test" ? "test" : name,
715
+ presetStatus: "FAIL_QUALITY_GATE",
716
+ summary: `Configured ${name} script is a no-op: ${script}`
717
+ });
718
+ return;
719
+ }
720
+ tasks.push({ name, command, kind: name === "test" ? "test" : name });
721
+ }
722
+ isNoOpScript(script = "") {
723
+ const value = String(script).trim().toLowerCase();
724
+ return /^(echo\b|true$|exit\s+0$|node\s+-e\s+["']?console\.log)/.test(value) || value.includes("tests-pass");
725
+ }
726
+ };
727
+
728
+ export {
729
+ QualityGuard,
730
+ VisualVerifier,
731
+ EvidenceCollector,
732
+ ValidationPlanner
733
+ };
734
+ //# sourceMappingURL=chunk-6EMG6FAO.js.map