@hiveai/cli 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +316 -263
- 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.1"}`;
|
|
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,
|
|
@@ -3782,6 +3782,7 @@ import {
|
|
|
3782
3782
|
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
3783
3783
|
loadUsageIndex as loadUsageIndex8,
|
|
3784
3784
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
|
|
3785
|
+
rankMemoriesLexical as rankMemoriesLexical2,
|
|
3785
3786
|
queryCodeMap as queryCodeMap2,
|
|
3786
3787
|
resolveBriefingBudget as resolveBriefingBudget2,
|
|
3787
3788
|
serializeMemory as serializeMemory9,
|
|
@@ -3822,7 +3823,10 @@ import { z as z24 } from "zod";
|
|
|
3822
3823
|
import { existsSync as existsSync24 } from "fs";
|
|
3823
3824
|
import {
|
|
3824
3825
|
addedLinesFromDiff,
|
|
3826
|
+
buildDocFrequency,
|
|
3827
|
+
CODE_STOPWORDS,
|
|
3825
3828
|
deriveConfidence as deriveConfidence6,
|
|
3829
|
+
diffHasDistinctiveOverlap,
|
|
3826
3830
|
getUsage as getUsage8,
|
|
3827
3831
|
isRetiredMemory as isRetiredMemory2,
|
|
3828
3832
|
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
@@ -5469,13 +5473,25 @@ async function getBriefing(input, ctx) {
|
|
|
5469
5473
|
}
|
|
5470
5474
|
if (act.applicable && act.activated) activatedSkills.add(id);
|
|
5471
5475
|
}
|
|
5476
|
+
const lexNorm = /* @__PURE__ */ new Map();
|
|
5477
|
+
if (input.task) {
|
|
5478
|
+
const candidates = [...seen.keys()].map((id) => byId.get(id)).filter((x) => Boolean(x));
|
|
5479
|
+
const lex = rankMemoriesLexical2(candidates, input.task, candidates.length);
|
|
5480
|
+
const maxScore = lex.scores.reduce((m, s) => s > m ? s : m, 0);
|
|
5481
|
+
if (maxScore > 0) {
|
|
5482
|
+
lex.ranked.forEach((loaded, i) => {
|
|
5483
|
+
lexNorm.set(loaded.memory.frontmatter.id, (lex.scores[i] ?? 0) / maxScore);
|
|
5484
|
+
});
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5472
5487
|
const ranked = [...seen.values()].sort((a, b) => {
|
|
5473
5488
|
const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
|
|
5474
5489
|
const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
|
|
5475
5490
|
const impactScore = (m) => (m.impact_score ?? 0) * 3;
|
|
5476
5491
|
const activationBoost = (m) => activatedSkills.has(m.id) ? 5 : 0;
|
|
5477
|
-
const
|
|
5478
|
-
const
|
|
5492
|
+
const lexScore = (m) => 12 * (lexNorm.get(m.id) ?? 0);
|
|
5493
|
+
const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + impactScore(a) + activationBoost(a) + lexScore(a) + (a.semantic_score ?? 0);
|
|
5494
|
+
const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + impactScore(b) + activationBoost(b) + lexScore(b) + (b.semantic_score ?? 0);
|
|
5479
5495
|
return sb - sa;
|
|
5480
5496
|
});
|
|
5481
5497
|
for (const mem of ranked.slice(0, briefingMaxMemories)) {
|
|
@@ -6145,53 +6161,6 @@ var AntiPatternsCheckInputSchema = {
|
|
|
6145
6161
|
"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
6162
|
)
|
|
6147
6163
|
};
|
|
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
6164
|
function tokenizeDiffForLiteral(diff) {
|
|
6196
6165
|
const lines = diff.split("\n");
|
|
6197
6166
|
const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
|
|
@@ -6224,6 +6193,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6224
6193
|
return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
|
|
6225
6194
|
}
|
|
6226
6195
|
const usage = await loadUsageIndex10(ctx.paths);
|
|
6196
|
+
const docFreq = buildDocFrequency(negative.map(({ memory: memory2 }) => memory2.body));
|
|
6227
6197
|
const seen = /* @__PURE__ */ new Map();
|
|
6228
6198
|
const upsert = (fm, body, reason, score) => {
|
|
6229
6199
|
const existing = seen.get(fm.id);
|
|
@@ -6257,10 +6227,16 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
6257
6227
|
}
|
|
6258
6228
|
if (input.diff) {
|
|
6259
6229
|
const tokens = tokenizeDiffForLiteral(input.diff);
|
|
6230
|
+
const added = addedLinesFromDiff(input.diff);
|
|
6231
|
+
const addedText = added.trim().length > 0 ? added : input.diff;
|
|
6260
6232
|
if (tokens.length > 0) {
|
|
6261
6233
|
for (const { memory: memory2 } of negative) {
|
|
6262
6234
|
if (literalMatchesAnyToken3(memory2, tokens)) {
|
|
6263
6235
|
upsert(memory2.frontmatter, memory2.body, "literal");
|
|
6236
|
+
if (diffHasDistinctiveOverlap(addedText, memory2.body, docFreq)) {
|
|
6237
|
+
const w = seen.get(memory2.frontmatter.id);
|
|
6238
|
+
if (w) w.distinctive_literal = true;
|
|
6239
|
+
}
|
|
6264
6240
|
}
|
|
6265
6241
|
}
|
|
6266
6242
|
}
|
|
@@ -6858,7 +6834,12 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
6858
6834
|
const hasSemantic = warning.reasons.includes("semantic");
|
|
6859
6835
|
const semanticScore = warning.semantic_score ?? 0;
|
|
6860
6836
|
const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
|
|
6861
|
-
if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") &&
|
|
6837
|
+
if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && // A literal overlap only corroborates a BLOCK when it is on a token *distinctive*
|
|
6838
|
+
// to this gotcha (rare in the corpus). Sharing a common domain word ("memory",
|
|
6839
|
+
// "scope", "version") — or a version-bump diff — no longer hard-blocks; it falls
|
|
6840
|
+
// through to `review` below. This kills the incidental-token false positives that
|
|
6841
|
+
// made agents work for nothing. A moderate semantic match still corroborates.
|
|
6842
|
+
(warning.distinctive_literal === true || hasSemantic && semanticScore >= 0.45)) {
|
|
6862
6843
|
if (warning.has_sensor && !warning.reasons.includes("sensor")) {
|
|
6863
6844
|
return {
|
|
6864
6845
|
...warning,
|
|
@@ -7550,7 +7531,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7550
7531
|
};
|
|
7551
7532
|
}
|
|
7552
7533
|
var SERVER_NAME = "haive";
|
|
7553
|
-
var SERVER_VERSION = "0.
|
|
7534
|
+
var SERVER_VERSION = "0.12.1";
|
|
7554
7535
|
function jsonResult(data) {
|
|
7555
7536
|
return {
|
|
7556
7537
|
content: [
|
|
@@ -9126,7 +9107,7 @@ function registerMemoryAdd(memory2) {
|
|
|
9126
9107
|
haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
|
|
9127
9108
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
9128
9109
|
`
|
|
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) => {
|
|
9110
|
+
).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
9111
|
const root = findProjectRoot13(opts.dir);
|
|
9131
9112
|
const paths = resolveHaivePaths10(root);
|
|
9132
9113
|
if (!existsSync33(paths.haiveDir)) {
|
|
@@ -9137,6 +9118,11 @@ function registerMemoryAdd(memory2) {
|
|
|
9137
9118
|
const config = await loadConfig5(paths);
|
|
9138
9119
|
const userTags = parseCsv2(opts.tags);
|
|
9139
9120
|
const anchorPaths = parseCsv2(opts.paths ?? opts.files);
|
|
9121
|
+
const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
|
|
9122
|
+
keywords: parseCsv2(opts.activationKeyword),
|
|
9123
|
+
globs: parseCsv2(opts.activationGlob),
|
|
9124
|
+
always: Boolean(opts.activationAlways)
|
|
9125
|
+
} : void 0;
|
|
9140
9126
|
const autoTagsEnabled = opts.autoTag !== false;
|
|
9141
9127
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
9142
9128
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
@@ -9194,6 +9180,7 @@ TODO \u2014 write the memory body.
|
|
|
9194
9180
|
const newFrontmatter = {
|
|
9195
9181
|
...fm,
|
|
9196
9182
|
revision_count: revisionCount,
|
|
9183
|
+
...activation ? { activation } : {},
|
|
9197
9184
|
tags: mergedTags.length ? mergedTags : fm.tags,
|
|
9198
9185
|
anchor: {
|
|
9199
9186
|
commit: opts.commit ?? fm.anchor.commit,
|
|
@@ -9224,7 +9211,8 @@ TODO \u2014 write the memory body.
|
|
|
9224
9211
|
commit: opts.commit,
|
|
9225
9212
|
topic: opts.topic,
|
|
9226
9213
|
status: config.defaultStatus === "validated" ? "validated" : void 0,
|
|
9227
|
-
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
|
|
9214
|
+
sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
|
|
9215
|
+
activation
|
|
9228
9216
|
});
|
|
9229
9217
|
const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
9230
9218
|
await mkdir11(path16.dirname(file), { recursive: true });
|
|
@@ -10556,14 +10544,68 @@ function pad(value, width) {
|
|
|
10556
10544
|
return value.slice(0, width - 1) + "\u2026";
|
|
10557
10545
|
}
|
|
10558
10546
|
|
|
10559
|
-
// src/commands/memory-
|
|
10560
|
-
import { writeFile as writeFile21 } from "fs/promises";
|
|
10547
|
+
// src/commands/memory-feedback.ts
|
|
10561
10548
|
import { existsSync as existsSync53 } from "fs";
|
|
10562
|
-
import path34 from "path";
|
|
10563
10549
|
import "commander";
|
|
10564
10550
|
import {
|
|
10551
|
+
computeImpact as computeImpact4,
|
|
10565
10552
|
findProjectRoot as findProjectRoot31,
|
|
10553
|
+
getUsage as getUsage19,
|
|
10554
|
+
loadUsageIndex as loadUsageIndex24,
|
|
10555
|
+
recordApplied as recordApplied2,
|
|
10556
|
+
recordRejection as recordRejection4,
|
|
10566
10557
|
resolveHaivePaths as resolveHaivePaths28,
|
|
10558
|
+
saveUsageIndex as saveUsageIndex6
|
|
10559
|
+
} from "@hiveai/core";
|
|
10560
|
+
function registerMemoryFeedback(memory2) {
|
|
10561
|
+
memory2.command("feedback <id>").description(
|
|
10562
|
+
"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`."
|
|
10563
|
+
).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) => {
|
|
10564
|
+
if (opts.applied === opts.rejected) {
|
|
10565
|
+
ui.error("Specify exactly one of --applied or --rejected.");
|
|
10566
|
+
process.exitCode = 1;
|
|
10567
|
+
return;
|
|
10568
|
+
}
|
|
10569
|
+
const root = findProjectRoot31(opts.dir);
|
|
10570
|
+
const paths = resolveHaivePaths28(root);
|
|
10571
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10572
|
+
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10573
|
+
process.exitCode = 1;
|
|
10574
|
+
return;
|
|
10575
|
+
}
|
|
10576
|
+
const all = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10577
|
+
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
10578
|
+
if (!target) {
|
|
10579
|
+
ui.error(`No memory with id '${id}'.`);
|
|
10580
|
+
process.exitCode = 1;
|
|
10581
|
+
return;
|
|
10582
|
+
}
|
|
10583
|
+
const index = await loadUsageIndex24(paths);
|
|
10584
|
+
const outcome = opts.applied ? "applied" : "rejected";
|
|
10585
|
+
if (opts.applied) recordApplied2(index, id);
|
|
10586
|
+
else recordRejection4(index, id, opts.reason ?? null);
|
|
10587
|
+
await saveUsageIndex6(paths, index);
|
|
10588
|
+
const usage = getUsage19(index, id);
|
|
10589
|
+
const impact = computeImpact4(target.memory.frontmatter, usage);
|
|
10590
|
+
if (opts.json) {
|
|
10591
|
+
console.log(JSON.stringify({ id, outcome, usage, impact }, null, 2));
|
|
10592
|
+
return;
|
|
10593
|
+
}
|
|
10594
|
+
ui.success(`Recorded '${outcome}' for ${id}`);
|
|
10595
|
+
ui.info(
|
|
10596
|
+
`applied=${usage.applied_count} \xB7 rejected=${usage.rejected_count} \xB7 read=${usage.read_count} \u2192 impact ${impact.score.toFixed(2)} (${impact.tier})`
|
|
10597
|
+
);
|
|
10598
|
+
});
|
|
10599
|
+
}
|
|
10600
|
+
|
|
10601
|
+
// src/commands/memory-verify.ts
|
|
10602
|
+
import { writeFile as writeFile21 } from "fs/promises";
|
|
10603
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10604
|
+
import path34 from "path";
|
|
10605
|
+
import "commander";
|
|
10606
|
+
import {
|
|
10607
|
+
findProjectRoot as findProjectRoot32,
|
|
10608
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
10567
10609
|
serializeMemory as serializeMemory19,
|
|
10568
10610
|
verifyAnchor as verifyAnchor3
|
|
10569
10611
|
} from "@hiveai/core";
|
|
@@ -10571,9 +10613,9 @@ function registerMemoryVerify(memory2) {
|
|
|
10571
10613
|
memory2.command("verify").description(
|
|
10572
10614
|
"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
10615
|
).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 (!
|
|
10616
|
+
const root = findProjectRoot32(opts.dir);
|
|
10617
|
+
const paths = resolveHaivePaths29(root);
|
|
10618
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
10577
10619
|
if (opts.json) {
|
|
10578
10620
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10579
10621
|
} else {
|
|
@@ -10694,24 +10736,24 @@ function applyVerification2(mem, result) {
|
|
|
10694
10736
|
|
|
10695
10737
|
// src/commands/memory-import.ts
|
|
10696
10738
|
import { readFile as readFile15 } from "fs/promises";
|
|
10697
|
-
import { existsSync as
|
|
10739
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10698
10740
|
import "commander";
|
|
10699
10741
|
import {
|
|
10700
|
-
findProjectRoot as
|
|
10701
|
-
resolveHaivePaths as
|
|
10742
|
+
findProjectRoot as findProjectRoot33,
|
|
10743
|
+
resolveHaivePaths as resolveHaivePaths30
|
|
10702
10744
|
} from "@hiveai/core";
|
|
10703
10745
|
function registerMemoryImport(memory2) {
|
|
10704
10746
|
memory2.command("import").description(
|
|
10705
10747
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
10706
10748
|
).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 (!
|
|
10749
|
+
const root = findProjectRoot33(opts.dir);
|
|
10750
|
+
const paths = resolveHaivePaths30(root);
|
|
10751
|
+
if (!existsSync55(paths.haiveDir)) {
|
|
10710
10752
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10711
10753
|
process.exitCode = 1;
|
|
10712
10754
|
return;
|
|
10713
10755
|
}
|
|
10714
|
-
if (!
|
|
10756
|
+
if (!existsSync55(opts.from)) {
|
|
10715
10757
|
ui.error(`File not found: ${opts.from}`);
|
|
10716
10758
|
process.exitCode = 1;
|
|
10717
10759
|
return;
|
|
@@ -10744,14 +10786,14 @@ function registerMemoryImport(memory2) {
|
|
|
10744
10786
|
}
|
|
10745
10787
|
|
|
10746
10788
|
// src/commands/memory-import-changelog.ts
|
|
10747
|
-
import { existsSync as
|
|
10789
|
+
import { existsSync as existsSync56 } from "fs";
|
|
10748
10790
|
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
|
|
10749
10791
|
import path35 from "path";
|
|
10750
10792
|
import "commander";
|
|
10751
10793
|
import {
|
|
10752
10794
|
buildFrontmatter as buildFrontmatter9,
|
|
10753
|
-
findProjectRoot as
|
|
10754
|
-
resolveHaivePaths as
|
|
10795
|
+
findProjectRoot as findProjectRoot34,
|
|
10796
|
+
resolveHaivePaths as resolveHaivePaths31,
|
|
10755
10797
|
serializeMemory as serializeMemory20
|
|
10756
10798
|
} from "@hiveai/core";
|
|
10757
10799
|
function parseChangelog(content) {
|
|
@@ -10816,10 +10858,10 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10816
10858
|
"--versions <csv>",
|
|
10817
10859
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
10818
10860
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10819
|
-
const root =
|
|
10820
|
-
const paths =
|
|
10861
|
+
const root = findProjectRoot34(opts.dir);
|
|
10862
|
+
const paths = resolveHaivePaths31(root);
|
|
10821
10863
|
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10822
|
-
if (!
|
|
10864
|
+
if (!existsSync56(changelogPath)) {
|
|
10823
10865
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10824
10866
|
process.exitCode = 1;
|
|
10825
10867
|
return;
|
|
@@ -10906,17 +10948,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10906
10948
|
}
|
|
10907
10949
|
|
|
10908
10950
|
// src/commands/memory-digest.ts
|
|
10909
|
-
import { existsSync as
|
|
10951
|
+
import { existsSync as existsSync57 } from "fs";
|
|
10910
10952
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
10911
10953
|
import path36 from "path";
|
|
10912
10954
|
import "commander";
|
|
10913
10955
|
import {
|
|
10914
10956
|
deriveConfidence as deriveConfidence12,
|
|
10915
|
-
findProjectRoot as
|
|
10916
|
-
getUsage as
|
|
10957
|
+
findProjectRoot as findProjectRoot35,
|
|
10958
|
+
getUsage as getUsage20,
|
|
10917
10959
|
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
10918
|
-
loadUsageIndex as
|
|
10919
|
-
resolveHaivePaths as
|
|
10960
|
+
loadUsageIndex as loadUsageIndex25,
|
|
10961
|
+
resolveHaivePaths as resolveHaivePaths32
|
|
10920
10962
|
} from "@hiveai/core";
|
|
10921
10963
|
var CONFIDENCE_EMOJI = {
|
|
10922
10964
|
unverified: "\u2B1C",
|
|
@@ -10929,9 +10971,9 @@ function registerMemoryDigest(program2) {
|
|
|
10929
10971
|
program2.command("digest").description(
|
|
10930
10972
|
"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
10973
|
).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 (!
|
|
10974
|
+
const root = findProjectRoot35(opts.dir);
|
|
10975
|
+
const paths = resolveHaivePaths32(root);
|
|
10976
|
+
if (!existsSync57(paths.memoriesDir)) {
|
|
10935
10977
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10936
10978
|
process.exitCode = 1;
|
|
10937
10979
|
return;
|
|
@@ -10940,7 +10982,7 @@ function registerMemoryDigest(program2) {
|
|
|
10940
10982
|
const scopeFilter = opts.scope ?? "team";
|
|
10941
10983
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
10942
10984
|
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10943
|
-
const usage = await
|
|
10985
|
+
const usage = await loadUsageIndex25(paths);
|
|
10944
10986
|
const recent = all.filter(({ memory: mem }) => {
|
|
10945
10987
|
const fm = mem.frontmatter;
|
|
10946
10988
|
if (fm.type === "session_recap") return false;
|
|
@@ -10971,7 +11013,7 @@ function registerMemoryDigest(program2) {
|
|
|
10971
11013
|
lines.push(``);
|
|
10972
11014
|
for (const { memory: mem } of mems) {
|
|
10973
11015
|
const fm = mem.frontmatter;
|
|
10974
|
-
const u =
|
|
11016
|
+
const u = getUsage20(usage, fm.id);
|
|
10975
11017
|
const confidence = deriveConfidence12(fm, u);
|
|
10976
11018
|
const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
|
|
10977
11019
|
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 +11056,21 @@ function registerMemoryDigest(program2) {
|
|
|
11014
11056
|
|
|
11015
11057
|
// src/commands/session-end.ts
|
|
11016
11058
|
import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
11017
|
-
import { existsSync as
|
|
11059
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11018
11060
|
import { spawn as spawn4 } from "child_process";
|
|
11019
11061
|
import path37 from "path";
|
|
11020
11062
|
import "commander";
|
|
11021
11063
|
import {
|
|
11022
11064
|
buildFrontmatter as buildFrontmatter10,
|
|
11023
|
-
findProjectRoot as
|
|
11065
|
+
findProjectRoot as findProjectRoot36,
|
|
11024
11066
|
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
11025
11067
|
memoryFilePath as memoryFilePath9,
|
|
11026
|
-
resolveHaivePaths as
|
|
11068
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
11027
11069
|
serializeMemory as serializeMemory21
|
|
11028
11070
|
} from "@hiveai/core";
|
|
11029
11071
|
async function buildAutoRecap(paths) {
|
|
11030
11072
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11031
|
-
if (!
|
|
11073
|
+
if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
|
|
11032
11074
|
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
11033
11075
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
11034
11076
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -11217,9 +11259,9 @@ function registerSessionEnd(session2) {
|
|
|
11217
11259
|
--next "Add integration tests for webhook signature validation"
|
|
11218
11260
|
`
|
|
11219
11261
|
).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 (!
|
|
11262
|
+
const root = findProjectRoot36(opts.dir);
|
|
11263
|
+
const paths = resolveHaivePaths33(root);
|
|
11264
|
+
if (!existsSync58(paths.haiveDir)) {
|
|
11223
11265
|
if (opts.auto || opts.quiet) return;
|
|
11224
11266
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11225
11267
|
process.exitCode = 1;
|
|
@@ -11252,7 +11294,7 @@ function registerSessionEnd(session2) {
|
|
|
11252
11294
|
});
|
|
11253
11295
|
const topic = recapTopic2(scope, opts.module);
|
|
11254
11296
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
11255
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
11297
|
+
const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
|
|
11256
11298
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
11257
11299
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
11258
11300
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -11260,10 +11302,10 @@ function registerSessionEnd(session2) {
|
|
|
11260
11302
|
const cleanupObservations = async () => {
|
|
11261
11303
|
if (!opts.auto) return;
|
|
11262
11304
|
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
11263
|
-
if (
|
|
11305
|
+
if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
|
|
11264
11306
|
});
|
|
11265
11307
|
};
|
|
11266
|
-
if (
|
|
11308
|
+
if (existsSync58(paths.memoriesDir)) {
|
|
11267
11309
|
const existing = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
11268
11310
|
const topicMatch = existing.find(
|
|
11269
11311
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -11325,15 +11367,15 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
11325
11367
|
}
|
|
11326
11368
|
|
|
11327
11369
|
// src/commands/snapshot.ts
|
|
11328
|
-
import { existsSync as
|
|
11370
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11329
11371
|
import { readdir as readdir4 } from "fs/promises";
|
|
11330
11372
|
import path38 from "path";
|
|
11331
11373
|
import "commander";
|
|
11332
11374
|
import {
|
|
11333
11375
|
diffContract,
|
|
11334
|
-
findProjectRoot as
|
|
11376
|
+
findProjectRoot as findProjectRoot37,
|
|
11335
11377
|
loadConfig as loadConfig7,
|
|
11336
|
-
resolveHaivePaths as
|
|
11378
|
+
resolveHaivePaths as resolveHaivePaths34,
|
|
11337
11379
|
snapshotContract
|
|
11338
11380
|
} from "@hiveai/core";
|
|
11339
11381
|
function registerSnapshot(program2) {
|
|
@@ -11358,16 +11400,16 @@ function registerSnapshot(program2) {
|
|
|
11358
11400
|
"--format <format>",
|
|
11359
11401
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
11360
11402
|
).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 (!
|
|
11403
|
+
const root = findProjectRoot37(opts.dir);
|
|
11404
|
+
const paths = resolveHaivePaths34(root);
|
|
11405
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
11364
11406
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
11365
11407
|
process.exitCode = 1;
|
|
11366
11408
|
return;
|
|
11367
11409
|
}
|
|
11368
11410
|
if (opts.list) {
|
|
11369
11411
|
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11370
|
-
if (!
|
|
11412
|
+
if (!existsSync59(contractsDir)) {
|
|
11371
11413
|
console.log(ui.dim("No contract snapshots found."));
|
|
11372
11414
|
return;
|
|
11373
11415
|
}
|
|
@@ -11491,16 +11533,16 @@ function detectFormat(filePath) {
|
|
|
11491
11533
|
}
|
|
11492
11534
|
|
|
11493
11535
|
// src/commands/hub.ts
|
|
11494
|
-
import { existsSync as
|
|
11536
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11495
11537
|
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
11496
11538
|
import path39 from "path";
|
|
11497
11539
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
11498
11540
|
import "commander";
|
|
11499
11541
|
import {
|
|
11500
|
-
findProjectRoot as
|
|
11542
|
+
findProjectRoot as findProjectRoot38,
|
|
11501
11543
|
loadConfig as loadConfig8,
|
|
11502
11544
|
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11503
|
-
resolveHaivePaths as
|
|
11545
|
+
resolveHaivePaths as resolveHaivePaths35,
|
|
11504
11546
|
saveConfig as saveConfig3,
|
|
11505
11547
|
serializeMemory as serializeMemory23
|
|
11506
11548
|
} from "@hiveai/core";
|
|
@@ -11582,8 +11624,8 @@ Next steps:
|
|
|
11582
11624
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
11583
11625
|
`
|
|
11584
11626
|
).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 =
|
|
11627
|
+
const root = findProjectRoot38(opts.dir);
|
|
11628
|
+
const paths = resolveHaivePaths35(root);
|
|
11587
11629
|
const config = await loadConfig8(paths);
|
|
11588
11630
|
if (!config.hubPath) {
|
|
11589
11631
|
ui.error(
|
|
@@ -11593,7 +11635,7 @@ Next steps:
|
|
|
11593
11635
|
return;
|
|
11594
11636
|
}
|
|
11595
11637
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11596
|
-
if (!
|
|
11638
|
+
if (!existsSync60(hubRoot)) {
|
|
11597
11639
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
11598
11640
|
process.exitCode = 1;
|
|
11599
11641
|
return;
|
|
@@ -11651,8 +11693,8 @@ Next steps:
|
|
|
11651
11693
|
hub.command("pull").description(
|
|
11652
11694
|
"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
11695
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11654
|
-
const root =
|
|
11655
|
-
const paths =
|
|
11696
|
+
const root = findProjectRoot38(opts.dir);
|
|
11697
|
+
const paths = resolveHaivePaths35(root);
|
|
11656
11698
|
const config = await loadConfig8(paths);
|
|
11657
11699
|
if (!config.hubPath) {
|
|
11658
11700
|
ui.error(
|
|
@@ -11663,7 +11705,7 @@ Next steps:
|
|
|
11663
11705
|
}
|
|
11664
11706
|
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11665
11707
|
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11666
|
-
if (!
|
|
11708
|
+
if (!existsSync60(hubSharedDir)) {
|
|
11667
11709
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11668
11710
|
return;
|
|
11669
11711
|
}
|
|
@@ -11710,15 +11752,15 @@ Next steps:
|
|
|
11710
11752
|
);
|
|
11711
11753
|
});
|
|
11712
11754
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11713
|
-
const root =
|
|
11714
|
-
const paths =
|
|
11755
|
+
const root = findProjectRoot38(opts.dir);
|
|
11756
|
+
const paths = resolveHaivePaths35(root);
|
|
11715
11757
|
const config = await loadConfig8(paths);
|
|
11716
11758
|
console.log(ui.bold("Hub status"));
|
|
11717
11759
|
console.log(
|
|
11718
11760
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11719
11761
|
);
|
|
11720
11762
|
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11721
|
-
if (
|
|
11763
|
+
if (existsSync60(sharedDir)) {
|
|
11722
11764
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11723
11765
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11724
11766
|
console.log(`
|
|
@@ -11747,17 +11789,17 @@ Next steps:
|
|
|
11747
11789
|
|
|
11748
11790
|
// src/commands/stats.ts
|
|
11749
11791
|
import "commander";
|
|
11750
|
-
import { existsSync as
|
|
11792
|
+
import { existsSync as existsSync61 } from "fs";
|
|
11751
11793
|
import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
|
|
11752
11794
|
import path40 from "path";
|
|
11753
11795
|
import {
|
|
11754
11796
|
aggregateUsage,
|
|
11755
|
-
findProjectRoot as
|
|
11797
|
+
findProjectRoot as findProjectRoot39,
|
|
11756
11798
|
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11757
|
-
loadUsageIndex as
|
|
11799
|
+
loadUsageIndex as loadUsageIndex26,
|
|
11758
11800
|
parseSince,
|
|
11759
11801
|
readUsageEvents as readUsageEvents2,
|
|
11760
|
-
resolveHaivePaths as
|
|
11802
|
+
resolveHaivePaths as resolveHaivePaths36,
|
|
11761
11803
|
usageLogSize
|
|
11762
11804
|
} from "@hiveai/core";
|
|
11763
11805
|
function registerStats(program2) {
|
|
@@ -11766,8 +11808,8 @@ function registerStats(program2) {
|
|
|
11766
11808
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
11767
11809
|
void 0
|
|
11768
11810
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11769
|
-
const root =
|
|
11770
|
-
const paths =
|
|
11811
|
+
const root = findProjectRoot39(opts.dir);
|
|
11812
|
+
const paths = resolveHaivePaths36(root);
|
|
11771
11813
|
if (opts.exportReport) {
|
|
11772
11814
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
11773
11815
|
return;
|
|
@@ -11825,7 +11867,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11825
11867
|
const size = await usageLogSize(paths);
|
|
11826
11868
|
let events = await readUsageEvents2(paths);
|
|
11827
11869
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11828
|
-
if (
|
|
11870
|
+
if (existsSync61(paths.memoriesDir)) {
|
|
11829
11871
|
const mems = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
11830
11872
|
for (const { memory: memory2 } of mems) {
|
|
11831
11873
|
const fm = memory2.frontmatter;
|
|
@@ -11840,7 +11882,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11840
11882
|
const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
|
|
11841
11883
|
let memoryHitsLeader = null;
|
|
11842
11884
|
try {
|
|
11843
|
-
const usageIdx = await
|
|
11885
|
+
const usageIdx = await loadUsageIndex26(paths);
|
|
11844
11886
|
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
11887
|
memoryHitsLeader = tops[0] ?? null;
|
|
11846
11888
|
} catch {
|
|
@@ -11872,7 +11914,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11872
11914
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
11873
11915
|
}
|
|
11874
11916
|
async function renderMemoryHits(paths, opts) {
|
|
11875
|
-
const index = await
|
|
11917
|
+
const index = await loadUsageIndex26(paths);
|
|
11876
11918
|
const since = parseSince(opts.since ?? "30d");
|
|
11877
11919
|
const sinceMs = since ? new Date(since).getTime() : null;
|
|
11878
11920
|
const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
|
|
@@ -11920,13 +11962,13 @@ import { performance } from "perf_hooks";
|
|
|
11920
11962
|
import "commander";
|
|
11921
11963
|
import {
|
|
11922
11964
|
estimateTokens as estimateTokens3,
|
|
11923
|
-
findProjectRoot as
|
|
11924
|
-
resolveHaivePaths as
|
|
11965
|
+
findProjectRoot as findProjectRoot40,
|
|
11966
|
+
resolveHaivePaths as resolveHaivePaths37
|
|
11925
11967
|
} from "@hiveai/core";
|
|
11926
11968
|
function registerBench(program2) {
|
|
11927
11969
|
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 =
|
|
11970
|
+
const root = findProjectRoot40(opts.dir);
|
|
11971
|
+
const paths = resolveHaivePaths37(root);
|
|
11930
11972
|
const ctx = { paths };
|
|
11931
11973
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
11932
11974
|
const scenarios = [
|
|
@@ -12045,11 +12087,11 @@ function summarize(name, t0, payload, notes) {
|
|
|
12045
12087
|
}
|
|
12046
12088
|
|
|
12047
12089
|
// src/commands/benchmark.ts
|
|
12048
|
-
import { existsSync as
|
|
12090
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12049
12091
|
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
|
|
12050
12092
|
import path41 from "path";
|
|
12051
12093
|
import "commander";
|
|
12052
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
12094
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
12053
12095
|
function registerBenchmark(program2) {
|
|
12054
12096
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
12055
12097
|
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 +12131,18 @@ function registerBenchmark(program2) {
|
|
|
12089
12131
|
function resolveBenchmarkRoot(dir) {
|
|
12090
12132
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
12091
12133
|
if (path41.isAbsolute(candidate)) return candidate;
|
|
12092
|
-
const projectRoot =
|
|
12134
|
+
const projectRoot = findProjectRoot41(process.cwd());
|
|
12093
12135
|
return path41.join(projectRoot, candidate);
|
|
12094
12136
|
}
|
|
12095
12137
|
async function collectRows(root) {
|
|
12096
|
-
if (!
|
|
12138
|
+
if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
12097
12139
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
12098
12140
|
const rows = [];
|
|
12099
12141
|
for (const entry of entries) {
|
|
12100
12142
|
if (!entry.isDirectory()) continue;
|
|
12101
12143
|
const fixtureDir = path41.join(root, entry.name);
|
|
12102
12144
|
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
12103
|
-
if (!
|
|
12145
|
+
if (!existsSync63(reportFile)) continue;
|
|
12104
12146
|
const report = await readFile19(reportFile, "utf8");
|
|
12105
12147
|
rows.push(parseAgentReport(entry.name, report));
|
|
12106
12148
|
}
|
|
@@ -12191,15 +12233,15 @@ function escapeRegExp(value) {
|
|
|
12191
12233
|
|
|
12192
12234
|
// src/commands/eval.ts
|
|
12193
12235
|
import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
|
|
12194
|
-
import { existsSync as
|
|
12236
|
+
import { existsSync as existsSync64 } from "fs";
|
|
12195
12237
|
import path43 from "path";
|
|
12196
12238
|
import "commander";
|
|
12197
12239
|
import {
|
|
12198
12240
|
aggregateRetrieval,
|
|
12199
12241
|
aggregateSensors,
|
|
12200
12242
|
buildReport,
|
|
12201
|
-
findProjectRoot as
|
|
12202
|
-
resolveHaivePaths as
|
|
12243
|
+
findProjectRoot as findProjectRoot42,
|
|
12244
|
+
resolveHaivePaths as resolveHaivePaths38,
|
|
12203
12245
|
scoreRetrievalCase,
|
|
12204
12246
|
scoreSensorCase,
|
|
12205
12247
|
synthesizeSelfEvalCases
|
|
@@ -12207,10 +12249,10 @@ import {
|
|
|
12207
12249
|
function registerEval(program2) {
|
|
12208
12250
|
program2.command("eval").description(
|
|
12209
12251
|
"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 (!
|
|
12252
|
+
).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) => {
|
|
12253
|
+
const root = findProjectRoot42(opts.dir);
|
|
12254
|
+
const paths = resolveHaivePaths38(root);
|
|
12255
|
+
if (!existsSync64(paths.memoriesDir)) {
|
|
12214
12256
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12215
12257
|
process.exitCode = 1;
|
|
12216
12258
|
return;
|
|
@@ -12243,16 +12285,26 @@ function registerEval(program2) {
|
|
|
12243
12285
|
const report = buildReport(retrievalAgg, sensorAgg);
|
|
12244
12286
|
if (opts.json) {
|
|
12245
12287
|
console.log(JSON.stringify({ root, k, report }, null, 2));
|
|
12246
|
-
|
|
12288
|
+
} else {
|
|
12289
|
+
const md = renderMarkdown2(root, k, report);
|
|
12290
|
+
if (opts.out) {
|
|
12291
|
+
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
12292
|
+
await writeFile29(outFile, md, "utf8");
|
|
12293
|
+
ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
|
|
12294
|
+
} else {
|
|
12295
|
+
console.log(md);
|
|
12296
|
+
}
|
|
12247
12297
|
}
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12298
|
+
if (opts.failUnder !== void 0) {
|
|
12299
|
+
const threshold = Number(opts.failUnder);
|
|
12300
|
+
if (Number.isNaN(threshold)) {
|
|
12301
|
+
ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
|
|
12302
|
+
process.exitCode = 1;
|
|
12303
|
+
} else if (report.score < threshold) {
|
|
12304
|
+
ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
|
|
12305
|
+
process.exitCode = 1;
|
|
12306
|
+
}
|
|
12254
12307
|
}
|
|
12255
|
-
console.log(md);
|
|
12256
12308
|
});
|
|
12257
12309
|
}
|
|
12258
12310
|
async function resolveSpec(opts, memoriesDir) {
|
|
@@ -12348,19 +12400,19 @@ function renderMarkdown2(root, k, report) {
|
|
|
12348
12400
|
|
|
12349
12401
|
// src/commands/memory-suggest.ts
|
|
12350
12402
|
import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
|
|
12351
|
-
import { existsSync as
|
|
12403
|
+
import { existsSync as existsSync65 } from "fs";
|
|
12352
12404
|
import path44 from "path";
|
|
12353
12405
|
import "commander";
|
|
12354
12406
|
import {
|
|
12355
12407
|
aggregateUsage as aggregateUsage2,
|
|
12356
12408
|
buildFrontmatter as buildFrontmatter11,
|
|
12357
|
-
findProjectRoot as
|
|
12409
|
+
findProjectRoot as findProjectRoot43,
|
|
12358
12410
|
loadConfig as loadConfig9,
|
|
12359
12411
|
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
12360
12412
|
memoryFilePath as memoryFilePath10,
|
|
12361
12413
|
parseSince as parseSince2,
|
|
12362
12414
|
readUsageEvents as readUsageEvents3,
|
|
12363
|
-
resolveHaivePaths as
|
|
12415
|
+
resolveHaivePaths as resolveHaivePaths39,
|
|
12364
12416
|
serializeMemory as serializeMemory24
|
|
12365
12417
|
} from "@hiveai/core";
|
|
12366
12418
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -12377,8 +12429,8 @@ function registerMemorySuggest(memory2) {
|
|
|
12377
12429
|
memory2.command("suggest").description(
|
|
12378
12430
|
"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
12431
|
).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 =
|
|
12432
|
+
const root = findProjectRoot43(opts.dir);
|
|
12433
|
+
const paths = resolveHaivePaths39(root);
|
|
12382
12434
|
const events = await readUsageEvents3(paths);
|
|
12383
12435
|
if (events.length === 0) {
|
|
12384
12436
|
if (opts.json) {
|
|
@@ -12427,7 +12479,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12427
12479
|
}
|
|
12428
12480
|
const created = [];
|
|
12429
12481
|
const skipped = [];
|
|
12430
|
-
const existing =
|
|
12482
|
+
const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
|
|
12431
12483
|
for (const s of top) {
|
|
12432
12484
|
const slug = slugify2(s.query);
|
|
12433
12485
|
if (!slug) {
|
|
@@ -12451,7 +12503,7 @@ function registerMemorySuggest(memory2) {
|
|
|
12451
12503
|
const body = renderTemplate(s, fm.id, status);
|
|
12452
12504
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
12453
12505
|
await mkdir18(path44.dirname(file), { recursive: true });
|
|
12454
|
-
if (
|
|
12506
|
+
if (existsSync65(file)) {
|
|
12455
12507
|
skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
|
|
12456
12508
|
continue;
|
|
12457
12509
|
}
|
|
@@ -12554,18 +12606,18 @@ function truncate2(text, max) {
|
|
|
12554
12606
|
}
|
|
12555
12607
|
|
|
12556
12608
|
// src/commands/memory-archive.ts
|
|
12557
|
-
import { existsSync as
|
|
12609
|
+
import { existsSync as existsSync66 } from "fs";
|
|
12558
12610
|
import { writeFile as writeFile31 } from "fs/promises";
|
|
12559
12611
|
import path45 from "path";
|
|
12560
12612
|
import "commander";
|
|
12561
12613
|
import {
|
|
12562
|
-
findProjectRoot as
|
|
12563
|
-
getUsage as
|
|
12614
|
+
findProjectRoot as findProjectRoot44,
|
|
12615
|
+
getUsage as getUsage21,
|
|
12564
12616
|
retirementSignal as retirementSignal2,
|
|
12565
12617
|
loadConfig as loadConfig10,
|
|
12566
12618
|
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
12567
|
-
loadUsageIndex as
|
|
12568
|
-
resolveHaivePaths as
|
|
12619
|
+
loadUsageIndex as loadUsageIndex27,
|
|
12620
|
+
resolveHaivePaths as resolveHaivePaths40,
|
|
12569
12621
|
serializeMemory as serializeMemory25
|
|
12570
12622
|
} from "@hiveai/core";
|
|
12571
12623
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
@@ -12573,9 +12625,9 @@ function registerMemoryArchive(memory2) {
|
|
|
12573
12625
|
memory2.command("archive").description(
|
|
12574
12626
|
"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
12627
|
).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 (!
|
|
12628
|
+
const root = findProjectRoot44(opts.dir);
|
|
12629
|
+
const paths = resolveHaivePaths40(root);
|
|
12630
|
+
if (!existsSync66(paths.memoriesDir)) {
|
|
12579
12631
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
12580
12632
|
process.exitCode = 1;
|
|
12581
12633
|
return;
|
|
@@ -12590,7 +12642,7 @@ function registerMemoryArchive(memory2) {
|
|
|
12590
12642
|
}
|
|
12591
12643
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
12592
12644
|
const all = await loadMemoriesFromDir33(paths.memoriesDir);
|
|
12593
|
-
const usage = await
|
|
12645
|
+
const usage = await loadUsageIndex27(paths);
|
|
12594
12646
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
12595
12647
|
const candidates = [];
|
|
12596
12648
|
for (const { memory: mem, filePath } of all) {
|
|
@@ -12600,10 +12652,10 @@ function registerMemoryArchive(memory2) {
|
|
|
12600
12652
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
12601
12653
|
const retired = retirementSignal2(fm, mem.body);
|
|
12602
12654
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
12603
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
12655
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
|
|
12604
12656
|
const isAnchorless = !hasAnyAnchor;
|
|
12605
12657
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
12606
|
-
const u =
|
|
12658
|
+
const u = getUsage21(usage, fm.id);
|
|
12607
12659
|
const lastSeen = u.last_read_at ?? fm.created_at;
|
|
12608
12660
|
if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
|
|
12609
12661
|
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 +12727,34 @@ function parseDays(input) {
|
|
|
12675
12727
|
}
|
|
12676
12728
|
|
|
12677
12729
|
// src/commands/doctor.ts
|
|
12678
|
-
import { existsSync as
|
|
12730
|
+
import { existsSync as existsSync67, statSync as statSync2 } from "fs";
|
|
12679
12731
|
import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
|
|
12680
12732
|
import path46 from "path";
|
|
12681
12733
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
12682
12734
|
import "commander";
|
|
12683
12735
|
import {
|
|
12684
12736
|
codeMapPath as codeMapPath2,
|
|
12685
|
-
findProjectRoot as
|
|
12686
|
-
getUsage as
|
|
12737
|
+
findProjectRoot as findProjectRoot45,
|
|
12738
|
+
getUsage as getUsage23,
|
|
12687
12739
|
isStackPackSeed as isStackPackSeed4,
|
|
12688
12740
|
loadCodeMap as loadCodeMap7,
|
|
12689
12741
|
loadConfig as loadConfig11,
|
|
12690
12742
|
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12691
|
-
loadUsageIndex as
|
|
12743
|
+
loadUsageIndex as loadUsageIndex28,
|
|
12692
12744
|
readUsageEvents as readUsageEvents4,
|
|
12693
|
-
resolveHaivePaths as
|
|
12745
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
12694
12746
|
} from "@hiveai/core";
|
|
12695
12747
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
12696
12748
|
function registerDoctor(program2) {
|
|
12697
12749
|
program2.command("doctor").description(
|
|
12698
12750
|
"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
12751
|
).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 =
|
|
12752
|
+
const root = findProjectRoot45(opts.dir);
|
|
12753
|
+
const paths = resolveHaivePaths41(root);
|
|
12702
12754
|
const findings = [];
|
|
12703
12755
|
const repairs = [];
|
|
12704
12756
|
const config = await loadConfig11(paths);
|
|
12705
|
-
if (!
|
|
12757
|
+
if (!existsSync67(paths.haiveDir)) {
|
|
12706
12758
|
findings.push({
|
|
12707
12759
|
severity: "error",
|
|
12708
12760
|
code: "not-initialized",
|
|
@@ -12723,7 +12775,7 @@ function registerDoctor(program2) {
|
|
|
12723
12775
|
})
|
|
12724
12776
|
);
|
|
12725
12777
|
}
|
|
12726
|
-
if (!
|
|
12778
|
+
if (!existsSync67(paths.projectContext)) {
|
|
12727
12779
|
findings.push({
|
|
12728
12780
|
severity: "warn",
|
|
12729
12781
|
code: "no-project-context",
|
|
@@ -12752,7 +12804,7 @@ function registerDoctor(program2) {
|
|
|
12752
12804
|
});
|
|
12753
12805
|
}
|
|
12754
12806
|
}
|
|
12755
|
-
const memories =
|
|
12807
|
+
const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
|
|
12756
12808
|
const now = Date.now();
|
|
12757
12809
|
if (memories.length === 0) {
|
|
12758
12810
|
findings.push({
|
|
@@ -12761,7 +12813,7 @@ function registerDoctor(program2) {
|
|
|
12761
12813
|
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
|
|
12762
12814
|
});
|
|
12763
12815
|
} else {
|
|
12764
|
-
const usage = await
|
|
12816
|
+
const usage = await loadUsageIndex28(paths);
|
|
12765
12817
|
const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
|
|
12766
12818
|
if (stale.length > 0) {
|
|
12767
12819
|
findings.push({
|
|
@@ -12818,7 +12870,7 @@ function registerDoctor(program2) {
|
|
|
12818
12870
|
}
|
|
12819
12871
|
const decayCandidates = memories.filter((m) => {
|
|
12820
12872
|
if (m.memory.frontmatter.status !== "validated") return false;
|
|
12821
|
-
const u =
|
|
12873
|
+
const u = getUsage23(usage, m.memory.frontmatter.id);
|
|
12822
12874
|
const last = u.last_read_at ?? m.memory.frontmatter.created_at;
|
|
12823
12875
|
return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
|
|
12824
12876
|
});
|
|
@@ -12904,7 +12956,7 @@ function registerDoctor(program2) {
|
|
|
12904
12956
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12905
12957
|
const claudeSettings = path46.join(root, ".claude", "settings.local.json");
|
|
12906
12958
|
let hasClaudeEnforcement = false;
|
|
12907
|
-
if (
|
|
12959
|
+
if (existsSync67(claudeSettings)) {
|
|
12908
12960
|
try {
|
|
12909
12961
|
const { readFile: readFile25 } = await import("fs/promises");
|
|
12910
12962
|
const raw = await readFile25(claudeSettings, "utf8");
|
|
@@ -12930,14 +12982,14 @@ function registerDoctor(program2) {
|
|
|
12930
12982
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12931
12983
|
});
|
|
12932
12984
|
}
|
|
12933
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
12985
|
+
findings.push(...await collectInstallFindings(root, "0.12.1"));
|
|
12934
12986
|
try {
|
|
12935
12987
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12936
12988
|
encoding: "utf8",
|
|
12937
12989
|
timeout: 3e3,
|
|
12938
12990
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12939
12991
|
}).trim();
|
|
12940
|
-
const cliVersion = "0.
|
|
12992
|
+
const cliVersion = "0.12.1";
|
|
12941
12993
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12942
12994
|
findings.push({
|
|
12943
12995
|
severity: "warn",
|
|
@@ -12959,7 +13011,7 @@ npm uninstall -g @hiveai/mcp`
|
|
|
12959
13011
|
];
|
|
12960
13012
|
const staleConfigs = [];
|
|
12961
13013
|
for (const cfgPath of configPaths) {
|
|
12962
|
-
if (!
|
|
13014
|
+
if (!existsSync67(cfgPath)) continue;
|
|
12963
13015
|
try {
|
|
12964
13016
|
const raw = await readFile21(cfgPath, "utf8");
|
|
12965
13017
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
@@ -13254,7 +13306,7 @@ which -a haive`
|
|
|
13254
13306
|
];
|
|
13255
13307
|
for (const rel of integrationFiles) {
|
|
13256
13308
|
const file = path46.join(root, rel);
|
|
13257
|
-
if (!
|
|
13309
|
+
if (!existsSync67(file)) continue;
|
|
13258
13310
|
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13259
13311
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
13260
13312
|
const version = versionForBinary(bin);
|
|
@@ -13350,7 +13402,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
13350
13402
|
}
|
|
13351
13403
|
}
|
|
13352
13404
|
async function readJson(file) {
|
|
13353
|
-
if (!
|
|
13405
|
+
if (!existsSync67(file)) return null;
|
|
13354
13406
|
try {
|
|
13355
13407
|
return JSON.parse(await readFile21(file, "utf8"));
|
|
13356
13408
|
} catch {
|
|
@@ -13397,22 +13449,22 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
13397
13449
|
}
|
|
13398
13450
|
|
|
13399
13451
|
// src/commands/playback.ts
|
|
13400
|
-
import { existsSync as
|
|
13452
|
+
import { existsSync as existsSync68 } from "fs";
|
|
13401
13453
|
import "commander";
|
|
13402
13454
|
import {
|
|
13403
|
-
findProjectRoot as
|
|
13455
|
+
findProjectRoot as findProjectRoot46,
|
|
13404
13456
|
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
13405
13457
|
parseSince as parseSince3,
|
|
13406
13458
|
readUsageEvents as readUsageEvents5,
|
|
13407
|
-
resolveHaivePaths as
|
|
13459
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
13408
13460
|
} from "@hiveai/core";
|
|
13409
13461
|
var MS_PER_MINUTE = 6e4;
|
|
13410
13462
|
function registerPlayback(program2) {
|
|
13411
13463
|
program2.command("playback").description(
|
|
13412
13464
|
"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
13465
|
).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 =
|
|
13466
|
+
const root = findProjectRoot46(opts.dir);
|
|
13467
|
+
const paths = resolveHaivePaths42(root);
|
|
13416
13468
|
const events = await readUsageEvents5(paths);
|
|
13417
13469
|
if (events.length === 0) {
|
|
13418
13470
|
if (opts.json) {
|
|
@@ -13427,7 +13479,7 @@ function registerPlayback(program2) {
|
|
|
13427
13479
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
13428
13480
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
13429
13481
|
const sessions = bucketSessions(filtered, gapMs);
|
|
13430
|
-
const all =
|
|
13482
|
+
const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
13431
13483
|
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
13484
|
const enriched = sessions.map((s, i) => {
|
|
13433
13485
|
const startMs = Date.parse(s.start);
|
|
@@ -13518,9 +13570,9 @@ import { spawn as spawn5 } from "child_process";
|
|
|
13518
13570
|
import "commander";
|
|
13519
13571
|
import {
|
|
13520
13572
|
antiPatternGateParams,
|
|
13521
|
-
findProjectRoot as
|
|
13573
|
+
findProjectRoot as findProjectRoot47,
|
|
13522
13574
|
loadConfig as loadConfig12,
|
|
13523
|
-
resolveHaivePaths as
|
|
13575
|
+
resolveHaivePaths as resolveHaivePaths43
|
|
13524
13576
|
} from "@hiveai/core";
|
|
13525
13577
|
function registerPrecommit(program2) {
|
|
13526
13578
|
program2.command("precommit").description(
|
|
@@ -13532,8 +13584,8 @@ function registerPrecommit(program2) {
|
|
|
13532
13584
|
"--no-anchored-blocks",
|
|
13533
13585
|
"do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
|
|
13534
13586
|
).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 =
|
|
13587
|
+
const root = findProjectRoot47(opts.dir);
|
|
13588
|
+
const paths = resolveHaivePaths43(root);
|
|
13537
13589
|
const ctx = { paths };
|
|
13538
13590
|
const config = await loadConfig12(paths);
|
|
13539
13591
|
const gate = config.enforcement?.antiPatternGate ?? "anchored";
|
|
@@ -13669,12 +13721,12 @@ function runCommand3(cmd, args, cwd) {
|
|
|
13669
13721
|
}
|
|
13670
13722
|
|
|
13671
13723
|
// src/commands/welcome.ts
|
|
13672
|
-
import { existsSync as
|
|
13724
|
+
import { existsSync as existsSync69 } from "fs";
|
|
13673
13725
|
import "commander";
|
|
13674
13726
|
import {
|
|
13675
|
-
findProjectRoot as
|
|
13727
|
+
findProjectRoot as findProjectRoot48,
|
|
13676
13728
|
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13677
|
-
resolveHaivePaths as
|
|
13729
|
+
resolveHaivePaths as resolveHaivePaths44
|
|
13678
13730
|
} from "@hiveai/core";
|
|
13679
13731
|
var TYPE_RANK = {
|
|
13680
13732
|
skill: 0,
|
|
@@ -13689,9 +13741,9 @@ function registerWelcome(program2) {
|
|
|
13689
13741
|
program2.command("welcome").description(
|
|
13690
13742
|
"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
13743
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13692
|
-
const root =
|
|
13693
|
-
const paths =
|
|
13694
|
-
if (!
|
|
13744
|
+
const root = findProjectRoot48(opts.dir);
|
|
13745
|
+
const paths = resolveHaivePaths44(root);
|
|
13746
|
+
if (!existsSync69(paths.memoriesDir)) {
|
|
13695
13747
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
13696
13748
|
process.exitCode = 1;
|
|
13697
13749
|
return;
|
|
@@ -13763,14 +13815,14 @@ function registerResolveProject(program2) {
|
|
|
13763
13815
|
}
|
|
13764
13816
|
|
|
13765
13817
|
// src/commands/runtime-journal.ts
|
|
13766
|
-
import { existsSync as
|
|
13818
|
+
import { existsSync as existsSync70 } from "fs";
|
|
13767
13819
|
import path48 from "path";
|
|
13768
13820
|
import "commander";
|
|
13769
13821
|
import {
|
|
13770
13822
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
13771
|
-
findProjectRoot as
|
|
13823
|
+
findProjectRoot as findProjectRoot49,
|
|
13772
13824
|
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
13773
|
-
resolveHaivePaths as
|
|
13825
|
+
resolveHaivePaths as resolveHaivePaths45
|
|
13774
13826
|
} from "@hiveai/core";
|
|
13775
13827
|
function registerRuntime(program2) {
|
|
13776
13828
|
const runtime = program2.command("runtime").description(
|
|
@@ -13779,7 +13831,7 @@ function registerRuntime(program2) {
|
|
|
13779
13831
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
13780
13832
|
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
13833
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13782
|
-
const paths =
|
|
13834
|
+
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13783
13835
|
const raw = opts.kind ?? "note";
|
|
13784
13836
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
13785
13837
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
@@ -13787,9 +13839,9 @@ function registerRuntime(program2) {
|
|
|
13787
13839
|
});
|
|
13788
13840
|
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
13841
|
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13790
|
-
const paths =
|
|
13842
|
+
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
13791
13843
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
13792
|
-
if (!
|
|
13844
|
+
if (!existsSync70(paths.haiveDir)) {
|
|
13793
13845
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
13794
13846
|
process.exitCode = 1;
|
|
13795
13847
|
return;
|
|
@@ -13804,13 +13856,13 @@ function registerRuntime(program2) {
|
|
|
13804
13856
|
}
|
|
13805
13857
|
|
|
13806
13858
|
// src/commands/memory-timeline.ts
|
|
13807
|
-
import { existsSync as
|
|
13859
|
+
import { existsSync as existsSync71 } from "fs";
|
|
13808
13860
|
import path49 from "path";
|
|
13809
13861
|
import "commander";
|
|
13810
13862
|
import {
|
|
13811
13863
|
collectTimelineEntries as collectTimelineEntries2,
|
|
13812
|
-
findProjectRoot as
|
|
13813
|
-
resolveHaivePaths as
|
|
13864
|
+
findProjectRoot as findProjectRoot50,
|
|
13865
|
+
resolveHaivePaths as resolveHaivePaths46
|
|
13814
13866
|
} from "@hiveai/core";
|
|
13815
13867
|
function registerMemoryTimeline(memory2) {
|
|
13816
13868
|
memory2.command("timeline").description(
|
|
@@ -13822,8 +13874,8 @@ function registerMemoryTimeline(memory2) {
|
|
|
13822
13874
|
return;
|
|
13823
13875
|
}
|
|
13824
13876
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13825
|
-
const paths =
|
|
13826
|
-
if (!
|
|
13877
|
+
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
13878
|
+
if (!existsSync71(paths.memoriesDir)) {
|
|
13827
13879
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13828
13880
|
process.exitCode = 1;
|
|
13829
13881
|
return;
|
|
@@ -13841,14 +13893,14 @@ function registerMemoryTimeline(memory2) {
|
|
|
13841
13893
|
}
|
|
13842
13894
|
|
|
13843
13895
|
// src/commands/memory-conflict-candidates.ts
|
|
13844
|
-
import { existsSync as
|
|
13896
|
+
import { existsSync as existsSync73 } from "fs";
|
|
13845
13897
|
import path50 from "path";
|
|
13846
13898
|
import "commander";
|
|
13847
13899
|
import {
|
|
13848
13900
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
13849
13901
|
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
13850
|
-
findProjectRoot as
|
|
13851
|
-
resolveHaivePaths as
|
|
13902
|
+
findProjectRoot as findProjectRoot51,
|
|
13903
|
+
resolveHaivePaths as resolveHaivePaths47
|
|
13852
13904
|
} from "@hiveai/core";
|
|
13853
13905
|
function parseTypes(csv) {
|
|
13854
13906
|
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
@@ -13865,8 +13917,8 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13865
13917
|
"decision,architecture"
|
|
13866
13918
|
).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
13919
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
13868
|
-
const paths =
|
|
13869
|
-
if (!
|
|
13920
|
+
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
13921
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
13870
13922
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13871
13923
|
process.exitCode = 1;
|
|
13872
13924
|
return;
|
|
@@ -13902,13 +13954,13 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13902
13954
|
|
|
13903
13955
|
// src/commands/enforce.ts
|
|
13904
13956
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
13905
|
-
import { existsSync as
|
|
13957
|
+
import { existsSync as existsSync74, statSync as statSync3 } from "fs";
|
|
13906
13958
|
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
|
|
13907
13959
|
import path51 from "path";
|
|
13908
13960
|
import "commander";
|
|
13909
13961
|
import {
|
|
13910
13962
|
antiPatternGateParams as antiPatternGateParams2,
|
|
13911
|
-
findProjectRoot as
|
|
13963
|
+
findProjectRoot as findProjectRoot52,
|
|
13912
13964
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
13913
13965
|
isFreshIsoDate,
|
|
13914
13966
|
loadConfig as loadConfig13,
|
|
@@ -13916,7 +13968,7 @@ import {
|
|
|
13916
13968
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
13917
13969
|
readRecentBriefingMarker,
|
|
13918
13970
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
13919
|
-
resolveHaivePaths as
|
|
13971
|
+
resolveHaivePaths as resolveHaivePaths48,
|
|
13920
13972
|
saveConfig as saveConfig4,
|
|
13921
13973
|
SESSION_RECAP_TTL_MS,
|
|
13922
13974
|
verifyAnchor as verifyAnchor4,
|
|
@@ -13929,8 +13981,8 @@ function registerEnforce(program2) {
|
|
|
13929
13981
|
"Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
|
|
13930
13982
|
);
|
|
13931
13983
|
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 =
|
|
13984
|
+
const root = findProjectRoot52(opts.dir);
|
|
13985
|
+
const paths = resolveHaivePaths48(root);
|
|
13934
13986
|
await mkdir19(paths.haiveDir, { recursive: true });
|
|
13935
13987
|
const current = await loadConfig13(paths);
|
|
13936
13988
|
await saveConfig4(paths, {
|
|
@@ -13974,17 +14026,17 @@ function registerEnforce(program2) {
|
|
|
13974
14026
|
if (report.should_block) process.exit(2);
|
|
13975
14027
|
});
|
|
13976
14028
|
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 =
|
|
14029
|
+
const root = findProjectRoot52(opts.dir);
|
|
14030
|
+
const paths = resolveHaivePaths48(root);
|
|
13979
14031
|
const cacheDir = path51.join(paths.haiveDir, ".cache");
|
|
13980
|
-
if (
|
|
14032
|
+
if (existsSync74(cacheDir)) {
|
|
13981
14033
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
13982
14034
|
else {
|
|
13983
14035
|
const removed = await cleanupCacheDir(cacheDir);
|
|
13984
14036
|
ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13985
14037
|
}
|
|
13986
14038
|
}
|
|
13987
|
-
if (
|
|
14039
|
+
if (existsSync74(paths.runtimeDir)) {
|
|
13988
14040
|
if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
13989
14041
|
else {
|
|
13990
14042
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -14008,8 +14060,8 @@ function registerEnforce(program2) {
|
|
|
14008
14060
|
const payload = await readHookPayload();
|
|
14009
14061
|
const root = resolveRoot(opts.dir, payload);
|
|
14010
14062
|
if (!root) return;
|
|
14011
|
-
const paths =
|
|
14012
|
-
if (!
|
|
14063
|
+
const paths = resolveHaivePaths48(root);
|
|
14064
|
+
if (!existsSync74(paths.haiveDir)) return;
|
|
14013
14065
|
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
14014
14066
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
14015
14067
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -14071,8 +14123,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14071
14123
|
const payload = await readHookPayload();
|
|
14072
14124
|
const root = resolveRoot(opts.dir, payload);
|
|
14073
14125
|
if (!root) return;
|
|
14074
|
-
const paths =
|
|
14075
|
-
if (!
|
|
14126
|
+
const paths = resolveHaivePaths48(root);
|
|
14127
|
+
if (!existsSync74(paths.haiveDir)) return;
|
|
14076
14128
|
if (!isWriteLikeTool(payload)) return;
|
|
14077
14129
|
const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
|
|
14078
14130
|
if (ok) {
|
|
@@ -14114,9 +14166,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
14114
14166
|
});
|
|
14115
14167
|
}
|
|
14116
14168
|
async function buildFinishReport(dir) {
|
|
14117
|
-
const root =
|
|
14118
|
-
const paths =
|
|
14119
|
-
const initialized =
|
|
14169
|
+
const root = findProjectRoot52(dir);
|
|
14170
|
+
const paths = resolveHaivePaths48(root);
|
|
14171
|
+
const initialized = existsSync74(paths.haiveDir);
|
|
14120
14172
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14121
14173
|
const mode = config.enforcement?.mode ?? "strict";
|
|
14122
14174
|
const findings = [];
|
|
@@ -14310,9 +14362,9 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
14310
14362
|
});
|
|
14311
14363
|
}
|
|
14312
14364
|
async function runWithEnforcement(command, args, opts) {
|
|
14313
|
-
const root =
|
|
14314
|
-
const paths =
|
|
14315
|
-
if (!
|
|
14365
|
+
const root = findProjectRoot52(opts.dir);
|
|
14366
|
+
const paths = resolveHaivePaths48(root);
|
|
14367
|
+
if (!existsSync74(paths.haiveDir)) {
|
|
14316
14368
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
14317
14369
|
process.exit(1);
|
|
14318
14370
|
}
|
|
@@ -14405,9 +14457,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
14405
14457
|
return file;
|
|
14406
14458
|
}
|
|
14407
14459
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
14408
|
-
const root =
|
|
14409
|
-
const paths =
|
|
14410
|
-
const initialized =
|
|
14460
|
+
const root = findProjectRoot52(dir);
|
|
14461
|
+
const paths = resolveHaivePaths48(root);
|
|
14462
|
+
const initialized = existsSync74(paths.haiveDir);
|
|
14411
14463
|
const config = initialized ? await loadConfig13(paths) : {};
|
|
14412
14464
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
14413
14465
|
const mode = config.enforcement?.mode ?? "strict";
|
|
@@ -14438,7 +14490,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
14438
14490
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
14439
14491
|
});
|
|
14440
14492
|
}
|
|
14441
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
14493
|
+
findings.push(...await inspectIntegrationVersions(root, "0.12.1"));
|
|
14442
14494
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
14443
14495
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
14444
14496
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -14508,7 +14560,7 @@ function withCategories(report) {
|
|
|
14508
14560
|
};
|
|
14509
14561
|
}
|
|
14510
14562
|
async function hasRecentSessionRecap(paths) {
|
|
14511
|
-
if (!
|
|
14563
|
+
if (!existsSync74(paths.memoriesDir)) return false;
|
|
14512
14564
|
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14513
14565
|
return all.some(({ memory: memory2 }) => {
|
|
14514
14566
|
const fm = memory2.frontmatter;
|
|
@@ -14517,7 +14569,7 @@ async function hasRecentSessionRecap(paths) {
|
|
|
14517
14569
|
});
|
|
14518
14570
|
}
|
|
14519
14571
|
async function verifyMemoryPolicy(paths, config) {
|
|
14520
|
-
if (!
|
|
14572
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
14521
14573
|
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
14522
14574
|
const findings = [];
|
|
14523
14575
|
const staleImportant = [];
|
|
@@ -14555,7 +14607,7 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
14555
14607
|
return findings;
|
|
14556
14608
|
}
|
|
14557
14609
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
14558
|
-
if (!
|
|
14610
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
14559
14611
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
14560
14612
|
if (changedFiles.length === 0) {
|
|
14561
14613
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
@@ -14667,7 +14719,7 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
14667
14719
|
removed++;
|
|
14668
14720
|
}
|
|
14669
14721
|
await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
14670
|
-
if (!
|
|
14722
|
+
if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
|
|
14671
14723
|
await writeFile34(
|
|
14672
14724
|
path51.join(runtimeDir, "README.md"),
|
|
14673
14725
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
@@ -14710,7 +14762,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
14710
14762
|
const findings = [];
|
|
14711
14763
|
for (const rel of files) {
|
|
14712
14764
|
const file = path51.join(root, rel);
|
|
14713
|
-
if (!
|
|
14765
|
+
if (!existsSync74(file)) continue;
|
|
14714
14766
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14715
14767
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
14716
14768
|
const version = versionForBinary2(bin);
|
|
@@ -14820,7 +14872,7 @@ async function resolveCiDiffRange(root) {
|
|
|
14820
14872
|
}
|
|
14821
14873
|
async function resolveGithubEventRange(root) {
|
|
14822
14874
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
14823
|
-
if (!eventPath || !
|
|
14875
|
+
if (!eventPath || !existsSync74(eventPath)) return null;
|
|
14824
14876
|
try {
|
|
14825
14877
|
const event = JSON.parse(await readFile23(eventPath, "utf8"));
|
|
14826
14878
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -15001,7 +15053,7 @@ function buildScore(findings, threshold = 80) {
|
|
|
15001
15053
|
}
|
|
15002
15054
|
async function installGitEnforcement(root) {
|
|
15003
15055
|
const hooksDir = path51.join(root, ".git", "hooks");
|
|
15004
|
-
if (!
|
|
15056
|
+
if (!existsSync74(path51.join(root, ".git"))) {
|
|
15005
15057
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
15006
15058
|
return;
|
|
15007
15059
|
}
|
|
@@ -15024,7 +15076,7 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
15024
15076
|
];
|
|
15025
15077
|
for (const hook of hooks) {
|
|
15026
15078
|
const file = path51.join(hooksDir, hook.name);
|
|
15027
|
-
if (
|
|
15079
|
+
if (existsSync74(file)) {
|
|
15028
15080
|
const current = await readFile23(file, "utf8").catch(() => "");
|
|
15029
15081
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
15030
15082
|
await writeFile34(file, hook.body, "utf8");
|
|
@@ -15043,7 +15095,7 @@ ${hook.body}`, "utf8");
|
|
|
15043
15095
|
async function installCiEnforcement(root) {
|
|
15044
15096
|
const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
15045
15097
|
await mkdir19(path51.dirname(workflowPath), { recursive: true });
|
|
15046
|
-
if (
|
|
15098
|
+
if (existsSync74(workflowPath)) {
|
|
15047
15099
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
15048
15100
|
return;
|
|
15049
15101
|
}
|
|
@@ -15134,7 +15186,7 @@ async function readHookPayload() {
|
|
|
15134
15186
|
}
|
|
15135
15187
|
function resolveRoot(dir, payload) {
|
|
15136
15188
|
try {
|
|
15137
|
-
return
|
|
15189
|
+
return findProjectRoot52(dir ?? payload.cwd);
|
|
15138
15190
|
} catch {
|
|
15139
15191
|
return null;
|
|
15140
15192
|
}
|
|
@@ -15175,7 +15227,7 @@ function normalizeToolPath(file, root) {
|
|
|
15175
15227
|
return path51.relative(root, normalized).replace(/\\/g, "/");
|
|
15176
15228
|
}
|
|
15177
15229
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
15178
|
-
if (!
|
|
15230
|
+
if (!existsSync74(paths.memoriesDir)) return [];
|
|
15179
15231
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
15180
15232
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
15181
15233
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
@@ -15249,16 +15301,16 @@ function registerRun(program2) {
|
|
|
15249
15301
|
|
|
15250
15302
|
// src/commands/sensors.ts
|
|
15251
15303
|
import { execFile as execFile2 } from "child_process";
|
|
15252
|
-
import { existsSync as
|
|
15304
|
+
import { existsSync as existsSync75 } from "fs";
|
|
15253
15305
|
import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
|
|
15254
15306
|
import path53 from "path";
|
|
15255
15307
|
import { promisify as promisify2 } from "util";
|
|
15256
15308
|
import "commander";
|
|
15257
15309
|
import {
|
|
15258
|
-
findProjectRoot as
|
|
15310
|
+
findProjectRoot as findProjectRoot53,
|
|
15259
15311
|
isRetiredMemory as isRetiredMemory3,
|
|
15260
15312
|
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15261
|
-
resolveHaivePaths as
|
|
15313
|
+
resolveHaivePaths as resolveHaivePaths49,
|
|
15262
15314
|
runSensors as runSensors2,
|
|
15263
15315
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15264
15316
|
serializeMemory as serializeMemory26
|
|
@@ -15267,8 +15319,8 @@ var exec2 = promisify2(execFile2);
|
|
|
15267
15319
|
function registerSensors(program2) {
|
|
15268
15320
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
15269
15321
|
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 =
|
|
15322
|
+
const root = findProjectRoot53(opts.dir);
|
|
15323
|
+
const paths = resolveHaivePaths49(root);
|
|
15272
15324
|
const rows = await sensorRows(paths);
|
|
15273
15325
|
if (opts.json) {
|
|
15274
15326
|
console.log(JSON.stringify(rows, null, 2));
|
|
@@ -15288,8 +15340,8 @@ function registerSensors(program2) {
|
|
|
15288
15340
|
}
|
|
15289
15341
|
});
|
|
15290
15342
|
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 =
|
|
15343
|
+
const root = findProjectRoot53(opts.dir);
|
|
15344
|
+
const paths = resolveHaivePaths49(root);
|
|
15293
15345
|
const memories = await runnableSensorMemories(paths);
|
|
15294
15346
|
const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
15295
15347
|
const targets = sensorTargetsFromDiff2(diff);
|
|
@@ -15330,9 +15382,9 @@ function registerSensors(program2) {
|
|
|
15330
15382
|
process.exitCode = 1;
|
|
15331
15383
|
return;
|
|
15332
15384
|
}
|
|
15333
|
-
const root =
|
|
15334
|
-
const paths =
|
|
15335
|
-
const loaded =
|
|
15385
|
+
const root = findProjectRoot53(opts.dir);
|
|
15386
|
+
const paths = resolveHaivePaths49(root);
|
|
15387
|
+
const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
|
|
15336
15388
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
15337
15389
|
if (!found) {
|
|
15338
15390
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -15364,8 +15416,8 @@ function registerSensors(program2) {
|
|
|
15364
15416
|
process.exitCode = 1;
|
|
15365
15417
|
return;
|
|
15366
15418
|
}
|
|
15367
|
-
const root =
|
|
15368
|
-
const paths =
|
|
15419
|
+
const root = findProjectRoot53(opts.dir);
|
|
15420
|
+
const paths = resolveHaivePaths49(root);
|
|
15369
15421
|
const rows = await sensorRows(paths);
|
|
15370
15422
|
const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
|
|
15371
15423
|
await mkdir20(outDir, { recursive: true });
|
|
@@ -15394,7 +15446,7 @@ async function sensorRows(paths) {
|
|
|
15394
15446
|
});
|
|
15395
15447
|
}
|
|
15396
15448
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
15397
|
-
if (!
|
|
15449
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
15398
15450
|
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
15399
15451
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
15400
15452
|
const sensor = memory2.frontmatter.sensor;
|
|
@@ -15436,8 +15488,8 @@ function shellQuote(value) {
|
|
|
15436
15488
|
}
|
|
15437
15489
|
|
|
15438
15490
|
// 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.
|
|
15491
|
+
var program = new Command56();
|
|
15492
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.1").option("--advanced", "show maintenance and experimental commands in help");
|
|
15441
15493
|
registerInit(program);
|
|
15442
15494
|
registerWelcome(program);
|
|
15443
15495
|
registerResolveProject(program);
|
|
@@ -15462,6 +15514,7 @@ registerMemoryPromote(memory);
|
|
|
15462
15514
|
registerMemoryVerify(memory);
|
|
15463
15515
|
registerMemoryStats(memory);
|
|
15464
15516
|
registerMemoryImpact(memory);
|
|
15517
|
+
registerMemoryFeedback(memory);
|
|
15465
15518
|
registerMemoryReject(memory);
|
|
15466
15519
|
registerMemoryAutoPromote(memory);
|
|
15467
15520
|
registerMemoryForFiles(memory);
|