@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
@@ -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