@h1veframework/cli 0.12.2 → 0.14.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/dist/index.js +90 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,6 +61,21 @@ async function switchBranch(name, runner = defaultGitRunner) {
|
|
|
61
61
|
throw new Error("GIT_SWITCH_FAILED");
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
async function commitsBehindOriginMain(runner = defaultGitRunner) {
|
|
65
|
+
try {
|
|
66
|
+
await runner(["fetch", "origin"]);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
let out;
|
|
71
|
+
try {
|
|
72
|
+
out = await runner(["rev-list", "--count", "HEAD..origin/main"]);
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const n = Number(out.trim());
|
|
77
|
+
return Number.isInteger(n) && n >= 0 ? n : null;
|
|
78
|
+
}
|
|
64
79
|
async function listLocalBranches(runner = defaultGitRunner) {
|
|
65
80
|
let out;
|
|
66
81
|
try {
|
|
@@ -197,6 +212,9 @@ function createClient(config) {
|
|
|
197
212
|
setDone(featureId, doneContent) {
|
|
198
213
|
return writeAction("PATCH", featureId, "done", { done_content: doneContent });
|
|
199
214
|
},
|
|
215
|
+
addDependency(featureId, dependsOnId) {
|
|
216
|
+
return postAction(featureId, "dependencies", { depends_on_id: dependsOnId });
|
|
217
|
+
},
|
|
200
218
|
// SPEC-100 (B2): best-effort — o juiz de drift é ADVISORY; qualquer falha → {checked:false}
|
|
201
219
|
// (nunca lança, pra não estragar o fluxo de gravar a SPEC no `set_spec` do MCP).
|
|
202
220
|
async checkSpecAlignment(featureId) {
|
|
@@ -360,6 +378,26 @@ async function writeEnvVars(pairs, opts = {}) {
|
|
|
360
378
|
return { file, keys: Object.keys(pairs) };
|
|
361
379
|
}
|
|
362
380
|
|
|
381
|
+
// ../core/src/format.ts
|
|
382
|
+
var MINUTE_MS = 6e4;
|
|
383
|
+
var HOUR_MS = 36e5;
|
|
384
|
+
var DAY_MS = 864e5;
|
|
385
|
+
function formatDuration(ms) {
|
|
386
|
+
if (ms < MINUTE_MS) return "<1min";
|
|
387
|
+
if (ms < HOUR_MS) return `${Math.floor(ms / MINUTE_MS)}min`;
|
|
388
|
+
if (ms < DAY_MS) {
|
|
389
|
+
const h2 = Math.floor(ms / HOUR_MS);
|
|
390
|
+
const m = Math.floor(ms % HOUR_MS / MINUTE_MS);
|
|
391
|
+
return m > 0 ? `${h2}h ${m}min` : `${h2}h`;
|
|
392
|
+
}
|
|
393
|
+
const d = Math.floor(ms / DAY_MS);
|
|
394
|
+
const h = Math.floor(ms % DAY_MS / HOUR_MS);
|
|
395
|
+
return h > 0 ? `${d}d ${h}h` : `${d}d`;
|
|
396
|
+
}
|
|
397
|
+
function formatMinutes(minutes) {
|
|
398
|
+
return formatDuration(minutes * MINUTE_MS);
|
|
399
|
+
}
|
|
400
|
+
|
|
363
401
|
// ../core/src/credentials.ts
|
|
364
402
|
import {
|
|
365
403
|
readFile as fsReadFile2,
|
|
@@ -457,6 +495,9 @@ async function clientFromConfig(cfg) {
|
|
|
457
495
|
}
|
|
458
496
|
|
|
459
497
|
// src/format.ts
|
|
498
|
+
function activeTime(d) {
|
|
499
|
+
return d.elapsed_minutes != null ? formatMinutes(d.elapsed_minutes) : `${d.days_active}d`;
|
|
500
|
+
}
|
|
460
501
|
var STAGE_LABEL = {
|
|
461
502
|
backlog: "Backlog",
|
|
462
503
|
spec: "Spec",
|
|
@@ -476,7 +517,7 @@ function formatStatus(d) {
|
|
|
476
517
|
const lines = [
|
|
477
518
|
`${f.name} [${f.priority}]`,
|
|
478
519
|
`Stage: ${stageLabel(d.stage)}`,
|
|
479
|
-
`
|
|
520
|
+
`Tempo ativo: ${activeTime(d)}`,
|
|
480
521
|
`Branch: ${f.branch_slug ?? "\u2014"}`
|
|
481
522
|
];
|
|
482
523
|
if (f.github_pr_url) lines.push(`PR: ${f.github_pr_url}`);
|
|
@@ -516,10 +557,30 @@ function formatStartList(list) {
|
|
|
516
557
|
const header = "Features para iniciar ou retomar \u2014 escolha: h1ve start <n\xBA>";
|
|
517
558
|
const rows = list.map((f, i) => {
|
|
518
559
|
const tag = f.branch_slug !== null ? " (retomar)" : "";
|
|
519
|
-
|
|
560
|
+
const dep = f.depends_on && f.depends_on.length > 0 ? ` \u26A0 depende de ${f.depends_on.map((d) => d.name).join(", ")}` : "";
|
|
561
|
+
return ` ${String(i + 1).padStart(2)}. ${f.name} [${stageLabel(f.stage)}]${tag}${dep}`;
|
|
520
562
|
});
|
|
521
563
|
return [header, ...rows].join("\n");
|
|
522
564
|
}
|
|
565
|
+
var PRIORITY_RANK = { HIGH: 0, MEDIUM: 1, LOW: 2 };
|
|
566
|
+
function rankStartable(list) {
|
|
567
|
+
return [...list].sort((a, b) => {
|
|
568
|
+
const resume = (a.branch_slug !== null ? 0 : 1) - (b.branch_slug !== null ? 0 : 1);
|
|
569
|
+
if (resume !== 0) return resume;
|
|
570
|
+
const blocked = (a.blocked_by_dep ? 1 : 0) - (b.blocked_by_dep ? 1 : 0);
|
|
571
|
+
if (blocked !== 0) return blocked;
|
|
572
|
+
const prio = PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority];
|
|
573
|
+
if (prio !== 0) return prio;
|
|
574
|
+
return (a.appetite ?? 0) - (b.appetite ?? 0);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
function formatDepWarning(f) {
|
|
578
|
+
if (!f.depends_on || f.depends_on.length === 0) return "";
|
|
579
|
+
const names = f.depends_on.map((d) => d.name).join(", ");
|
|
580
|
+
return `\u26A0 "${f.name}" depende de ${names} (ainda n\xE3o conclu\xEDdo) \u2014 considere fazer isso primeiro.
|
|
581
|
+
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
523
584
|
var PRE_DEV_STAGES = /* @__PURE__ */ new Set(["backlog", "spec", "blocked"]);
|
|
524
585
|
function startFlowLines(r) {
|
|
525
586
|
const stage = r.landed_stage ?? r.feature.stage;
|
|
@@ -554,6 +615,13 @@ function formatStart(r, opts) {
|
|
|
554
615
|
...startFlowLines(r)
|
|
555
616
|
].join("\n");
|
|
556
617
|
}
|
|
618
|
+
function formatBehindMainWarning(behind) {
|
|
619
|
+
const plural = behind === 1 ? "commit" : "commits";
|
|
620
|
+
return [
|
|
621
|
+
`\u26A0 Sua base est\xE1 ${behind} ${plural} atr\xE1s de origin/main.`,
|
|
622
|
+
" Sincronize antes de codar: git fetch origin && git rebase origin/main"
|
|
623
|
+
].join("\n");
|
|
624
|
+
}
|
|
557
625
|
function formatHealth(snaps) {
|
|
558
626
|
if (!snaps.length) return "Nenhum snapshot de sa\xFAde t\xE9cnica registrado.";
|
|
559
627
|
const header = "Semana CI% Deploys Bugs D\xEDvida";
|
|
@@ -705,6 +773,17 @@ async function health(io) {
|
|
|
705
773
|
}
|
|
706
774
|
|
|
707
775
|
// src/commands/start.ts
|
|
776
|
+
async function behindMainWarning(io) {
|
|
777
|
+
if (!io.checkBehindMain) return "";
|
|
778
|
+
try {
|
|
779
|
+
const behind = await io.checkBehindMain();
|
|
780
|
+
return behind && behind > 0 ? `${formatBehindMainWarning(behind)}
|
|
781
|
+
|
|
782
|
+
` : "";
|
|
783
|
+
} catch {
|
|
784
|
+
return "";
|
|
785
|
+
}
|
|
786
|
+
}
|
|
708
787
|
function branchSlugOf(branch) {
|
|
709
788
|
return branch.match(/^feat\/[^/]+\/(.+)$/)?.[1] ?? null;
|
|
710
789
|
}
|
|
@@ -725,7 +804,7 @@ function pick(list, arg) {
|
|
|
725
804
|
}
|
|
726
805
|
async function start(io, args) {
|
|
727
806
|
const [features, localBranches] = await Promise.all([io.client.listFeatures(), io.listBranches()]);
|
|
728
|
-
const list = startable(features, localBranches);
|
|
807
|
+
const list = rankStartable(startable(features, localBranches));
|
|
729
808
|
if (list.length === 0) {
|
|
730
809
|
throw new CliError("Nenhuma feature para iniciar ou retomar (sem branch local e fora de main).");
|
|
731
810
|
}
|
|
@@ -733,6 +812,7 @@ async function start(io, args) {
|
|
|
733
812
|
if (!selected) {
|
|
734
813
|
return { human: formatStartList(list), data: { features: list } };
|
|
735
814
|
}
|
|
815
|
+
const warning = await behindMainWarning(io);
|
|
736
816
|
const resuming = selected.branch_slug !== null;
|
|
737
817
|
const result = await io.client.startFeature(selected.id, args.slug);
|
|
738
818
|
try {
|
|
@@ -742,7 +822,8 @@ async function start(io, args) {
|
|
|
742
822
|
if (code === "BRANCH_EXISTS") await io.switchBranch(result.branch_name);
|
|
743
823
|
else throw err;
|
|
744
824
|
}
|
|
745
|
-
|
|
825
|
+
const depWarn = formatDepWarning(selected);
|
|
826
|
+
return { human: `${warning}${depWarn}${formatStart(result, { resumed: resuming })}`, data: result };
|
|
746
827
|
}
|
|
747
828
|
|
|
748
829
|
// src/commands/connect.ts
|
|
@@ -1437,8 +1518,11 @@ async function mcp(io, args) {
|
|
|
1437
1518
|
};
|
|
1438
1519
|
}
|
|
1439
1520
|
|
|
1521
|
+
// src/version.ts
|
|
1522
|
+
import { readFileSync } from "fs";
|
|
1523
|
+
var VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
1524
|
+
|
|
1440
1525
|
// src/index.ts
|
|
1441
|
-
var VERSION = "0.12.2";
|
|
1442
1526
|
var HELP = `h1ve \u2014 CLI do H1VE
|
|
1443
1527
|
(o comando legado \`nf\` continua funcionando como alias)
|
|
1444
1528
|
|
|
@@ -1562,6 +1646,7 @@ ${HELP}`);
|
|
|
1562
1646
|
createBranch: (name) => createBranch(name, defaultGitRunner),
|
|
1563
1647
|
switchBranch: (name) => switchBranch(name, defaultGitRunner),
|
|
1564
1648
|
listBranches: () => listLocalBranches(defaultGitRunner),
|
|
1649
|
+
checkBehindMain: () => commitsBehindOriginMain(defaultGitRunner),
|
|
1565
1650
|
readFile: (path) => readFile(path, "utf8"),
|
|
1566
1651
|
readStdin,
|
|
1567
1652
|
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.
|
|
3
|
+
"version": "0.14.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",
|