@ghostwater/soulforge 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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +483 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/daemon/daemon-entry.d.ts +2 -0
- package/dist/daemon/daemon-entry.js +8 -0
- package/dist/daemon/daemon-entry.js.map +1 -0
- package/dist/daemon/daemon.d.ts +9 -0
- package/dist/daemon/daemon.js +88 -0
- package/dist/daemon/daemon.js.map +1 -0
- package/dist/daemon/runner.d.ts +3 -0
- package/dist/daemon/runner.js +310 -0
- package/dist/daemon/runner.js.map +1 -0
- package/dist/db/database.d.ts +95 -0
- package/dist/db/database.js +277 -0
- package/dist/db/database.js.map +1 -0
- package/dist/executors/claude-code.d.ts +9 -0
- package/dist/executors/claude-code.js +86 -0
- package/dist/executors/claude-code.js.map +1 -0
- package/dist/executors/codex.d.ts +9 -0
- package/dist/executors/codex.js +66 -0
- package/dist/executors/codex.js.map +1 -0
- package/dist/executors/openclaw.d.ts +10 -0
- package/dist/executors/openclaw.js +19 -0
- package/dist/executors/openclaw.js.map +1 -0
- package/dist/executors/registry.d.ts +4 -0
- package/dist/executors/registry.js +20 -0
- package/dist/executors/registry.js.map +1 -0
- package/dist/executors/self.d.ts +10 -0
- package/dist/executors/self.js +25 -0
- package/dist/executors/self.js.map +1 -0
- package/dist/executors/types.d.ts +41 -0
- package/dist/executors/types.js +2 -0
- package/dist/executors/types.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/logger.d.ts +7 -0
- package/dist/lib/logger.js +56 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/worktree.d.ts +30 -0
- package/dist/lib/worktree.js +131 -0
- package/dist/lib/worktree.js.map +1 -0
- package/dist/workflow/parser.d.ts +2 -0
- package/dist/workflow/parser.js +95 -0
- package/dist/workflow/parser.js.map +1 -0
- package/dist/workflow/template.d.ts +14 -0
- package/dist/workflow/template.js +71 -0
- package/dist/workflow/template.js.map +1 -0
- package/dist/workflow/types.d.ts +65 -0
- package/dist/workflow/types.js +2 -0
- package/dist/workflow/types.js.map +1 -0
- package/package.json +46 -0
- package/workflows/feature-dev/workflow.yml +169 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from "./db/database.js";
|
|
2
|
+
export * from "./workflow/types.js";
|
|
3
|
+
export * from "./workflow/parser.js";
|
|
4
|
+
export * from "./workflow/template.js";
|
|
5
|
+
export * from "./executors/types.js";
|
|
6
|
+
export * from "./executors/registry.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const logger: {
|
|
2
|
+
info: (msg: string, meta?: Record<string, any>) => void;
|
|
3
|
+
warn: (msg: string, meta?: Record<string, any>) => void;
|
|
4
|
+
error: (msg: string, meta?: Record<string, any>) => void;
|
|
5
|
+
debug: (msg: string, meta?: Record<string, any>) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function readRecentLogs(lines?: number): string[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
const LOG_DIR = path.join(os.homedir(), ".soulforge");
|
|
5
|
+
const LOG_PATH = path.join(LOG_DIR, "daemon.log");
|
|
6
|
+
const MAX_LOG_SIZE = 5 * 1024 * 1024; // 5MB
|
|
7
|
+
function ensureLogDir() {
|
|
8
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
function rotateIfNeeded() {
|
|
11
|
+
try {
|
|
12
|
+
const stat = fs.statSync(LOG_PATH);
|
|
13
|
+
if (stat.size > MAX_LOG_SIZE) {
|
|
14
|
+
const backup = LOG_PATH + ".1";
|
|
15
|
+
try {
|
|
16
|
+
fs.unlinkSync(backup);
|
|
17
|
+
}
|
|
18
|
+
catch { }
|
|
19
|
+
fs.renameSync(LOG_PATH, backup);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// File doesn't exist yet
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function formatEntry(level, message, meta) {
|
|
27
|
+
const ts = new Date().toISOString();
|
|
28
|
+
const metaStr = meta ? " " + JSON.stringify(meta) : "";
|
|
29
|
+
return `[${ts}] ${level.toUpperCase()} ${message}${metaStr}\n`;
|
|
30
|
+
}
|
|
31
|
+
function write(level, message, meta) {
|
|
32
|
+
ensureLogDir();
|
|
33
|
+
rotateIfNeeded();
|
|
34
|
+
const entry = formatEntry(level, message, meta);
|
|
35
|
+
fs.appendFileSync(LOG_PATH, entry);
|
|
36
|
+
if (level === "error") {
|
|
37
|
+
process.stderr.write(entry);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export const logger = {
|
|
41
|
+
info: (msg, meta) => write("info", msg, meta),
|
|
42
|
+
warn: (msg, meta) => write("warn", msg, meta),
|
|
43
|
+
error: (msg, meta) => write("error", msg, meta),
|
|
44
|
+
debug: (msg, meta) => write("debug", msg, meta),
|
|
45
|
+
};
|
|
46
|
+
export function readRecentLogs(lines = 50) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(LOG_PATH, "utf-8");
|
|
49
|
+
const allLines = content.split("\n").filter((l) => l.trim());
|
|
50
|
+
return allLines.slice(-lines);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface WorktreeResult {
|
|
2
|
+
worktreePath: string;
|
|
3
|
+
branch: string;
|
|
4
|
+
}
|
|
5
|
+
export interface GitRepoInfo {
|
|
6
|
+
type: 'bare' | 'standard' | 'worktree' | 'not-git';
|
|
7
|
+
repoRoot: string | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validate whether a path is a git repository and determine its type.
|
|
11
|
+
* - 'bare': Uses bare+worktree layout (.bare/ exists in parent)
|
|
12
|
+
* - 'standard': Regular .git directory
|
|
13
|
+
* - 'worktree': Inside an existing worktree
|
|
14
|
+
* - 'not-git': Not a git repository
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateGitRepo(repoPath: string): GitRepoInfo;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a branch already exists in the repository.
|
|
19
|
+
*/
|
|
20
|
+
export declare function branchExists(repoPath: string, branchName: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Create a worktree for the given repository with a new branch.
|
|
23
|
+
*
|
|
24
|
+
* @param repoInfo - Validated git repo info from validateGitRepo()
|
|
25
|
+
* @param repoPath - Original path provided by user
|
|
26
|
+
* @param branch - Branch name to create
|
|
27
|
+
* @returns WorktreeResult with the path to the created worktree
|
|
28
|
+
*/
|
|
29
|
+
export declare function createWorktree(repoPath: string, branch: string, repoInfo?: GitRepoInfo): WorktreeResult;
|
|
30
|
+
export declare function removeWorktree(repoPath: string, worktreePath: string): void;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
/**
|
|
5
|
+
* Validate whether a path is a git repository and determine its type.
|
|
6
|
+
* - 'bare': Uses bare+worktree layout (.bare/ exists in parent)
|
|
7
|
+
* - 'standard': Regular .git directory
|
|
8
|
+
* - 'worktree': Inside an existing worktree
|
|
9
|
+
* - 'not-git': Not a git repository
|
|
10
|
+
*/
|
|
11
|
+
export function validateGitRepo(repoPath) {
|
|
12
|
+
const resolvedPath = path.resolve(repoPath);
|
|
13
|
+
// Check if .bare/ exists in parent (bare+worktree layout)
|
|
14
|
+
const parentDir = path.dirname(resolvedPath);
|
|
15
|
+
const bareDir = path.join(parentDir, '.bare');
|
|
16
|
+
if (fs.existsSync(bareDir) && fs.statSync(bareDir).isDirectory()) {
|
|
17
|
+
return { type: 'bare', repoRoot: parentDir };
|
|
18
|
+
}
|
|
19
|
+
// Check if we're inside a worktree by looking for .git file (not directory)
|
|
20
|
+
const gitPath = path.join(resolvedPath, '.git');
|
|
21
|
+
if (fs.existsSync(gitPath)) {
|
|
22
|
+
const stat = fs.statSync(gitPath);
|
|
23
|
+
if (stat.isFile()) {
|
|
24
|
+
// .git is a file, meaning this is a worktree - resolve to parent repo
|
|
25
|
+
try {
|
|
26
|
+
const superproject = execSync('git rev-parse --show-superproject-working-tree', {
|
|
27
|
+
cwd: resolvedPath,
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
}).trim();
|
|
30
|
+
if (superproject) {
|
|
31
|
+
// Recursively validate the parent repo
|
|
32
|
+
return validateGitRepo(superproject);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Fall through to standard check
|
|
37
|
+
}
|
|
38
|
+
return { type: 'worktree', repoRoot: resolvedPath };
|
|
39
|
+
}
|
|
40
|
+
else if (stat.isDirectory()) {
|
|
41
|
+
return { type: 'standard', repoRoot: resolvedPath };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Check if we're somewhere inside a git repo
|
|
45
|
+
try {
|
|
46
|
+
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
|
47
|
+
cwd: resolvedPath,
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
}).trim();
|
|
50
|
+
if (gitRoot) {
|
|
51
|
+
// Check if this git root has a bare parent
|
|
52
|
+
const gitRootParent = path.dirname(gitRoot);
|
|
53
|
+
const gitRootBareDir = path.join(gitRootParent, '.bare');
|
|
54
|
+
if (fs.existsSync(gitRootBareDir) && fs.statSync(gitRootBareDir).isDirectory()) {
|
|
55
|
+
return { type: 'bare', repoRoot: gitRootParent };
|
|
56
|
+
}
|
|
57
|
+
return { type: 'standard', repoRoot: gitRoot };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Not a git repo
|
|
62
|
+
}
|
|
63
|
+
return { type: 'not-git', repoRoot: null };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Check if a branch already exists in the repository.
|
|
67
|
+
*/
|
|
68
|
+
export function branchExists(repoPath, branchName) {
|
|
69
|
+
try {
|
|
70
|
+
const result = execSync(`git branch --list "${branchName}"`, {
|
|
71
|
+
cwd: repoPath,
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
}).trim();
|
|
74
|
+
return result.length > 0;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a worktree for the given repository with a new branch.
|
|
82
|
+
*
|
|
83
|
+
* @param repoInfo - Validated git repo info from validateGitRepo()
|
|
84
|
+
* @param repoPath - Original path provided by user
|
|
85
|
+
* @param branch - Branch name to create
|
|
86
|
+
* @returns WorktreeResult with the path to the created worktree
|
|
87
|
+
*/
|
|
88
|
+
export function createWorktree(repoPath, branch, repoInfo) {
|
|
89
|
+
// If no repoInfo provided, validate the repo
|
|
90
|
+
const info = repoInfo ?? validateGitRepo(repoPath);
|
|
91
|
+
if (info.type === 'not-git') {
|
|
92
|
+
throw new Error(`Cannot create worktree: ${repoPath} is not a git repository. Use --workdir instead.`);
|
|
93
|
+
}
|
|
94
|
+
const repoRoot = info.repoRoot;
|
|
95
|
+
// Check if branch already exists
|
|
96
|
+
if (branchExists(repoRoot, branch)) {
|
|
97
|
+
throw new Error(`Branch '${branch}' already exists. Use --branch to specify a different name, or --workdir to work in an existing directory.`);
|
|
98
|
+
}
|
|
99
|
+
// Determine worktree placement based on repo type
|
|
100
|
+
let worktreesDir;
|
|
101
|
+
let gitDir;
|
|
102
|
+
let workingDir;
|
|
103
|
+
if (info.type === 'bare') {
|
|
104
|
+
// Bare layout: worktrees go in <parent>/worktrees/
|
|
105
|
+
worktreesDir = path.join(repoRoot, 'worktrees');
|
|
106
|
+
gitDir = path.join(repoRoot, '.bare');
|
|
107
|
+
workingDir = repoRoot;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// Standard or worktree: worktrees go in <repo>/worktrees/
|
|
111
|
+
worktreesDir = path.join(repoRoot, 'worktrees');
|
|
112
|
+
gitDir = path.join(repoRoot, '.git');
|
|
113
|
+
workingDir = repoRoot;
|
|
114
|
+
}
|
|
115
|
+
const worktreePath = path.join(worktreesDir, branch.replace(/\//g, '-'));
|
|
116
|
+
fs.mkdirSync(worktreesDir, { recursive: true });
|
|
117
|
+
// Get current HEAD to branch from
|
|
118
|
+
const head = execSync('git rev-parse HEAD', { cwd: workingDir }).toString().trim();
|
|
119
|
+
// Create worktree with new branch
|
|
120
|
+
execSync(`git worktree add "${worktreePath}" -b "${branch}" ${head}`, { cwd: workingDir, env: { ...process.env, GIT_DIR: gitDir } });
|
|
121
|
+
return { worktreePath, branch };
|
|
122
|
+
}
|
|
123
|
+
export function removeWorktree(repoPath, worktreePath) {
|
|
124
|
+
try {
|
|
125
|
+
execSync(`git worktree remove "${worktreePath}" --force`, { cwd: repoPath });
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Best effort cleanup
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=worktree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worktree.js","sourceRoot":"","sources":["../../src/lib/worktree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAYzB;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE5C,0DAA0D;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACjE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC/C,CAAC;IAED,4EAA4E;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAChD,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;YAClB,sEAAsE;YACtE,IAAI,CAAC;gBACH,MAAM,YAAY,GAAG,QAAQ,CAAC,gDAAgD,EAAE;oBAC9E,GAAG,EAAE,YAAY;oBACjB,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,YAAY,EAAE,CAAC;oBACjB,uCAAuC;oBACvC,OAAO,eAAe,CAAC,YAAY,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,iCAAiC;YACnC,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QACtD,CAAC;aAAM,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;YAC9B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,+BAA+B,EAAE;YACxD,GAAG,EAAE,YAAY;YACjB,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,OAAO,EAAE,CAAC;YACZ,2CAA2C;YAC3C,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;YACzD,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBAC/E,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;YACnD,CAAC;YACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iBAAiB;IACnB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,UAAkB;IAC/D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,sBAAsB,UAAU,GAAG,EAAE;YAC3D,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,MAAc,EAAE,QAAsB;IACrF,6CAA6C;IAC7C,MAAM,IAAI,GAAG,QAAQ,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEnD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,kDAAkD,CAAC,CAAC;IACzG,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAS,CAAC;IAEhC,iCAAiC;IACjC,IAAI,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,4GAA4G,CAAC,CAAC;IACjJ,CAAC;IAED,kDAAkD;IAClD,IAAI,YAAoB,CAAC;IACzB,IAAI,MAAc,CAAC;IACnB,IAAI,UAAkB,CAAC;IAEvB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,mDAAmD;QACnD,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACtC,UAAU,GAAG,QAAQ,CAAC;IACxB,CAAC;SAAM,CAAC;QACN,0DAA0D;QAC1D,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAChD,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrC,UAAU,GAAG,QAAQ,CAAC;IACxB,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAEzE,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhD,kCAAkC;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,oBAAoB,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IAEnF,kCAAkC;IAClC,QAAQ,CACN,qBAAqB,YAAY,SAAS,MAAM,KAAK,IAAI,EAAE,EAC3D,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAC9D,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,YAAoB;IACnE,IAAI,CAAC;QACH,QAAQ,CAAC,wBAAwB,YAAY,WAAW,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,sBAAsB;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
export async function loadWorkflowSpec(workflowPath) {
|
|
5
|
+
// workflowPath can be a directory (containing workflow.yml) or a file
|
|
6
|
+
let filePath;
|
|
7
|
+
const stat = await fs.stat(workflowPath);
|
|
8
|
+
if (stat.isDirectory()) {
|
|
9
|
+
filePath = path.join(workflowPath, "workflow.yml");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
filePath = workflowPath;
|
|
13
|
+
}
|
|
14
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
15
|
+
const parsed = YAML.parse(raw);
|
|
16
|
+
if (!parsed?.id) {
|
|
17
|
+
throw new Error(`workflow.yml missing 'id' in ${filePath}`);
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(parsed.steps) || parsed.steps.length === 0) {
|
|
20
|
+
throw new Error(`workflow.yml missing 'steps' in ${filePath}`);
|
|
21
|
+
}
|
|
22
|
+
// Parse loop configs from raw YAML
|
|
23
|
+
for (const step of parsed.steps) {
|
|
24
|
+
const rawStep = step;
|
|
25
|
+
if (rawStep.type)
|
|
26
|
+
step.type = rawStep.type;
|
|
27
|
+
if (rawStep.loop)
|
|
28
|
+
step.loop = parseLoopConfig(rawStep.loop);
|
|
29
|
+
}
|
|
30
|
+
// Apply defaults
|
|
31
|
+
if (parsed.defaults) {
|
|
32
|
+
for (const step of parsed.steps) {
|
|
33
|
+
if (!step.executor && parsed.defaults.executor)
|
|
34
|
+
step.executor = parsed.defaults.executor;
|
|
35
|
+
if (!step.model && parsed.defaults.model)
|
|
36
|
+
step.model = parsed.defaults.model;
|
|
37
|
+
if (!step.timeout && parsed.defaults.timeout)
|
|
38
|
+
step.timeout = parsed.defaults.timeout;
|
|
39
|
+
if (step.max_retries === undefined && parsed.defaults.max_retries !== undefined) {
|
|
40
|
+
step.max_retries = parsed.defaults.max_retries;
|
|
41
|
+
}
|
|
42
|
+
if (!step.workdir && parsed.defaults.workdir)
|
|
43
|
+
step.workdir = parsed.defaults.workdir;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Default executor to openclaw for backward compat
|
|
47
|
+
for (const step of parsed.steps) {
|
|
48
|
+
if (!step.executor)
|
|
49
|
+
step.executor = "openclaw";
|
|
50
|
+
}
|
|
51
|
+
validateSteps(parsed.steps);
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
function parseLoopConfig(raw) {
|
|
55
|
+
return {
|
|
56
|
+
over: raw.over,
|
|
57
|
+
completion: raw.completion,
|
|
58
|
+
freshSession: raw.fresh_session ?? raw.freshSession,
|
|
59
|
+
verifyEach: raw.verify_each ?? raw.verifyEach,
|
|
60
|
+
verifyStep: raw.verify_step ?? raw.verifyStep,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function validateSteps(steps) {
|
|
64
|
+
const ids = new Set();
|
|
65
|
+
for (const step of steps) {
|
|
66
|
+
if (!step.id?.trim()) {
|
|
67
|
+
throw new Error("Step missing 'id'");
|
|
68
|
+
}
|
|
69
|
+
if (ids.has(step.id)) {
|
|
70
|
+
throw new Error(`Duplicate step id "${step.id}"`);
|
|
71
|
+
}
|
|
72
|
+
ids.add(step.id);
|
|
73
|
+
if (!step.input?.trim() && step.executor !== "self") {
|
|
74
|
+
throw new Error(`Step "${step.id}" missing 'input'`);
|
|
75
|
+
}
|
|
76
|
+
// Validate loop config
|
|
77
|
+
if (step.type === "loop") {
|
|
78
|
+
if (!step.loop) {
|
|
79
|
+
throw new Error(`Step "${step.id}" has type=loop but no loop config`);
|
|
80
|
+
}
|
|
81
|
+
if (step.loop.over !== "stories") {
|
|
82
|
+
throw new Error(`Step "${step.id}" loop.over must be "stories"`);
|
|
83
|
+
}
|
|
84
|
+
if (step.loop.verifyEach && step.loop.verifyStep && !ids.has(step.loop.verifyStep)) {
|
|
85
|
+
// verifyStep might come later in the steps array — defer this check
|
|
86
|
+
// Actually it usually comes after, so we just skip validation here
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// openclaw executor requires agent
|
|
90
|
+
if (step.executor === "openclaw" && !step.agent) {
|
|
91
|
+
throw new Error(`Step "${step.id}" uses openclaw executor but has no 'agent'`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/workflow/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve {{key}} placeholders in a template against a context object.
|
|
3
|
+
* Case-insensitive key lookup.
|
|
4
|
+
*/
|
|
5
|
+
export declare function resolveTemplate(template: string, context: Record<string, string>): string;
|
|
6
|
+
/**
|
|
7
|
+
* Extract KEY: value pairs from output text.
|
|
8
|
+
* Keys must be uppercase with optional underscores.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractContext(output: string): Record<string, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Parse STORIES_JSON from step output.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseStoriesJson(output: string): any[] | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve {{key}} placeholders in a template against a context object.
|
|
3
|
+
* Case-insensitive key lookup.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveTemplate(template, context) {
|
|
6
|
+
return template.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (_match, key) => {
|
|
7
|
+
if (key in context)
|
|
8
|
+
return context[key];
|
|
9
|
+
const lower = key.toLowerCase();
|
|
10
|
+
if (lower in context)
|
|
11
|
+
return context[lower];
|
|
12
|
+
// Try uppercase
|
|
13
|
+
const upper = key.toUpperCase();
|
|
14
|
+
if (upper in context)
|
|
15
|
+
return context[upper];
|
|
16
|
+
return `[missing: ${key}]`;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extract KEY: value pairs from output text.
|
|
21
|
+
* Keys must be uppercase with optional underscores.
|
|
22
|
+
*/
|
|
23
|
+
export function extractContext(output) {
|
|
24
|
+
const context = {};
|
|
25
|
+
for (const line of output.split("\n")) {
|
|
26
|
+
const match = line.match(/^([A-Z][A-Z0-9_]+):\s*(.+)$/);
|
|
27
|
+
if (match && !match[1].startsWith("STORIES_JSON")) {
|
|
28
|
+
context[match[1].toLowerCase()] = match[2].trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return context;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse STORIES_JSON from step output.
|
|
35
|
+
*/
|
|
36
|
+
export function parseStoriesJson(output) {
|
|
37
|
+
const lines = output.split("\n");
|
|
38
|
+
const startIdx = lines.findIndex((l) => l.startsWith("STORIES_JSON:"));
|
|
39
|
+
if (startIdx === -1)
|
|
40
|
+
return null;
|
|
41
|
+
const firstLine = lines[startIdx].slice("STORIES_JSON:".length).trim();
|
|
42
|
+
const jsonLines = [firstLine];
|
|
43
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
44
|
+
if (/^[A-Z_]+:\s/.test(lines[i]))
|
|
45
|
+
break;
|
|
46
|
+
jsonLines.push(lines[i]);
|
|
47
|
+
}
|
|
48
|
+
const jsonText = jsonLines.join("\n").trim();
|
|
49
|
+
const stories = JSON.parse(jsonText);
|
|
50
|
+
if (!Array.isArray(stories)) {
|
|
51
|
+
throw new Error("STORIES_JSON must be an array");
|
|
52
|
+
}
|
|
53
|
+
if (stories.length > 30) {
|
|
54
|
+
throw new Error(`STORIES_JSON has ${stories.length} stories, max is 30`);
|
|
55
|
+
}
|
|
56
|
+
// Validate each story
|
|
57
|
+
const seenIds = new Set();
|
|
58
|
+
for (let i = 0; i < stories.length; i++) {
|
|
59
|
+
const s = stories[i];
|
|
60
|
+
const ac = s.acceptanceCriteria ?? s.acceptance_criteria;
|
|
61
|
+
if (!s.id || !s.title || !s.description || !Array.isArray(ac) || ac.length === 0) {
|
|
62
|
+
throw new Error(`Story at index ${i} missing required fields (id, title, description, acceptanceCriteria)`);
|
|
63
|
+
}
|
|
64
|
+
if (seenIds.has(s.id)) {
|
|
65
|
+
throw new Error(`Duplicate story id "${s.id}"`);
|
|
66
|
+
}
|
|
67
|
+
seenIds.add(s.id);
|
|
68
|
+
}
|
|
69
|
+
return stories;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/workflow/template.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type ExecutorType = "openclaw" | "claude-code" | "codex" | "self";
|
|
2
|
+
export type LoopConfig = {
|
|
3
|
+
over: "stories";
|
|
4
|
+
completion: "all_done";
|
|
5
|
+
freshSession?: boolean;
|
|
6
|
+
verifyEach?: boolean;
|
|
7
|
+
verifyStep?: string;
|
|
8
|
+
};
|
|
9
|
+
export type StepFailure = {
|
|
10
|
+
retry_step?: string;
|
|
11
|
+
max_retries?: number;
|
|
12
|
+
escalate_to?: string;
|
|
13
|
+
on_exhausted?: {
|
|
14
|
+
escalate_to?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type WorkflowStep = {
|
|
18
|
+
id: string;
|
|
19
|
+
executor?: ExecutorType;
|
|
20
|
+
agent?: string;
|
|
21
|
+
model?: string;
|
|
22
|
+
workdir?: string;
|
|
23
|
+
type?: "single" | "loop";
|
|
24
|
+
loop?: LoopConfig;
|
|
25
|
+
input: string;
|
|
26
|
+
expects?: string;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
max_retries?: number;
|
|
29
|
+
on_fail?: StepFailure;
|
|
30
|
+
};
|
|
31
|
+
export type AgentRole = "analysis" | "coding" | "verification" | "testing" | "pr" | "scanning";
|
|
32
|
+
export type WorkflowAgent = {
|
|
33
|
+
id: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
role?: AgentRole;
|
|
37
|
+
workspace: {
|
|
38
|
+
baseDir: string;
|
|
39
|
+
files: Record<string, string>;
|
|
40
|
+
skills?: string[];
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export type WorkflowDefaults = {
|
|
44
|
+
executor?: ExecutorType;
|
|
45
|
+
model?: string;
|
|
46
|
+
timeout?: number;
|
|
47
|
+
max_retries?: number;
|
|
48
|
+
workdir?: string;
|
|
49
|
+
};
|
|
50
|
+
export type WorkflowSpec = {
|
|
51
|
+
id: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
version?: number;
|
|
54
|
+
description?: string;
|
|
55
|
+
defaults?: WorkflowDefaults;
|
|
56
|
+
context?: Record<string, string>;
|
|
57
|
+
agents?: WorkflowAgent[];
|
|
58
|
+
steps: WorkflowStep[];
|
|
59
|
+
};
|
|
60
|
+
export interface CallbackConfig {
|
|
61
|
+
url: string;
|
|
62
|
+
method?: string;
|
|
63
|
+
headers?: Record<string, string>;
|
|
64
|
+
bodyTemplate?: Record<string, any>;
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ghostwater/soulforge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pluggable executor workflow engine for AI coding agents. Dispatch workflow steps to Claude Code, Codex CLI, or human review checkpoints.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"soulforge": "dist/cli/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/cli/cli.js",
|
|
14
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
15
|
+
"start": "node dist/cli/cli.js",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"workflow",
|
|
20
|
+
"ai",
|
|
21
|
+
"coding-agent",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"codex",
|
|
24
|
+
"executor",
|
|
25
|
+
"daemon",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "Ghostwater Studio",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/ghostwater-ai/soulforge.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/ghostwater-ai/soulforge",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"workflows",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"yaml": "^2.4.5",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"@types/node": "^22.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|