symphifo 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 +201 -0
- package/NOTICE +13 -0
- package/README.md +394 -0
- package/SYMPHIFO.md +171 -0
- package/WORKFLOW.md +39 -0
- package/bin/symphifo.js +37 -0
- package/package.json +46 -0
- package/src/cli.ts +213 -0
- package/src/dashboard/app.js +1390 -0
- package/src/dashboard/index.html +139 -0
- package/src/dashboard/styles.css +1528 -0
- package/src/fixtures/local-issues.json +13 -0
- package/src/integrations/catalog.ts +151 -0
- package/src/mcp/server.ts +1237 -0
- package/src/routing/capability-resolver.ts +390 -0
- package/src/runtime/agent.ts +1050 -0
- package/src/runtime/api-server.ts +306 -0
- package/src/runtime/constants.ts +102 -0
- package/src/runtime/helpers.ts +134 -0
- package/src/runtime/issues.ts +456 -0
- package/src/runtime/logger.ts +59 -0
- package/src/runtime/providers.ts +310 -0
- package/src/runtime/run-local.ts +146 -0
- package/src/runtime/scheduler.ts +214 -0
- package/src/runtime/skills.ts +55 -0
- package/src/runtime/store.ts +313 -0
- package/src/runtime/types.ts +274 -0
- package/src/runtime/workflow.ts +185 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { extname, join } from "node:path";
|
|
3
|
+
import { env, argv, exit } from "node:process";
|
|
4
|
+
import { stringify as stringifyYaml } from "yaml";
|
|
5
|
+
import type { JsonRecord, WorkflowDefinition } from "./types.ts";
|
|
6
|
+
import {
|
|
7
|
+
PACKAGE_ROOT,
|
|
8
|
+
SOURCE_ROOT,
|
|
9
|
+
SOURCE_MARKER,
|
|
10
|
+
TARGET_ROOT,
|
|
11
|
+
WORKFLOW_TEMPLATE,
|
|
12
|
+
WORKFLOW_RENDERED,
|
|
13
|
+
WORKSPACE_ROOT,
|
|
14
|
+
} from "./constants.ts";
|
|
15
|
+
import {
|
|
16
|
+
now,
|
|
17
|
+
parseFrontMatter,
|
|
18
|
+
getNestedRecord,
|
|
19
|
+
getNestedString,
|
|
20
|
+
fail,
|
|
21
|
+
parseIntArg,
|
|
22
|
+
} from "./helpers.ts";
|
|
23
|
+
import { logger } from "./logger.ts";
|
|
24
|
+
import {
|
|
25
|
+
normalizeAgentProvider,
|
|
26
|
+
resolveAgentProfile,
|
|
27
|
+
resolveWorkflowAgentProviders,
|
|
28
|
+
} from "./providers.ts";
|
|
29
|
+
|
|
30
|
+
export function bootstrapSource(): void {
|
|
31
|
+
if (existsSync(SOURCE_MARKER)) return;
|
|
32
|
+
|
|
33
|
+
logger.info("Creating local source snapshot for Symphifo (local-only runtime)...");
|
|
34
|
+
|
|
35
|
+
const skipDirs = new Set([
|
|
36
|
+
".git", ".symphifo", "node_modules", ".venv", "data",
|
|
37
|
+
"dist", "build", ".turbo", ".next", ".nuxt", ".tanstack",
|
|
38
|
+
"coverage", "artifacts", "captures", "tmp", "temp",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const shouldSkip = (relativePath: string): boolean => {
|
|
42
|
+
const parts = relativePath.split("/");
|
|
43
|
+
if (parts.some((segment) => skipDirs.has(segment))) return true;
|
|
44
|
+
const base = relativePath.split("/").at(-1) ?? "";
|
|
45
|
+
if (base.startsWith("map_scan_") && extname(base) === ".json") return true;
|
|
46
|
+
if (extname(base) === ".xlsx") return true;
|
|
47
|
+
return false;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const copyRecursive = (source: string, target: string, rel = "") => {
|
|
51
|
+
mkdirSync(target, { recursive: true });
|
|
52
|
+
const items = readdirSync(source, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
for (const item of items) {
|
|
55
|
+
const nextRel = rel ? `${rel}/${item.name}` : item.name;
|
|
56
|
+
if (shouldSkip(nextRel)) continue;
|
|
57
|
+
|
|
58
|
+
const sourcePath = `${source}/${item.name}`;
|
|
59
|
+
const targetPath = `${target}/${item.name}`;
|
|
60
|
+
const itemStat = statSync(sourcePath);
|
|
61
|
+
|
|
62
|
+
if (item.isDirectory()) {
|
|
63
|
+
copyRecursive(sourcePath, targetPath, nextRel);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (item.isSymbolicLink() || itemStat.isSymbolicLink()) continue;
|
|
68
|
+
|
|
69
|
+
if (itemStat.isFile() || itemStat.isFIFO()) {
|
|
70
|
+
try {
|
|
71
|
+
const file = readFileSync(sourcePath);
|
|
72
|
+
writeFileSync(targetPath, file);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
75
|
+
logger.debug(`Skipped missing source file: ${sourcePath}`);
|
|
76
|
+
} else {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
mkdirSync(SOURCE_ROOT, { recursive: true });
|
|
85
|
+
copyRecursive(TARGET_ROOT, SOURCE_ROOT);
|
|
86
|
+
writeFileSync(SOURCE_MARKER, `${now()}\n`, "utf8");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function loadWorkflowDefinition(): WorkflowDefinition {
|
|
90
|
+
const template = WORKFLOW_TEMPLATE
|
|
91
|
+
? readFileSync(WORKFLOW_TEMPLATE, "utf8")
|
|
92
|
+
: [
|
|
93
|
+
"---",
|
|
94
|
+
"tracker:",
|
|
95
|
+
" kind: filesystem",
|
|
96
|
+
"workspace:",
|
|
97
|
+
` root: "${WORKSPACE_ROOT}"`,
|
|
98
|
+
"agent:",
|
|
99
|
+
" max_concurrent_agents: 2",
|
|
100
|
+
" max_attempts: 3",
|
|
101
|
+
"codex:",
|
|
102
|
+
' command: ""',
|
|
103
|
+
"---",
|
|
104
|
+
"",
|
|
105
|
+
"You are working on {{ issue.identifier }}.",
|
|
106
|
+
"",
|
|
107
|
+
"Title: {{ issue.title }}",
|
|
108
|
+
"Description:",
|
|
109
|
+
"{{ issue.description }}",
|
|
110
|
+
].join("\n");
|
|
111
|
+
|
|
112
|
+
const { config, body } = parseFrontMatter(template);
|
|
113
|
+
const normalizedConfig: JsonRecord = {
|
|
114
|
+
...config,
|
|
115
|
+
tracker: {
|
|
116
|
+
...getNestedRecord(config, "tracker"),
|
|
117
|
+
kind: "filesystem",
|
|
118
|
+
project_slug: "",
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const rendered = [
|
|
123
|
+
"---",
|
|
124
|
+
stringifyYaml(normalizedConfig).trim(),
|
|
125
|
+
"---",
|
|
126
|
+
"",
|
|
127
|
+
body,
|
|
128
|
+
"",
|
|
129
|
+
].join("\n");
|
|
130
|
+
|
|
131
|
+
const agentConfig = getNestedRecord(normalizedConfig, "agent");
|
|
132
|
+
const agentProvider = normalizeAgentProvider(getNestedString(agentConfig, "provider", "codex"));
|
|
133
|
+
const agentProfile = getNestedString(agentConfig, "profile");
|
|
134
|
+
const resolvedProfile = resolveAgentProfile(agentProfile);
|
|
135
|
+
const agentProviders = resolveWorkflowAgentProviders(normalizedConfig, agentProvider, agentProfile, "");
|
|
136
|
+
|
|
137
|
+
writeFileSync(WORKFLOW_RENDERED, rendered, "utf8");
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
workflowPath: WORKFLOW_TEMPLATE || WORKFLOW_RENDERED,
|
|
141
|
+
rendered,
|
|
142
|
+
config: normalizedConfig,
|
|
143
|
+
promptTemplate: body,
|
|
144
|
+
agentProvider,
|
|
145
|
+
agentProfile,
|
|
146
|
+
agentProfilePath: resolvedProfile.profilePath,
|
|
147
|
+
agentProfileInstructions: resolvedProfile.instructions,
|
|
148
|
+
agentProviders,
|
|
149
|
+
afterCreateHook: getNestedString(getNestedRecord(normalizedConfig, "hooks"), "after_create"),
|
|
150
|
+
beforeRunHook: getNestedString(getNestedRecord(normalizedConfig, "hooks"), "before_run"),
|
|
151
|
+
afterRunHook: getNestedString(getNestedRecord(normalizedConfig, "hooks"), "after_run"),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function parsePort(args: string[]): number | undefined {
|
|
156
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
157
|
+
const arg = args[i];
|
|
158
|
+
|
|
159
|
+
if (arg === "--help" || arg === "-h") {
|
|
160
|
+
console.log(
|
|
161
|
+
`Usage: ${argv[1]} [options]\n` +
|
|
162
|
+
"Options:\n" +
|
|
163
|
+
" --port <n> Start local dashboard (default: no UI and single batch run)\n" +
|
|
164
|
+
" --workspace <path> Target workspace root (default: current directory)\n" +
|
|
165
|
+
" --persistence <path> Persistence root (default: current directory)\n" +
|
|
166
|
+
" --concurrency <n> Maximum number of parallel issue runners\n" +
|
|
167
|
+
" --attempts <n> Maximum attempts per issue\n" +
|
|
168
|
+
" --poll <ms> Polling interval for the scheduler\n" +
|
|
169
|
+
" --once Run one local batch and exit\n" +
|
|
170
|
+
" --help Show this message",
|
|
171
|
+
);
|
|
172
|
+
exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (arg === "--port") {
|
|
176
|
+
const value = args[i + 1];
|
|
177
|
+
if (!value || !/^\d+$/.test(value)) {
|
|
178
|
+
fail(`Invalid value for --port: ${value ?? "<empty>"}`);
|
|
179
|
+
}
|
|
180
|
+
return parseIntArg(value, 4040);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|