@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,193 +0,0 @@
|
|
|
1
|
-
import { OpenCodeAdapter } from "./runtime/opencode-adapter.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* DelegationController - Manages and logs coordination between Atlas, Astra, Sage, and Phoenix.
|
|
5
|
-
*/
|
|
6
|
-
export class DelegationController {
|
|
7
|
-
constructor({ cwd = process.cwd(), ledger, adapter } = {}) {
|
|
8
|
-
this.cwd = cwd;
|
|
9
|
-
this.ledger = ledger;
|
|
10
|
-
this.adapter = adapter || new OpenCodeAdapter({ cwd });
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Logs routing decision by Atlas.
|
|
15
|
-
*/
|
|
16
|
-
async route(naturalRequest, classification) {
|
|
17
|
-
const decision = classification.routingDecision || {};
|
|
18
|
-
this.lastRoutingDecision = decision;
|
|
19
|
-
|
|
20
|
-
if (this.ledger) {
|
|
21
|
-
this.ledger.logEvent({
|
|
22
|
-
actor: "Atlas",
|
|
23
|
-
actorType: "control-plane",
|
|
24
|
-
observed: true,
|
|
25
|
-
eventType: "routing",
|
|
26
|
-
provenance: "request-classifier",
|
|
27
|
-
data: {
|
|
28
|
-
request: naturalRequest,
|
|
29
|
-
requestedActor: decision.requestedActor || null,
|
|
30
|
-
selectedActor: decision.selectedActor || null,
|
|
31
|
-
operationType: decision.operationType || null,
|
|
32
|
-
mutationIntent: decision.mutationIntent || null,
|
|
33
|
-
permissionDecision: decision.permissionDecision || null,
|
|
34
|
-
reason: decision.reason || null,
|
|
35
|
-
workflowPath: decision.workflowPath || [],
|
|
36
|
-
classification,
|
|
37
|
-
event: "routing.completed",
|
|
38
|
-
runtimeAgentRequested: null,
|
|
39
|
-
runtimeAgentApplied: null,
|
|
40
|
-
runtimeAgentSupported: false,
|
|
41
|
-
status: decision.permissionDecision === "blocked" ? "BLOCKED" : "COMPLETED"
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Logs and executes the implementation process by Astra.
|
|
49
|
-
*/
|
|
50
|
-
async implement(promptMsg, owner, options = {}) {
|
|
51
|
-
const decision = this.lastRoutingDecision || {};
|
|
52
|
-
const inspection = await this.adapter.inspect();
|
|
53
|
-
const isSpecialized = ["Nexus", "Orion", "Astra", "Sage", "Phoenix"].includes(owner);
|
|
54
|
-
const supportsAgent = inspection.supports?.agent;
|
|
55
|
-
const canApply = !isSpecialized || supportsAgent;
|
|
56
|
-
|
|
57
|
-
if (this.ledger) {
|
|
58
|
-
this.ledger.logEvent({
|
|
59
|
-
actor: canApply ? owner : "Atlas",
|
|
60
|
-
actorType: canApply ? "runtime-agent" : "control-plane",
|
|
61
|
-
observed: true,
|
|
62
|
-
runtime: "opencode",
|
|
63
|
-
eventType: "implementation_start",
|
|
64
|
-
provenance: "opencode-adapter",
|
|
65
|
-
data: {
|
|
66
|
-
agent: owner,
|
|
67
|
-
prompt: promptMsg,
|
|
68
|
-
event: "implementation.started",
|
|
69
|
-
requestedActor: decision.requestedActor || null,
|
|
70
|
-
selectedActor: decision.selectedActor || null,
|
|
71
|
-
runtimeAgentRequested: owner,
|
|
72
|
-
runtimeAgentApplied: null,
|
|
73
|
-
runtimeAgentSupported: supportsAgent || false,
|
|
74
|
-
permissionDecision: decision.permissionDecision || null,
|
|
75
|
-
reason: decision.reason || null,
|
|
76
|
-
status: "started"
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const runResult = await this.adapter.execute(promptMsg, { agent: owner, ...options });
|
|
82
|
-
|
|
83
|
-
if (this.ledger) {
|
|
84
|
-
const isApplied = runResult.runtimeAgentApplied === owner;
|
|
85
|
-
this.ledger.logEvent({
|
|
86
|
-
actor: isApplied ? owner : "Atlas",
|
|
87
|
-
actorType: isApplied ? "runtime-agent" : "control-plane",
|
|
88
|
-
observed: true,
|
|
89
|
-
runtime: "opencode",
|
|
90
|
-
eventType: "implementation_complete",
|
|
91
|
-
provenance: "opencode-adapter",
|
|
92
|
-
data: {
|
|
93
|
-
success: runResult.success,
|
|
94
|
-
commandsRun: runResult.commandsRun || [],
|
|
95
|
-
eventCount: runResult.eventCount || 0,
|
|
96
|
-
error: runResult.error,
|
|
97
|
-
event: "implementation.completed",
|
|
98
|
-
requestedActor: decision.requestedActor || null,
|
|
99
|
-
selectedActor: decision.selectedActor || null,
|
|
100
|
-
runtimeAgentRequested: runResult.runtimeAgentRequested || owner || null,
|
|
101
|
-
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
102
|
-
runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
|
|
103
|
-
permissionDecision: decision.permissionDecision || null,
|
|
104
|
-
reason: runResult.error || decision.reason || null,
|
|
105
|
-
status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return runResult;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Runs validation locally without logging Sage since no validation agent runtime was executed.
|
|
115
|
-
*/
|
|
116
|
-
async validate(taskSlug, mode, profile, verifyFn) {
|
|
117
|
-
const result = await verifyFn();
|
|
118
|
-
|
|
119
|
-
if (mode === "full") {
|
|
120
|
-
const decision = this.lastRoutingDecision || {};
|
|
121
|
-
const inspection = await this.adapter.inspect();
|
|
122
|
-
const supportsAgent = inspection.supports?.agent;
|
|
123
|
-
const canApply = supportsAgent;
|
|
124
|
-
|
|
125
|
-
if (this.ledger) {
|
|
126
|
-
this.ledger.logEvent({
|
|
127
|
-
actor: canApply ? "Sage" : "Atlas",
|
|
128
|
-
actorType: canApply ? "auditor" : "control-plane",
|
|
129
|
-
observed: true,
|
|
130
|
-
runtime: "opencode",
|
|
131
|
-
eventType: "validation_start",
|
|
132
|
-
provenance: "opencode-adapter",
|
|
133
|
-
data: {
|
|
134
|
-
agent: "Sage",
|
|
135
|
-
event: "validation.started",
|
|
136
|
-
requestedActor: decision.requestedActor || null,
|
|
137
|
-
selectedActor: decision.selectedActor || null,
|
|
138
|
-
runtimeAgentRequested: "Sage",
|
|
139
|
-
runtimeAgentApplied: null,
|
|
140
|
-
runtimeAgentSupported: supportsAgent || false,
|
|
141
|
-
permissionDecision: decision.permissionDecision || null,
|
|
142
|
-
reason: decision.reason || null,
|
|
143
|
-
status: "started"
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let gitDiff = "";
|
|
149
|
-
try {
|
|
150
|
-
const { execSync } = await import("node:child_process");
|
|
151
|
-
gitDiff = execSync("git diff HEAD", { cwd: this.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
152
|
-
} catch {
|
|
153
|
-
// ignore
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const prompt = `Please validate the implementation. Diff:\n${gitDiff}\nLocal Evidence:\n${JSON.stringify(result, null, 2)}`;
|
|
157
|
-
const runResult = await this.adapter.execute(prompt, { agent: "Sage" });
|
|
158
|
-
|
|
159
|
-
if (this.ledger) {
|
|
160
|
-
const isApplied = runResult.runtimeAgentApplied === "Sage";
|
|
161
|
-
this.ledger.logEvent({
|
|
162
|
-
actor: isApplied ? "Sage" : "Atlas",
|
|
163
|
-
actorType: isApplied ? "auditor" : "control-plane",
|
|
164
|
-
observed: true,
|
|
165
|
-
runtime: "opencode",
|
|
166
|
-
eventType: "validation_complete",
|
|
167
|
-
provenance: "opencode-adapter",
|
|
168
|
-
data: {
|
|
169
|
-
success: runResult.success,
|
|
170
|
-
commandsRun: runResult.commandsRun || [],
|
|
171
|
-
eventCount: runResult.eventCount || 0,
|
|
172
|
-
error: runResult.error,
|
|
173
|
-
event: "validation.completed",
|
|
174
|
-
requestedActor: decision.requestedActor || null,
|
|
175
|
-
selectedActor: decision.selectedActor || null,
|
|
176
|
-
runtimeAgentRequested: runResult.runtimeAgentRequested || "Sage",
|
|
177
|
-
runtimeAgentApplied: runResult.runtimeAgentApplied || null,
|
|
178
|
-
runtimeAgentSupported: runResult.runtimeAgentSupported !== undefined ? runResult.runtimeAgentSupported : (supportsAgent || false),
|
|
179
|
-
permissionDecision: decision.permissionDecision || null,
|
|
180
|
-
reason: runResult.error || decision.reason || null,
|
|
181
|
-
status: runResult.status || (runResult.success ? "COMPLETED" : "FAILED")
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (!runResult.success) {
|
|
187
|
-
result.overallStatus = "FAIL_QUALITY_GATE";
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* EvidenceLedger - Traces structural workflow events to make delegation observable.
|
|
6
|
-
*/
|
|
7
|
-
export class EvidenceLedger {
|
|
8
|
-
constructor({ cwd = process.cwd(), workflowId = "unknown" } = {}) {
|
|
9
|
-
this.cwd = cwd;
|
|
10
|
-
this.workflowId = workflowId;
|
|
11
|
-
this.events = [];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Logs a workflow event to the ledger.
|
|
16
|
-
* @param {Object} eventParam
|
|
17
|
-
* @param {string} eventParam.actor - The agent name (Atlas, Astra, Sage, Phoenix).
|
|
18
|
-
* @param {string} eventParam.eventType - Type of event.
|
|
19
|
-
* @param {string} eventParam.provenance - Originating component.
|
|
20
|
-
* @param {Object} [eventParam.data] - Additional metadata.
|
|
21
|
-
* @returns {Object} The logged event.
|
|
22
|
-
*/
|
|
23
|
-
logEvent({ actor, eventType, provenance, data = {}, actorType, observed, runtime }) {
|
|
24
|
-
const event = {
|
|
25
|
-
workflowId: this.workflowId,
|
|
26
|
-
timestamp: new Date().toISOString(),
|
|
27
|
-
actor,
|
|
28
|
-
actorType: actorType !== undefined ? actorType : undefined,
|
|
29
|
-
observed: observed !== undefined ? observed : undefined,
|
|
30
|
-
runtime: runtime !== undefined ? runtime : undefined,
|
|
31
|
-
eventType,
|
|
32
|
-
provenance,
|
|
33
|
-
data
|
|
34
|
-
};
|
|
35
|
-
this.events.push(event);
|
|
36
|
-
console.log(`[LEDGER] [${actor}] ${eventType}: ${JSON.stringify(data)}`);
|
|
37
|
-
return event;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
getEvents() {
|
|
41
|
-
return this.events;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Saves the ledger events to a JSON file.
|
|
46
|
-
* @param {string} filePath - Absolute or relative path.
|
|
47
|
-
*/
|
|
48
|
-
async save(filePath) {
|
|
49
|
-
const targetPath = path.isAbsolute(filePath) ? filePath : path.join(this.cwd, filePath);
|
|
50
|
-
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
51
|
-
await fs.writeFile(targetPath, JSON.stringify(this.events, null, 2), "utf8");
|
|
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
|
-
}
|
|
98
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* ExecutionPlanner - Formulates a minimum operational plan based on classification.
|
|
5
|
-
*/
|
|
6
|
-
export class ExecutionPlanner {
|
|
7
|
-
constructor({ cwd = process.cwd() } = {}) {
|
|
8
|
-
this.cwd = cwd;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Generates an execution plan.
|
|
13
|
-
* @param {Object} classification - The output of RequestClassifier.
|
|
14
|
-
* @param {string} taskSlug - Descriptive slug for the task.
|
|
15
|
-
* @returns {Object} Operational plan contract.
|
|
16
|
-
*/
|
|
17
|
-
plan(classification, taskSlug = "task") {
|
|
18
|
-
const remediationLimit = classification.mode === "full" ? 3 : classification.mode === "quick" ? 1 : 2;
|
|
19
|
-
const branchNeeded = classification.intent === "write";
|
|
20
|
-
const specPath = classification.specNeeded
|
|
21
|
-
? path.join("docs/workflows", taskSlug, "spec.md")
|
|
22
|
-
: null;
|
|
23
|
-
|
|
24
|
-
const owner = classification.routingDecision?.selectedActor ||
|
|
25
|
-
(classification.intent === "write" ? "Astra" : classification.owner);
|
|
26
|
-
|
|
27
|
-
// Build default expected validations based on the resolved profile
|
|
28
|
-
const validationsExpected = [];
|
|
29
|
-
if (classification.validationNeeded) {
|
|
30
|
-
validationsExpected.push("test");
|
|
31
|
-
if (classification.profile.startsWith("frontend")) {
|
|
32
|
-
validationsExpected.push("lint", "build");
|
|
33
|
-
} else if (classification.profile === "backend-api") {
|
|
34
|
-
validationsExpected.push("lint");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const restrictions = [
|
|
39
|
-
"Never commit or push directly to main/master.",
|
|
40
|
-
"Always execute behavior tests for new or modified behavior."
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
if (classification.risk === "high") {
|
|
44
|
-
restrictions.push("Require independent validation review.");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
objective: classification.request,
|
|
49
|
-
scope: classification.intent === "read-only" ? "Analysis and verification" : "Implementation of requested behavior",
|
|
50
|
-
restrictions,
|
|
51
|
-
owner,
|
|
52
|
-
skills: [...classification.skills],
|
|
53
|
-
branchNeeded,
|
|
54
|
-
branchName: branchNeeded ? `feat/${taskSlug}` : null,
|
|
55
|
-
validationsExpected,
|
|
56
|
-
remediationLimit,
|
|
57
|
-
specPath,
|
|
58
|
-
mode: classification.mode,
|
|
59
|
-
profile: classification.profile
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
package/src/core/filesystem.js
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export async function exists(filePath) {
|
|
5
|
-
try {
|
|
6
|
-
await fs.access(filePath);
|
|
7
|
-
return true;
|
|
8
|
-
} catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function writeFileSafe(filePath, content) {
|
|
14
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
15
|
-
await fs.writeFile(filePath, content, "utf8");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function readJson(filePath) {
|
|
19
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
20
|
-
return JSON.parse(content);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function stripJsonComments(content) {
|
|
24
|
-
let output = "";
|
|
25
|
-
let inString = false;
|
|
26
|
-
let quote = "";
|
|
27
|
-
let escaped = false;
|
|
28
|
-
let inLineComment = false;
|
|
29
|
-
let inBlockComment = false;
|
|
30
|
-
|
|
31
|
-
for (let index = 0; index < content.length; index += 1) {
|
|
32
|
-
const char = content[index];
|
|
33
|
-
const next = content[index + 1];
|
|
34
|
-
|
|
35
|
-
if (inLineComment) {
|
|
36
|
-
if (char === "\n" || char === "\r") {
|
|
37
|
-
inLineComment = false;
|
|
38
|
-
output += char;
|
|
39
|
-
}
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (inBlockComment) {
|
|
44
|
-
if (char === "*" && next === "/") {
|
|
45
|
-
inBlockComment = false;
|
|
46
|
-
index += 1;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (char === "\n" || char === "\r") {
|
|
50
|
-
output += char;
|
|
51
|
-
}
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (inString) {
|
|
56
|
-
output += char;
|
|
57
|
-
if (escaped) {
|
|
58
|
-
escaped = false;
|
|
59
|
-
} else if (char === "\\") {
|
|
60
|
-
escaped = true;
|
|
61
|
-
} else if (char === quote) {
|
|
62
|
-
inString = false;
|
|
63
|
-
quote = "";
|
|
64
|
-
}
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (char === '"' || char === "'") {
|
|
69
|
-
inString = true;
|
|
70
|
-
quote = char;
|
|
71
|
-
output += char;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (char === "/" && next === "/") {
|
|
76
|
-
inLineComment = true;
|
|
77
|
-
index += 1;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (char === "/" && next === "*") {
|
|
82
|
-
inBlockComment = true;
|
|
83
|
-
index += 1;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
output += char;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return output;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function stripTrailingCommas(content) {
|
|
94
|
-
let output = "";
|
|
95
|
-
let inString = false;
|
|
96
|
-
let quote = "";
|
|
97
|
-
let escaped = false;
|
|
98
|
-
|
|
99
|
-
for (let index = 0; index < content.length; index += 1) {
|
|
100
|
-
const char = content[index];
|
|
101
|
-
|
|
102
|
-
if (inString) {
|
|
103
|
-
output += char;
|
|
104
|
-
if (escaped) {
|
|
105
|
-
escaped = false;
|
|
106
|
-
} else if (char === "\\") {
|
|
107
|
-
escaped = true;
|
|
108
|
-
} else if (char === quote) {
|
|
109
|
-
inString = false;
|
|
110
|
-
quote = "";
|
|
111
|
-
}
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (char === '"' || char === "'") {
|
|
116
|
-
inString = true;
|
|
117
|
-
quote = char;
|
|
118
|
-
output += char;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (char === ",") {
|
|
123
|
-
let nextIndex = index + 1;
|
|
124
|
-
while (/\s/.test(content[nextIndex] ?? "")) {
|
|
125
|
-
nextIndex += 1;
|
|
126
|
-
}
|
|
127
|
-
if (content[nextIndex] === "}" || content[nextIndex] === "]") {
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
output += char;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return output;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function readJsonc(filePath) {
|
|
139
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
140
|
-
return JSON.parse(stripTrailingCommas(stripJsonComments(content)));
|
|
141
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { WorkspaceSnapshot } from "./workspace-snapshot.js";
|
|
2
|
-
import { DeliveryDecisionEngine } from "../validation/delivery-decision-engine.js";
|
|
3
|
-
import { ArtifactFidelityGate } from "../validation/artifact-fidelity-gate.js";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
|
|
6
|
-
export class Finalizer {
|
|
7
|
-
constructor({ cwd = process.cwd() } = {}) {
|
|
8
|
-
this.cwd = cwd;
|
|
9
|
-
this.snapshotManager = new WorkspaceSnapshot({ cwd });
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
getChangedFilesFromGit() {
|
|
13
|
-
try {
|
|
14
|
-
const files = new Set();
|
|
15
|
-
// Unstaged changes
|
|
16
|
-
const diff = execSync("git diff --name-only HEAD", { cwd: this.cwd, encoding: "utf8" });
|
|
17
|
-
diff.split("\n").map(f => f.trim()).filter(Boolean).forEach(f => files.add(f));
|
|
18
|
-
|
|
19
|
-
// Untracked/staged status
|
|
20
|
-
const status = execSync("git status --short --untracked-files=all", { cwd: this.cwd, encoding: "utf8" });
|
|
21
|
-
status.split("\n").forEach(line => {
|
|
22
|
-
const match = line.match(/^(?:[ MADRCU?!]{2}\s+|[MADRCU?!]\s+)(.+)$/);
|
|
23
|
-
if (match) {
|
|
24
|
-
const candidate = match[1].trim();
|
|
25
|
-
const renamed = candidate.includes(" -> ") ? candidate.split(" -> ").at(-1) : candidate;
|
|
26
|
-
if (renamed) files.add(renamed);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
return [...files].sort();
|
|
30
|
-
} catch {
|
|
31
|
-
return [];
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
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
|
-
|
|
54
|
-
const filesToCheck = changedFiles || this.getChangedFilesFromGit();
|
|
55
|
-
const decisionEngine = new DeliveryDecisionEngine({ cwd: this.cwd });
|
|
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
|
-
|
|
88
|
-
return {
|
|
89
|
-
passed,
|
|
90
|
-
status: requestFidelity.status || (passed ? "PASS" : "BLOCKED"),
|
|
91
|
-
reason,
|
|
92
|
-
deliveryClass: actualDeliveryClass,
|
|
93
|
-
requestFidelity
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Compares the current workspace state with a previously captured snapshot.
|
|
99
|
-
* @param {Object} previousSnapshot - The snapshot object captured earlier.
|
|
100
|
-
* @returns {Object} { valid: boolean, changes: { added: string[], modified: string[], deleted: string[] } }
|
|
101
|
-
*/
|
|
102
|
-
async verifyIntegrity(previousSnapshot) {
|
|
103
|
-
const currentSnapshot = await this.snapshotManager.capture();
|
|
104
|
-
const added = [];
|
|
105
|
-
const modified = [];
|
|
106
|
-
const deleted = [];
|
|
107
|
-
|
|
108
|
-
let valid = true;
|
|
109
|
-
|
|
110
|
-
// Compare metadata and git trees
|
|
111
|
-
if (previousSnapshot.branch !== currentSnapshot.branch) {
|
|
112
|
-
modified.push(`branch:${previousSnapshot.branch}->${currentSnapshot.branch}`);
|
|
113
|
-
valid = false;
|
|
114
|
-
}
|
|
115
|
-
if (previousSnapshot.head !== currentSnapshot.head) {
|
|
116
|
-
modified.push(`head:${previousSnapshot.head}->${currentSnapshot.head}`);
|
|
117
|
-
valid = false;
|
|
118
|
-
}
|
|
119
|
-
if (previousSnapshot.indexTree !== currentSnapshot.indexTree) {
|
|
120
|
-
modified.push(`indexTree:${previousSnapshot.indexTree}->${currentSnapshot.indexTree}`);
|
|
121
|
-
valid = false;
|
|
122
|
-
}
|
|
123
|
-
if (previousSnapshot.stagedDiffHash !== currentSnapshot.stagedDiffHash) {
|
|
124
|
-
modified.push("stagedDiff");
|
|
125
|
-
valid = false;
|
|
126
|
-
}
|
|
127
|
-
if (previousSnapshot.unstagedDiffHash !== currentSnapshot.unstagedDiffHash) {
|
|
128
|
-
modified.push("unstagedDiff");
|
|
129
|
-
valid = false;
|
|
130
|
-
}
|
|
131
|
-
if (previousSnapshot.untrackedFilesHash !== currentSnapshot.untrackedFilesHash) {
|
|
132
|
-
modified.push("untrackedFiles");
|
|
133
|
-
valid = false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Check for additions and modifications in files
|
|
137
|
-
const prevFiles = previousSnapshot.files || {};
|
|
138
|
-
const currFiles = currentSnapshot.files || {};
|
|
139
|
-
|
|
140
|
-
for (const [file, info] of Object.entries(currFiles)) {
|
|
141
|
-
const prevInfo = prevFiles[file];
|
|
142
|
-
if (!prevInfo) {
|
|
143
|
-
added.push(file);
|
|
144
|
-
} else if (prevInfo.sha256 !== info.sha256 || prevInfo.mode !== info.mode) {
|
|
145
|
-
modified.push(file);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check for deletions in files
|
|
150
|
-
for (const file of Object.keys(prevFiles)) {
|
|
151
|
-
if (!currFiles[file]) {
|
|
152
|
-
deleted.push(file);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (added.length > 0 || modified.length > 0 || deleted.length > 0) {
|
|
157
|
-
valid = false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
valid,
|
|
162
|
-
changes: { added, modified, deleted }
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|