@agentic-coding-framework/orchestrator-core 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/dist/cli.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — CLI entry point for the Agentic Coding Orchestrator
4
+ *
5
+ * Commands:
6
+ * init <project-root> <project-name> Initialize .ai/STATE.json
7
+ * start-story <project-root> <story-id> Begin a new User Story (micro-waterfall)
8
+ * start-custom <project-root> <instruction> Begin a custom ad-hoc task
9
+ * dispatch <project-root> Dispatch next step (prints prompt)
10
+ * apply-handoff <project-root> Parse HANDOFF.md → update STATE
11
+ * approve <project-root> [note] Approve review step
12
+ * reject <project-root> <reason> [note] Reject review step
13
+ * status <project-root> Print current STATE.json
14
+ */
15
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * cli.ts — CLI entry point for the Agentic Coding Orchestrator
5
+ *
6
+ * Commands:
7
+ * init <project-root> <project-name> Initialize .ai/STATE.json
8
+ * start-story <project-root> <story-id> Begin a new User Story (micro-waterfall)
9
+ * start-custom <project-root> <instruction> Begin a custom ad-hoc task
10
+ * dispatch <project-root> Dispatch next step (prints prompt)
11
+ * apply-handoff <project-root> Parse HANDOFF.md → update STATE
12
+ * approve <project-root> [note] Approve review step
13
+ * reject <project-root> <reason> [note] Reject review step
14
+ * status <project-root> Print current STATE.json
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ const path_1 = require("path");
18
+ const child_process_1 = require("child_process");
19
+ const state_1 = require("./state");
20
+ const dispatch_1 = require("./dispatch");
21
+ const [, , command, ...args] = process.argv;
22
+ function usage() {
23
+ console.error(`Usage: orchestrator <command> [args]
24
+
25
+ Commands:
26
+ init <project-root> <project-name> Initialize .ai/STATE.json
27
+ start-story <project-root> <story-id> Begin a new User Story (micro-waterfall)
28
+ start-custom <project-root> <instruction> Begin a custom ad-hoc task
29
+ dispatch <project-root> Dispatch next step (prints prompt to stdout)
30
+ apply-handoff <project-root> Parse HANDOFF.md → update STATE.json
31
+ post-check <project-root> Run step's post_check command
32
+ approve <project-root> [note] Approve review step
33
+ reject <project-root> <reason> [note] Reject review step
34
+ status <project-root> Print current STATE.json
35
+ query <project-root> Project status summary (for OpenClaw)
36
+ detect <project-root> Check if project uses the framework
37
+ list-projects <workspace-root> List all projects in workspace
38
+ `);
39
+ process.exit(1);
40
+ }
41
+ function resolveRoot(raw) {
42
+ if (!raw) {
43
+ console.error("Error: <project-root> is required");
44
+ process.exit(1);
45
+ }
46
+ return (0, path_1.resolve)(raw);
47
+ }
48
+ try {
49
+ switch (command) {
50
+ case "init": {
51
+ const projectRoot = resolveRoot(args[0]);
52
+ const projectName = args[1];
53
+ if (!projectName) {
54
+ console.error("Error: <project-name> is required");
55
+ process.exit(1);
56
+ }
57
+ const { created, state } = (0, state_1.initState)(projectRoot, projectName);
58
+ if (created) {
59
+ console.log(`Initialized .ai/STATE.json for "${projectName}"`);
60
+ }
61
+ else {
62
+ console.log(`STATE.json already exists (step: ${state.step}, status: ${state.status})`);
63
+ }
64
+ break;
65
+ }
66
+ case "start-story": {
67
+ const projectRoot = resolveRoot(args[0]);
68
+ const storyId = args[1];
69
+ if (!storyId) {
70
+ console.error("Error: <story-id> is required");
71
+ process.exit(1);
72
+ }
73
+ const state = (0, dispatch_1.startStory)(projectRoot, storyId);
74
+ console.log(`Started story ${storyId} (step: ${state.step}, attempt: ${state.attempt})`);
75
+ break;
76
+ }
77
+ case "start-custom": {
78
+ const projectRoot = resolveRoot(args[0]);
79
+ const instruction = args[1];
80
+ if (!instruction) {
81
+ console.error("Error: <instruction> is required");
82
+ console.error('Example: orchestrator start-custom ./project "Refactor auth module into separate package"');
83
+ process.exit(1);
84
+ }
85
+ const label = args[2] || undefined; // optional label
86
+ const state = (0, dispatch_1.startCustom)(projectRoot, instruction, label);
87
+ console.log(`Started custom task: "${instruction}"`);
88
+ console.log(` label: ${state.story}, step: ${state.step}, task_type: ${state.task_type}`);
89
+ break;
90
+ }
91
+ case "dispatch": {
92
+ const projectRoot = resolveRoot(args[0]);
93
+ const result = (0, dispatch_1.dispatch)(projectRoot);
94
+ switch (result.type) {
95
+ case "dispatched":
96
+ // Print prompt to stdout (can be piped to claude -p)
97
+ console.log(result.prompt);
98
+ // Print metadata to stderr so it doesn't pollute prompt
99
+ console.error(`[dispatch] step=${result.step} attempt=${result.attempt}`);
100
+ break;
101
+ case "done":
102
+ console.error(`[dispatch] DONE: ${result.summary}`);
103
+ break;
104
+ case "needs_human":
105
+ console.error(`[dispatch] NEEDS HUMAN REVIEW`);
106
+ console.error(result.message);
107
+ break;
108
+ case "blocked":
109
+ console.error(`[dispatch] BLOCKED: ${result.reason}`);
110
+ process.exit(1);
111
+ break;
112
+ case "already_running":
113
+ console.error(`[dispatch] Already running (${result.elapsed_min} min)`);
114
+ break;
115
+ case "timeout":
116
+ console.error(`[dispatch] TIMEOUT at ${result.step} (${result.elapsed_min} min)`);
117
+ process.exit(1);
118
+ break;
119
+ }
120
+ break;
121
+ }
122
+ case "apply-handoff": {
123
+ const projectRoot = resolveRoot(args[0]);
124
+ const state = (0, dispatch_1.applyHandoff)(projectRoot);
125
+ console.log(`Applied HANDOFF → step: ${state.step}, status: ${state.status}, reason: ${state.reason ?? "(none)"}`);
126
+ if (state.tests) {
127
+ console.log(` tests: pass=${state.tests.pass} fail=${state.tests.fail} skip=${state.tests.skip}`);
128
+ }
129
+ break;
130
+ }
131
+ case "post-check": {
132
+ const projectRoot = resolveRoot(args[0]);
133
+ const passed = (0, dispatch_1.runPostCheck)(projectRoot, child_process_1.execSync);
134
+ console.log(passed ? "post_check: PASSED" : "post_check: FAILED");
135
+ if (!passed)
136
+ process.exit(1);
137
+ break;
138
+ }
139
+ case "approve": {
140
+ const projectRoot = resolveRoot(args[0]);
141
+ const note = args[1] || undefined;
142
+ (0, dispatch_1.approveReview)(projectRoot, note);
143
+ console.log(`Review approved${note ? ` (note: "${note}")` : ""}`);
144
+ break;
145
+ }
146
+ case "reject": {
147
+ const projectRoot = resolveRoot(args[0]);
148
+ const reason = args[1];
149
+ if (!reason) {
150
+ console.error("Error: <reason> is required (constitution_violation, needs_clarification, nfr_missing, scope_warning, test_timeout)");
151
+ process.exit(1);
152
+ }
153
+ const note = args[2] || undefined;
154
+ (0, dispatch_1.rejectReview)(projectRoot, reason, note);
155
+ console.log(`Review rejected (reason: ${reason}${note ? `, note: "${note}"` : ""})`);
156
+ break;
157
+ }
158
+ case "status": {
159
+ const projectRoot = resolveRoot(args[0]);
160
+ const state = (0, state_1.readState)(projectRoot);
161
+ console.log(JSON.stringify(state, null, 2));
162
+ break;
163
+ }
164
+ case "query": {
165
+ const projectRoot = resolveRoot(args[0]);
166
+ const status = (0, dispatch_1.queryProjectStatus)(projectRoot);
167
+ console.log(JSON.stringify(status, null, 2));
168
+ break;
169
+ }
170
+ case "detect": {
171
+ const projectRoot = resolveRoot(args[0]);
172
+ const result = (0, dispatch_1.detectFramework)(projectRoot);
173
+ console.log(JSON.stringify(result, null, 2));
174
+ const levelNames = ["Not adopted", "Partial adoption", "Full adoption"];
175
+ console.error(`Framework adoption: Level ${result.level} — ${levelNames[result.level]}`);
176
+ break;
177
+ }
178
+ case "list-projects": {
179
+ const workspaceRoot = resolveRoot(args[0]);
180
+ const projects = (0, dispatch_1.listProjects)(workspaceRoot);
181
+ if (projects.length === 0) {
182
+ console.log("No projects found with .ai/STATE.json");
183
+ }
184
+ else {
185
+ console.log(JSON.stringify(projects, null, 2));
186
+ }
187
+ break;
188
+ }
189
+ default:
190
+ if (command) {
191
+ console.error(`Unknown command: ${command}`);
192
+ }
193
+ usage();
194
+ }
195
+ }
196
+ catch (err) {
197
+ console.error(`Error: ${err.message}`);
198
+ process.exit(1);
199
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * dispatch.ts — Orchestrator State Machine + Prompt Builder + Handoff Parser
3
+ *
4
+ * The only file with logic. Combines state.ts (read/write) and rules.ts (lookup)
5
+ * to drive the micro-waterfall pipeline. All decisions are deterministic —
6
+ * zero LLM tokens.
7
+ *
8
+ * Main entry point: dispatch(projectRoot)
9
+ */
10
+ import { type State, type Step, type Status, type Reason } from "./state";
11
+ import { type StepRule } from "./rules";
12
+ export type DispatchResult = {
13
+ type: "dispatched";
14
+ step: Step;
15
+ attempt: number;
16
+ prompt: string;
17
+ fw_lv: 0 | 1 | 2;
18
+ } | {
19
+ type: "blocked";
20
+ step: Step;
21
+ reason: string;
22
+ } | {
23
+ type: "needs_human";
24
+ step: Step;
25
+ message: string;
26
+ } | {
27
+ type: "done";
28
+ story: string;
29
+ summary: string;
30
+ } | {
31
+ type: "already_running";
32
+ step: Step;
33
+ elapsed_min: number;
34
+ } | {
35
+ type: "timeout";
36
+ step: Step;
37
+ elapsed_min: number;
38
+ };
39
+ /**
40
+ * Core dispatch logic — direct translation of Protocol's dispatch(project).
41
+ *
42
+ * Reads STATE.json, applies the step rules table, and returns a DispatchResult
43
+ * telling the caller what to do next. The caller is responsible for actually
44
+ * invoking the executor (e.g., spawning a Claude Code session).
45
+ *
46
+ * This function updates STATE.json as a side effect (marks running, advances
47
+ * steps, etc.).
48
+ */
49
+ export declare function dispatch(projectRoot: string): DispatchResult;
50
+ /**
51
+ * Build the dispatch prompt from the template.
52
+ * Pure template filling — zero LLM reasoning.
53
+ */
54
+ export declare function buildPrompt(state: State, rule: StepRule): string;
55
+ /** Parsed result from HANDOFF.md's YAML front matter */
56
+ export interface HandoffResult {
57
+ story: string | null;
58
+ step: string | null;
59
+ attempt: number | null;
60
+ status: Status | null;
61
+ reason: Reason | null;
62
+ files_changed: string[];
63
+ tests_pass: number | null;
64
+ tests_fail: number | null;
65
+ tests_skip: number | null;
66
+ /** The markdown body (everything after the second ---) */
67
+ body: string;
68
+ }
69
+ /**
70
+ * Parse HANDOFF.md — prioritize YAML front matter, fallback to grep.
71
+ * Direct translation of Protocol's hook pseudocode.
72
+ */
73
+ export declare function parseHandoff(projectRoot: string): HandoffResult | null;
74
+ /**
75
+ * After executor exits, read HANDOFF.md and update STATE.json accordingly.
76
+ * This is the TypeScript equivalent of the Protocol's post-execution hook.
77
+ *
78
+ * Call this after the executor process exits, before the next dispatch().
79
+ */
80
+ export declare function applyHandoff(projectRoot: string): State;
81
+ /**
82
+ * Run the post_check command for the current step.
83
+ * Returns true if check passed (or no check defined), false if failed.
84
+ *
85
+ * This is a synchronous shell execution — zero LLM tokens.
86
+ */
87
+ export declare function runPostCheck(projectRoot: string, execSync: (cmd: string, opts: object) => Buffer): boolean;
88
+ /**
89
+ * Mark the review step as approved by human.
90
+ * Optionally attach a human note (modification requests, clarifications).
91
+ */
92
+ export declare function approveReview(projectRoot: string, humanNote?: string): void;
93
+ /**
94
+ * Reject the review with a reason and optional note.
95
+ */
96
+ export declare function rejectReview(projectRoot: string, reason: Reason, humanNote?: string): void;
97
+ /**
98
+ * Begin a new User Story. Resets state to bdd step with attempt 1.
99
+ * Auto-initializes STATE.json if the project hasn't adopted the framework yet.
100
+ */
101
+ export declare function startStory(projectRoot: string, storyId: string): State;
102
+ /**
103
+ * Get a human-friendly project status summary.
104
+ * OpenClaw calls this when the user asks "how's the project?" or "open project X".
105
+ *
106
+ * Returns structured data that OpenClaw LLM can translate to natural language.
107
+ */
108
+ export interface ProjectStatus {
109
+ project: string;
110
+ task_type: string;
111
+ story: string | null;
112
+ step: string;
113
+ status: string;
114
+ attempt: number;
115
+ max_attempts: number;
116
+ reason: string | null;
117
+ tests: {
118
+ pass: number;
119
+ fail: number;
120
+ skip: number;
121
+ } | null;
122
+ lint_pass: boolean | null;
123
+ files_changed: string[];
124
+ blocked_by: string[];
125
+ human_note: string | null;
126
+ memory_summary: string | null;
127
+ has_framework: FrameworkDetection;
128
+ }
129
+ export interface FrameworkDetection {
130
+ has_state: boolean;
131
+ has_memory: boolean;
132
+ has_context: boolean;
133
+ has_constitution: boolean;
134
+ has_sdd: boolean;
135
+ has_handoff: boolean;
136
+ has_history: boolean;
137
+ /** Adoption level: 0 = none, 1 = partial (some files), 2 = full (all core files) */
138
+ level: 0 | 1 | 2;
139
+ }
140
+ /**
141
+ * Detect whether a project uses the Agentic Coding Framework.
142
+ * OpenClaw calls this when the user asks "is this project using the framework?"
143
+ */
144
+ export declare function detectFramework(projectRoot: string): FrameworkDetection;
145
+ /**
146
+ * Get comprehensive project status for OpenClaw to summarize to the user.
147
+ * Works for ANY project — with or without the Agentic Coding Framework.
148
+ *
149
+ * - Framework project (has STATE.json): returns full state + memory summary
150
+ * - Non-framework project: returns framework detection + whatever files exist
151
+ */
152
+ export declare function queryProjectStatus(projectRoot: string): ProjectStatus;
153
+ /** Project entry returned by listProjects */
154
+ export interface ProjectEntry {
155
+ /** Project name (from STATE.json, package.json, or directory name) */
156
+ name: string;
157
+ /** Directory name relative to workspace root */
158
+ dir: string;
159
+ /** Current step ("none" if not using framework) */
160
+ step: string;
161
+ /** Current status ("not_initialized" if not using framework) */
162
+ status: string;
163
+ /** Current story ID */
164
+ story: string | null;
165
+ /** Whether this project uses the Agentic Coding Framework */
166
+ has_framework: boolean;
167
+ }
168
+ /**
169
+ * Scan a workspace directory for all projects — both framework and non-framework.
170
+ * OpenClaw calls this when the user asks "list my projects" or "switch project".
171
+ *
172
+ * A directory is considered a "project" if it contains any of:
173
+ * - .ai/STATE.json (framework project)
174
+ * - package.json (Node.js project)
175
+ * - go.mod (Go project)
176
+ * - Cargo.toml (Rust project)
177
+ * - pyproject.toml or setup.py (Python project)
178
+ * - .git/ (any git repo)
179
+ */
180
+ export declare function listProjects(workspaceRoot: string): ProjectEntry[];
181
+ /**
182
+ * Begin a custom (ad-hoc) task. The orchestrator forwards the instruction
183
+ * to Claude Code with full project context, without going through the
184
+ * micro-waterfall pipeline.
185
+ *
186
+ * Pipeline: custom → update-memory → done
187
+ * Auto-initializes STATE.json if the project hasn't adopted the framework yet.
188
+ *
189
+ * Use cases: refactoring, code review, bug fix, DevOps, documentation,
190
+ * testing, migration, performance optimization, security, cleanup, etc.
191
+ */
192
+ export declare function startCustom(projectRoot: string, instruction: string, label?: string): State;