@williambeto/ai-workflow 2.3.6 → 2.4.0
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/AGENTS.md +5 -0
- package/CHANGELOG.md +43 -0
- package/dist-assets/skills/frontend-design-system/SKILL.md +83 -0
- package/package.json +2 -1
- package/src/cli.js +29 -3
- package/src/commands/collect-evidence.js +2 -2
- package/src/commands/execute.js +11 -2
- package/src/commands/validate.js +81 -0
- package/src/core/evidence/evidence-ledger.js +45 -0
- package/src/core/finalization/finalizer.js +56 -7
- package/src/core/validation/artifact-fidelity-gate.js +364 -3
- package/src/core/validation/delivery-decision-engine.js +283 -8
- package/src/core/validation/evidence-collector.js +112 -3
- package/src/core/validation/visual-verifier.js +159 -0
package/AGENTS.md
CHANGED
|
@@ -25,3 +25,8 @@ Atlas routes. Nexus owns discovery, requirements, and spec authoring. Orion owns
|
|
|
25
25
|
## Evidence
|
|
26
26
|
|
|
27
27
|
Quick and standard work require only branch, changed areas, commands/results, and limitations in the handoff. Full/release/audit/security work may persist machine-generated evidence.
|
|
28
|
+
|
|
29
|
+
## Core Concept
|
|
30
|
+
|
|
31
|
+
AI Workflow is an npm CLI that installs agents, commands, skills, policies and templates into your repository.
|
|
32
|
+
It guides coding agents from request to implementation, validation and evidence. AI Workflow Kit preserves workflow integrity so coding agents cannot skip from request to code to success without an explicit implementation path, proportional validation, and final evidence.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
|
+
## [2.4.0] - 2026-06-20
|
|
2
|
+
|
|
3
|
+
### Added
|
|
4
|
+
- Native Visual Evidence capture in `collect-evidence` via `--visual-dist` and `--port` parameters, launching a static loopback server and capturing Chromium screenshots using Playwright.
|
|
5
|
+
- CSS/HTML Design System Gate validation in `ArtifactFidelityGate` that automatically runs when the `frontend-design-system` skill is loaded in the repository, checking for CSS custom properties, inline style density, CDN scripts, and semantic HTML layout tags.
|
|
6
|
+
- Native accessibility auditing command `npx aw validate --a11y` via Axe-core running inside Playwright, blocking critical violations and persisting reports in `.evidence/a11y/report.json`.
|
|
7
|
+
- Standard `frontend-design-system` skill (`.agents/skills/frontend-design-system/SKILL.md`) to guide Astra and other agents through visual hierarchy, spacing grids, theme palettes, accessible contrast, and output rules.
|
|
8
|
+
- Automated tests covering static loopback servers, Playwright screenshots, Axe-core checks, inline style gates, CSS tokens, and HTML semantics.
|
|
9
|
+
|
|
10
|
+
## [2.3.7] - 2026-06-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Bumped version to 2.3.7.
|
|
14
|
+
|
|
15
|
+
## [2.3.6] - 2026-06-20
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Request-aware delivery decision and artifact fidelity gates (GREEN phase) to prevent false-positive completions in frontend/backend stack tasks.
|
|
19
|
+
- Hardened check for blocking delivery decisions in `ArtifactFidelityGate` and `Finalizer`.
|
|
20
|
+
- False-positive regression tests and release readiness audit documentation.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Aligned WordPress root detection logic between `DeliveryDecisionEngine` and `ArtifactFidelityGate`.
|
|
24
|
+
- Corrected architecture and design patterns policy relative paths in the technical plan prompt.
|
|
25
|
+
- Normalized approved release decision string to `RELEASE_OR_DEPLOY_APPROVED`.
|
|
26
|
+
|
|
27
|
+
## [2.3.5] - 2026-06-20
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- Corrected the governance policy relative path header in all standard skill Markdown files.
|
|
31
|
+
|
|
32
|
+
## [2.3.4] - 2026-06-20
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- Corrected the mapping and resolution path of policies under `opencode/docs`.
|
|
36
|
+
|
|
37
|
+
## [2.3.3] - 2026-06-20
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- First phase implementation of the Delivery Decision Engine and Artifact Fidelity Gate checking for correct technology stacks.
|
|
41
|
+
- Hardened PromptContracts and validation scripts for stack-aware quality checking.
|
|
42
|
+
|
|
1
43
|
## [2.3.2] - 2026-06-19
|
|
2
44
|
|
|
45
|
+
|
|
3
46
|
### Documentation
|
|
4
47
|
- Clarified first-time usage with `npx @williambeto/ai-workflow init`.
|
|
5
48
|
- Clarified local installed-package alias usage with `npx aw init`, `npx aw doctor`, and `npx aw run`.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-design-system
|
|
3
|
+
description: Design system guidelines and rules for creating premium, consistent, and accessible interfaces.
|
|
4
|
+
governance: ../../docs/policies/SKILLS_COMMON_GOVERNANCE.md
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend Design System PromptContract
|
|
8
|
+
|
|
9
|
+
## Role
|
|
10
|
+
Guides Astra and other agents in styling UI, hierarchy, and responsive layout states to build premium interfaces.
|
|
11
|
+
|
|
12
|
+
## Objective
|
|
13
|
+
Evolve technical governance to guarantee real visual and frontend quality by enforcing visual thesis, design tokens, responsive states, semantic HTML, and accessibility validations.
|
|
14
|
+
|
|
15
|
+
## Trusted context
|
|
16
|
+
- Project design system tokens (CSS custom properties)
|
|
17
|
+
- HTML semantic markup standards
|
|
18
|
+
- Accessible color ratios (WCAG 2.1 AA/AAA)
|
|
19
|
+
- Visual asset registries (images, local SVGs)
|
|
20
|
+
- Client styling guidelines
|
|
21
|
+
|
|
22
|
+
## User input
|
|
23
|
+
- Feature requirements and wireframes
|
|
24
|
+
- Active UI components and styles
|
|
25
|
+
- Target viewport dimensions (mobile, desktop)
|
|
26
|
+
- Feedback on visual quality or layout defects
|
|
27
|
+
|
|
28
|
+
## Constraints
|
|
29
|
+
- **Visual Thesis & Deliberate Composition**: Layouts must be cohesive. Avoid generic grids, pill soup, or unorganized structures.
|
|
30
|
+
- **Copy Specificity**: Never use placeholders ("Lorem Ipsum"). Write domain-specific, realistic copy.
|
|
31
|
+
- **Strict Design System / CSS Gate**:
|
|
32
|
+
- CSS tokens must define a primary palette, background, surface, text styles, and spacing scale.
|
|
33
|
+
- Exposing excessive inline styles (`style="..."`) is prohibited.
|
|
34
|
+
- CDN dependencies for visual libraries (Tailwind, Bootstrap) are blocked unless explicitly authorized.
|
|
35
|
+
- **Typography Scale**: Maintain a single `<h1>` per page. Line heights must be balanced: 1.1-1.25 for headings, 1.5-1.7 for readable body text.
|
|
36
|
+
- **Acessibilidade (A11y)**: Must achieve WCAG compliance. Contrast ratio must be at least 4.5:1 for body text (3:1 for headings/large text). Semantic HTML elements (`<header>`, `<main>`, `<footer>`, `<nav>`, `<section>`) must structure the layout.
|
|
37
|
+
- **Tokens de Cor & Spacing**: Spacing must adhere to an 8px-grid scale (e.g. 4px, 8px, 16px, 24px, 32px, 48px). Colors must use CSS custom properties: `--color-primary`, `--color-background`, `--color-surface`, `--color-text`.
|
|
38
|
+
- **Regras Anti-Output Fraco**: Prevent empty blocks, broken states, and plain gradient fills with no concrete content.
|
|
39
|
+
|
|
40
|
+
## Allowed tools
|
|
41
|
+
- `view_file` to inspect existing design tokens, components, and templates.
|
|
42
|
+
- `write_to_file` and `replace_file_content` to edit CSS variables and structured HTML markup.
|
|
43
|
+
- `run_command` to execute validate command line checks (`npx aw validate --a11y` and `collect-evidence`).
|
|
44
|
+
|
|
45
|
+
## Forbidden actions
|
|
46
|
+
- Deploying raw HTML without CSS custom properties/tokens.
|
|
47
|
+
- Injecting raw CDN CSS/JS libraries into page headers without authorization.
|
|
48
|
+
- Committing unstyled layout states or missing loading/empty/error/success conditions.
|
|
49
|
+
|
|
50
|
+
## Procedure
|
|
51
|
+
1. **Analyze existing Design Tokens**: Check if the project already has CSS variables or custom properties. Adapt to the project's tokens if found, otherwise define a default safe set (primary, background, surface, text, space-*, font-*).
|
|
52
|
+
2. **Draft Semantic Structure**: Build the UI skeleton using HTML5 semantic elements (`<header>`, `<main>`, etc.) with unique IDs for testing.
|
|
53
|
+
3. **Apply Spacing and Typography Scales**: Implement the 8px grid system and a tight heading line-height / relaxed body line-height scale.
|
|
54
|
+
4. **Implement UI States**: Ensure loading, empty, error, and success states are styled, fully functional, and visually robust.
|
|
55
|
+
5. **A11y Verification**: Verify WCAG contrast levels. Run validation commands with `npm run validate` or `npx aw validate --a11y` to check for accessibility violations.
|
|
56
|
+
6. **Collect Visual Evidence**: Capture desktop and mobile screenshots using `collect-evidence --visual-dist` to verify design fidelity.
|
|
57
|
+
|
|
58
|
+
## Expected output schema
|
|
59
|
+
The output must report:
|
|
60
|
+
- CSS variables/tokens detected or introduced
|
|
61
|
+
- Accessibility audit summary (Axe-core run results)
|
|
62
|
+
- UI states implemented (loading, empty, error, success)
|
|
63
|
+
- Semantic HTML tags list
|
|
64
|
+
- Visual evidence location (`.evidence/visual/`)
|
|
65
|
+
- Compliance decision: PASS | FAIL (with detailed reasons)
|
|
66
|
+
|
|
67
|
+
## Evidence required
|
|
68
|
+
- Screenshot artifacts in `.evidence/visual/` (mobile and desktop)
|
|
69
|
+
- Axe-core accessibility report in `.evidence/a11y/`
|
|
70
|
+
- Verified HTML/CSS files conforming to the design gates
|
|
71
|
+
|
|
72
|
+
## Stop conditions
|
|
73
|
+
- Absence of CSS variables/tokens defining colors or spacing
|
|
74
|
+
- Axe-core report containing critical a11y violations
|
|
75
|
+
- Exceeding inline style limits or using unauthorized CDNs
|
|
76
|
+
|
|
77
|
+
## Failure modes
|
|
78
|
+
- **White screenshots or rendering failures**: Local server path or assets not loaded.
|
|
79
|
+
- **Unstyled outputs**: Styles not applied or missing CSS files.
|
|
80
|
+
- **Broken layout states**: Misaligned responsive viewports or broken container grids.
|
|
81
|
+
|
|
82
|
+
## Escalation rules
|
|
83
|
+
- Report critical design system deviations to the frontend lead or escalate to the primary owner.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williambeto/ai-workflow",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "AI Workflow Kit — OpenCode-first software delivery workflow with agents, commands, skills, validation, and evidence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "José Willams",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"@semantic-release/npm": "^13.1.5",
|
|
93
93
|
"ajv": "^8.20.0",
|
|
94
94
|
"ajv-formats": "^3.0.1",
|
|
95
|
+
"axe-core": "^4.10.0",
|
|
95
96
|
"markdownlint-cli": "^0.48.0",
|
|
96
97
|
"playwright": "^1.60.0",
|
|
97
98
|
"semantic-release": "^25.0.3",
|
package/src/cli.js
CHANGED
|
@@ -12,7 +12,8 @@ Usage:
|
|
|
12
12
|
ai-workflow execute "<request>" [--task=<slug>] [--request="<request>"]
|
|
13
13
|
ai-workflow run --spec-path=<path>
|
|
14
14
|
ai-workflow init [--yes] [--force] [--dry-run] [--no-install] [--no-overwrite] [--gemini] [--claude] [--codex] [--antigravity] [--profile=<profile>]
|
|
15
|
-
ai-workflow
|
|
15
|
+
ai-workflow validate [--a11y] [--visual-dist=<path>] [--port=<port>]
|
|
16
|
+
ai-workflow collect-evidence [--task=<slug>] [--mode=<quick|standard|full>] [--dry-run] [--visual-dist=<path>] [--port=<port>]
|
|
16
17
|
ai-workflow doctor
|
|
17
18
|
|
|
18
19
|
Commands:
|
|
@@ -30,6 +31,8 @@ function parseFlags(args) {
|
|
|
30
31
|
const taskArg = args.find((arg) => arg.startsWith("--task="));
|
|
31
32
|
const modeArg = args.find((arg) => arg.startsWith("--mode="));
|
|
32
33
|
const requestArg = args.find((arg) => arg.startsWith("--request="));
|
|
34
|
+
const visualDistArg = args.find((arg) => arg.startsWith("--visual-dist="));
|
|
35
|
+
const portArg = args.find((arg) => arg.startsWith("--port="));
|
|
33
36
|
const profileIdx = args.indexOf("--profile");
|
|
34
37
|
const profileVal = profileEqArg
|
|
35
38
|
? profileEqArg.replace("--profile=", "")
|
|
@@ -52,7 +55,10 @@ function parseFlags(args) {
|
|
|
52
55
|
profile: profileVal || undefined,
|
|
53
56
|
taskSlug: taskArg ? taskArg.replace("--task=", "") : undefined,
|
|
54
57
|
mode: modeArg ? modeArg.replace("--mode=", "") : undefined,
|
|
55
|
-
request: requestArg ? requestArg.replace("--request=", "") : undefined
|
|
58
|
+
request: requestArg ? requestArg.replace("--request=", "") : undefined,
|
|
59
|
+
visualDist: visualDistArg ? visualDistArg.replace("--visual-dist=", "") : undefined,
|
|
60
|
+
port: portArg ? portArg.replace("--port=", "") : undefined,
|
|
61
|
+
a11y: args.includes("--a11y")
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
64
|
|
|
@@ -104,7 +110,27 @@ export async function runCli(args) {
|
|
|
104
110
|
if (command === "collect-evidence") {
|
|
105
111
|
const flags = parseFlags(args.slice(1));
|
|
106
112
|
if (flags.mode && !["quick", "standard", "full"].includes(flags.mode)) throw new Error("--mode must be quick, standard, or full");
|
|
107
|
-
await runCollectEvidence({
|
|
113
|
+
await runCollectEvidence({
|
|
114
|
+
cwd: process.cwd(),
|
|
115
|
+
taskSlug: flags.taskSlug,
|
|
116
|
+
mode: flags.mode,
|
|
117
|
+
dryRun: flags.dryRun,
|
|
118
|
+
visualDist: flags.visualDist,
|
|
119
|
+
port: flags.port
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (command === "validate") {
|
|
125
|
+
const flags = parseFlags(args.slice(1));
|
|
126
|
+
const { runValidate } = await import("./commands/validate.js");
|
|
127
|
+
await runValidate({
|
|
128
|
+
cwd: process.cwd(),
|
|
129
|
+
taskSlug: flags.taskSlug,
|
|
130
|
+
a11y: flags.a11y,
|
|
131
|
+
visualDist: flags.visualDist,
|
|
132
|
+
port: flags.port
|
|
133
|
+
});
|
|
108
134
|
return;
|
|
109
135
|
}
|
|
110
136
|
|
|
@@ -5,13 +5,13 @@ import { buildDeliverySummary, formatDeliverySummary } from "../core/validation/
|
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
|
|
8
|
-
export async function runCollectEvidence({ cwd, exitOnError = true, taskSlug = null, mode = null, dryRun = false, profile = "generic", branchRecovery = "NOT_RECORDED" }) {
|
|
8
|
+
export async function runCollectEvidence({ cwd, exitOnError = true, taskSlug = null, mode = null, dryRun = false, profile = "generic", branchRecovery = "NOT_RECORDED", userRequest = null, explicitApprovals = [], visualDist = null, port = null }) {
|
|
9
9
|
const qualityGuard = new QualityGuard({ cwd, taskSlug, mode });
|
|
10
10
|
const planner = new ValidationPlanner({ cwd, qualityGuard });
|
|
11
11
|
const tasks = await planner.plan(profile);
|
|
12
12
|
|
|
13
13
|
const persist = !dryRun && mode === "full";
|
|
14
|
-
const collector = new EvidenceCollector({ cwd, timeout: 60000, taskSlug, mode, profile, branchRecovery });
|
|
14
|
+
const collector = new EvidenceCollector({ cwd, timeout: 60000, taskSlug, mode, profile, branchRecovery, userRequest, explicitApprovals, visualDist, port });
|
|
15
15
|
const evidence = await collector.collect(tasks, { writeArtifact: persist });
|
|
16
16
|
const summary = buildDeliverySummary({ evidence });
|
|
17
17
|
|
package/src/commands/execute.js
CHANGED
|
@@ -257,7 +257,9 @@ export async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverri
|
|
|
257
257
|
taskSlug,
|
|
258
258
|
mode: plan.mode,
|
|
259
259
|
profile: plan.profile,
|
|
260
|
-
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED"
|
|
260
|
+
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
|
|
261
|
+
userRequest: naturalRequest,
|
|
262
|
+
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map(a => a.trim()) : []
|
|
261
263
|
});
|
|
262
264
|
const quality = await new QualityGuard({ cwd, taskSlug, mode: plan.mode }).verify();
|
|
263
265
|
return combineValidation(evidence, quality);
|
|
@@ -329,7 +331,14 @@ export async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverri
|
|
|
329
331
|
finalStatus = "FAIL_QUALITY_GATE";
|
|
330
332
|
} else {
|
|
331
333
|
// Workspace stabilized, now run Artifact Fidelity Gate
|
|
332
|
-
const fidelityResult = await finalizer.verifyFidelity(
|
|
334
|
+
const fidelityResult = await finalizer.verifyFidelity({
|
|
335
|
+
userRequest: naturalRequest,
|
|
336
|
+
projectContext: result.evidence?.deliveryDecision?.projectContext || null,
|
|
337
|
+
deliveryDecision: result.evidence?.deliveryDecision || null,
|
|
338
|
+
changedFiles: result.evidence?.changedFiles || [],
|
|
339
|
+
evidence: result.evidence || null,
|
|
340
|
+
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map(a => a.trim()) : []
|
|
341
|
+
});
|
|
333
342
|
if (!fidelityResult.passed) {
|
|
334
343
|
console.log(`\n[FINALIZER BLOCKED] BLOCKED: Artifact fidelity check failed!`);
|
|
335
344
|
console.log(`Reason: ${fidelityResult.reason}`);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EvidenceCollector } from "../core/validation/evidence-collector.js";
|
|
2
|
+
import { QualityGuard } from "../core/validation/quality-guard.js";
|
|
3
|
+
import { ValidationPlanner } from "../core/validation/validation-planner.js";
|
|
4
|
+
import { VisualVerifier } from "../core/validation/visual-verifier.js";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
export async function runValidate({ cwd = process.cwd(), exitOnError = true, taskSlug = null, a11y = false, visualDist = null, port = 8080 }) {
|
|
9
|
+
console.log("--- Running Validation ---");
|
|
10
|
+
const qualityGuard = new QualityGuard({ cwd, taskSlug, mode: "standard" });
|
|
11
|
+
const planner = new ValidationPlanner({ cwd, qualityGuard });
|
|
12
|
+
const tasks = await planner.plan("standard");
|
|
13
|
+
|
|
14
|
+
const collector = new EvidenceCollector({ cwd, timeout: 60000, taskSlug, mode: "standard" });
|
|
15
|
+
const results = [];
|
|
16
|
+
|
|
17
|
+
for (const task of tasks) {
|
|
18
|
+
console.log(`Running validation task: ${task.name}...`);
|
|
19
|
+
const res = collector.runTask(task);
|
|
20
|
+
results.push(res);
|
|
21
|
+
console.log(`- Status: ${res.status} (${res.summary})`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let a11yResult = null;
|
|
25
|
+
if (a11y) {
|
|
26
|
+
if (!visualDist) {
|
|
27
|
+
throw new Error("Accessibility audit (--a11y) requires a target build directory specified via --visual-dist=<path>.");
|
|
28
|
+
}
|
|
29
|
+
console.log(`\n--- Running Accessibility Audit ---`);
|
|
30
|
+
console.log(`Serving ${visualDist} on port ${port}...`);
|
|
31
|
+
try {
|
|
32
|
+
const verifier = new VisualVerifier({ cwd, visualDist, port });
|
|
33
|
+
a11yResult = await verifier.runAccessibilityCheck();
|
|
34
|
+
console.log(`Accessibility audit complete.`);
|
|
35
|
+
console.log(`- Critical violations: ${a11yResult.critical.length}`);
|
|
36
|
+
console.log(`- Serious violations: ${a11yResult.serious.length}`);
|
|
37
|
+
console.log(`- Report saved to: ${path.relative(cwd, a11yResult.reportPath)}`);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`Accessibility audit failed: ${err.message}`);
|
|
40
|
+
if (exitOnError) process.exit(1);
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Load future configuration if present
|
|
46
|
+
let config = {};
|
|
47
|
+
try {
|
|
48
|
+
const configContent = await fs.readFile(path.join(cwd, "ai-workflow.config.json"), "utf8");
|
|
49
|
+
config = JSON.parse(configContent);
|
|
50
|
+
} catch {}
|
|
51
|
+
|
|
52
|
+
const hasFailure = results.some((r) => ["FAIL", "BLOCKED", "FAIL_QUALITY_GATE"].includes(r.status));
|
|
53
|
+
|
|
54
|
+
let hasA11yFailure = false;
|
|
55
|
+
if (a11yResult) {
|
|
56
|
+
const criticalIsBlocker = true; // Critical violations are always blockers
|
|
57
|
+
const seriousIsBlocker = config.a11y?.seriousAsBlocker === true;
|
|
58
|
+
|
|
59
|
+
if (criticalIsBlocker && a11yResult.critical.length > 0) {
|
|
60
|
+
hasA11yFailure = true;
|
|
61
|
+
console.error(`Accessibility FAILED: ${a11yResult.critical.length} critical accessibility violations detected.`);
|
|
62
|
+
}
|
|
63
|
+
if (a11yResult.serious.length > 0) {
|
|
64
|
+
if (seriousIsBlocker) {
|
|
65
|
+
hasA11yFailure = true;
|
|
66
|
+
console.error(`Accessibility FAILED: ${a11yResult.serious.length} serious accessibility violations detected (blocked by configuration).`);
|
|
67
|
+
} else {
|
|
68
|
+
console.warn(`Accessibility WARNING: ${a11yResult.serious.length} serious accessibility violations detected.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (hasFailure || hasA11yFailure) {
|
|
74
|
+
console.log("\nValidation FAILED.");
|
|
75
|
+
if (exitOnError) process.exit(1);
|
|
76
|
+
return { passed: false, results, a11yResult };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log("\nValidation PASSED.");
|
|
80
|
+
return { passed: true, results, a11yResult };
|
|
81
|
+
}
|
|
@@ -50,4 +50,49 @@ export class EvidenceLedger {
|
|
|
50
50
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
51
51
|
await fs.writeFile(targetPath, JSON.stringify(this.events, null, 2), "utf8");
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
async verifyRoleConfinement() {
|
|
55
|
+
const violations = [];
|
|
56
|
+
const events = this.events;
|
|
57
|
+
|
|
58
|
+
const atlasImplements = events.some(e => e.actor === "Atlas" && (e.eventType === "implementation_start" || e.eventType === "implementation_complete"));
|
|
59
|
+
if (atlasImplements) {
|
|
60
|
+
violations.push("Atlas cannot implement directly");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const nexusMutates = events.some(e => e.actor === "Nexus" && e.eventType === "file_mutate");
|
|
64
|
+
if (nexusMutates) {
|
|
65
|
+
violations.push("Nexus cannot mutate files during discovery");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sageValidates = events.some(e => e.actor === "Sage" && (e.eventType === "validation_start" || e.eventType === "validation_complete"));
|
|
69
|
+
const hasFidelity = events.some(e => e.eventType === "fidelity_check" || e.eventType === "fidelity_verification");
|
|
70
|
+
if (sageValidates && !hasFidelity) {
|
|
71
|
+
violations.push("Sage cannot validate without fidelity evidence");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const phoenixRemediates = events.find(e => e.actor === "Phoenix" && e.eventType === "remediation_start");
|
|
75
|
+
if (phoenixRemediates) {
|
|
76
|
+
const findings = phoenixRemediates.data?.findings;
|
|
77
|
+
if (!findings || findings.length === 0) {
|
|
78
|
+
violations.push("Phoenix cannot perform remediation without findings");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const orionReleases = events.find(e => e.actor === "Orion" && e.eventType === "release_start");
|
|
83
|
+
if (orionReleases) {
|
|
84
|
+
const approvals = orionReleases.data?.approvals;
|
|
85
|
+
if (!approvals || approvals.length === 0) {
|
|
86
|
+
violations.push("Orion cannot release without explicit approval");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const passed = violations.length === 0;
|
|
91
|
+
return {
|
|
92
|
+
passed,
|
|
93
|
+
status: passed ? "PASS" : "BLOCKED",
|
|
94
|
+
reason: violations.length > 0 ? `Role violations: ${violations.join("; ")}` : "Verification passed",
|
|
95
|
+
violations
|
|
96
|
+
};
|
|
97
|
+
}
|
|
53
98
|
}
|
|
@@ -32,16 +32,65 @@ export class Finalizer {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async verifyFidelity(
|
|
35
|
+
async verifyFidelity(arg) {
|
|
36
|
+
let userRequest = null;
|
|
37
|
+
let projectContext = null;
|
|
38
|
+
let deliveryDecision = null;
|
|
39
|
+
let changedFiles = null;
|
|
40
|
+
let evidence = null;
|
|
41
|
+
let explicitApprovals = [];
|
|
42
|
+
|
|
43
|
+
if (Array.isArray(arg)) {
|
|
44
|
+
changedFiles = arg;
|
|
45
|
+
} else if (arg && typeof arg === "object") {
|
|
46
|
+
userRequest = arg.userRequest;
|
|
47
|
+
projectContext = arg.projectContext;
|
|
48
|
+
deliveryDecision = arg.deliveryDecision;
|
|
49
|
+
changedFiles = arg.changedFiles;
|
|
50
|
+
evidence = arg.evidence;
|
|
51
|
+
explicitApprovals = arg.explicitApprovals || [];
|
|
52
|
+
}
|
|
53
|
+
|
|
36
54
|
const filesToCheck = changedFiles || this.getChangedFilesFromGit();
|
|
37
55
|
const decisionEngine = new DeliveryDecisionEngine({ cwd: this.cwd });
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
56
|
+
const actualProjectContext = projectContext || await decisionEngine.detectContext();
|
|
57
|
+
const actualUserRequest = userRequest || "";
|
|
58
|
+
|
|
59
|
+
const actualDeliveryDecision = deliveryDecision || await decisionEngine.determineDecision({
|
|
60
|
+
userRequest: actualUserRequest,
|
|
61
|
+
projectContext: actualProjectContext,
|
|
62
|
+
explicitApprovals
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const actualDeliveryClass = await decisionEngine.determineClass(filesToCheck);
|
|
66
|
+
|
|
67
|
+
const fidelityGate = new ArtifactFidelityGate({
|
|
68
|
+
cwd: this.cwd,
|
|
69
|
+
deliveryClass: actualDeliveryClass,
|
|
70
|
+
userRequest: actualUserRequest,
|
|
71
|
+
explicitApprovals
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const requestFidelity = await fidelityGate.verifyRequestFidelity({
|
|
75
|
+
userRequest: actualUserRequest,
|
|
76
|
+
deliveryDecision: actualDeliveryDecision,
|
|
77
|
+
projectContext: actualProjectContext,
|
|
78
|
+
changedFiles: filesToCheck,
|
|
79
|
+
evidence,
|
|
80
|
+
explicitApprovals
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const verifyResult = await fidelityGate.verify(filesToCheck);
|
|
84
|
+
|
|
85
|
+
const passed = requestFidelity.passed && verifyResult.passed;
|
|
86
|
+
const reason = !requestFidelity.passed ? requestFidelity.reason : verifyResult.reason;
|
|
87
|
+
|
|
41
88
|
return {
|
|
42
|
-
passed
|
|
43
|
-
|
|
44
|
-
|
|
89
|
+
passed,
|
|
90
|
+
status: requestFidelity.status || (passed ? "PASS" : "BLOCKED"),
|
|
91
|
+
reason,
|
|
92
|
+
deliveryClass: actualDeliveryClass,
|
|
93
|
+
requestFidelity
|
|
45
94
|
};
|
|
46
95
|
}
|
|
47
96
|
|