vibe-coding-master 0.3.23 → 0.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/adapters/git-adapter.js +53 -0
- package/dist/backend/api/artifact-routes.js +1 -28
- package/dist/backend/api/harness-routes.js +10 -0
- package/dist/backend/api/task-routes.js +2 -6
- package/dist/backend/gateway/gateway-service.js +3 -16
- package/dist/backend/server.js +3 -1
- package/dist/backend/services/artifact-service.js +1 -30
- package/dist/backend/services/harness-service.js +106 -0
- package/dist/backend/services/session-service.js +1 -4
- package/dist/backend/services/status-service.js +1 -6
- package/dist/backend/services/task-service.js +42 -110
- package/dist/backend/templates/harness/project-manager-agent.js +10 -1
- package/dist/backend/templates/harness/vcm-route-message-skill.js +8 -2
- package/dist-frontend/assets/{index-C2xTNLmb.js → index-CKWy15WL.js} +46 -47
- package/dist-frontend/index.html +1 -1
- package/docs/cc-best-practices.md +1 -1
- package/docs/full-harness-baseline.md +0 -1
- package/docs/gateway-design.md +9 -13
- package/docs/product-design.md +18 -39
- package/docs/vcm-cc-best-practices.md +0 -1
- package/package.json +1 -1
|
@@ -9,13 +9,8 @@ export function createTaskService(deps) {
|
|
|
9
9
|
const config = await deps.projectService.loadConfig(repoRoot);
|
|
10
10
|
const taskStoreRoot = deps.projectService.getProjectDataRoot(repoRoot);
|
|
11
11
|
const taskPath = getTaskPath(taskStoreRoot, input.taskSlug);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
? `feature/${input.taskSlug}`
|
|
15
|
-
: await deps.git.getCurrentBranch(repoRoot);
|
|
16
|
-
const worktreePath = shouldCreateWorktree
|
|
17
|
-
? getTaskWorktreePath(repoRoot, input.taskSlug)
|
|
18
|
-
: undefined;
|
|
12
|
+
const taskBranch = `feature/${input.taskSlug}`;
|
|
13
|
+
const worktreePath = getTaskWorktreePath(repoRoot, input.taskSlug);
|
|
19
14
|
if (await deps.fs.pathExists(taskPath)) {
|
|
20
15
|
throw new VcmError({
|
|
21
16
|
code: "TASK_EXISTS",
|
|
@@ -31,7 +26,7 @@ export function createTaskService(deps) {
|
|
|
31
26
|
hint: "Apply VCM Harness first so .gitignore contains the VCM managed block."
|
|
32
27
|
});
|
|
33
28
|
}
|
|
34
|
-
if (
|
|
29
|
+
if (!(await deps.git.isIgnored(repoRoot, ".claude/worktrees/.probe"))) {
|
|
35
30
|
throw new VcmError({
|
|
36
31
|
code: "VCM_WORKTREES_NOT_IGNORED",
|
|
37
32
|
message: ".claude/worktrees/ is not ignored by Git.",
|
|
@@ -39,53 +34,39 @@ export function createTaskService(deps) {
|
|
|
39
34
|
hint: "Apply VCM Harness first so .gitignore ignores Claude-compatible task worktrees."
|
|
40
35
|
});
|
|
41
36
|
}
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
if (await deps.git.branchExists(repoRoot, taskBranch)) {
|
|
38
|
+
throw new VcmError({
|
|
39
|
+
code: "TASK_BRANCH_EXISTS",
|
|
40
|
+
message: `Task branch already exists: ${taskBranch}`,
|
|
41
|
+
statusCode: 409,
|
|
42
|
+
hint: "Choose a different task name or clean up the existing branch."
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
if (await deps.fs.pathExists(worktreePath)) {
|
|
46
|
+
throw new VcmError({
|
|
47
|
+
code: "TASK_WORKTREE_EXISTS",
|
|
48
|
+
message: `Task worktree already exists: ${worktreePath}`,
|
|
49
|
+
statusCode: 409,
|
|
50
|
+
hint: "Choose a different task name or clean up the existing worktree."
|
|
51
|
+
});
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
if (await deps.fs.pathExists(worktreePath)) {
|
|
63
|
-
throw new VcmError({
|
|
64
|
-
code: "TASK_WORKTREE_EXISTS",
|
|
65
|
-
message: `Task worktree already exists: ${worktreePath}`,
|
|
66
|
-
statusCode: 409,
|
|
67
|
-
hint: "Choose a different task name or clean up the existing worktree."
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
const baseStatus = await deps.git.getStatusPorcelain(repoRoot);
|
|
71
|
-
if (baseStatus.trim()) {
|
|
72
|
-
throw new VcmError({
|
|
73
|
-
code: "BASE_REPO_DIRTY",
|
|
74
|
-
message: "The connected repository has uncommitted changes.",
|
|
75
|
-
statusCode: 409,
|
|
76
|
-
hint: "Commit, stash, or discard base repository changes before creating a task worktree."
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
await deps.fs.ensureDir(path.dirname(worktreePath));
|
|
80
|
-
await deps.git.createWorktree({
|
|
81
|
-
repoRoot,
|
|
82
|
-
branch: taskBranch,
|
|
83
|
-
worktreePath,
|
|
84
|
-
baseRef: "HEAD"
|
|
53
|
+
const baseStatus = await deps.git.getStatusPorcelain(repoRoot);
|
|
54
|
+
if (baseStatus.trim()) {
|
|
55
|
+
throw new VcmError({
|
|
56
|
+
code: "BASE_REPO_DIRTY",
|
|
57
|
+
message: "The connected repository has uncommitted changes.",
|
|
58
|
+
statusCode: 409,
|
|
59
|
+
hint: "Commit, stash, or discard base repository changes before creating a task worktree."
|
|
85
60
|
});
|
|
86
61
|
}
|
|
87
62
|
const timestamp = now();
|
|
88
|
-
|
|
63
|
+
await deps.fs.ensureDir(path.dirname(worktreePath));
|
|
64
|
+
await deps.git.createWorktree({
|
|
65
|
+
repoRoot,
|
|
66
|
+
branch: taskBranch,
|
|
67
|
+
worktreePath,
|
|
68
|
+
baseRef: "HEAD"
|
|
69
|
+
});
|
|
89
70
|
const task = {
|
|
90
71
|
version: 1,
|
|
91
72
|
taskSlug: input.taskSlug,
|
|
@@ -100,14 +81,14 @@ export function createTaskService(deps) {
|
|
|
100
81
|
specPath: input.specPath,
|
|
101
82
|
cleanupStatus: "active"
|
|
102
83
|
};
|
|
103
|
-
await ensureTaskRuntimeStateDirs(deps.fs,
|
|
84
|
+
await ensureTaskRuntimeStateDirs(deps.fs, worktreePath, config.stateRoot);
|
|
104
85
|
await deps.artifactService.ensureHandoffStructure({
|
|
105
|
-
repoRoot:
|
|
86
|
+
repoRoot: worktreePath,
|
|
106
87
|
taskSlug: input.taskSlug,
|
|
107
88
|
handoffDir: task.handoffDir
|
|
108
89
|
});
|
|
109
90
|
await deps.artifactService.createArtifactTemplates({
|
|
110
|
-
repoRoot:
|
|
91
|
+
repoRoot: worktreePath,
|
|
111
92
|
taskSlug: input.taskSlug,
|
|
112
93
|
handoffDir: task.handoffDir,
|
|
113
94
|
branch: task.branch
|
|
@@ -123,7 +104,7 @@ export function createTaskService(deps) {
|
|
|
123
104
|
const entries = await deps.fs.readDir(tasksDir);
|
|
124
105
|
const tasks = [];
|
|
125
106
|
for (const entry of entries.filter((candidate) => candidate.endsWith(".json"))) {
|
|
126
|
-
tasks.push(
|
|
107
|
+
tasks.push(await deps.fs.readJson(path.join(tasksDir, entry)));
|
|
127
108
|
}
|
|
128
109
|
return tasks.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
129
110
|
},
|
|
@@ -137,7 +118,7 @@ export function createTaskService(deps) {
|
|
|
137
118
|
statusCode: 404
|
|
138
119
|
});
|
|
139
120
|
}
|
|
140
|
-
return
|
|
121
|
+
return deps.fs.readJson(taskPath);
|
|
141
122
|
},
|
|
142
123
|
async saveTask(repoRoot, task) {
|
|
143
124
|
await deps.fs.writeJsonAtomic(getTaskPath(deps.projectService.getProjectDataRoot(repoRoot), task.taskSlug), task);
|
|
@@ -167,15 +148,9 @@ export function createTaskService(deps) {
|
|
|
167
148
|
const statePaths = getTaskStatePaths(deps.projectService.getProjectDataRoot(repoRoot), taskRepoRoot, config.stateRoot, config.handoffRoot, taskSlug);
|
|
168
149
|
const removedStatePaths = [];
|
|
169
150
|
const cleanedAt = now();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
let deletedBranch;
|
|
175
|
-
if (task.worktreePath && (options.deleteBranch ?? true)) {
|
|
176
|
-
await deps.git.deleteBranch(repoRoot, task.branch, { force: options.forceDeleteBranch ?? true });
|
|
177
|
-
deletedBranch = task.branch;
|
|
178
|
-
}
|
|
151
|
+
assertTaskWorktreePath(repoRoot, task.worktreePath);
|
|
152
|
+
await deps.git.removeWorktree(repoRoot, task.worktreePath, { force: options.force ?? true });
|
|
153
|
+
await deps.git.deleteBranch(repoRoot, task.branch, { force: options.forceDeleteBranch ?? true });
|
|
179
154
|
for (const statePath of statePaths) {
|
|
180
155
|
await deps.fs.removePath(statePath, { recursive: true, force: true });
|
|
181
156
|
removedStatePaths.push(statePath);
|
|
@@ -184,14 +159,14 @@ export function createTaskService(deps) {
|
|
|
184
159
|
taskSlug,
|
|
185
160
|
removedWorktreePath: task.worktreePath,
|
|
186
161
|
removedStatePaths,
|
|
187
|
-
deletedBranch,
|
|
162
|
+
deletedBranch: task.branch,
|
|
188
163
|
cleanedAt
|
|
189
164
|
};
|
|
190
165
|
}
|
|
191
166
|
};
|
|
192
167
|
}
|
|
193
168
|
export function getTaskRuntimeRepoRoot(task) {
|
|
194
|
-
return task.worktreePath
|
|
169
|
+
return task.worktreePath;
|
|
195
170
|
}
|
|
196
171
|
function getTaskPath(taskStoreRoot, taskSlug) {
|
|
197
172
|
return path.join(taskStoreRoot, "tasks", `${taskSlug}.json`);
|
|
@@ -206,49 +181,6 @@ async function ensureTaskRuntimeStateDirs(fs, taskRepoRoot, stateRoot) {
|
|
|
206
181
|
await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "translation"));
|
|
207
182
|
await fs.ensureDir(path.join(taskRepoRoot, stateRoot, "codex-reviews"));
|
|
208
183
|
}
|
|
209
|
-
async function findActiveInlineTask(fs, taskStoreRoot) {
|
|
210
|
-
const tasksDir = path.join(taskStoreRoot, "tasks");
|
|
211
|
-
if (!(await fs.pathExists(tasksDir))) {
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
|
214
|
-
const entries = await fs.readDir(tasksDir);
|
|
215
|
-
for (const entry of entries.filter((candidate) => candidate.endsWith(".json"))) {
|
|
216
|
-
const task = normalizeTaskRecord(await fs.readJson(path.join(tasksDir, entry)));
|
|
217
|
-
if (!task.worktreePath && task.cleanupStatus !== "cleaned") {
|
|
218
|
-
return task;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return undefined;
|
|
222
|
-
}
|
|
223
|
-
function normalizeTaskRecord(input) {
|
|
224
|
-
return {
|
|
225
|
-
version: 1,
|
|
226
|
-
taskSlug: input.taskSlug ?? "",
|
|
227
|
-
title: input.title,
|
|
228
|
-
createdAt: input.createdAt ?? "",
|
|
229
|
-
updatedAt: input.updatedAt ?? input.createdAt ?? "",
|
|
230
|
-
repoRoot: input.repoRoot ?? "",
|
|
231
|
-
worktreePath: input.worktreePath,
|
|
232
|
-
branch: input.branch ?? "",
|
|
233
|
-
handoffDir: input.handoffDir ?? ".ai/vcm/handoffs",
|
|
234
|
-
status: normalizeTaskStatus(input.status),
|
|
235
|
-
specPath: input.specPath,
|
|
236
|
-
cleanupStatus: input.cleanupStatus,
|
|
237
|
-
cleanedAt: input.cleanedAt
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
function normalizeTaskStatus(value) {
|
|
241
|
-
if (value === "created" || value === "running" || value === "stopped") {
|
|
242
|
-
return value;
|
|
243
|
-
}
|
|
244
|
-
if (value === undefined || value === null) {
|
|
245
|
-
return "created";
|
|
246
|
-
}
|
|
247
|
-
if (value === "planning") {
|
|
248
|
-
return "created";
|
|
249
|
-
}
|
|
250
|
-
return "stopped";
|
|
251
|
-
}
|
|
252
184
|
function getTaskStatePaths(taskStoreRoot, taskRepoRoot, stateRoot, handoffRoot, taskSlug) {
|
|
253
185
|
return [
|
|
254
186
|
getTaskPath(taskStoreRoot, taskSlug),
|
|
@@ -35,10 +35,19 @@ export function renderProjectManagerHarnessRules() {
|
|
|
35
35
|
### Dispatch
|
|
36
36
|
|
|
37
37
|
- Use the \`vcm-route-message\` skill for every role dispatch, question, result, blocker, or finding.
|
|
38
|
-
-
|
|
38
|
+
- Formal route messages contain PM-owned routing context only: target role, user request summary, known user constraints, source of truth, required next gate, skipped gates when applicable, required handoff inputs, expected artifact, stop conditions, and confirmed worktree information.
|
|
39
39
|
- Do not write technical design into route messages; ask architect to determine architecture, file scope, public contracts, behavior/contract proof points, docs impact, and Replan triggers.
|
|
40
40
|
- For coder or reviewer messages, reference existing handoff artifacts instead of making new technical judgments.
|
|
41
41
|
|
|
42
|
+
### Simple User Relay
|
|
43
|
+
|
|
44
|
+
When forwarding a user's answer, clarification, or small preference update to an active role, use a lightweight relay message.
|
|
45
|
+
|
|
46
|
+
PM may lightly rewrite the user's words to:
|
|
47
|
+
- clarify pronouns or references from the current context
|
|
48
|
+
- translate the user's intent into clear role-facing language
|
|
49
|
+
- state whether this is confirmation, rejection, preference, or a small constraint
|
|
50
|
+
|
|
42
51
|
### Phased Tasks
|
|
43
52
|
|
|
44
53
|
- When architect provides a phased plan, dispatch only one phase at a time.
|
|
@@ -33,6 +33,12 @@ If the same route file already contains a not-yet-delivered message, update that
|
|
|
33
33
|
|
|
34
34
|
## Message Format
|
|
35
35
|
|
|
36
|
+
Use the smallest body that is complete. Include artifact refs instead of copying long documents.
|
|
37
|
+
|
|
38
|
+
For simple user relay, use a lightweight body instead of the formal dispatch format.
|
|
39
|
+
|
|
40
|
+
For formal dispatch, blocker, finding, review, or gate routing, use:
|
|
41
|
+
|
|
36
42
|
\`\`\`md
|
|
37
43
|
---
|
|
38
44
|
type: task
|
|
@@ -54,9 +60,9 @@ Expected next action:
|
|
|
54
60
|
...
|
|
55
61
|
\`\`\`
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
## Formal Body Content
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
Formal messages should include:
|
|
60
66
|
|
|
61
67
|
- why this message exists
|
|
62
68
|
- what the target role should do or what result is being reported
|