@williambeto/ai-workflow 2.8.0 → 2.8.1
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/CHANGELOG.md +41 -0
- package/bin/ai-workflow.js +193 -49
- package/bin/ai-workflow.js.map +1 -1
- package/{chunk-LI76KI7C.js → chunk-4FI5ODAM.js} +259 -296
- package/{chunk-XW747GIG.js → chunk-H7GIKXFO.js} +339 -14
- package/{chunk-Y4RLP6ZM.js → chunk-LD7EHAU2.js} +343 -38
- package/core/index.d.ts +20 -1
- package/core/index.js +2 -2
- package/dist-assets/agents/atlas.md +25 -6
- package/dist-assets/commands/atlas.md +7 -2
- package/dist-assets/docs/QUICKSTART.md +10 -1
- package/dist-assets/docs/compatibility/provider-usage.md +16 -1
- package/dist-assets/docs/compatibility/runtime-matrix.md +13 -2
- package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +11 -1
- package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +16 -0
- package/dist-assets/schemas/README.md +4 -0
- package/dist-assets/schemas/evidence.schema.json +60 -1
- package/package.json +1 -1
- package/{validate-IEHLAVZ6.js → validate-F3ZH63LI.js} +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
## [unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.8.1] - 2026-07-14
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Route natural workspace mutations to Astra instead of stopping after a
|
|
8
|
+
specialist prints code
|
|
9
|
+
- Keep Phoenix limited to bounded remediation of observed findings
|
|
10
|
+
- Require execution-scoped repository changes and proportional validation for
|
|
11
|
+
successful workspace delivery
|
|
12
|
+
- Preserve explicit read-only, preview, already-satisfied, clarification, and
|
|
13
|
+
non-delivery outcomes without manufacturing changes
|
|
14
|
+
- Prevent natural phrases such as `next step` from being interpreted as a
|
|
15
|
+
Next.js stack request
|
|
16
|
+
- Propagate observed OpenCode commands to the evidence ledger and derive test
|
|
17
|
+
actions from the delivered diff
|
|
18
|
+
- Prevent duplicate finalization and require exactly one observed Astra
|
|
19
|
+
delegation in the OpenCode fallback path
|
|
20
|
+
- Route mixed inspection-and-change requests to Astra whenever the request has
|
|
21
|
+
write intent
|
|
22
|
+
- Allow read-only Git, `sed`, and `awk` inspection while continuing to block
|
|
23
|
+
mutations, and exclude ignored build artifacts from delivery deltas
|
|
24
|
+
- Keep validation proportional for documentation, simple code, backend scripts,
|
|
25
|
+
and React applications while retaining blocking build requirements for UI
|
|
26
|
+
|
|
27
|
+
### 🧪 Testing
|
|
28
|
+
|
|
29
|
+
- Cover routing ownership, delivery outcomes, answer-only confinement, project
|
|
30
|
+
context compatibility, execution delta attribution, and generated OpenCode
|
|
31
|
+
permissions through reusable invariants
|
|
32
|
+
- Keep pre-existing dirty work from satisfying delivery or test-action evidence
|
|
33
|
+
- Validate the corrected workflow with a real GPT-5.6 Sol OpenCode delivery and
|
|
34
|
+
record GPT-5.3 Codex Spark medium as experimental after observed incomplete
|
|
35
|
+
delivery was correctly blocked
|
|
36
|
+
|
|
37
|
+
### 📚 Documentation
|
|
38
|
+
|
|
39
|
+
- Align Atlas, quickstart, ownership, runtime-flow, evidence, and skill
|
|
40
|
+
governance documentation with the natural-request delivery contract
|
|
41
|
+
- Document the distinction between CLI-enforced gates and model-qualified
|
|
42
|
+
direct Atlas sessions
|
|
43
|
+
|
|
3
44
|
## [2.8.0] - 2026-07-14
|
|
4
45
|
|
|
5
46
|
### 🚀 Features
|
package/bin/ai-workflow.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
isTerminalFailure,
|
|
22
22
|
readPackageFile,
|
|
23
23
|
resolveWorkflowProfile
|
|
24
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-4FI5ODAM.js";
|
|
25
25
|
import {
|
|
26
26
|
EvidenceValidator,
|
|
27
27
|
require_ajv,
|
|
@@ -30,9 +30,12 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
EvidenceCollector,
|
|
32
32
|
QualityGuard,
|
|
33
|
-
ValidationPlanner
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
ValidationPlanner,
|
|
34
|
+
parseDeliveryOutcomeClaim
|
|
35
|
+
} from "../chunk-LD7EHAU2.js";
|
|
36
|
+
import {
|
|
37
|
+
DeliveryDecisionEngine
|
|
38
|
+
} from "../chunk-H7GIKXFO.js";
|
|
36
39
|
import {
|
|
37
40
|
createManagedBackup,
|
|
38
41
|
createManagedPathBackup
|
|
@@ -726,34 +729,34 @@ async function readJsonc(filePath) {
|
|
|
726
729
|
import path3 from "path";
|
|
727
730
|
var LEGACY_MARKER = "_aiWorkflowManaged";
|
|
728
731
|
var AGENTS = {
|
|
729
|
-
Atlas: { description: "Router and workflow coordinator for AI Workflow Kit" },
|
|
730
|
-
Nexus: { description: "Discovery, requirement, scope, and specification owner" },
|
|
731
|
-
Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner" },
|
|
732
|
-
Astra: { description: "Implementation owner for incremental, scoped changes" },
|
|
733
|
-
Sage: { description: "Audit, validation, evidence, and quality gate owner" },
|
|
734
|
-
Phoenix: { description: "
|
|
732
|
+
Atlas: { description: "Router and workflow coordinator for AI Workflow Kit", mode: "primary" },
|
|
733
|
+
Nexus: { description: "Discovery, requirement, scope, and specification owner", mode: "all" },
|
|
734
|
+
Orion: { description: "Planning, PR sequencing, release, and deployment strategy owner", mode: "all" },
|
|
735
|
+
Astra: { description: "Implementation owner for incremental, scoped workspace changes", mode: "all" },
|
|
736
|
+
Sage: { description: "Audit, validation, evidence, and quality gate owner", mode: "all" },
|
|
737
|
+
Phoenix: { description: "Bounded remediation and regression recovery owner", mode: "all" }
|
|
735
738
|
};
|
|
736
739
|
var SUBAGENTS = {
|
|
737
|
-
"Architecture-Specialist": { description: "
|
|
738
|
-
"Backend-Engineer": { description: "
|
|
739
|
-
"Deployment-Specialist": { description: "
|
|
740
|
-
"Design-Specialist": { description: "
|
|
741
|
-
"Docs-Engineer": { description: "
|
|
742
|
-
"Frontend-Engineer": { description: "
|
|
743
|
-
"Frontend-Design-System-Specialist": { description: "
|
|
744
|
-
"Full-Stack-Engineer": { description: "
|
|
745
|
-
"Token-Economist": { description: "
|
|
746
|
-
"PR-Manager": { description: "
|
|
747
|
-
"Discovery-Analyst": { description: "
|
|
748
|
-
"Product-Planner": { description: "
|
|
749
|
-
"Memory-Guardian": { description: "
|
|
750
|
-
"Prompt-Engineer": { description: "
|
|
751
|
-
"QA-Engineer": { description: "
|
|
752
|
-
"Refactoring-Specialist": { description: "
|
|
753
|
-
"Release-Specialist": { description: "
|
|
754
|
-
"SDD-Specialist": { description: "
|
|
755
|
-
"Technical-Leader": { description: "
|
|
756
|
-
"UI-UX-Engineer": { description: "
|
|
740
|
+
"Architecture-Specialist": { description: "Capability only: advise on the simplest safe architecture for current requirements", skill: "architecture" },
|
|
741
|
+
"Backend-Engineer": { description: "Capability only: advise on PHP, Node, or Python APIs and persistence", skill: "backend-development" },
|
|
742
|
+
"Deployment-Specialist": { description: "Capability only: analyze deployment, release, and production readiness", skill: "deployment" },
|
|
743
|
+
"Design-Specialist": { description: "Capability only: advise on simple and maintainable design principles", skill: "design-principles" },
|
|
744
|
+
"Docs-Engineer": { description: "Capability only: advise on documentation, READMEs, guides, and prompts", skill: "documentation" },
|
|
745
|
+
"Frontend-Engineer": { description: "Capability only: advise on UI, state, routing, and accessibility", skill: "frontend-development" },
|
|
746
|
+
"Frontend-Design-System-Specialist": { description: "Capability only: review CSS and HTML tokens, semantics, typography, and visual quality", skill: "frontend-design-system" },
|
|
747
|
+
"Full-Stack-Engineer": { description: "Capability only: analyze frontend and backend integration as one testable increment", skill: "full-stack-development" },
|
|
748
|
+
"Token-Economist": { description: "Capability only: analyze context size while preserving safe execution information", skill: "optimize-tokens" },
|
|
749
|
+
"PR-Manager": { description: "Capability only: plan or review small pull requests while preserving scope", skill: "pr-workflow" },
|
|
750
|
+
"Discovery-Analyst": { description: "Capability only: clarify ambiguous requests into actionable outcomes", skill: "product-discovery" },
|
|
751
|
+
"Product-Planner": { description: "Capability only: shape clarified needs into scoped requirements", skill: "product-planning" },
|
|
752
|
+
"Memory-Guardian": { description: "Capability only: advise on durable project decisions and recurring corrections", skill: "project-memory" },
|
|
753
|
+
"Prompt-Engineer": { description: "Capability only: advise on prompts for agents and workflows", skill: "prompt-engineer" },
|
|
754
|
+
"QA-Engineer": { description: "Capability only: design or review tests, acceptance criteria, and validation evidence", skill: "qa-workflow" },
|
|
755
|
+
"Refactoring-Specialist": { description: "Capability only: review structure and behavior-preserving simplifications", skill: "refactoring" },
|
|
756
|
+
"Release-Specialist": { description: "Capability only: analyze release readiness with explicit gates and evidence", skill: "release-workflow" },
|
|
757
|
+
"SDD-Specialist": { description: "Capability only: shape approved requirements into testable specifications", skill: "spec-driven-development" },
|
|
758
|
+
"Technical-Leader": { description: "Capability only: analyze technical decisions, architecture, and trade-offs", skill: "technical-leadership" },
|
|
759
|
+
"UI-UX-Engineer": { description: "Capability only: review usability and interface clarity", skill: "ui-ux-design" }
|
|
757
760
|
};
|
|
758
761
|
var COMMANDS = {
|
|
759
762
|
atlas: { description: "Decide safest next step and delegate to the appropriate workflow or agent", agent: "Atlas" },
|
|
@@ -773,18 +776,36 @@ var COMMANDS = {
|
|
|
773
776
|
};
|
|
774
777
|
function buildManagedConfig() {
|
|
775
778
|
const agent = {};
|
|
779
|
+
const coreTaskPermissions = Object.fromEntries([
|
|
780
|
+
["*", "deny"],
|
|
781
|
+
...Object.keys(AGENTS).filter((name) => name !== "Atlas").map((name) => [name, "allow"])
|
|
782
|
+
]);
|
|
783
|
+
const capabilityTaskPermissions = Object.fromEntries([
|
|
784
|
+
["*", "deny"],
|
|
785
|
+
...Object.keys(SUBAGENTS).map((name) => [name, "allow"])
|
|
786
|
+
]);
|
|
776
787
|
for (const [name, def] of Object.entries(AGENTS)) {
|
|
777
788
|
agent[name] = {
|
|
778
|
-
mode:
|
|
789
|
+
mode: def.mode,
|
|
779
790
|
description: def.description,
|
|
780
|
-
prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}
|
|
791
|
+
prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`,
|
|
792
|
+
permission: {
|
|
793
|
+
...name === "Atlas" ? { edit: "deny", bash: "ask" } : {},
|
|
794
|
+
...name === "Astra" ? { edit: "allow" } : {},
|
|
795
|
+
task: name === "Atlas" ? coreTaskPermissions : capabilityTaskPermissions
|
|
796
|
+
}
|
|
781
797
|
};
|
|
782
798
|
}
|
|
783
799
|
for (const [name, def] of Object.entries(SUBAGENTS)) {
|
|
784
800
|
agent[name] = {
|
|
785
801
|
mode: "subagent",
|
|
786
802
|
description: def.description,
|
|
787
|
-
prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}
|
|
803
|
+
prompt: `{file:./.ai-workflow/opencode/skills/${def.skill}/SKILL.md}`,
|
|
804
|
+
permission: {
|
|
805
|
+
edit: "deny",
|
|
806
|
+
bash: "ask",
|
|
807
|
+
task: { "*": "deny" }
|
|
808
|
+
}
|
|
788
809
|
};
|
|
789
810
|
}
|
|
790
811
|
const command = {};
|
|
@@ -2079,7 +2100,7 @@ function buildDeliverySummary({ evidence }) {
|
|
|
2079
2100
|
return {
|
|
2080
2101
|
Status: evidence?.status || "BLOCKED",
|
|
2081
2102
|
Branch: evidence?.branch || "unknown",
|
|
2082
|
-
Changes: (evidence?.changedFiles || []).join(", ") || "No changed files recorded",
|
|
2103
|
+
Changes: (evidence?.deliveryChangedFiles || evidence?.changedFiles || []).join(", ") || "No changed files recorded",
|
|
2083
2104
|
Validation: commands,
|
|
2084
2105
|
"Known limitations": (evidence?.limitations || []).join("; ") || "None recorded"
|
|
2085
2106
|
};
|
|
@@ -2114,12 +2135,40 @@ async function runCollectEvidence({
|
|
|
2114
2135
|
explicitApprovals = [],
|
|
2115
2136
|
visualDist = null,
|
|
2116
2137
|
port = null,
|
|
2117
|
-
workflowEvidence = null
|
|
2138
|
+
workflowEvidence = null,
|
|
2139
|
+
claimedOutcome = null,
|
|
2140
|
+
deliveryChangedFiles = null,
|
|
2141
|
+
deliveryAddedFiles = null,
|
|
2142
|
+
deliveryDeletedFiles = null
|
|
2118
2143
|
}) {
|
|
2144
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
2145
|
+
console.log("[FINALIZATION DEFERRED] Parent ai-workflow execute process owns canonical finalization.");
|
|
2146
|
+
return { status: "DEFERRED_TO_ORCHESTRATOR", internalStatus: "DEFERRED", commands: [] };
|
|
2147
|
+
}
|
|
2119
2148
|
const policies = resolvePoliciesFromLegacyMode(mode, evidencePolicy);
|
|
2120
|
-
const
|
|
2149
|
+
const workspaceGuard = new QualityGuard({ cwd, taskSlug, evidencePolicy: policies.evidencePolicy, riskLevel: policies.riskLevel });
|
|
2150
|
+
const observedDeliveryFiles = deliveryChangedFiles ?? workspaceGuard.getChangedFiles();
|
|
2151
|
+
const qualityGuard = new QualityGuard({
|
|
2152
|
+
cwd,
|
|
2153
|
+
taskSlug,
|
|
2154
|
+
evidencePolicy: policies.evidencePolicy,
|
|
2155
|
+
riskLevel: policies.riskLevel,
|
|
2156
|
+
scopedChangedFiles: observedDeliveryFiles
|
|
2157
|
+
});
|
|
2121
2158
|
const planner = new ValidationPlanner({ cwd, qualityGuard });
|
|
2122
|
-
const tasks = await planner.plan(profile
|
|
2159
|
+
const tasks = await planner.plan(profile, {
|
|
2160
|
+
requireObservedValidation: claimedOutcome === "ALREADY_SATISFIED" && observedDeliveryFiles.length === 0
|
|
2161
|
+
});
|
|
2162
|
+
const docs = observedDeliveryFiles.filter((file) => /(^|\/)(docs?|readme|changelog)|\.md$/i.test(file));
|
|
2163
|
+
if (tasks.length === 0 && docs.length > 0 && docs.length === observedDeliveryFiles.length) {
|
|
2164
|
+
tasks.push({
|
|
2165
|
+
name: "docs-integrity",
|
|
2166
|
+
command: ["aiwk", "internal", "docs-integrity"],
|
|
2167
|
+
kind: "lint",
|
|
2168
|
+
internal: "document-integrity",
|
|
2169
|
+
files: docs
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2123
2172
|
const persist = !dryRun && policies.evidencePolicy === "required";
|
|
2124
2173
|
const collector = new EvidenceCollector({
|
|
2125
2174
|
cwd,
|
|
@@ -2132,7 +2181,12 @@ async function runCollectEvidence({
|
|
|
2132
2181
|
explicitApprovals,
|
|
2133
2182
|
visualDist,
|
|
2134
2183
|
port: port || 8080,
|
|
2135
|
-
workflowEvidence
|
|
2184
|
+
workflowEvidence,
|
|
2185
|
+
claimedOutcome,
|
|
2186
|
+
deliveryChangedFiles: observedDeliveryFiles,
|
|
2187
|
+
deliveryAddedFiles,
|
|
2188
|
+
deliveryDeletedFiles,
|
|
2189
|
+
riskLevel: policies.riskLevel
|
|
2136
2190
|
});
|
|
2137
2191
|
const evidence = await collector.collect(tasks, { writeArtifact: persist });
|
|
2138
2192
|
const summary = buildDeliverySummary({ evidence });
|
|
@@ -2592,6 +2646,9 @@ async function runDeepImplementation(cwd, absoluteSpecPath, specPath, approvalPa
|
|
|
2592
2646
|
};
|
|
2593
2647
|
}
|
|
2594
2648
|
async function runMasterOrchestrator({ cwd, specPath, approvalPath, override, remediationExecutor = null }) {
|
|
2649
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
2650
|
+
throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
|
|
2651
|
+
}
|
|
2595
2652
|
console.log("\n[AI WORKFLOW] Starting evidence-backed orchestration...\n");
|
|
2596
2653
|
const branchGate = new BranchGate({ memoryDir: path14.join(cwd, ".ai-workflow"), cwd });
|
|
2597
2654
|
const taskSlug = specPath ? normalizeTaskSlugFromSpec(specPath) : "implementation";
|
|
@@ -2801,6 +2858,19 @@ Please inspect the code, identify the root cause of these failures (e.g. failing
|
|
|
2801
2858
|
import fs14 from "fs/promises";
|
|
2802
2859
|
import path16 from "path";
|
|
2803
2860
|
import { execSync as execSync4 } from "child_process";
|
|
2861
|
+
var INTEGRITY_METADATA_CHANGES = /* @__PURE__ */ new Set(["stagedDiff", "unstagedDiff", "untrackedFiles"]);
|
|
2862
|
+
function extractDeliveryChangedFiles(integrity) {
|
|
2863
|
+
const files = /* @__PURE__ */ new Set();
|
|
2864
|
+
for (const file of integrity.changes.added) files.add(file);
|
|
2865
|
+
for (const file of integrity.changes.deleted) files.add(file);
|
|
2866
|
+
for (const change of integrity.changes.modified) {
|
|
2867
|
+
if (change.startsWith("branch:") || change.startsWith("head:")) continue;
|
|
2868
|
+
if (INTEGRITY_METADATA_CHANGES.has(change)) continue;
|
|
2869
|
+
if (change.startsWith("indexTree:")) continue;
|
|
2870
|
+
files.add(change);
|
|
2871
|
+
}
|
|
2872
|
+
return [...files].sort();
|
|
2873
|
+
}
|
|
2804
2874
|
async function captureReadOnlyState(cwd, fastTrack = false) {
|
|
2805
2875
|
const finalizer = new Finalizer({ cwd, fastTrack });
|
|
2806
2876
|
const snapshot = await finalizer.snapshotManager.capture();
|
|
@@ -2888,6 +2958,13 @@ async function classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegatio
|
|
|
2888
2958
|
const plan = planner.plan(classification, taskSlug);
|
|
2889
2959
|
console.log(`[PLANNED] Owner: ${plan.owner}, Remediation limit: ${plan.remediationLimit}`);
|
|
2890
2960
|
stateMachine.transitionTo("PLANNED");
|
|
2961
|
+
if (classification.requestUnderstanding.deliveryMode === "workspace") {
|
|
2962
|
+
const deliveryDecision = await new DeliveryDecisionEngine({ cwd }).determineDecision({ userRequest: naturalRequest });
|
|
2963
|
+
if (deliveryDecision.decision.startsWith("BLOCK_")) {
|
|
2964
|
+
stateMachine.transitionTo("BLOCKED");
|
|
2965
|
+
throw new Error(`[CONTEXT BLOCKED] NEEDS_CLARIFICATION: ${deliveryDecision.reason || deliveryDecision.decision}`);
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2891
2968
|
return { plan, classification };
|
|
2892
2969
|
}
|
|
2893
2970
|
function runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate) {
|
|
@@ -3026,7 +3103,21 @@ async function runImplementation(plan, classification, delegationController, sta
|
|
|
3026
3103
|
if (plan.specPath) {
|
|
3027
3104
|
promptMsg = `Please review and fill in the specification file at: ${plan.specPath}. Make sure to change the Status field to 'APPROVED' in the Metadata section, and then implement the behavior described.`;
|
|
3028
3105
|
}
|
|
3029
|
-
|
|
3106
|
+
if (plan.branchNeeded) {
|
|
3107
|
+
promptMsg = [
|
|
3108
|
+
promptMsg,
|
|
3109
|
+
"",
|
|
3110
|
+
"AIWK delivery contract:",
|
|
3111
|
+
"- Work in the active repository; printed code is not a substitute for workspace delivery.",
|
|
3112
|
+
"- Keep the diff scoped and run proportional validation.",
|
|
3113
|
+
"- The parent already satisfied the branch gate. Do not repeat branch/status checks unless a new safety concern makes them necessary.",
|
|
3114
|
+
"- Documentation, screenshots, Sage review, Phoenix remediation, and persisted evidence are conditional; do not create them unless the request, risk policy, or an observed finding requires them.",
|
|
3115
|
+
"- Do not run collect-evidence, create a handoff, or finalize this workflow; the parent execute process is the sole finalization owner.",
|
|
3116
|
+
"- End with exactly one machine-readable line: AIWK_DELIVERY_OUTCOME: CHANGED or AIWK_DELIVERY_OUTCOME: ALREADY_SATISFIED.",
|
|
3117
|
+
"- Use ALREADY_SATISFIED only when inspection and relevant validation prove that no repository change is needed."
|
|
3118
|
+
].join("\n");
|
|
3119
|
+
}
|
|
3120
|
+
const runResult = await delegationController.implement(promptMsg, plan.owner, { readOnly: !plan.branchNeeded, fastTrack, orchestratedChild: true });
|
|
3030
3121
|
if (!runResult.success) {
|
|
3031
3122
|
console.error(`[EXECUTE] OpenCode adapter execution reported failure: ${runResult.error || "Unknown error"}`);
|
|
3032
3123
|
stateMachine.transitionTo("BLOCKED");
|
|
@@ -3047,9 +3138,13 @@ async function runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachin
|
|
|
3047
3138
|
${violations.join("\n")}`);
|
|
3048
3139
|
}
|
|
3049
3140
|
}
|
|
3050
|
-
async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, delegationController, stateMachine) {
|
|
3141
|
+
async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, claimedOutcome, deliveryBaseline, deliveryFinalizer, delegationController, stateMachine) {
|
|
3051
3142
|
stateMachine.transitionTo("VALIDATING");
|
|
3052
3143
|
const validateWorkflow = async () => {
|
|
3144
|
+
const deliveryIntegrity = await deliveryFinalizer.verifyIntegrity(deliveryBaseline);
|
|
3145
|
+
const deliveryChangedFiles = extractDeliveryChangedFiles(deliveryIntegrity);
|
|
3146
|
+
const deliveryAddedFiles = deliveryIntegrity.changes.added.filter((file) => deliveryChangedFiles.includes(file));
|
|
3147
|
+
const deliveryDeletedFiles = deliveryIntegrity.changes.deleted.filter((file) => deliveryChangedFiles.includes(file));
|
|
3053
3148
|
const evidence = await runCollectEvidence({
|
|
3054
3149
|
cwd,
|
|
3055
3150
|
exitOnError: false,
|
|
@@ -3058,7 +3153,11 @@ async function runValidation(plan, taskSlug, cwd, gateResult, naturalRequest, de
|
|
|
3058
3153
|
profile: plan.profile,
|
|
3059
3154
|
branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED",
|
|
3060
3155
|
userRequest: naturalRequest,
|
|
3061
|
-
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
|
|
3156
|
+
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : [],
|
|
3157
|
+
claimedOutcome,
|
|
3158
|
+
deliveryChangedFiles,
|
|
3159
|
+
deliveryAddedFiles,
|
|
3160
|
+
deliveryDeletedFiles
|
|
3062
3161
|
});
|
|
3063
3162
|
const quality = await new QualityGuard({
|
|
3064
3163
|
cwd,
|
|
@@ -3161,7 +3260,7 @@ async function runFidelityValidation(naturalRequest, result, finalizer) {
|
|
|
3161
3260
|
userRequest: naturalRequest,
|
|
3162
3261
|
projectContext: result.evidence?.deliveryDecision?.projectContext || null,
|
|
3163
3262
|
deliveryDecision: result.evidence?.deliveryDecision || null,
|
|
3164
|
-
changedFiles: result.evidence?.changedFiles || [],
|
|
3263
|
+
changedFiles: result.evidence?.deliveryChangedFiles || result.evidence?.changedFiles || [],
|
|
3165
3264
|
evidence: result.evidence || null,
|
|
3166
3265
|
explicitApprovals: process.env.AI_WORKFLOW_APPROVALS ? process.env.AI_WORKFLOW_APPROVALS.split(",").map((a) => a.trim()) : []
|
|
3167
3266
|
});
|
|
@@ -3178,7 +3277,7 @@ function computeFinalState(finalStatus) {
|
|
|
3178
3277
|
if (finalStatus === "PASS_WITH_NOTES") return "COMPLETED_WITH_NOTES";
|
|
3179
3278
|
return "BLOCKED";
|
|
3180
3279
|
}
|
|
3181
|
-
async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack) {
|
|
3280
|
+
async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, deliveryOutcome) {
|
|
3182
3281
|
await runReadOnlyConfinementCheck(readOnlyStateBefore, cwd, stateMachine, fastTrack);
|
|
3183
3282
|
stateMachine.transitionTo("IMPLEMENTED");
|
|
3184
3283
|
stateMachine.transitionTo("VALIDATING");
|
|
@@ -3192,7 +3291,11 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
|
|
|
3192
3291
|
overallStatus: "PASS",
|
|
3193
3292
|
evidence: {
|
|
3194
3293
|
internalStatus: "PASS",
|
|
3195
|
-
commandsRun: []
|
|
3294
|
+
commandsRun: [],
|
|
3295
|
+
deliveryMode: "answer-only",
|
|
3296
|
+
deliveryOutcome,
|
|
3297
|
+
testAction: "none",
|
|
3298
|
+
testReason: "Read-only work does not change executable behavior."
|
|
3196
3299
|
},
|
|
3197
3300
|
quality: {
|
|
3198
3301
|
overallStatus: "PASS"
|
|
@@ -3201,6 +3304,9 @@ async function executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, f
|
|
|
3201
3304
|
};
|
|
3202
3305
|
}
|
|
3203
3306
|
async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
3307
|
+
if (process.env.AI_WORKFLOW_FINALIZATION_OWNER === "execute") {
|
|
3308
|
+
throw new Error("[NESTED EXECUTION BLOCKED] The parent execute process owns orchestration and finalization.");
|
|
3309
|
+
}
|
|
3204
3310
|
if (!naturalRequest || !naturalRequest.trim()) {
|
|
3205
3311
|
throw new Error("Missing request. Please provide a natural request string via positional arguments or --request flag.");
|
|
3206
3312
|
}
|
|
@@ -3211,6 +3317,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
3211
3317
|
const ledger = new EvidenceLedger({ cwd, workflowId: taskSlug });
|
|
3212
3318
|
const delegationController = new DelegationController({ cwd, ledger, adapter: new OpenCodeAdapter({ cwd }) });
|
|
3213
3319
|
let plan = null;
|
|
3320
|
+
let branchAuthorized = false;
|
|
3214
3321
|
try {
|
|
3215
3322
|
const stateMachine = new WorkflowStateMachine();
|
|
3216
3323
|
const { plan: planObj, classification } = await classifyAndPlanExecution(naturalRequest, cwd, taskSlug, delegationController, stateMachine);
|
|
@@ -3218,6 +3325,7 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
3218
3325
|
const fastTrack = plan.riskLevel === "low" && (!plan.branchNeeded || plan.evidencePolicy === "optional");
|
|
3219
3326
|
const branchGate = new BranchGate({ memoryDir: path16.join(cwd, ".ai-workflow"), cwd });
|
|
3220
3327
|
const gateResult = runBranchGate(plan, taskSlug, cwd, stateMachine, branchGate);
|
|
3328
|
+
branchAuthorized = true;
|
|
3221
3329
|
const checkBranchSafety = () => {
|
|
3222
3330
|
if (!planObj.branchNeeded) return;
|
|
3223
3331
|
const currentBranch = branchGate.getCurrentBranch();
|
|
@@ -3231,18 +3339,38 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
3231
3339
|
}
|
|
3232
3340
|
await createSpecIfRequired(plan, classification, taskSlug, cwd);
|
|
3233
3341
|
let readOnlyStateBefore = null;
|
|
3342
|
+
let deliveryBaseline = null;
|
|
3343
|
+
const deliveryFinalizer = new Finalizer({ cwd, fastTrack: false });
|
|
3234
3344
|
if (!plan.branchNeeded) {
|
|
3235
3345
|
readOnlyStateBefore = await captureReadOnlyState(cwd, fastTrack);
|
|
3346
|
+
} else {
|
|
3347
|
+
deliveryBaseline = await deliveryFinalizer.snapshotManager.capture();
|
|
3236
3348
|
}
|
|
3237
|
-
await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
|
|
3349
|
+
const implementationResult = await runImplementation(plan, classification, delegationController, stateMachine, fastTrack);
|
|
3350
|
+
const claimedOutcome = parseDeliveryOutcomeClaim(implementationResult.output || "");
|
|
3238
3351
|
if (!plan.branchNeeded && readOnlyStateBefore) {
|
|
3239
|
-
|
|
3352
|
+
const readOnlyOutcome = classification.requestUnderstanding.mutationIntent === "readonly" ? "READ_ONLY_RESULT" : "PREVIEW_ONLY";
|
|
3353
|
+
return await executeReadOnlyWorkflow(cwd, stateMachine, readOnlyStateBefore, fastTrack, readOnlyOutcome);
|
|
3354
|
+
}
|
|
3355
|
+
if (!deliveryBaseline) {
|
|
3356
|
+
throw new Error("[WORKFLOW BLOCKED] Missing delivery baseline for a write workflow.");
|
|
3240
3357
|
}
|
|
3241
3358
|
stateMachine.transitionTo("IMPLEMENTED");
|
|
3242
3359
|
checkBranchSafety();
|
|
3243
3360
|
const finalizer = new Finalizer({ cwd, fastTrack });
|
|
3244
3361
|
const snapshotRef = { current: await finalizer.snapshotManager.capture() };
|
|
3245
|
-
const { result: valResult, validateWorkflow } = await runValidation(
|
|
3362
|
+
const { result: valResult, validateWorkflow } = await runValidation(
|
|
3363
|
+
plan,
|
|
3364
|
+
taskSlug,
|
|
3365
|
+
cwd,
|
|
3366
|
+
gateResult,
|
|
3367
|
+
naturalRequest,
|
|
3368
|
+
claimedOutcome,
|
|
3369
|
+
deliveryBaseline,
|
|
3370
|
+
deliveryFinalizer,
|
|
3371
|
+
delegationController,
|
|
3372
|
+
stateMachine
|
|
3373
|
+
);
|
|
3246
3374
|
let result = valResult;
|
|
3247
3375
|
result = await runBoundedRemediation(
|
|
3248
3376
|
plan,
|
|
@@ -3292,10 +3420,26 @@ async function runExecute({ cwd, naturalRequest, taskSlug: taskSlugOverride }) {
|
|
|
3292
3420
|
}
|
|
3293
3421
|
const finalState = computeFinalState(finalStatus);
|
|
3294
3422
|
stateMachine.transitionTo(finalState);
|
|
3423
|
+
ledger.logEvent({
|
|
3424
|
+
actor: "Atlas",
|
|
3425
|
+
actorType: "control-plane",
|
|
3426
|
+
observed: true,
|
|
3427
|
+
eventType: "finalization_started",
|
|
3428
|
+
provenance: "execute",
|
|
3429
|
+
data: { owner: "execute", status: finalState }
|
|
3430
|
+
});
|
|
3295
3431
|
await generateFinalHandoff(plan, taskSlug, cwd, finalState, result.evidence);
|
|
3432
|
+
ledger.logEvent({
|
|
3433
|
+
actor: "Atlas",
|
|
3434
|
+
actorType: "control-plane",
|
|
3435
|
+
observed: true,
|
|
3436
|
+
eventType: "finalization_completed",
|
|
3437
|
+
provenance: "execute",
|
|
3438
|
+
data: { owner: "execute", status: finalState }
|
|
3439
|
+
});
|
|
3296
3440
|
return { ...result, overallStatus: finalStatus, stateHistory: stateMachine.getHistory() };
|
|
3297
3441
|
} finally {
|
|
3298
|
-
if (plan && !plan.branchNeeded) {
|
|
3442
|
+
if (!branchAuthorized || plan && !plan.branchNeeded) {
|
|
3299
3443
|
console.log(`
|
|
3300
3444
|
[READ-ONLY LEDGER] (In-Memory/Stdout)
|
|
3301
3445
|
${JSON.stringify(ledger.getEvents(), null, 2)}
|
|
@@ -3499,7 +3643,7 @@ var commandMap = {
|
|
|
3499
3643
|
});
|
|
3500
3644
|
},
|
|
3501
3645
|
validate: async (_, flags) => {
|
|
3502
|
-
const { runValidate } = await import("../validate-
|
|
3646
|
+
const { runValidate } = await import("../validate-F3ZH63LI.js");
|
|
3503
3647
|
await runValidate({
|
|
3504
3648
|
cwd: process.cwd(),
|
|
3505
3649
|
taskSlug: flags.taskSlug,
|