@s_s/harmonia 1.0.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 +3 -0
- package/build/core/dispatch.d.ts +38 -0
- package/build/core/dispatch.js +156 -0
- package/build/core/dispatch.js.map +1 -0
- package/build/core/docs.d.ts +19 -0
- package/build/core/docs.js +59 -0
- package/build/core/docs.js.map +1 -0
- package/build/core/overrides.d.ts +62 -0
- package/build/core/overrides.js +206 -0
- package/build/core/overrides.js.map +1 -0
- package/build/core/registry.d.ts +72 -0
- package/build/core/registry.js +121 -0
- package/build/core/registry.js.map +1 -0
- package/build/core/reviews.d.ts +26 -0
- package/build/core/reviews.js +83 -0
- package/build/core/reviews.js.map +1 -0
- package/build/core/state.d.ts +26 -0
- package/build/core/state.js +103 -0
- package/build/core/state.js.map +1 -0
- package/build/core/types.d.ts +181 -0
- package/build/core/types.js +6 -0
- package/build/core/types.js.map +1 -0
- package/build/core/workflow.d.ts +13 -0
- package/build/core/workflow.js +63 -0
- package/build/core/workflow.js.map +1 -0
- package/build/index.d.ts +15 -0
- package/build/index.js +57 -0
- package/build/index.js.map +1 -0
- package/build/setup/inject.d.ts +34 -0
- package/build/setup/inject.js +115 -0
- package/build/setup/inject.js.map +1 -0
- package/build/setup/templates.d.ts +21 -0
- package/build/setup/templates.js +177 -0
- package/build/setup/templates.js.map +1 -0
- package/build/tools/approve-doc.d.ts +6 -0
- package/build/tools/approve-doc.js +79 -0
- package/build/tools/approve-doc.js.map +1 -0
- package/build/tools/dispatch-role.d.ts +16 -0
- package/build/tools/dispatch-role.js +232 -0
- package/build/tools/dispatch-role.js.map +1 -0
- package/build/tools/doc-tools.d.ts +8 -0
- package/build/tools/doc-tools.js +102 -0
- package/build/tools/doc-tools.js.map +1 -0
- package/build/tools/get-project-status.d.ts +8 -0
- package/build/tools/get-project-status.js +230 -0
- package/build/tools/get-project-status.js.map +1 -0
- package/build/tools/get-role-prompt.d.ts +7 -0
- package/build/tools/get-role-prompt.js +95 -0
- package/build/tools/get-role-prompt.js.map +1 -0
- package/build/tools/override-tools.d.ts +6 -0
- package/build/tools/override-tools.js +129 -0
- package/build/tools/override-tools.js.map +1 -0
- package/build/tools/project-init.d.ts +6 -0
- package/build/tools/project-init.js +71 -0
- package/build/tools/project-init.js.map +1 -0
- package/build/tools/report-dispatch.d.ts +11 -0
- package/build/tools/report-dispatch.js +142 -0
- package/build/tools/report-dispatch.js.map +1 -0
- package/build/tools/setup-project.d.ts +8 -0
- package/build/tools/setup-project.js +88 -0
- package/build/tools/setup-project.js.map +1 -0
- package/build/tools/update-phase.d.ts +7 -0
- package/build/tools/update-phase.js +79 -0
- package/build/tools/update-phase.js.map +1 -0
- package/package.json +51 -0
- package/workflows/dev/roles/architect.md +66 -0
- package/workflows/dev/roles/developer.md +44 -0
- package/workflows/dev/roles/pm.md +99 -0
- package/workflows/dev/roles/tester.md +44 -0
- package/workflows/dev/workflow.json +134 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global registry — manages Harmonia data directory and project name → directory mappings.
|
|
3
|
+
*
|
|
4
|
+
* Data directory follows system conventions (see getGlobalDir()):
|
|
5
|
+
* macOS: ~/Library/Application Support/harmonia
|
|
6
|
+
* Linux: $XDG_DATA_HOME/harmonia (defaults to ~/.local/share/harmonia)
|
|
7
|
+
* Windows: %APPDATA%/harmonia
|
|
8
|
+
* Override: HARMONIA_DATA_DIR env var
|
|
9
|
+
*
|
|
10
|
+
* Structure:
|
|
11
|
+
* <data_dir>/
|
|
12
|
+
* ├── registry.json # { projects: { "my-app": { dir: "/path/to/src", ... } } }
|
|
13
|
+
* ├── overrides.json # global overrides (optional)
|
|
14
|
+
* ├── my-app/
|
|
15
|
+
* │ ├── state.json
|
|
16
|
+
* │ ├── docs/
|
|
17
|
+
* │ ├── reviews.json
|
|
18
|
+
* │ ├── overrides.json # project-level overrides (optional)
|
|
19
|
+
* │ └── ...
|
|
20
|
+
* └── another-project/
|
|
21
|
+
* └── ...
|
|
22
|
+
*/
|
|
23
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { platform } from 'node:os';
|
|
27
|
+
const REGISTRY_FILE = 'registry.json';
|
|
28
|
+
/**
|
|
29
|
+
* Get the global Harmonia data directory, following system conventions.
|
|
30
|
+
*
|
|
31
|
+
* Resolution order:
|
|
32
|
+
* 1. HARMONIA_DATA_DIR env var (explicit override)
|
|
33
|
+
* 2. Platform default:
|
|
34
|
+
* - macOS: ~/Library/Application Support/harmonia
|
|
35
|
+
* - Windows: %APPDATA%/harmonia
|
|
36
|
+
* - Linux: $XDG_DATA_HOME/harmonia (defaults to ~/.local/share/harmonia)
|
|
37
|
+
*/
|
|
38
|
+
export function getGlobalDir() {
|
|
39
|
+
const envDir = process.env.HARMONIA_DATA_DIR;
|
|
40
|
+
if (envDir)
|
|
41
|
+
return envDir;
|
|
42
|
+
const home = homedir();
|
|
43
|
+
switch (platform()) {
|
|
44
|
+
case 'darwin':
|
|
45
|
+
return join(home, 'Library', 'Application Support', 'harmonia');
|
|
46
|
+
case 'win32':
|
|
47
|
+
return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'harmonia');
|
|
48
|
+
default:
|
|
49
|
+
// Linux and others: follow XDG Base Directory spec
|
|
50
|
+
return join(process.env.XDG_DATA_HOME || join(home, '.local', 'share'), 'harmonia');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the data directory for a specific project within the global dir.
|
|
55
|
+
*/
|
|
56
|
+
export function getProjectDataDir(projectName) {
|
|
57
|
+
return join(getGlobalDir(), projectName);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read the global registry. Returns empty registry if file doesn't exist.
|
|
61
|
+
*/
|
|
62
|
+
export async function readRegistry() {
|
|
63
|
+
try {
|
|
64
|
+
const content = await readFile(join(getGlobalDir(), REGISTRY_FILE), 'utf-8');
|
|
65
|
+
return JSON.parse(content);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { projects: {} };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Write the global registry to disk.
|
|
73
|
+
*/
|
|
74
|
+
export async function writeRegistry(registry) {
|
|
75
|
+
const globalDir = getGlobalDir();
|
|
76
|
+
await mkdir(globalDir, { recursive: true });
|
|
77
|
+
await writeFile(join(globalDir, REGISTRY_FILE), JSON.stringify(registry, null, 2) + '\n', 'utf-8');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Register a new project. Creates the project data directory.
|
|
81
|
+
*/
|
|
82
|
+
export async function registerProject(projectName, projectDir, workflow) {
|
|
83
|
+
const registry = await readRegistry();
|
|
84
|
+
if (registry.projects[projectName]) {
|
|
85
|
+
throw new Error(`Project "${projectName}" already exists. Use a different name or remove the existing project first.`);
|
|
86
|
+
}
|
|
87
|
+
registry.projects[projectName] = {
|
|
88
|
+
dir: projectDir,
|
|
89
|
+
workflow,
|
|
90
|
+
createdAt: new Date().toISOString(),
|
|
91
|
+
};
|
|
92
|
+
// Create project data directory structure under global dir
|
|
93
|
+
const dataDir = getProjectDataDir(projectName);
|
|
94
|
+
await mkdir(join(dataDir, 'docs'), { recursive: true });
|
|
95
|
+
// Create the project source directory if it doesn't exist
|
|
96
|
+
await mkdir(projectDir, { recursive: true });
|
|
97
|
+
await writeRegistry(registry);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Look up a project by name. Returns null if not found.
|
|
101
|
+
*/
|
|
102
|
+
export async function getProject(projectName) {
|
|
103
|
+
const registry = await readRegistry();
|
|
104
|
+
return registry.projects[projectName] ?? null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* List all registered project names.
|
|
108
|
+
*/
|
|
109
|
+
export async function listProjects() {
|
|
110
|
+
const registry = await readRegistry();
|
|
111
|
+
return Object.keys(registry.projects);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Remove a project from the registry (does NOT delete files).
|
|
115
|
+
*/
|
|
116
|
+
export async function unregisterProject(projectName) {
|
|
117
|
+
const registry = await readRegistry();
|
|
118
|
+
delete registry.projects[projectName];
|
|
119
|
+
await writeRegistry(registry);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document review state management — <data_dir>/<project_name>/reviews.json
|
|
3
|
+
*
|
|
4
|
+
* Tracks which documents are pending review, approved, or rejected.
|
|
5
|
+
*/
|
|
6
|
+
import type { DocReviewState } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Read the reviews state for a project.
|
|
9
|
+
*/
|
|
10
|
+
export declare function readReviews(projectName: string): Promise<Record<string, DocReviewState>>;
|
|
11
|
+
/**
|
|
12
|
+
* Submit a document for review. Sets status to "pending".
|
|
13
|
+
*/
|
|
14
|
+
export declare function submitForReview(projectName: string, docId: string): Promise<DocReviewState>;
|
|
15
|
+
/**
|
|
16
|
+
* Approve or reject a document review.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveReview(projectName: string, docId: string, status: 'approved' | 'rejected', comment?: string): Promise<DocReviewState>;
|
|
19
|
+
/**
|
|
20
|
+
* Get the review state for a specific document.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getDocReview(projectName: string, docId: string): Promise<DocReviewState | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Get all pending reviews for a project.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getPendingReviews(projectName: string): Promise<DocReviewState[]>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document review state management — <data_dir>/<project_name>/reviews.json
|
|
3
|
+
*
|
|
4
|
+
* Tracks which documents are pending review, approved, or rejected.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { getProjectDataDir } from './registry.js';
|
|
9
|
+
const REVIEWS_FILE = 'reviews.json';
|
|
10
|
+
function reviewsPath(projectName) {
|
|
11
|
+
return join(getProjectDataDir(projectName), REVIEWS_FILE);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read the reviews state for a project.
|
|
15
|
+
*/
|
|
16
|
+
export async function readReviews(projectName) {
|
|
17
|
+
try {
|
|
18
|
+
const content = await readFile(reviewsPath(projectName), 'utf-8');
|
|
19
|
+
const data = JSON.parse(content);
|
|
20
|
+
return data.docs ?? {};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Write reviews state to disk.
|
|
28
|
+
*/
|
|
29
|
+
async function writeReviews(projectName, docs) {
|
|
30
|
+
const filePath = reviewsPath(projectName);
|
|
31
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
32
|
+
const data = { docs };
|
|
33
|
+
await writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Submit a document for review. Sets status to "pending".
|
|
37
|
+
*/
|
|
38
|
+
export async function submitForReview(projectName, docId) {
|
|
39
|
+
const reviews = await readReviews(projectName);
|
|
40
|
+
const state = {
|
|
41
|
+
docId,
|
|
42
|
+
status: 'pending',
|
|
43
|
+
submittedAt: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
reviews[docId] = state;
|
|
46
|
+
await writeReviews(projectName, reviews);
|
|
47
|
+
return state;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Approve or reject a document review.
|
|
51
|
+
*/
|
|
52
|
+
export async function resolveReview(projectName, docId, status, comment) {
|
|
53
|
+
const reviews = await readReviews(projectName);
|
|
54
|
+
const existing = reviews[docId];
|
|
55
|
+
if (!existing) {
|
|
56
|
+
throw new Error(`No review pending for document "${docId}". Submit it for review first.`);
|
|
57
|
+
}
|
|
58
|
+
if (existing.status !== 'pending') {
|
|
59
|
+
throw new Error(`Document "${docId}" review is already ${existing.status}.`);
|
|
60
|
+
}
|
|
61
|
+
existing.status = status;
|
|
62
|
+
existing.reviewedAt = new Date().toISOString();
|
|
63
|
+
if (comment) {
|
|
64
|
+
existing.comment = comment;
|
|
65
|
+
}
|
|
66
|
+
await writeReviews(projectName, reviews);
|
|
67
|
+
return existing;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the review state for a specific document.
|
|
71
|
+
*/
|
|
72
|
+
export async function getDocReview(projectName, docId) {
|
|
73
|
+
const reviews = await readReviews(projectName);
|
|
74
|
+
return reviews[docId] ?? null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get all pending reviews for a project.
|
|
78
|
+
*/
|
|
79
|
+
export async function getPendingReviews(projectName) {
|
|
80
|
+
const reviews = await readReviews(projectName);
|
|
81
|
+
return Object.values(reviews).filter((r) => r.status === 'pending');
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=reviews.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviews.js","sourceRoot":"","sources":["../../src/core/reviews.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project state management — manages <data_dir>/<project_name>/state.json
|
|
3
|
+
*/
|
|
4
|
+
import type { PhaseStatus, ProjectScale, ProjectState, LoadedWorkflow } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Initialize a new project state file.
|
|
7
|
+
* Note: directory creation and registry are handled by registry.registerProject().
|
|
8
|
+
* This only writes the state.json.
|
|
9
|
+
*/
|
|
10
|
+
export declare function initProjectState(projectName: string, projectDir: string, workflow: LoadedWorkflow, scale?: ProjectScale): Promise<ProjectState>;
|
|
11
|
+
/**
|
|
12
|
+
* Read the current project state.
|
|
13
|
+
*/
|
|
14
|
+
export declare function readState(projectName: string): Promise<ProjectState>;
|
|
15
|
+
/**
|
|
16
|
+
* Write project state to disk.
|
|
17
|
+
*/
|
|
18
|
+
export declare function writeState(projectName: string, state: ProjectState): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Update a specific phase's status.
|
|
21
|
+
*/
|
|
22
|
+
export declare function updatePhaseStatus(projectName: string, phaseId: string, status: PhaseStatus, blockedReason?: string): Promise<ProjectState>;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a project state file exists.
|
|
25
|
+
*/
|
|
26
|
+
export declare function projectStateExists(projectName: string): Promise<boolean>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project state management — manages <data_dir>/<project_name>/state.json
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
import { getProjectDataDir } from './registry.js';
|
|
7
|
+
const STATE_FILE = 'state.json';
|
|
8
|
+
function statePath(projectName) {
|
|
9
|
+
return join(getProjectDataDir(projectName), STATE_FILE);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Initialize a new project state file.
|
|
13
|
+
* Note: directory creation and registry are handled by registry.registerProject().
|
|
14
|
+
* This only writes the state.json.
|
|
15
|
+
*/
|
|
16
|
+
export async function initProjectState(projectName, projectDir, workflow, scale = 'small') {
|
|
17
|
+
const now = new Date().toISOString();
|
|
18
|
+
const phases = workflow.definition.phases;
|
|
19
|
+
const firstPhaseId = phases[0]?.id ?? '';
|
|
20
|
+
const state = {
|
|
21
|
+
projectName,
|
|
22
|
+
projectDir,
|
|
23
|
+
workflow: workflow.definition.name,
|
|
24
|
+
scale,
|
|
25
|
+
currentPhase: firstPhaseId,
|
|
26
|
+
phases: phases.map((p, i) => ({
|
|
27
|
+
id: p.id,
|
|
28
|
+
status: i === 0 ? 'in_progress' : 'pending',
|
|
29
|
+
...(i === 0 ? { startedAt: now } : {}),
|
|
30
|
+
})),
|
|
31
|
+
createdAt: now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
};
|
|
34
|
+
await writeState(projectName, state);
|
|
35
|
+
return state;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the current project state.
|
|
39
|
+
*/
|
|
40
|
+
export async function readState(projectName) {
|
|
41
|
+
const content = await readFile(statePath(projectName), 'utf-8');
|
|
42
|
+
return JSON.parse(content);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Write project state to disk.
|
|
46
|
+
*/
|
|
47
|
+
export async function writeState(projectName, state) {
|
|
48
|
+
const filePath = statePath(projectName);
|
|
49
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
50
|
+
state.updatedAt = new Date().toISOString();
|
|
51
|
+
await writeFile(filePath, JSON.stringify(state, null, 2) + '\n', 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Update a specific phase's status.
|
|
55
|
+
*/
|
|
56
|
+
export async function updatePhaseStatus(projectName, phaseId, status, blockedReason) {
|
|
57
|
+
const state = await readState(projectName);
|
|
58
|
+
const phase = state.phases.find((p) => p.id === phaseId);
|
|
59
|
+
if (!phase) {
|
|
60
|
+
throw new Error(`Phase "${phaseId}" not found in project state`);
|
|
61
|
+
}
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
phase.status = status;
|
|
64
|
+
if (status === 'in_progress' && !phase.startedAt) {
|
|
65
|
+
phase.startedAt = now;
|
|
66
|
+
}
|
|
67
|
+
if (status === 'completed') {
|
|
68
|
+
phase.completedAt = now;
|
|
69
|
+
delete phase.blockedReason;
|
|
70
|
+
}
|
|
71
|
+
if (status === 'blocked' && blockedReason) {
|
|
72
|
+
phase.blockedReason = blockedReason;
|
|
73
|
+
}
|
|
74
|
+
// If advancing to in_progress, update currentPhase
|
|
75
|
+
if (status === 'in_progress') {
|
|
76
|
+
state.currentPhase = phaseId;
|
|
77
|
+
}
|
|
78
|
+
// If completing a phase, auto-advance currentPhase to the next pending one
|
|
79
|
+
if (status === 'completed') {
|
|
80
|
+
const idx = state.phases.findIndex((p) => p.id === phaseId);
|
|
81
|
+
const next = state.phases.find((p, i) => i > idx && p.status === 'pending');
|
|
82
|
+
if (next) {
|
|
83
|
+
next.status = 'in_progress';
|
|
84
|
+
next.startedAt = now;
|
|
85
|
+
state.currentPhase = next.id;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await writeState(projectName, state);
|
|
89
|
+
return state;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a project state file exists.
|
|
93
|
+
*/
|
|
94
|
+
export async function projectStateExists(projectName) {
|
|
95
|
+
try {
|
|
96
|
+
await readFile(statePath(projectName), 'utf-8');
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/core/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for Harmonia.
|
|
3
|
+
* These types are workflow-agnostic — no hardcoded role names or phase names.
|
|
4
|
+
*/
|
|
5
|
+
export interface PhaseDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
roles: string[];
|
|
9
|
+
inputs?: string[];
|
|
10
|
+
outputs: string[];
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
export type DocScale = 'full' | 'lite' | 'skip' | 'optional';
|
|
14
|
+
export interface DocDefinition {
|
|
15
|
+
name: string;
|
|
16
|
+
scale: Record<ProjectScale, DocScale>;
|
|
17
|
+
/** File format: "md" (default) or "html" */
|
|
18
|
+
format?: 'md' | 'html';
|
|
19
|
+
/** Whether this doc requires user review/approval after creation */
|
|
20
|
+
review?: boolean;
|
|
21
|
+
/** External output — not managed by write_doc (e.g. code written directly to project dir) */
|
|
22
|
+
external?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ScaleDimension {
|
|
25
|
+
small: string | number | boolean;
|
|
26
|
+
medium: string | number | boolean;
|
|
27
|
+
large: string | number | boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface WorkflowDefinition {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
phases: PhaseDefinition[];
|
|
33
|
+
docs: Record<string, DocDefinition>;
|
|
34
|
+
scale_criteria: {
|
|
35
|
+
description: string;
|
|
36
|
+
dimensions: Record<string, ScaleDimension>;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface RoleCapability {
|
|
40
|
+
/** Unique ID for this capability */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Human-readable description of what this capability does */
|
|
43
|
+
description: string;
|
|
44
|
+
/** Associated doc ID — if set, this capability produces this document */
|
|
45
|
+
doc?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface RoleFrontmatter {
|
|
48
|
+
model: string;
|
|
49
|
+
session: 'none' | 'persistent' | 'optional';
|
|
50
|
+
parallel: boolean;
|
|
51
|
+
/** Capabilities this role provides (used by override system) */
|
|
52
|
+
capabilities?: RoleCapability[];
|
|
53
|
+
}
|
|
54
|
+
export interface RoleDefinition {
|
|
55
|
+
id: string;
|
|
56
|
+
frontmatter: RoleFrontmatter;
|
|
57
|
+
prompt: string;
|
|
58
|
+
}
|
|
59
|
+
export interface LoadedWorkflow {
|
|
60
|
+
definition: WorkflowDefinition;
|
|
61
|
+
roles: Record<string, RoleDefinition>;
|
|
62
|
+
}
|
|
63
|
+
export type PhaseStatus = 'pending' | 'in_progress' | 'completed' | 'blocked' | 'review';
|
|
64
|
+
export type ProjectScale = 'small' | 'medium' | 'large';
|
|
65
|
+
export interface PhaseState {
|
|
66
|
+
id: string;
|
|
67
|
+
status: PhaseStatus;
|
|
68
|
+
startedAt?: string;
|
|
69
|
+
completedAt?: string;
|
|
70
|
+
blockedReason?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface ProjectState {
|
|
73
|
+
/** Project name (unique identifier) */
|
|
74
|
+
projectName: string;
|
|
75
|
+
/** Absolute path to the project source directory */
|
|
76
|
+
projectDir: string;
|
|
77
|
+
/** Workflow name, e.g. "dev" */
|
|
78
|
+
workflow: string;
|
|
79
|
+
/** Project scale determined by PM */
|
|
80
|
+
scale: ProjectScale;
|
|
81
|
+
/** Current phase id */
|
|
82
|
+
currentPhase: string;
|
|
83
|
+
/** State of each phase */
|
|
84
|
+
phases: PhaseState[];
|
|
85
|
+
/** Timestamp of project initialization */
|
|
86
|
+
createdAt: string;
|
|
87
|
+
/** Last updated timestamp */
|
|
88
|
+
updatedAt: string;
|
|
89
|
+
}
|
|
90
|
+
export type ReviewStatus = 'pending' | 'approved' | 'rejected';
|
|
91
|
+
export interface DocReviewState {
|
|
92
|
+
docId: string;
|
|
93
|
+
status: ReviewStatus;
|
|
94
|
+
submittedAt: string;
|
|
95
|
+
reviewedAt?: string;
|
|
96
|
+
comment?: string;
|
|
97
|
+
}
|
|
98
|
+
export type SessionStatus = 'active' | 'idle' | 'closed' | 'lost';
|
|
99
|
+
export interface SessionRecord {
|
|
100
|
+
/** Harmonia-generated session ID, e.g. "ses-001" */
|
|
101
|
+
id: string;
|
|
102
|
+
/** Role this session belongs to */
|
|
103
|
+
role: string;
|
|
104
|
+
/** Actual session ID from the host agent (e.g. OpenCode session ID) */
|
|
105
|
+
agentSessionId?: string;
|
|
106
|
+
/** Agent type used for this session */
|
|
107
|
+
agentType?: AgentType;
|
|
108
|
+
/** Current session status */
|
|
109
|
+
status: SessionStatus;
|
|
110
|
+
/** PM-defined label, e.g. "dev-auth-module" */
|
|
111
|
+
label?: string;
|
|
112
|
+
/** When this session was created */
|
|
113
|
+
createdAt: string;
|
|
114
|
+
/** When this session was last active */
|
|
115
|
+
lastActiveAt: string;
|
|
116
|
+
}
|
|
117
|
+
export type DispatchStatus = 'dispatched' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
118
|
+
export interface DispatchRecord {
|
|
119
|
+
/** Auto-incremented dispatch ID, e.g. "dispatch-001" */
|
|
120
|
+
id: string;
|
|
121
|
+
/** Role dispatched */
|
|
122
|
+
role: string;
|
|
123
|
+
/** Associated session ID (set when agent is launched) */
|
|
124
|
+
sessionId?: string;
|
|
125
|
+
/** Task description for the dispatched role */
|
|
126
|
+
taskBrief: string;
|
|
127
|
+
/** Current dispatch status */
|
|
128
|
+
status: DispatchStatus;
|
|
129
|
+
/** Expected output doc IDs from this dispatch */
|
|
130
|
+
expectedOutputs: string[];
|
|
131
|
+
/** When this dispatch was created */
|
|
132
|
+
createdAt: string;
|
|
133
|
+
/** Last updated timestamp */
|
|
134
|
+
updatedAt: string;
|
|
135
|
+
/** When this dispatch was completed */
|
|
136
|
+
completedAt?: string;
|
|
137
|
+
/** Failure reason or notes */
|
|
138
|
+
note?: string;
|
|
139
|
+
}
|
|
140
|
+
export type OverrideToolType = 'skill' | 'mcp';
|
|
141
|
+
export interface CapabilityOverride {
|
|
142
|
+
/** Tool source type */
|
|
143
|
+
type: OverrideToolType;
|
|
144
|
+
/** Tool name */
|
|
145
|
+
tool: string;
|
|
146
|
+
/** MCP server name (required when type is "mcp") */
|
|
147
|
+
server?: string;
|
|
148
|
+
/** Static parameters to always pass when calling the tool */
|
|
149
|
+
params?: Record<string, unknown>;
|
|
150
|
+
/** Additional notes for prompt generation (rarely needed) */
|
|
151
|
+
notes?: string;
|
|
152
|
+
}
|
|
153
|
+
/** Agent type for spawning team member agents */
|
|
154
|
+
export type AgentType = 'opencode' | 'openclaw' | 'claude-code' | 'codex';
|
|
155
|
+
/** Per-role override configuration */
|
|
156
|
+
export interface RoleOverride {
|
|
157
|
+
/** Agent type to use for this role */
|
|
158
|
+
agent?: AgentType;
|
|
159
|
+
/** Model to use for this role (overrides the role's default model level) */
|
|
160
|
+
model?: string;
|
|
161
|
+
/** Capability overrides for this role */
|
|
162
|
+
capabilities?: Record<string, CapabilityOverride>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Override configuration file structure (<data_dir>/overrides.json or project-level).
|
|
166
|
+
*
|
|
167
|
+
* review: boolean | Record<docId, boolean>
|
|
168
|
+
* - boolean: global toggle for all docs
|
|
169
|
+
* - Record: per-doc toggle
|
|
170
|
+
*
|
|
171
|
+
* roles: Record<roleId, RoleOverride>
|
|
172
|
+
* - agent: agent type for spawning
|
|
173
|
+
* - model: model override
|
|
174
|
+
* - capabilities: Record<capabilityId, CapabilityOverride>
|
|
175
|
+
*/
|
|
176
|
+
export interface OverrideConfig {
|
|
177
|
+
/** Review overrides — global toggle or per-doc */
|
|
178
|
+
review?: boolean | Record<string, boolean>;
|
|
179
|
+
/** Role overrides (agent, model, capabilities) */
|
|
180
|
+
roles?: Record<string, RoleOverride>;
|
|
181
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow loader — reads workflow definitions and role prompts from the
|
|
3
|
+
* workflows/ directory bundled with Harmonia.
|
|
4
|
+
*/
|
|
5
|
+
import type { LoadedWorkflow } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Load a single workflow by name from the given workflows root directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadWorkflow(workflowsDir: string, name: string): Promise<LoadedWorkflow>;
|
|
10
|
+
/**
|
|
11
|
+
* List all available workflow names.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listWorkflows(workflowsDir: string): Promise<string[]>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow loader — reads workflow definitions and role prompts from the
|
|
3
|
+
* workflows/ directory bundled with Harmonia.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
6
|
+
import { join, parse } from 'node:path';
|
|
7
|
+
import YAML from 'yaml';
|
|
8
|
+
/**
|
|
9
|
+
* Parse a role markdown file.
|
|
10
|
+
* Format: YAML frontmatter (---\n...\n---) followed by markdown prompt.
|
|
11
|
+
*/
|
|
12
|
+
function parseRoleFile(id, content) {
|
|
13
|
+
const fmRegex = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
|
14
|
+
const match = content.match(fmRegex);
|
|
15
|
+
if (!match) {
|
|
16
|
+
return {
|
|
17
|
+
id,
|
|
18
|
+
frontmatter: { model: 'medium', session: 'none', parallel: false },
|
|
19
|
+
prompt: content.trim(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const yamlBlock = match[1];
|
|
23
|
+
const prompt = match[2].trim();
|
|
24
|
+
const parsed = YAML.parse(yamlBlock);
|
|
25
|
+
const fm = parsed ?? {};
|
|
26
|
+
const capabilities = Array.isArray(fm.capabilities) ? fm.capabilities : undefined;
|
|
27
|
+
const frontmatter = {
|
|
28
|
+
model: fm.model ?? 'medium',
|
|
29
|
+
session: fm.session ?? 'none',
|
|
30
|
+
parallel: fm.parallel ?? false,
|
|
31
|
+
...(capabilities ? { capabilities } : {}),
|
|
32
|
+
};
|
|
33
|
+
return { id, frontmatter, prompt };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Load a single workflow by name from the given workflows root directory.
|
|
37
|
+
*/
|
|
38
|
+
export async function loadWorkflow(workflowsDir, name) {
|
|
39
|
+
const workflowDir = join(workflowsDir, name);
|
|
40
|
+
// Load workflow.json
|
|
41
|
+
const workflowJson = await readFile(join(workflowDir, 'workflow.json'), 'utf-8');
|
|
42
|
+
const definition = JSON.parse(workflowJson);
|
|
43
|
+
// Load roles
|
|
44
|
+
const rolesDir = join(workflowDir, 'roles');
|
|
45
|
+
const roleFiles = await readdir(rolesDir);
|
|
46
|
+
const roles = {};
|
|
47
|
+
for (const file of roleFiles) {
|
|
48
|
+
if (!file.endsWith('.md'))
|
|
49
|
+
continue;
|
|
50
|
+
const roleId = parse(file).name;
|
|
51
|
+
const content = await readFile(join(rolesDir, file), 'utf-8');
|
|
52
|
+
roles[roleId] = parseRoleFile(roleId, content);
|
|
53
|
+
}
|
|
54
|
+
return { definition, roles };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* List all available workflow names.
|
|
58
|
+
*/
|
|
59
|
+
export async function listWorkflows(workflowsDir) {
|
|
60
|
+
const entries = await readdir(workflowsDir, { withFileTypes: true });
|
|
61
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow.js","sourceRoot":"","sources":["../../src/core/workflow.ts"],"names":[],"mappings":"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"}
|