@shenlee/devcrew 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +21 -0
- package/LICENSE +186 -0
- package/README.md +142 -0
- package/README.zh-CN.md +156 -0
- package/SECURITY.md +19 -0
- package/dist/packages/adapters/src/index.js +234 -0
- package/dist/packages/cli/src/index.js +76 -0
- package/dist/packages/core/src/active-run.js +24 -0
- package/dist/packages/core/src/artifacts.js +120 -0
- package/dist/packages/core/src/config.js +51 -0
- package/dist/packages/core/src/index.js +11 -0
- package/dist/packages/core/src/paths.js +51 -0
- package/dist/packages/core/src/standards.js +61 -0
- package/dist/packages/core/src/store.js +24 -0
- package/dist/packages/core/src/types.js +48 -0
- package/dist/packages/core/src/validation.js +52 -0
- package/dist/packages/core/src/verification.js +157 -0
- package/dist/packages/core/src/version.js +2 -0
- package/dist/packages/core/src/workflow.js +166 -0
- package/dist/packages/orchestrator/src/index.js +376 -0
- package/dist/packages/plugins/src/index.js +173 -0
- package/dist/packages/service/src/index.js +2 -0
- package/dist/packages/service/src/stdio.js +100 -0
- package/dist/packages/service/src/tools.js +177 -0
- package/docs/claude-code.md +37 -0
- package/docs/codex.md +80 -0
- package/docs/quickstart.md +64 -0
- package/docs/roles.md +43 -0
- package/docs/workflow.md +70 -0
- package/examples/README.md +11 -0
- package/examples/feature-request.md +13 -0
- package/examples/greenfield-request.md +14 -0
- package/package.json +60 -0
- package/packages/adapters/src/index.ts +404 -0
- package/packages/cli/src/index.ts +88 -0
- package/packages/core/src/active-run.ts +31 -0
- package/packages/core/src/artifacts.ts +148 -0
- package/packages/core/src/config.ts +56 -0
- package/packages/core/src/index.ts +11 -0
- package/packages/core/src/paths.ts +66 -0
- package/packages/core/src/standards.ts +70 -0
- package/packages/core/src/store.ts +28 -0
- package/packages/core/src/types.ts +182 -0
- package/packages/core/src/validation.ts +79 -0
- package/packages/core/src/verification.ts +163 -0
- package/packages/core/src/version.ts +2 -0
- package/packages/core/src/workflow.ts +214 -0
- package/packages/orchestrator/src/index.ts +469 -0
- package/packages/plugins/assets/composer-icon.png +0 -0
- package/packages/plugins/assets/logo.png +0 -0
- package/packages/plugins/src/index.ts +211 -0
- package/packages/service/src/index.ts +2 -0
- package/packages/service/src/stdio.ts +121 -0
- package/packages/service/src/tools.ts +215 -0
- package/plugins/devcrew-codex/.codex-plugin/plugin.json +41 -0
- package/plugins/devcrew-codex/.mcp.json +16 -0
- package/plugins/devcrew-codex/agents/architect.toml +6 -0
- package/plugins/devcrew-codex/agents/implementer.toml +6 -0
- package/plugins/devcrew-codex/agents/pm.toml +6 -0
- package/plugins/devcrew-codex/agents/tester.toml +6 -0
- package/plugins/devcrew-codex/assets/composer-icon.png +0 -0
- package/plugins/devcrew-codex/assets/logo.png +0 -0
- package/plugins/devcrew-codex/skills/devcrew/SKILL.md +17 -0
- package/scripts/smoke-codex-plugin.mjs +331 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export const WORKFLOW_MODES = ["feature", "greenfield"];
|
|
2
|
+
export const EXECUTION_MODES = ["plan", "apply"];
|
|
3
|
+
export const HOSTS = ["codex", "claude"];
|
|
4
|
+
export const BACKENDS = ["codex", "claude", "local"];
|
|
5
|
+
export const PHASES = [
|
|
6
|
+
"requirements",
|
|
7
|
+
"architecture",
|
|
8
|
+
"implementation",
|
|
9
|
+
"testing",
|
|
10
|
+
"acceptance",
|
|
11
|
+
"complete",
|
|
12
|
+
];
|
|
13
|
+
export const GATES = ["requirements", "architecture", "implementation", "testing"];
|
|
14
|
+
export const ARTIFACTS = [
|
|
15
|
+
"requirements",
|
|
16
|
+
"architecture",
|
|
17
|
+
"implementation-plan",
|
|
18
|
+
"implementation-review",
|
|
19
|
+
"test-report",
|
|
20
|
+
"acceptance",
|
|
21
|
+
];
|
|
22
|
+
export const ROLE_SECTIONS = {
|
|
23
|
+
pm: [
|
|
24
|
+
{ heading: "Functional Scope", description: "explicit In Scope and Out of Scope lists" },
|
|
25
|
+
{ heading: "Users and Scenarios", description: "primary users and their key scenarios" },
|
|
26
|
+
{ heading: "Acceptance Criteria", description: "testable criteria written as Given / When / Then" },
|
|
27
|
+
{ heading: "Priorities", description: "classify each requirement as Must / Should / Could / Won't (MoSCoW)" },
|
|
28
|
+
{ heading: "Open Questions", description: "unresolved clarifications for the requester" },
|
|
29
|
+
],
|
|
30
|
+
architect: [
|
|
31
|
+
{ heading: "Technical Decisions", description: "for each key decision record Decision, Options Considered, Choice, Rationale, and Trade-offs" },
|
|
32
|
+
{ heading: "Interface Contracts", description: "for each interface give the signature, request/response schema, error contract, and data model" },
|
|
33
|
+
{ heading: "Data Flow and Deployment", description: "data flow, deployment expectations, and rollback strategy" },
|
|
34
|
+
{ heading: "Architecture Review Checklist", description: "how the design traces back to the approved requirements" },
|
|
35
|
+
],
|
|
36
|
+
implementer: [
|
|
37
|
+
{ heading: "Implementation Summary", description: "the smallest change that satisfies the approved architecture" },
|
|
38
|
+
{ heading: "Standards Compliance", description: "follow discovered standards and lint/format rules; run available lint/format/typecheck and report results" },
|
|
39
|
+
{ heading: "Changed Files", description: "every file you created or modified" },
|
|
40
|
+
{ heading: "Tests Added or Updated", description: "tests covering success, edge, and failure paths" },
|
|
41
|
+
],
|
|
42
|
+
tester: [
|
|
43
|
+
{ heading: "Test Cases", description: "enumerate cases as a table with ID, Scenario, Type (happy/edge/failure/regression), and Expected result" },
|
|
44
|
+
{ heading: "Coverage", description: "run the coverage command and report the coverage summary plus any gaps" },
|
|
45
|
+
{ heading: "Verification Evidence", description: "exact commands, exit codes, and key output" },
|
|
46
|
+
{ heading: "Known Risks", description: "residual risks and follow-ups" },
|
|
47
|
+
],
|
|
48
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ARTIFACTS, BACKENDS, EXECUTION_MODES, GATES, HOSTS, WORKFLOW_MODES, } from "./types.js";
|
|
2
|
+
function assertString(value, field) {
|
|
3
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
5
|
+
}
|
|
6
|
+
return value.trim();
|
|
7
|
+
}
|
|
8
|
+
function oneOf(value, field, values) {
|
|
9
|
+
if (typeof value !== "string" || !values.includes(value)) {
|
|
10
|
+
throw new Error(`${field} must be one of: ${values.join(", ")}`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
export function parseWorkflowMode(value) {
|
|
15
|
+
return oneOf(value, "mode", WORKFLOW_MODES);
|
|
16
|
+
}
|
|
17
|
+
export function parseExecutionMode(value) {
|
|
18
|
+
return oneOf(value, "executionMode", EXECUTION_MODES);
|
|
19
|
+
}
|
|
20
|
+
export function parseHost(value) {
|
|
21
|
+
return oneOf(value, "host", HOSTS);
|
|
22
|
+
}
|
|
23
|
+
export function parseBackend(value) {
|
|
24
|
+
return oneOf(value, "backend", BACKENDS);
|
|
25
|
+
}
|
|
26
|
+
export function parseGate(value) {
|
|
27
|
+
return oneOf(value, "gate", GATES);
|
|
28
|
+
}
|
|
29
|
+
export function parseArtifactName(value) {
|
|
30
|
+
return oneOf(value, "name", ARTIFACTS);
|
|
31
|
+
}
|
|
32
|
+
export function parseCwd(value) {
|
|
33
|
+
return assertString(value, "cwd");
|
|
34
|
+
}
|
|
35
|
+
export function parseRunId(value) {
|
|
36
|
+
return assertString(value, "runId");
|
|
37
|
+
}
|
|
38
|
+
export function parseRequest(value) {
|
|
39
|
+
return assertString(value, "request");
|
|
40
|
+
}
|
|
41
|
+
export function parseOptionalNote(value) {
|
|
42
|
+
if (value === undefined || value === null || value === "") {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return assertString(value, "note");
|
|
46
|
+
}
|
|
47
|
+
export function parseFeedback(value) {
|
|
48
|
+
return assertString(value, "feedback");
|
|
49
|
+
}
|
|
50
|
+
export function parseAnswer(value) {
|
|
51
|
+
return assertString(value, "answer");
|
|
52
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
async function readIfExists(path) {
|
|
4
|
+
try {
|
|
5
|
+
await access(path);
|
|
6
|
+
return readFile(path, "utf8");
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function exists(path) {
|
|
13
|
+
try {
|
|
14
|
+
await access(path);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function packageVerifyCommands(cwd) {
|
|
22
|
+
const raw = await readIfExists(join(cwd, "package.json"));
|
|
23
|
+
if (!raw) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
const scripts = parsed.scripts ?? {};
|
|
29
|
+
if (scripts.validate) {
|
|
30
|
+
return ["npm run validate"];
|
|
31
|
+
}
|
|
32
|
+
if (scripts.test) {
|
|
33
|
+
return ["npm test"];
|
|
34
|
+
}
|
|
35
|
+
const commands = [];
|
|
36
|
+
if (scripts.typecheck) {
|
|
37
|
+
commands.push("npm run typecheck");
|
|
38
|
+
}
|
|
39
|
+
if (scripts.lint) {
|
|
40
|
+
commands.push("npm run lint");
|
|
41
|
+
}
|
|
42
|
+
return commands;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function pythonVerifyCommands(cwd) {
|
|
49
|
+
const pyproject = await readIfExists(join(cwd, "pyproject.toml"));
|
|
50
|
+
if (!pyproject) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
if (pyproject.includes("[tool.pytest") || pyproject.includes("pytest")) {
|
|
54
|
+
return ["python -m pytest"];
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
export async function discoverVerifyCommands(cwd) {
|
|
59
|
+
const packageCommands = await packageVerifyCommands(cwd);
|
|
60
|
+
if (packageCommands.length > 0) {
|
|
61
|
+
return packageCommands;
|
|
62
|
+
}
|
|
63
|
+
if (await exists(join(cwd, "go.mod"))) {
|
|
64
|
+
return ["go test ./..."];
|
|
65
|
+
}
|
|
66
|
+
if (await exists(join(cwd, "Cargo.toml"))) {
|
|
67
|
+
return ["cargo test"];
|
|
68
|
+
}
|
|
69
|
+
return pythonVerifyCommands(cwd);
|
|
70
|
+
}
|
|
71
|
+
export async function readPackageJson(cwd) {
|
|
72
|
+
const raw = await readIfExists(join(cwd, "package.json"));
|
|
73
|
+
if (!raw) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(raw);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function packageLintCommands(cwd) {
|
|
84
|
+
const parsed = await readPackageJson(cwd);
|
|
85
|
+
if (!parsed) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const scripts = parsed.scripts ?? {};
|
|
89
|
+
const commands = [];
|
|
90
|
+
if (scripts.typecheck) {
|
|
91
|
+
commands.push("npm run typecheck");
|
|
92
|
+
}
|
|
93
|
+
if (scripts.lint) {
|
|
94
|
+
commands.push("npm run lint");
|
|
95
|
+
}
|
|
96
|
+
if (scripts["format:check"]) {
|
|
97
|
+
commands.push("npm run format:check");
|
|
98
|
+
}
|
|
99
|
+
return commands;
|
|
100
|
+
}
|
|
101
|
+
export async function discoverLintCommands(cwd) {
|
|
102
|
+
const packageCommands = await packageLintCommands(cwd);
|
|
103
|
+
if (packageCommands.length > 0) {
|
|
104
|
+
return packageCommands;
|
|
105
|
+
}
|
|
106
|
+
const pyproject = await readIfExists(join(cwd, "pyproject.toml"));
|
|
107
|
+
if (pyproject) {
|
|
108
|
+
const commands = [];
|
|
109
|
+
if (pyproject.includes("ruff")) {
|
|
110
|
+
commands.push("ruff check .");
|
|
111
|
+
}
|
|
112
|
+
if (pyproject.includes("black")) {
|
|
113
|
+
commands.push("black --check .");
|
|
114
|
+
}
|
|
115
|
+
if (commands.length > 0) {
|
|
116
|
+
return commands;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (await exists(join(cwd, "go.mod"))) {
|
|
120
|
+
return ["files=$(gofmt -l .) && test -z \"$files\" || { printf '%s\\n' \"$files\"; exit 1; }", "go vet ./..."];
|
|
121
|
+
}
|
|
122
|
+
if (await exists(join(cwd, "Cargo.toml"))) {
|
|
123
|
+
return ["cargo fmt --check", "cargo clippy"];
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
async function packageCoverageCommands(cwd) {
|
|
128
|
+
const parsed = await readPackageJson(cwd);
|
|
129
|
+
if (!parsed) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const scripts = parsed.scripts ?? {};
|
|
133
|
+
if (scripts.coverage) {
|
|
134
|
+
return ["npm run coverage"];
|
|
135
|
+
}
|
|
136
|
+
const deps = { ...(parsed.dependencies ?? {}), ...(parsed.devDependencies ?? {}) };
|
|
137
|
+
const testScript = scripts.test ?? "";
|
|
138
|
+
const usesCoverageRunner = "jest" in deps || "vitest" in deps || testScript.includes("jest") || testScript.includes("vitest");
|
|
139
|
+
if (scripts.test && usesCoverageRunner) {
|
|
140
|
+
return ["npm test -- --coverage"];
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
export async function discoverCoverageCommands(cwd) {
|
|
145
|
+
const packageCommands = await packageCoverageCommands(cwd);
|
|
146
|
+
if (packageCommands.length > 0) {
|
|
147
|
+
return packageCommands;
|
|
148
|
+
}
|
|
149
|
+
const pyproject = await readIfExists(join(cwd, "pyproject.toml"));
|
|
150
|
+
if (pyproject && (pyproject.includes("[tool.pytest") || pyproject.includes("pytest"))) {
|
|
151
|
+
return ["python -m pytest --cov"];
|
|
152
|
+
}
|
|
153
|
+
if (await exists(join(cwd, "go.mod"))) {
|
|
154
|
+
return ["go test -cover ./..."];
|
|
155
|
+
}
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ensureConfig } from "./config.js";
|
|
3
|
+
import { readArtifact, writeArtifact } from "./artifacts.js";
|
|
4
|
+
import { discoverStandards } from "./standards.js";
|
|
5
|
+
import { loadState, saveState } from "./store.js";
|
|
6
|
+
import { parseAnswer, parseArtifactName, parseBackend, parseCwd, parseExecutionMode, parseFeedback, parseGate, parseHost, parseOptionalNote, parseRequest, parseRunId, parseWorkflowMode, } from "./validation.js";
|
|
7
|
+
function now() {
|
|
8
|
+
return new Date().toISOString();
|
|
9
|
+
}
|
|
10
|
+
function newRunId() {
|
|
11
|
+
return `dc-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
12
|
+
}
|
|
13
|
+
export function nextPhaseAfterGate(gate) {
|
|
14
|
+
const nextByGate = {
|
|
15
|
+
requirements: "architecture",
|
|
16
|
+
architecture: "implementation",
|
|
17
|
+
implementation: "testing",
|
|
18
|
+
testing: "acceptance",
|
|
19
|
+
};
|
|
20
|
+
return nextByGate[gate];
|
|
21
|
+
}
|
|
22
|
+
export function artifactForPhase(phase) {
|
|
23
|
+
const artifactByPhase = {
|
|
24
|
+
requirements: "requirements",
|
|
25
|
+
architecture: "architecture",
|
|
26
|
+
implementation: "implementation-plan",
|
|
27
|
+
testing: "test-report",
|
|
28
|
+
acceptance: "acceptance",
|
|
29
|
+
complete: "acceptance",
|
|
30
|
+
};
|
|
31
|
+
return artifactByPhase[phase];
|
|
32
|
+
}
|
|
33
|
+
export function gateForPhase(phase) {
|
|
34
|
+
if (phase === "requirements" || phase === "architecture" || phase === "implementation" || phase === "testing") {
|
|
35
|
+
return phase;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function assertCurrentGate(state, gate) {
|
|
40
|
+
const expected = gateForPhase(state.phase);
|
|
41
|
+
if (expected && expected !== gate) {
|
|
42
|
+
throw new Error(`Cannot act on ${gate} while current gate is ${expected}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function writeCurrentArtifact(state) {
|
|
46
|
+
const artifact = artifactForPhase(state.phase);
|
|
47
|
+
const path = await writeArtifact(artifact, state);
|
|
48
|
+
state.artifacts[artifact] = path;
|
|
49
|
+
return state;
|
|
50
|
+
}
|
|
51
|
+
export async function startWorkflow(input, options = {}) {
|
|
52
|
+
const cwd = parseCwd(input.cwd);
|
|
53
|
+
const host = parseHost(input.host);
|
|
54
|
+
const mode = parseWorkflowMode(input.mode);
|
|
55
|
+
const request = parseRequest(input.request);
|
|
56
|
+
const config = await ensureConfig(cwd);
|
|
57
|
+
const backend = input.backend ? parseBackend(input.backend) : config.defaultBackend === "host-preferred" ? host : config.defaultBackend;
|
|
58
|
+
const executionMode = input.executionMode ? parseExecutionMode(input.executionMode) : config.executionMode;
|
|
59
|
+
const createdAt = now();
|
|
60
|
+
const state = {
|
|
61
|
+
version: 1,
|
|
62
|
+
runId: newRunId(),
|
|
63
|
+
cwd,
|
|
64
|
+
host,
|
|
65
|
+
mode,
|
|
66
|
+
executionMode,
|
|
67
|
+
backend,
|
|
68
|
+
request,
|
|
69
|
+
phase: "requirements",
|
|
70
|
+
status: "awaiting_approval",
|
|
71
|
+
createdAt,
|
|
72
|
+
updatedAt: createdAt,
|
|
73
|
+
gates: {
|
|
74
|
+
requirements: "pending",
|
|
75
|
+
architecture: "not_started",
|
|
76
|
+
implementation: "not_started",
|
|
77
|
+
testing: "not_started",
|
|
78
|
+
},
|
|
79
|
+
artifacts: {},
|
|
80
|
+
roles: [],
|
|
81
|
+
answers: [],
|
|
82
|
+
approvals: [],
|
|
83
|
+
feedback: [],
|
|
84
|
+
standards: await discoverStandards(cwd),
|
|
85
|
+
changedFiles: [],
|
|
86
|
+
implementationDiff: "",
|
|
87
|
+
verification: [],
|
|
88
|
+
lintResults: [],
|
|
89
|
+
};
|
|
90
|
+
if (!options.skipArtifactWrite) {
|
|
91
|
+
await writeCurrentArtifact(state);
|
|
92
|
+
}
|
|
93
|
+
return saveState(state);
|
|
94
|
+
}
|
|
95
|
+
export async function getWorkflowStatus(input) {
|
|
96
|
+
return loadState(parseCwd(input.cwd), parseRunId(input.runId));
|
|
97
|
+
}
|
|
98
|
+
export async function continueWorkflow(input) {
|
|
99
|
+
const state = await getWorkflowStatus(input);
|
|
100
|
+
if (state.status === "awaiting_approval" || state.status === "awaiting_input" || state.status === "complete") {
|
|
101
|
+
return state;
|
|
102
|
+
}
|
|
103
|
+
if (state.phase === "acceptance") {
|
|
104
|
+
await writeCurrentArtifact(state);
|
|
105
|
+
state.phase = "complete";
|
|
106
|
+
state.status = "complete";
|
|
107
|
+
return saveState(state);
|
|
108
|
+
}
|
|
109
|
+
const gate = gateForPhase(state.phase);
|
|
110
|
+
if (!gate) {
|
|
111
|
+
state.status = "complete";
|
|
112
|
+
return saveState(state);
|
|
113
|
+
}
|
|
114
|
+
state.gates[gate] = "pending";
|
|
115
|
+
state.status = "awaiting_approval";
|
|
116
|
+
await writeCurrentArtifact(state);
|
|
117
|
+
return saveState(state);
|
|
118
|
+
}
|
|
119
|
+
export async function approveWorkflow(input) {
|
|
120
|
+
const state = await getWorkflowStatus(input);
|
|
121
|
+
const gate = parseGate(input.gate);
|
|
122
|
+
assertCurrentGate(state, gate);
|
|
123
|
+
state.gates[gate] = "approved";
|
|
124
|
+
state.approvals.push({
|
|
125
|
+
gate,
|
|
126
|
+
note: parseOptionalNote(input.note),
|
|
127
|
+
createdAt: now(),
|
|
128
|
+
});
|
|
129
|
+
const nextPhase = nextPhaseAfterGate(gate);
|
|
130
|
+
state.phase = nextPhase;
|
|
131
|
+
state.status = "ready";
|
|
132
|
+
return saveState(state);
|
|
133
|
+
}
|
|
134
|
+
export async function rejectWorkflow(input) {
|
|
135
|
+
const state = await getWorkflowStatus(input);
|
|
136
|
+
const gate = parseGate(input.gate);
|
|
137
|
+
assertCurrentGate(state, gate);
|
|
138
|
+
state.gates[gate] = "rejected";
|
|
139
|
+
state.status = "awaiting_input";
|
|
140
|
+
state.feedback.push({
|
|
141
|
+
gate,
|
|
142
|
+
message: parseFeedback(input.feedback),
|
|
143
|
+
createdAt: now(),
|
|
144
|
+
});
|
|
145
|
+
return saveState(state);
|
|
146
|
+
}
|
|
147
|
+
export async function answerWorkflow(input, options = {}) {
|
|
148
|
+
const state = await getWorkflowStatus(input);
|
|
149
|
+
state.answers.push({
|
|
150
|
+
answer: parseAnswer(input.answer),
|
|
151
|
+
createdAt: now(),
|
|
152
|
+
});
|
|
153
|
+
const gate = gateForPhase(state.phase);
|
|
154
|
+
if (gate) {
|
|
155
|
+
state.gates[gate] = "pending";
|
|
156
|
+
state.status = "awaiting_approval";
|
|
157
|
+
}
|
|
158
|
+
if (!options.skipArtifactWrite) {
|
|
159
|
+
await writeCurrentArtifact(state);
|
|
160
|
+
}
|
|
161
|
+
return saveState(state);
|
|
162
|
+
}
|
|
163
|
+
export async function getArtifact(input) {
|
|
164
|
+
const state = await getWorkflowStatus(input);
|
|
165
|
+
return readArtifact(state, parseArtifactName(input.name));
|
|
166
|
+
}
|