vibe-coding-master 0.0.6 → 0.0.8
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 +207 -66
- package/dist/backend/adapters/filesystem.js +13 -0
- package/dist/backend/adapters/git-adapter.js +79 -1
- package/dist/backend/adapters/translation-provider.js +145 -0
- package/dist/backend/api/artifact-routes.js +16 -7
- package/dist/backend/api/harness-routes.js +22 -0
- package/dist/backend/api/message-routes.js +2 -0
- package/dist/backend/api/project-routes.js +3 -8
- package/dist/backend/api/task-routes.js +14 -0
- package/dist/backend/api/translation-routes.js +70 -0
- package/dist/backend/runtime/node-pty-runtime.js +20 -18
- package/dist/backend/server.js +33 -2
- package/dist/backend/services/app-settings-service.js +128 -0
- package/dist/backend/services/artifact-service.js +7 -4
- package/dist/backend/services/claude-transcript-service.js +509 -0
- package/dist/backend/services/command-dispatcher.js +4 -2
- package/dist/backend/services/harness-service.js +196 -0
- package/dist/backend/services/message-service.js +1 -1
- package/dist/backend/services/project-service.js +50 -9
- package/dist/backend/services/session-service.js +13 -9
- package/dist/backend/services/status-service.js +79 -1
- package/dist/backend/services/task-service.js +118 -4
- package/dist/backend/services/translation-prompts.js +173 -0
- package/dist/backend/services/translation-queue.js +39 -0
- package/dist/backend/services/translation-service.js +546 -0
- package/dist/backend/templates/handoff.js +32 -0
- package/dist/backend/templates/harness/architect-agent.js +12 -0
- package/dist/backend/templates/harness/claude-root.js +14 -0
- package/dist/backend/templates/harness/coder-agent.js +11 -0
- package/dist/backend/templates/harness/gitignore.js +9 -0
- package/dist/backend/templates/harness/project-manager-agent.js +14 -0
- package/dist/backend/templates/harness/reviewer-agent.js +13 -0
- package/dist/backend/ws/translation-ws.js +35 -0
- package/dist/shared/types/harness.js +1 -0
- package/dist/shared/types/translation.js +5 -0
- package/dist/shared/validation/artifact-check.js +15 -1
- package/dist/shared/validation/language-detect.js +46 -0
- package/dist-frontend/assets/index-CuiNNOzj.css +32 -0
- package/dist-frontend/assets/index-D59GuHCR.js +58 -0
- package/dist-frontend/index.html +2 -2
- package/docs/cc-best-practices.md +109 -40
- package/docs/product-design.md +370 -1374
- package/docs/v1-architecture-design.md +595 -1114
- package/docs/v1-implementation-plan.md +898 -1603
- package/package.json +1 -1
- package/scripts/verify-package.mjs +8 -0
- package/dist/backend/templates/role-messaging-context.js +0 -44
- package/dist-frontend/assets/index-Bah6k-Ix.css +0 -32
- package/dist-frontend/assets/index-EMaQuIB6.js +0 -58
- package/docs/v1-message-bus-orchestration-design.md +0 -534
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
|
|
2
2
|
import { VcmError } from "../errors.js";
|
|
3
|
+
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
3
4
|
export function registerArtifactRoutes(app, deps) {
|
|
4
5
|
app.get("/api/tasks/:taskSlug/artifacts", async (request) => {
|
|
5
6
|
const project = await requireCurrentProject(deps.projectService);
|
|
6
7
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
8
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
7
9
|
return deps.artifactService.listArtifacts({
|
|
8
|
-
repoRoot:
|
|
10
|
+
repoRoot: taskRepoRoot,
|
|
9
11
|
handoffDir: task.handoffDir
|
|
10
12
|
});
|
|
11
13
|
});
|
|
12
14
|
app.get("/api/tasks/:taskSlug/artifacts/:artifactName", async (request) => {
|
|
13
15
|
const project = await requireCurrentProject(deps.projectService);
|
|
14
16
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
15
|
-
const
|
|
17
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
18
|
+
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
16
19
|
const artifactPath = artifactNameToPath(paths, request.params.artifactName);
|
|
17
20
|
return {
|
|
18
21
|
path: artifactPath,
|
|
19
22
|
content: await deps.artifactService.readArtifact({
|
|
20
|
-
repoRoot:
|
|
23
|
+
repoRoot: taskRepoRoot,
|
|
21
24
|
artifactPath
|
|
22
25
|
})
|
|
23
26
|
};
|
|
@@ -26,10 +29,11 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
26
29
|
const project = await requireCurrentProject(deps.projectService);
|
|
27
30
|
const role = parseDispatchableRole(request.params.role);
|
|
28
31
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
32
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
29
33
|
return {
|
|
30
34
|
role,
|
|
31
35
|
content: await deps.artifactService.readRoleCommand({
|
|
32
|
-
repoRoot:
|
|
36
|
+
repoRoot: taskRepoRoot,
|
|
33
37
|
handoffDir: task.handoffDir,
|
|
34
38
|
role
|
|
35
39
|
})
|
|
@@ -39,8 +43,9 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
39
43
|
const project = await requireCurrentProject(deps.projectService);
|
|
40
44
|
const role = parseDispatchableRole(request.params.role);
|
|
41
45
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
46
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
42
47
|
await deps.artifactService.saveRoleCommand({
|
|
43
|
-
repoRoot:
|
|
48
|
+
repoRoot: taskRepoRoot,
|
|
44
49
|
handoffDir: task.handoffDir,
|
|
45
50
|
role,
|
|
46
51
|
content: request.body.content
|
|
@@ -57,11 +62,12 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
57
62
|
});
|
|
58
63
|
}
|
|
59
64
|
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
60
|
-
const
|
|
65
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
66
|
+
const paths = deps.artifactService.getHandoffPaths(taskRepoRoot, task.handoffDir);
|
|
61
67
|
return {
|
|
62
68
|
role: request.params.role,
|
|
63
69
|
content: await deps.artifactService.readArtifact({
|
|
64
|
-
repoRoot:
|
|
70
|
+
repoRoot: taskRepoRoot,
|
|
65
71
|
artifactPath: paths.roleLogPaths[request.params.role]
|
|
66
72
|
})
|
|
67
73
|
};
|
|
@@ -90,6 +96,9 @@ function artifactNameToPath(paths, artifactName) {
|
|
|
90
96
|
if (artifactName === "review-report.md") {
|
|
91
97
|
return paths.reviewReportPath;
|
|
92
98
|
}
|
|
99
|
+
if (artifactName === "docs-sync-report.md") {
|
|
100
|
+
return paths.docsSyncReportPath;
|
|
101
|
+
}
|
|
93
102
|
throw new VcmError({
|
|
94
103
|
code: "ARTIFACT_UNKNOWN",
|
|
95
104
|
message: `Unknown artifact: ${artifactName}`,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
export function registerHarnessRoutes(app, deps) {
|
|
3
|
+
app.get("/api/projects/harness", async () => {
|
|
4
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
5
|
+
return deps.harnessService.getHarnessStatus(project.repoRoot);
|
|
6
|
+
});
|
|
7
|
+
app.post("/api/projects/harness/apply", async () => {
|
|
8
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
9
|
+
return deps.harnessService.applyHarness(project.repoRoot);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
async function requireCurrentProject(projectService) {
|
|
13
|
+
const project = await projectService.getCurrentProject();
|
|
14
|
+
if (!project) {
|
|
15
|
+
throw new VcmError({
|
|
16
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
17
|
+
message: "Connect a repository first.",
|
|
18
|
+
statusCode: 409
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return project;
|
|
22
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VcmError } from "../errors.js";
|
|
2
|
+
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
2
3
|
export function registerMessageRoutes(app, deps) {
|
|
3
4
|
app.get("/api/tasks/:taskSlug/messages", async (request) => {
|
|
4
5
|
const context = await getRouteContext(deps, request.params.taskSlug);
|
|
@@ -72,6 +73,7 @@ async function getRouteContext(deps, taskSlug) {
|
|
|
72
73
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
73
74
|
return {
|
|
74
75
|
repoRoot: project.repoRoot,
|
|
76
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
75
77
|
stateRoot: config.stateRoot,
|
|
76
78
|
handoffDir: task.handoffDir,
|
|
77
79
|
taskSlug
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
export function registerProjectRoutes(app, deps) {
|
|
2
2
|
app.get("/api/health", async () => ({ ok: true }));
|
|
3
|
+
app.get("/api/projects/recent", async () => {
|
|
4
|
+
return deps.projectService.getRecentRepositoryPaths();
|
|
5
|
+
});
|
|
3
6
|
app.post("/api/projects/connect", async (request) => {
|
|
4
7
|
return deps.projectService.connectProject(request.body);
|
|
5
8
|
});
|
|
6
9
|
app.get("/api/projects/current", async () => {
|
|
7
10
|
return deps.projectService.getCurrentProject();
|
|
8
11
|
});
|
|
9
|
-
app.get("/api/projects/harness", async () => ({
|
|
10
|
-
checks: [
|
|
11
|
-
{
|
|
12
|
-
name: "local-gui",
|
|
13
|
-
status: "ok"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}));
|
|
17
12
|
}
|
|
@@ -16,6 +16,20 @@ export function registerTaskRoutes(app, deps) {
|
|
|
16
16
|
const project = await requireCurrentProject(deps.projectService);
|
|
17
17
|
return deps.statusService.getTaskStatus(project.repoRoot, request.params.taskSlug);
|
|
18
18
|
});
|
|
19
|
+
app.post("/api/tasks/:taskSlug/cleanup", async (request) => {
|
|
20
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
21
|
+
const sessions = await deps.sessionService.listRoleSessions(project.repoRoot, request.params.taskSlug);
|
|
22
|
+
const running = sessions.filter((session) => session.status === "running");
|
|
23
|
+
if (running.length > 0) {
|
|
24
|
+
throw new VcmError({
|
|
25
|
+
code: "TASK_SESSIONS_RUNNING",
|
|
26
|
+
message: "Stop all role sessions before cleaning up the task.",
|
|
27
|
+
statusCode: 409,
|
|
28
|
+
hint: running.map((session) => session.role).join(", ")
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return deps.taskService.cleanupTask(project.repoRoot, request.params.taskSlug, request.body ?? {});
|
|
32
|
+
});
|
|
19
33
|
}
|
|
20
34
|
async function requireCurrentProject(projectService) {
|
|
21
35
|
const project = await projectService.getCurrentProject();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { isRoleName } from "../../shared/constants.js";
|
|
2
|
+
import { VcmError } from "../errors.js";
|
|
3
|
+
export function registerTranslationRoutes(app, deps) {
|
|
4
|
+
app.get("/api/translation/settings", async () => {
|
|
5
|
+
return deps.translationService.getSettings();
|
|
6
|
+
});
|
|
7
|
+
app.put("/api/translation/settings", async (request) => {
|
|
8
|
+
const { apiKey, ...settings } = request.body ?? {};
|
|
9
|
+
return deps.translationService.updateSettings(settings, apiKey !== undefined ? { apiKey } : undefined);
|
|
10
|
+
});
|
|
11
|
+
app.get("/api/translation/prompts", async () => {
|
|
12
|
+
return deps.translationService.getPromptPreviews();
|
|
13
|
+
});
|
|
14
|
+
app.post("/api/translation/test", async () => {
|
|
15
|
+
return deps.translationService.testProvider();
|
|
16
|
+
});
|
|
17
|
+
app.post("/api/tasks/:taskSlug/sessions/:role/translation/input", async (request) => {
|
|
18
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
19
|
+
const role = parseRole(request.params.role);
|
|
20
|
+
await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
21
|
+
return deps.translationService.translateUserInput({
|
|
22
|
+
repoRoot: project.repoRoot,
|
|
23
|
+
taskSlug: request.params.taskSlug,
|
|
24
|
+
role,
|
|
25
|
+
...(request.body ?? { text: "" })
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
app.post("/api/tasks/:taskSlug/sessions/:role/translation/send", async (request) => {
|
|
29
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
30
|
+
const role = parseRole(request.params.role);
|
|
31
|
+
await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
32
|
+
await deps.translationService.sendTranslatedInput({
|
|
33
|
+
repoRoot: project.repoRoot,
|
|
34
|
+
taskSlug: request.params.taskSlug,
|
|
35
|
+
role,
|
|
36
|
+
englishText: request.body?.englishText ?? ""
|
|
37
|
+
});
|
|
38
|
+
return { ok: true };
|
|
39
|
+
});
|
|
40
|
+
app.post("/api/translation/sessions/:sessionId/clear", async (request) => {
|
|
41
|
+
await requireCurrentProject(deps.projectService);
|
|
42
|
+
deps.translationService.clearSession(request.params.sessionId);
|
|
43
|
+
return { ok: true };
|
|
44
|
+
});
|
|
45
|
+
app.post("/api/translation/sessions/:sessionId/retry/:translationId", async (request) => {
|
|
46
|
+
await requireCurrentProject(deps.projectService);
|
|
47
|
+
return deps.translationService.retryTranslation(request.params.sessionId, request.params.translationId);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function parseRole(role) {
|
|
51
|
+
if (!isRoleName(role)) {
|
|
52
|
+
throw new VcmError({
|
|
53
|
+
code: "UNKNOWN_ROLE",
|
|
54
|
+
message: `Unknown role: ${role}`,
|
|
55
|
+
statusCode: 400
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return role;
|
|
59
|
+
}
|
|
60
|
+
async function requireCurrentProject(projectService) {
|
|
61
|
+
const project = await projectService.getCurrentProject();
|
|
62
|
+
if (!project) {
|
|
63
|
+
throw new VcmError({
|
|
64
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
65
|
+
message: "Connect a repository first.",
|
|
66
|
+
statusCode: 409
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return project;
|
|
70
|
+
}
|
|
@@ -109,27 +109,29 @@ export function createNodePtyTerminalRuntime(deps) {
|
|
|
109
109
|
entry.process.kill();
|
|
110
110
|
return create(entry.input, sessionId);
|
|
111
111
|
},
|
|
112
|
-
subscribe(sessionId, listener) {
|
|
112
|
+
subscribe(sessionId, listener, options = {}) {
|
|
113
113
|
const entry = getEntry(entries, sessionId);
|
|
114
114
|
entry.listeners.add(listener);
|
|
115
|
-
|
|
116
|
-
.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
115
|
+
if (options.replay !== false) {
|
|
116
|
+
void deps.fs.readText(entry.input.logPath)
|
|
117
|
+
.then((data) => {
|
|
118
|
+
if (!data || !entry.listeners.has(listener)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
listener({
|
|
122
|
+
id: `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
|
123
|
+
sessionId,
|
|
124
|
+
taskSlug: entry.session.taskSlug,
|
|
125
|
+
role: entry.session.role,
|
|
126
|
+
type: "output",
|
|
127
|
+
timestamp: now(),
|
|
128
|
+
data
|
|
129
|
+
});
|
|
130
|
+
})
|
|
131
|
+
.catch(() => {
|
|
132
|
+
// The log file may not exist yet for a brand-new session.
|
|
128
133
|
});
|
|
129
|
-
}
|
|
130
|
-
.catch(() => {
|
|
131
|
-
// The log file may not exist yet for a brand-new session.
|
|
132
|
-
});
|
|
134
|
+
}
|
|
133
135
|
return () => {
|
|
134
136
|
entry.listeners.delete(listener);
|
|
135
137
|
};
|
package/dist/backend/server.js
CHANGED
|
@@ -8,20 +8,28 @@ import { createClaudeAdapter } from "./adapters/claude-adapter.js";
|
|
|
8
8
|
import { createCommandRunner } from "./adapters/command-runner.js";
|
|
9
9
|
import { createCommandDispatcher } from "./services/command-dispatcher.js";
|
|
10
10
|
import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
11
|
+
import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
12
|
+
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
13
|
+
import { createHarnessService } from "./services/harness-service.js";
|
|
11
14
|
import { createNodeFileSystemAdapter } from "./adapters/filesystem.js";
|
|
12
15
|
import { createNodePtyTerminalRuntime } from "./runtime/node-pty-runtime.js";
|
|
16
|
+
import { createOpenAiCompatibleTranslationProvider } from "./adapters/translation-provider.js";
|
|
13
17
|
import { createProjectService } from "./services/project-service.js";
|
|
14
18
|
import { createSessionRegistry } from "./runtime/session-registry.js";
|
|
15
19
|
import { createSessionService } from "./services/session-service.js";
|
|
16
20
|
import { createMessageService } from "./services/message-service.js";
|
|
17
21
|
import { createStatusService } from "./services/status-service.js";
|
|
18
22
|
import { createTaskService } from "./services/task-service.js";
|
|
23
|
+
import { createTranslationService } from "./services/translation-service.js";
|
|
19
24
|
import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
25
|
+
import { registerHarnessRoutes } from "./api/harness-routes.js";
|
|
20
26
|
import { registerMessageRoutes } from "./api/message-routes.js";
|
|
21
27
|
import { registerProjectRoutes } from "./api/project-routes.js";
|
|
22
28
|
import { registerSessionRoutes } from "./api/session-routes.js";
|
|
23
29
|
import { registerTaskRoutes } from "./api/task-routes.js";
|
|
30
|
+
import { registerTranslationRoutes } from "./api/translation-routes.js";
|
|
24
31
|
import { registerTerminalWs } from "./ws/terminal-ws.js";
|
|
32
|
+
import { registerTranslationWs } from "./ws/translation-ws.js";
|
|
25
33
|
import { toVcmError } from "./errors.js";
|
|
26
34
|
export async function createServer(deps, options = {}) {
|
|
27
35
|
const app = Fastify({
|
|
@@ -38,10 +46,15 @@ export async function createServer(deps, options = {}) {
|
|
|
38
46
|
});
|
|
39
47
|
});
|
|
40
48
|
registerProjectRoutes(app, { projectService: deps.projectService });
|
|
49
|
+
registerHarnessRoutes(app, {
|
|
50
|
+
projectService: deps.projectService,
|
|
51
|
+
harnessService: deps.harnessService
|
|
52
|
+
});
|
|
41
53
|
registerTaskRoutes(app, {
|
|
42
54
|
projectService: deps.projectService,
|
|
43
55
|
taskService: deps.taskService,
|
|
44
|
-
statusService: deps.statusService
|
|
56
|
+
statusService: deps.statusService,
|
|
57
|
+
sessionService: deps.sessionService
|
|
45
58
|
});
|
|
46
59
|
registerSessionRoutes(app, {
|
|
47
60
|
projectService: deps.projectService,
|
|
@@ -58,7 +71,13 @@ export async function createServer(deps, options = {}) {
|
|
|
58
71
|
taskService: deps.taskService,
|
|
59
72
|
messageService: deps.messageService
|
|
60
73
|
});
|
|
74
|
+
registerTranslationRoutes(app, {
|
|
75
|
+
projectService: deps.projectService,
|
|
76
|
+
taskService: deps.taskService,
|
|
77
|
+
translationService: deps.translationService
|
|
78
|
+
});
|
|
61
79
|
registerTerminalWs(app, { runtime: deps.runtime });
|
|
80
|
+
registerTranslationWs(app, { translationService: deps.translationService });
|
|
62
81
|
if (options.staticDir) {
|
|
63
82
|
await app.register(fastifyStatic, {
|
|
64
83
|
root: options.staticDir,
|
|
@@ -90,10 +109,12 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
90
109
|
const runner = createCommandRunner();
|
|
91
110
|
const git = createGitAdapter(runner);
|
|
92
111
|
const claude = createClaudeAdapter(runner);
|
|
112
|
+
const appSettings = createAppSettingsService({ fs });
|
|
93
113
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
94
114
|
const registry = createSessionRegistry();
|
|
95
115
|
const artifactService = createArtifactService(fs);
|
|
96
|
-
const
|
|
116
|
+
const harnessService = createHarnessService({ fs });
|
|
117
|
+
const projectService = createProjectService({ fs, git, claude, appSettings });
|
|
97
118
|
const taskService = createTaskService({ fs, git, artifactService, projectService });
|
|
98
119
|
const sessionService = createSessionService({
|
|
99
120
|
fs,
|
|
@@ -123,14 +144,24 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
123
144
|
sessionService,
|
|
124
145
|
taskService
|
|
125
146
|
});
|
|
147
|
+
const translationService = createTranslationService({
|
|
148
|
+
runtime,
|
|
149
|
+
sessionRegistry: registry,
|
|
150
|
+
transcripts: createClaudeTranscriptService(),
|
|
151
|
+
sessionService,
|
|
152
|
+
appSettings,
|
|
153
|
+
provider: createOpenAiCompatibleTranslationProvider()
|
|
154
|
+
});
|
|
126
155
|
return {
|
|
127
156
|
projectService,
|
|
128
157
|
taskService,
|
|
129
158
|
sessionService,
|
|
130
159
|
artifactService,
|
|
160
|
+
harnessService,
|
|
131
161
|
commandDispatcher,
|
|
132
162
|
messageService,
|
|
133
163
|
statusService,
|
|
164
|
+
translationService,
|
|
134
165
|
runtime
|
|
135
166
|
};
|
|
136
167
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
const MAX_RECENT_REPOSITORIES = 5;
|
|
4
|
+
export function createAppSettingsService(deps) {
|
|
5
|
+
const settingsPath = deps.settingsPath ?? path.join(homedir(), ".vcm", "settings.json");
|
|
6
|
+
const legacySettingsPath = deps.legacySettingsPath
|
|
7
|
+
?? path.join(homedir(), ".vibe-coding-master", "settings.json");
|
|
8
|
+
const legacyTranslationPath = deps.legacyTranslationPath
|
|
9
|
+
?? path.join(homedir(), ".vibe-coding-master", "translation.json");
|
|
10
|
+
let cachedSettings = null;
|
|
11
|
+
async function loadSettings() {
|
|
12
|
+
if (cachedSettings) {
|
|
13
|
+
return cachedSettings;
|
|
14
|
+
}
|
|
15
|
+
let raw = {};
|
|
16
|
+
let shouldSave = false;
|
|
17
|
+
if (await deps.fs.pathExists(settingsPath)) {
|
|
18
|
+
raw = await deps.fs.readJson(settingsPath);
|
|
19
|
+
}
|
|
20
|
+
else if (await deps.fs.pathExists(legacySettingsPath)) {
|
|
21
|
+
raw = await deps.fs.readJson(legacySettingsPath);
|
|
22
|
+
shouldSave = true;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
shouldSave = true;
|
|
26
|
+
}
|
|
27
|
+
if (!raw.translation && await deps.fs.pathExists(legacyTranslationPath)) {
|
|
28
|
+
raw = {
|
|
29
|
+
...raw,
|
|
30
|
+
translation: normalizeTranslationConfig(await deps.fs.readJson(legacyTranslationPath))
|
|
31
|
+
};
|
|
32
|
+
shouldSave = true;
|
|
33
|
+
}
|
|
34
|
+
cachedSettings = normalizeSettingsFile(raw);
|
|
35
|
+
if (shouldSave) {
|
|
36
|
+
await saveSettings(cachedSettings);
|
|
37
|
+
}
|
|
38
|
+
return cachedSettings;
|
|
39
|
+
}
|
|
40
|
+
async function saveSettings(settings) {
|
|
41
|
+
cachedSettings = settings;
|
|
42
|
+
await deps.fs.writeJsonAtomic(settingsPath, settings);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
loadSettings,
|
|
46
|
+
async updateTranslationConfig(config) {
|
|
47
|
+
const current = await loadSettings();
|
|
48
|
+
const translation = normalizeTranslationConfig(config) ?? { settings: {}, secrets: {} };
|
|
49
|
+
await saveSettings({
|
|
50
|
+
...current,
|
|
51
|
+
translation
|
|
52
|
+
});
|
|
53
|
+
return translation;
|
|
54
|
+
},
|
|
55
|
+
async getTranslationConfig() {
|
|
56
|
+
return (await loadSettings()).translation;
|
|
57
|
+
},
|
|
58
|
+
async getRecentRepositoryPaths() {
|
|
59
|
+
return (await loadSettings()).recentRepositoryPaths;
|
|
60
|
+
},
|
|
61
|
+
async recordRecentRepositoryPath(repoRoot) {
|
|
62
|
+
const normalizedPath = repoRoot.trim();
|
|
63
|
+
if (!normalizedPath) {
|
|
64
|
+
return (await loadSettings()).recentRepositoryPaths;
|
|
65
|
+
}
|
|
66
|
+
const current = await loadSettings();
|
|
67
|
+
const recentRepositoryPaths = normalizeRecentRepositoryPaths([
|
|
68
|
+
normalizedPath,
|
|
69
|
+
...current.recentRepositoryPaths
|
|
70
|
+
]);
|
|
71
|
+
await saveSettings({
|
|
72
|
+
...current,
|
|
73
|
+
recentRepositoryPaths
|
|
74
|
+
});
|
|
75
|
+
return recentRepositoryPaths;
|
|
76
|
+
},
|
|
77
|
+
getSettingsPath() {
|
|
78
|
+
return settingsPath;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function normalizeSettingsFile(input) {
|
|
83
|
+
return {
|
|
84
|
+
version: 1,
|
|
85
|
+
translation: normalizeTranslationConfig(input.translation),
|
|
86
|
+
recentRepositoryPaths: normalizeRecentRepositoryPaths(input.recentRepositoryPaths)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function normalizeTranslationConfig(input) {
|
|
90
|
+
if (!input || typeof input !== "object") {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const candidate = input;
|
|
94
|
+
const rawSettings = isObject(candidate.settings) ? candidate.settings : {};
|
|
95
|
+
const rawSecrets = isObject(candidate.secrets) ? candidate.secrets : {};
|
|
96
|
+
const { apiKey: settingsApiKey, ...settings } = rawSettings;
|
|
97
|
+
const apiKey = rawSecrets.apiKey ?? settingsApiKey;
|
|
98
|
+
return {
|
|
99
|
+
settings,
|
|
100
|
+
secrets: {
|
|
101
|
+
...rawSecrets,
|
|
102
|
+
...(apiKey !== undefined ? { apiKey } : {})
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function normalizeRecentRepositoryPaths(input) {
|
|
107
|
+
const paths = Array.isArray(input) ? input : [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
const normalized = [];
|
|
110
|
+
for (const value of paths) {
|
|
111
|
+
if (typeof value !== "string") {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const repoPath = value.trim();
|
|
115
|
+
if (!repoPath || seen.has(repoPath)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
seen.add(repoPath);
|
|
119
|
+
normalized.push(repoPath);
|
|
120
|
+
if (normalized.length >= MAX_RECENT_REPOSITORIES) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return normalized;
|
|
125
|
+
}
|
|
126
|
+
function isObject(value) {
|
|
127
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
128
|
+
}
|
|
@@ -3,13 +3,14 @@ import { DISPATCHABLE_ROLES, ROLE_NAMES } from "../../shared/constants.js";
|
|
|
3
3
|
import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js";
|
|
4
4
|
import { VcmError } from "../errors.js";
|
|
5
5
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
6
|
-
import { renderArchitecturePlanTemplate, renderImplementationLogTemplate, renderReviewReportTemplate, renderValidationLogTemplate } from "../templates/handoff.js";
|
|
6
|
+
import { renderArchitecturePlanTemplate, renderDocsSyncReportTemplate, renderImplementationLogTemplate, renderReviewReportTemplate, renderValidationLogTemplate } from "../templates/handoff.js";
|
|
7
7
|
import { renderRoleCommandTemplate } from "../templates/role-command.js";
|
|
8
8
|
const ARTIFACT_PATH_KEYS = [
|
|
9
9
|
["architecture-plan", "architecturePlanPath"],
|
|
10
10
|
["implementation-log", "implementationLogPath"],
|
|
11
11
|
["validation-log", "validationLogPath"],
|
|
12
|
-
["review-report", "reviewReportPath"]
|
|
12
|
+
["review-report", "reviewReportPath"],
|
|
13
|
+
["docs-sync-report", "docsSyncReportPath"]
|
|
13
14
|
];
|
|
14
15
|
const ROLE_COMMAND_PLACEHOLDER_PATTERN = /(^|\n)\s*(TBD|status:\s*draft)\s*(\n|$)/i;
|
|
15
16
|
export function createArtifactService(fs) {
|
|
@@ -35,7 +36,8 @@ export function createArtifactService(fs) {
|
|
|
35
36
|
architecturePlanPath: path.posix.join(handoffDir, "architecture-plan.md"),
|
|
36
37
|
implementationLogPath: path.posix.join(handoffDir, "implementation-log.md"),
|
|
37
38
|
validationLogPath: path.posix.join(handoffDir, "validation-log.md"),
|
|
38
|
-
reviewReportPath: path.posix.join(handoffDir, "review-report.md")
|
|
39
|
+
reviewReportPath: path.posix.join(handoffDir, "review-report.md"),
|
|
40
|
+
docsSyncReportPath: path.posix.join(handoffDir, "docs-sync-report.md")
|
|
39
41
|
};
|
|
40
42
|
},
|
|
41
43
|
async ensureHandoffStructure(input) {
|
|
@@ -54,7 +56,8 @@ export function createArtifactService(fs) {
|
|
|
54
56
|
[paths.architecturePlanPath, renderArchitecturePlanTemplate(input.taskSlug)],
|
|
55
57
|
[paths.implementationLogPath, renderImplementationLogTemplate(input.taskSlug)],
|
|
56
58
|
[paths.validationLogPath, renderValidationLogTemplate(input.taskSlug)],
|
|
57
|
-
[paths.reviewReportPath, renderReviewReportTemplate(input.taskSlug)]
|
|
59
|
+
[paths.reviewReportPath, renderReviewReportTemplate(input.taskSlug)],
|
|
60
|
+
[paths.docsSyncReportPath, renderDocsSyncReportTemplate(input.taskSlug)]
|
|
58
61
|
];
|
|
59
62
|
const created = [];
|
|
60
63
|
for (const [artifactPath, content] of files) {
|