agentweaver 0.1.13 → 0.1.15
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/README.md +24 -19
- package/dist/artifacts.js +6 -1
- package/dist/doctor/checks/cwd-context.js +4 -3
- package/dist/doctor/checks/env-diagnostics.js +168 -71
- package/dist/doctor/checks/flow-readiness.js +210 -198
- package/dist/doctor/index.js +1 -1
- package/dist/doctor/orchestrator.js +18 -7
- package/dist/doctor/runner.js +9 -8
- package/dist/doctor/types.js +12 -0
- package/dist/index.js +119 -55
- package/dist/interactive-ui.js +25 -25
- package/dist/pipeline/declarative-flows.js +1 -0
- package/dist/pipeline/flow-catalog.js +4 -0
- package/dist/pipeline/flow-specs/auto-common.json +1 -0
- package/dist/pipeline/flow-specs/auto-golang.json +2 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +1 -0
- package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
- package/dist/pipeline/flow-specs/design-review.json +239 -0
- package/dist/pipeline/flow-specs/git-commit.json +1 -0
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +3 -2
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +3 -2
- package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
- package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +3 -2
- package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +3 -2
- package/dist/pipeline/flow-specs/implement.json +13 -0
- package/dist/pipeline/flow-specs/plan-revise.json +261 -0
- package/dist/pipeline/flow-specs/plan.json +2 -1
- package/dist/pipeline/flow-specs/review/review-fix.json +1 -0
- package/dist/pipeline/flow-specs/review/review-loop.json +1 -0
- package/dist/pipeline/flow-specs/review/review-project.json +1 -0
- package/dist/pipeline/flow-specs/review/review.json +2 -1
- package/dist/pipeline/flow-specs/task-describe.json +67 -8
- package/dist/pipeline/node-registry.js +8 -0
- package/dist/pipeline/nodes/ensure-summary-json-node.js +59 -0
- package/dist/pipeline/nodes/git-commit-node.js +1 -1
- package/dist/pipeline/nodes/git-status-node.js +1 -1
- package/dist/pipeline/nodes/review-findings-form-node.js +8 -8
- package/dist/pipeline/nodes/user-input-node.js +2 -2
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/spec-types.js +2 -0
- package/dist/pipeline/value-resolver.js +11 -1
- package/dist/prompts.js +49 -2
- package/dist/runtime/design-review-input-contract.js +112 -0
- package/dist/runtime/plan-revise-input-contract.js +144 -0
- package/dist/runtime/process-runner.js +2 -2
- package/dist/runtime/ready-to-merge.js +10 -0
- package/dist/scope.js +13 -4
- package/dist/structured-artifact-schema-registry.js +1 -0
- package/dist/structured-artifact-schemas.json +117 -0
- package/dist/structured-artifacts.js +6 -0
- package/package.json +3 -2
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { DoctorStatus } from "../types.js";
|
|
3
|
+
import { DoctorImpact, DoctorStatus, WorkflowContinuityState } from "../types.js";
|
|
4
4
|
import { CATEGORY } from "./category.js";
|
|
5
5
|
import { BUILT_IN_COMMAND_FLOW_IDS } from "../../pipeline/flow-catalog.js";
|
|
6
6
|
import { validateStructuredArtifact } from "../../structured-artifacts.js";
|
|
7
|
-
import { designJsonFile, planJsonFile,
|
|
7
|
+
import { designJsonFile, jiraDescriptionJsonFile, latestArtifactIteration, planJsonFile, } from "../../artifacts.js";
|
|
8
8
|
const GO_BINARY_PATTERNS = ["go", "go.exe"];
|
|
9
9
|
const GIT_BINARY_PATTERNS = ["git", "git.exe"];
|
|
10
|
+
function statePriority(state) {
|
|
11
|
+
switch (state) {
|
|
12
|
+
case WorkflowContinuityState.InvalidState:
|
|
13
|
+
return 3;
|
|
14
|
+
case WorkflowContinuityState.NotConfigured:
|
|
15
|
+
return 2;
|
|
16
|
+
case WorkflowContinuityState.NeedsPreviousStage:
|
|
17
|
+
return 1;
|
|
18
|
+
case WorkflowContinuityState.Available:
|
|
19
|
+
default:
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function formatState(state) {
|
|
24
|
+
switch (state) {
|
|
25
|
+
case WorkflowContinuityState.Available:
|
|
26
|
+
return "available";
|
|
27
|
+
case WorkflowContinuityState.NeedsPreviousStage:
|
|
28
|
+
return "requires previous stage outputs";
|
|
29
|
+
case WorkflowContinuityState.NotConfigured:
|
|
30
|
+
return "not configured";
|
|
31
|
+
case WorkflowContinuityState.InvalidState:
|
|
32
|
+
return "invalid state";
|
|
33
|
+
default:
|
|
34
|
+
return state;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function setEntryState(entry, state, reason) {
|
|
38
|
+
if (statePriority(state) > statePriority(entry.state)) {
|
|
39
|
+
entry.state = state;
|
|
40
|
+
}
|
|
41
|
+
entry.reasons.push(reason);
|
|
42
|
+
}
|
|
43
|
+
function createEntry(flowId, mode) {
|
|
44
|
+
return {
|
|
45
|
+
flowId,
|
|
46
|
+
...(mode ? { mode } : {}),
|
|
47
|
+
state: WorkflowContinuityState.Available,
|
|
48
|
+
reasons: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
10
51
|
function findBinary(name) {
|
|
11
52
|
const envPath = process.env.PATH ?? "";
|
|
12
53
|
const pathDirs = envPath.split(path.delimiter);
|
|
@@ -20,233 +61,150 @@ function findBinary(name) {
|
|
|
20
61
|
}
|
|
21
62
|
return null;
|
|
22
63
|
}
|
|
23
|
-
function checkBinaryPresence(flowId) {
|
|
24
|
-
const goFlows = new Set([
|
|
25
|
-
"run-go-tests-loop",
|
|
26
|
-
"run-go-linter-loop",
|
|
27
|
-
"auto-golang",
|
|
28
|
-
]);
|
|
29
|
-
const gitFlows = new Set(["git-commit", "gitlab-review", "gitlab-diff-review", "mr-description"]);
|
|
30
|
-
if (goFlows.has(flowId)) {
|
|
31
|
-
return findBinary("go");
|
|
32
|
-
}
|
|
33
|
-
if (gitFlows.has(flowId)) {
|
|
34
|
-
return findBinary("git");
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
64
|
function isJiraConfigured() {
|
|
39
65
|
return !!(process.env.JIRA_API_KEY && process.env.JIRA_BASE_URL);
|
|
40
66
|
}
|
|
41
67
|
function isGitLabConfigured() {
|
|
42
68
|
return !!process.env.GITLAB_TOKEN;
|
|
43
69
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
if (!existsSync(artifactsDir)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
const files = readdirSync(artifactsDir);
|
|
50
|
-
const designFiles = files.filter((f) => /^design-.*-\d+\.json$/.test(f));
|
|
51
|
-
if (designFiles.length === 0) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const iterations = designFiles.map((f) => {
|
|
55
|
-
const match = f.match(/^design-.*-(\d+)\.json$/);
|
|
56
|
-
return match?.[1] ? parseInt(match[1], 10) : 0;
|
|
57
|
-
});
|
|
58
|
-
return Math.max(...iterations);
|
|
70
|
+
function getLatestJsonArtifactIteration(scopeKey, prefix) {
|
|
71
|
+
return latestArtifactIteration(scopeKey, prefix, "json");
|
|
59
72
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const match = f.match(/^plan-.*-(\d+)\.json$/);
|
|
72
|
-
return match?.[1] ? parseInt(match[1], 10) : 0;
|
|
73
|
-
});
|
|
74
|
-
return Math.max(...iterations);
|
|
73
|
+
function checkStructuredArtifactFile(entry, artifactPath, schemaId, missingReason, invalidReasonPrefix) {
|
|
74
|
+
if (!existsSync(artifactPath)) {
|
|
75
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, missingReason);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
validateStructuredArtifact(artifactPath, schemaId);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
setEntryState(entry, WorkflowContinuityState.InvalidState, `${invalidReasonPrefix}: ${error.message}`);
|
|
83
|
+
}
|
|
75
84
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
const latestDesignIteration =
|
|
79
|
-
const latestPlanIteration =
|
|
85
|
+
function checkImplementWorkflowContinuity(scopeKey) {
|
|
86
|
+
const entry = createEntry("implement");
|
|
87
|
+
const latestDesignIteration = getLatestJsonArtifactIteration(scopeKey, "design");
|
|
88
|
+
const latestPlanIteration = getLatestJsonArtifactIteration(scopeKey, "plan");
|
|
80
89
|
if (latestDesignIteration === null) {
|
|
81
|
-
|
|
90
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing design artifact from the planning stage.");
|
|
82
91
|
}
|
|
83
92
|
else {
|
|
84
|
-
|
|
85
|
-
if (!existsSync(designPath)) {
|
|
86
|
-
blockers.push(`design artifact not found at ${designPath}`);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
try {
|
|
90
|
-
validateStructuredArtifact(designPath, "implementation-design/v1");
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
blockers.push(`design artifact schema invalid: ${error.message}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
93
|
+
checkStructuredArtifactFile(entry, designJsonFile(scopeKey, latestDesignIteration), "implementation-design/v1", "Missing design artifact from the planning stage.", "Design artifact schema is invalid");
|
|
96
94
|
}
|
|
97
95
|
if (latestPlanIteration === null) {
|
|
98
|
-
|
|
96
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing plan artifact from the planning stage.");
|
|
99
97
|
}
|
|
100
98
|
else {
|
|
101
|
-
|
|
102
|
-
if (!existsSync(planPath)) {
|
|
103
|
-
blockers.push(`plan artifact not found at ${planPath}`);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
try {
|
|
107
|
-
validateStructuredArtifact(planPath, "implementation-plan/v1");
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
blockers.push(`plan artifact schema invalid: ${error.message}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
99
|
+
checkStructuredArtifactFile(entry, planJsonFile(scopeKey, latestPlanIteration), "implementation-plan/v1", "Missing plan artifact from the planning stage.", "Plan artifact schema is invalid");
|
|
113
100
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
status: blockers.length === 0 ? "ready" : "not_ready",
|
|
117
|
-
blockers,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
function checkReviewProjectFlowReadiness(scopeKey) {
|
|
121
|
-
const blockers = [];
|
|
122
|
-
const latestDesignIteration = getLatestDesignIteration(scopeKey);
|
|
123
|
-
if (latestDesignIteration === null) {
|
|
124
|
-
blockers.push("design artifact not found");
|
|
101
|
+
if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
|
|
102
|
+
entry.nextStep = "Run plan to generate design, plan, and QA artifacts for this scope.";
|
|
125
103
|
}
|
|
126
|
-
else {
|
|
127
|
-
|
|
128
|
-
if (!existsSync(designPath)) {
|
|
129
|
-
blockers.push(`design artifact not found at ${designPath}`);
|
|
130
|
-
}
|
|
104
|
+
else if (entry.state === WorkflowContinuityState.InvalidState) {
|
|
105
|
+
entry.nextStep = "Regenerate the planning artifacts for this scope before running implement.";
|
|
131
106
|
}
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
blockers,
|
|
137
|
-
};
|
|
107
|
+
return entry;
|
|
108
|
+
}
|
|
109
|
+
function checkReviewProjectWorkflowContinuity() {
|
|
110
|
+
return createEntry("review", "project");
|
|
138
111
|
}
|
|
139
|
-
function
|
|
140
|
-
const
|
|
112
|
+
function checkReviewJiraWorkflowContinuity(scopeKey) {
|
|
113
|
+
const entry = createEntry("review", "jira");
|
|
141
114
|
if (!isJiraConfigured()) {
|
|
142
|
-
|
|
115
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "Jira integration is not configured (JIRA_API_KEY or JIRA_BASE_URL missing).");
|
|
143
116
|
}
|
|
144
|
-
const latestDesignIteration =
|
|
117
|
+
const latestDesignIteration = getLatestJsonArtifactIteration(scopeKey, "design");
|
|
118
|
+
const latestPlanIteration = getLatestJsonArtifactIteration(scopeKey, "plan");
|
|
145
119
|
if (latestDesignIteration === null) {
|
|
146
|
-
|
|
120
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing design artifact from the planning stage.");
|
|
147
121
|
}
|
|
148
122
|
else {
|
|
149
|
-
|
|
150
|
-
if (!existsSync(designPath)) {
|
|
151
|
-
blockers.push(`design artifact not found at ${designPath}`);
|
|
152
|
-
}
|
|
123
|
+
checkStructuredArtifactFile(entry, designJsonFile(scopeKey, latestDesignIteration), "implementation-design/v1", "Missing design artifact from the planning stage.", "Design artifact schema is invalid");
|
|
153
124
|
}
|
|
154
|
-
const latestPlanIteration = getLatestPlanIteration(scopeKey);
|
|
155
125
|
if (latestPlanIteration === null) {
|
|
156
|
-
|
|
126
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing plan artifact from the planning stage.");
|
|
157
127
|
}
|
|
158
128
|
else {
|
|
159
|
-
|
|
160
|
-
if (!existsSync(planPath)) {
|
|
161
|
-
blockers.push(`plan artifact not found at ${planPath}`);
|
|
162
|
-
}
|
|
129
|
+
checkStructuredArtifactFile(entry, planJsonFile(scopeKey, latestPlanIteration), "implementation-plan/v1", "Missing plan artifact from the planning stage.", "Plan artifact schema is invalid");
|
|
163
130
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
131
|
+
if (entry.state === WorkflowContinuityState.NotConfigured) {
|
|
132
|
+
entry.nextStep = "Configure Jira access and ensure the planning artifacts exist before running review:jira.";
|
|
133
|
+
}
|
|
134
|
+
else if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
|
|
135
|
+
entry.nextStep = "Run plan first to generate the design and plan artifacts required by review:jira.";
|
|
136
|
+
}
|
|
137
|
+
else if (entry.state === WorkflowContinuityState.InvalidState) {
|
|
138
|
+
entry.nextStep = "Regenerate invalid planning artifacts before running review:jira.";
|
|
139
|
+
}
|
|
140
|
+
return entry;
|
|
170
141
|
}
|
|
171
|
-
function
|
|
172
|
-
const
|
|
142
|
+
function checkPlanJiraWorkflowContinuity(scopeKey) {
|
|
143
|
+
const entry = createEntry("plan", "jira");
|
|
173
144
|
if (!isJiraConfigured()) {
|
|
174
|
-
|
|
145
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "Jira integration is not configured (JIRA_API_KEY or JIRA_BASE_URL missing).");
|
|
175
146
|
}
|
|
176
147
|
if (!process.env.JIRA_ISSUE_KEY && !scopeKey.includes("@")) {
|
|
177
|
-
|
|
148
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "No Jira issue key is available for the current scope.");
|
|
178
149
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
blockers,
|
|
184
|
-
};
|
|
150
|
+
if (entry.state === WorkflowContinuityState.NotConfigured) {
|
|
151
|
+
entry.nextStep = "Set Jira environment variables and provide a Jira issue key or browse URL before running plan:jira.";
|
|
152
|
+
}
|
|
153
|
+
return entry;
|
|
185
154
|
}
|
|
186
|
-
function
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
|
|
155
|
+
function checkPlanProjectWorkflowContinuity(scopeKey) {
|
|
156
|
+
const entry = createEntry("plan", "project");
|
|
157
|
+
const latestDescriptionIteration = getLatestJsonArtifactIteration(scopeKey, "jira-description");
|
|
158
|
+
if (latestDescriptionIteration === null) {
|
|
159
|
+
setEntryState(entry, WorkflowContinuityState.NeedsPreviousStage, "Missing task description artifact from task-describe.");
|
|
191
160
|
}
|
|
192
161
|
else {
|
|
193
|
-
|
|
194
|
-
if (!existsSync(designPath)) {
|
|
195
|
-
blockers.push("design artifact not found");
|
|
196
|
-
}
|
|
162
|
+
checkStructuredArtifactFile(entry, jiraDescriptionJsonFile(scopeKey, latestDescriptionIteration), "jira-description/v1", "Missing task description artifact from task-describe.", "Task description artifact schema is invalid");
|
|
197
163
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
164
|
+
if (entry.state === WorkflowContinuityState.NeedsPreviousStage) {
|
|
165
|
+
entry.nextStep = "Run task-describe first to create a task description artifact for this scope.";
|
|
166
|
+
}
|
|
167
|
+
else if (entry.state === WorkflowContinuityState.InvalidState) {
|
|
168
|
+
entry.nextStep = "Regenerate the task description artifact before running plan:project.";
|
|
169
|
+
}
|
|
170
|
+
return entry;
|
|
204
171
|
}
|
|
205
|
-
function
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"git_commit": "git",
|
|
214
|
-
"gitlab-review": "git",
|
|
215
|
-
"gitlab-diff-review": "git",
|
|
216
|
-
"mr-description": "git",
|
|
217
|
-
};
|
|
218
|
-
const required = requiredBinaries[flowId];
|
|
219
|
-
if (required) {
|
|
220
|
-
blockers.push(`${required} binary not found in PATH`);
|
|
221
|
-
}
|
|
172
|
+
function checkGenericWorkflowContinuity(flowId) {
|
|
173
|
+
const entry = createEntry(flowId);
|
|
174
|
+
const goFlows = new Set(["run-go-tests-loop", "run-go-linter-loop", "auto-golang"]);
|
|
175
|
+
const gitFlows = new Set(["git-commit", "gitlab-review", "gitlab-diff-review", "mr-description"]);
|
|
176
|
+
const gitLabFlows = new Set(["gitlab-review", "gitlab-diff-review", "mr-description"]);
|
|
177
|
+
if (goFlows.has(flowId) && findBinary("go") === null) {
|
|
178
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "Go binary is not available in PATH.");
|
|
179
|
+
entry.nextStep = "Install Go and ensure the go binary is available in PATH.";
|
|
222
180
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
181
|
+
if (gitFlows.has(flowId) && findBinary("git") === null) {
|
|
182
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "Git binary is not available in PATH.");
|
|
183
|
+
entry.nextStep = "Install Git and ensure the git binary is available in PATH.";
|
|
226
184
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
185
|
+
if (gitLabFlows.has(flowId) && !isGitLabConfigured()) {
|
|
186
|
+
setEntryState(entry, WorkflowContinuityState.NotConfigured, "GitLab integration is not configured (GITLAB_TOKEN missing).");
|
|
187
|
+
entry.nextStep = "Set GITLAB_TOKEN before running this GitLab-backed flow.";
|
|
188
|
+
}
|
|
189
|
+
return entry;
|
|
232
190
|
}
|
|
233
|
-
function
|
|
191
|
+
function determineWorkflowContinuity(flowId, scopeKey) {
|
|
234
192
|
if (flowId === "implement") {
|
|
235
|
-
return [
|
|
193
|
+
return [checkImplementWorkflowContinuity(scopeKey)];
|
|
236
194
|
}
|
|
237
195
|
if (flowId === "review") {
|
|
238
196
|
return [
|
|
239
|
-
|
|
240
|
-
|
|
197
|
+
checkReviewProjectWorkflowContinuity(),
|
|
198
|
+
checkReviewJiraWorkflowContinuity(scopeKey),
|
|
241
199
|
];
|
|
242
200
|
}
|
|
243
201
|
if (flowId === "plan") {
|
|
244
202
|
return [
|
|
245
|
-
|
|
246
|
-
|
|
203
|
+
checkPlanJiraWorkflowContinuity(scopeKey),
|
|
204
|
+
checkPlanProjectWorkflowContinuity(scopeKey),
|
|
247
205
|
];
|
|
248
206
|
}
|
|
249
|
-
return [
|
|
207
|
+
return [checkGenericWorkflowContinuity(flowId)];
|
|
250
208
|
}
|
|
251
209
|
function getScopeKey() {
|
|
252
210
|
const cwd = process.cwd();
|
|
@@ -260,46 +218,100 @@ function getScopeKey() {
|
|
|
260
218
|
}
|
|
261
219
|
return entries[0];
|
|
262
220
|
}
|
|
221
|
+
function buildSummary(entries) {
|
|
222
|
+
return {
|
|
223
|
+
available: entries.filter((entry) => entry.state === WorkflowContinuityState.Available).length,
|
|
224
|
+
needsPreviousStage: entries.filter((entry) => entry.state === WorkflowContinuityState.NeedsPreviousStage).length,
|
|
225
|
+
notConfigured: entries.filter((entry) => entry.state === WorkflowContinuityState.NotConfigured).length,
|
|
226
|
+
invalidState: entries.filter((entry) => entry.state === WorkflowContinuityState.InvalidState).length,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function buildMessage(summary) {
|
|
230
|
+
const parts = [`${summary.available} flows available`];
|
|
231
|
+
if (summary.needsPreviousStage > 0) {
|
|
232
|
+
parts.push(`${summary.needsPreviousStage} require earlier stage outputs`);
|
|
233
|
+
}
|
|
234
|
+
if (summary.notConfigured > 0) {
|
|
235
|
+
parts.push(`${summary.notConfigured} not configured`);
|
|
236
|
+
}
|
|
237
|
+
if (summary.invalidState > 0) {
|
|
238
|
+
parts.push(`${summary.invalidState} in invalid state`);
|
|
239
|
+
}
|
|
240
|
+
return parts.join(", ");
|
|
241
|
+
}
|
|
242
|
+
function buildDetails(scopeKey, entries) {
|
|
243
|
+
const lines = [`scope: ${scopeKey}`];
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const modeSuffix = entry.mode ? `:${entry.mode}` : "";
|
|
246
|
+
lines.push(` ${entry.flowId}${modeSuffix}: ${formatState(entry.state)}`);
|
|
247
|
+
for (const reason of entry.reasons) {
|
|
248
|
+
lines.push(` - ${reason}`);
|
|
249
|
+
}
|
|
250
|
+
if (entry.nextStep) {
|
|
251
|
+
lines.push(` next: ${entry.nextStep}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
263
256
|
function performFlowReadinessCheck() {
|
|
264
257
|
const scopeKey = getScopeKey();
|
|
265
|
-
const
|
|
258
|
+
const entries = [];
|
|
266
259
|
for (const flowId of BUILT_IN_COMMAND_FLOW_IDS) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
260
|
+
entries.push(...determineWorkflowContinuity(flowId, scopeKey));
|
|
261
|
+
}
|
|
262
|
+
const summary = buildSummary(entries);
|
|
263
|
+
const message = buildMessage(summary);
|
|
264
|
+
const details = buildDetails(scopeKey, entries);
|
|
265
|
+
const data = {
|
|
266
|
+
kind: "workflow-continuity",
|
|
267
|
+
scopeKey,
|
|
268
|
+
summary,
|
|
269
|
+
entries,
|
|
270
|
+
};
|
|
271
|
+
if (summary.invalidState > 0) {
|
|
272
|
+
return {
|
|
273
|
+
status: DoctorStatus.Warn,
|
|
274
|
+
impact: DoctorImpact.Blocking,
|
|
275
|
+
message,
|
|
276
|
+
hint: "Current scope contains invalid workflow artifacts. Regenerate or clean them up before continuing those flows.",
|
|
277
|
+
details,
|
|
278
|
+
data,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (summary.needsPreviousStage > 0 || summary.notConfigured > 0) {
|
|
282
|
+
return {
|
|
283
|
+
status: DoctorStatus.Warn,
|
|
284
|
+
impact: DoctorImpact.Advisory,
|
|
285
|
+
message,
|
|
286
|
+
hint: "These continuity findings describe what can be continued in the current scope and do not affect overall application readiness.",
|
|
287
|
+
details,
|
|
288
|
+
data,
|
|
289
|
+
};
|
|
281
290
|
}
|
|
282
|
-
const overallStatus = notReadyCount > 0 ? DoctorStatus.Fail : notConfiguredCount > 0 ? DoctorStatus.Warn : DoctorStatus.Ok;
|
|
283
|
-
const message = `${readyCount} flows ready, ${notReadyCount} not ready, ${notConfiguredCount} not configured`;
|
|
284
291
|
return {
|
|
285
|
-
status:
|
|
292
|
+
status: DoctorStatus.Ok,
|
|
293
|
+
impact: DoctorImpact.Advisory,
|
|
286
294
|
message,
|
|
287
|
-
details
|
|
295
|
+
details,
|
|
296
|
+
data,
|
|
288
297
|
};
|
|
289
298
|
}
|
|
290
299
|
export const flowReadinessCheck = {
|
|
291
300
|
id: "flow-readiness-01",
|
|
292
301
|
category: CATEGORY.FLOW_READINESS,
|
|
293
|
-
title: "
|
|
302
|
+
title: "workflow-continuity",
|
|
294
303
|
dependencies: ["env-diagnostics-01", "cwd-context-01"],
|
|
295
304
|
execute: async () => {
|
|
296
305
|
const result = performFlowReadinessCheck();
|
|
297
306
|
return {
|
|
298
307
|
id: "flow-readiness-01",
|
|
308
|
+
impact: result.impact,
|
|
299
309
|
status: result.status,
|
|
300
|
-
title: "
|
|
310
|
+
title: "workflow-continuity",
|
|
301
311
|
message: result.message,
|
|
312
|
+
...(result.hint ? { hint: result.hint } : {}),
|
|
302
313
|
details: result.details,
|
|
314
|
+
data: result.data,
|
|
303
315
|
};
|
|
304
316
|
},
|
|
305
317
|
};
|
package/dist/doctor/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DoctorStatus, ReadinessStatus } from "./types.js";
|
|
1
|
+
export { DoctorImpact, DoctorStatus, ReadinessStatus, WorkflowContinuityState } from "./types.js";
|
|
2
2
|
export { CheckRegistry, REGISTRY } from "./registry.js";
|
|
3
3
|
export { DoctorOrchestrator } from "./orchestrator.js";
|
|
4
4
|
export { runDoctorCommand } from "./runner.js";
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { DoctorStatus, ReadinessStatus } from "./types.js";
|
|
1
|
+
import { DoctorImpact, DoctorStatus, ReadinessStatus } from "./types.js";
|
|
2
2
|
import { REGISTRY } from "./registry.js";
|
|
3
|
-
import { SOFT_CHECK_IDS } from "./checks/cwd-context.js";
|
|
4
3
|
class DoctorOrchestrator {
|
|
5
4
|
async run(checks, filter) {
|
|
6
5
|
let checksToRun;
|
|
@@ -35,7 +34,7 @@ class DoctorOrchestrator {
|
|
|
35
34
|
for (const check of checksToRun) {
|
|
36
35
|
const result = await this.executeCheck(check);
|
|
37
36
|
results.push(result);
|
|
38
|
-
if (result.status === DoctorStatus.Fail) {
|
|
37
|
+
if (result.status === DoctorStatus.Fail && result.impact === DoctorImpact.Blocking) {
|
|
39
38
|
break;
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -48,28 +47,40 @@ class DoctorOrchestrator {
|
|
|
48
47
|
}
|
|
49
48
|
async executeCheck(check) {
|
|
50
49
|
const timeout = check.timeout ?? 30000;
|
|
50
|
+
let timeoutHandle;
|
|
51
51
|
try {
|
|
52
52
|
const timeoutPromise = new Promise((_, reject) => {
|
|
53
|
-
setTimeout(() => reject(new Error(`Check '${check.id}' timed out after ${timeout}ms`)), timeout);
|
|
53
|
+
timeoutHandle = setTimeout(() => reject(new Error(`Check '${check.id}' timed out after ${timeout}ms`)), timeout);
|
|
54
|
+
timeoutHandle.unref?.();
|
|
54
55
|
});
|
|
55
56
|
const result = await Promise.race([check.execute(), timeoutPromise]);
|
|
56
|
-
return
|
|
57
|
+
return {
|
|
58
|
+
...result,
|
|
59
|
+
impact: result.impact ?? check.impact ?? DoctorImpact.Blocking,
|
|
60
|
+
};
|
|
57
61
|
}
|
|
58
62
|
catch (error) {
|
|
59
63
|
return {
|
|
60
64
|
id: check.id,
|
|
65
|
+
impact: check.impact ?? DoctorImpact.Blocking,
|
|
61
66
|
status: DoctorStatus.Fail,
|
|
62
67
|
title: check.title,
|
|
63
68
|
message: error instanceof Error ? error.message : "Unknown error occurred",
|
|
64
69
|
hint: `Check execution failed: ${check.id}`,
|
|
65
70
|
};
|
|
66
71
|
}
|
|
72
|
+
finally {
|
|
73
|
+
if (timeoutHandle) {
|
|
74
|
+
clearTimeout(timeoutHandle);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
aggregateReadiness(results) {
|
|
69
|
-
|
|
79
|
+
const blockingResults = results.filter((result) => result.impact === DoctorImpact.Blocking);
|
|
80
|
+
if (blockingResults.some((r) => r.status === DoctorStatus.Fail)) {
|
|
70
81
|
return ReadinessStatus.NotReady;
|
|
71
82
|
}
|
|
72
|
-
if (
|
|
83
|
+
if (blockingResults.some((r) => r.status === DoctorStatus.Warn)) {
|
|
73
84
|
return ReadinessStatus.ReadyWithWarnings;
|
|
74
85
|
}
|
|
75
86
|
return ReadinessStatus.Ready;
|
package/dist/doctor/runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DoctorStatus, ReadinessStatus } from "./types.js";
|
|
1
|
+
import { DoctorImpact, DoctorStatus, ReadinessStatus } from "./types.js";
|
|
2
2
|
import { REGISTRY } from "./registry.js";
|
|
3
3
|
import { DoctorOrchestrator } from "./orchestrator.js";
|
|
4
4
|
import { CATEGORY } from "./checks/category.js";
|
|
@@ -17,8 +17,13 @@ const CATEGORY_LABELS = {
|
|
|
17
17
|
[CATEGORY.SYSTEM]: "System",
|
|
18
18
|
[CATEGORY.EXECUTORS]: "Executors",
|
|
19
19
|
[CATEGORY.ENV_DIAGNOSTICS]: "Environment",
|
|
20
|
-
[CATEGORY.FLOW_READINESS]: "
|
|
20
|
+
[CATEGORY.FLOW_READINESS]: "Workflow Continuity",
|
|
21
21
|
};
|
|
22
|
+
function formatCheckLine(result) {
|
|
23
|
+
const icon = STATUS_ICONS[result.status];
|
|
24
|
+
const impactLabel = result.impact === DoctorImpact.Advisory ? " (advisory)" : "";
|
|
25
|
+
return `[${icon}] ${result.title}${impactLabel} - ${result.message}`;
|
|
26
|
+
}
|
|
22
27
|
async function runDoctorCommand(args) {
|
|
23
28
|
const jsonMode = args.includes("--json");
|
|
24
29
|
const filter = args.find((arg) => arg !== "--json");
|
|
@@ -51,9 +56,7 @@ async function runDoctorCommand(args) {
|
|
|
51
56
|
console.log(`## ${label}`);
|
|
52
57
|
console.log();
|
|
53
58
|
for (const result of items) {
|
|
54
|
-
|
|
55
|
-
const line = `[${icon}] ${result.title} - ${result.message}`;
|
|
56
|
-
console.log(line);
|
|
59
|
+
console.log(formatCheckLine(result));
|
|
57
60
|
if (result.hint) {
|
|
58
61
|
console.log(` Hint: ${result.hint}`);
|
|
59
62
|
}
|
|
@@ -70,9 +73,7 @@ async function runDoctorCommand(args) {
|
|
|
70
73
|
console.log(`## ${label}`);
|
|
71
74
|
console.log();
|
|
72
75
|
for (const result of items) {
|
|
73
|
-
|
|
74
|
-
const line = `[${icon}] ${result.title} - ${result.message}`;
|
|
75
|
-
console.log(line);
|
|
76
|
+
console.log(formatCheckLine(result));
|
|
76
77
|
if (result.hint) {
|
|
77
78
|
console.log(` Hint: ${result.hint}`);
|
|
78
79
|
}
|
package/dist/doctor/types.js
CHANGED
|
@@ -4,9 +4,21 @@ export var DoctorStatus;
|
|
|
4
4
|
DoctorStatus["Warn"] = "warn";
|
|
5
5
|
DoctorStatus["Fail"] = "fail";
|
|
6
6
|
})(DoctorStatus || (DoctorStatus = {}));
|
|
7
|
+
export var DoctorImpact;
|
|
8
|
+
(function (DoctorImpact) {
|
|
9
|
+
DoctorImpact["Blocking"] = "blocking";
|
|
10
|
+
DoctorImpact["Advisory"] = "advisory";
|
|
11
|
+
})(DoctorImpact || (DoctorImpact = {}));
|
|
7
12
|
export var ReadinessStatus;
|
|
8
13
|
(function (ReadinessStatus) {
|
|
9
14
|
ReadinessStatus["Ready"] = "ready";
|
|
10
15
|
ReadinessStatus["ReadyWithWarnings"] = "ready_with_warnings";
|
|
11
16
|
ReadinessStatus["NotReady"] = "not_ready";
|
|
12
17
|
})(ReadinessStatus || (ReadinessStatus = {}));
|
|
18
|
+
export var WorkflowContinuityState;
|
|
19
|
+
(function (WorkflowContinuityState) {
|
|
20
|
+
WorkflowContinuityState["Available"] = "available";
|
|
21
|
+
WorkflowContinuityState["NeedsPreviousStage"] = "needs_previous_stage";
|
|
22
|
+
WorkflowContinuityState["NotConfigured"] = "not_configured";
|
|
23
|
+
WorkflowContinuityState["InvalidState"] = "invalid_state";
|
|
24
|
+
})(WorkflowContinuityState || (WorkflowContinuityState = {}));
|