@hiveai/cli 0.11.0 → 0.12.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 +301 -261
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command56 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -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.
|
|
2746
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.12.0"}`;
|
|
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,
|
|
@@ -3822,7 +3822,10 @@ import { z as z24 } from "zod";
|
|
|
3822
3822
|
import { existsSync as existsSync24 } from "fs";
|
|
3823
3823
|
import {
|
|
3824
3824
|
addedLinesFromDiff,
|
|
3825
|
+
buildDocFrequency,
|
|
3826
|
+
CODE_STOPWORDS,
|
|
3825
3827
|
deriveConfidence as deriveConfidence6,
|
|
3828
|
+
diffHasDistinctiveOverlap,
|
|
3826
3829
|
getUsage as getUsage8,
|
|
3827
3830
|
isRetiredMemory as isRetiredMemory2,
|
|
3828
3831
|
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
@@ -6145,53 +6148,6 @@ var AntiPatternsCheckInputSchema = {
|
|
|
6145
6148
|
"Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
|
|
6146
6149
|
)
|
|
6147
6150
|
};
|
|
6148
|
-
var CODE_STOPWORDS = /* @__PURE__ */ new Set([
|
|
6149
|
-
"import",
|
|
6150
|
-
"export",
|
|
6151
|
-
"function",
|
|
6152
|
-
"return",
|
|
6153
|
-
"const",
|
|
6154
|
-
"let",
|
|
6155
|
-
"var",
|
|
6156
|
-
"class",
|
|
6157
|
-
"public",
|
|
6158
|
-
"private",
|
|
6159
|
-
"protected",
|
|
6160
|
-
"static",
|
|
6161
|
-
"this",
|
|
6162
|
-
"true",
|
|
6163
|
-
"false",
|
|
6164
|
-
"null",
|
|
6165
|
-
"undefined",
|
|
6166
|
-
"void",
|
|
6167
|
-
"async",
|
|
6168
|
-
"await",
|
|
6169
|
-
"from",
|
|
6170
|
-
"type",
|
|
6171
|
-
"interface",
|
|
6172
|
-
"extends",
|
|
6173
|
-
"implements",
|
|
6174
|
-
"number",
|
|
6175
|
-
"string",
|
|
6176
|
-
"boolean",
|
|
6177
|
-
"value",
|
|
6178
|
-
"default",
|
|
6179
|
-
"case",
|
|
6180
|
-
"break",
|
|
6181
|
-
"continue",
|
|
6182
|
-
"throw",
|
|
6183
|
-
"catch",
|
|
6184
|
-
"finally",
|
|
6185
|
-
"else",
|
|
6186
|
-
"while",
|
|
6187
|
-
"for",
|
|
6188
|
-
"new",
|
|
6189
|
-
"super",
|
|
6190
|
-
"yield",
|
|
6191
|
-
"module",
|
|
6192
|
-
"require",
|
|
6193
|
-
"console"
|
|
6194
|
-
]);
|
|
6195
6151
|
function tokenizeDiffForLiteral(diff) {
|
|
6196
6152
|
const lines = diff.split("\n");
|
|
6197
6153
|
const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
|
|
@@ -6224,6 +6180,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6224
6180
|
return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
|
|
6225
6181
|
}
|
|
6226
6182
|
const usage = await loadUsageIndex10(ctx.paths);
|
|
6183
|
+
const docFreq = buildDocFrequency(negative.map(({ memory: memory2 }) => memory2.body));
|
|
6227
6184
|
const seen = /* @__PURE__ */ new Map();
|
|
6228
6185
|
const upsert = (fm, body, reason, score) => {
|
|
6229
6186
|
const existing = seen.get(fm.id);
|
|
@@ -6257,10 +6214,16 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6257
6214
|
}
|
|
6258
6215
|
if (input.diff) {
|
|
6259
6216
|
const tokens = tokenizeDiffForLiteral(input.diff);
|
|
6217
|
+
const added = addedLinesFromDiff(input.diff);
|
|
6218
|
+
const addedText = added.trim().length > 0 ? added : input.diff;
|
|
6260
6219
|
if (tokens.length > 0) {
|
|
6261
6220
|
for (const { memory: memory2 } of negative) {
|
|
6262
6221
|
if (literalMatchesAnyToken3(memory2, tokens)) {
|
|
6263
6222
|
upsert(memory2.frontmatter, memory2.body, "literal");
|
|
6223
|
+
if (diffHasDistinctiveOverlap(addedText, memory2.body, docFreq)) {
|
|
6224
|
+
const w = seen.get(memory2.frontmatter.id);
|
|
6225
|
+
if (w) w.distinctive_literal = true;
|
|
6226
|
+
}
|
|
6264
6227
|
}
|
|
6265
6228
|
}
|
|
6266
6229
|
}
|
|
@@ -6858,7 +6821,12 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
6858
6821
|
const hasSemantic = warning.reasons.includes("semantic");
|
|
6859
6822
|
const semanticScore = warning.semantic_score ?? 0;
|
|
6860
6823
|
const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
|
|
6861
|
-
if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") &&
|
|
6824
|
+
if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && // A literal overlap only corroborates a BLOCK when it is on a token *distinctive*
|
|
6825
|
+
// to this gotcha (rare in the corpus). Sharing a common domain word ("memory",
|
|
6826
|
+
// "scope", "version") — or a version-bump diff — no longer hard-blocks; it falls
|
|
6827
|
+
// through to `review` below. This kills the incidental-token false positives that
|
|
6828
|
+
// made agents work for nothing. A moderate semantic match still corroborates.
|
|
6829
|
+
(warning.distinctive_literal === true || hasSemantic && semanticScore >= 0.45)) {
|
|
6862
6830
|
if (warning.has_sensor && !warning.reasons.includes("sensor")) {
|
|
6863
6831
|
return {
|
|
6864
6832
|
...warning,
|
|
@@ -7550,7 +7518,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7550
7518
|
};
|
|
7551
7519
|
}
|
|
7552
7520
|
var SERVER_NAME = "haive";
|
|
7553
|
-
var SERVER_VERSION = "0.
|
|
7521
|
+
var SERVER_VERSION = "0.12.0";
|
|
7554
7522
|
function jsonResult(data) {
|
|
7555
7523
|
return {
|
|
7556
7524
|
content: [
|
|
@@ -9126,7 +9094,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9126
9094
|
haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
|
|
9127
9095
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
9128
9096
|
`
|
|
9129
|
-
).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9097
|
+
).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9130
9098
|
const root = findProjectRoot13(opts.dir);
|
|
9131
9099
|
const paths = resolveHaivePaths10(root);
|
|
9132
9100
|
if (!existsSync33(paths.haiveDir)) {
|
|
@@ -9137,6 +9105,11 @@ function registerMemoryAdd(memory2) {
|
|
|
9137
9105
|
const config = await loadConfig5(paths);
|
|
9138
9106
|
const userTags = parseCsv2(opts.tags);
|
|
9139
9107
|
const anchorPaths = parseCsv2(opts.paths ?? opts.files);
|
|
9108
|
+
const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
|
|
9109
|
+
keywords: parseCsv2(opts.activationKeyword),
|
|
9110
|
+
globs: parseCsv2(opts.activationGlob),
|
|
9111
|
+
always: Boolean(opts.activationAlways)
|
|
9112
|
+
} : void 0;
|
|
9140
9113
|
const autoTagsEnabled = opts.autoTag !== false;
|
|
9141
9114
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9142
9115
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
@@ -9194,6 +9167,7 @@ TODO \u2014 write the memory body.
|
|
|
9194
9167
|
const newFrontmatter = {
|
|
9195
9168
|
...fm,
|
|
9196
9169
|
revision_count: revisionCount,
|
|
9170
|
+
...activation ? { activation } : {},
|
|
9197
9171
|
tags: mergedTags.length ? mergedTags : fm.tags,
|
|
9198
9172
|
anchor: {
|
|
9199
9173
|
commit: opts.commit ?? fm.anchor.commit,
|
|
@@ -9224,7 +9198,8 @@ TODO \u2014 write the memory body.
|
|
|
9224
9198
|
commit: opts.commit,
|
|
9225
9199
|
topic: opts.topic,
|
|
9226
9200
|
status: config.defaultStatus === "validated" ? "validated" : void 0,
|
|
9227
|
-
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
|
|
9201
|
+
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
|
|
9202
|
+
activation
|
|
9228
9203
|
});
|
|
9229
9204
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9230
9205
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
@@ -10556,14 +10531,68 @@ function pad(value, width) {
|
|
|
10556
10531
|
return value.slice(0, width - 1) + "\u2026";
|
|
10557
10532
|
}
|
|
10558
10533
|
|
|
10559
|
-
// src/commands/memory-
|
|
10560
|
-
import { writeFile as writeFile21 } from "fs/promises";
|
|
10534
|
+
// src/commands/memory-feedback.ts
|
|
10561
10535
|
import { existsSync as existsSync53 } from "fs";
|
|
10562
|
-
import path34 from "path";
|
|
10563
10536
|
import "commander";
|
|
10564
10537
|
import {
|
|
10538
|
+
computeImpact as computeImpact4,
|
|
10565
10539
|
findProjectRoot as findProjectRoot31,
|
|
10540
|
+
getUsage as getUsage19,
|
|
10541
|
+
loadUsageIndex as loadUsageIndex24,
|
|
10542
|
+
recordApplied as recordApplied2,
|
|
10543
|
+
recordRejection as recordRejection4,
|
|
10566
10544
|
resolveHaivePaths as resolveHaivePaths28,
|
|
10545
|
+
saveUsageIndex as saveUsageIndex6
|
|
10546
|
+
} from "@hiveai/core";
|
|
10547
|
+
function registerMemoryFeedback(memory2) {
|
|
10548
|
+
memory2.command("feedback <id>").description(
|
|
10549
|
+
"Record whether a memory actually helped \u2014 the closed-loop utility signal (mirror of the mem_feedback MCP tool). 'applied' = it steered your work; 'rejected' = it was wrong/unhelpful. Feeds `haive memory impact`."
|
|
10550
|
+
).option("--applied", "the memory changed what you did (positive signal)", false).option("--rejected", "the memory was wrong/outdated/unhelpful (negative signal)", false).option("--reason <text>", "why it was rejected (stored on the usage record)").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10551
|
+
if (opts.applied === opts.rejected) {
|
|
10552
|
+
ui.error("Specify exactly one of --applied or --rejected.");
|
|
10553
|
+
process.exitCode = 1;
|
|
10554
|
+
return;
|
|
10555
|
+
}
|
|
10556
|
+
const root = findProjectRoot31(opts.dir);
|
|
10557
|
+
const paths = resolveHaivePaths28(root);
|
|
10558
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10559
|
+
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10560
|
+
process.exitCode = 1;
|
|
10561
|
+
return;
|
|
10562
|
+
}
|
|
10563
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10564
|
+
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
10565
|
+
if (!target) {
|
|
10566
|
+
ui.error(`No memory with id '${id}'.`);
|
|
10567
|
+
process.exitCode = 1;
|
|
10568
|
+
return;
|
|
10569
|
+
}
|
|
10570
|
+
const index = await loadUsageIndex24(paths);
|
|
10571
|
+
const outcome = opts.applied ? "applied" : "rejected";
|
|
10572
|
+
if (opts.applied) recordApplied2(index, id);
|
|
10573
|
+
else recordRejection4(index, id, opts.reason ?? null);
|
|
10574
|
+
await saveUsageIndex6(paths, index);
|
|
10575
|
+
const usage = getUsage19(index, id);
|
|
10576
|
+
const impact = computeImpact4(target.memory.frontmatter, usage);
|
|
10577
|
+
if (opts.json) {
|
|
10578
|
+
console.log(JSON.stringify({ id, outcome, usage, impact }, null, 2));
|
|
10579
|
+
return;
|
|
10580
|
+
}
|
|
10581
|
+
ui.success(`Recorded '${outcome}' for ${id}`);
|
|
10582
|
+
ui.info(
|
|
10583
|
+
`applied=${usage.applied_count} \xB7 rejected=${usage.rejected_count} \xB7 read=${usage.read_count} \u2192 impact ${impact.score.toFixed(2)} (${impact.tier})`
|
|
10584
|
+
);
|
|
10585
|
+
});
|
|
10586
|
+
}
|
|
10587
|
+
|
|
10588
|
+
// src/commands/memory-verify.ts
|
|
10589
|
+
import { writeFile as writeFile21 } from "fs/promises";
|
|
10590
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10591
|
+
import path34 from "path";
|
|
10592
|
+
import "commander";
|
|
10593
|
+
import {
|
|
10594
|
+
findProjectRoot as findProjectRoot32,
|
|
10595
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
10567
10596
|
serializeMemory as serializeMemory19,
|
|
10568
10597
|
verifyAnchor as verifyAnchor3
|
|
10569
10598
|
} from "@hiveai/core";
|
|
@@ -10571,9 +10600,9 @@ function registerMemoryVerify(memory2) {
|
|
|
10571
10600
|
memory2.command("verify").description(
|
|
10572
10601
|
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
10573
10602
|
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10574
|
-
const root =
|
|
10575
|
-
const paths =
|
|
10576
|
-
if (!
|
|
10603
|
+
const root = findProjectRoot32(opts.dir);
|
|
10604
|
+
const paths = resolveHaivePaths29(root);
|
|
10605
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
10577
10606
|
if (opts.json) {
|
|
10578
10607
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10579
10608
|
} else {
|
|
@@ -10694,24 +10723,24 @@ function applyVerification2(mem, result) {
|
|
|
10694
10723
|
|
|
10695
10724
|
// src/commands/memory-import.ts
|
|
10696
10725
|
import { readFile as readFile15 } from "fs/promises";
|
|
10697
|
-
import { existsSync as
|
|
10726
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10698
10727
|
import "commander";
|
|
10699
10728
|
import {
|
|
10700
|
-
findProjectRoot as
|
|
10701
|
-
resolveHaivePaths as
|
|
10729
|
+
findProjectRoot as findProjectRoot33,
|
|
10730
|
+
resolveHaivePaths as resolveHaivePaths30
|
|
10702
10731
|
} from "@hiveai/core";
|
|
10703
10732
|
function registerMemoryImport(memory2) {
|
|
10704
10733
|
memory2.command("import").description(
|
|
10705
10734
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
10706
10735
|
).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10707
|
-
const root =
|
|
10708
|
-
const paths =
|
|
10709
|
-
if (!
|
|
10736
|
+
const root = findProjectRoot33(opts.dir);
|
|
10737
|
+
const paths = resolveHaivePaths30(root);
|
|
10738
|
+
if (!existsSync55(paths.haiveDir)) {
|
|
10710
10739
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10711
10740
|
process.exitCode = 1;
|
|
10712
10741
|
return;
|
|
10713
10742
|
}
|
|
10714
|
-
if (!
|
|
10743
|
+
if (!existsSync55(opts.from)) {
|
|
10715
10744
|
ui.error(`File not found: ${opts.from}`);
|
|
10716
10745
|
process.exitCode = 1;
|
|
10717
10746
|
return;
|
|
@@ -10744,14 +10773,14 @@ function registerMemoryImport(memory2) {
|
|
|
10744
10773
|
}
|
|
10745
10774
|
|
|
10746
10775
|
// src/commands/memory-import-changelog.ts
|
|
10747
|
-
import { existsSync as
|
|
10776
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10748
10777
|
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
|
|
10749
10778
|
import path35 from "path";
|
|
10750
10779
|
import "commander";
|
|
10751
10780
|
import {
|
|
10752
10781
|
buildFrontmatter as buildFrontmatter9,
|
|
10753
|
-
findProjectRoot as
|
|
10754
|
-
resolveHaivePaths as
|
|
10782
|
+
findProjectRoot as findProjectRoot34,
|
|
10783
|
+
resolveHaivePaths as resolveHaivePaths31,
|
|
10755
10784
|
serializeMemory as serializeMemory20
|
|
10756
10785
|
} from "@hiveai/core";
|
|
10757
10786
|
function parseChangelog(content) {
|
|
@@ -10816,10 +10845,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10816
10845
|
"--versions <csv>",
|
|
10817
10846
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
10818
10847
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10819
|
-
const root =
|
|
10820
|
-
const paths =
|
|
10848
|
+
const root = findProjectRoot34(opts.dir);
|
|
10849
|
+
const paths = resolveHaivePaths31(root);
|
|
10821
10850
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10822
|
-
if (!
|
|
10851
|
+
if (!existsSync56(changelogPath)) {
|
|
10823
10852
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10824
10853
|
process.exitCode = 1;
|
|
10825
10854
|
return;
|
|
@@ -10906,17 +10935,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10906
10935
|
}
|
|
10907
10936
|
|
|
10908
10937
|
// src/commands/memory-digest.ts
|
|
10909
|
-
import { existsSync as
|
|
10938
|
+
import { existsSync as existsSync57 } from "fs";
|
|
10910
10939
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
10911
10940
|
import path36 from "path";
|
|
10912
10941
|
import "commander";
|
|
10913
10942
|
import {
|
|
10914
10943
|
deriveConfidence as deriveConfidence12,
|
|
10915
|
-
findProjectRoot as
|
|
10916
|
-
getUsage as
|
|
10944
|
+
findProjectRoot as findProjectRoot35,
|
|
10945
|
+
getUsage as getUsage20,
|
|
10917
10946
|
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
10918
|
-
loadUsageIndex as
|
|
10919
|
-
resolveHaivePaths as
|
|
10947
|
+
loadUsageIndex as loadUsageIndex25,
|
|
10948
|
+
resolveHaivePaths as resolveHaivePaths32
|
|
10920
10949
|
} from "@hiveai/core";
|
|
10921
10950
|
var CONFIDENCE_EMOJI = {
|
|
10922
10951
|
unverified: "\u2B1C",
|
|
@@ -10929,9 +10958,9 @@ function registerMemoryDigest(program2) {
|
|
|
10929
10958
|
program2.command("digest").description(
|
|
10930
10959
|
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
10931
10960
|
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10932
|
-
const root =
|
|
10933
|
-
const paths =
|
|
10934
|
-
if (!
|
|
10961
|
+
const root = findProjectRoot35(opts.dir);
|
|
10962
|
+
const paths = resolveHaivePaths32(root);
|
|
10963
|
+
if (!existsSync57(paths.memoriesDir)) {
|
|
10935
10964
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10936
10965
|
process.exitCode = 1;
|
|
10937
10966
|
return;
|
|
@@ -10940,7 +10969,7 @@ function registerMemoryDigest(program2) {
|
|
|
10940
10969
|
const scopeFilter = opts.scope ?? "team";
|
|
10941
10970
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10942
10971
|
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10943
|
-
const usage = await
|
|
10972
|
+
const usage = await loadUsageIndex25(paths);
|
|
10944
10973
|
const recent = all.filter(({ memory: mem }) => {
|
|
10945
10974
|
const fm = mem.frontmatter;
|
|
10946
10975
|
if (fm.type === "session_recap") return false;
|
|
@@ -10971,7 +11000,7 @@ function registerMemoryDigest(program2) {
|
|
|
10971
11000
|
lines.push(``);
|
|
10972
11001
|
for (const { memory: mem } of mems) {
|
|
10973
11002
|
const fm = mem.frontmatter;
|
|
10974
|
-
const u =
|
|
11003
|
+
const u = getUsage20(usage, fm.id);
|
|
10975
11004
|
const confidence = deriveConfidence12(fm, u);
|
|
10976
11005
|
const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
|
|
10977
11006
|
const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
|
|
@@ -11014,21 +11043,21 @@ function registerMemoryDigest(program2) {
|
|
|
11014
11043
|
|
|
11015
11044
|
// src/commands/session-end.ts
|
|
11016
11045
|
import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
11017
|
-
import { existsSync as
|
|
11046
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11018
11047
|
import { spawn as spawn4 } from "child_process";
|
|
11019
11048
|
import path37 from "path";
|
|
11020
11049
|
import "commander";
|
|
11021
11050
|
import {
|
|
11022
11051
|
buildFrontmatter as buildFrontmatter10,
|
|
11023
|
-
findProjectRoot as
|
|
11052
|
+
findProjectRoot as findProjectRoot36,
|
|
11024
11053
|
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
11025
11054
|
memoryFilePath as memoryFilePath9,
|
|
11026
|
-
resolveHaivePaths as
|
|
11055
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
11027
11056
|
serializeMemory as serializeMemory21
|
|
11028
11057
|
} from "@hiveai/core";
|
|
11029
11058
|
async function buildAutoRecap(paths) {
|
|
11030
11059
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11031
|
-
if (!
|
|
11060
|
+
if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
|
|
11032
11061
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
11033
11062
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
11034
11063
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11217,9 +11246,9 @@ function registerSessionEnd(session2) {
|
|
|
11217
11246
|
--next "Add integration tests for webhook signature validation"
|
|
11218
11247
|
`
|
|
11219
11248
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11220
|
-
const root =
|
|
11221
|
-
const paths =
|
|
11222
|
-
if (!
|
|
11249
|
+
const root = findProjectRoot36(opts.dir);
|
|
11250
|
+
const paths = resolveHaivePaths33(root);
|
|
11251
|
+
if (!existsSync58(paths.haiveDir)) {
|
|
11223
11252
|
if (opts.auto || opts.quiet) return;
|
|
11224
11253
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11225
11254
|
process.exitCode = 1;
|
|
@@ -11252,7 +11281,7 @@ function registerSessionEnd(session2) {
|
|
|
11252
11281
|
});
|
|
11253
11282
|
const topic = recapTopic2(scope, opts.module);
|
|
11254
11283
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11255
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11284
|
+
const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
|
|
11256
11285
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11257
11286
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11258
11287
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11260,10 +11289,10 @@ function registerSessionEnd(session2) {
|
|
|
11260
11289
|
const cleanupObservations = async () => {
|
|
11261
11290
|
if (!opts.auto) return;
|
|
11262
11291
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11263
|
-
if (
|
|
11292
|
+
if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
|
|
11264
11293
|
});
|
|
11265
11294
|
};
|
|
11266
|
-
if (
|
|
11295
|
+
if (existsSync58(paths.memoriesDir)) {
|
|
11267
11296
|
const existing = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
11268
11297
|
const topicMatch = existing.find(
|
|
11269
11298
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -11325,15 +11354,15 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11325
11354
|
}
|
|
11326
11355
|
|
|
11327
11356
|
// src/commands/snapshot.ts
|
|
11328
|
-
import { existsSync as
|
|
11357
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11329
11358
|
import { readdir as readdir4 } from "fs/promises";
|
|
11330
11359
|
import path38 from "path";
|
|
11331
11360
|
import "commander";
|
|
11332
11361
|
import {
|
|
11333
11362
|
diffContract,
|
|
11334
|
-
findProjectRoot as
|
|
11363
|
+
findProjectRoot as findProjectRoot37,
|
|
11335
11364
|
loadConfig as loadConfig7,
|
|
11336
|
-
resolveHaivePaths as
|
|
11365
|
+
resolveHaivePaths as resolveHaivePaths34,
|
|
11337
11366
|
snapshotContract
|
|
11338
11367
|
} from "@hiveai/core";
|
|
11339
11368
|
function registerSnapshot(program2) {
|
|
@@ -11358,16 +11387,16 @@ function registerSnapshot(program2) {
|
|
|
11358
11387
|
"--format <format>",
|
|
11359
11388
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
11360
11389
|
).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11361
|
-
const root =
|
|
11362
|
-
const paths =
|
|
11363
|
-
if (!
|
|
11390
|
+
const root = findProjectRoot37(opts.dir);
|
|
11391
|
+
const paths = resolveHaivePaths34(root);
|
|
11392
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
11364
11393
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11365
11394
|
process.exitCode = 1;
|
|
11366
11395
|
return;
|
|
11367
11396
|
}
|
|
11368
11397
|
if (opts.list) {
|
|
11369
11398
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11370
|
-
if (!
|
|
11399
|
+
if (!existsSync59(contractsDir)) {
|
|
11371
11400
|
console.log(ui.dim("No contract snapshots found."));
|
|
11372
11401
|
return;
|
|
11373
11402
|
}
|
|
@@ -11491,16 +11520,16 @@ function detectFormat(filePath) {
|
|
|
11491
11520
|
}
|
|
11492
11521
|
|
|
11493
11522
|
// src/commands/hub.ts
|
|
11494
|
-
import { existsSync as
|
|
11523
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11495
11524
|
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
11496
11525
|
import path39 from "path";
|
|
11497
11526
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11498
11527
|
import "commander";
|
|
11499
11528
|
import {
|
|
11500
|
-
findProjectRoot as
|
|
11529
|
+
findProjectRoot as findProjectRoot38,
|
|
11501
11530
|
loadConfig as loadConfig8,
|
|
11502
11531
|
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11503
|
-
resolveHaivePaths as
|
|
11532
|
+
resolveHaivePaths as resolveHaivePaths35,
|
|
11504
11533
|
saveConfig as saveConfig3,
|
|
11505
11534
|
serializeMemory as serializeMemory23
|
|
11506
11535
|
} from "@hiveai/core";
|
|
@@ -11582,8 +11611,8 @@ Next steps:
|
|
|
11582
11611
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
11583
11612
|
`
|
|
11584
11613
|
).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
|
|
11585
|
-
const root =
|
|
11586
|
-
const paths =
|
|
11614
|
+
const root = findProjectRoot38(opts.dir);
|
|
11615
|
+
const paths = resolveHaivePaths35(root);
|
|
11587
11616
|
const config = await loadConfig8(paths);
|
|
11588
11617
|
if (!config.hubPath) {
|
|
11589
11618
|
ui.error(
|
|
@@ -11593,7 +11622,7 @@ Next steps:
|
|
|
11593
11622
|
return;
|
|
11594
11623
|
}
|
|
11595
11624
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11596
|
-
if (!
|
|
11625
|
+
if (!existsSync60(hubRoot)) {
|
|
11597
11626
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11598
11627
|
process.exitCode = 1;
|
|
11599
11628
|
return;
|
|
@@ -11651,8 +11680,8 @@ Next steps:
|
|
|
11651
11680
|
hub.command("pull").description(
|
|
11652
11681
|
"Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
|
|
11653
11682
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11654
|
-
const root =
|
|
11655
|
-
const paths =
|
|
11683
|
+
const root = findProjectRoot38(opts.dir);
|
|
11684
|
+
const paths = resolveHaivePaths35(root);
|
|
11656
11685
|
const config = await loadConfig8(paths);
|
|
11657
11686
|
if (!config.hubPath) {
|
|
11658
11687
|
ui.error(
|
|
@@ -11663,7 +11692,7 @@ Next steps:
|
|
|
11663
11692
|
}
|
|
11664
11693
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11665
11694
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11666
|
-
if (!
|
|
11695
|
+
if (!existsSync60(hubSharedDir)) {
|
|
11667
11696
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11668
11697
|
return;
|
|
11669
11698
|
}
|
|
@@ -11710,15 +11739,15 @@ Next steps:
|
|
|
11710
11739
|
);
|
|
11711
11740
|
});
|
|
11712
11741
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11713
|
-
const root =
|
|
11714
|
-
const paths =
|
|
11742
|
+
const root = findProjectRoot38(opts.dir);
|
|
11743
|
+
const paths = resolveHaivePaths35(root);
|
|
11715
11744
|
const config = await loadConfig8(paths);
|
|
11716
11745
|
console.log(ui.bold("Hub status"));
|
|
11717
11746
|
console.log(
|
|
11718
11747
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11719
11748
|
);
|
|
11720
11749
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11721
|
-
if (
|
|
11750
|
+
if (existsSync60(sharedDir)) {
|
|
11722
11751
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11723
11752
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11724
11753
|
console.log(`
|
|
@@ -11747,17 +11776,17 @@ Next steps:
|
|
|
11747
11776
|
|
|
11748
11777
|
// src/commands/stats.ts
|
|
11749
11778
|
import "commander";
|
|
11750
|
-
import { existsSync as
|
|
11779
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11751
11780
|
import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
|
|
11752
11781
|
import path40 from "path";
|
|
11753
11782
|
import {
|
|
11754
11783
|
aggregateUsage,
|
|
11755
|
-
findProjectRoot as
|
|
11784
|
+
findProjectRoot as findProjectRoot39,
|
|
11756
11785
|
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11757
|
-
loadUsageIndex as
|
|
11786
|
+
loadUsageIndex as loadUsageIndex26,
|
|
11758
11787
|
parseSince,
|
|
11759
11788
|
readUsageEvents as readUsageEvents2,
|
|
11760
|
-
resolveHaivePaths as
|
|
11789
|
+
resolveHaivePaths as resolveHaivePaths36,
|
|
11761
11790
|
usageLogSize
|
|
11762
11791
|
} from "@hiveai/core";
|
|
11763
11792
|
function registerStats(program2) {
|
|
@@ -11766,8 +11795,8 @@ function registerStats(program2) {
|
|
|
11766
11795
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
11767
11796
|
void 0
|
|
11768
11797
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11769
|
-
const root =
|
|
11770
|
-
const paths =
|
|
11798
|
+
const root = findProjectRoot39(opts.dir);
|
|
11799
|
+
const paths = resolveHaivePaths36(root);
|
|
11771
11800
|
if (opts.exportReport) {
|
|
11772
11801
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
11773
11802
|
return;
|
|
@@ -11825,7 +11854,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11825
11854
|
const size = await usageLogSize(paths);
|
|
11826
11855
|
let events = await readUsageEvents2(paths);
|
|
11827
11856
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11828
|
-
if (
|
|
11857
|
+
if (existsSync61(paths.memoriesDir)) {
|
|
11829
11858
|
const mems = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11830
11859
|
for (const { memory: memory2 } of mems) {
|
|
11831
11860
|
const fm = memory2.frontmatter;
|
|
@@ -11840,7 +11869,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11840
11869
|
const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
|
|
11841
11870
|
let memoryHitsLeader = null;
|
|
11842
11871
|
try {
|
|
11843
|
-
const usageIdx = await
|
|
11872
|
+
const usageIdx = await loadUsageIndex26(paths);
|
|
11844
11873
|
const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
|
|
11845
11874
|
memoryHitsLeader = tops[0] ?? null;
|
|
11846
11875
|
} catch {
|
|
@@ -11872,7 +11901,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11872
11901
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11873
11902
|
}
|
|
11874
11903
|
async function renderMemoryHits(paths, opts) {
|
|
11875
|
-
const index = await
|
|
11904
|
+
const index = await loadUsageIndex26(paths);
|
|
11876
11905
|
const since = parseSince(opts.since ?? "30d");
|
|
11877
11906
|
const sinceMs = since ? new Date(since).getTime() : null;
|
|
11878
11907
|
const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
|
|
@@ -11920,13 +11949,13 @@ import { performance } from "perf_hooks";
|
|
|
11920
11949
|
import "commander";
|
|
11921
11950
|
import {
|
|
11922
11951
|
estimateTokens as estimateTokens3,
|
|
11923
|
-
findProjectRoot as
|
|
11924
|
-
resolveHaivePaths as
|
|
11952
|
+
findProjectRoot as findProjectRoot40,
|
|
11953
|
+
resolveHaivePaths as resolveHaivePaths37
|
|
11925
11954
|
} from "@hiveai/core";
|
|
11926
11955
|
function registerBench(program2) {
|
|
11927
11956
|
program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11928
|
-
const root =
|
|
11929
|
-
const paths =
|
|
11957
|
+
const root = findProjectRoot40(opts.dir);
|
|
11958
|
+
const paths = resolveHaivePaths37(root);
|
|
11930
11959
|
const ctx = { paths };
|
|
11931
11960
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
11932
11961
|
const scenarios = [
|
|
@@ -12045,11 +12074,11 @@ function summarize(name, t0, payload, notes) {
|
|
|
12045
12074
|
}
|
|
12046
12075
|
|
|
12047
12076
|
// src/commands/benchmark.ts
|
|
12048
|
-
import { existsSync as
|
|
12077
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12049
12078
|
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
|
|
12050
12079
|
import path41 from "path";
|
|
12051
12080
|
import "commander";
|
|
12052
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
12081
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
12053
12082
|
function registerBenchmark(program2) {
|
|
12054
12083
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
12055
12084
|
benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
|
|
@@ -12089,18 +12118,18 @@ function registerBenchmark(program2) {
|
|
|
12089
12118
|
function resolveBenchmarkRoot(dir) {
|
|
12090
12119
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
12091
12120
|
if (path41.isAbsolute(candidate)) return candidate;
|
|
12092
|
-
const projectRoot =
|
|
12121
|
+
const projectRoot = findProjectRoot41(process.cwd());
|
|
12093
12122
|
return path41.join(projectRoot, candidate);
|
|
12094
12123
|
}
|
|
12095
12124
|
async function collectRows(root) {
|
|
12096
|
-
if (!
|
|
12125
|
+
if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
12097
12126
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
12098
12127
|
const rows = [];
|
|
12099
12128
|
for (const entry of entries) {
|
|
12100
12129
|
if (!entry.isDirectory()) continue;
|
|
12101
12130
|
const fixtureDir = path41.join(root, entry.name);
|
|
12102
12131
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
12103
|
-
if (!
|
|
12132
|
+
if (!existsSync63(reportFile)) continue;
|
|
12104
12133
|
const report = await readFile19(reportFile, "utf8");
|
|
12105
12134
|
rows.push(parseAgentReport(entry.name, report));
|
|
12106
12135
|
}
|
|
@@ -12191,15 +12220,15 @@ function escapeRegExp(value) {
|
|
|
12191
12220
|
|
|
12192
12221
|
// src/commands/eval.ts
|
|
12193
12222
|
import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
|
|
12194
|
-
import { existsSync as
|
|
12223
|
+
import { existsSync as existsSync64 } from "fs";
|
|
12195
12224
|
import path43 from "path";
|
|
12196
12225
|
import "commander";
|
|
12197
12226
|
import {
|
|
12198
12227
|
aggregateRetrieval,
|
|
12199
12228
|
aggregateSensors,
|
|
12200
12229
|
buildReport,
|
|
12201
|
-
findProjectRoot as
|
|
12202
|
-
resolveHaivePaths as
|
|
12230
|
+
findProjectRoot as findProjectRoot42,
|
|
12231
|
+
resolveHaivePaths as resolveHaivePaths38,
|
|
12203
12232
|
scoreRetrievalCase,
|
|
12204
12233
|
scoreSensorCase,
|
|
12205
12234
|
synthesizeSelfEvalCases
|
|
@@ -12207,10 +12236,10 @@ import {
|
|
|
12207
12236
|
function registerEval(program2) {
|
|
12208
12237
|
program2.command("eval").description(
|
|
12209
12238
|
"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."
|
|
12210
|
-
).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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12211
|
-
const root =
|
|
12212
|
-
const paths =
|
|
12213
|
-
if (!
|
|
12239
|
+
).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("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12240
|
+
const root = findProjectRoot42(opts.dir);
|
|
12241
|
+
const paths = resolveHaivePaths38(root);
|
|
12242
|
+
if (!existsSync64(paths.memoriesDir)) {
|
|
12214
12243
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12215
12244
|
process.exitCode = 1;
|
|
12216
12245
|
return;
|
|
@@ -12243,16 +12272,26 @@ function registerEval(program2) {
|
|
|
12243
12272
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12244
12273
|
if (opts.json) {
|
|
12245
12274
|
console.log(JSON.stringify({ root, k, report }, null, 2));
|
|
12246
|
-
|
|
12275
|
+
} else {
|
|
12276
|
+
const md = renderMarkdown2(root, k, report);
|
|
12277
|
+
if (opts.out) {
|
|
12278
|
+
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
12279
|
+
await writeFile29(outFile, md, "utf8");
|
|
12280
|
+
ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
|
|
12281
|
+
} else {
|
|
12282
|
+
console.log(md);
|
|
12283
|
+
}
|
|
12247
12284
|
}
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12285
|
+
if (opts.failUnder !== void 0) {
|
|
12286
|
+
const threshold = Number(opts.failUnder);
|
|
12287
|
+
if (Number.isNaN(threshold)) {
|
|
12288
|
+
ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
|
|
12289
|
+
process.exitCode = 1;
|
|
12290
|
+
} else if (report.score < threshold) {
|
|
12291
|
+
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12292
|
+
process.exitCode = 1;
|
|
12293
|
+
}
|
|
12254
12294
|
}
|
|
12255
|
-
console.log(md);
|
|
12256
12295
|
});
|
|
12257
12296
|
}
|
|
12258
12297
|
async function resolveSpec(opts, memoriesDir) {
|
|
@@ -12348,19 +12387,19 @@ function renderMarkdown2(root, k, report) {
|
|
|
12348
12387
|
|
|
12349
12388
|
// src/commands/memory-suggest.ts
|
|
12350
12389
|
import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
|
|
12351
|
-
import { existsSync as
|
|
12390
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12352
12391
|
import path44 from "path";
|
|
12353
12392
|
import "commander";
|
|
12354
12393
|
import {
|
|
12355
12394
|
aggregateUsage as aggregateUsage2,
|
|
12356
12395
|
buildFrontmatter as buildFrontmatter11,
|
|
12357
|
-
findProjectRoot as
|
|
12396
|
+
findProjectRoot as findProjectRoot43,
|
|
12358
12397
|
loadConfig as loadConfig9,
|
|
12359
12398
|
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
12360
12399
|
memoryFilePath as memoryFilePath10,
|
|
12361
12400
|
parseSince as parseSince2,
|
|
12362
12401
|
readUsageEvents as readUsageEvents3,
|
|
12363
|
-
resolveHaivePaths as
|
|
12402
|
+
resolveHaivePaths as resolveHaivePaths39,
|
|
12364
12403
|
serializeMemory as serializeMemory24
|
|
12365
12404
|
} from "@hiveai/core";
|
|
12366
12405
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -12377,8 +12416,8 @@ function registerMemorySuggest(memory2) {
|
|
|
12377
12416
|
memory2.command("suggest").description(
|
|
12378
12417
|
"Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
|
|
12379
12418
|
).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12380
|
-
const root =
|
|
12381
|
-
const paths =
|
|
12419
|
+
const root = findProjectRoot43(opts.dir);
|
|
12420
|
+
const paths = resolveHaivePaths39(root);
|
|
12382
12421
|
const events = await readUsageEvents3(paths);
|
|
12383
12422
|
if (events.length === 0) {
|
|
12384
12423
|
if (opts.json) {
|
|
@@ -12427,7 +12466,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12427
12466
|
}
|
|
12428
12467
|
const created = [];
|
|
12429
12468
|
const skipped = [];
|
|
12430
|
-
const existing =
|
|
12469
|
+
const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
|
|
12431
12470
|
for (const s of top) {
|
|
12432
12471
|
const slug = slugify2(s.query);
|
|
12433
12472
|
if (!slug) {
|
|
@@ -12451,7 +12490,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12451
12490
|
const body = renderTemplate(s, fm.id, status);
|
|
12452
12491
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
12453
12492
|
await mkdir18(path44.dirname(file), { recursive: true });
|
|
12454
|
-
if (
|
|
12493
|
+
if (existsSync65(file)) {
|
|
12455
12494
|
skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
|
|
12456
12495
|
continue;
|
|
12457
12496
|
}
|
|
@@ -12554,18 +12593,18 @@ function truncate2(text, max) {
|
|
|
12554
12593
|
}
|
|
12555
12594
|
|
|
12556
12595
|
// src/commands/memory-archive.ts
|
|
12557
|
-
import { existsSync as
|
|
12596
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12558
12597
|
import { writeFile as writeFile31 } from "fs/promises";
|
|
12559
12598
|
import path45 from "path";
|
|
12560
12599
|
import "commander";
|
|
12561
12600
|
import {
|
|
12562
|
-
findProjectRoot as
|
|
12563
|
-
getUsage as
|
|
12601
|
+
findProjectRoot as findProjectRoot44,
|
|
12602
|
+
getUsage as getUsage21,
|
|
12564
12603
|
retirementSignal as retirementSignal2,
|
|
12565
12604
|
loadConfig as loadConfig10,
|
|
12566
12605
|
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12567
|
-
loadUsageIndex as
|
|
12568
|
-
resolveHaivePaths as
|
|
12606
|
+
loadUsageIndex as loadUsageIndex27,
|
|
12607
|
+
resolveHaivePaths as resolveHaivePaths40,
|
|
12569
12608
|
serializeMemory as serializeMemory25
|
|
12570
12609
|
} from "@hiveai/core";
|
|
12571
12610
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
@@ -12573,9 +12612,9 @@ function registerMemoryArchive(memory2) {
|
|
|
12573
12612
|
memory2.command("archive").description(
|
|
12574
12613
|
"Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
|
|
12575
12614
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12576
|
-
const root =
|
|
12577
|
-
const paths =
|
|
12578
|
-
if (!
|
|
12615
|
+
const root = findProjectRoot44(opts.dir);
|
|
12616
|
+
const paths = resolveHaivePaths40(root);
|
|
12617
|
+
if (!existsSync66(paths.memoriesDir)) {
|
|
12579
12618
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12580
12619
|
process.exitCode = 1;
|
|
12581
12620
|
return;
|
|
@@ -12590,7 +12629,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12590
12629
|
}
|
|
12591
12630
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12592
12631
|
const all = await loadMemoriesFromDir33(paths.memoriesDir);
|
|
12593
|
-
const usage = await
|
|
12632
|
+
const usage = await loadUsageIndex27(paths);
|
|
12594
12633
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12595
12634
|
const candidates = [];
|
|
12596
12635
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -12600,10 +12639,10 @@ function registerMemoryArchive(memory2) {
|
|
|
12600
12639
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12601
12640
|
const retired = retirementSignal2(fm, mem.body);
|
|
12602
12641
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12603
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
12642
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
|
|
12604
12643
|
const isAnchorless = !hasAnyAnchor;
|
|
12605
12644
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12606
|
-
const u =
|
|
12645
|
+
const u = getUsage21(usage, fm.id);
|
|
12607
12646
|
const lastSeen = u.last_read_at ?? fm.created_at;
|
|
12608
12647
|
if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
|
|
12609
12648
|
const reason = retired.retired ? `retired lifecycle signal: ${retired.reason ?? "unknown"}` : isAnchorless ? `anchorless and not read since ${lastSeen.slice(0, 10)}` : allPathsGone ? `all ${fm.anchor.paths.length} anchored path(s) missing and not read since ${lastSeen.slice(0, 10)}` : `not read since ${lastSeen.slice(0, 10)} (unread decay)`;
|
|
@@ -12675,34 +12714,34 @@ function parseDays(input) {
|
|
|
12675
12714
|
}
|
|
12676
12715
|
|
|
12677
12716
|
// src/commands/doctor.ts
|
|
12678
|
-
import { existsSync as
|
|
12717
|
+
import { existsSync as existsSync67, statSync as statSync2 } from "fs";
|
|
12679
12718
|
import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
|
|
12680
12719
|
import path46 from "path";
|
|
12681
12720
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12682
12721
|
import "commander";
|
|
12683
12722
|
import {
|
|
12684
12723
|
codeMapPath as codeMapPath2,
|
|
12685
|
-
findProjectRoot as
|
|
12686
|
-
getUsage as
|
|
12724
|
+
findProjectRoot as findProjectRoot45,
|
|
12725
|
+
getUsage as getUsage23,
|
|
12687
12726
|
isStackPackSeed as isStackPackSeed4,
|
|
12688
12727
|
loadCodeMap as loadCodeMap7,
|
|
12689
12728
|
loadConfig as loadConfig11,
|
|
12690
12729
|
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12691
|
-
loadUsageIndex as
|
|
12730
|
+
loadUsageIndex as loadUsageIndex28,
|
|
12692
12731
|
readUsageEvents as readUsageEvents4,
|
|
12693
|
-
resolveHaivePaths as
|
|
12732
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
12694
12733
|
} from "@hiveai/core";
|
|
12695
12734
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
12696
12735
|
function registerDoctor(program2) {
|
|
12697
12736
|
program2.command("doctor").description(
|
|
12698
12737
|
"Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
|
|
12699
12738
|
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12700
|
-
const root =
|
|
12701
|
-
const paths =
|
|
12739
|
+
const root = findProjectRoot45(opts.dir);
|
|
12740
|
+
const paths = resolveHaivePaths41(root);
|
|
12702
12741
|
const findings = [];
|
|
12703
12742
|
const repairs = [];
|
|
12704
12743
|
const config = await loadConfig11(paths);
|
|
12705
|
-
if (!
|
|
12744
|
+
if (!existsSync67(paths.haiveDir)) {
|
|
12706
12745
|
findings.push({
|
|
12707
12746
|
severity: "error",
|
|
12708
12747
|
code: "not-initialized",
|
|
@@ -12723,7 +12762,7 @@ function registerDoctor(program2) {
|
|
|
12723
12762
|
})
|
|
12724
12763
|
);
|
|
12725
12764
|
}
|
|
12726
|
-
if (!
|
|
12765
|
+
if (!existsSync67(paths.projectContext)) {
|
|
12727
12766
|
findings.push({
|
|
12728
12767
|
severity: "warn",
|
|
12729
12768
|
code: "no-project-context",
|
|
@@ -12752,7 +12791,7 @@ function registerDoctor(program2) {
|
|
|
12752
12791
|
});
|
|
12753
12792
|
}
|
|
12754
12793
|
}
|
|
12755
|
-
const memories =
|
|
12794
|
+
const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
|
|
12756
12795
|
const now = Date.now();
|
|
12757
12796
|
if (memories.length === 0) {
|
|
12758
12797
|
findings.push({
|
|
@@ -12761,7 +12800,7 @@ function registerDoctor(program2) {
|
|
|
12761
12800
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
12762
12801
|
});
|
|
12763
12802
|
} else {
|
|
12764
|
-
const usage = await
|
|
12803
|
+
const usage = await loadUsageIndex28(paths);
|
|
12765
12804
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
12766
12805
|
if (stale.length > 0) {
|
|
12767
12806
|
findings.push({
|
|
@@ -12818,7 +12857,7 @@ function registerDoctor(program2) {
|
|
|
12818
12857
|
}
|
|
12819
12858
|
const decayCandidates = memories.filter((m) => {
|
|
12820
12859
|
if (m.memory.frontmatter.status !== "validated") return false;
|
|
12821
|
-
const u =
|
|
12860
|
+
const u = getUsage23(usage, m.memory.frontmatter.id);
|
|
12822
12861
|
const last = u.last_read_at ?? m.memory.frontmatter.created_at;
|
|
12823
12862
|
return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
|
|
12824
12863
|
});
|
|
@@ -12904,7 +12943,7 @@ function registerDoctor(program2) {
|
|
|
12904
12943
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12905
12944
|
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12906
12945
|
let hasClaudeEnforcement = false;
|
|
12907
|
-
if (
|
|
12946
|
+
if (existsSync67(claudeSettings)) {
|
|
12908
12947
|
try {
|
|
12909
12948
|
const { readFile: readFile25 } = await import("fs/promises");
|
|
12910
12949
|
const raw = await readFile25(claudeSettings, "utf8");
|
|
@@ -12930,14 +12969,14 @@ function registerDoctor(program2) {
|
|
|
12930
12969
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12931
12970
|
});
|
|
12932
12971
|
}
|
|
12933
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
12972
|
+
findings.push(...await collectInstallFindings(root, "0.12.0"));
|
|
12934
12973
|
try {
|
|
12935
12974
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12936
12975
|
encoding: "utf8",
|
|
12937
12976
|
timeout: 3e3,
|
|
12938
12977
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12939
12978
|
}).trim();
|
|
12940
|
-
const cliVersion = "0.
|
|
12979
|
+
const cliVersion = "0.12.0";
|
|
12941
12980
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12942
12981
|
findings.push({
|
|
12943
12982
|
severity: "warn",
|
|
@@ -12959,7 +12998,7 @@ npm uninstall -g @hiveai/mcp`
|
|
|
12959
12998
|
];
|
|
12960
12999
|
const staleConfigs = [];
|
|
12961
13000
|
for (const cfgPath of configPaths) {
|
|
12962
|
-
if (!
|
|
13001
|
+
if (!existsSync67(cfgPath)) continue;
|
|
12963
13002
|
try {
|
|
12964
13003
|
const raw = await readFile21(cfgPath, "utf8");
|
|
12965
13004
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
@@ -13254,7 +13293,7 @@ which -a haive`
|
|
|
13254
13293
|
];
|
|
13255
13294
|
for (const rel of integrationFiles) {
|
|
13256
13295
|
const file = path46.join(root, rel);
|
|
13257
|
-
if (!
|
|
13296
|
+
if (!existsSync67(file)) continue;
|
|
13258
13297
|
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13259
13298
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
13260
13299
|
const version = versionForBinary(bin);
|
|
@@ -13350,7 +13389,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13350
13389
|
}
|
|
13351
13390
|
}
|
|
13352
13391
|
async function readJson(file) {
|
|
13353
|
-
if (!
|
|
13392
|
+
if (!existsSync67(file)) return null;
|
|
13354
13393
|
try {
|
|
13355
13394
|
return JSON.parse(await readFile21(file, "utf8"));
|
|
13356
13395
|
} catch {
|
|
@@ -13397,22 +13436,22 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13397
13436
|
}
|
|
13398
13437
|
|
|
13399
13438
|
// src/commands/playback.ts
|
|
13400
|
-
import { existsSync as
|
|
13439
|
+
import { existsSync as existsSync68 } from "fs";
|
|
13401
13440
|
import "commander";
|
|
13402
13441
|
import {
|
|
13403
|
-
findProjectRoot as
|
|
13442
|
+
findProjectRoot as findProjectRoot46,
|
|
13404
13443
|
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
13405
13444
|
parseSince as parseSince3,
|
|
13406
13445
|
readUsageEvents as readUsageEvents5,
|
|
13407
|
-
resolveHaivePaths as
|
|
13446
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
13408
13447
|
} from "@hiveai/core";
|
|
13409
13448
|
var MS_PER_MINUTE = 6e4;
|
|
13410
13449
|
function registerPlayback(program2) {
|
|
13411
13450
|
program2.command("playback").description(
|
|
13412
13451
|
"Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
|
|
13413
13452
|
).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13414
|
-
const root =
|
|
13415
|
-
const paths =
|
|
13453
|
+
const root = findProjectRoot46(opts.dir);
|
|
13454
|
+
const paths = resolveHaivePaths42(root);
|
|
13416
13455
|
const events = await readUsageEvents5(paths);
|
|
13417
13456
|
if (events.length === 0) {
|
|
13418
13457
|
if (opts.json) {
|
|
@@ -13427,7 +13466,7 @@ function registerPlayback(program2) {
|
|
|
13427
13466
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13428
13467
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13429
13468
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13430
|
-
const all =
|
|
13469
|
+
const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
13431
13470
|
const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
|
|
13432
13471
|
const enriched = sessions.map((s, i) => {
|
|
13433
13472
|
const startMs = Date.parse(s.start);
|
|
@@ -13518,9 +13557,9 @@ import { spawn as spawn5 } from "child_process";
|
|
|
13518
13557
|
import "commander";
|
|
13519
13558
|
import {
|
|
13520
13559
|
antiPatternGateParams,
|
|
13521
|
-
findProjectRoot as
|
|
13560
|
+
findProjectRoot as findProjectRoot47,
|
|
13522
13561
|
loadConfig as loadConfig12,
|
|
13523
|
-
resolveHaivePaths as
|
|
13562
|
+
resolveHaivePaths as resolveHaivePaths43
|
|
13524
13563
|
} from "@hiveai/core";
|
|
13525
13564
|
function registerPrecommit(program2) {
|
|
13526
13565
|
program2.command("precommit").description(
|
|
@@ -13532,8 +13571,8 @@ function registerPrecommit(program2) {
|
|
|
13532
13571
|
"--no-anchored-blocks",
|
|
13533
13572
|
"do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
|
|
13534
13573
|
).option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13535
|
-
const root =
|
|
13536
|
-
const paths =
|
|
13574
|
+
const root = findProjectRoot47(opts.dir);
|
|
13575
|
+
const paths = resolveHaivePaths43(root);
|
|
13537
13576
|
const ctx = { paths };
|
|
13538
13577
|
const config = await loadConfig12(paths);
|
|
13539
13578
|
const gate = config.enforcement?.antiPatternGate ?? "anchored";
|
|
@@ -13669,12 +13708,12 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13669
13708
|
}
|
|
13670
13709
|
|
|
13671
13710
|
// src/commands/welcome.ts
|
|
13672
|
-
import { existsSync as
|
|
13711
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13673
13712
|
import "commander";
|
|
13674
13713
|
import {
|
|
13675
|
-
findProjectRoot as
|
|
13714
|
+
findProjectRoot as findProjectRoot48,
|
|
13676
13715
|
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13677
|
-
resolveHaivePaths as
|
|
13716
|
+
resolveHaivePaths as resolveHaivePaths44
|
|
13678
13717
|
} from "@hiveai/core";
|
|
13679
13718
|
var TYPE_RANK = {
|
|
13680
13719
|
skill: 0,
|
|
@@ -13689,9 +13728,9 @@ function registerWelcome(program2) {
|
|
|
13689
13728
|
program2.command("welcome").description(
|
|
13690
13729
|
"Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
|
|
13691
13730
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13692
|
-
const root =
|
|
13693
|
-
const paths =
|
|
13694
|
-
if (!
|
|
13731
|
+
const root = findProjectRoot48(opts.dir);
|
|
13732
|
+
const paths = resolveHaivePaths44(root);
|
|
13733
|
+
if (!existsSync69(paths.memoriesDir)) {
|
|
13695
13734
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13696
13735
|
process.exitCode = 1;
|
|
13697
13736
|
return;
|
|
@@ -13763,14 +13802,14 @@ function registerResolveProject(program2) {
|
|
|
13763
13802
|
}
|
|
13764
13803
|
|
|
13765
13804
|
// src/commands/runtime-journal.ts
|
|
13766
|
-
import { existsSync as
|
|
13805
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13767
13806
|
import path48 from "path";
|
|
13768
13807
|
import "commander";
|
|
13769
13808
|
import {
|
|
13770
13809
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
13771
|
-
findProjectRoot as
|
|
13810
|
+
findProjectRoot as findProjectRoot49,
|
|
13772
13811
|
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
13773
|
-
resolveHaivePaths as
|
|
13812
|
+
resolveHaivePaths as resolveHaivePaths45
|
|
13774
13813
|
} from "@hiveai/core";
|
|
13775
13814
|
function registerRuntime(program2) {
|
|
13776
13815
|
const runtime = program2.command("runtime").description(
|
|
@@ -13779,7 +13818,7 @@ function registerRuntime(program2) {
|
|
|
13779
13818
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
13780
13819
|
journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
|
|
13781
13820
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13782
|
-
const paths =
|
|
13821
|
+
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13783
13822
|
const raw = opts.kind ?? "note";
|
|
13784
13823
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
13785
13824
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
@@ -13787,9 +13826,9 @@ function registerRuntime(program2) {
|
|
|
13787
13826
|
});
|
|
13788
13827
|
journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
13789
13828
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13790
|
-
const paths =
|
|
13829
|
+
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13791
13830
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13792
|
-
if (!
|
|
13831
|
+
if (!existsSync70(paths.haiveDir)) {
|
|
13793
13832
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13794
13833
|
process.exitCode = 1;
|
|
13795
13834
|
return;
|
|
@@ -13804,13 +13843,13 @@ function registerRuntime(program2) {
|
|
|
13804
13843
|
}
|
|
13805
13844
|
|
|
13806
13845
|
// src/commands/memory-timeline.ts
|
|
13807
|
-
import { existsSync as
|
|
13846
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13808
13847
|
import path49 from "path";
|
|
13809
13848
|
import "commander";
|
|
13810
13849
|
import {
|
|
13811
13850
|
collectTimelineEntries as collectTimelineEntries2,
|
|
13812
|
-
findProjectRoot as
|
|
13813
|
-
resolveHaivePaths as
|
|
13851
|
+
findProjectRoot as findProjectRoot50,
|
|
13852
|
+
resolveHaivePaths as resolveHaivePaths46
|
|
13814
13853
|
} from "@hiveai/core";
|
|
13815
13854
|
function registerMemoryTimeline(memory2) {
|
|
13816
13855
|
memory2.command("timeline").description(
|
|
@@ -13822,8 +13861,8 @@ function registerMemoryTimeline(memory2) {
|
|
|
13822
13861
|
return;
|
|
13823
13862
|
}
|
|
13824
13863
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13825
|
-
const paths =
|
|
13826
|
-
if (!
|
|
13864
|
+
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13865
|
+
if (!existsSync71(paths.memoriesDir)) {
|
|
13827
13866
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13828
13867
|
process.exitCode = 1;
|
|
13829
13868
|
return;
|
|
@@ -13841,14 +13880,14 @@ function registerMemoryTimeline(memory2) {
|
|
|
13841
13880
|
}
|
|
13842
13881
|
|
|
13843
13882
|
// src/commands/memory-conflict-candidates.ts
|
|
13844
|
-
import { existsSync as
|
|
13883
|
+
import { existsSync as existsSync73 } from "fs";
|
|
13845
13884
|
import path50 from "path";
|
|
13846
13885
|
import "commander";
|
|
13847
13886
|
import {
|
|
13848
13887
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
13849
13888
|
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
13850
|
-
findProjectRoot as
|
|
13851
|
-
resolveHaivePaths as
|
|
13889
|
+
findProjectRoot as findProjectRoot51,
|
|
13890
|
+
resolveHaivePaths as resolveHaivePaths47
|
|
13852
13891
|
} from "@hiveai/core";
|
|
13853
13892
|
function parseTypes(csv) {
|
|
13854
13893
|
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
@@ -13865,8 +13904,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13865
13904
|
"decision,architecture"
|
|
13866
13905
|
).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
|
|
13867
13906
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
13868
|
-
const paths =
|
|
13869
|
-
if (!
|
|
13907
|
+
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
13908
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
13870
13909
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13871
13910
|
process.exitCode = 1;
|
|
13872
13911
|
return;
|
|
@@ -13902,13 +13941,13 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13902
13941
|
|
|
13903
13942
|
// src/commands/enforce.ts
|
|
13904
13943
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
13905
|
-
import { existsSync as
|
|
13944
|
+
import { existsSync as existsSync74, statSync as statSync3 } from "fs";
|
|
13906
13945
|
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
|
|
13907
13946
|
import path51 from "path";
|
|
13908
13947
|
import "commander";
|
|
13909
13948
|
import {
|
|
13910
13949
|
antiPatternGateParams as antiPatternGateParams2,
|
|
13911
|
-
findProjectRoot as
|
|
13950
|
+
findProjectRoot as findProjectRoot52,
|
|
13912
13951
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
13913
13952
|
isFreshIsoDate,
|
|
13914
13953
|
loadConfig as loadConfig13,
|
|
@@ -13916,7 +13955,7 @@ import {
|
|
|
13916
13955
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
13917
13956
|
readRecentBriefingMarker,
|
|
13918
13957
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
13919
|
-
resolveHaivePaths as
|
|
13958
|
+
resolveHaivePaths as resolveHaivePaths48,
|
|
13920
13959
|
saveConfig as saveConfig4,
|
|
13921
13960
|
SESSION_RECAP_TTL_MS,
|
|
13922
13961
|
verifyAnchor as verifyAnchor4,
|
|
@@ -13929,8 +13968,8 @@ function registerEnforce(program2) {
|
|
|
13929
13968
|
"Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
|
|
13930
13969
|
);
|
|
13931
13970
|
enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
|
|
13932
|
-
const root =
|
|
13933
|
-
const paths =
|
|
13971
|
+
const root = findProjectRoot52(opts.dir);
|
|
13972
|
+
const paths = resolveHaivePaths48(root);
|
|
13934
13973
|
await mkdir19(paths.haiveDir, { recursive: true });
|
|
13935
13974
|
const current = await loadConfig13(paths);
|
|
13936
13975
|
await saveConfig4(paths, {
|
|
@@ -13974,17 +14013,17 @@ function registerEnforce(program2) {
|
|
|
13974
14013
|
if (report.should_block) process.exit(2);
|
|
13975
14014
|
});
|
|
13976
14015
|
enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
|
|
13977
|
-
const root =
|
|
13978
|
-
const paths =
|
|
14016
|
+
const root = findProjectRoot52(opts.dir);
|
|
14017
|
+
const paths = resolveHaivePaths48(root);
|
|
13979
14018
|
const cacheDir = path51.join(paths.haiveDir, ".cache");
|
|
13980
|
-
if (
|
|
14019
|
+
if (existsSync74(cacheDir)) {
|
|
13981
14020
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
13982
14021
|
else {
|
|
13983
14022
|
const removed = await cleanupCacheDir(cacheDir);
|
|
13984
14023
|
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13985
14024
|
}
|
|
13986
14025
|
}
|
|
13987
|
-
if (
|
|
14026
|
+
if (existsSync74(paths.runtimeDir)) {
|
|
13988
14027
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
13989
14028
|
else {
|
|
13990
14029
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -14008,8 +14047,8 @@ function registerEnforce(program2) {
|
|
|
14008
14047
|
const payload = await readHookPayload();
|
|
14009
14048
|
const root = resolveRoot(opts.dir, payload);
|
|
14010
14049
|
if (!root) return;
|
|
14011
|
-
const paths =
|
|
14012
|
-
if (!
|
|
14050
|
+
const paths = resolveHaivePaths48(root);
|
|
14051
|
+
if (!existsSync74(paths.haiveDir)) return;
|
|
14013
14052
|
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
14014
14053
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
14015
14054
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -14071,8 +14110,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14071
14110
|
const payload = await readHookPayload();
|
|
14072
14111
|
const root = resolveRoot(opts.dir, payload);
|
|
14073
14112
|
if (!root) return;
|
|
14074
|
-
const paths =
|
|
14075
|
-
if (!
|
|
14113
|
+
const paths = resolveHaivePaths48(root);
|
|
14114
|
+
if (!existsSync74(paths.haiveDir)) return;
|
|
14076
14115
|
if (!isWriteLikeTool(payload)) return;
|
|
14077
14116
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14078
14117
|
if (ok) {
|
|
@@ -14114,9 +14153,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14114
14153
|
});
|
|
14115
14154
|
}
|
|
14116
14155
|
async function buildFinishReport(dir) {
|
|
14117
|
-
const root =
|
|
14118
|
-
const paths =
|
|
14119
|
-
const initialized =
|
|
14156
|
+
const root = findProjectRoot52(dir);
|
|
14157
|
+
const paths = resolveHaivePaths48(root);
|
|
14158
|
+
const initialized = existsSync74(paths.haiveDir);
|
|
14120
14159
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14121
14160
|
const mode = config.enforcement?.mode ?? "strict";
|
|
14122
14161
|
const findings = [];
|
|
@@ -14310,9 +14349,9 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
14310
14349
|
});
|
|
14311
14350
|
}
|
|
14312
14351
|
async function runWithEnforcement(command, args, opts) {
|
|
14313
|
-
const root =
|
|
14314
|
-
const paths =
|
|
14315
|
-
if (!
|
|
14352
|
+
const root = findProjectRoot52(opts.dir);
|
|
14353
|
+
const paths = resolveHaivePaths48(root);
|
|
14354
|
+
if (!existsSync74(paths.haiveDir)) {
|
|
14316
14355
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
14317
14356
|
process.exit(1);
|
|
14318
14357
|
}
|
|
@@ -14405,9 +14444,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14405
14444
|
return file;
|
|
14406
14445
|
}
|
|
14407
14446
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14408
|
-
const root =
|
|
14409
|
-
const paths =
|
|
14410
|
-
const initialized =
|
|
14447
|
+
const root = findProjectRoot52(dir);
|
|
14448
|
+
const paths = resolveHaivePaths48(root);
|
|
14449
|
+
const initialized = existsSync74(paths.haiveDir);
|
|
14411
14450
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14412
14451
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14413
14452
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14438,7 +14477,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14438
14477
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14439
14478
|
});
|
|
14440
14479
|
}
|
|
14441
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
14480
|
+
findings.push(...await inspectIntegrationVersions(root, "0.12.0"));
|
|
14442
14481
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14443
14482
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14444
14483
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14508,7 +14547,7 @@ function withCategories(report) {
|
|
|
14508
14547
|
};
|
|
14509
14548
|
}
|
|
14510
14549
|
async function hasRecentSessionRecap(paths) {
|
|
14511
|
-
if (!
|
|
14550
|
+
if (!existsSync74(paths.memoriesDir)) return false;
|
|
14512
14551
|
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14513
14552
|
return all.some(({ memory: memory2 }) => {
|
|
14514
14553
|
const fm = memory2.frontmatter;
|
|
@@ -14517,7 +14556,7 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14517
14556
|
});
|
|
14518
14557
|
}
|
|
14519
14558
|
async function verifyMemoryPolicy(paths, config) {
|
|
14520
|
-
if (!
|
|
14559
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
14521
14560
|
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14522
14561
|
const findings = [];
|
|
14523
14562
|
const staleImportant = [];
|
|
@@ -14555,7 +14594,7 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14555
14594
|
return findings;
|
|
14556
14595
|
}
|
|
14557
14596
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14558
|
-
if (!
|
|
14597
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
14559
14598
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14560
14599
|
if (changedFiles.length === 0) {
|
|
14561
14600
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
@@ -14667,7 +14706,7 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14667
14706
|
removed++;
|
|
14668
14707
|
}
|
|
14669
14708
|
await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
14670
|
-
if (!
|
|
14709
|
+
if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
|
|
14671
14710
|
await writeFile34(
|
|
14672
14711
|
path51.join(runtimeDir, "README.md"),
|
|
14673
14712
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
@@ -14710,7 +14749,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14710
14749
|
const findings = [];
|
|
14711
14750
|
for (const rel of files) {
|
|
14712
14751
|
const file = path51.join(root, rel);
|
|
14713
|
-
if (!
|
|
14752
|
+
if (!existsSync74(file)) continue;
|
|
14714
14753
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14715
14754
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14716
14755
|
const version = versionForBinary2(bin);
|
|
@@ -14820,7 +14859,7 @@ async function resolveCiDiffRange(root) {
|
|
|
14820
14859
|
}
|
|
14821
14860
|
async function resolveGithubEventRange(root) {
|
|
14822
14861
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14823
|
-
if (!eventPath || !
|
|
14862
|
+
if (!eventPath || !existsSync74(eventPath)) return null;
|
|
14824
14863
|
try {
|
|
14825
14864
|
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
14826
14865
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -15001,7 +15040,7 @@ function buildScore(findings, threshold = 80) {
|
|
|
15001
15040
|
}
|
|
15002
15041
|
async function installGitEnforcement(root) {
|
|
15003
15042
|
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15004
|
-
if (!
|
|
15043
|
+
if (!existsSync74(path51.join(root, ".git"))) {
|
|
15005
15044
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
15006
15045
|
return;
|
|
15007
15046
|
}
|
|
@@ -15024,7 +15063,7 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
15024
15063
|
];
|
|
15025
15064
|
for (const hook of hooks) {
|
|
15026
15065
|
const file = path51.join(hooksDir, hook.name);
|
|
15027
|
-
if (
|
|
15066
|
+
if (existsSync74(file)) {
|
|
15028
15067
|
const current = await readFile23(file, "utf8").catch(() => "");
|
|
15029
15068
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
15030
15069
|
await writeFile34(file, hook.body, "utf8");
|
|
@@ -15043,7 +15082,7 @@ ${hook.body}`, "utf8");
|
|
|
15043
15082
|
async function installCiEnforcement(root) {
|
|
15044
15083
|
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15045
15084
|
await mkdir19(path51.dirname(workflowPath), { recursive: true });
|
|
15046
|
-
if (
|
|
15085
|
+
if (existsSync74(workflowPath)) {
|
|
15047
15086
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
15048
15087
|
return;
|
|
15049
15088
|
}
|
|
@@ -15134,7 +15173,7 @@ async function readHookPayload() {
|
|
|
15134
15173
|
}
|
|
15135
15174
|
function resolveRoot(dir, payload) {
|
|
15136
15175
|
try {
|
|
15137
|
-
return
|
|
15176
|
+
return findProjectRoot52(dir ?? payload.cwd);
|
|
15138
15177
|
} catch {
|
|
15139
15178
|
return null;
|
|
15140
15179
|
}
|
|
@@ -15175,7 +15214,7 @@ function normalizeToolPath(file, root) {
|
|
|
15175
15214
|
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
15176
15215
|
}
|
|
15177
15216
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
15178
|
-
if (!
|
|
15217
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
15179
15218
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
15180
15219
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
15181
15220
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
@@ -15249,16 +15288,16 @@ function registerRun(program2) {
|
|
|
15249
15288
|
|
|
15250
15289
|
// src/commands/sensors.ts
|
|
15251
15290
|
import { execFile as execFile2 } from "child_process";
|
|
15252
|
-
import { existsSync as
|
|
15291
|
+
import { existsSync as existsSync75 } from "fs";
|
|
15253
15292
|
import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
|
|
15254
15293
|
import path53 from "path";
|
|
15255
15294
|
import { promisify as promisify2 } from "util";
|
|
15256
15295
|
import "commander";
|
|
15257
15296
|
import {
|
|
15258
|
-
findProjectRoot as
|
|
15297
|
+
findProjectRoot as findProjectRoot53,
|
|
15259
15298
|
isRetiredMemory as isRetiredMemory3,
|
|
15260
15299
|
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15261
|
-
resolveHaivePaths as
|
|
15300
|
+
resolveHaivePaths as resolveHaivePaths49,
|
|
15262
15301
|
runSensors as runSensors2,
|
|
15263
15302
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15264
15303
|
serializeMemory as serializeMemory26
|
|
@@ -15267,8 +15306,8 @@ var exec2 = promisify2(execFile2);
|
|
|
15267
15306
|
function registerSensors(program2) {
|
|
15268
15307
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
15269
15308
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
15270
|
-
const root =
|
|
15271
|
-
const paths =
|
|
15309
|
+
const root = findProjectRoot53(opts.dir);
|
|
15310
|
+
const paths = resolveHaivePaths49(root);
|
|
15272
15311
|
const rows = await sensorRows(paths);
|
|
15273
15312
|
if (opts.json) {
|
|
15274
15313
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -15288,8 +15327,8 @@ function registerSensors(program2) {
|
|
|
15288
15327
|
}
|
|
15289
15328
|
});
|
|
15290
15329
|
sensors.command("check").description("Run regex sensors against a diff; defaults to `git diff --cached`").option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
15291
|
-
const root =
|
|
15292
|
-
const paths =
|
|
15330
|
+
const root = findProjectRoot53(opts.dir);
|
|
15331
|
+
const paths = resolveHaivePaths49(root);
|
|
15293
15332
|
const memories = await runnableSensorMemories(paths);
|
|
15294
15333
|
const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
15295
15334
|
const targets = sensorTargetsFromDiff2(diff);
|
|
@@ -15330,9 +15369,9 @@ function registerSensors(program2) {
|
|
|
15330
15369
|
process.exitCode = 1;
|
|
15331
15370
|
return;
|
|
15332
15371
|
}
|
|
15333
|
-
const root =
|
|
15334
|
-
const paths =
|
|
15335
|
-
const loaded =
|
|
15372
|
+
const root = findProjectRoot53(opts.dir);
|
|
15373
|
+
const paths = resolveHaivePaths49(root);
|
|
15374
|
+
const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
|
|
15336
15375
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
15337
15376
|
if (!found) {
|
|
15338
15377
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15364,8 +15403,8 @@ function registerSensors(program2) {
|
|
|
15364
15403
|
process.exitCode = 1;
|
|
15365
15404
|
return;
|
|
15366
15405
|
}
|
|
15367
|
-
const root =
|
|
15368
|
-
const paths =
|
|
15406
|
+
const root = findProjectRoot53(opts.dir);
|
|
15407
|
+
const paths = resolveHaivePaths49(root);
|
|
15369
15408
|
const rows = await sensorRows(paths);
|
|
15370
15409
|
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15371
15410
|
await mkdir20(outDir, { recursive: true });
|
|
@@ -15394,7 +15433,7 @@ async function sensorRows(paths) {
|
|
|
15394
15433
|
});
|
|
15395
15434
|
}
|
|
15396
15435
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15397
|
-
if (!
|
|
15436
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15398
15437
|
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15399
15438
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15400
15439
|
const sensor = memory2.frontmatter.sensor;
|
|
@@ -15436,8 +15475,8 @@ function shellQuote(value) {
|
|
|
15436
15475
|
}
|
|
15437
15476
|
|
|
15438
15477
|
// src/index.ts
|
|
15439
|
-
var program = new
|
|
15440
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
15478
|
+
var program = new Command56();
|
|
15479
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
15441
15480
|
registerInit(program);
|
|
15442
15481
|
registerWelcome(program);
|
|
15443
15482
|
registerResolveProject(program);
|
|
@@ -15462,6 +15501,7 @@ registerMemoryPromote(memory);
|
|
|
15462
15501
|
registerMemoryVerify(memory);
|
|
15463
15502
|
registerMemoryStats(memory);
|
|
15464
15503
|
registerMemoryImpact(memory);
|
|
15504
|
+
registerMemoryFeedback(memory);
|
|
15465
15505
|
registerMemoryReject(memory);
|
|
15466
15506
|
registerMemoryAutoPromote(memory);
|
|
15467
15507
|
registerMemoryForFiles(memory);
|