git-stint 0.1.1

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.
@@ -0,0 +1,130 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import * as git from "./git.js";
5
+ import { WORKTREE_DIR } from "./manifest.js";
6
+ import { resolveSession, getWorktreePath, loadManifest, getRepoRoot } from "./manifest.js";
7
+ /**
8
+ * Run a command in a directory. Uses shell: true to support piped/complex commands,
9
+ * but passes the command as a single string to execFileSync to avoid arg-splitting issues.
10
+ */
11
+ function runCommand(cmd, cwd) {
12
+ // Use execFileSync with shell to handle commands like "npm test" or "pytest -x"
13
+ // while still avoiding uncontrolled injection from file paths.
14
+ execFileSync("sh", ["-c", cmd], { cwd, stdio: "inherit" });
15
+ }
16
+ /**
17
+ * Run tests in the current session's worktree.
18
+ * The worktree already contains only this session's changes — it's isolated by default.
19
+ */
20
+ export function test(sessionName, testCmd) {
21
+ const manifest = resolveSession(sessionName);
22
+ const worktree = getWorktreePath(manifest);
23
+ const cmd = testCmd || detectTestCommand(worktree);
24
+ if (!cmd) {
25
+ throw new Error("No test command detected. Pass a command: git stint test -- <command>");
26
+ }
27
+ console.log(`Running tests in ${worktree}...`);
28
+ console.log(` $ ${cmd}\n`);
29
+ try {
30
+ runCommand(cmd, worktree);
31
+ console.log("\nTests passed.");
32
+ }
33
+ catch {
34
+ // Throw instead of process.exit so callers can handle it.
35
+ // cli.ts will catch and exit with code 1.
36
+ throw new Error("Tests failed.");
37
+ }
38
+ }
39
+ /**
40
+ * Test multiple sessions combined by creating a temporary worktree
41
+ * with an octopus merge of all specified session branches.
42
+ *
43
+ * Uses --detach to avoid "branch already checked out" errors,
44
+ * then creates a temporary branch for the merge.
45
+ */
46
+ export function testCombine(sessionNames, testCmd) {
47
+ const topLevel = getRepoRoot();
48
+ const mainBranch = git.getDefaultBranch();
49
+ const tmpName = `stint-combine-${Date.now()}`;
50
+ const tmpWorktree = resolve(topLevel, `${WORKTREE_DIR}/${tmpName}`);
51
+ // Validate all sessions exist
52
+ const branches = [];
53
+ for (const name of sessionNames) {
54
+ const m = loadManifest(name);
55
+ if (!m)
56
+ throw new Error(`Session '${name}' not found.`);
57
+ branches.push(m.branch);
58
+ }
59
+ console.log(`Testing combined: ${sessionNames.join(" + ")}`);
60
+ try {
61
+ // Create temp worktree in detached HEAD to avoid "already checked out" error
62
+ git.addWorktreeDetached(tmpWorktree, mainBranch);
63
+ // Create a temporary branch for the merge
64
+ git.gitInDir(tmpWorktree, "checkout", "-b", tmpName);
65
+ // Merge all session branches
66
+ try {
67
+ git.gitInDir(tmpWorktree, "merge", ...branches);
68
+ }
69
+ catch (err) {
70
+ const e = err;
71
+ throw new Error(`Merge conflicts detected when combining sessions: ${e.message}`);
72
+ }
73
+ const cmd = testCmd || detectTestCommand(tmpWorktree);
74
+ if (!cmd) {
75
+ throw new Error("No test command detected. Pass a command: git stint test --combine A B -- <command>");
76
+ }
77
+ console.log(`Running: ${cmd}\n`);
78
+ try {
79
+ runCommand(cmd, tmpWorktree);
80
+ console.log("\nCombined tests passed.");
81
+ }
82
+ catch {
83
+ throw new Error("Combined tests failed.");
84
+ }
85
+ }
86
+ finally {
87
+ // Clean up temp worktree — always runs, even when we throw
88
+ if (existsSync(tmpWorktree)) {
89
+ try {
90
+ git.removeWorktree(tmpWorktree, true);
91
+ }
92
+ catch { /* best effort */ }
93
+ }
94
+ // Delete the temp branch
95
+ try {
96
+ git.deleteBranch(tmpName);
97
+ }
98
+ catch { /* may not exist */ }
99
+ }
100
+ }
101
+ function detectTestCommand(dir) {
102
+ if (existsSync(resolve(dir, "package.json"))) {
103
+ try {
104
+ const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
105
+ if (pkg.scripts?.test && !pkg.scripts.test.includes("no test specified")) {
106
+ return "npm test";
107
+ }
108
+ }
109
+ catch { /* ignore */ }
110
+ }
111
+ if (existsSync(resolve(dir, "pyproject.toml")) || existsSync(resolve(dir, "pytest.ini"))) {
112
+ return "pytest";
113
+ }
114
+ if (existsSync(resolve(dir, "Cargo.toml"))) {
115
+ return "cargo test";
116
+ }
117
+ if (existsSync(resolve(dir, "go.mod"))) {
118
+ return "go test ./...";
119
+ }
120
+ if (existsSync(resolve(dir, "Makefile"))) {
121
+ try {
122
+ const makefile = readFileSync(resolve(dir, "Makefile"), "utf-8");
123
+ if (makefile.includes("test:")) {
124
+ return "make test";
125
+ }
126
+ }
127
+ catch { /* ignore */ }
128
+ }
129
+ return null;
130
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "git-stint",
3
+ "version": "0.1.1",
4
+ "description": "Session-scoped change tracking for AI coding agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "git-stint": "./bin/git-stint",
8
+ "git-stint-hook-pre-tool": "./adapters/claude-code/hooks/git-stint-hook-pre-tool",
9
+ "git-stint-hook-stop": "./adapters/claude-code/hooks/git-stint-hook-stop"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsc --watch",
14
+ "pretest": "tsc",
15
+ "test": "node --test test/unit/*.test.js",
16
+ "test:security": "node --test test/security/*.test.js",
17
+ "test:integration": "node --test test/integration/*.test.js",
18
+ "test:all": "tsc && node --test test/unit/*.test.js test/security/*.test.js test/integration/*.test.js"
19
+ },
20
+ "files": [
21
+ "bin/",
22
+ "dist/",
23
+ "adapters/"
24
+ ],
25
+ "engines": {
26
+ "node": ">=20.0.0"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/rchaz/git-stint.git"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/rchaz/git-stint/issues"
34
+ },
35
+ "homepage": "https://github.com/rchaz/git-stint#readme",
36
+ "keywords": [
37
+ "git",
38
+ "session",
39
+ "worktree",
40
+ "ai-agent",
41
+ "claude-code",
42
+ "change-tracking",
43
+ "branch-management",
44
+ "developer-tools"
45
+ ],
46
+ "author": "Rahul Chandrasekaran",
47
+ "license": "MIT",
48
+ "devDependencies": {
49
+ "typescript": "^5.7.0",
50
+ "@types/node": "^22.0.0"
51
+ }
52
+ }