dahrk-node 0.1.1 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/main.js +151 -69
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1269,6 +1269,25 @@ function createGitService(opts = {}) {
1269
1269
  log2.warn(`could not set worktree scratch exclude at ${worktreePath}: ${e.message}`);
1270
1270
  }
1271
1271
  };
1272
+ const commitPending = (worktreePath, message) => {
1273
+ const SCRATCH = ".skakel/scratch";
1274
+ excludeScratchLocally(worktreePath);
1275
+ git(worktreePath, ["rm", "-r", "--cached", "--ignore-unmatch", "--quiet", SCRATCH]);
1276
+ git(worktreePath, ["add", "-A", "--", "."]);
1277
+ const dirty = !gitOk(worktreePath, ["diff", "--cached", "--quiet"]);
1278
+ if (dirty) {
1279
+ git(worktreePath, [
1280
+ "-c",
1281
+ `user.name=${authorName}`,
1282
+ "-c",
1283
+ `user.email=${authorEmail}`,
1284
+ "commit",
1285
+ "-m",
1286
+ message
1287
+ ]);
1288
+ }
1289
+ return { headSha: git(worktreePath, ["rev-parse", "HEAD"]).trim(), dirty };
1290
+ };
1272
1291
  const setupAuth = (token) => {
1273
1292
  const dir = mkdtempSync(join2(tmpdir(), "dahrk-cred-"));
1274
1293
  const script = join2(dir, "askpass.sh");
@@ -1334,6 +1353,9 @@ function createGitService(opts = {}) {
1334
1353
  } finally {
1335
1354
  auth?.cleanup();
1336
1355
  }
1356
+ if (!gitOk(worktreePath, ["rev-parse", "--verify", "-q", "HEAD"])) {
1357
+ throw new Error(`base '${baseBranch}' did not materialise into ${worktreePath} (unborn HEAD)`);
1358
+ }
1337
1359
  mkdirSync(join2(worktreePath, ".skakel", "scratch"), { recursive: true });
1338
1360
  return refFor(spec, worktreePath);
1339
1361
  },
@@ -1343,24 +1365,8 @@ function createGitService(opts = {}) {
1343
1365
  throw new Error(`worktree missing for push: ${worktreePath}`);
1344
1366
  }
1345
1367
  const branch = sanitizeBranchName(opts2.branch);
1346
- const SCRATCH = ".skakel/scratch";
1347
- excludeScratchLocally(worktreePath);
1348
- git(worktreePath, ["rm", "-r", "--cached", "--ignore-unmatch", "--quiet", SCRATCH]);
1349
- git(worktreePath, ["add", "-A", "--", "."]);
1350
- const hasStaged = !gitOk(worktreePath, ["diff", "--cached", "--quiet"]);
1351
- if (hasStaged) {
1352
- git(worktreePath, [
1353
- "-c",
1354
- `user.name=${authorName}`,
1355
- "-c",
1356
- `user.email=${authorEmail}`,
1357
- "commit",
1358
- "-m",
1359
- opts2.message
1360
- ]);
1361
- }
1362
- const dirty = hasStaged;
1363
- let headSha = git(worktreePath, ["rev-parse", "HEAD"]).trim();
1368
+ const { headSha: committedSha, dirty } = commitPending(worktreePath, opts2.message);
1369
+ let headSha = committedSha;
1364
1370
  let commitsAhead = 0;
1365
1371
  for (const baseRef of [opts2.base, `origin/${opts2.base}`, `refs/heads/${opts2.base}`]) {
1366
1372
  if (!baseRef) continue;
@@ -1385,6 +1391,9 @@ function createGitService(opts = {}) {
1385
1391
  }
1386
1392
  }
1387
1393
  if (fetched) {
1394
+ if (!gitOk(worktreePath, ["merge-base", "HEAD", "FETCH_HEAD"])) {
1395
+ return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "diverged" };
1396
+ }
1388
1397
  try {
1389
1398
  git(worktreePath, [
1390
1399
  "-c",
@@ -1397,10 +1406,18 @@ function createGitService(opts = {}) {
1397
1406
  ], auth?.env);
1398
1407
  integration = "clean";
1399
1408
  headSha = git(worktreePath, ["rev-parse", "HEAD"]).trim();
1400
- } catch {
1401
- const conflictFiles = git(worktreePath, ["diff", "--name-only", "--diff-filter=U"]).split("\n").map((l) => l.trim()).filter(Boolean);
1402
- git(worktreePath, ["merge", "--abort"]);
1403
- return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "conflict", conflictFiles };
1409
+ } catch (mergeErr) {
1410
+ const inMerge = gitOk(worktreePath, ["rev-parse", "--verify", "-q", "MERGE_HEAD"]);
1411
+ if (inMerge) {
1412
+ const conflictFiles = git(worktreePath, ["diff", "--name-only", "--diff-filter=U"]).split("\n").map((l) => l.trim()).filter(Boolean);
1413
+ git(worktreePath, ["merge", "--abort"]);
1414
+ return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "conflict", conflictFiles };
1415
+ }
1416
+ const msg = mergeErr.message;
1417
+ if (/unrelated histories|refusing to merge/i.test(msg)) {
1418
+ return { headSha, pushed: false, nothingToCommit: !dirty, commitsAhead, integration: "diverged" };
1419
+ }
1420
+ throw mergeErr;
1404
1421
  }
