@williambeto/ai-workflow 2.4.8 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ai-workflow.d.ts +3 -0
- package/bin/ai-workflow.js +2669 -1
- package/bin/ai-workflow.js.map +1 -0
- package/chunk-2AOV2ATY.js +749 -0
- package/chunk-5WRI5ZAA.js +31 -0
- package/chunk-6EMG6FAO.js +734 -0
- package/chunk-UHLZX2EG.js +1827 -0
- package/core/index.d.ts +440 -0
- package/core/index.js +32 -0
- package/core/index.js.map +1 -0
- package/dist-assets/AGENTS.md +23 -6
- package/dist-assets/agents/atlas.md +8 -8
- package/dist-assets/agents/nexus.md +4 -4
- package/dist-assets/agents/orion.md +1 -1
- package/dist-assets/agents/phoenix.md +1 -1
- package/dist-assets/agents/sage.md +1 -1
- package/dist-assets/commands/implement.md +1 -1
- package/dist-assets/docs/QUICKSTART.md +8 -8
- package/dist-assets/docs/cli-reference.md +7 -7
- package/dist-assets/docs/compatibility/provider-usage.md +1 -9
- package/dist-assets/docs/compatibility/runtime-matrix.md +5 -6
- package/dist-assets/docs/consumer-onboarding.md +2 -2
- package/dist-assets/docs/design-patterns-policy.md +1 -1
- package/dist-assets/docs/full-documentation.md +0 -2
- package/dist-assets/docs/npm-consumer-quickstart.md +70 -109
- package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
- package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
- package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
- package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
- package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
- package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
- package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
- package/dist-assets/docs/profiles/README.md +2 -2
- package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
- package/dist-assets/docs/troubleshooting-guide.md +2 -2
- package/dist-assets/docs/visual-validation-guide.md +65 -3
- package/dist-assets/prompts/01-create-requirement.md +2 -2
- package/dist-assets/prompts/02-create-spec.md +2 -2
- package/dist-assets/prompts/03-create-tech-plan.md +4 -4
- package/dist-assets/prompts/04-breakdown-prs.md +4 -4
- package/dist-assets/prompts/05-implement-pr.md +5 -5
- package/dist-assets/prompts/06-review-and-fix.md +3 -3
- package/dist-assets/prompts/07-apply-design.md +2 -2
- package/dist-assets/prompts/08-validate.md +5 -5
- package/dist-assets/prompts/09-deploy.md +5 -5
- package/dist-assets/prompts/commands/implement.md +1 -1
- package/dist-assets/prompts/commands/tech-plan.md +2 -2
- package/dist-assets/prompts/commands/validate.md +2 -2
- package/dist-assets/runbooks/deploy-checklist.md +5 -5
- package/dist-assets/runbooks/how-to-use-skills.md +10 -0
- package/dist-assets/runbooks/validation-checklist.md +5 -5
- package/dist-assets/schemas/evidence.schema.json +109 -0
- package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
- package/dist-assets/templates/CLAUDE.md.template +7 -5
- package/dist-assets/templates/CODEX.md.template +7 -5
- package/evidence-validator-76ZQQYDU.js +6661 -0
- package/package.json +7 -65
- package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
- package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
- package/AGENTS.md +0 -34
- package/CHANGELOG.md +0 -237
- package/LICENSE +0 -21
- package/PUBLISH_MANIFEST.json +0 -34
- package/README.md +0 -129
- package/dist-assets/runbooks/use-linear-for-operational-planning.md +0 -45
- package/dist-assets/runbooks/use-napkin-project-memory.md +0 -33
- package/dist-assets/templates/.geminiignore.template +0 -8
- package/dist-assets/templates/GEMINI.md.template +0 -20
- package/docs/compatibility/provider-usage.md +0 -46
- package/docs/compatibility/runtime-matrix.md +0 -31
- package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
- package/docs/getting-started/quickstart.md +0 -117
- package/docs/getting-started/troubleshooting.md +0 -123
- package/docs/getting-started/upgrading-to-v2.md +0 -62
- package/read_only_safety_verification.md +0 -48
- package/src/adapters/index.js +0 -4
- package/src/adapters/platforms/antigravity.js +0 -382
- package/src/adapters/platforms/claude.js +0 -126
- package/src/adapters/platforms/codex.js +0 -113
- package/src/adapters/platforms/gemini.js +0 -245
- package/src/cli.js +0 -155
- package/src/commands/clean.js +0 -106
- package/src/commands/collect-evidence.js +0 -25
- package/src/commands/doctor.js +0 -202
- package/src/commands/execute.js +0 -382
- package/src/commands/init.js +0 -359
- package/src/commands/run.js +0 -112
- package/src/core/backup.js +0 -65
- package/src/core/completion-contract.js +0 -35
- package/src/core/delegation-controller.js +0 -193
- package/src/core/evidence/evidence-ledger.js +0 -98
- package/src/core/execution-planner.js +0 -62
- package/src/core/filesystem.js +0 -141
- package/src/core/finalization/finalizer.js +0 -165
- package/src/core/finalization/workspace-snapshot.js +0 -110
- package/src/core/gates/branch-gate.js +0 -134
- package/src/core/gates/merge-gate.js +0 -74
- package/src/core/handoff/handoff-engine.js +0 -104
- package/src/core/healing/cli-remediation-executor.js +0 -153
- package/src/core/healing/healer-engine.js +0 -212
- package/src/core/healing/runtime-remediation-executor.js +0 -136
- package/src/core/identity.js +0 -43
- package/src/core/install-plan.js +0 -34
- package/src/core/opencode-merge.js +0 -150
- package/src/core/package-assets.js +0 -141
- package/src/core/request-classifier.js +0 -73
- package/src/core/request-router.js +0 -289
- package/src/core/runtime/opencode-adapter.js +0 -202
- package/src/core/sdd/validator.js +0 -67
- package/src/core/statuses.js +0 -29
- package/src/core/symlink-layout.js +0 -93
- package/src/core/templates.js +0 -229
- package/src/core/validation/artifact-fidelity-gate.js +0 -445
- package/src/core/validation/canonical-finalization.js +0 -43
- package/src/core/validation/delivery-decision-engine.js +0 -344
- package/src/core/validation/evidence-collector.js +0 -245
- package/src/core/validation/quality-guard.js +0 -251
- package/src/core/validation/stack-detector.js +0 -65
- package/src/core/validation/validation-planner.js +0 -134
- package/src/core/validation/visual-verifier.js +0 -175
- package/src/core/workflow-profiles.js +0 -107
- 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 };
|