@duckcodeailabs/dql-cli 1.3.1 → 1.3.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.
@@ -1,16 +1,16 @@
1
1
  <!DOCTYPE html>
2
- <html lang="en">
2
+ <html lang="en" data-theme="paper">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>DQL Notebook</title>
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-Wl3Yxney.js"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600;700&family=Kalam:wght@400;700&family=Caveat:wght@400;600;700&display=swap" rel="stylesheet" />
10
+ <script type="module" crossorigin src="/assets/index-CudxJ9DW.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/react-CRB3T2We.js">
12
- <link rel="modulepreload" crossorigin href="/assets/codemirror-BqWuFwtC.js">
13
- <link rel="stylesheet" crossorigin href="/assets/index-C_x4yXmA.css">
12
+ <link rel="modulepreload" crossorigin href="/assets/codemirror-DJYUkPr1.js">
13
+ <link rel="stylesheet" crossorigin href="/assets/index-BfBn8l8M.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="root"></div>
@@ -1 +1 @@
1
- {"version":3,"file":"local-runtime.d.ts","sourceRoot":"","sources":["../src/local-runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-runtime.d.ts","sourceRoot":"","sources":["../src/local-runtime.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAUtF,OAAO,EAcL,KAAK,aAAa,EAClB,KAAK,2BAA2B,EAUjC,MAAM,0BAA0B,CAAC;AAclC,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,gBAAgB,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,2BAA2B,CAAC;IAC5C,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,aAAa,CAAC;IACxB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+pEhF;AAED,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,aAAa,EACvB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,gBAAgB,EAC5B,KAAK,EAAE,OAAO,GACb,MAAM,CAaR;AAmCD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQpD;AA8BD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYxD;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAwBpE;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,gBAAgB,EAC5B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,aAAa,GAC3B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,gBAAgB,CAAA;CAAE,CAQ/C;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC1D,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,aAAa,GAAG,SAAS,GACvC,mBAAmB,CAarB;AAED,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAY9G;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAezG;AAgaD,KAAK,qBAAqB,GAAG;IAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAkMxG,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,aAAa,GAC5B;IACD,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,qBAAqB,EAAE,CAAC;IACrC,YAAY,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC9E,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzF,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CA4FA;AA2MD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAa5E;AAED,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACvC,GACA;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAgD1D;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,UAAU,GAAG,QAAQ,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CA2C1D;AAqcD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6CD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB"}
@@ -282,8 +282,121 @@ export async function startLocalServer(opts) {
282
282
  }
283
283
  if (req.method === 'GET' && path === '/api/git/diff') {
284
284
  const filePath = url.searchParams.get('path') ?? '';
285
+ const staged = url.searchParams.get('staged') === 'true';
285
286
  res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
286
- res.end(serializeJSON(await readGitDiff(projectRoot, filePath)));
287
+ res.end(serializeJSON(await readGitDiff(projectRoot, filePath, staged)));
288
+ return;
289
+ }
290
+ if (req.method === 'GET' && path === '/api/git/branches') {
291
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
292
+ res.end(serializeJSON(await readGitBranches(projectRoot)));
293
+ return;
294
+ }
295
+ if (req.method === 'GET' && path === '/api/git/remote') {
296
+ res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
297
+ res.end(serializeJSON(await readGitRemote(projectRoot)));
298
+ return;
299
+ }
300
+ if (req.method === 'POST' && path === '/api/git/stage') {
301
+ try {
302
+ const body = (await readJSON(req));
303
+ const result = await gitStage(projectRoot, body.paths ?? []);
304
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
305
+ res.end(serializeJSON(result));
306
+ }
307
+ catch (e) {
308
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
309
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
310
+ }
311
+ return;
312
+ }
313
+ if (req.method === 'POST' && path === '/api/git/unstage') {
314
+ try {
315
+ const body = (await readJSON(req));
316
+ const result = await gitUnstage(projectRoot, body.paths ?? []);
317
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
318
+ res.end(serializeJSON(result));
319
+ }
320
+ catch (e) {
321
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
322
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
323
+ }
324
+ return;
325
+ }
326
+ if (req.method === 'POST' && path === '/api/git/discard') {
327
+ try {
328
+ const body = (await readJSON(req));
329
+ const result = await gitDiscard(projectRoot, body.paths ?? []);
330
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
331
+ res.end(serializeJSON(result));
332
+ }
333
+ catch (e) {
334
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
335
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
336
+ }
337
+ return;
338
+ }
339
+ if (req.method === 'POST' && path === '/api/git/commit') {
340
+ try {
341
+ const body = (await readJSON(req));
342
+ const result = await gitCommit(projectRoot, body.message ?? '', body.stageAll === true);
343
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
344
+ res.end(serializeJSON(result));
345
+ }
346
+ catch (e) {
347
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
348
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
349
+ }
350
+ return;
351
+ }
352
+ if (req.method === 'POST' && path === '/api/git/push') {
353
+ try {
354
+ const result = await gitPush(projectRoot);
355
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
356
+ res.end(serializeJSON(result));
357
+ }
358
+ catch (e) {
359
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
360
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
361
+ }
362
+ return;
363
+ }
364
+ if (req.method === 'POST' && path === '/api/git/pull') {
365
+ try {
366
+ const result = await gitPull(projectRoot);
367
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
368
+ res.end(serializeJSON(result));
369
+ }
370
+ catch (e) {
371
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
372
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
373
+ }
374
+ return;
375
+ }
376
+ if (req.method === 'POST' && path === '/api/git/branch') {
377
+ try {
378
+ const body = (await readJSON(req));
379
+ const result = await gitCreateBranch(projectRoot, body.name ?? '', body.checkout !== false);
380
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
381
+ res.end(serializeJSON(result));
382
+ }
383
+ catch (e) {
384
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
385
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
386
+ }
387
+ return;
388
+ }
389
+ if (req.method === 'POST' && path === '/api/git/checkout') {
390
+ try {
391
+ const body = (await readJSON(req));
392
+ const result = await gitCheckout(projectRoot, body.name ?? '');
393
+ res.writeHead(result.ok ? 200 : 400, { 'Content-Type': 'application/json; charset=utf-8' });
394
+ res.end(serializeJSON(result));
395
+ }
396
+ catch (e) {
397
+ res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
398
+ res.end(serializeJSON({ ok: false, error: e instanceof Error ? e.message : String(e) }));
399
+ }
287
400
  return;
288
401
  }
289
402
  if (req.method === 'GET' && path === '/api/schema') {
@@ -3523,24 +3636,157 @@ function ensureGitignoreEntry(projectRoot, pattern) {
3523
3636
  // Best-effort; failure to write .gitignore shouldn't fail the snapshot.
3524
3637
  }
3525
3638
  }
3526
- async function readGitDiff(cwd, filePath) {
3639
+ async function readGitDiff(cwd, filePath, staged = false) {
3527
3640
  const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
3528
3641
  if (isRepo.code !== 0) {
3529
3642
  return { inRepo: false, diff: '', before: null, after: null, diffReport: null };
3530
3643
  }
3644
+ const baseArgs = staged ? ['diff', '--cached', '--no-color'] : ['diff', '--no-color'];
3531
3645
  if (!filePath) {
3532
- const res = await execGit(cwd, ['diff', '--no-color']);
3646
+ const res = await execGit(cwd, baseArgs);
3533
3647
  return { inRepo: true, diff: res.stdout, before: null, after: null, diffReport: null };
3534
3648
  }
3535
3649
  const isSemantic = filePath.endsWith('.dql') || filePath.endsWith('.dqlnb');
3536
3650
  const [diffRes, before, after] = await Promise.all([
3537
- execGit(cwd, ['diff', '--no-color', '--', filePath]),
3651
+ execGit(cwd, [...baseArgs, '--', filePath]),
3538
3652
  isSemantic ? readHeadBlob(cwd, filePath) : Promise.resolve(null),
3539
3653
  isSemantic ? readWorkingCopy(join(cwd, filePath)) : Promise.resolve(null),
3540
3654
  ]);
3541
3655
  const diffReport = isSemantic ? computeSemanticDiff(filePath, before, after) : null;
3542
3656
  return { inRepo: true, diff: diffRes.stdout, before, after, diffReport };
3543
3657
  }
3658
+ // ── git write operations ──────────────────────────────────────────────────
3659
+ // Each helper validates inputs, shells out via execFile (no shell expansion),
3660
+ // then reports the trimmed stderr on failure so the UI can surface it. We
3661
+ // never accept absolute paths or paths containing `..` — staged paths must
3662
+ // stay inside the project root.
3663
+ function validatePaths(cwd, paths) {
3664
+ if (!Array.isArray(paths) || paths.length === 0) {
3665
+ return { ok: false, error: 'No paths provided' };
3666
+ }
3667
+ const cleaned = [];
3668
+ for (const p of paths) {
3669
+ if (typeof p !== 'string' || p.length === 0)
3670
+ return { ok: false, error: 'Invalid path' };
3671
+ if (p.startsWith('/'))
3672
+ return { ok: false, error: `Absolute path not allowed: ${p}` };
3673
+ if (p.split('/').includes('..'))
3674
+ return { ok: false, error: `Path escape not allowed: ${p}` };
3675
+ const resolved = join(cwd, p);
3676
+ if (!resolved.startsWith(cwd))
3677
+ return { ok: false, error: `Path outside project: ${p}` };
3678
+ cleaned.push(p);
3679
+ }
3680
+ return { ok: true, paths: cleaned };
3681
+ }
3682
+ function gitErrorOutput(res) {
3683
+ return (res.stderr || res.stdout || '').trim();
3684
+ }
3685
+ async function gitStage(cwd, paths) {
3686
+ const v = validatePaths(cwd, paths);
3687
+ if (!v.ok)
3688
+ return { ok: false, error: v.error };
3689
+ const res = await execGit(cwd, ['add', '--', ...v.paths]);
3690
+ return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
3691
+ }
3692
+ async function gitUnstage(cwd, paths) {
3693
+ const v = validatePaths(cwd, paths);
3694
+ if (!v.ok)
3695
+ return { ok: false, error: v.error };
3696
+ // `restore --staged` works with or without HEAD; for an initial commit (no
3697
+ // HEAD yet) git's `rm --cached` is the fallback. Try restore first.
3698
+ const res = await execGit(cwd, ['restore', '--staged', '--', ...v.paths]);
3699
+ if (res.code === 0)
3700
+ return { ok: true };
3701
+ const fallback = await execGit(cwd, ['rm', '--cached', '-r', '--', ...v.paths]);
3702
+ return fallback.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(fallback) };
3703
+ }
3704
+ async function gitDiscard(cwd, paths) {
3705
+ const v = validatePaths(cwd, paths);
3706
+ if (!v.ok)
3707
+ return { ok: false, error: v.error };
3708
+ // For tracked files: `restore --worktree` reverts to HEAD. For untracked
3709
+ // files: that's a no-op and we delete them via `clean -f`. Run both so
3710
+ // the caller doesn't have to know which list each path is in.
3711
+ const restore = await execGit(cwd, ['restore', '--worktree', '--', ...v.paths]);
3712
+ const clean = await execGit(cwd, ['clean', '-f', '--', ...v.paths]);
3713
+ if (restore.code !== 0 && clean.code !== 0) {
3714
+ return { ok: false, error: gitErrorOutput(restore) || gitErrorOutput(clean) };
3715
+ }
3716
+ return { ok: true };
3717
+ }
3718
+ async function gitCommit(cwd, message, stageAll) {
3719
+ const trimmed = message.trim();
3720
+ if (!trimmed)
3721
+ return { ok: false, error: 'Commit message required' };
3722
+ if (stageAll) {
3723
+ const add = await execGit(cwd, ['add', '-A']);
3724
+ if (add.code !== 0)
3725
+ return { ok: false, error: gitErrorOutput(add) };
3726
+ }
3727
+ const res = await execGit(cwd, ['commit', '-m', trimmed]);
3728
+ if (res.code !== 0)
3729
+ return { ok: false, error: gitErrorOutput(res) };
3730
+ const hashRes = await execGit(cwd, ['rev-parse', 'HEAD']);
3731
+ return { ok: true, hash: hashRes.code === 0 ? hashRes.stdout.trim() : undefined };
3732
+ }
3733
+ async function gitPush(cwd) {
3734
+ const res = await execGit(cwd, ['push']);
3735
+ return res.code === 0
3736
+ ? { ok: true, output: gitErrorOutput(res) }
3737
+ : { ok: false, error: gitErrorOutput(res) };
3738
+ }
3739
+ async function gitPull(cwd) {
3740
+ // `--ff-only` keeps the operation non-destructive: if the local branch has
3741
+ // diverged from upstream, we surface the error rather than auto-merging.
3742
+ // The user can resolve via the terminal or a future merge UI.
3743
+ const res = await execGit(cwd, ['pull', '--ff-only']);
3744
+ return res.code === 0
3745
+ ? { ok: true, output: gitErrorOutput(res) }
3746
+ : { ok: false, error: gitErrorOutput(res) };
3747
+ }
3748
+ async function readGitBranches(cwd) {
3749
+ const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
3750
+ if (isRepo.code !== 0)
3751
+ return { inRepo: false, current: null, branches: [] };
3752
+ const cur = await execGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
3753
+ const list = await execGit(cwd, ['branch', '--list', '--format=%(refname:short)']);
3754
+ const branches = list.code === 0
3755
+ ? list.stdout.split('\n').map((s) => s.trim()).filter(Boolean)
3756
+ : [];
3757
+ return { inRepo: true, current: cur.code === 0 ? cur.stdout.trim() : null, branches };
3758
+ }
3759
+ async function readGitRemote(cwd) {
3760
+ const isRepo = await execGit(cwd, ['rev-parse', '--is-inside-work-tree']);
3761
+ if (isRepo.code !== 0)
3762
+ return { inRepo: false, url: null, name: null };
3763
+ const remoteName = await execGit(cwd, ['config', '--get', 'remote.pushDefault']);
3764
+ const name = remoteName.code === 0 && remoteName.stdout.trim() ? remoteName.stdout.trim() : 'origin';
3765
+ const url = await execGit(cwd, ['remote', 'get-url', name]);
3766
+ return { inRepo: true, url: url.code === 0 ? url.stdout.trim() : null, name };
3767
+ }
3768
+ async function gitCreateBranch(cwd, name, checkout) {
3769
+ const trimmed = name.trim();
3770
+ // Branch names can't start with `-` (would be parsed as a flag) and must be
3771
+ // non-empty. git itself enforces the rest of the ref-name rules.
3772
+ if (!trimmed)
3773
+ return { ok: false, error: 'Branch name required' };
3774
+ if (trimmed.startsWith('-'))
3775
+ return { ok: false, error: 'Invalid branch name' };
3776
+ const res = checkout
3777
+ ? await execGit(cwd, ['checkout', '-b', trimmed])
3778
+ : await execGit(cwd, ['branch', trimmed]);
3779
+ return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
3780
+ }
3781
+ async function gitCheckout(cwd, name) {
3782
+ const trimmed = name.trim();
3783
+ if (!trimmed)
3784
+ return { ok: false, error: 'Branch name required' };
3785
+ if (trimmed.startsWith('-'))
3786
+ return { ok: false, error: 'Invalid branch name' };
3787
+ const res = await execGit(cwd, ['checkout', trimmed]);
3788
+ return res.code === 0 ? { ok: true } : { ok: false, error: gitErrorOutput(res) };
3789
+ }
3544
3790
  async function readHeadBlob(cwd, filePath) {
3545
3791
  try {
3546
3792
  const res = await execGit(cwd, ['show', `HEAD:${filePath}`]);