@hiveai/cli 0.12.1 → 0.12.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.
- package/README.md +29 -0
- package/dist/index.js +242 -18
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -187,6 +187,35 @@ Autogenerated sensors are conservative: they start as `warn` and `autogen: true`
|
|
|
187
187
|
high-confidence sensors to `severity: block`, which makes a deterministic pre-commit blocker when the
|
|
188
188
|
sensor matches added diff lines.
|
|
189
189
|
|
|
190
|
+
### `haive eval`
|
|
191
|
+
|
|
192
|
+
Run the repeatable quality gate for hAIve itself or for a project using hAIve:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
haive eval
|
|
196
|
+
haive eval --semantic-only
|
|
197
|
+
haive eval --spec .ai/eval/spec.json --fail-under 80
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Without `--spec`, hAIve synthesizes retrieval cases from anchored memories. If `.ai/eval/spec.json`
|
|
201
|
+
exists, it is loaded automatically and merged with those synthesized retrieval cases. Use that file
|
|
202
|
+
for labeled sensor cases and hard retrieval probes so CI measures both “did the right memory surface?”
|
|
203
|
+
and “did the executable guardrail fire?”.
|
|
204
|
+
|
|
205
|
+
### `haive doctor`
|
|
206
|
+
|
|
207
|
+
`doctor` is the first stop when hAIve feels inconsistent locally:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
haive doctor
|
|
211
|
+
haive doctor --json
|
|
212
|
+
haive doctor --fix
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
It reports missing `pnpm`, stale workspace `dist` artifacts after a pull, global CLI/MCP version skew,
|
|
216
|
+
outdated code-search indexes, memory-lint findings, and harness coverage. The output is intentionally
|
|
217
|
+
actionable: every setup finding should carry the exact command to run next.
|
|
218
|
+
|
|
190
219
|
### `haive benchmark`
|
|
191
220
|
|
|
192
221
|
Turn hAIve-vs-plain agent trials into a repeatable demo/report.
|
package/dist/index.js
CHANGED
|
@@ -2743,7 +2743,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
2743
2743
|
}
|
|
2744
2744
|
|
|
2745
2745
|
// src/commands/init.ts
|
|
2746
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.12.
|
|
2746
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.12.4"}`;
|
|
2747
2747
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
2748
2748
|
|
|
2749
2749
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -2778,7 +2778,7 @@ This repo uses **hAIve** for shared context. The map:
|
|
|
2778
2778
|
1. **Before editing** for a goal, call \`get_briefing\` (task + files/symbols) to load ranked context \u2014 or \`mem_relevant_to\` if project context is already loaded this session.
|
|
2779
2779
|
2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
|
|
2780
2780
|
3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
|
|
2781
|
-
4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, then rerun it.
|
|
2781
|
+
4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, wait for GitHub Actions to pass when applicable, then rerun it.
|
|
2782
2782
|
|
|
2783
2783
|
If the haive MCP server is not available, tell the developer rather than silently skipping it.
|
|
2784
2784
|
|
|
@@ -2805,7 +2805,7 @@ This repository uses **hAIve**. Running \`haive init\` means the team expects ag
|
|
|
2805
2805
|
|
|
2806
2806
|
- On failure: **\`mem_tried\`** immediately.
|
|
2807
2807
|
- Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
|
|
2808
|
-
- Before final response: **\`haive enforce finish\`** must pass; it checks commit/push
|
|
2808
|
+
- Before final response: **\`haive enforce finish\`** must pass; it checks commit/push, release version/tag protocol, and GitHub Actions success for pushed HEAD when the repo has a GitHub remote.
|
|
2809
2809
|
|
|
2810
2810
|
## If haive MCP is missing
|
|
2811
2811
|
|
|
@@ -7440,7 +7440,7 @@ This creates/updates a single rolling recap that **get_briefing automatically su
|
|
|
7440
7440
|
|
|
7441
7441
|
Calling \`mem_session_end\` also **clears the pending-distill marker** (if any), confirming that this session's learnings have been properly captured rather than left as an auto-recap skeleton.
|
|
7442
7442
|
|
|
7443
|
-
### 7. Verify the git/release exit protocol \u2014 always
|
|
7443
|
+
### 7. Verify the git/release/pipeline exit protocol \u2014 always
|
|
7444
7444
|
Run **\`haive enforce finish\`** before your final response.
|
|
7445
7445
|
|
|
7446
7446
|
This executable gate checks the multi-agent git-sync decision:
|
|
@@ -7448,11 +7448,12 @@ This executable gate checks the multi-agent git-sync decision:
|
|
|
7448
7448
|
- shippable package changes have a lockstep version bump
|
|
7449
7449
|
- the release tag \`vX.Y.Z\` exists when a version was bumped
|
|
7450
7450
|
- commits and tags have been pushed
|
|
7451
|
+
- the pushed HEAD's GitHub Actions workflow runs have completed successfully when the repo has a GitHub remote
|
|
7451
7452
|
- agents never run \`npm publish\` (publication remains human-owned)
|
|
7452
7453
|
|
|
7453
|
-
If it blocks, fix the reported Git/version/tag/push issue before telling the developer the task is done.
|
|
7454
|
+
If it blocks, fix the reported Git/version/tag/push/pipeline issue before telling the developer the task is done.
|
|
7454
7455
|
|
|
7455
|
-
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed."
|
|
7456
|
+
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved. hAIve finish gate passed; GitHub Actions passed when applicable."
|
|
7456
7457
|
`;
|
|
7457
7458
|
return {
|
|
7458
7459
|
description: "Post-task reflection: capture what you learned before closing the session",
|
|
@@ -7531,7 +7532,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7531
7532
|
};
|
|
7532
7533
|
}
|
|
7533
7534
|
var SERVER_NAME = "haive";
|
|
7534
|
-
var SERVER_VERSION = "0.12.
|
|
7535
|
+
var SERVER_VERSION = "0.12.4";
|
|
7535
7536
|
function jsonResult(data) {
|
|
7536
7537
|
return {
|
|
7537
7538
|
content: [
|
|
@@ -12259,7 +12260,8 @@ function registerEval(program2) {
|
|
|
12259
12260
|
}
|
|
12260
12261
|
const k = Math.max(1, parseInt(opts.top ?? "8", 10) || 8);
|
|
12261
12262
|
const ctx = { paths };
|
|
12262
|
-
const
|
|
12263
|
+
const resolvedSpec = await resolveSpec(opts, root, paths.memoriesDir);
|
|
12264
|
+
const spec = resolvedSpec.spec;
|
|
12263
12265
|
if ((spec.retrieval?.length ?? 0) === 0 && (spec.sensors?.length ?? 0) === 0) {
|
|
12264
12266
|
ui.warn("No eval cases (no anchored memories and no --spec). Nothing to score.");
|
|
12265
12267
|
return;
|
|
@@ -12284,9 +12286,9 @@ function registerEval(program2) {
|
|
|
12284
12286
|
}
|
|
12285
12287
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12286
12288
|
if (opts.json) {
|
|
12287
|
-
console.log(JSON.stringify({ root, k, report }, null, 2));
|
|
12289
|
+
console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report }, null, 2));
|
|
12288
12290
|
} else {
|
|
12289
|
-
const md = renderMarkdown2(root, k, report);
|
|
12291
|
+
const md = renderMarkdown2(root, k, resolvedSpec.source, report);
|
|
12290
12292
|
if (opts.out) {
|
|
12291
12293
|
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
12292
12294
|
await writeFile29(outFile, md, "utf8");
|
|
@@ -12307,14 +12309,31 @@ function registerEval(program2) {
|
|
|
12307
12309
|
}
|
|
12308
12310
|
});
|
|
12309
12311
|
}
|
|
12310
|
-
async function resolveSpec(opts, memoriesDir) {
|
|
12312
|
+
async function resolveSpec(opts, root, memoriesDir) {
|
|
12311
12313
|
if (opts.spec) {
|
|
12312
12314
|
const file = path43.resolve(opts.spec);
|
|
12313
12315
|
const raw = await readFile20(file, "utf8");
|
|
12314
|
-
return JSON.parse(raw);
|
|
12316
|
+
return { spec: JSON.parse(raw), source: file };
|
|
12317
|
+
}
|
|
12318
|
+
const defaultSpec = path43.join(root, ".ai", "eval", "spec.json");
|
|
12319
|
+
if (existsSync64(defaultSpec)) {
|
|
12320
|
+
const raw = await readFile20(defaultSpec, "utf8");
|
|
12321
|
+
const explicit = JSON.parse(raw);
|
|
12322
|
+
const memories2 = await loadMemoriesFromDir26(memoriesDir);
|
|
12323
|
+
const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
12324
|
+
return {
|
|
12325
|
+
spec: {
|
|
12326
|
+
retrieval: [...synthesized, ...explicit.retrieval ?? []],
|
|
12327
|
+
sensors: explicit.sensors ?? []
|
|
12328
|
+
},
|
|
12329
|
+
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
12330
|
+
};
|
|
12315
12331
|
}
|
|
12316
12332
|
const memories = await loadMemoriesFromDir26(memoriesDir);
|
|
12317
|
-
return {
|
|
12333
|
+
return {
|
|
12334
|
+
spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
|
|
12335
|
+
source: "synthesized anchored retrieval"
|
|
12336
|
+
};
|
|
12318
12337
|
}
|
|
12319
12338
|
async function runRetrieval(c, k, ctx) {
|
|
12320
12339
|
const out = await getBriefing(
|
|
@@ -12346,11 +12365,12 @@ async function runSensorCase(c, ctx) {
|
|
|
12346
12365
|
function pct(n) {
|
|
12347
12366
|
return `${Math.round(n * 100)}%`;
|
|
12348
12367
|
}
|
|
12349
|
-
function renderMarkdown2(root, k, report) {
|
|
12368
|
+
function renderMarkdown2(root, k, source, report) {
|
|
12350
12369
|
const lines = [
|
|
12351
12370
|
"# hAIve eval report",
|
|
12352
12371
|
"",
|
|
12353
12372
|
`Project: \`${root}\` \xB7 top-k: ${k}`,
|
|
12373
|
+
`Spec: ${source}`,
|
|
12354
12374
|
"",
|
|
12355
12375
|
`## Overall score: ${report.score}/100`,
|
|
12356
12376
|
""
|
|
@@ -12982,14 +13002,15 @@ function registerDoctor(program2) {
|
|
|
12982
13002
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12983
13003
|
});
|
|
12984
13004
|
}
|
|
12985
|
-
findings.push(...await collectInstallFindings(root, "0.12.
|
|
13005
|
+
findings.push(...await collectInstallFindings(root, "0.12.4"));
|
|
13006
|
+
findings.push(...await collectToolchainFindings(root));
|
|
12986
13007
|
try {
|
|
12987
13008
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12988
13009
|
encoding: "utf8",
|
|
12989
13010
|
timeout: 3e3,
|
|
12990
13011
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12991
13012
|
}).trim();
|
|
12992
|
-
const cliVersion = "0.12.
|
|
13013
|
+
const cliVersion = "0.12.4";
|
|
12993
13014
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12994
13015
|
findings.push({
|
|
12995
13016
|
severity: "warn",
|
|
@@ -13266,6 +13287,7 @@ function isSearchTool(name) {
|
|
|
13266
13287
|
async function collectInstallFindings(root, expectedVersion) {
|
|
13267
13288
|
const findings = [];
|
|
13268
13289
|
findings.push(...await collectWorkspaceVersionFindings(root, expectedVersion));
|
|
13290
|
+
findings.push(...await collectDistFreshnessFindings(root, expectedVersion));
|
|
13269
13291
|
const haiveBins = listHaiveBins();
|
|
13270
13292
|
if (haiveBins.length === 0) {
|
|
13271
13293
|
findings.push({
|
|
@@ -13329,6 +13351,71 @@ which -a haive`
|
|
|
13329
13351
|
}
|
|
13330
13352
|
return findings;
|
|
13331
13353
|
}
|
|
13354
|
+
async function collectToolchainFindings(root) {
|
|
13355
|
+
const findings = [];
|
|
13356
|
+
const pkg = await readJson(
|
|
13357
|
+
path46.join(root, "package.json")
|
|
13358
|
+
);
|
|
13359
|
+
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
13360
|
+
if (!wantsPnpm) return findings;
|
|
13361
|
+
const expected = pkg?.packageManager?.replace(/^pnpm@/, "") ?? "9.14.2";
|
|
13362
|
+
if (!commandExists2("pnpm", ["--version"])) {
|
|
13363
|
+
const corepackAvailable = commandExists2("corepack", ["--version"]);
|
|
13364
|
+
findings.push({
|
|
13365
|
+
severity: "warn",
|
|
13366
|
+
code: "pnpm-not-on-path",
|
|
13367
|
+
message: `This workspace uses pnpm${expected ? ` ${expected}` : ""}, but no pnpm binary is available on PATH. Local build/test commands may fail even though CI works.`,
|
|
13368
|
+
fix: corepackAvailable ? `corepack prepare pnpm@${expected} --activate` : `npx pnpm@${expected} install --frozen-lockfile`,
|
|
13369
|
+
section: "Agent coverage"
|
|
13370
|
+
});
|
|
13371
|
+
}
|
|
13372
|
+
return findings;
|
|
13373
|
+
}
|
|
13374
|
+
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
13375
|
+
const findings = [];
|
|
13376
|
+
const isHaiveWorkspace = (await readJson(path46.join(root, "package.json")))?.name === "haive-monorepo";
|
|
13377
|
+
if (!isHaiveWorkspace) return findings;
|
|
13378
|
+
const cliDist = path46.join(root, "packages/cli/dist/index.js");
|
|
13379
|
+
if (!existsSync67(cliDist)) {
|
|
13380
|
+
findings.push({
|
|
13381
|
+
severity: "warn",
|
|
13382
|
+
code: "workspace-dist-missing",
|
|
13383
|
+
message: "packages/cli/dist/index.js is missing; local hAIve smoke commands cannot reflect source changes.",
|
|
13384
|
+
fix: "pnpm -r build",
|
|
13385
|
+
section: "Agent coverage"
|
|
13386
|
+
});
|
|
13387
|
+
return findings;
|
|
13388
|
+
}
|
|
13389
|
+
const distVersion = versionForNodeEntrypoint(cliDist);
|
|
13390
|
+
if (distVersion && distVersion !== expectedVersion) {
|
|
13391
|
+
findings.push({
|
|
13392
|
+
severity: "warn",
|
|
13393
|
+
code: "workspace-dist-version-mismatch",
|
|
13394
|
+
message: `packages/cli/dist/index.js reports ${distVersion}, but this source build expects ${expectedVersion}. Run a fresh workspace build after pull before trusting local doctor/enforce output.`,
|
|
13395
|
+
fix: "pnpm -r build\npnpm check:artifacts",
|
|
13396
|
+
section: "Agent coverage"
|
|
13397
|
+
});
|
|
13398
|
+
}
|
|
13399
|
+
const sourceFiles = [
|
|
13400
|
+
"packages/core/src/index.ts",
|
|
13401
|
+
"packages/mcp/src/server.ts",
|
|
13402
|
+
"packages/cli/src/index.ts"
|
|
13403
|
+
].map((rel) => path46.join(root, rel)).filter(existsSync67);
|
|
13404
|
+
if (sourceFiles.length > 0) {
|
|
13405
|
+
const distMtime = statSync2(cliDist).mtimeMs;
|
|
13406
|
+
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
13407
|
+
if (newestSource > distMtime + 1e3) {
|
|
13408
|
+
findings.push({
|
|
13409
|
+
severity: "info",
|
|
13410
|
+
code: "workspace-dist-older-than-source",
|
|
13411
|
+
message: "Built CLI artifacts are older than key source files; rebuild before running release smoke checks.",
|
|
13412
|
+
fix: "pnpm -r build\npnpm check:artifacts",
|
|
13413
|
+
section: "Agent coverage"
|
|
13414
|
+
});
|
|
13415
|
+
}
|
|
13416
|
+
}
|
|
13417
|
+
return findings;
|
|
13418
|
+
}
|
|
13332
13419
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
13333
13420
|
const findings = [];
|
|
13334
13421
|
const rootPkg = await readJson(path46.join(root, "package.json"));
|
|
@@ -13432,6 +13519,30 @@ function versionForBinary(bin) {
|
|
|
13432
13519
|
return null;
|
|
13433
13520
|
}
|
|
13434
13521
|
}
|
|
13522
|
+
function versionForNodeEntrypoint(file) {
|
|
13523
|
+
try {
|
|
13524
|
+
const out = execFileSync(process.execPath, [file, "--version"], {
|
|
13525
|
+
encoding: "utf8",
|
|
13526
|
+
timeout: 3e3,
|
|
13527
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
13528
|
+
}).trim();
|
|
13529
|
+
return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
|
|
13530
|
+
} catch {
|
|
13531
|
+
return null;
|
|
13532
|
+
}
|
|
13533
|
+
}
|
|
13534
|
+
function commandExists2(command, args) {
|
|
13535
|
+
try {
|
|
13536
|
+
execFileSync(command, args, {
|
|
13537
|
+
encoding: "utf8",
|
|
13538
|
+
timeout: 3e3,
|
|
13539
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
13540
|
+
});
|
|
13541
|
+
return true;
|
|
13542
|
+
} catch {
|
|
13543
|
+
return false;
|
|
13544
|
+
}
|
|
13545
|
+
}
|
|
13435
13546
|
function extractAbsoluteHaiveBins(text) {
|
|
13436
13547
|
const out = /* @__PURE__ */ new Set();
|
|
13437
13548
|
const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
|
|
@@ -14261,6 +14372,7 @@ async function buildFinishReport(dir) {
|
|
|
14261
14372
|
code: "release-version-not-required",
|
|
14262
14373
|
message: "No shippable package code changed since upstream; no version/tag required."
|
|
14263
14374
|
});
|
|
14375
|
+
findings.push(...await verifyGithubActionsForHead(root, status));
|
|
14264
14376
|
return finishReport(root, initialized, mode, findings, config);
|
|
14265
14377
|
}
|
|
14266
14378
|
findings.push({
|
|
@@ -14347,6 +14459,7 @@ async function buildFinishReport(dir) {
|
|
|
14347
14459
|
impact: 10
|
|
14348
14460
|
});
|
|
14349
14461
|
}
|
|
14462
|
+
findings.push(...await verifyGithubActionsForHead(root, status));
|
|
14350
14463
|
return finishReport(root, initialized, mode, findings, config);
|
|
14351
14464
|
}
|
|
14352
14465
|
function finishReport(root, initialized, mode, findings, config) {
|
|
@@ -14490,7 +14603,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14490
14603
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14491
14604
|
});
|
|
14492
14605
|
}
|
|
14493
|
-
findings.push(...await inspectIntegrationVersions(root, "0.12.
|
|
14606
|
+
findings.push(...await inspectIntegrationVersions(root, "0.12.4"));
|
|
14494
14607
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14495
14608
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14496
14609
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14628,6 +14741,15 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
14628
14741
|
}];
|
|
14629
14742
|
}
|
|
14630
14743
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
14744
|
+
if (stage === "ci" && !marker) {
|
|
14745
|
+
return [{
|
|
14746
|
+
severity: "ok",
|
|
14747
|
+
code: "decision-coverage-ci-pass",
|
|
14748
|
+
message: `CI surfaced ${relevant.length} relevant anchored decision/polic${relevant.length === 1 ? "y" : "ies"} for ${changedFiles.length} changed file(s). Runtime briefing markers are local-only and are not expected on GitHub Actions.`,
|
|
14749
|
+
memory_ids: relevant.slice(0, 20).map((memory2) => memory2.frontmatter.id),
|
|
14750
|
+
affected_files: changedFiles.slice(0, 10)
|
|
14751
|
+
}];
|
|
14752
|
+
}
|
|
14631
14753
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
14632
14754
|
const missing = relevant.filter((memory2) => !consulted.has(memory2.frontmatter.id));
|
|
14633
14755
|
if (missing.length === 0) {
|
|
@@ -15033,6 +15155,108 @@ async function remoteTagExists(root, tag) {
|
|
|
15033
15155
|
return null;
|
|
15034
15156
|
}
|
|
15035
15157
|
}
|
|
15158
|
+
async function verifyGithubActionsForHead(root, status) {
|
|
15159
|
+
if (!status.upstream) return [];
|
|
15160
|
+
if (status.ahead > 0) {
|
|
15161
|
+
return [{
|
|
15162
|
+
severity: "info",
|
|
15163
|
+
code: "github-actions-waiting-for-push",
|
|
15164
|
+
message: "GitHub Actions verification waits until HEAD is pushed."
|
|
15165
|
+
}];
|
|
15166
|
+
}
|
|
15167
|
+
const remote = await githubRemoteForCurrentBranch(root);
|
|
15168
|
+
if (!remote) {
|
|
15169
|
+
return [{
|
|
15170
|
+
severity: "info",
|
|
15171
|
+
code: "github-actions-not-applicable",
|
|
15172
|
+
message: "No GitHub remote was detected; GitHub Actions pipeline verification was skipped."
|
|
15173
|
+
}];
|
|
15174
|
+
}
|
|
15175
|
+
const sha = (await runCommand4("git", ["rev-parse", "HEAD"], root).catch(() => "")).trim();
|
|
15176
|
+
if (!sha) {
|
|
15177
|
+
return [{
|
|
15178
|
+
severity: "error",
|
|
15179
|
+
code: "github-actions-head-unreadable",
|
|
15180
|
+
message: "Could not read HEAD SHA for GitHub Actions verification.",
|
|
15181
|
+
fix: "Run `git rev-parse HEAD`, then verify GitHub Actions manually before finishing.",
|
|
15182
|
+
impact: 30
|
|
15183
|
+
}];
|
|
15184
|
+
}
|
|
15185
|
+
let runs;
|
|
15186
|
+
try {
|
|
15187
|
+
const raw = await runCommand4("gh", [
|
|
15188
|
+
"run",
|
|
15189
|
+
"list",
|
|
15190
|
+
"--commit",
|
|
15191
|
+
sha,
|
|
15192
|
+
"--limit",
|
|
15193
|
+
"50",
|
|
15194
|
+
"--json",
|
|
15195
|
+
"conclusion,databaseId,name,status,workflowName"
|
|
15196
|
+
], root);
|
|
15197
|
+
runs = JSON.parse(raw);
|
|
15198
|
+
} catch {
|
|
15199
|
+
return [{
|
|
15200
|
+
severity: "error",
|
|
15201
|
+
code: "github-actions-unverified",
|
|
15202
|
+
message: "Could not verify GitHub Actions runs for HEAD.",
|
|
15203
|
+
fix: "Install/authenticate GitHub CLI, then run `gh run list --commit $(git rev-parse HEAD)` and ensure every workflow is successful before finishing.",
|
|
15204
|
+
reason: `Detected GitHub remote ${remote}, but hAIve could not query workflow runs.`,
|
|
15205
|
+
impact: 50
|
|
15206
|
+
}];
|
|
15207
|
+
}
|
|
15208
|
+
if (runs.length === 0) {
|
|
15209
|
+
return [{
|
|
15210
|
+
severity: "error",
|
|
15211
|
+
code: "github-actions-runs-missing",
|
|
15212
|
+
message: "No GitHub Actions runs were found for HEAD.",
|
|
15213
|
+
fix: "Wait for GitHub to create the workflow runs, or verify that the push was not skipped by a `[skip ci]` head commit; rerun `haive enforce finish` after the runs appear.",
|
|
15214
|
+
impact: 50
|
|
15215
|
+
}];
|
|
15216
|
+
}
|
|
15217
|
+
const pending = runs.filter((run) => run.status !== "completed");
|
|
15218
|
+
if (pending.length > 0) {
|
|
15219
|
+
return [{
|
|
15220
|
+
severity: "error",
|
|
15221
|
+
code: "github-actions-pending",
|
|
15222
|
+
message: `${pending.length}/${runs.length} GitHub Actions workflow run(s) for HEAD are still pending: ${formatGithubRunNames(pending)}.`,
|
|
15223
|
+
fix: "Wait for the runs to finish (`gh run watch <run-id> --exit-status`), then rerun `haive enforce finish`.",
|
|
15224
|
+
impact: 50
|
|
15225
|
+
}];
|
|
15226
|
+
}
|
|
15227
|
+
const failed = runs.filter((run) => run.conclusion !== "success");
|
|
15228
|
+
if (failed.length > 0) {
|
|
15229
|
+
return [{
|
|
15230
|
+
severity: "error",
|
|
15231
|
+
code: "github-actions-failed",
|
|
15232
|
+
message: `${failed.length}/${runs.length} GitHub Actions workflow run(s) for HEAD did not pass: ${formatGithubRunNames(failed)}.`,
|
|
15233
|
+
fix: "Inspect the failed run logs with `gh run view <run-id> --log`, fix the issue, push the fix, then rerun `haive enforce finish`.",
|
|
15234
|
+
impact: 80
|
|
15235
|
+
}];
|
|
15236
|
+
}
|
|
15237
|
+
return [{
|
|
15238
|
+
severity: "ok",
|
|
15239
|
+
code: "github-actions-pass",
|
|
15240
|
+
message: `All ${runs.length} GitHub Actions workflow run(s) for HEAD completed successfully.`
|
|
15241
|
+
}];
|
|
15242
|
+
}
|
|
15243
|
+
async function githubRemoteForCurrentBranch(root) {
|
|
15244
|
+
const branch = (await runCommand4("git", ["branch", "--show-current"], root).catch(() => "")).trim();
|
|
15245
|
+
const branchRemote = branch ? (await runCommand4("git", ["config", "--get", `branch.${branch}.remote`], root).catch(() => "")).trim() : "";
|
|
15246
|
+
const remoteName = branchRemote || "origin";
|
|
15247
|
+
const remoteUrl = (await runCommand4("git", ["config", "--get", `remote.${remoteName}.url`], root).catch(() => "")).trim();
|
|
15248
|
+
if (!isGithubRemoteUrl(remoteUrl)) return null;
|
|
15249
|
+
return remoteUrl;
|
|
15250
|
+
}
|
|
15251
|
+
function isGithubRemoteUrl(url) {
|
|
15252
|
+
return /(^git@github\.com:|github\.com[/:])/.test(url);
|
|
15253
|
+
}
|
|
15254
|
+
function formatGithubRunNames(runs) {
|
|
15255
|
+
return runs.slice(0, 6).map((run) => {
|
|
15256
|
+
const label = run.workflowName ?? run.name ?? "workflow";
|
|
15257
|
+
return run.databaseId ? `${label}#${run.databaseId}` : label;
|
|
15258
|
+
}).join(", ");
|
|
15259
|
+
}
|
|
15036
15260
|
function buildScore(findings, threshold = 80) {
|
|
15037
15261
|
const checks = {
|
|
15038
15262
|
total: findings.length,
|
|
@@ -15489,7 +15713,7 @@ function shellQuote(value) {
|
|
|
15489
15713
|
|
|
15490
15714
|
// src/index.ts
|
|
15491
15715
|
var program = new Command56();
|
|
15492
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.
|
|
15716
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.4").option("--advanced", "show maintenance and experimental commands in help");
|
|
15493
15717
|
registerInit(program);
|
|
15494
15718
|
registerWelcome(program);
|
|
15495
15719
|
registerResolveProject(program);
|