vibe-coding-master 0.0.2 → 0.0.4
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
|
@@ -147,6 +147,8 @@ Important container notes:
|
|
|
147
147
|
- Install Claude Code inside the container, or make the `claude` command available in the container PATH.
|
|
148
148
|
- Make sure Claude Code authentication works inside the container.
|
|
149
149
|
- Make sure the container has network access to Claude services.
|
|
150
|
+
- Use the path as seen from inside the container, for example `/workspace`.
|
|
151
|
+
- VCM accepts normal repositories by checking `/workspace/.git` directly; it does not require global Git `safe.directory` config to connect.
|
|
150
152
|
- Keep the user project, `.vcm`, and `.ai/handoffs` on the same mounted workspace so paths are consistent.
|
|
151
153
|
- Treat the container as the sandbox boundary, especially when using relaxed Claude Code permission modes.
|
|
152
154
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { VcmError } from "../errors.js";
|
|
2
4
|
export function createGitAdapter(runner) {
|
|
3
5
|
return {
|
|
6
|
+
async checkRepo(repoRoot) {
|
|
7
|
+
return checkGitMarker(repoRoot);
|
|
8
|
+
},
|
|
4
9
|
async isRepo(repoRoot) {
|
|
5
|
-
|
|
6
|
-
return result.exitCode === 0 && result.stdout.trim() === "true";
|
|
10
|
+
return (await this.checkRepo(repoRoot)).isRepo;
|
|
7
11
|
},
|
|
8
12
|
async getCurrentBranch(repoRoot) {
|
|
9
|
-
const result = await runner
|
|
13
|
+
const result = await runGit(runner, repoRoot, ["branch", "--show-current"]);
|
|
10
14
|
if (result.exitCode !== 0) {
|
|
11
15
|
throw new VcmError({
|
|
12
16
|
code: "GIT_ERROR",
|
|
@@ -18,7 +22,7 @@ export function createGitAdapter(runner) {
|
|
|
18
22
|
return result.stdout.trim() || "detached";
|
|
19
23
|
},
|
|
20
24
|
async isDirty(repoRoot) {
|
|
21
|
-
const result = await runner
|
|
25
|
+
const result = await runGit(runner, repoRoot, ["status", "--porcelain"]);
|
|
22
26
|
if (result.exitCode !== 0) {
|
|
23
27
|
throw new VcmError({
|
|
24
28
|
code: "GIT_ERROR",
|
|
@@ -31,3 +35,74 @@ export function createGitAdapter(runner) {
|
|
|
31
35
|
}
|
|
32
36
|
};
|
|
33
37
|
}
|
|
38
|
+
async function checkGitMarker(repoRoot) {
|
|
39
|
+
const markerPath = path.join(repoRoot, ".git");
|
|
40
|
+
try {
|
|
41
|
+
const markerStat = await fs.lstat(markerPath);
|
|
42
|
+
if (markerStat.isDirectory()) {
|
|
43
|
+
return checkGitDirectory(markerPath);
|
|
44
|
+
}
|
|
45
|
+
if (markerStat.isFile()) {
|
|
46
|
+
return checkGitFile(repoRoot, markerPath);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
isRepo: false,
|
|
50
|
+
hint: `.git exists but is neither a directory nor a gitdir file: ${markerPath}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {
|
|
55
|
+
isRepo: false,
|
|
56
|
+
hint: `.git not found under selected path: ${markerPath}`
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function checkGitDirectory(gitDir) {
|
|
61
|
+
if (await pathExists(path.join(gitDir, "HEAD"))) {
|
|
62
|
+
return { isRepo: true };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
isRepo: false,
|
|
66
|
+
hint: `.git directory exists but HEAD is missing: ${gitDir}`
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function checkGitFile(repoRoot, markerPath) {
|
|
70
|
+
const marker = await fs.readFile(markerPath, "utf8");
|
|
71
|
+
const match = marker.match(/^gitdir:\s*(.+)\s*$/m);
|
|
72
|
+
if (!match) {
|
|
73
|
+
return {
|
|
74
|
+
isRepo: false,
|
|
75
|
+
hint: `.git file does not contain a gitdir pointer: ${markerPath}`
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const gitDir = path.resolve(repoRoot, match[1]);
|
|
79
|
+
if (await pathExists(path.join(gitDir, "HEAD"))) {
|
|
80
|
+
return { isRepo: true };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
isRepo: false,
|
|
84
|
+
hint: `.git file points to a gitdir without HEAD: ${gitDir}`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function pathExists(targetPath) {
|
|
88
|
+
try {
|
|
89
|
+
await fs.access(targetPath);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function runGit(runner, repoRoot, args) {
|
|
97
|
+
return runner.run("git", [...await buildSafeDirectoryArgs(repoRoot), ...args], { cwd: repoRoot });
|
|
98
|
+
}
|
|
99
|
+
async function buildSafeDirectoryArgs(repoRoot) {
|
|
100
|
+
const safeDirs = new Set([repoRoot]);
|
|
101
|
+
try {
|
|
102
|
+
safeDirs.add(await fs.realpath(repoRoot));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// The main Git command will return the actionable path error.
|
|
106
|
+
}
|
|
107
|
+
return [...safeDirs].flatMap((safeDir) => ["-c", `safe.directory=${safeDir}`]);
|
|
108
|
+
}
|
|
@@ -5,11 +5,24 @@ export function createProjectService(deps) {
|
|
|
5
5
|
let currentProject = null;
|
|
6
6
|
return {
|
|
7
7
|
async connectProject(input) {
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const requestedPath = input.repoPath.trim();
|
|
9
|
+
const repoRoot = path.resolve(requestedPath);
|
|
10
|
+
if (!requestedPath || !(await deps.fs.pathExists(repoRoot))) {
|
|
10
11
|
throw new VcmError({
|
|
11
12
|
code: "INVALID_REPO",
|
|
12
13
|
message: "Selected path is not a Git repository.",
|
|
14
|
+
hint: requestedPath
|
|
15
|
+
? `Path does not exist inside the VCM runtime: ${repoRoot}`
|
|
16
|
+
: "Repository path cannot be empty.",
|
|
17
|
+
statusCode: 400
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const repoCheck = await deps.git.checkRepo(repoRoot);
|
|
21
|
+
if (!repoCheck.isRepo) {
|
|
22
|
+
throw new VcmError({
|
|
23
|
+
code: "INVALID_REPO",
|
|
24
|
+
message: "Selected path is not a Git repository.",
|
|
25
|
+
hint: repoCheck.hint,
|
|
13
26
|
statusCode: 400
|
|
14
27
|
});
|
|
15
28
|
}
|
|
@@ -18,9 +31,21 @@ export function createProjectService(deps) {
|
|
|
18
31
|
await deps.fs.ensureDir(path.join(repoRoot, config.stateRoot, "tasks"));
|
|
19
32
|
await deps.fs.ensureDir(path.join(repoRoot, config.stateRoot, "sessions"));
|
|
20
33
|
await this.saveConfig(config, true);
|
|
21
|
-
const branch = await deps.git.getCurrentBranch(repoRoot);
|
|
22
|
-
const isDirty = await deps.git.isDirty(repoRoot);
|
|
23
34
|
const warnings = [];
|
|
35
|
+
let branch = "unknown";
|
|
36
|
+
let isDirty = false;
|
|
37
|
+
try {
|
|
38
|
+
branch = await deps.git.getCurrentBranch(repoRoot);
|
|
39
|
+
}
|
|
40
|
+
catch (caught) {
|
|
41
|
+
warnings.push(`Unable to read current Git branch. ${getErrorHint(caught)}`);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
isDirty = await deps.git.isDirty(repoRoot);
|
|
45
|
+
}
|
|
46
|
+
catch (caught) {
|
|
47
|
+
warnings.push(`Unable to read Git dirty status. ${getErrorHint(caught)}`);
|
|
48
|
+
}
|
|
24
49
|
if (branch === "main" || branch === "master") {
|
|
25
50
|
warnings.push(`You are on ${branch}. Consider creating a task branch before coding.`);
|
|
26
51
|
}
|
|
@@ -58,6 +83,15 @@ export function createProjectService(deps) {
|
|
|
58
83
|
}
|
|
59
84
|
};
|
|
60
85
|
}
|
|
86
|
+
function getErrorHint(caught) {
|
|
87
|
+
if (caught instanceof VcmError) {
|
|
88
|
+
return caught.hint?.trim() || caught.message;
|
|
89
|
+
}
|
|
90
|
+
if (caught instanceof Error) {
|
|
91
|
+
return caught.message;
|
|
92
|
+
}
|
|
93
|
+
return "Unknown Git metadata error.";
|
|
94
|
+
}
|
|
61
95
|
export function buildDefaultProjectConfig(repoRoot) {
|
|
62
96
|
return {
|
|
63
97
|
version: 1,
|