1405
1422
  }
1406
1423
  git(worktreePath, ["push", remote, `HEAD:refs/heads/${branch}`], netEnv(auth?.env));
@@ -1410,6 +1427,22 @@ function createGitService(opts = {}) {
1410
1427
  }
1411
1428
  return { headSha, pushed, nothingToCommit: !dirty, commitsAhead, ...integration ? { integration } : {} };
1412
1429
  },
1430
+ async backupPush(ref, opts2) {
1431
+ const { worktreePath } = ref;
1432
+ if (!existsSync(worktreePath) || !gitOk(worktreePath, ["rev-parse", "--git-dir"])) {
1433
+ throw new Error(`worktree missing for backup push: ${worktreePath}`);
1434
+ }
1435
+ const wipRef = sanitizeBranchName(opts2.branch);
1436
+ const { headSha, dirty } = commitPending(worktreePath, opts2.message);
1437
+ const auth = opts2.credentialToken ? setupAuth(opts2.credentialToken) : void 0;
1438
+ const remote = opts2.credentialToken ? withTokenUser(ref.gitUrl) : ref.gitUrl;
1439
+ try {
1440
+ git(worktreePath, ["push", "--force", remote, `HEAD:refs/heads/${wipRef}`], netEnv(auth?.env));
1441
+ } finally {
1442
+ auth?.cleanup();
1443
+ }
1444
+ return { headSha, pushed: true, nothingToCommit: !dirty, wipRef };
1445
+ },
1413
1446
  async openPrAmbient(ref, opts2) {
1414
1447
  const ownerRepo = parseOwnerRepo(ref.gitUrl);
1415
1448
  if (!ownerRepo) return { prError: `cannot derive owner/repo from ${ref.gitUrl}` };
@@ -2277,62 +2310,111 @@ function createStageRunner(deps) {
2277
2310
  return { jobId, status: "fail", summary: `edge does not serve repo "${job.workspaceRef.repoId}"` };
2278
2311
  }
2279
2312
  lastUsed.set(runId, Date.now());
2280
- let ref = worktrees.get(runId);
2281
- if (!ref) {
2282
- ref = await deps.gitService.createWorktree({
2283
- repoId: job.workspaceRef.repoId,
2284
- gitUrl: job.workspaceRef.gitUrl,
2285
- baseBranch: job.workspaceRef.baseBranch,
2286
- runId,
2287
- repo: job.workspaceRef.repo,
2288
- branch: job.branch,
2289
- ...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
2290
- });
2291
- worktrees.set(runId, ref);
2292
- }
2313
+ inFlight.set(runId, (inFlight.get(runId) ?? 0) + 1);
2293
2314
  try {
2294
- const r = await deps.gitService.commitAndPush(ref, {
2295
- message: job.message,
2296
- branch: job.branch,
2297
- base: job.base,
2298
- ...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
2299
- });
2300
- if (r.integration === "conflict") {
2315
+ const mode = job.mode ?? "deliver";
2316
+ if (mode === "backup") {
2317
+ const ref2 = worktrees.get(runId);
2318
+ if (!ref2) {
2319
+ return {
2320
+ jobId,
2321
+ status: "fail",
2322
+ branch: job.branch,
2323
+ summary: `backup push: run ${runId} has no live worktree; committed HEAD cannot be preserved`
2324
+ };
2325
+ }
2326
+ try {
2327
+ const r = await deps.gitService.backupPush(ref2, {
2328
+ message: job.message,
2329
+ branch: job.branch,
2330
+ ...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
2331
+ });
2332
+ const result = {
2333
+ jobId,
2334
+ status: "ok",
2335
+ branch: job.branch,
2336
+ headSha: r.headSha,
2337
+ pushed: r.pushed,
2338
+ nothingToCommit: r.nothingToCommit,
2339
+ wipRef: r.wipRef,
2340
+ summary: `backup: preserved ${r.headSha.slice(0, 7)} on ${r.wipRef} (no base merge, no PR)`
2341
+ };
2342
+ return result;
2343
+ } catch (e) {
2344
+ return { jobId, status: "fail", branch: job.branch, summary: `backup push failed: ${e.message}` };
2345
+ }
2346
+ }
2347
+ let ref = worktrees.get(runId);
2348
+ if (!ref) {
2349
+ ref = await deps.gitService.createWorktree({
2350
+ repoId: job.workspaceRef.repoId,
2351
+ gitUrl: job.workspaceRef.gitUrl,
2352
+ baseBranch: job.workspaceRef.baseBranch,
2353
+ runId,
2354
+ repo: job.workspaceRef.repo,
2355
+ branch: job.branch,
2356
+ ...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
2357
+ });
2358
+ worktrees.set(runId, ref);
2359
+ }
2360
+ try {
2361
+ const r = await deps.gitService.commitAndPush(ref, {
2362
+ message: job.message,
2363
+ branch: job.branch,
2364
+ base: job.base,
2365
+ ...job.workspaceRef.credentialToken ? { credentialToken: job.workspaceRef.credentialToken } : {}
2366
+ });
2367
+ if (r.integration === "conflict") {
2368
+ return {
2369
+ jobId,
2370
+ status: "ok",
2371
+ branch: job.branch,
2372
+ headSha: r.headSha,
2373
+ pushed: false,
2374
+ nothingToCommit: r.nothingToCommit,
2375
+ commitsAhead: r.commitsAhead,
2376
+ integration: "conflict",
2377
+ ...r.conflictFiles ? { conflictFiles: r.conflictFiles } : {},
2378
+ summary: `base advanced; merge conflict on ${job.branch} (manual merge needed)`
2379
+ };
2380
+ }
2381
+ if (r.integration === "diverged") {
2382
+ return {
2383
+ jobId,
2384
+ status: "fail",
2385
+ branch: job.branch,
2386
+ headSha: r.headSha,
2387
+ pushed: false,
2388
+ nothingToCommit: r.nothingToCommit,
2389
+ commitsAhead: r.commitsAhead,
2390
+ summary: `branch history diverged from ${job.base}; cannot auto-integrate on ${job.branch} (the branch likely needs rebuilding from ${job.base})`
2391
+ };
2392
+ }
2393
+ const pr = job.openPr && r.pushed ? await deps.gitService.openPrAmbient(ref, {
2394
+ branch: job.branch,
2395
+ base: job.base,
2396
+ title: job.openPr.title,
2397
+ body: job.openPr.body
2398
+ }) : void 0;
2301
2399
  return {
2302
2400
  jobId,
2303
2401
  status: "ok",
2304
2402
  branch: job.branch,
2305
2403
  headSha: r.headSha,
2306
- pushed: false,
2404
+ pushed: r.pushed,
2307
2405
  nothingToCommit: r.nothingToCommit,
2308
2406
  commitsAhead: r.commitsAhead,
2309
- integration: "conflict",
2310
- ...r.conflictFiles ? { conflictFiles: r.conflictFiles } : {},
2311
- summary: `base advanced; merge conflict on ${job.branch} (manual merge needed)`
2407
+ ...r.integration ? { integration: r.integration } : {},
2408
+ ...pr?.prUrl ? { prUrl: pr.prUrl } : {},
2409
+ ...pr?.prNumber !== void 0 ? { prNumber: pr.prNumber } : {},
2410
+ ...pr?.prError ? { prError: pr.prError } : {},
2411
+ summary: r.nothingToCommit ? `no changes to commit; ${r.pushed ? "branch pushed" : "nothing pushed"}` : `committed ${r.headSha.slice(0, 7)} and pushed ${job.branch}`
2312
2412
  };
2413
+ } catch (e) {
2414
+ return { jobId, status: "fail", summary: `push failed: ${e.message}` };
2313
2415
  }
2314
- const pr = job.openPr && r.pushed ? await deps.gitService.openPrAmbient(ref, {
2315
- branch: job.branch,
2316
- base: job.base,
2317
- title: job.openPr.title,
2318
- body: job.openPr.body
2319
- }) : void 0;
2320
- return {
2321
- jobId,
2322
- status: "ok",
2323
- branch: job.branch,
2324
- headSha: r.headSha,
2325
- pushed: r.pushed,
2326
- nothingToCommit: r.nothingToCommit,
2327
- commitsAhead: r.commitsAhead,
2328
- ...r.integration ? { integration: r.integration } : {},
2329
- ...pr?.prUrl ? { prUrl: pr.prUrl } : {},
2330
- ...pr?.prNumber !== void 0 ? { prNumber: pr.prNumber } : {},
2331
- ...pr?.prError ? { prError: pr.prError } : {},
2332
- summary: r.nothingToCommit ? `no changes to commit; ${r.pushed ? "branch pushed" : "nothing pushed"}` : `committed ${r.headSha.slice(0, 7)} and pushed ${job.branch}`
2333
- };
2334
- } catch (e) {
2335
- return { jobId, status: "fail", summary: `push failed: ${e.message}` };
2416
+ } finally {
2417
+ inFlight.set(runId, Math.max(0, (inFlight.get(runId) ?? 1) - 1));
2336
2418
  }
2337
2419
  },
2338
2420
  cancel(jobId) {
@@ -2882,7 +2964,7 @@ async function runDoctor(inputs, deps = {}) {
2882
2964
  }
2883
2965
 
2884
2966
  // src/main.ts
2885
- var CLIENT_VERSION = "0.1.1";
2967
+ var CLIENT_VERSION = "0.1.2";
2886
2968
  var DEFAULT_HUB_URL = "wss://api.dahrk.ai";
2887
2969
  var list = (v) => (v ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2888
2970
  var RUNTIMES = ["claude-code", "codex", "pi"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dahrk-node",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",