hammadev 0.1.0-alpha.0
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/LICENSE +15 -0
- package/README.md +226 -0
- package/dist/adapters/claude/discover.js +91 -0
- package/dist/adapters/claude/index.js +18 -0
- package/dist/adapters/claude/parse.js +130 -0
- package/dist/adapters/claude/paths.js +26 -0
- package/dist/adapters/claude/resolve.js +34 -0
- package/dist/adapters/claude/shape.js +86 -0
- package/dist/adapters/codex/discover.js +27 -0
- package/dist/adapters/codex/index.js +18 -0
- package/dist/adapters/codex/paths.js +21 -0
- package/dist/adapters/codex/resolve.js +59 -0
- package/dist/adapters/codex/rollout.js +214 -0
- package/dist/cli.js +248 -0
- package/dist/core/doctor.js +187 -0
- package/dist/core/handoff.js +530 -0
- package/dist/core/history.js +117 -0
- package/dist/core/project-status.js +167 -0
- package/dist/core/redact.js +21 -0
- package/dist/core/schema.js +1 -0
- package/dist/core/state.js +444 -0
- package/dist/session-loader.js +54 -0
- package/package.json +58 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { CodexAdapter } from "../adapters/codex/index.js";
|
|
6
|
+
import { defaultCodexHome } from "../adapters/codex/paths.js";
|
|
7
|
+
const MIN_NODE_MAJOR = 20;
|
|
8
|
+
function marker(status) {
|
|
9
|
+
if (status === "pass")
|
|
10
|
+
return pc.green("✔");
|
|
11
|
+
if (status === "warn")
|
|
12
|
+
return pc.yellow("!");
|
|
13
|
+
return pc.red("✖");
|
|
14
|
+
}
|
|
15
|
+
async function pathExists(p) {
|
|
16
|
+
try {
|
|
17
|
+
await fs.access(p);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function checkNode() {
|
|
25
|
+
const raw = process.versions.node;
|
|
26
|
+
const major = Number(raw.split(".")[0]);
|
|
27
|
+
if (!Number.isFinite(major) || major < MIN_NODE_MAJOR) {
|
|
28
|
+
return {
|
|
29
|
+
name: "Node.js version",
|
|
30
|
+
status: "fail",
|
|
31
|
+
message: `Node ${raw} detected. HammaDev requires Node ${MIN_NODE_MAJOR}+.`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return { name: "Node.js version", status: "pass", message: `Node ${raw}` };
|
|
35
|
+
}
|
|
36
|
+
function checkGit() {
|
|
37
|
+
try {
|
|
38
|
+
const out = execSync("git --version", {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41
|
+
}).trim();
|
|
42
|
+
return { name: "git availability", status: "pass", message: out };
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
const detail = err.message?.split("\n")[0] ?? "unknown error";
|
|
46
|
+
return {
|
|
47
|
+
name: "git availability",
|
|
48
|
+
status: "fail",
|
|
49
|
+
message: `git not found on PATH (${detail}). Handoff repo-state section will be empty.`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function checkCodexSessions() {
|
|
54
|
+
const home = defaultCodexHome();
|
|
55
|
+
if (!(await pathExists(home))) {
|
|
56
|
+
return {
|
|
57
|
+
check: {
|
|
58
|
+
name: "Codex sessions",
|
|
59
|
+
status: "warn",
|
|
60
|
+
message: `Codex home not found at ${home}. Install Codex CLI and run a session first.`,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const sessions = await CodexAdapter.list();
|
|
65
|
+
if (sessions.length === 0) {
|
|
66
|
+
return {
|
|
67
|
+
check: {
|
|
68
|
+
name: "Codex sessions",
|
|
69
|
+
status: "warn",
|
|
70
|
+
message: `No rollout-*.jsonl files under ${home}/sessions.`,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const latest = sessions[0];
|
|
75
|
+
return {
|
|
76
|
+
check: {
|
|
77
|
+
name: "Codex sessions",
|
|
78
|
+
status: "pass",
|
|
79
|
+
message: `${sessions.length} session(s) found. Newest: ${latest.startedAt ?? "unknown-time"} (${latest.conversationId}).`,
|
|
80
|
+
},
|
|
81
|
+
latestPath: latest.path,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function checkProjectPath(latestPath) {
|
|
85
|
+
if (!latestPath) {
|
|
86
|
+
return {
|
|
87
|
+
check: {
|
|
88
|
+
name: "projectPath detection",
|
|
89
|
+
status: "warn",
|
|
90
|
+
message: "Skipped — no Codex session available to parse.",
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const session = await CodexAdapter.inspect(latestPath);
|
|
96
|
+
const projectPath = session.meta.projectPath;
|
|
97
|
+
if (!projectPath) {
|
|
98
|
+
return {
|
|
99
|
+
check: {
|
|
100
|
+
name: "projectPath detection",
|
|
101
|
+
status: "fail",
|
|
102
|
+
message: "Latest session has no projectPath. Handoff will fail with 'source session has no projectPath'.",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (!(await pathExists(projectPath))) {
|
|
107
|
+
return {
|
|
108
|
+
check: {
|
|
109
|
+
name: "projectPath detection",
|
|
110
|
+
status: "warn",
|
|
111
|
+
message: `Detected projectPath ${projectPath} does not exist on this machine.`,
|
|
112
|
+
},
|
|
113
|
+
projectPath,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
check: {
|
|
118
|
+
name: "projectPath detection",
|
|
119
|
+
status: "pass",
|
|
120
|
+
message: `Detected: ${projectPath}`,
|
|
121
|
+
},
|
|
122
|
+
projectPath,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
return {
|
|
127
|
+
check: {
|
|
128
|
+
name: "projectPath detection",
|
|
129
|
+
status: "fail",
|
|
130
|
+
message: `Failed to parse latest session: ${err.message}`,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function checkGitignoreSafety(projectPath) {
|
|
136
|
+
const targetDir = projectPath ?? process.cwd();
|
|
137
|
+
const label = projectPath ? `source project ${projectPath}` : `current directory ${targetDir}`;
|
|
138
|
+
const isGitRepo = await pathExists(path.join(targetDir, ".git"));
|
|
139
|
+
if (!isGitRepo) {
|
|
140
|
+
return {
|
|
141
|
+
name: ".gitignore safety",
|
|
142
|
+
status: "pass",
|
|
143
|
+
message: `${label} is not a git repo — nothing to ignore.`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
147
|
+
if (!(await pathExists(gitignorePath))) {
|
|
148
|
+
return {
|
|
149
|
+
name: ".gitignore safety",
|
|
150
|
+
status: "warn",
|
|
151
|
+
message: `No .gitignore in ${label}. Handoff will create one with \`.hamma/\`.`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const content = await fs.readFile(gitignorePath, "utf8");
|
|
155
|
+
if (!content.includes(".hamma/")) {
|
|
156
|
+
return {
|
|
157
|
+
name: ".gitignore safety",
|
|
158
|
+
status: "warn",
|
|
159
|
+
message: `\`.hamma/\` is not in .gitignore of ${label}. Handoff will append it (or pass --no-gitignore to skip).`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
name: ".gitignore safety",
|
|
164
|
+
status: "pass",
|
|
165
|
+
message: `\`.hamma/\` is ignored in ${label}.`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export async function runDoctor() {
|
|
169
|
+
const checks = [];
|
|
170
|
+
checks.push(checkNode());
|
|
171
|
+
checks.push(checkGit());
|
|
172
|
+
const { check: codexCheck, latestPath } = await checkCodexSessions();
|
|
173
|
+
checks.push(codexCheck);
|
|
174
|
+
const { check: projectPathCheck, projectPath } = await checkProjectPath(latestPath);
|
|
175
|
+
checks.push(projectPathCheck);
|
|
176
|
+
checks.push(await checkGitignoreSafety(projectPath));
|
|
177
|
+
console.log(pc.bold("hamma doctor\n"));
|
|
178
|
+
for (const c of checks) {
|
|
179
|
+
console.log(`${marker(c.status)} ${pc.bold(c.name)} — ${c.message}`);
|
|
180
|
+
}
|
|
181
|
+
const passed = checks.filter((c) => c.status === "pass").length;
|
|
182
|
+
const warned = checks.filter((c) => c.status === "warn").length;
|
|
183
|
+
const failed = checks.filter((c) => c.status === "fail").length;
|
|
184
|
+
console.log("");
|
|
185
|
+
console.log(`${pc.green(`${passed} pass`)}, ${pc.yellow(`${warned} warn`)}, ${pc.red(`${failed} fail`)}`);
|
|
186
|
+
return failed > 0 ? 1 : 0;
|
|
187
|
+
}
|