@hiveai/cli 0.24.0 → 0.26.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 +41 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -2253,6 +2253,7 @@ import path11 from "path";
|
|
|
2253
2253
|
import {
|
|
2254
2254
|
buildFrontmatter,
|
|
2255
2255
|
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
2256
|
+
meetsSeedQualityFloor,
|
|
2256
2257
|
memoryFilePath,
|
|
2257
2258
|
serializeMemory as serializeMemory2,
|
|
2258
2259
|
STACK_PACK_TAG
|
|
@@ -2293,7 +2294,12 @@ app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: t
|
|
|
2293
2294
|
body: `Controllers must never import Prisma/TypeORM directly \u2014 that belongs in Services.
|
|
2294
2295
|
|
|
2295
2296
|
Controller \u2192 Service \u2192 Repository (or direct ORM) is the required layering.
|
|
2296
|
-
Direct ORM usage in controllers makes testing impossible and couples transport to persistence
|
|
2297
|
+
Direct ORM usage in controllers makes testing impossible and couples transport to persistence.`,
|
|
2298
|
+
sensor: {
|
|
2299
|
+
pattern: "@prisma/client|PrismaClient|getRepository\\(|createQueryBuilder\\(",
|
|
2300
|
+
paths: ["**/*.controller.ts"],
|
|
2301
|
+
message: "ORM/Prisma used directly in a controller \u2014 move persistence into a Service (Controller \u2192 Service \u2192 Repository)."
|
|
2302
|
+
}
|
|
2297
2303
|
},
|
|
2298
2304
|
{
|
|
2299
2305
|
slug: "nestjs-exception-filter-for-prisma",
|
|
@@ -2478,7 +2484,11 @@ Routes without schema accept any body and bypass Fastify's fast-json-stringify s
|
|
|
2478
2484
|
|
|
2479
2485
|
Calling $disconnect() after each request wastes the warm connection pool.
|
|
2480
2486
|
Create one PrismaClient per process (module-level singleton), not per request.
|
|
2481
|
-
Disconnecting is only needed when the process is shutting down
|
|
2487
|
+
Disconnecting is only needed when the process is shutting down.`,
|
|
2488
|
+
sensor: {
|
|
2489
|
+
pattern: "\\$disconnect\\(\\)",
|
|
2490
|
+
message: "prisma.$disconnect() per request drains the warm connection pool in serverless \u2014 use a module-level PrismaClient singleton and only disconnect on shutdown."
|
|
2491
|
+
}
|
|
2482
2492
|
},
|
|
2483
2493
|
{
|
|
2484
2494
|
slug: "prisma-migrations-never-modify",
|
|
@@ -2534,7 +2544,11 @@ const store = useStore();
|
|
|
2534
2544
|
const count = useStore((s) => s.count);
|
|
2535
2545
|
\`\`\`
|
|
2536
2546
|
|
|
2537
|
-
Subscribing to the whole store is the single most common Zustand performance mistake
|
|
2547
|
+
Subscribing to the whole store is the single most common Zustand performance mistake.`,
|
|
2548
|
+
sensor: {
|
|
2549
|
+
pattern: "use[A-Z]\\w*Store\\(\\s*\\)",
|
|
2550
|
+
message: "A Zustand store hook called with no selector subscribes to the WHOLE store (re-renders on any change) \u2014 pass a slice selector: useStore(s => s.field)."
|
|
2551
|
+
}
|
|
2538
2552
|
},
|
|
2539
2553
|
{
|
|
2540
2554
|
slug: "zustand-devtools-wrap-dev-only",
|
|
@@ -2755,7 +2769,8 @@ const users = await User.find({});
|
|
|
2755
2769
|
const users = await User.find({}).lean();
|
|
2756
2770
|
\`\`\`
|
|
2757
2771
|
|
|
2758
|
-
|
|
2772
|
+
\`.lean()\` skips hydration into a Mongoose \`Document\`, so getters, virtuals, \`toObject()\` and
|
|
2773
|
+
instance methods are gone. Never use \`.lean()\` when you then call \`.save()\` or instance methods.`
|
|
2759
2774
|
},
|
|
2760
2775
|
{
|
|
2761
2776
|
slug: "mongoose-index-frequently-queried-fields",
|
|
@@ -2974,7 +2989,11 @@ A committed key lets anyone forge sessions and CSRF tokens.`
|
|
|
2974
2989
|
db.execute(f"SELECT * FROM users WHERE id = {uid}")
|
|
2975
2990
|
# \u2705
|
|
2976
2991
|
db.execute("SELECT * FROM users WHERE id = %s", (uid,))
|
|
2977
|
-
|
|
2992
|
+
\`\`\``,
|
|
2993
|
+
sensor: {
|
|
2994
|
+
pattern: `execute\\(\\s*f["']`,
|
|
2995
|
+
message: 'SQL built with an f-string inside execute() \u2014 SQL injection risk; use a parameterized query: execute("\u2026 %s \u2026", (params,)).'
|
|
2996
|
+
}
|
|
2978
2997
|
}
|
|
2979
2998
|
],
|
|
2980
2999
|
vue: [
|
|
@@ -3342,6 +3361,7 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3342
3361
|
let memCount = 0;
|
|
3343
3362
|
let sensorCount = 0;
|
|
3344
3363
|
for (const mem of memories) {
|
|
3364
|
+
if (!meetsSeedQualityFloor(mem.body, Boolean(mem.sensor))) continue;
|
|
3345
3365
|
const sensor = mem.sensor ? {
|
|
3346
3366
|
kind: "regex",
|
|
3347
3367
|
pattern: mem.sensor.pattern,
|
|
@@ -3390,7 +3410,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3390
3410
|
|
|
3391
3411
|
// src/commands/init.ts
|
|
3392
3412
|
var execFileAsync = promisify2(execFile2);
|
|
3393
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3413
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.26.0"}`;
|
|
3394
3414
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3395
3415
|
|
|
3396
3416
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -5829,6 +5849,7 @@ var IngestFindingsInputSchema = {
|
|
|
5829
5849
|
scope: z16.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
5830
5850
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
5831
5851
|
min_severity: z16.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
5852
|
+
include_stylistic: z16.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
|
|
5832
5853
|
limit: z16.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
5833
5854
|
author: z16.string().optional().describe("Author handle or email"),
|
|
5834
5855
|
dry_run: z16.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
@@ -5854,6 +5875,7 @@ async function ingestFindings(input, ctx) {
|
|
|
5854
5875
|
module: input.module,
|
|
5855
5876
|
author: input.author,
|
|
5856
5877
|
...input.min_severity ? { minSeverity: input.min_severity } : {},
|
|
5878
|
+
...input.include_stylistic ? { includeStylistic: true } : {},
|
|
5857
5879
|
...input.limit ? { limit: input.limit } : {}
|
|
5858
5880
|
});
|
|
5859
5881
|
const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir13(ctx.paths.memoriesDir) : [];
|
|
@@ -8676,7 +8698,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8676
8698
|
};
|
|
8677
8699
|
}
|
|
8678
8700
|
var SERVER_NAME = "haive";
|
|
8679
|
-
var SERVER_VERSION = "0.
|
|
8701
|
+
var SERVER_VERSION = "0.26.0";
|
|
8680
8702
|
function jsonResult(data) {
|
|
8681
8703
|
return {
|
|
8682
8704
|
content: [
|
|
@@ -14453,7 +14475,7 @@ function registerDoctor(program2) {
|
|
|
14453
14475
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14454
14476
|
});
|
|
14455
14477
|
}
|
|
14456
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14478
|
+
findings.push(...await collectInstallFindings(root, "0.26.0"));
|
|
14457
14479
|
findings.push(...await collectToolchainFindings(root));
|
|
14458
14480
|
try {
|
|
14459
14481
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14461,7 +14483,7 @@ function registerDoctor(program2) {
|
|
|
14461
14483
|
timeout: 3e3,
|
|
14462
14484
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14463
14485
|
}).trim();
|
|
14464
|
-
const cliVersion = "0.
|
|
14486
|
+
const cliVersion = "0.26.0";
|
|
14465
14487
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14466
14488
|
findings.push({
|
|
14467
14489
|
severity: "warn",
|
|
@@ -16165,7 +16187,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16165
16187
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16166
16188
|
});
|
|
16167
16189
|
}
|
|
16168
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16190
|
+
findings.push(...await inspectIntegrationVersions(root, "0.26.0"));
|
|
16169
16191
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16170
16192
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16171
16193
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -17534,7 +17556,7 @@ var SEVERITIES = ["info", "minor", "major", "critical", "blocker"];
|
|
|
17534
17556
|
function registerIngest(program2) {
|
|
17535
17557
|
program2.command("ingest").description(
|
|
17536
17558
|
"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 eslint eslint-report.json --min-severity major\n haive ingest --from npm-audit audit.json --scope team\n haive ingest --from sarif report.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\n Generate the input reports:\n eslint -f json -o eslint-report.json . # --from eslint\n npm audit --json > audit.json # --from npm-audit\n"
|
|
17537
|
-
).argument("[file]", "path to the findings report JSON (required for --from sarif|sonar|eslint|npm-audit)").requiredOption("--from <format>", "report format: sarif | sonar | sonar-api | eslint | npm-audit").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) => {
|
|
17559
|
+
).argument("[file]", "path to the findings report JSON (required for --from sarif|sonar|eslint|npm-audit)").requiredOption("--from <format>", "report format: sarif | sonar | sonar-api | eslint | npm-audit").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("--include-stylistic", "also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise", false).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) => {
|
|
17538
17560
|
const format = opts.from;
|
|
17539
17561
|
const VALID_FORMATS = ["sarif", "sonar", "sonar-api", "eslint", "npm-audit"];
|
|
17540
17562
|
if (!format || !VALID_FORMATS.includes(format)) {
|
|
@@ -17590,14 +17612,17 @@ function registerIngest(program2) {
|
|
|
17590
17612
|
}
|
|
17591
17613
|
}
|
|
17592
17614
|
let drafts;
|
|
17615
|
+
let findingsCount = 0;
|
|
17593
17616
|
try {
|
|
17594
17617
|
const findings = parseFindings2(parseFormat, raw, { cwd: root });
|
|
17618
|
+
findingsCount = findings.length;
|
|
17595
17619
|
drafts = draftsFromFindings2(findings, {
|
|
17596
17620
|
type: opts.type ?? "gotcha",
|
|
17597
17621
|
scope: opts.scope ?? "team",
|
|
17598
17622
|
module: opts.module,
|
|
17599
17623
|
author: opts.author,
|
|
17600
17624
|
...opts.minSeverity ? { minSeverity: opts.minSeverity } : {},
|
|
17625
|
+
...opts.includeStylistic ? { includeStylistic: true } : {},
|
|
17601
17626
|
...opts.limit ? { limit: Math.max(0, Number.parseInt(opts.limit, 10) || 0) } : {}
|
|
17602
17627
|
});
|
|
17603
17628
|
} catch (err) {
|
|
@@ -17620,7 +17645,9 @@ function registerIngest(program2) {
|
|
|
17620
17645
|
JSON.stringify(
|
|
17621
17646
|
{
|
|
17622
17647
|
format,
|
|
17648
|
+
findings: findingsCount,
|
|
17623
17649
|
parsed: drafts.length,
|
|
17650
|
+
filtered_low_value: Math.max(0, findingsCount - drafts.length),
|
|
17624
17651
|
new: fresh.length,
|
|
17625
17652
|
skipped_existing: skipped,
|
|
17626
17653
|
dry_run: Boolean(opts.dryRun),
|
|
@@ -17639,9 +17666,10 @@ function registerIngest(program2) {
|
|
|
17639
17666
|
);
|
|
17640
17667
|
return;
|
|
17641
17668
|
}
|
|
17669
|
+
const filteredLowValue = Math.max(0, findingsCount - drafts.length);
|
|
17642
17670
|
console.log(
|
|
17643
17671
|
ui.bold(
|
|
17644
|
-
`hAIve ingest (${format}) \u2014 ${
|
|
17672
|
+
`hAIve ingest (${format}) \u2014 ${findingsCount} finding(s), ${fresh.length} new` + (filteredLowValue > 0 ? `, ${filteredLowValue} low-value/stylistic filtered` : "") + (skipped > 0 ? `, ${skipped} already ingested` : "")
|
|
17645
17673
|
)
|
|
17646
17674
|
);
|
|
17647
17675
|
if (fresh.length === 0) {
|
|
@@ -18324,7 +18352,7 @@ function registerBridges(program2) {
|
|
|
18324
18352
|
|
|
18325
18353
|
// src/index.ts
|
|
18326
18354
|
var program = new Command64();
|
|
18327
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18355
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.26.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18328
18356
|
registerInit(program);
|
|
18329
18357
|
registerWelcome(program);
|
|
18330
18358
|
registerResolveProject(program);
|