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 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. `.vcm/` is ignored only as a legacy safety rule so older local state cannot be accidentally committed during migration.
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
- .ai/vcm/config.json
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 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");
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 configPath = this.getConfigPath(repoRoot);
90
- if (await deps.fs.pathExists(configPath)) {
91
- return normalizeProjectConfig(await deps.fs.readJson(configPath), repoRoot);
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.fs.writeJsonAtomic(configPath, normalizedConfig);
89
+ await deps.appSettings.saveProjectConfig(normalizedConfig);
109
90
  },
110
91
  getConfigPath(repoRoot) {
111
- return path.join(repoRoot, DEFAULT_STATE_ROOT, "config.json");
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}/config.json`))) {
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
  }
@@ -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. `.vcm/` is included only as a legacy ignore rule for older VCM state.
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
- In task-worktree mode, the base repository's `.ai/vcm/` directory is the project control area. Task source changes and handoff artifacts live inside the task worktree at `.ai/vcm/worktrees/<task>/`.
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 .ai/vcm/config.json
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 `.ai/vcm/config.json`
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
- .ai/vcm/config.json
1145
+ ~/.vcm/projects/<project-id>/config.json
1146
+ ~/.vcm/projects/index.json
1153
1147
  ```
1154
1148
 
1155
1149
  Task state:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-coding-master",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Local GUI session cockpit for Claude Code role sessions.",
5
5
  "type": "module",
6
6
  "files": [