packmind 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +136 -0
- package/dist/adapters/claude-code.js +67 -0
- package/dist/bin/packmind.js +13 -0
- package/dist/cli/backup-cmd.js +41 -0
- package/dist/cli/ctx.js +14 -0
- package/dist/cli/dashboard-cmd.js +34 -0
- package/dist/cli/doctor.js +45 -0
- package/dist/cli/index-cmd.js +15 -0
- package/dist/cli/index.js +68 -0
- package/dist/cli/init.js +83 -0
- package/dist/cli/insights-cmd.js +28 -0
- package/dist/cli/locate.js +15 -0
- package/dist/cli/maintain-cmd.js +39 -0
- package/dist/cli/mcp-cmd.js +5 -0
- package/dist/cli/policy-cmd.js +40 -0
- package/dist/cli/recall-cmd.js +18 -0
- package/dist/cli/registry.js +32 -0
- package/dist/cli/scan.js +18 -0
- package/dist/cli/solutions-cmd.js +24 -0
- package/dist/cli/status.js +28 -0
- package/dist/cli/update.js +73 -0
- package/dist/cost/estimator.js +21 -0
- package/dist/cost/exact.js +35 -0
- package/dist/cost/insights.js +80 -0
- package/dist/cost/ledger.js +47 -0
- package/dist/cost/pricing.js +27 -0
- package/dist/dashboard/server.js +128 -0
- package/dist/guard/path-guard.js +26 -0
- package/dist/guard/policy.js +0 -0
- package/dist/guard/secrets.js +29 -0
- package/dist/hooks/post-read.js +80 -0
- package/dist/hooks/post-write.js +107 -0
- package/dist/hooks/pre-read.js +94 -0
- package/dist/hooks/pre-write.js +101 -0
- package/dist/hooks/prompt-submit.js +37 -0
- package/dist/hooks/runtime.js +471 -0
- package/dist/hooks/session-start.js +72 -0
- package/dist/hooks/stop.js +69 -0
- package/dist/mcp/server.js +112 -0
- package/dist/mcp/tools.js +130 -0
- package/dist/recall/chunker.js +24 -0
- package/dist/recall/embedder.js +51 -0
- package/dist/recall/indexer.js +94 -0
- package/dist/recall/queue.js +24 -0
- package/dist/recall/store.js +48 -0
- package/dist/state/describe.js +63 -0
- package/dist/state/files.js +45 -0
- package/dist/state/formats.js +80 -0
- package/dist/state/maintain.js +25 -0
- package/dist/state/mapper.js +56 -0
- package/dist/state/project.js +33 -0
- package/dist/state/schema.js +47 -0
- package/dist/state/snapshot.js +69 -0
- package/dist/state/walk.js +75 -0
- package/dist/util/fs-atomic.js +106 -0
- package/dist/util/logger.js +17 -0
- package/dist/util/paths.js +20 -0
- package/dist/util/platform.js +10 -0
- package/package.json +72 -0
- package/src/templates/PACKMIND.md +42 -0
- package/src/templates/claude-md-snippet.md +9 -0
- package/src/templates/config.json +48 -0
- package/src/templates/dashboard.html +359 -0
- package/src/templates/gitattributes +2 -0
- package/src/templates/handoff.md +3 -0
- package/src/templates/hooks-package.json +4 -0
- package/src/templates/identity.md +5 -0
- package/src/templates/journal.md +3 -0
- package/src/templates/knowledge.md +12 -0
- package/src/templates/logo-dark.svg +23 -0
- package/src/templates/logo.svg +23 -0
- package/src/templates/map.md +3 -0
- package/src/templates/policy.json +11 -0
- package/src/templates/solutions.json +1 -0
- package/src/templates/usage.json +17 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
import { URL } from "node:url";
|
|
5
|
+
import { brain } from "../state/files.js";
|
|
6
|
+
import { loadConfig } from "../state/schema.js";
|
|
7
|
+
import { readTextOr, readJsonOr } from "../util/fs-atomic.js";
|
|
8
|
+
import { parseMap } from "../state/formats.js";
|
|
9
|
+
import { readLedger, totalCost } from "../cost/ledger.js";
|
|
10
|
+
import { computeInsights } from "../cost/insights.js";
|
|
11
|
+
import { VectorStore } from "../recall/store.js";
|
|
12
|
+
import { recall } from "../recall/indexer.js";
|
|
13
|
+
import { LocalEmbedder } from "../recall/embedder.js";
|
|
14
|
+
import { TEMPLATES_DIR } from "../cli/locate.js";
|
|
15
|
+
function send(res, code, type, body) {
|
|
16
|
+
res.writeHead(code, { "content-type": type, "cache-control": "no-store" });
|
|
17
|
+
res.end(body);
|
|
18
|
+
}
|
|
19
|
+
function json(res, code, value) {
|
|
20
|
+
send(res, code, "application/json", JSON.stringify(value));
|
|
21
|
+
}
|
|
22
|
+
function overview(ctx) {
|
|
23
|
+
const b = brain(ctx.projectRoot);
|
|
24
|
+
const ledger = readLedger(ctx.projectRoot, ctx.config.model);
|
|
25
|
+
const map = parseMap(readTextOr(b.map));
|
|
26
|
+
let files = 0;
|
|
27
|
+
for (const [, list] of map)
|
|
28
|
+
files += list.length;
|
|
29
|
+
const vectors = new VectorStore(b.vectors).size();
|
|
30
|
+
return {
|
|
31
|
+
project: ctx.projectRoot.split("/").pop(),
|
|
32
|
+
model: ledger.model,
|
|
33
|
+
files,
|
|
34
|
+
vectors,
|
|
35
|
+
totals: ledger.totals,
|
|
36
|
+
cost: totalCost(ledger),
|
|
37
|
+
sessions: ledger.sessions.slice(-30),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function mapEntries(ctx) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const [section, list] of parseMap(readTextOr(brain(ctx.projectRoot).map))) {
|
|
43
|
+
for (const e of list)
|
|
44
|
+
out.push({ section, file: e.file, description: e.description, tokens: e.tokens, cost: e.cost ?? 0 });
|
|
45
|
+
}
|
|
46
|
+
return out.sort((a, b) => b.tokens - a.tokens);
|
|
47
|
+
}
|
|
48
|
+
async function handle(req, res, ctx, html) {
|
|
49
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
50
|
+
const token = url.searchParams.get("token") ?? req.headers["x-packmind-token"];
|
|
51
|
+
if (url.pathname === "/") {
|
|
52
|
+
return send(res, 200, "text/html; charset=utf-8", html.replace("__TOKEN__", ctx.token));
|
|
53
|
+
}
|
|
54
|
+
// The logo is public (no token) — favicon uses logo.svg, the header uses the
|
|
55
|
+
// dark-mode variant (light strokes) so it reads on the dark UI without a badge.
|
|
56
|
+
if (url.pathname === "/logo.svg") {
|
|
57
|
+
return send(res, 200, "image/svg+xml", readTextOr(`${TEMPLATES_DIR}/logo.svg`));
|
|
58
|
+
}
|
|
59
|
+
if (url.pathname === "/logo-dark.svg") {
|
|
60
|
+
return send(res, 200, "image/svg+xml", readTextOr(`${TEMPLATES_DIR}/logo-dark.svg`));
|
|
61
|
+
}
|
|
62
|
+
// Everything under /api requires the token.
|
|
63
|
+
if (url.pathname.startsWith("/api/")) {
|
|
64
|
+
if (token !== ctx.token)
|
|
65
|
+
return json(res, 401, { error: "unauthorized" });
|
|
66
|
+
try {
|
|
67
|
+
if (url.pathname === "/api/overview")
|
|
68
|
+
return json(res, 200, overview(ctx));
|
|
69
|
+
if (url.pathname === "/api/insights")
|
|
70
|
+
return json(res, 200, computeInsights(ctx.projectRoot, ctx.config));
|
|
71
|
+
if (url.pathname === "/api/map")
|
|
72
|
+
return json(res, 200, mapEntries(ctx));
|
|
73
|
+
if (url.pathname === "/api/solutions")
|
|
74
|
+
return json(res, 200, readJsonOr(brain(ctx.projectRoot).solutions, []));
|
|
75
|
+
if (url.pathname === "/api/journal")
|
|
76
|
+
return json(res, 200, { text: readTextOr(brain(ctx.projectRoot).journal).split(/\r?\n/).slice(-200).join("\n") });
|
|
77
|
+
if (url.pathname === "/api/knowledge")
|
|
78
|
+
return json(res, 200, { text: readTextOr(brain(ctx.projectRoot).knowledge) });
|
|
79
|
+
if (url.pathname === "/api/recall") {
|
|
80
|
+
const q = url.searchParams.get("q") ?? "";
|
|
81
|
+
if (!q.trim())
|
|
82
|
+
return json(res, 200, { hits: [] });
|
|
83
|
+
const embedder = new LocalEmbedder(ctx.config.recall.embedModel);
|
|
84
|
+
const hits = await recall(ctx.projectRoot, ctx.config, embedder, q);
|
|
85
|
+
return json(res, 200, { hits });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
return json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
send(res, 404, "text/plain", "not found");
|
|
93
|
+
}
|
|
94
|
+
function listenOnFreePort(server, start) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
let port = start;
|
|
97
|
+
const tryListen = () => {
|
|
98
|
+
server.once("error", (err) => {
|
|
99
|
+
if (err.code === "EADDRINUSE" && port < start + 50) {
|
|
100
|
+
port++;
|
|
101
|
+
tryListen();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
reject(err);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
// SECURITY: bind to loopback only — never exposed to the network.
|
|
108
|
+
server.listen(port, "127.0.0.1", () => resolve(port));
|
|
109
|
+
};
|
|
110
|
+
tryListen();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
export async function startDashboard(projectRoot, preferredPort = 7878) {
|
|
114
|
+
const config = loadConfig(brain(projectRoot).config);
|
|
115
|
+
const token = crypto.randomBytes(16).toString("hex");
|
|
116
|
+
const ctx = { projectRoot, config, token };
|
|
117
|
+
const html = fs.readFileSync(`${TEMPLATES_DIR}/dashboard.html`, "utf8");
|
|
118
|
+
const server = http.createServer((req, res) => {
|
|
119
|
+
handle(req, res, ctx, html).catch(() => send(res, 500, "text/plain", "error"));
|
|
120
|
+
});
|
|
121
|
+
const port = await listenOnFreePort(server, preferredPort);
|
|
122
|
+
return {
|
|
123
|
+
url: `http://127.0.0.1:${port}/?token=${token}`,
|
|
124
|
+
port,
|
|
125
|
+
token,
|
|
126
|
+
close: () => server.close(),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve `candidate` and confirm it stays within `root`. Returns the resolved
|
|
4
|
+
* absolute path, or null if it escapes (path traversal). Never throws.
|
|
5
|
+
*/
|
|
6
|
+
export function confineToRoot(root, candidate) {
|
|
7
|
+
const base = path.resolve(root);
|
|
8
|
+
const resolved = path.isAbsolute(candidate)
|
|
9
|
+
? path.resolve(candidate)
|
|
10
|
+
: path.resolve(base, candidate);
|
|
11
|
+
const rel = path.relative(base, resolved);
|
|
12
|
+
if (rel === "")
|
|
13
|
+
return resolved;
|
|
14
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
15
|
+
return null;
|
|
16
|
+
return resolved;
|
|
17
|
+
}
|
|
18
|
+
export function withinRoot(root, candidate) {
|
|
19
|
+
return confineToRoot(root, candidate) !== null;
|
|
20
|
+
}
|
|
21
|
+
/** Equal-path test by full resolution (avoids suffix-match false positives). */
|
|
22
|
+
export function samePath(root, a, b) {
|
|
23
|
+
const ra = confineToRoot(root, a);
|
|
24
|
+
const rb = confineToRoot(root, b);
|
|
25
|
+
return ra !== null && rb !== null && ra === rb;
|
|
26
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Files whose contents must never be ingested into PackMind's brain files, and
|
|
4
|
+
* which the write guard can optionally hard-block. Matched against the basename,
|
|
5
|
+
* case-insensitively, as simple globs (`*` and `?`).
|
|
6
|
+
*/
|
|
7
|
+
export const SECRET_GLOBS = [
|
|
8
|
+
".env", ".env.*", "*.env",
|
|
9
|
+
"*.pem", "*.key", "*.p8", "*.p12", "*.pfx", "*.ppk",
|
|
10
|
+
"*.keystore", "*.jks", "*.cer", "*.crt", "*.der",
|
|
11
|
+
"id_rsa*", "id_dsa*", "id_ecdsa*", "id_ed25519*",
|
|
12
|
+
"credentials", "credentials.*", ".netrc", ".npmrc", ".pypirc",
|
|
13
|
+
"*.secret", "*.secrets", "secrets.*", "service-account*.json",
|
|
14
|
+
"*.kdbx", "*.gpg", "*.asc",
|
|
15
|
+
];
|
|
16
|
+
function toRegExp(glob) {
|
|
17
|
+
const body = glob
|
|
18
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
19
|
+
.replace(/\*/g, ".*")
|
|
20
|
+
.replace(/\?/g, ".");
|
|
21
|
+
return new RegExp(`^${body}$`, "i");
|
|
22
|
+
}
|
|
23
|
+
const compiled = SECRET_GLOBS.map(toRegExp);
|
|
24
|
+
export function looksSecret(filePath, extraGlobs = []) {
|
|
25
|
+
const base = path.basename(filePath);
|
|
26
|
+
if (compiled.some((re) => re.test(base)))
|
|
27
|
+
return true;
|
|
28
|
+
return extraGlobs.map(toRegExp).some((re) => re.test(base));
|
|
29
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("node:path"));
|
|
37
|
+
const runtime_js_1 = require("./runtime.js");
|
|
38
|
+
function extractContent(input) {
|
|
39
|
+
const resp = input.tool_response ?? input.tool_output;
|
|
40
|
+
if (typeof resp === "string")
|
|
41
|
+
return resp;
|
|
42
|
+
if (resp && typeof resp === "object") {
|
|
43
|
+
const c = resp.content ?? resp.text ?? resp.output ?? resp.file?.content;
|
|
44
|
+
if (typeof c === "string")
|
|
45
|
+
return c;
|
|
46
|
+
if (Array.isArray(c))
|
|
47
|
+
return c.map((x) => (typeof x === "string" ? x : x?.text ?? "")).join("");
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
async function main() {
|
|
52
|
+
(0, runtime_js_1.requireState)();
|
|
53
|
+
const root = (0, runtime_js_1.projectRoot)();
|
|
54
|
+
const input = (0, runtime_js_1.parseInput)(await (0, runtime_js_1.readStdin)());
|
|
55
|
+
const filePath = input?.tool_input?.file_path;
|
|
56
|
+
if (!filePath)
|
|
57
|
+
process.exit(0);
|
|
58
|
+
const rel = path.relative(root, path.resolve(root, filePath)).split(path.sep).join("/");
|
|
59
|
+
if (rel.startsWith(".packmind/"))
|
|
60
|
+
process.exit(0);
|
|
61
|
+
const content = extractContent(input);
|
|
62
|
+
if (!content)
|
|
63
|
+
process.exit(0);
|
|
64
|
+
const session = (0, runtime_js_1.readSession)();
|
|
65
|
+
if (!session)
|
|
66
|
+
process.exit(0);
|
|
67
|
+
const rec = session.reads[rel];
|
|
68
|
+
if (!rec)
|
|
69
|
+
process.exit(0);
|
|
70
|
+
const cfg = (0, runtime_js_1.hookConfig)();
|
|
71
|
+
const tokens = (0, runtime_js_1.estimateTokens)(content, filePath);
|
|
72
|
+
const cost = (0, runtime_js_1.inputCost)(cfg.model, tokens);
|
|
73
|
+
// Account the delta vs. whatever we previously attributed to this read.
|
|
74
|
+
session.inputTokens += tokens - rec.tokens;
|
|
75
|
+
session.inputCost += cost - rec.cost;
|
|
76
|
+
rec.tokens = tokens;
|
|
77
|
+
rec.cost = cost;
|
|
78
|
+
(0, runtime_js_1.writeSession)(session);
|
|
79
|
+
}
|
|
80
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("node:fs"));
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const runtime_js_1 = require("./runtime.js");
|
|
39
|
+
function refreshMap(rel, content, model) {
|
|
40
|
+
const map = (0, runtime_js_1.parseMap)((0, runtime_js_1.readText)((0, runtime_js_1.brainPath)("map.md")));
|
|
41
|
+
const dir = path.posix.dirname(rel);
|
|
42
|
+
const section = dir === "." ? "./" : dir + "/";
|
|
43
|
+
const file = path.posix.basename(rel);
|
|
44
|
+
const tokens = (0, runtime_js_1.estimateTokens)(content, rel);
|
|
45
|
+
const entry = {
|
|
46
|
+
file,
|
|
47
|
+
description: (0, runtime_js_1.describeLite)(file, content),
|
|
48
|
+
tokens,
|
|
49
|
+
cost: (0, runtime_js_1.inputCost)(model, tokens),
|
|
50
|
+
};
|
|
51
|
+
if (!map.has(section))
|
|
52
|
+
map.set(section, []);
|
|
53
|
+
const list = map.get(section);
|
|
54
|
+
const idx = list.findIndex((e) => e.file === file);
|
|
55
|
+
if (idx >= 0) {
|
|
56
|
+
if (!entry.description && list[idx].description)
|
|
57
|
+
entry.description = list[idx].description;
|
|
58
|
+
list[idx] = entry;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
list.push(entry);
|
|
62
|
+
}
|
|
63
|
+
let count = 0;
|
|
64
|
+
for (const [, l] of map)
|
|
65
|
+
count += l.length;
|
|
66
|
+
(0, runtime_js_1.writeText)((0, runtime_js_1.brainPath)("map.md"), (0, runtime_js_1.serializeMap)(map, { fileCount: count, updated: new Date().toISOString() }));
|
|
67
|
+
return tokens;
|
|
68
|
+
}
|
|
69
|
+
async function main() {
|
|
70
|
+
(0, runtime_js_1.requireState)();
|
|
71
|
+
const root = (0, runtime_js_1.projectRoot)();
|
|
72
|
+
const cfg = (0, runtime_js_1.hookConfig)();
|
|
73
|
+
const input = (0, runtime_js_1.parseInput)(await (0, runtime_js_1.readStdin)());
|
|
74
|
+
const ti = input.tool_input ?? {};
|
|
75
|
+
const filePath = ti.file_path;
|
|
76
|
+
if (!filePath)
|
|
77
|
+
process.exit(0);
|
|
78
|
+
const abs = path.resolve(root, filePath);
|
|
79
|
+
const rel = path.relative(root, abs).split(path.sep).join("/");
|
|
80
|
+
if (rel.startsWith(".packmind/"))
|
|
81
|
+
process.exit(0);
|
|
82
|
+
if ((0, runtime_js_1.looksSecret)(path.basename(filePath), cfg.extraSecretGlobs))
|
|
83
|
+
process.exit(0);
|
|
84
|
+
let content = "";
|
|
85
|
+
try {
|
|
86
|
+
content = fs.readFileSync(abs, "utf8");
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* file may have been deleted */
|
|
90
|
+
}
|
|
91
|
+
const action = input.tool_name || "edit";
|
|
92
|
+
const tokens = content ? refreshMap(rel, content, cfg.model) : 0;
|
|
93
|
+
(0, runtime_js_1.appendLine)((0, runtime_js_1.brainPath)("journal.md"), `| ${new Date().toISOString().slice(11, 16)} | ${action} | \`${rel}\` | ~${tokens} |\n`);
|
|
94
|
+
if (cfg.recallEnabled && content)
|
|
95
|
+
(0, runtime_js_1.enqueueRecall)(rel);
|
|
96
|
+
const session = (0, runtime_js_1.readSession)() ?? (0, runtime_js_1.newSession)("s-adhoc");
|
|
97
|
+
delete session.reads[rel]; // a write invalidates the prior read-dedupe guard
|
|
98
|
+
session.writes.push({ file: rel, action, tokens, at: new Date().toISOString() });
|
|
99
|
+
session.editCounts[rel] = (session.editCounts[rel] ?? 0) + 1;
|
|
100
|
+
session.outputTokens += tokens;
|
|
101
|
+
session.outputCost += (0, runtime_js_1.outputCost)(cfg.model, tokens);
|
|
102
|
+
(0, runtime_js_1.writeSession)(session);
|
|
103
|
+
if (session.editCounts[rel] >= 4) {
|
|
104
|
+
(0, runtime_js_1.emitContext)("PostToolUse", `You've edited \`${rel}\` ${session.editCounts[rel]} times this session — consider a different approach and record the lesson via the \`remember\` tool.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const fs = __importStar(require("node:fs"));
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const runtime_js_1 = require("./runtime.js");
|
|
39
|
+
function mtime(p) {
|
|
40
|
+
try {
|
|
41
|
+
return fs.statSync(p).mtimeMs;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function main() {
|
|
48
|
+
(0, runtime_js_1.requireState)();
|
|
49
|
+
const root = (0, runtime_js_1.projectRoot)();
|
|
50
|
+
const input = (0, runtime_js_1.parseInput)(await (0, runtime_js_1.readStdin)());
|
|
51
|
+
const filePath = input?.tool_input?.file_path;
|
|
52
|
+
if (!filePath)
|
|
53
|
+
process.exit(0);
|
|
54
|
+
const abs = path.resolve(root, filePath);
|
|
55
|
+
const rel = path.relative(root, abs).split(path.sep).join("/");
|
|
56
|
+
if (rel.startsWith(".packmind/"))
|
|
57
|
+
process.exit(0);
|
|
58
|
+
const session = (0, runtime_js_1.readSession)() ?? (0, runtime_js_1.newSession)("s-adhoc");
|
|
59
|
+
const notes = [];
|
|
60
|
+
const cur = mtime(abs);
|
|
61
|
+
const prior = session.reads[rel];
|
|
62
|
+
// Re-read warning only when the file is unchanged since the last read.
|
|
63
|
+
if (prior && prior.count > 0 && cur > 0 && cur <= prior.mtime) {
|
|
64
|
+
session.dedupedReads++;
|
|
65
|
+
notes.push(`Already read \`${rel}\` this session and it's unchanged (~${prior.tokens} tok) — re-reading is usually wasteful.`);
|
|
66
|
+
}
|
|
67
|
+
// Map lookup with exact path comparison.
|
|
68
|
+
const map = (0, runtime_js_1.parseMap)((0, runtime_js_1.readText)((0, runtime_js_1.brainPath)("map.md")));
|
|
69
|
+
let described = false;
|
|
70
|
+
for (const [section, entries] of map) {
|
|
71
|
+
for (const e of entries) {
|
|
72
|
+
const entryRel = (section === "./" ? "" : section) + e.file;
|
|
73
|
+
if ((0, runtime_js_1.samePath)(root, path.resolve(root, entryRel), abs)) {
|
|
74
|
+
if (e.description)
|
|
75
|
+
notes.push(`map.md: \`${rel}\` — ${e.description} (~${e.tokens} tok)`);
|
|
76
|
+
described = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (described)
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
described ? session.mapHits++ : session.mapMisses++;
|
|
84
|
+
session.reads[rel] = {
|
|
85
|
+
count: (prior?.count ?? 0) + 1,
|
|
86
|
+
tokens: prior?.tokens ?? 0,
|
|
87
|
+
cost: prior?.cost ?? 0,
|
|
88
|
+
mtime: cur,
|
|
89
|
+
first: prior?.first ?? new Date().toISOString(),
|
|
90
|
+
};
|
|
91
|
+
(0, runtime_js_1.writeSession)(session);
|
|
92
|
+
(0, runtime_js_1.emitContext)("PreToolUse", notes.join(" "));
|
|
93
|
+
}
|
|
94
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const path = __importStar(require("node:path"));
|
|
37
|
+
const runtime_js_1 = require("./runtime.js");
|
|
38
|
+
function pendingContent(input) {
|
|
39
|
+
const ti = input.tool_input ?? {};
|
|
40
|
+
return [ti.content, ti.new_string, ti.new_str].filter((x) => typeof x === "string").join("\n");
|
|
41
|
+
}
|
|
42
|
+
async function main() {
|
|
43
|
+
(0, runtime_js_1.requireState)();
|
|
44
|
+
const root = (0, runtime_js_1.projectRoot)();
|
|
45
|
+
const input = (0, runtime_js_1.parseInput)(await (0, runtime_js_1.readStdin)());
|
|
46
|
+
const filePath = input?.tool_input?.file_path;
|
|
47
|
+
if (!filePath)
|
|
48
|
+
process.exit(0);
|
|
49
|
+
const rel = path.relative(root, path.resolve(root, filePath)).split(path.sep).join("/");
|
|
50
|
+
const content = pendingContent(input);
|
|
51
|
+
const cfg = (0, runtime_js_1.hookConfig)();
|
|
52
|
+
const policy = (0, runtime_js_1.readJson)((0, runtime_js_1.brainPath)("policy.json"), { rules: [] });
|
|
53
|
+
const { findings, block } = (0, runtime_js_1.evaluateWrite)(policy.rules, {
|
|
54
|
+
relPath: rel,
|
|
55
|
+
content,
|
|
56
|
+
blockSecrets: cfg.blockSecrets,
|
|
57
|
+
extraSecretGlobs: cfg.extraSecretGlobs,
|
|
58
|
+
});
|
|
59
|
+
if (block) {
|
|
60
|
+
const reasons = findings.filter((f) => f.severity === "block").map((f) => f.message).join(" ");
|
|
61
|
+
(0, runtime_js_1.emitDeny)(`PackMind guardrail blocked this write to \`${rel}\`: ${reasons}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const notes = findings.map((f) => `Guardrail (${f.ruleId}): ${f.message}`);
|
|
65
|
+
// Surface known solutions relevant to this file (by recorded file or by path
|
|
66
|
+
// keyword overlap with the solution's error/tags) so a past fix resurfaces
|
|
67
|
+
// before re-debugging.
|
|
68
|
+
const solutions = (0, runtime_js_1.readJson)((0, runtime_js_1.brainPath)("solutions.json"), []);
|
|
69
|
+
if (solutions.length) {
|
|
70
|
+
const pathTokens = new Set((rel.toLowerCase().match(/[a-z0-9]{4,}/g) ?? []));
|
|
71
|
+
const relevant = solutions
|
|
72
|
+
.map((s) => {
|
|
73
|
+
let score = 0;
|
|
74
|
+
if (s.file && s.file === rel)
|
|
75
|
+
score += 5;
|
|
76
|
+
const hay = `${s.error ?? ""} ${(s.tags ?? []).join(" ")}`.toLowerCase();
|
|
77
|
+
for (const w of hay.match(/[a-z0-9]{4,}/g) ?? [])
|
|
78
|
+
if (pathTokens.has(w))
|
|
79
|
+
score += 1;
|
|
80
|
+
return { s, score };
|
|
81
|
+
})
|
|
82
|
+
.filter((x) => x.score >= 2)
|
|
83
|
+
.sort((a, b) => b.score - a.score)
|
|
84
|
+
.slice(0, 2);
|
|
85
|
+
for (const { s } of relevant) {
|
|
86
|
+
notes.push(`Known solution for this area — ${s.error}${s.fix ? ` → ${s.fix}` : ""}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Surface relevant Never-Do notes from knowledge.md.
|
|
90
|
+
if (content) {
|
|
91
|
+
for (const entry of (0, runtime_js_1.parseNeverDo)((0, runtime_js_1.readText)((0, runtime_js_1.brainPath)("knowledge.md")))) {
|
|
92
|
+
const token = entry.match(/[`"']([^`"']{2,})[`"']/)?.[1];
|
|
93
|
+
if (token && new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(content)) {
|
|
94
|
+
notes.push(`knowledge.md Never-Do: "${entry}"`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (notes.length)
|
|
99
|
+
(0, runtime_js_1.emitContext)("PreToolUse", notes.join(" "));
|
|
100
|
+
}
|
|
101
|
+
main().catch(() => process.exit(0));
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const runtime_js_1 = require("./runtime.js");
|
|
4
|
+
function keywords(text) {
|
|
5
|
+
return (text.toLowerCase().match(/[a-z0-9_]{4,}/g) ?? []).slice(0, 40);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Lightweight LEXICAL recall in the hook (semantic recall lives in the MCP
|
|
9
|
+
* server). Surfaces previously-recorded solutions that share keywords with the
|
|
10
|
+
* user's prompt, so a known fix resurfaces even before Claude calls a tool.
|
|
11
|
+
*/
|
|
12
|
+
async function main() {
|
|
13
|
+
(0, runtime_js_1.requireState)();
|
|
14
|
+
const input = (0, runtime_js_1.parseInput)(await (0, runtime_js_1.readStdin)());
|
|
15
|
+
const prompt = typeof input.prompt === "string" ? input.prompt : "";
|
|
16
|
+
if (!prompt)
|
|
17
|
+
process.exit(0);
|
|
18
|
+
const kws = new Set(keywords(prompt));
|
|
19
|
+
if (kws.size === 0)
|
|
20
|
+
process.exit(0);
|
|
21
|
+
const solutions = (0, runtime_js_1.readJson)((0, runtime_js_1.brainPath)("solutions.json"), []);
|
|
22
|
+
const scored = solutions
|
|
23
|
+
.map((s) => {
|
|
24
|
+
const hay = keywords([s.error, s.cause, s.fix, ...(s.tags ?? [])].filter(Boolean).join(" "));
|
|
25
|
+
const overlap = hay.filter((w) => kws.has(w)).length;
|
|
26
|
+
return { s, overlap };
|
|
27
|
+
})
|
|
28
|
+
.filter((x) => x.overlap >= 2)
|
|
29
|
+
.sort((a, b) => b.overlap - a.overlap)
|
|
30
|
+
.slice(0, 3);
|
|
31
|
+
if (scored.length === 0)
|
|
32
|
+
process.exit(0);
|
|
33
|
+
const lines = scored.map(({ s }) => `• ${s.error ?? s.id}${s.fix ? ` → fix: ${s.fix}` : ""}`);
|
|
34
|
+
(0, runtime_js_1.emitContext)("UserPromptSubmit", "PackMind found related past solutions:\n" + lines.join("\n") +
|
|
35
|
+
"\n(Use the `recall` MCP tool for a deeper semantic search.)");
|
|
36
|
+
}
|
|
37
|
+
main().catch(() => process.exit(0));
|