@h1veframework/cli 0.5.2 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +46 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -50,6 +50,24 @@ async function createBranch(name, runner = defaultGitRunner) {
50
50
  throw new Error("GIT_BRANCH_FAILED");
51
51
  }
52
52
  }
53
+ async function switchBranch(name, runner = defaultGitRunner) {
54
+ try {
55
+ await runner(["switch", name]);
56
+ } catch (err) {
57
+ const text = gitErrText(err);
58
+ if (/not a git repository/i.test(text)) throw new Error("NOT_A_GIT_REPO");
59
+ throw new Error("GIT_SWITCH_FAILED");
60
+ }
61
+ }
62
+ async function listLocalBranches(runner = defaultGitRunner) {
63
+ let out;
64
+ try {
65
+ out = await runner(["for-each-ref", "--format=%(refname:short)", "refs/heads"]);
66
+ } catch {
67
+ return [];
68
+ }
69
+ return out.split("\n").map((l) => l.trim()).filter(Boolean);
70
+ }
53
71
 
54
72
  // ../core/src/client.ts
55
73
  var NexusApiError = class extends Error {
@@ -472,16 +490,17 @@ function formatBlocker(r) {
472
490
  return `\u2713 Blocker aberto: ${r.description}`;
473
491
  }
474
492
  function formatStartList(list) {
475
- const header = "Features atribu\xEDdas a iniciar \u2014 escolha: nf start <n\xBA>";
476
- const rows = list.map(
477
- (f, i) => ` ${String(i + 1).padStart(2)}. ${f.name} [${stageLabel(f.stage)}]`
478
- );
493
+ const header = "Features para iniciar ou retomar \u2014 escolha: nf start <n\xBA>";
494
+ const rows = list.map((f, i) => {
495
+ const tag = f.branch_slug !== null ? " (retomar)" : "";
496
+ return ` ${String(i + 1).padStart(2)}. ${f.name} [${stageLabel(f.stage)}]${tag}`;
497
+ });
479
498
  return [header, ...rows].join("\n");
480
499
  }
481
- function formatStart(r) {
500
+ function formatStart(r, opts) {
482
501
  return [
483
- `\u2713 Iniciada: ${r.feature.name}`,
484
- `Branch: ${r.branch_name} (criada e ativa)`,
502
+ `\u2713 ${opts?.resumed ? "Retomada" : "Iniciada"}: ${r.feature.name}`,
503
+ `Branch: ${r.branch_name} (${opts?.resumed ? "pronta e ativa" : "criada e ativa"})`,
485
504
  "Pr\xF3ximo: codar \u2192 'nf status' \u2192 'nf done' (move dev \u2192 pr)"
486
505
  ].join("\n");
487
506
  }
@@ -603,8 +622,15 @@ async function health(io) {
603
622
  }
604
623
 
605
624
  // src/commands/start.ts
606
- function startable(features) {
607
- return features.filter((f) => f.branch_slug === null && f.stage !== "main");
625
+ function branchSlugOf(branch) {
626
+ return branch.match(/^feat\/[^/]+\/(.+)$/)?.[1] ?? null;
627
+ }
628
+ function startable(features, localBranches) {
629
+ const localSlugs = new Set(
630
+ localBranches.map(branchSlugOf).filter((s) => s !== null)
631
+ );
632
+ const hasLocalBranch = (f) => f.branch_slug !== null && localSlugs.has(f.branch_slug);
633
+ return features.filter((f) => f.stage !== "main" && !hasLocalBranch(f));
608
634
  }
609
635
  function pick(list, arg) {
610
636
  if (!arg) return list.length === 1 ? list[0] ?? null : null;
@@ -615,27 +641,25 @@ function pick(list, arg) {
615
641
  throw new CliError(`Feature n\xE3o encontrada: ${arg}. Rode 'nf start' para ver a lista.`);
616
642
  }
617
643
  async function start(io, args) {
618
- const list = startable(await io.client.listFeatures());
644
+ const [features, localBranches] = await Promise.all([io.client.listFeatures(), io.listBranches()]);
645
+ const list = startable(features, localBranches);
619
646
  if (list.length === 0) {
620
- throw new CliError("Nenhuma feature atribu\xEDda a iniciar (sem branch e fora de main).");
647
+ throw new CliError("Nenhuma feature para iniciar ou retomar (sem branch local e fora de main).");
621
648
  }
622
649
  const selected = pick(list, args.positional);
623
650
  if (!selected) {
624
651
  return { human: formatStartList(list), data: { features: list } };
625
652
  }
653
+ const resuming = selected.branch_slug !== null;
626
654
  const result = await io.client.startFeature(selected.id, args.slug);
627
655
  try {
628
656
  await io.createBranch(result.branch_name);
629
657
  } catch (err) {
630
658
  const code = err instanceof Error ? err.message : "";
631
- if (code === "BRANCH_EXISTS") {
632
- throw new CliError(
633
- `branch_slug gravado, mas a branch '${result.branch_name}' j\xE1 existe. Rode: git switch ${result.branch_name}`
634
- );
635
- }
636
- throw err;
659
+ if (code === "BRANCH_EXISTS") await io.switchBranch(result.branch_name);
660
+ else throw err;
637
661
  }
638
- return { human: formatStart(result), data: result };
662
+ return { human: formatStart(result, { resumed: resuming }), data: result };
639
663
  }
640
664
 
641
665
  // src/commands/connect.ts
@@ -1262,14 +1286,14 @@ async function logout(io, _args) {
1262
1286
  }
1263
1287
 
1264
1288
  // src/index.ts
1265
- var VERSION = "0.5.0";
1289
+ var VERSION = "0.6.0";
1266
1290
  var HELP = `nf \u2014 CLI do Nexus Flow
1267
1291
 
1268
1292
  Uso:
1269
1293
  nf login autoriza no navegador e salva a credencial local (sem colar nada)
1270
1294
  nf logout remove a credencial local
1271
1295
  nf start [<n\xBA|id>] [--slug <s>]
1272
- inicia uma feature atribu\xEDda: cria a branch e grava o slug
1296
+ inicia (ou retoma) uma feature: cria/entra na branch e grava o slug
1273
1297
  nf status estado da feature da branch atual
1274
1298
  nf spec imprime a spec da feature
1275
1299
  nf move <stage> [--note] move a feature de stage
@@ -1363,6 +1387,8 @@ ${HELP}`);
1363
1387
  client,
1364
1388
  resolveBranch: () => currentBranch(defaultGitRunner),
1365
1389
  createBranch: (name) => createBranch(name, defaultGitRunner),
1390
+ switchBranch: (name) => switchBranch(name, defaultGitRunner),
1391
+ listBranches: () => listLocalBranches(defaultGitRunner),
1366
1392
  readFile: (path) => readFile(path, "utf8"),
1367
1393
  readStdin,
1368
1394
  writeEnvVars: (pairs, opts) => writeEnvVars(pairs, { file: opts?.file }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h1veframework/cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "The CLI for H1VE — the governance & memory layer for teams building with AI. Drive your feature flow (start, status, move, spec, done) from the terminal. Works with Claude Code, Cursor and Copilot.",
5
5
  "license": "MIT",
6
6
  "type": "module",