@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,445 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export class ArtifactFidelityGate {
|
|
5
|
-
constructor({ cwd = process.cwd(), deliveryClass, userRequest, explicitApprovals } = {}) {
|
|
6
|
-
this.cwd = cwd;
|
|
7
|
-
this.deliveryClass = deliveryClass;
|
|
8
|
-
this.userRequest = userRequest;
|
|
9
|
-
this.explicitApprovals = explicitApprovals;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async verify(changedFiles = []) {
|
|
13
|
-
if (changedFiles.length === 0) {
|
|
14
|
-
return { passed: true };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (this.deliveryClass === "framework-native-frontend") {
|
|
18
|
-
for (const file of changedFiles) {
|
|
19
|
-
const ext = path.extname(file).toLowerCase();
|
|
20
|
-
|
|
21
|
-
if (ext === ".html" || ext === ".htm") {
|
|
22
|
-
const basename = path.basename(file).toLowerCase();
|
|
23
|
-
const isSpaEntry = basename === "index.html" &&
|
|
24
|
-
(file === "index.html" || file === "public/index.html" || file === "src/index.html");
|
|
25
|
-
if (!isSpaEntry) {
|
|
26
|
-
return {
|
|
27
|
-
passed: false,
|
|
28
|
-
reason: `Fidelity Violation: standalone HTML file '${file}' is not permitted in a framework-native frontend project. UI must be implemented within framework components (e.g. React/Vue/Next/Nuxt files) instead of static prototypes.`
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const content = await fs.readFile(path.join(this.cwd, file), "utf8");
|
|
34
|
-
if (content.includes("unpkg.com") || content.includes("cdnjs.cloudflare.com") || content.includes("cdn.jsdelivr.net")) {
|
|
35
|
-
if (content.includes("vue") || content.includes("react")) {
|
|
36
|
-
return {
|
|
37
|
-
passed: false,
|
|
38
|
-
reason: `Fidelity Violation: SPA entry point '${file}' imports framework library via CDN scripts. Framework dependencies must be installed and resolved locally via npm/package.json.`
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
} catch {
|
|
43
|
-
// ignore
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (this.deliveryClass === "wordpress-php") {
|
|
50
|
-
for (const file of changedFiles) {
|
|
51
|
-
const ext = path.extname(file).toLowerCase();
|
|
52
|
-
if (ext === ".html" || ext === ".htm") {
|
|
53
|
-
return {
|
|
54
|
-
passed: false,
|
|
55
|
-
reason: `Fidelity Violation: standalone HTML file '${file}' is not permitted in a WordPress project. UI templates must be implemented in PHP files within theme or plugin structures.`
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const frontendViolations = await this.verifyFrontendFidelity(changedFiles);
|
|
62
|
-
if (frontendViolations && frontendViolations.length > 0) {
|
|
63
|
-
return {
|
|
64
|
-
passed: false,
|
|
65
|
-
reason: frontendViolations.join("; ")
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { passed: true };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async verifyRequestFidelity(arg) {
|
|
73
|
-
let userRequest = this.userRequest || "";
|
|
74
|
-
let deliveryDecision = this.deliveryDecision || {};
|
|
75
|
-
let projectContext = this.projectContext || {};
|
|
76
|
-
let changedFiles = [];
|
|
77
|
-
let explicitApprovals = this.explicitApprovals || [];
|
|
78
|
-
|
|
79
|
-
if (Array.isArray(arg)) {
|
|
80
|
-
changedFiles = arg;
|
|
81
|
-
} else if (arg && typeof arg === "object") {
|
|
82
|
-
userRequest = arg.userRequest || userRequest;
|
|
83
|
-
deliveryDecision = arg.deliveryDecision || deliveryDecision;
|
|
84
|
-
projectContext = arg.projectContext || projectContext;
|
|
85
|
-
changedFiles = arg.changedFiles || changedFiles;
|
|
86
|
-
explicitApprovals = arg.explicitApprovals || explicitApprovals;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const blockingDecisions = new Set([
|
|
90
|
-
"BLOCK_STACK_CONFLICT",
|
|
91
|
-
"BLOCK_UNCLEAR_SCOPE",
|
|
92
|
-
"BLOCK_UNSUPPORTED_CONTEXT",
|
|
93
|
-
"REQUIRE_EXPLICIT_AUTHORIZATION",
|
|
94
|
-
"RELEASE_OR_DEPLOY_REQUIRES_APPROVAL"
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
if (blockingDecisions.has(deliveryDecision?.decision)) {
|
|
98
|
-
const filtered = changedFiles.filter(file =>
|
|
99
|
-
!file.startsWith(".ai-workflow/") &&
|
|
100
|
-
!file.startsWith(".git/") &&
|
|
101
|
-
file !== "EVIDENCE.json" &&
|
|
102
|
-
file !== "EVIDENCE.temp.json"
|
|
103
|
-
);
|
|
104
|
-
return {
|
|
105
|
-
status: "BLOCKED",
|
|
106
|
-
passed: false,
|
|
107
|
-
reason: `Delivery decision is blocking: ${deliveryDecision.decision}`,
|
|
108
|
-
decision: deliveryDecision.decision,
|
|
109
|
-
checkedFiles: filtered,
|
|
110
|
-
violations: [`Blocking delivery decision: ${deliveryDecision.decision}`]
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const filteredFiles = changedFiles.filter(file =>
|
|
115
|
-
!file.startsWith(".ai-workflow/") &&
|
|
116
|
-
!file.startsWith(".git/") &&
|
|
117
|
-
file !== "EVIDENCE.json" &&
|
|
118
|
-
file !== "EVIDENCE.temp.json"
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
const req = userRequest.toLowerCase();
|
|
122
|
-
const isReadOnly = /(inspect|audit|read-only|dependencies)/.test(req);
|
|
123
|
-
const isDocsOnly = /(document|doc|readme)/.test(req) && !req.includes("code");
|
|
124
|
-
const isRelease = /(release|publish|deploy)/.test(req);
|
|
125
|
-
|
|
126
|
-
const violations = [];
|
|
127
|
-
|
|
128
|
-
if (isReadOnly && filteredFiles.length > 0) {
|
|
129
|
-
violations.push("Read-only audit task mutated repository files mismatch.");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (isDocsOnly) {
|
|
133
|
-
const codeChanged = filteredFiles.some(file =>
|
|
134
|
-
!file.endsWith(".md") && !file.endsWith(".txt") && !file.startsWith("docs/")
|
|
135
|
-
);
|
|
136
|
-
if (codeChanged) {
|
|
137
|
-
violations.push("Documentation-only task mutated codebase files without justification mismatch.");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (isRelease) {
|
|
142
|
-
const approved = explicitApprovals.includes("npm-publish") || explicitApprovals.includes("deploy-production");
|
|
143
|
-
if (!approved) {
|
|
144
|
-
violations.push("Release/deploy task proceeds without explicit approval token mismatch.");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const reqReact = /(react|next)/.test(req);
|
|
149
|
-
const reqVue = /(vue|nuxt)/.test(req);
|
|
150
|
-
const reqBackend = /(laravel|django|rails|express|nest|fastify|flask)/.test(req);
|
|
151
|
-
const reqWp = /(wordpress|wp)/.test(req);
|
|
152
|
-
const reqCli = /(cli|command)/.test(req);
|
|
153
|
-
|
|
154
|
-
if (reqReact && this.deliveryClass === "standalone-web" && !/prototype/.test(req)) {
|
|
155
|
-
violations.push("React framework page requested, but a standalone HTML/CSS substitute mismatch was delivered instead.");
|
|
156
|
-
}
|
|
157
|
-
if (reqVue && this.deliveryClass === "standalone-web" && !/prototype/.test(req)) {
|
|
158
|
-
violations.push("Vue framework page requested, but a standalone HTML/CSS substitute mismatch was delivered instead.");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (reqBackend && filteredFiles.length > 0) {
|
|
162
|
-
const isLaravel = /\blaravel\b/i.test(req);
|
|
163
|
-
const isDjango = /\bdjango\b/i.test(req);
|
|
164
|
-
const isExpressNestFastify = /\b(express|nest|fastify)\b/i.test(req);
|
|
165
|
-
const isRails = /\brails\b/i.test(req);
|
|
166
|
-
|
|
167
|
-
if (isLaravel) {
|
|
168
|
-
const isLaravelArtifact = filteredFiles.some(file =>
|
|
169
|
-
file === "composer.json" ||
|
|
170
|
-
file === "artisan" ||
|
|
171
|
-
file.startsWith("routes/") ||
|
|
172
|
-
file.startsWith("app/Http/Controllers/") ||
|
|
173
|
-
file.startsWith("app/Models/") ||
|
|
174
|
-
file.startsWith("app/Services/") ||
|
|
175
|
-
file.startsWith("tests/Feature/") ||
|
|
176
|
-
file.startsWith("tests/Unit/") ||
|
|
177
|
-
/(^|\/)(routes\/.*\.php|app\/Http\/Controllers\/.*\.php|app\/Models\/.*\.php|app\/Services\/.*\.php|tests\/Feature\/.*\.php|tests\/Unit\/.*\.php|artisan|composer\.json)$/.test(file)
|
|
178
|
-
);
|
|
179
|
-
if (!isLaravelArtifact) {
|
|
180
|
-
violations.push("Laravel endpoint/scaffold requested, but no valid Laravel files/structure mismatch was delivered.");
|
|
181
|
-
}
|
|
182
|
-
} else if (isDjango) {
|
|
183
|
-
const isDjangoArtifact = filteredFiles.some(file =>
|
|
184
|
-
file === "manage.py" ||
|
|
185
|
-
file === "requirements.txt" ||
|
|
186
|
-
file === "pyproject.toml" ||
|
|
187
|
-
file.endsWith("settings.py") ||
|
|
188
|
-
file.endsWith("urls.py") ||
|
|
189
|
-
file.endsWith("views.py") ||
|
|
190
|
-
file.endsWith("models.py") ||
|
|
191
|
-
file.endsWith("tests.py") ||
|
|
192
|
-
/(^|\/)(manage\.py|settings\.py|urls\.py|views\.py|models\.py|tests\.py|requirements\.txt|pyproject\.toml)$/.test(file)
|
|
193
|
-
);
|
|
194
|
-
if (!isDjangoArtifact) {
|
|
195
|
-
violations.push("Django backend requested, but no valid Django files/structure mismatch was delivered.");
|
|
196
|
-
}
|
|
197
|
-
} else if (isExpressNestFastify) {
|
|
198
|
-
const isExpressNestFastifyArtifact = filteredFiles.some(file =>
|
|
199
|
-
file === "package.json" ||
|
|
200
|
-
file.startsWith("src/routes/") ||
|
|
201
|
-
file.startsWith("src/controllers/") ||
|
|
202
|
-
file.startsWith("src/services/") ||
|
|
203
|
-
file.startsWith("tests/") ||
|
|
204
|
-
/(^|\/)(package\.json|src\/routes\/.*|src\/controllers\/.*|src\/services\/.*|tests\/.*|server\.[a-z]+|app\.[a-z]+|main\.[a-z]+)$/.test(file)
|
|
205
|
-
);
|
|
206
|
-
if (!isExpressNestFastifyArtifact) {
|
|
207
|
-
violations.push("Express/Fastify/Nest backend requested, but no valid Node.js server files/structure mismatch was delivered.");
|
|
208
|
-
}
|
|
209
|
-
} else if (isRails) {
|
|
210
|
-
const isRailsArtifact = filteredFiles.some(file =>
|
|
211
|
-
file === "Gemfile" ||
|
|
212
|
-
file === "config/routes.rb" ||
|
|
213
|
-
file.startsWith("app/controllers/") ||
|
|
214
|
-
file.startsWith("app/models/") ||
|
|
215
|
-
file.startsWith("app/services/") ||
|
|
216
|
-
file.startsWith("test/") ||
|
|
217
|
-
file.startsWith("spec/") ||
|
|
218
|
-
/(^|\/)(Gemfile|config\/routes\.rb|app\/controllers\/.*|app\/models\/.*|app\/services\/.*|test\/.*|spec\/.*)$/.test(file)
|
|
219
|
-
);
|
|
220
|
-
if (!isRailsArtifact) {
|
|
221
|
-
violations.push("Ruby on Rails backend requested, but no valid Rails files/structure mismatch was delivered.");
|
|
222
|
-
}
|
|
223
|
-
} else {
|
|
224
|
-
const isGenericBackendArtifact = filteredFiles.some(file =>
|
|
225
|
-
!file.includes("plain_script.py") &&
|
|
226
|
-
(
|
|
227
|
-
file.startsWith("app/") ||
|
|
228
|
-
file.startsWith("routes/") ||
|
|
229
|
-
file.startsWith("config/") ||
|
|
230
|
-
file.startsWith("src/controllers/") ||
|
|
231
|
-
file.startsWith("src/routes/") ||
|
|
232
|
-
file.startsWith("src/services/") ||
|
|
233
|
-
file.startsWith("backend/") ||
|
|
234
|
-
file.startsWith("server/") ||
|
|
235
|
-
file.startsWith("api/") ||
|
|
236
|
-
file.endsWith(".py") ||
|
|
237
|
-
file.endsWith(".php") ||
|
|
238
|
-
file.endsWith(".rb") ||
|
|
239
|
-
file.endsWith(".go")
|
|
240
|
-
)
|
|
241
|
-
);
|
|
242
|
-
if (!isGenericBackendArtifact) {
|
|
243
|
-
violations.push("Backend endpoint requested, but wrong backend artifact structure mismatch was delivered.");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (reqCli && filteredFiles.length > 0) {
|
|
249
|
-
const isCliArtifact = filteredFiles.some(file =>
|
|
250
|
-
file.startsWith("bin/") || file.startsWith("src/commands/")
|
|
251
|
-
);
|
|
252
|
-
if (!isCliArtifact) {
|
|
253
|
-
violations.push("CLI command requested, but a loose script outside the executable bin/commands configuration mismatch was delivered.");
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (reqWp && filteredFiles.length > 0) {
|
|
258
|
-
let isWpArtifact = false;
|
|
259
|
-
for (const file of filteredFiles) {
|
|
260
|
-
const basename = path.basename(file);
|
|
261
|
-
const ext = path.extname(file).toLowerCase();
|
|
262
|
-
|
|
263
|
-
if (file.startsWith("wp-content/") || file === "wp-config.php") {
|
|
264
|
-
isWpArtifact = true;
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (basename === "functions.php" ||
|
|
269
|
-
basename === "front-page.php" ||
|
|
270
|
-
basename === "single.php" ||
|
|
271
|
-
basename === "page.php" ||
|
|
272
|
-
basename === "archive.php" ||
|
|
273
|
-
basename === "theme.json" ||
|
|
274
|
-
file.startsWith("template-parts/") ||
|
|
275
|
-
file.includes("/template-parts/")) {
|
|
276
|
-
isWpArtifact = true;
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
if (basename === "style.css") {
|
|
280
|
-
const hasHeader = await hasWpThemeHeader(this.cwd, file);
|
|
281
|
-
if (hasHeader) {
|
|
282
|
-
isWpArtifact = true;
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (basename === "index.php") {
|
|
287
|
-
isWpArtifact = true;
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (basename === "uninstall.php" ||
|
|
292
|
-
basename === "composer.json" ||
|
|
293
|
-
file.startsWith("includes/") || file.includes("/includes/") ||
|
|
294
|
-
file.startsWith("admin/") || file.includes("/admin/") ||
|
|
295
|
-
file.startsWith("public/") || file.includes("/public/") ||
|
|
296
|
-
file.startsWith("src/") || file.includes("/src/")) {
|
|
297
|
-
isWpArtifact = true;
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
if (ext === ".php") {
|
|
301
|
-
const hasHeader = await hasWpPluginHeader(this.cwd, file);
|
|
302
|
-
if (hasHeader) {
|
|
303
|
-
isWpArtifact = true;
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const hasHtml = filteredFiles.some(file => path.extname(file).toLowerCase() === ".html" || path.extname(file).toLowerCase() === ".htm");
|
|
310
|
-
|
|
311
|
-
if (!isWpArtifact || hasHtml) {
|
|
312
|
-
violations.push("WordPress plugin or theme requested, but files were delivered outside the WordPress structure or contained standalone HTML.");
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const frontendViolations = await this.verifyFrontendFidelity(filteredFiles);
|
|
317
|
-
if (frontendViolations && frontendViolations.length > 0) {
|
|
318
|
-
violations.push(...frontendViolations);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const status = violations.length > 0 ? "BLOCKED" : "PASS";
|
|
322
|
-
return {
|
|
323
|
-
status,
|
|
324
|
-
passed: status === "PASS",
|
|
325
|
-
reason: violations.length > 0 ? `Fidelity violations: ${violations.join("; ")}` : "Verification passed",
|
|
326
|
-
decision: deliveryDecision.decision || this.deliveryClass,
|
|
327
|
-
checkedFiles: filteredFiles,
|
|
328
|
-
violations
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async verifyFrontendFidelity(changedFiles) {
|
|
333
|
-
const skillPaths = [
|
|
334
|
-
path.join(this.cwd, ".agents/skills/frontend-design-system/SKILL.md"),
|
|
335
|
-
path.join(this.cwd, ".agents/skills/frontend_design_system/SKILL.md"),
|
|
336
|
-
path.join(this.cwd, ".ai-workflow/opencode/skills/frontend-design-system/SKILL.md"),
|
|
337
|
-
path.join(this.cwd, ".ai-workflow/opencode/skills/frontend_design_system/SKILL.md"),
|
|
338
|
-
path.join(this.cwd, "opencode/skills/frontend-design-system/SKILL.md"),
|
|
339
|
-
path.join(this.cwd, "opencode/skills/frontend_design_system/SKILL.md")
|
|
340
|
-
];
|
|
341
|
-
|
|
342
|
-
let hasDesignSystemSkill = false;
|
|
343
|
-
for (const p of skillPaths) {
|
|
344
|
-
const exists = await fs.access(p).then(() => true).catch(() => false);
|
|
345
|
-
if (exists) {
|
|
346
|
-
hasDesignSystemSkill = true;
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (!hasDesignSystemSkill) {
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const uiFiles = changedFiles.filter((file) =>
|
|
356
|
-
/\.(tsx|jsx|vue|html|css|scss|less|blade\.php)$/.test(file)
|
|
357
|
-
);
|
|
358
|
-
if (uiFiles.length === 0) return null;
|
|
359
|
-
|
|
360
|
-
const violations = [];
|
|
361
|
-
let cssContentCombined = "";
|
|
362
|
-
let htmlContentCombined = "";
|
|
363
|
-
let styleAttrsCount = 0;
|
|
364
|
-
|
|
365
|
-
for (const file of uiFiles) {
|
|
366
|
-
try {
|
|
367
|
-
const fullPath = path.join(this.cwd, file);
|
|
368
|
-
const content = await fs.readFile(fullPath, "utf8");
|
|
369
|
-
|
|
370
|
-
if (/\.(css|scss|less|vue|tsx|jsx)$/.test(file)) {
|
|
371
|
-
cssContentCombined += content;
|
|
372
|
-
}
|
|
373
|
-
if (/\.(html|vue|tsx|jsx|blade\.php)$/.test(file)) {
|
|
374
|
-
htmlContentCombined += content;
|
|
375
|
-
|
|
376
|
-
// Match inline style attributes
|
|
377
|
-
const inlineStyles = content.match(/style\s*=\s*["']/g) || [];
|
|
378
|
-
styleAttrsCount += inlineStyles.length;
|
|
379
|
-
|
|
380
|
-
// CDN Checks
|
|
381
|
-
if (/(unpkg\.com|cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net|tailwindcss|bootstrap)/i.test(content)) {
|
|
382
|
-
const isApproved = (this.explicitApprovals || []).some(app =>
|
|
383
|
-
/cdn-styles|tailwind|bootstrap/i.test(app)
|
|
384
|
-
);
|
|
385
|
-
if (!isApproved) {
|
|
386
|
-
violations.push(`Fidelity Violation: standalone external CDN style/library dependency detected in '${file}' without explicit authorization.`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
} catch {
|
|
391
|
-
// ignore
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// 1. Minimum CSS tokens check
|
|
396
|
-
// Option D: only enforce when CSS volume is substantial (>200 chars of effective rules)
|
|
397
|
-
// and bypass is available via explicit approval token 'no-css-vars' or 'css-vars-bypass'.
|
|
398
|
-
const hasRules = cssContentCombined.includes("{");
|
|
399
|
-
const cssVarsApproved = (this.explicitApprovals || []).some(app =>
|
|
400
|
-
/no-css-vars|css-vars-bypass/i.test(app)
|
|
401
|
-
);
|
|
402
|
-
const cssRulesContent = cssContentCombined.replace(/\/\*[\s\S]*?\*\//g, "").trim();
|
|
403
|
-
const isSubstantialCss = cssRulesContent.length > 200;
|
|
404
|
-
const hasCssVars = /(--[a-zA-Z0-9_-]+\s*:|var\(--[a-zA-Z0-9_-]+\))/g.test(cssContentCombined);
|
|
405
|
-
if (hasRules && isSubstantialCss && !hasCssVars && !cssVarsApproved) {
|
|
406
|
-
violations.push("Fidelity Violation: Design System token mismatch. No CSS custom properties (variables) detected or used in changed UI stylesheets. Add explicit approval 'no-css-vars' to bypass this check for simple or legacy stylesheets.");
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// 2. Excessive style="..." inline styles check
|
|
410
|
-
if (styleAttrsCount > 5) {
|
|
411
|
-
violations.push(`Fidelity Violation: excessive inline styles detected (${styleAttrsCount} style="..." declarations). Use CSS classes or utility classes instead.`);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// 3. Main HTML semantic tags check
|
|
415
|
-
if (htmlContentCombined.length > 0) {
|
|
416
|
-
const hasSemanticTags = /<(header|main|footer|nav|section)\b/i.test(htmlContentCombined);
|
|
417
|
-
const hasH1 = /<h1\b/i.test(htmlContentCombined);
|
|
418
|
-
if (!hasSemanticTags && !hasH1) {
|
|
419
|
-
violations.push("Fidelity Violation: HTML layout structure lacks basic semantic elements (<header>, <main>, <footer>, <nav>, <section>) or <h1> tag.");
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return violations;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
async function hasWpThemeHeader(cwd, file) {
|
|
428
|
-
try {
|
|
429
|
-
const fullPath = path.join(cwd, file);
|
|
430
|
-
const content = await fs.readFile(fullPath, "utf8");
|
|
431
|
-
return /\bTheme Name\s*:/i.test(content) || /\bTheme URI\s*:/i.test(content);
|
|
432
|
-
} catch {
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function hasWpPluginHeader(cwd, file) {
|
|
438
|
-
try {
|
|
439
|
-
const fullPath = path.join(cwd, file);
|
|
440
|
-
const content = await fs.readFile(fullPath, "utf8");
|
|
441
|
-
return /\bPlugin Name\s*:/i.test(content) || /\bDescription\s*:/i.test(content);
|
|
442
|
-
} catch {
|
|
443
|
-
return false;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export const DELIVERY_SUMMARY_FIELDS = Object.freeze([
|
|
2
|
-
"Status",
|
|
3
|
-
"Branch",
|
|
4
|
-
"Changes",
|
|
5
|
-
"Validation",
|
|
6
|
-
"Known limitations"
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
export function buildDeliverySummary({ evidence }) {
|
|
10
|
-
const commands = (evidence?.commands || []).map((item) => `${item.command || item.name}: ${item.status}`).join("; ") || "No validation commands recorded";
|
|
11
|
-
return {
|
|
12
|
-
Status: evidence?.status || "BLOCKED",
|
|
13
|
-
Branch: evidence?.branch || "unknown",
|
|
14
|
-
Changes: (evidence?.changedFiles || []).join(", ") || "No changed files recorded",
|
|
15
|
-
Validation: commands,
|
|
16
|
-
"Known limitations": (evidence?.limitations || []).join("; ") || "None recorded"
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function validateDeliverySummary(summary) {
|
|
21
|
-
const missing = DELIVERY_SUMMARY_FIELDS.filter((field) => !String(summary?.[field] || "").trim());
|
|
22
|
-
const status = String(summary?.Status || "").trim();
|
|
23
|
-
const invalid = [];
|
|
24
|
-
if (!["COMPLETED", "COMPLETED_WITH_NOTES", "BLOCKED"].includes(status)) invalid.push("Status");
|
|
25
|
-
if (status !== "BLOCKED" && /FAIL|BLOCKED/.test(String(summary?.Validation || ""))) invalid.push("successful status with failed validation");
|
|
26
|
-
return { status: missing.length || invalid.length ? "FAIL" : "PASS", missing, invalid };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function formatDeliverySummary(summary) {
|
|
30
|
-
return DELIVERY_SUMMARY_FIELDS.map((field) => `${field}: ${summary?.[field] || "NOT_RECORDED"}`).join("\n");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Backward-compatible aliases for integrations that imported the previous API.
|
|
34
|
-
export const CANONICAL_FINALIZATION_FIELDS = DELIVERY_SUMMARY_FIELDS;
|
|
35
|
-
export const buildCanonicalFinalization = ({ collectorStatus = "BLOCKED", branchRecovery = "unknown" } = {}) => ({
|
|
36
|
-
Status: collectorStatus === "PASS" ? "COMPLETED" : collectorStatus === "PASS_WITH_NOTES" ? "COMPLETED_WITH_NOTES" : "BLOCKED",
|
|
37
|
-
Branch: branchRecovery,
|
|
38
|
-
Changes: "See observed diff",
|
|
39
|
-
Validation: collectorStatus,
|
|
40
|
-
"Known limitations": "See evidence"
|
|
41
|
-
});
|
|
42
|
-
export const validateCanonicalFinalization = validateDeliverySummary;
|
|
43
|
-
export const formatCanonicalFinalization = formatDeliverySummary;
|