@hiveai/cli 0.12.9 → 0.13.1
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 +214 -31
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2789,6 +2789,111 @@ Use \`select_related\` (FK / one-to-one, SQL JOIN) and \`prefetch_related\` (M2M
|
|
|
2789
2789
|
\`\`\`py
|
|
2790
2790
|
for order in Order.objects.select_related("customer").all():
|
|
2791
2791
|
order.customer.name # no extra query
|
|
2792
|
+
\`\`\``
|
|
2793
|
+
}
|
|
2794
|
+
],
|
|
2795
|
+
flask: [
|
|
2796
|
+
{
|
|
2797
|
+
slug: "flask-no-debug-in-prod",
|
|
2798
|
+
type: "gotcha",
|
|
2799
|
+
tags: ["flask", "python", "security", "deployment"],
|
|
2800
|
+
body: `\`app.run(debug=True)\` enables the Werkzeug debugger \u2014 remote code execution if exposed.
|
|
2801
|
+
|
|
2802
|
+
Never ship debug mode. Run behind a real WSGI server (gunicorn/uwsgi) in production and
|
|
2803
|
+
drive debug from the environment for local dev only.`,
|
|
2804
|
+
sensor: {
|
|
2805
|
+
pattern: "app\\.run\\([^)]*debug\\s*=\\s*True",
|
|
2806
|
+
message: "Flask debug=True exposes the Werkzeug console (RCE) \u2014 never run it in production."
|
|
2807
|
+
}
|
|
2808
|
+
},
|
|
2809
|
+
{
|
|
2810
|
+
slug: "flask-secret-key-from-env",
|
|
2811
|
+
type: "convention",
|
|
2812
|
+
tags: ["flask", "python", "security"],
|
|
2813
|
+
body: `Load \`SECRET_KEY\` from the environment \u2014 never commit a literal.
|
|
2814
|
+
|
|
2815
|
+
\`\`\`py
|
|
2816
|
+
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]
|
|
2817
|
+
\`\`\`
|
|
2818
|
+
A committed key lets anyone forge sessions and CSRF tokens.`
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
slug: "flask-no-sql-string-interpolation",
|
|
2822
|
+
type: "gotcha",
|
|
2823
|
+
tags: ["flask", "python", "security", "sql-injection"],
|
|
2824
|
+
body: `Never build SQL with f-strings/%-formatting \u2014 use parameterized queries.
|
|
2825
|
+
|
|
2826
|
+
\`\`\`py
|
|
2827
|
+
# \u274C SQL injection
|
|
2828
|
+
db.execute(f"SELECT * FROM users WHERE id = {uid}")
|
|
2829
|
+
# \u2705
|
|
2830
|
+
db.execute("SELECT * FROM users WHERE id = %s", (uid,))
|
|
2831
|
+
\`\`\``
|
|
2832
|
+
}
|
|
2833
|
+
],
|
|
2834
|
+
vue: [
|
|
2835
|
+
{
|
|
2836
|
+
slug: "vue-v-html-xss",
|
|
2837
|
+
type: "gotcha",
|
|
2838
|
+
tags: ["vue", "security", "xss"],
|
|
2839
|
+
body: `\`v-html\` renders raw HTML and bypasses Vue's escaping \u2014 an XSS sink for user content.
|
|
2840
|
+
|
|
2841
|
+
Only use it on trusted/sanitized content. Prefer text interpolation ({{ }}) or sanitize
|
|
2842
|
+
with DOMPurify before binding.`,
|
|
2843
|
+
sensor: {
|
|
2844
|
+
pattern: "v-html",
|
|
2845
|
+
message: "v-html renders unescaped HTML (XSS risk) \u2014 sanitize the value or use text interpolation."
|
|
2846
|
+
}
|
|
2847
|
+
},
|
|
2848
|
+
{
|
|
2849
|
+
slug: "vue-key-in-v-for",
|
|
2850
|
+
type: "convention",
|
|
2851
|
+
tags: ["vue", "performance"],
|
|
2852
|
+
body: `Always bind a stable \`:key\` on \`v-for\` \u2014 and never the loop index.
|
|
2853
|
+
|
|
2854
|
+
Index keys corrupt component state on reorder/insert, exactly like React. Use a stable id.`
|
|
2855
|
+
},
|
|
2856
|
+
{
|
|
2857
|
+
slug: "vue-props-are-readonly",
|
|
2858
|
+
type: "gotcha",
|
|
2859
|
+
tags: ["vue", "reactivity"],
|
|
2860
|
+
body: `Never mutate a prop inside a child component \u2014 props are one-way (parent \u2192 child).
|
|
2861
|
+
|
|
2862
|
+
Mutating a prop breaks the data flow and warns in dev. Emit an event (\`update:modelValue\`)
|
|
2863
|
+
or copy the prop into local state, depending on intent.`
|
|
2864
|
+
}
|
|
2865
|
+
],
|
|
2866
|
+
spring: [
|
|
2867
|
+
{
|
|
2868
|
+
slug: "spring-constructor-injection",
|
|
2869
|
+
type: "convention",
|
|
2870
|
+
tags: ["spring", "java", "di", "testing"],
|
|
2871
|
+
body: `Prefer constructor injection over \`@Autowired\` field injection.
|
|
2872
|
+
|
|
2873
|
+
Constructor injection makes dependencies explicit, allows \`final\` fields, and lets you
|
|
2874
|
+
instantiate the class in tests without a Spring context. Field injection hides dependencies
|
|
2875
|
+
and forces reflection-based test setup.`
|
|
2876
|
+
},
|
|
2877
|
+
{
|
|
2878
|
+
slug: "spring-no-cors-wildcard",
|
|
2879
|
+
type: "gotcha",
|
|
2880
|
+
tags: ["spring", "java", "security", "cors"],
|
|
2881
|
+
body: `\`@CrossOrigin(origins = "*")\` (or wildcard CORS config) allows any site to call your API.
|
|
2882
|
+
|
|
2883
|
+
Combined with credentials it leaks authenticated data cross-origin. Whitelist explicit origins.`,
|
|
2884
|
+
sensor: {
|
|
2885
|
+
pattern: "@CrossOrigin\\([^)]*\\*",
|
|
2886
|
+
message: 'Wildcard CORS (@CrossOrigin origins="*") lets any site call your API \u2014 whitelist explicit origins.'
|
|
2887
|
+
}
|
|
2888
|
+
},
|
|
2889
|
+
{
|
|
2890
|
+
slug: "spring-no-field-secrets",
|
|
2891
|
+
type: "convention",
|
|
2892
|
+
tags: ["spring", "java", "security", "config"],
|
|
2893
|
+
body: `Keep secrets in externalized config (env / vault / application.yml placeholders), not in source.
|
|
2894
|
+
|
|
2895
|
+
\`\`\`java
|
|
2896
|
+
@Value("\${app.api-key}") private String apiKey; // resolved from env, not hardcoded
|
|
2792
2897
|
\`\`\``
|
|
2793
2898
|
}
|
|
2794
2899
|
],
|
|
@@ -2907,7 +3012,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
2907
3012
|
}
|
|
2908
3013
|
|
|
2909
3014
|
// src/commands/init.ts
|
|
2910
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3015
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.13.1"}`;
|
|
2911
3016
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
2912
3017
|
|
|
2913
3018
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3071,6 +3176,27 @@ jobs:
|
|
|
3071
3176
|
# post-if-empty: 'true' # uncomment to always post (even when no memories found)
|
|
3072
3177
|
# max-memories: '5' # limit memories per file in the comment
|
|
3073
3178
|
|
|
3179
|
+
# On pull request: fail if the harness quality score regressed vs the committed baseline.
|
|
3180
|
+
# Measures whether the right memories still surface and the right sensors still fire.
|
|
3181
|
+
# No-op (passes) when no .ai/eval/baseline.json exists \u2014 safe to keep enabled before you
|
|
3182
|
+
# ever create one. To turn the gate on: run \`haive eval --baseline\` locally and commit
|
|
3183
|
+
# .ai/eval/baseline.json. Needs nothing external \u2014 no secrets, no services.
|
|
3184
|
+
pr-eval-gate:
|
|
3185
|
+
if: github.event_name == 'pull_request'
|
|
3186
|
+
runs-on: ubuntu-latest
|
|
3187
|
+
steps:
|
|
3188
|
+
- uses: actions/checkout@v4
|
|
3189
|
+
|
|
3190
|
+
- uses: actions/setup-node@v4
|
|
3191
|
+
with:
|
|
3192
|
+
node-version: '20'
|
|
3193
|
+
|
|
3194
|
+
- name: install haive
|
|
3195
|
+
run: npm install -g @hiveai/cli
|
|
3196
|
+
|
|
3197
|
+
- name: harness quality regression gate
|
|
3198
|
+
run: haive eval --regression-gate
|
|
3199
|
+
|
|
3074
3200
|
# On push to main: push shared memories to the hub (if hubPath is configured)
|
|
3075
3201
|
# Uncomment and configure hubPath in .ai/haive.config.json to enable.
|
|
3076
3202
|
# hub-push:
|
|
@@ -7786,7 +7912,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7786
7912
|
};
|
|
7787
7913
|
}
|
|
7788
7914
|
var SERVER_NAME = "haive";
|
|
7789
|
-
var SERVER_VERSION = "0.
|
|
7915
|
+
var SERVER_VERSION = "0.13.1";
|
|
7790
7916
|
function jsonResult(data) {
|
|
7791
7917
|
return {
|
|
7792
7918
|
content: [
|
|
@@ -12546,7 +12672,7 @@ import {
|
|
|
12546
12672
|
function registerEval(program2) {
|
|
12547
12673
|
program2.command("eval").description(
|
|
12548
12674
|
"Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a chiffr\xE9 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
|
|
12549
|
-
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12675
|
+
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12550
12676
|
const root = findProjectRoot42(opts.dir);
|
|
12551
12677
|
const paths = resolveHaivePaths38(root);
|
|
12552
12678
|
if (!existsSync65(paths.memoriesDir)) {
|
|
@@ -12594,14 +12720,19 @@ function registerEval(program2) {
|
|
|
12594
12720
|
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path43.relative(root, baselineFile)}`);
|
|
12595
12721
|
}
|
|
12596
12722
|
let delta = null;
|
|
12597
|
-
if (opts.compare) {
|
|
12723
|
+
if (opts.compare || opts.regressionGate) {
|
|
12598
12724
|
if (!existsSync65(baselineFile)) {
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12725
|
+
if (opts.regressionGate) {
|
|
12726
|
+
if (!opts.json) ui.info(`No baseline at ${path43.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
|
|
12727
|
+
} else {
|
|
12728
|
+
ui.error(`No baseline at ${path43.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
|
|
12729
|
+
process.exitCode = 1;
|
|
12730
|
+
return;
|
|
12731
|
+
}
|
|
12732
|
+
} else {
|
|
12733
|
+
const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
|
|
12734
|
+
delta = compareEvalReports(snapshot.report, report);
|
|
12602
12735
|
}
|
|
12603
|
-
const snapshot = JSON.parse(await readFile20(baselineFile, "utf8"));
|
|
12604
|
-
delta = compareEvalReports(snapshot.report, report);
|
|
12605
12736
|
}
|
|
12606
12737
|
if (opts.json) {
|
|
12607
12738
|
console.log(JSON.stringify({ root, k, spec_source: resolvedSpec.source, report, ...delta ? { delta } : {} }, null, 2));
|
|
@@ -12633,7 +12764,7 @@ function applyExitGates(opts, report, delta) {
|
|
|
12633
12764
|
process.exitCode = 1;
|
|
12634
12765
|
}
|
|
12635
12766
|
}
|
|
12636
|
-
if (opts.failOnRegression && delta?.regressed) {
|
|
12767
|
+
if ((opts.failOnRegression || opts.regressionGate) && delta?.regressed) {
|
|
12637
12768
|
ui.error(`eval score regressed ${delta.score.baseline} \u2192 ${delta.score.current} (\u0394 ${delta.score.delta}) vs baseline`);
|
|
12638
12769
|
process.exitCode = 1;
|
|
12639
12770
|
}
|
|
@@ -13350,7 +13481,7 @@ function registerDoctor(program2) {
|
|
|
13350
13481
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
13351
13482
|
});
|
|
13352
13483
|
}
|
|
13353
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
13484
|
+
findings.push(...await collectInstallFindings(root, "0.13.1"));
|
|
13354
13485
|
findings.push(...await collectToolchainFindings(root));
|
|
13355
13486
|
try {
|
|
13356
13487
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -13358,7 +13489,7 @@ function registerDoctor(program2) {
|
|
|
13358
13489
|
timeout: 3e3,
|
|
13359
13490
|
stdio: ["ignore", "pipe", "ignore"]
|
|
13360
13491
|
}).trim();
|
|
13361
|
-
const cliVersion = "0.
|
|
13492
|
+
const cliVersion = "0.13.1";
|
|
13362
13493
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
13363
13494
|
findings.push({
|
|
13364
13495
|
severity: "warn",
|
|
@@ -14951,7 +15082,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14951
15082
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14952
15083
|
});
|
|
14953
15084
|
}
|
|
14954
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
15085
|
+
findings.push(...await inspectIntegrationVersions(root, "0.13.1"));
|
|
14955
15086
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14956
15087
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14957
15088
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16077,11 +16208,11 @@ import {
|
|
|
16077
16208
|
var SEVERITIES = ["info", "minor", "major", "critical", "blocker"];
|
|
16078
16209
|
function registerIngest(program2) {
|
|
16079
16210
|
program2.command("ingest").description(
|
|
16080
|
-
"Ingest scanner findings (SonarQube / SARIF) as proposed, anchored memories with sensors.\n\n Closes the review\u2194memory loop: a real defect a scanner found becomes a `gotcha`/`convention`\n memory anchored to the file, pre-filled with a conservative `warn` sensor, so the next agent\n is steered away from it. Drafts are status=proposed; a human validates/promotes them.\n\n Example:\n haive ingest --from sarif eslint.sarif --dry-run\n haive ingest --from sonar sonar-issues.json --scope team --min-severity major\n"
|
|
16081
|
-
).argument("
|
|
16211
|
+
"Ingest scanner findings (SonarQube / SARIF) as proposed, anchored memories with sensors.\n\n Closes the review\u2194memory loop: a real defect a scanner found becomes a `gotcha`/`convention`\n memory anchored to the file, pre-filled with a conservative `warn` sensor, so the next agent\n is steered away from it. Drafts are status=proposed; a human validates/promotes them.\n\n `sonar-api` fetches issues live over plain HTTPS from any SonarQube/SonarCloud instance \u2014\n no MCP or special setup required, just a URL + token you provide (or SONAR_HOST_URL /\n SONAR_TOKEN env). If you don't use it, file-based ingest works exactly the same.\n\n Example:\n haive ingest --from sarif eslint.sarif --dry-run\n haive ingest --from sonar sonar-issues.json --scope team --min-severity major\n haive ingest --from sonar-api --sonar-component my_project --min-severity major\n"
|
|
16212
|
+
).argument("[file]", "path to the findings report JSON (required for --from sarif|sonar)").requiredOption("--from <format>", "report format: sarif | sonar | sonar-api").option("--dry-run", "show what would be created without writing", false).option("--scope <scope>", "memory scope: personal | team | module", "team").option("--module <name>", "module name (required when scope=module)").option("--type <type>", "memory type: gotcha | convention", "gotcha").option("--min-severity <severity>", "ignore findings below this severity (info|minor|major|critical|blocker)").option("--limit <n>", "cap the number of memories created").option("--author <author>", "author email or handle").option("--json", "emit JSON", false).option("--sonar-url <url>", "SonarQube base URL for --from sonar-api (or env SONAR_HOST_URL)").option("--sonar-token <token>", "SonarQube token for --from sonar-api (or env SONAR_TOKEN)").option("--sonar-component <key>", "SonarQube project/component key for --from sonar-api").option("--sonar-branch <branch>", "optional SonarQube branch for --from sonar-api").option("-d, --dir <dir>", "project root").action(async (file, opts) => {
|
|
16082
16213
|
const format = opts.from;
|
|
16083
|
-
if (format !== "sarif" && format !== "sonar") {
|
|
16084
|
-
ui.error("--from must be sarif or sonar");
|
|
16214
|
+
if (format !== "sarif" && format !== "sonar" && format !== "sonar-api") {
|
|
16215
|
+
ui.error("--from must be sarif, sonar, or sonar-api");
|
|
16085
16216
|
process.exitCode = 1;
|
|
16086
16217
|
return;
|
|
16087
16218
|
}
|
|
@@ -16102,23 +16233,39 @@ function registerIngest(program2) {
|
|
|
16102
16233
|
process.exitCode = 1;
|
|
16103
16234
|
return;
|
|
16104
16235
|
}
|
|
16105
|
-
const
|
|
16106
|
-
if (!existsSync77(reportPath)) {
|
|
16107
|
-
ui.error(`Report file not found: ${reportPath}`);
|
|
16108
|
-
process.exitCode = 1;
|
|
16109
|
-
return;
|
|
16110
|
-
}
|
|
16236
|
+
const parseFormat = format === "sarif" ? "sarif" : "sonar";
|
|
16111
16237
|
let raw;
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16238
|
+
if (format === "sonar-api") {
|
|
16239
|
+
const fetched = await fetchSonarIssues(opts);
|
|
16240
|
+
if (!fetched.ok) {
|
|
16241
|
+
ui.error(fetched.error);
|
|
16242
|
+
process.exitCode = 1;
|
|
16243
|
+
return;
|
|
16244
|
+
}
|
|
16245
|
+
raw = fetched.json;
|
|
16246
|
+
} else {
|
|
16247
|
+
if (!file) {
|
|
16248
|
+
ui.error(`--from ${format} needs a report file argument, e.g. \`haive ingest --from ${format} report.json\`.`);
|
|
16249
|
+
process.exitCode = 1;
|
|
16250
|
+
return;
|
|
16251
|
+
}
|
|
16252
|
+
const reportPath = path54.resolve(root, file);
|
|
16253
|
+
if (!existsSync77(reportPath)) {
|
|
16254
|
+
ui.error(`Report file not found: ${reportPath}`);
|
|
16255
|
+
process.exitCode = 1;
|
|
16256
|
+
return;
|
|
16257
|
+
}
|
|
16258
|
+
try {
|
|
16259
|
+
raw = await readFile25(reportPath, "utf8");
|
|
16260
|
+
} catch (err) {
|
|
16261
|
+
ui.error(`Could not read ${reportPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
16262
|
+
process.exitCode = 1;
|
|
16263
|
+
return;
|
|
16264
|
+
}
|
|
16118
16265
|
}
|
|
16119
16266
|
let drafts;
|
|
16120
16267
|
try {
|
|
16121
|
-
const findings = parseFindings2(
|
|
16268
|
+
const findings = parseFindings2(parseFormat, raw);
|
|
16122
16269
|
drafts = draftsFromFindings2(findings, {
|
|
16123
16270
|
type: opts.type ?? "gotcha",
|
|
16124
16271
|
scope: opts.scope ?? "team",
|
|
@@ -16201,6 +16348,42 @@ async function writeDraft2(paths, draft) {
|
|
|
16201
16348
|
await writeFile37(file, serializeMemory28({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
16202
16349
|
return file;
|
|
16203
16350
|
}
|
|
16351
|
+
async function fetchSonarIssues(opts) {
|
|
16352
|
+
const baseUrl = (opts.sonarUrl ?? process.env.SONAR_HOST_URL ?? "").trim().replace(/\/+$/, "");
|
|
16353
|
+
const token = (opts.sonarToken ?? process.env.SONAR_TOKEN ?? "").trim();
|
|
16354
|
+
const component = (opts.sonarComponent ?? "").trim();
|
|
16355
|
+
if (!baseUrl) {
|
|
16356
|
+
return { ok: false, error: "--from sonar-api needs --sonar-url (or env SONAR_HOST_URL)." };
|
|
16357
|
+
}
|
|
16358
|
+
if (!token) {
|
|
16359
|
+
return { ok: false, error: "--from sonar-api needs --sonar-token (or env SONAR_TOKEN)." };
|
|
16360
|
+
}
|
|
16361
|
+
if (!component) {
|
|
16362
|
+
return { ok: false, error: "--from sonar-api needs --sonar-component <projectKey>." };
|
|
16363
|
+
}
|
|
16364
|
+
if (typeof fetch !== "function") {
|
|
16365
|
+
return { ok: false, error: "global fetch is unavailable \u2014 Node 18+ is required for --from sonar-api." };
|
|
16366
|
+
}
|
|
16367
|
+
const params = new URLSearchParams({ componentKeys: component, resolved: "false", ps: "500" });
|
|
16368
|
+
if (opts.sonarBranch) params.set("branch", opts.sonarBranch);
|
|
16369
|
+
const url = `${baseUrl}/api/issues/search?${params.toString()}`;
|
|
16370
|
+
try {
|
|
16371
|
+
const res = await fetch(url, {
|
|
16372
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
|
|
16373
|
+
});
|
|
16374
|
+
if (!res.ok) {
|
|
16375
|
+
const hint = res.status === 401 || res.status === 403 ? " (check the token and its permissions)" : "";
|
|
16376
|
+
return { ok: false, error: `SonarQube API returned ${res.status} ${res.statusText}${hint}.` };
|
|
16377
|
+
}
|
|
16378
|
+
const json = await res.text();
|
|
16379
|
+
return { ok: true, json };
|
|
16380
|
+
} catch (err) {
|
|
16381
|
+
return {
|
|
16382
|
+
ok: false,
|
|
16383
|
+
error: `Could not reach SonarQube at ${baseUrl}: ${err instanceof Error ? err.message : String(err)}. File-based ingest (--from sonar) still works.`
|
|
16384
|
+
};
|
|
16385
|
+
}
|
|
16386
|
+
}
|
|
16204
16387
|
|
|
16205
16388
|
// src/commands/dashboard.ts
|
|
16206
16389
|
import { existsSync as existsSync78 } from "fs";
|
|
@@ -16305,7 +16488,7 @@ function warnNum(n) {
|
|
|
16305
16488
|
|
|
16306
16489
|
// src/index.ts
|
|
16307
16490
|
var program = new Command58();
|
|
16308
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
16491
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.1").option("--advanced", "show maintenance and experimental commands in help");
|
|
16309
16492
|
registerInit(program);
|
|
16310
16493
|
registerWelcome(program);
|
|
16311
16494
|
registerResolveProject(program);
|