vibe-coding-master 0.0.8 → 0.0.9
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 +4 -3
- package/dist/backend/adapters/filesystem.js +0 -7
- package/dist/backend/services/app-settings-service.js +90 -15
- package/dist/backend/services/project-service.js +5 -24
- package/dist/backend/services/task-service.js +1 -1
- package/dist/backend/templates/harness/gitignore.js +1 -4
- package/docs/product-design.md +9 -4
- package/docs/v1-architecture-design.md +8 -8
- package/docs/v1-implementation-plan.md +3 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -246,11 +246,10 @@ For `.gitignore`, VCM uses a gitignore-native managed block:
|
|
|
246
246
|
```gitignore
|
|
247
247
|
# VCM:BEGIN version=1
|
|
248
248
|
.ai/vcm/
|
|
249
|
-
.vcm/
|
|
250
249
|
# VCM:END
|
|
251
250
|
```
|
|
252
251
|
|
|
253
|
-
`.ai/vcm/` is the active VCM local control area
|
|
252
|
+
`.ai/vcm/` is the active VCM local control area for task state, session state, orchestration state, and task worktrees.
|
|
254
253
|
|
|
255
254
|
After applying harness changes, VCM reports the exact files changed and reminds the user to review and commit them before starting long-running work.
|
|
256
255
|
|
|
@@ -332,7 +331,7 @@ Session buttons behave as follows:
|
|
|
332
331
|
For a connected repository, VCM uses:
|
|
333
332
|
|
|
334
333
|
```text
|
|
335
|
-
|
|
334
|
+
~/.vcm/projects/<project-id>/config.json
|
|
336
335
|
.ai/vcm/tasks/<task>.json
|
|
337
336
|
.ai/vcm/sessions/<task>.json
|
|
338
337
|
.ai/vcm/messages/<task>.jsonl
|
|
@@ -347,6 +346,8 @@ For a connected repository, VCM uses:
|
|
|
347
346
|
.ai/handoffs/<task>/logs/{project-manager,architect,coder,reviewer}.log
|
|
348
347
|
```
|
|
349
348
|
|
|
349
|
+
The project config is stored under `~/.vcm` so it is durable local app state and is not hidden inside a Git-ignored repository directory. `.ai/vcm/` stays repository-local runtime state for tasks, sessions, messages, orchestration snapshots, and nested task worktrees.
|
|
350
|
+
|
|
350
351
|
## Packaging
|
|
351
352
|
|
|
352
353
|
The npm package publishes built output, not raw TypeScript entry files. `package.json` includes:
|
|
@@ -47,13 +47,6 @@ export function createNodeFileSystemAdapter() {
|
|
|
47
47
|
await this.writeText(targetPath, content);
|
|
48
48
|
return true;
|
|
49
49
|
},
|
|
50
|
-
async copyDir(sourcePath, targetPath) {
|
|
51
|
-
await fs.cp(sourcePath, targetPath, {
|
|
52
|
-
recursive: true,
|
|
53
|
-
force: false,
|
|
54
|
-
errorOnExist: false
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
50
|
async removePath(targetPath, options = {}) {
|
|
58
51
|
await fs.rm(targetPath, {
|
|
59
52
|
recursive: options.recursive ?? false,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
const MAX_RECENT_REPOSITORIES = 5;
|
|
4
5
|
export function createAppSettingsService(deps) {
|
|
5
6
|
const settingsPath = deps.settingsPath ?? path.join(homedir(), ".vcm", "settings.json");
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const legacyTranslationPath = deps.legacyTranslationPath
|
|
9
|
-
?? path.join(homedir(), ".vibe-coding-master", "translation.json");
|
|
7
|
+
const settingsRoot = path.dirname(settingsPath);
|
|
8
|
+
const projectIndexPath = path.join(settingsRoot, "projects", "index.json");
|
|
10
9
|
let cachedSettings = null;
|
|
10
|
+
let cachedProjectIndex = null;
|
|
11
11
|
async function loadSettings() {
|
|
12
12
|
if (cachedSettings) {
|
|
13
13
|
return cachedSettings;
|
|
@@ -17,20 +17,9 @@ export function createAppSettingsService(deps) {
|
|
|
17
17
|
if (await deps.fs.pathExists(settingsPath)) {
|
|
18
18
|
raw = await deps.fs.readJson(settingsPath);
|
|
19
19
|
}
|
|
20
|
-
else if (await deps.fs.pathExists(legacySettingsPath)) {
|
|
21
|
-
raw = await deps.fs.readJson(legacySettingsPath);
|
|
22
|
-
shouldSave = true;
|
|
23
|
-
}
|
|
24
20
|
else {
|
|
25
21
|
shouldSave = true;
|
|
26
22
|
}
|
|
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
23
|
cachedSettings = normalizeSettingsFile(raw);
|
|
35
24
|
if (shouldSave) {
|
|
36
25
|
await saveSettings(cachedSettings);
|
|
@@ -41,6 +30,26 @@ export function createAppSettingsService(deps) {
|
|
|
41
30
|
cachedSettings = settings;
|
|
42
31
|
await deps.fs.writeJsonAtomic(settingsPath, settings);
|
|
43
32
|
}
|
|
33
|
+
async function loadProjectIndex() {
|
|
34
|
+
if (cachedProjectIndex) {
|
|
35
|
+
return cachedProjectIndex;
|
|
36
|
+
}
|
|
37
|
+
if (await deps.fs.pathExists(projectIndexPath)) {
|
|
38
|
+
cachedProjectIndex = normalizeProjectIndexFile(await deps.fs.readJson(projectIndexPath));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
cachedProjectIndex = { version: 1, projects: [] };
|
|
42
|
+
await saveProjectIndex(cachedProjectIndex);
|
|
43
|
+
}
|
|
44
|
+
return cachedProjectIndex;
|
|
45
|
+
}
|
|
46
|
+
async function saveProjectIndex(index) {
|
|
47
|
+
cachedProjectIndex = normalizeProjectIndexFile(index);
|
|
48
|
+
await deps.fs.writeJsonAtomic(projectIndexPath, cachedProjectIndex);
|
|
49
|
+
}
|
|
50
|
+
function getProjectConfigPath(repoRoot) {
|
|
51
|
+
return path.join(settingsRoot, "projects", getProjectId(repoRoot), "config.json");
|
|
52
|
+
}
|
|
44
53
|
return {
|
|
45
54
|
loadSettings,
|
|
46
55
|
async updateTranslationConfig(config) {
|
|
@@ -74,9 +83,75 @@ export function createAppSettingsService(deps) {
|
|
|
74
83
|
});
|
|
75
84
|
return recentRepositoryPaths;
|
|
76
85
|
},
|
|
86
|
+
loadProjectIndex,
|
|
87
|
+
async loadProjectConfig(repoRoot) {
|
|
88
|
+
const configPath = getProjectConfigPath(repoRoot);
|
|
89
|
+
if (!(await deps.fs.pathExists(configPath))) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
return deps.fs.readJson(configPath);
|
|
93
|
+
},
|
|
94
|
+
async saveProjectConfig(config) {
|
|
95
|
+
const configPath = getProjectConfigPath(config.repoRoot);
|
|
96
|
+
await deps.fs.writeJsonAtomic(configPath, config);
|
|
97
|
+
const projectId = getProjectId(config.repoRoot);
|
|
98
|
+
const current = await loadProjectIndex();
|
|
99
|
+
const projects = [
|
|
100
|
+
{
|
|
101
|
+
projectId,
|
|
102
|
+
repoRoot: config.repoRoot,
|
|
103
|
+
configPath,
|
|
104
|
+
lastOpenedAt: new Date().toISOString()
|
|
105
|
+
},
|
|
106
|
+
...current.projects.filter((entry) => entry.projectId !== projectId)
|
|
107
|
+
];
|
|
108
|
+
await saveProjectIndex({
|
|
109
|
+
version: 1,
|
|
110
|
+
projects
|
|
111
|
+
});
|
|
112
|
+
return config;
|
|
113
|
+
},
|
|
77
114
|
getSettingsPath() {
|
|
78
115
|
return settingsPath;
|
|
116
|
+
},
|
|
117
|
+
getProjectIndexPath() {
|
|
118
|
+
return projectIndexPath;
|
|
119
|
+
},
|
|
120
|
+
getProjectConfigPath
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export function getProjectId(repoRoot) {
|
|
124
|
+
return createHash("sha256")
|
|
125
|
+
.update(path.resolve(repoRoot))
|
|
126
|
+
.digest("hex")
|
|
127
|
+
.slice(0, 16);
|
|
128
|
+
}
|
|
129
|
+
function normalizeProjectIndexFile(input) {
|
|
130
|
+
const rawProjects = Array.isArray(input.projects) ? input.projects : [];
|
|
131
|
+
const projects = [];
|
|
132
|
+
const seen = new Set();
|
|
133
|
+
for (const value of rawProjects) {
|
|
134
|
+
if (!isObject(value)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const projectId = typeof value.projectId === "string" ? value.projectId.trim() : "";
|
|
138
|
+
const repoRoot = typeof value.repoRoot === "string" ? value.repoRoot.trim() : "";
|
|
139
|
+
const configPath = typeof value.configPath === "string" ? value.configPath.trim() : "";
|
|
140
|
+
const lastOpenedAt = typeof value.lastOpenedAt === "string" ? value.lastOpenedAt.trim() : "";
|
|
141
|
+
if (!projectId || !repoRoot || !configPath || seen.has(projectId)) {
|
|
142
|
+
continue;
|
|
79
143
|
}
|
|
144
|
+
seen.add(projectId);
|
|
145
|
+
projects.push({
|
|
146
|
+
projectId,
|
|
147
|
+
repoRoot,
|
|
148
|
+
configPath,
|
|
149
|
+
lastOpenedAt: lastOpenedAt || new Date(0).toISOString()
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
version: 1,
|
|
154
|
+
projects
|
|
80
155
|
};
|
|
81
156
|
}
|
|
82
157
|
function normalizeSettingsFile(input) {
|
|
@@ -3,20 +3,8 @@ import { ROLE_NAMES } from "../../shared/constants.js";
|
|
|
3
3
|
import { VcmError } from "../errors.js";
|
|
4
4
|
const DEFAULT_HANDOFF_ROOT = ".ai/handoffs";
|
|
5
5
|
const DEFAULT_STATE_ROOT = ".ai/vcm";
|
|
6
|
-
const LEGACY_STATE_ROOT = ".vcm";
|
|
7
6
|
export function createProjectService(deps) {
|
|
8
7
|
let currentProject = null;
|
|
9
|
-
async function migrateLegacyStateRoot(repoRoot) {
|
|
10
|
-
if (!deps.fs.copyDir) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
const legacyStateRoot = path.join(repoRoot, LEGACY_STATE_ROOT);
|
|
14
|
-
const currentStateRoot = path.join(repoRoot, DEFAULT_STATE_ROOT);
|
|
15
|
-
if (!(await deps.fs.pathExists(legacyStateRoot)) || await deps.fs.pathExists(currentStateRoot)) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
await deps.fs.copyDir(legacyStateRoot, currentStateRoot);
|
|
19
|
-
}
|
|
20
8
|
return {
|
|
21
9
|
async connectProject(input) {
|
|
22
10
|
const requestedPath = input.repoPath.trim();
|
|
@@ -86,16 +74,9 @@ export function createProjectService(deps) {
|
|
|
86
74
|
return deps.appSettings.getRecentRepositoryPaths();
|
|
87
75
|
},
|
|
88
76
|
async loadConfig(repoRoot) {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
return normalizeProjectConfig(
|
|
92
|
-
}
|
|
93
|
-
const legacyConfigPath = path.join(repoRoot, LEGACY_STATE_ROOT, "config.json");
|
|
94
|
-
if (await deps.fs.pathExists(legacyConfigPath)) {
|
|
95
|
-
const migratedConfig = normalizeProjectConfig(await deps.fs.readJson(legacyConfigPath), repoRoot);
|
|
96
|
-
await migrateLegacyStateRoot(repoRoot);
|
|
97
|
-
await this.saveConfig(migratedConfig, true);
|
|
98
|
-
return migratedConfig;
|
|
77
|
+
const appConfig = await deps.appSettings.loadProjectConfig(repoRoot);
|
|
78
|
+
if (appConfig) {
|
|
79
|
+
return normalizeProjectConfig(appConfig, repoRoot);
|
|
99
80
|
}
|
|
100
81
|
return buildDefaultProjectConfig(repoRoot);
|
|
101
82
|
},
|
|
@@ -105,10 +86,10 @@ export function createProjectService(deps) {
|
|
|
105
86
|
if (!force && await deps.fs.pathExists(configPath)) {
|
|
106
87
|
return;
|
|
107
88
|
}
|
|
108
|
-
await deps.
|
|
89
|
+
await deps.appSettings.saveProjectConfig(normalizedConfig);
|
|
109
90
|
},
|
|
110
91
|
getConfigPath(repoRoot) {
|
|
111
|
-
return
|
|
92
|
+
return deps.appSettings.getProjectConfigPath(repoRoot);
|
|
112
93
|
}
|
|
113
94
|
};
|
|
114
95
|
}
|
|
@@ -33,7 +33,7 @@ export function createTaskService(deps) {
|
|
|
33
33
|
hint: "Choose a different task name or clean up the existing worktree."
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
if (!(await deps.git.isIgnored(repoRoot, `${config.stateRoot}/
|
|
36
|
+
if (!(await deps.git.isIgnored(repoRoot, `${config.stateRoot}/tasks/.probe`))) {
|
|
37
37
|
throw new VcmError({
|
|
38
38
|
code: "VCM_STATE_NOT_IGNORED",
|
|
39
39
|
message: `${config.stateRoot}/ is not ignored by Git.`,
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
export function renderGitignoreHarnessRules() {
|
|
2
2
|
return [
|
|
3
3
|
"# VCM local app state, task metadata, session records, and task worktrees.",
|
|
4
|
-
".ai/vcm/"
|
|
5
|
-
"",
|
|
6
|
-
"# Legacy VCM local state from early versions. Keep ignored during migration.",
|
|
7
|
-
".vcm/"
|
|
4
|
+
".ai/vcm/"
|
|
8
5
|
].join("\n");
|
|
9
6
|
}
|
package/docs/product-design.md
CHANGED
|
@@ -364,11 +364,10 @@ For `.gitignore`, VCM uses hash comments:
|
|
|
364
364
|
```gitignore
|
|
365
365
|
# VCM:BEGIN version=1
|
|
366
366
|
.ai/vcm/
|
|
367
|
-
.vcm/
|
|
368
367
|
# VCM:END
|
|
369
368
|
```
|
|
370
369
|
|
|
371
|
-
`.ai/vcm/` is the active VCM local control area
|
|
370
|
+
`.ai/vcm/` is the active VCM local control area for task state, session state, orchestration state, and task worktrees.
|
|
372
371
|
|
|
373
372
|
VCM must preserve all user-authored content outside the managed block.
|
|
374
373
|
|
|
@@ -531,7 +530,6 @@ App-level settings:
|
|
|
531
530
|
Repository-level VCM state:
|
|
532
531
|
|
|
533
532
|
```text
|
|
534
|
-
.ai/vcm/config.json
|
|
535
533
|
.ai/vcm/tasks/<task>.json
|
|
536
534
|
.ai/vcm/sessions/<task>.json
|
|
537
535
|
.ai/vcm/messages/<task>.jsonl
|
|
@@ -539,7 +537,14 @@ Repository-level VCM state:
|
|
|
539
537
|
.ai/vcm/worktrees/<task>/
|
|
540
538
|
```
|
|
541
539
|
|
|
542
|
-
|
|
540
|
+
Project config:
|
|
541
|
+
|
|
542
|
+
```text
|
|
543
|
+
~/.vcm/projects/<project-id>/config.json
|
|
544
|
+
~/.vcm/projects/index.json
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The base repository's `.ai/vcm/` directory is the repository-local runtime control area. Long-lived project config is stored under `~/.vcm` so it survives outside Git-ignored repo state. Task source changes and handoff artifacts live inside the task worktree at `.ai/vcm/worktrees/<task>/`.
|
|
543
548
|
|
|
544
549
|
Task worktree local files:
|
|
545
550
|
|
|
@@ -202,7 +202,7 @@ POST /api/projects/connect
|
|
|
202
202
|
-> resolve path
|
|
203
203
|
-> check path exists
|
|
204
204
|
-> check .git marker directly
|
|
205
|
-
-> create
|
|
205
|
+
-> create ~/.vcm/projects/<project-id>/config.json
|
|
206
206
|
-> ensure .ai/vcm state dirs
|
|
207
207
|
-> read branch and dirty state
|
|
208
208
|
-> record recent repo path in ~/.vcm/settings.json
|
|
@@ -268,7 +268,6 @@ VCM distinguishes:
|
|
|
268
268
|
Project-level VCM control state stays under the base repo:
|
|
269
269
|
|
|
270
270
|
```text
|
|
271
|
-
<baseRepoRoot>/.ai/vcm/config.json
|
|
272
271
|
<baseRepoRoot>/.ai/vcm/tasks/<task>.json
|
|
273
272
|
<baseRepoRoot>/.ai/vcm/sessions/<task>.json
|
|
274
273
|
<baseRepoRoot>/.ai/vcm/messages/<task>.jsonl
|
|
@@ -276,6 +275,13 @@ Project-level VCM control state stays under the base repo:
|
|
|
276
275
|
<baseRepoRoot>/.ai/vcm/worktrees/<task>/
|
|
277
276
|
```
|
|
278
277
|
|
|
278
|
+
Project configuration is app-local and stored outside the repository:
|
|
279
|
+
|
|
280
|
+
```text
|
|
281
|
+
~/.vcm/projects/<project-id>/config.json
|
|
282
|
+
~/.vcm/projects/index.json
|
|
283
|
+
```
|
|
284
|
+
|
|
279
285
|
Task source changes and handoff artifacts live in the task worktree:
|
|
280
286
|
|
|
281
287
|
```text
|
|
@@ -636,7 +642,6 @@ Managed block:
|
|
|
636
642
|
```gitignore
|
|
637
643
|
# VCM:BEGIN version=1
|
|
638
644
|
.ai/vcm/
|
|
639
|
-
.vcm/
|
|
640
645
|
# VCM:END
|
|
641
646
|
```
|
|
642
647
|
|
|
@@ -682,11 +687,6 @@ Stored data:
|
|
|
682
687
|
- translation secrets
|
|
683
688
|
- recent repository paths, max 5
|
|
684
689
|
|
|
685
|
-
Legacy migration:
|
|
686
|
-
|
|
687
|
-
- `~/.vibe-coding-master/settings.json`
|
|
688
|
-
- `~/.vibe-coding-master/translation.json`
|
|
689
|
-
|
|
690
690
|
### Provider
|
|
691
691
|
|
|
692
692
|
Provider type:
|
|
@@ -484,7 +484,7 @@ Responsibilities:
|
|
|
484
484
|
- connect repo
|
|
485
485
|
- store current project in process memory
|
|
486
486
|
- record recent repo paths in app settings
|
|
487
|
-
- create
|
|
487
|
+
- create `~/.vcm/projects/<project-id>/config.json`
|
|
488
488
|
- ensure base state directories
|
|
489
489
|
- ensure `.ai/vcm/` is ignored by Git before task-worktree creation
|
|
490
490
|
- expose base repo as the project control root
|
|
@@ -706,13 +706,6 @@ Storage:
|
|
|
706
706
|
~/.vcm/settings.json
|
|
707
707
|
```
|
|
708
708
|
|
|
709
|
-
Also migrates legacy:
|
|
710
|
-
|
|
711
|
-
```text
|
|
712
|
-
~/.vibe-coding-master/settings.json
|
|
713
|
-
~/.vibe-coding-master/translation.json
|
|
714
|
-
```
|
|
715
|
-
|
|
716
709
|
### `src/backend/services/translation-prompts.ts`
|
|
717
710
|
|
|
718
711
|
Exports:
|
|
@@ -1149,7 +1142,8 @@ App settings:
|
|
|
1149
1142
|
Project config:
|
|
1150
1143
|
|
|
1151
1144
|
```text
|
|
1152
|
-
|
|
1145
|
+
~/.vcm/projects/<project-id>/config.json
|
|
1146
|
+
~/.vcm/projects/index.json
|
|
1153
1147
|
```
|
|
1154
1148
|
|
|
1155
1149
|
Task state:
|