work-kit-cli 0.2.7 → 0.3.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/README.md +13 -13
- package/cli/src/commands/bootstrap.ts +39 -13
- package/cli/src/commands/cancel.ts +1 -16
- package/cli/src/commands/complete.ts +92 -98
- package/cli/src/commands/completions.ts +2 -2
- package/cli/src/commands/doctor.ts +1 -1
- package/cli/src/commands/init.ts +40 -32
- package/cli/src/commands/loopback.ts +8 -11
- package/cli/src/commands/next.ts +64 -51
- package/cli/src/commands/pause-resume.test.ts +142 -0
- package/cli/src/commands/pause.ts +34 -0
- package/cli/src/commands/report.ts +217 -0
- package/cli/src/commands/resume.ts +38 -0
- package/cli/src/commands/setup.ts +136 -0
- package/cli/src/commands/status.ts +6 -6
- package/cli/src/commands/uninstall.ts +8 -3
- package/cli/src/commands/workflow.ts +27 -27
- package/cli/src/config/agent-map.ts +9 -9
- package/cli/src/config/constants.ts +44 -0
- package/cli/src/config/loopback-routes.ts +13 -13
- package/cli/src/config/project-config.test.ts +127 -0
- package/cli/src/config/project-config.ts +106 -0
- package/cli/src/config/{phases.ts → workflow.ts} +40 -23
- package/cli/src/context/prompt-builder.ts +10 -9
- package/cli/src/index.ts +63 -7
- package/cli/src/observer/data.ts +64 -56
- package/cli/src/observer/renderer.ts +162 -75
- package/cli/src/state/helpers.test.ts +28 -28
- package/cli/src/state/helpers.ts +37 -25
- package/cli/src/state/schema.ts +88 -45
- package/cli/src/state/store.ts +92 -7
- package/cli/src/state/validators.test.ts +13 -13
- package/cli/src/state/validators.ts +3 -4
- package/cli/src/utils/colors.ts +2 -0
- package/cli/src/utils/json.ts +20 -0
- package/cli/src/utils/time.ts +27 -0
- package/cli/src/{engine → workflow}/loopbacks.test.ts +2 -2
- package/cli/src/workflow/loopbacks.ts +42 -0
- package/cli/src/workflow/parallel.ts +64 -0
- package/cli/src/workflow/transitions.test.ts +129 -0
- package/cli/src/{engine → workflow}/transitions.ts +18 -22
- package/package.json +2 -2
- package/skills/auto-kit/SKILL.md +22 -22
- package/skills/cancel-kit/SKILL.md +4 -4
- package/skills/full-kit/SKILL.md +23 -23
- package/skills/pause-kit/SKILL.md +25 -0
- package/skills/resume-kit/SKILL.md +28 -0
- package/skills/wk-bootstrap/SKILL.md +5 -5
- package/skills/wk-build/SKILL.md +10 -10
- package/skills/wk-build/{stages → steps}/commit.md +1 -1
- package/skills/wk-build/{stages → steps}/core.md +3 -3
- package/skills/wk-build/{stages → steps}/integration.md +2 -2
- package/skills/wk-build/{stages → steps}/migration.md +1 -1
- package/skills/wk-build/{stages → steps}/red.md +1 -1
- package/skills/wk-build/{stages → steps}/refactor.md +1 -1
- package/skills/wk-build/{stages → steps}/setup.md +1 -1
- package/skills/wk-build/{stages → steps}/ui.md +1 -1
- package/skills/wk-deploy/SKILL.md +6 -6
- package/skills/wk-deploy/{stages → steps}/merge.md +1 -1
- package/skills/wk-deploy/{stages → steps}/monitor.md +1 -1
- package/skills/wk-deploy/{stages → steps}/remediate.md +1 -1
- package/skills/wk-plan/SKILL.md +13 -13
- package/skills/wk-plan/{stages → steps}/architecture.md +1 -1
- package/skills/wk-plan/{stages → steps}/audit.md +2 -2
- package/skills/wk-plan/{stages → steps}/blueprint.md +2 -2
- package/skills/wk-plan/{stages → steps}/clarify.md +1 -1
- package/skills/wk-plan/{stages → steps}/investigate.md +1 -1
- package/skills/wk-plan/{stages → steps}/scope.md +1 -1
- package/skills/wk-plan/{stages → steps}/sketch.md +1 -1
- package/skills/wk-plan/{stages → steps}/ux-flow.md +1 -1
- package/skills/wk-review/SKILL.md +10 -10
- package/skills/wk-review/{stages → steps}/compliance.md +1 -1
- package/skills/wk-review/{stages → steps}/handoff.md +2 -2
- package/skills/wk-review/{stages → steps}/performance.md +1 -1
- package/skills/wk-review/{stages → steps}/security.md +1 -1
- package/skills/wk-review/{stages → steps}/self-review.md +1 -1
- package/skills/wk-test/SKILL.md +8 -8
- package/skills/wk-test/{stages → steps}/e2e.md +1 -1
- package/skills/wk-test/{stages → steps}/validate.md +1 -1
- package/skills/wk-test/{stages → steps}/verify.md +1 -1
- package/skills/wk-wrap-up/SKILL.md +6 -5
- package/skills/wk-wrap-up/steps/summary.md +86 -0
- package/cli/src/engine/loopbacks.ts +0 -32
- package/cli/src/engine/parallel.ts +0 -60
- package/cli/src/engine/transitions.test.ts +0 -129
- /package/cli/src/{engine/phases.ts → workflow/gates.ts} +0 -0
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Location, StepOutcome } from "../state/schema.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Defines when a completed
|
|
4
|
+
* Defines when a completed step should trigger a loop-back
|
|
5
5
|
* based on its outcome.
|
|
6
6
|
*/
|
|
7
7
|
export interface LoopbackRoute {
|
|
8
8
|
from: Location;
|
|
9
|
-
triggerOutcome:
|
|
9
|
+
triggerOutcome: StepOutcome;
|
|
10
10
|
to: Location;
|
|
11
11
|
reason: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export const LOOPBACK_ROUTES: LoopbackRoute[] = [
|
|
15
15
|
{
|
|
16
|
-
from: { phase: "plan",
|
|
16
|
+
from: { phase: "plan", step: "audit" },
|
|
17
17
|
triggerOutcome: "revise",
|
|
18
|
-
to: { phase: "plan",
|
|
18
|
+
to: { phase: "plan", step: "blueprint" },
|
|
19
19
|
reason: "Audit found gaps — revising Blueprint",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
-
from: { phase: "build",
|
|
22
|
+
from: { phase: "build", step: "refactor" },
|
|
23
23
|
triggerOutcome: "broken",
|
|
24
|
-
to: { phase: "build",
|
|
24
|
+
to: { phase: "build", step: "core" },
|
|
25
25
|
reason: "Refactor broke tests — re-running Core to fix",
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
from: { phase: "review",
|
|
28
|
+
from: { phase: "review", step: "handoff" },
|
|
29
29
|
triggerOutcome: "changes_requested",
|
|
30
|
-
to: { phase: "build",
|
|
30
|
+
to: { phase: "build", step: "core" },
|
|
31
31
|
reason: "Review requested changes — looping back to Build/Core",
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
|
-
from: { phase: "deploy",
|
|
34
|
+
from: { phase: "deploy", step: "merge" },
|
|
35
35
|
triggerOutcome: "fix_needed",
|
|
36
|
-
to: { phase: "build",
|
|
36
|
+
to: { phase: "build", step: "core" },
|
|
37
37
|
reason: "Merge blocked — fix needed in Build/Core",
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
from: { phase: "deploy",
|
|
40
|
+
from: { phase: "deploy", step: "remediate" },
|
|
41
41
|
triggerOutcome: "fix_and_redeploy",
|
|
42
|
-
to: { phase: "build",
|
|
42
|
+
to: { phase: "build", step: "core" },
|
|
43
43
|
reason: "Deployment issue — fix and redeploy from Build/Core",
|
|
44
44
|
},
|
|
45
45
|
];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, afterEach } from "node:test";
|
|
2
|
+
import * as assert from "node:assert/strict";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { loadProjectConfig } from "./project-config.js";
|
|
8
|
+
import { resolveParallelGroups, DEFAULT_PARALLEL_GROUPS } from "../workflow/parallel.js";
|
|
9
|
+
import { PROJECT_CONFIG_FILE } from "./constants.js";
|
|
10
|
+
|
|
11
|
+
function makeTmpDir(): string {
|
|
12
|
+
const dir = path.join(os.tmpdir(), `wk-config-${randomUUID()}`);
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
return dir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let tmpDirs: string[] = [];
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
for (const dir of tmpDirs) fs.rmSync(dir, { recursive: true, force: true });
|
|
21
|
+
tmpDirs = [];
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("loadProjectConfig", () => {
|
|
25
|
+
it("returns empty config when file is missing", () => {
|
|
26
|
+
const tmp = makeTmpDir();
|
|
27
|
+
tmpDirs.push(tmp);
|
|
28
|
+
const config = loadProjectConfig(tmp);
|
|
29
|
+
assert.deepStrictEqual(config, {});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("loads valid defaults", () => {
|
|
33
|
+
const tmp = makeTmpDir();
|
|
34
|
+
tmpDirs.push(tmp);
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
37
|
+
JSON.stringify({ defaults: { mode: "auto", classification: "feature", gated: true } }),
|
|
38
|
+
);
|
|
39
|
+
const config = loadProjectConfig(tmp);
|
|
40
|
+
assert.equal(config.defaults?.mode, "auto");
|
|
41
|
+
assert.equal(config.defaults?.classification, "feature");
|
|
42
|
+
assert.equal(config.defaults?.gated, true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("ignores invalid mode and classification", () => {
|
|
46
|
+
const tmp = makeTmpDir();
|
|
47
|
+
tmpDirs.push(tmp);
|
|
48
|
+
fs.writeFileSync(
|
|
49
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
50
|
+
JSON.stringify({ defaults: { mode: "wat", classification: "nope" } }),
|
|
51
|
+
);
|
|
52
|
+
const config = loadProjectConfig(tmp);
|
|
53
|
+
assert.equal(config.defaults?.mode, undefined);
|
|
54
|
+
assert.equal(config.defaults?.classification, undefined);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("loads parallel group overrides", () => {
|
|
58
|
+
const tmp = makeTmpDir();
|
|
59
|
+
tmpDirs.push(tmp);
|
|
60
|
+
fs.writeFileSync(
|
|
61
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
62
|
+
JSON.stringify({
|
|
63
|
+
parallel: {
|
|
64
|
+
test: { parallel: ["verify"], thenSequential: "validate" },
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
const config = loadProjectConfig(tmp);
|
|
69
|
+
assert.deepStrictEqual(config.parallel?.test, { parallel: ["verify"], thenSequential: "validate" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("filters invalid step names from parallel overrides", () => {
|
|
73
|
+
const tmp = makeTmpDir();
|
|
74
|
+
tmpDirs.push(tmp);
|
|
75
|
+
fs.writeFileSync(
|
|
76
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
77
|
+
JSON.stringify({
|
|
78
|
+
parallel: { test: { parallel: ["verify", "made-up"] } },
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
const config = loadProjectConfig(tmp);
|
|
82
|
+
assert.deepStrictEqual(config.parallel?.test?.parallel, ["verify"]);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("validates workflow include/exclude refs", () => {
|
|
86
|
+
const tmp = makeTmpDir();
|
|
87
|
+
tmpDirs.push(tmp);
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
workflow: { include: ["review/security", "bogus/step"], exclude: ["test/e2e"] },
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
const config = loadProjectConfig(tmp);
|
|
95
|
+
assert.deepStrictEqual(config.workflow?.include, ["review/security"]);
|
|
96
|
+
assert.deepStrictEqual(config.workflow?.exclude, ["test/e2e"]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("survives invalid JSON", () => {
|
|
100
|
+
const tmp = makeTmpDir();
|
|
101
|
+
tmpDirs.push(tmp);
|
|
102
|
+
fs.writeFileSync(path.join(tmp, PROJECT_CONFIG_FILE), "not json");
|
|
103
|
+
const config = loadProjectConfig(tmp);
|
|
104
|
+
assert.deepStrictEqual(config, {});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("resolveParallelGroups", () => {
|
|
109
|
+
it("returns defaults with no project root", () => {
|
|
110
|
+
assert.deepStrictEqual(resolveParallelGroups(), DEFAULT_PARALLEL_GROUPS);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("merges project overrides over defaults", () => {
|
|
114
|
+
const tmp = makeTmpDir();
|
|
115
|
+
tmpDirs.push(tmp);
|
|
116
|
+
fs.writeFileSync(
|
|
117
|
+
path.join(tmp, PROJECT_CONFIG_FILE),
|
|
118
|
+
JSON.stringify({
|
|
119
|
+
parallel: { test: { parallel: ["verify"], thenSequential: "validate" } },
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
const groups = resolveParallelGroups(tmp);
|
|
123
|
+
assert.deepStrictEqual(groups.test, { parallel: ["verify"], thenSequential: "validate" });
|
|
124
|
+
// review (not overridden) keeps default
|
|
125
|
+
assert.deepStrictEqual(groups.review, DEFAULT_PARALLEL_GROUPS.review);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
PHASE_NAMES,
|
|
4
|
+
STEPS_BY_PHASE,
|
|
5
|
+
isClassification,
|
|
6
|
+
type PhaseName,
|
|
7
|
+
type Classification,
|
|
8
|
+
} from "../state/schema.js";
|
|
9
|
+
import { readJsonFile } from "../utils/json.js";
|
|
10
|
+
import { PROJECT_CONFIG_FILE } from "./constants.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Optional `.work-kit-config.json` lives at the main repo root.
|
|
14
|
+
*
|
|
15
|
+
* Example:
|
|
16
|
+
* {
|
|
17
|
+
* "defaults": { "mode": "auto", "classification": "feature", "gated": false },
|
|
18
|
+
* "parallel": {
|
|
19
|
+
* "review": { "parallel": ["self-review", "security"], "thenSequential": "handoff" }
|
|
20
|
+
* },
|
|
21
|
+
* "workflow": {
|
|
22
|
+
* "include": ["review/security"],
|
|
23
|
+
* "exclude": ["review/performance"]
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*/
|
|
27
|
+
export interface ProjectParallelGroup {
|
|
28
|
+
parallel: string[];
|
|
29
|
+
thenSequential?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ProjectConfig {
|
|
33
|
+
defaults?: {
|
|
34
|
+
mode?: "full" | "auto";
|
|
35
|
+
classification?: Classification;
|
|
36
|
+
gated?: boolean;
|
|
37
|
+
};
|
|
38
|
+
/** Override or extend the per-phase parallel groups. */
|
|
39
|
+
parallel?: Partial<Record<PhaseName, ProjectParallelGroup>>;
|
|
40
|
+
/** Force-include or force-exclude specific steps (phase/step). */
|
|
41
|
+
workflow?: {
|
|
42
|
+
include?: string[];
|
|
43
|
+
exclude?: string[];
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const EMPTY_CONFIG: ProjectConfig = {};
|
|
48
|
+
|
|
49
|
+
/** Load and validate the project config. Returns empty config when missing/invalid. */
|
|
50
|
+
export function loadProjectConfig(mainRepoRoot: string): ProjectConfig {
|
|
51
|
+
const parsed = readJsonFile<unknown>(path.join(mainRepoRoot, PROJECT_CONFIG_FILE));
|
|
52
|
+
if (!parsed) return EMPTY_CONFIG;
|
|
53
|
+
return validateConfig(parsed);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateConfig(raw: any): ProjectConfig {
|
|
57
|
+
const out: ProjectConfig = {};
|
|
58
|
+
|
|
59
|
+
if (raw && typeof raw === "object") {
|
|
60
|
+
if (raw.defaults && typeof raw.defaults === "object") {
|
|
61
|
+
const d = raw.defaults;
|
|
62
|
+
out.defaults = {};
|
|
63
|
+
if (d.mode === "full" || d.mode === "auto") out.defaults.mode = d.mode;
|
|
64
|
+
if (typeof d.classification === "string" && isClassification(d.classification)) {
|
|
65
|
+
out.defaults.classification = d.classification;
|
|
66
|
+
}
|
|
67
|
+
if (typeof d.gated === "boolean") out.defaults.gated = d.gated;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (raw.parallel && typeof raw.parallel === "object") {
|
|
71
|
+
out.parallel = {};
|
|
72
|
+
for (const [phase, group] of Object.entries(raw.parallel)) {
|
|
73
|
+
if (!(PHASE_NAMES as readonly string[]).includes(phase)) continue;
|
|
74
|
+
const g = group as any;
|
|
75
|
+
if (!g || !Array.isArray(g.parallel)) continue;
|
|
76
|
+
const validSteps = STEPS_BY_PHASE[phase as PhaseName];
|
|
77
|
+
const parallel = g.parallel.filter((s: any) => typeof s === "string" && validSteps.includes(s));
|
|
78
|
+
if (parallel.length === 0) continue;
|
|
79
|
+
const entry: ProjectParallelGroup = { parallel };
|
|
80
|
+
if (typeof g.thenSequential === "string" && validSteps.includes(g.thenSequential)) {
|
|
81
|
+
entry.thenSequential = g.thenSequential;
|
|
82
|
+
}
|
|
83
|
+
out.parallel[phase as PhaseName] = entry;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (raw.workflow && typeof raw.workflow === "object") {
|
|
88
|
+
out.workflow = {};
|
|
89
|
+
for (const key of ["include", "exclude"] as const) {
|
|
90
|
+
if (Array.isArray(raw.workflow[key])) {
|
|
91
|
+
out.workflow[key] = raw.workflow[key].filter((s: any) => isValidStepRef(s));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isValidStepRef(ref: any): boolean {
|
|
101
|
+
if (typeof ref !== "string") return false;
|
|
102
|
+
const [phase, step] = ref.split("/");
|
|
103
|
+
if (!phase || !step) return false;
|
|
104
|
+
if (!(PHASE_NAMES as readonly string[]).includes(phase)) return false;
|
|
105
|
+
return STEPS_BY_PHASE[phase as PhaseName].includes(step);
|
|
106
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PhaseName,
|
|
3
|
-
|
|
3
|
+
PHASE_NAMES,
|
|
4
|
+
STEPS_BY_PHASE,
|
|
4
5
|
Classification,
|
|
5
6
|
WorkflowStep,
|
|
6
7
|
} from "../state/schema.js";
|
|
8
|
+
import { SKILL_DIR_PREFIX } from "./constants.js";
|
|
7
9
|
|
|
8
|
-
// ── Phase Order
|
|
10
|
+
// ── Phase Order ─────────────────────────────────────────────────────
|
|
9
11
|
|
|
10
|
-
export const PHASE_ORDER: PhaseName[] = [
|
|
12
|
+
export const PHASE_ORDER: PhaseName[] = [...PHASE_NAMES];
|
|
11
13
|
|
|
12
|
-
// ── Prerequisites
|
|
14
|
+
// ── Prerequisites ───────────────────────────────────────────────────
|
|
13
15
|
|
|
14
16
|
export const PHASE_PREREQUISITES: Record<PhaseName, PhaseName | null> = {
|
|
15
17
|
plan: null,
|
|
@@ -20,15 +22,15 @@ export const PHASE_PREREQUISITES: Record<PhaseName, PhaseName | null> = {
|
|
|
20
22
|
"wrap-up": "review", // or deploy if deploy was included
|
|
21
23
|
};
|
|
22
24
|
|
|
23
|
-
// ── Skill File Paths
|
|
25
|
+
// ── Skill File Paths ────────────────────────────────────────────────
|
|
24
26
|
|
|
25
|
-
export function skillFilePath(phase: PhaseName,
|
|
26
|
-
const dir =
|
|
27
|
-
if (!
|
|
28
|
-
return `.claude/skills/${dir}/
|
|
27
|
+
export function skillFilePath(phase: PhaseName, step?: string): string {
|
|
28
|
+
const dir = `${SKILL_DIR_PREFIX}${phase}`;
|
|
29
|
+
if (!step) return `.claude/skills/${dir}/SKILL.md`;
|
|
30
|
+
return `.claude/skills/${dir}/steps/${step}.md`;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
// ── Auto-kit Default Workflows
|
|
33
|
+
// ── Auto-kit Default Workflows ──────────────────────────────────────
|
|
32
34
|
|
|
33
35
|
type InclusionRule = "YES" | "skip" | "if UI" | "if DB" | "optional";
|
|
34
36
|
|
|
@@ -42,7 +44,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
|
|
|
42
44
|
"review/self-review": "YES", "review/security": "skip", "review/performance": "skip",
|
|
43
45
|
"review/compliance": "skip", "review/handoff": "YES",
|
|
44
46
|
"deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
|
|
45
|
-
"wrap-up/
|
|
47
|
+
"wrap-up/summary": "YES",
|
|
46
48
|
},
|
|
47
49
|
"small-change": {
|
|
48
50
|
"plan/clarify": "YES", "plan/investigate": "skip", "plan/sketch": "skip", "plan/scope": "skip",
|
|
@@ -53,7 +55,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
|
|
|
53
55
|
"review/self-review": "YES", "review/security": "skip", "review/performance": "skip",
|
|
54
56
|
"review/compliance": "skip", "review/handoff": "YES",
|
|
55
57
|
"deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
|
|
56
|
-
"wrap-up/
|
|
58
|
+
"wrap-up/summary": "YES",
|
|
57
59
|
},
|
|
58
60
|
refactor: {
|
|
59
61
|
"plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "skip", "plan/scope": "skip",
|
|
@@ -64,7 +66,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
|
|
|
64
66
|
"review/self-review": "YES", "review/security": "skip", "review/performance": "YES",
|
|
65
67
|
"review/compliance": "skip", "review/handoff": "YES",
|
|
66
68
|
"deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
|
|
67
|
-
"wrap-up/
|
|
69
|
+
"wrap-up/summary": "YES",
|
|
68
70
|
},
|
|
69
71
|
feature: {
|
|
70
72
|
"plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "YES", "plan/scope": "YES",
|
|
@@ -75,7 +77,7 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
|
|
|
75
77
|
"review/self-review": "YES", "review/security": "YES", "review/performance": "skip",
|
|
76
78
|
"review/compliance": "YES", "review/handoff": "YES",
|
|
77
79
|
"deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
|
|
78
|
-
"wrap-up/
|
|
80
|
+
"wrap-up/summary": "YES",
|
|
79
81
|
},
|
|
80
82
|
"large-feature": {
|
|
81
83
|
"plan/clarify": "YES", "plan/investigate": "YES", "plan/sketch": "YES", "plan/scope": "YES",
|
|
@@ -86,20 +88,35 @@ const WORKFLOW_MATRIX: Record<Classification, Record<string, InclusionRule>> = {
|
|
|
86
88
|
"review/self-review": "YES", "review/security": "YES", "review/performance": "YES",
|
|
87
89
|
"review/compliance": "YES", "review/handoff": "YES",
|
|
88
90
|
"deploy/merge": "YES", "deploy/monitor": "optional", "deploy/remediate": "optional",
|
|
89
|
-
"wrap-up/
|
|
91
|
+
"wrap-up/summary": "YES",
|
|
90
92
|
},
|
|
91
93
|
};
|
|
92
94
|
|
|
93
|
-
export function buildDefaultWorkflow(
|
|
95
|
+
export function buildDefaultWorkflow(
|
|
96
|
+
classification: Classification,
|
|
97
|
+
overrides?: { include?: string[]; exclude?: string[] }
|
|
98
|
+
): WorkflowStep[] {
|
|
94
99
|
const matrix = WORKFLOW_MATRIX[classification];
|
|
95
100
|
const steps: WorkflowStep[] = [];
|
|
96
101
|
|
|
102
|
+
const forceInclude = new Set(overrides?.include ?? []);
|
|
103
|
+
const forceExclude = new Set(overrides?.exclude ?? []);
|
|
104
|
+
|
|
97
105
|
for (const [key, rule] of Object.entries(matrix)) {
|
|
98
|
-
const [phase,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
106
|
+
const [phase, step] = key.split("/") as [PhaseName, string];
|
|
107
|
+
let included = rule === "YES" || rule === "if UI" || rule === "if DB";
|
|
108
|
+
if (forceInclude.has(key)) included = true;
|
|
109
|
+
if (forceExclude.has(key)) included = false;
|
|
110
|
+
// "skip" excluded by default, but project config may force-include
|
|
111
|
+
if (rule === "skip" && !forceInclude.has(key)) continue;
|
|
112
|
+
steps.push({ phase, step, included });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add any force-included steps not in the matrix
|
|
116
|
+
for (const ref of forceInclude) {
|
|
117
|
+
const [phase, step] = ref.split("/") as [PhaseName, string];
|
|
118
|
+
if (!steps.some(s => s.phase === phase && s.step === step)) {
|
|
119
|
+
steps.push({ phase, step, included: true });
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
122
|
|
|
@@ -109,8 +126,8 @@ export function buildDefaultWorkflow(classification: Classification): WorkflowSt
|
|
|
109
126
|
export function buildFullWorkflow(): WorkflowStep[] {
|
|
110
127
|
const steps: WorkflowStep[] = [];
|
|
111
128
|
for (const phase of PHASE_ORDER) {
|
|
112
|
-
for (const
|
|
113
|
-
steps.push({ phase,
|
|
129
|
+
for (const step of STEPS_BY_PHASE[phase]) {
|
|
130
|
+
steps.push({ phase, step, included: true });
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
return steps;
|
|
@@ -2,27 +2,28 @@ import { WorkKitState, PhaseName } from "../state/schema.js";
|
|
|
2
2
|
import { getContextFor } from "../config/agent-map.js";
|
|
3
3
|
import { extractSection, extractTopSection } from "./extractor.js";
|
|
4
4
|
import { readStateMd } from "../state/store.js";
|
|
5
|
-
import { skillFilePath } from "../config/
|
|
5
|
+
import { skillFilePath } from "../config/workflow.js";
|
|
6
6
|
import { redactIgnoredBlocks } from "./redactor.js";
|
|
7
|
+
import { CLI_BINARY } from "../config/constants.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Build a complete agent prompt for a given phase/
|
|
10
|
+
* Build a complete agent prompt for a given phase/step.
|
|
10
11
|
* Accepts optional pre-read stateMd to avoid repeated file reads in parallel scenarios.
|
|
11
12
|
*/
|
|
12
13
|
export function buildAgentPrompt(
|
|
13
14
|
worktreeRoot: string,
|
|
14
15
|
state: WorkKitState,
|
|
15
16
|
phase: PhaseName,
|
|
16
|
-
|
|
17
|
+
step: string,
|
|
17
18
|
stateMd?: string | null
|
|
18
19
|
): string {
|
|
19
|
-
const ctx = getContextFor(phase,
|
|
20
|
+
const ctx = getContextFor(phase, step);
|
|
20
21
|
const md = stateMd ?? readStateMd(worktreeRoot);
|
|
21
|
-
const skill = skillFilePath(phase,
|
|
22
|
+
const skill = skillFilePath(phase, step);
|
|
22
23
|
|
|
23
24
|
const parts: string[] = [];
|
|
24
25
|
|
|
25
|
-
parts.push(`# Agent: ${phase}/${
|
|
26
|
+
parts.push(`# Agent: ${phase}/${step}`);
|
|
26
27
|
parts.push(`**Worktree:** ${worktreeRoot}`);
|
|
27
28
|
parts.push(`**Slug:** ${state.slug}`);
|
|
28
29
|
parts.push(`**Branch:** ${state.branch}`);
|
|
@@ -59,12 +60,12 @@ export function buildAgentPrompt(
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
parts.push(`## Output`);
|
|
62
|
-
parts.push(`Write your outputs to \`.work-kit/state.md\` under a section for this
|
|
63
|
+
parts.push(`Write your outputs to \`.work-kit/state.md\` under a section for this step.`);
|
|
63
64
|
|
|
64
|
-
if (
|
|
65
|
+
if (step === "wrap-up") {
|
|
65
66
|
parts.push(`Follow the wrap-up skill file instructions for archiving and cleanup.`);
|
|
66
67
|
} else {
|
|
67
|
-
parts.push(`When done, report your outcome so the orchestrator can run:
|
|
68
|
+
parts.push(`When done, report your outcome so the orchestrator can run: \`${CLI_BINARY} complete ${phase}/${step} --outcome <outcome>\``);
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
return redactIgnoredBlocks(parts.join("\n"));
|
package/cli/src/index.ts
CHANGED
|
@@ -17,6 +17,9 @@ import { observeCommand } from "./commands/observe.js";
|
|
|
17
17
|
import { uninstallCommand } from "./commands/uninstall.js";
|
|
18
18
|
import { bootstrapCommand } from "./commands/bootstrap.js";
|
|
19
19
|
import { cancelCommand } from "./commands/cancel.js";
|
|
20
|
+
import { pauseCommand } from "./commands/pause.js";
|
|
21
|
+
import { resumeCommand } from "./commands/resume.js";
|
|
22
|
+
import { reportCommand } from "./commands/report.js";
|
|
20
23
|
import { bold, green, yellow, red } from "./utils/colors.js";
|
|
21
24
|
import type { Classification, PhaseName } from "./state/schema.js";
|
|
22
25
|
|
|
@@ -37,7 +40,7 @@ program
|
|
|
37
40
|
program
|
|
38
41
|
.command("init")
|
|
39
42
|
.description("Create worktree and initialize state")
|
|
40
|
-
.
|
|
43
|
+
.option("--mode <mode>", "Workflow mode: full or auto (default: from project config or 'full')")
|
|
41
44
|
.requiredOption("--description <text>", "Description of the work")
|
|
42
45
|
.option("--classification <type>", "Work classification (auto mode): bug-fix, small-change, refactor, feature, large-feature")
|
|
43
46
|
.option("--gated", "Wait for user approval between phases (default: auto-proceed)")
|
|
@@ -45,10 +48,10 @@ program
|
|
|
45
48
|
.action((opts) => {
|
|
46
49
|
try {
|
|
47
50
|
const result = initCommand({
|
|
48
|
-
mode: opts.mode as "full" | "auto",
|
|
51
|
+
mode: opts.mode as "full" | "auto" | undefined,
|
|
49
52
|
description: opts.description,
|
|
50
53
|
classification: opts.classification as Classification | undefined,
|
|
51
|
-
gated: opts.gated
|
|
54
|
+
gated: opts.gated,
|
|
52
55
|
worktreeRoot: opts.worktreeRoot,
|
|
53
56
|
});
|
|
54
57
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -78,7 +81,7 @@ program
|
|
|
78
81
|
|
|
79
82
|
program
|
|
80
83
|
.command("complete <target>")
|
|
81
|
-
.description("Mark a phase/
|
|
84
|
+
.description("Mark a phase/step as complete (e.g., plan/clarify)")
|
|
82
85
|
.option("--outcome <value>", "Outcome of the step (e.g., done, revise, broken, changes_requested)")
|
|
83
86
|
.option("--worktree-root <path>", "Override worktree root")
|
|
84
87
|
.action((target, opts) => {
|
|
@@ -144,8 +147,8 @@ program
|
|
|
144
147
|
program
|
|
145
148
|
.command("loopback")
|
|
146
149
|
.description("Register a loop-back transition")
|
|
147
|
-
.requiredOption("--from <source>", "Source phase/
|
|
148
|
-
.requiredOption("--to <target>", "Target phase/
|
|
150
|
+
.requiredOption("--from <source>", "Source phase/step (e.g., review/handoff)")
|
|
151
|
+
.requiredOption("--to <target>", "Target phase/step (e.g., build/core)")
|
|
149
152
|
.requiredOption("--reason <text>", "Reason for loop-back")
|
|
150
153
|
.option("--worktree-root <path>", "Override worktree root")
|
|
151
154
|
.action((opts) => {
|
|
@@ -260,9 +263,10 @@ program
|
|
|
260
263
|
.command("bootstrap")
|
|
261
264
|
.description("Detect work-kit state and output session orientation")
|
|
262
265
|
.option("--json", "Output as JSON", true)
|
|
266
|
+
.option("--auto-resume", "If paused or stale, auto-flip to in-progress")
|
|
263
267
|
.action((opts) => {
|
|
264
268
|
try {
|
|
265
|
-
const result = bootstrapCommand();
|
|
269
|
+
const result = bootstrapCommand(undefined, { autoResume: opts.autoResume });
|
|
266
270
|
console.log(JSON.stringify(result, null, 2));
|
|
267
271
|
} catch (e: any) {
|
|
268
272
|
console.error(JSON.stringify({ action: "error", message: e.message }));
|
|
@@ -270,6 +274,58 @@ program
|
|
|
270
274
|
}
|
|
271
275
|
});
|
|
272
276
|
|
|
277
|
+
// ── pause ───────────────────────────────────────────────────────────
|
|
278
|
+
|
|
279
|
+
program
|
|
280
|
+
.command("pause")
|
|
281
|
+
.description("Pause the active work-kit session")
|
|
282
|
+
.option("--reason <text>", "Optional reason for pausing")
|
|
283
|
+
.option("--worktree-root <path>", "Override worktree root")
|
|
284
|
+
.action((opts) => {
|
|
285
|
+
try {
|
|
286
|
+
const result = pauseCommand(opts.reason, opts.worktreeRoot);
|
|
287
|
+
console.log(JSON.stringify(result, null, 2));
|
|
288
|
+
process.exit(result.action === "error" ? 1 : 0);
|
|
289
|
+
} catch (e: any) {
|
|
290
|
+
console.error(JSON.stringify({ action: "error", message: e.message }));
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ── resume ──────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
program
|
|
298
|
+
.command("resume")
|
|
299
|
+
.description("Resume a paused work-kit session")
|
|
300
|
+
.option("--worktree-root <path>", "Override worktree root")
|
|
301
|
+
.action((opts) => {
|
|
302
|
+
try {
|
|
303
|
+
const result = resumeCommand(opts.worktreeRoot);
|
|
304
|
+
console.log(JSON.stringify(result, null, 2));
|
|
305
|
+
process.exit(result.action === "error" ? 1 : 0);
|
|
306
|
+
} catch (e: any) {
|
|
307
|
+
console.error(JSON.stringify({ action: "error", message: e.message }));
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ── report ──────────────────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
program
|
|
315
|
+
.command("report")
|
|
316
|
+
.description("Show stats across completed work-kits")
|
|
317
|
+
.option("--json", "Output as JSON")
|
|
318
|
+
.option("--repo <path>", "Main repository root")
|
|
319
|
+
.option("--worktree-root <path>", "Override worktree root")
|
|
320
|
+
.action((opts) => {
|
|
321
|
+
try {
|
|
322
|
+
reportCommand({ json: opts.json, repo: opts.repo, worktreeRoot: opts.worktreeRoot });
|
|
323
|
+
} catch (e: any) {
|
|
324
|
+
console.error(JSON.stringify({ action: "error", message: e.message }));
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
273
329
|
// ── cancel ──────────────────────────────────────────────────────────
|
|
274
330
|
|
|
275
331
|
program
|