@yurtsever/capsa 0.1.0-alpha.4 → 0.1.0-alpha.5
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 +191 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7228,6 +7228,9 @@ var DEFAULT_EXCLUDES = [
|
|
|
7228
7228
|
".Trash"
|
|
7229
7229
|
];
|
|
7230
7230
|
var DEFAULT_MAX_DEPTH = 12;
|
|
7231
|
+
function isSourceAdapter(adapter) {
|
|
7232
|
+
return typeof adapter.collect === "function";
|
|
7233
|
+
}
|
|
7231
7234
|
function stableId(absPath) {
|
|
7232
7235
|
return createHash("sha1").update(absPath).digest("hex").slice(0, 12);
|
|
7233
7236
|
}
|
|
@@ -7246,16 +7249,24 @@ async function scan(adapters, options = {}) {
|
|
|
7246
7249
|
const items = [];
|
|
7247
7250
|
const seenItemPaths = /* @__PURE__ */ new Set();
|
|
7248
7251
|
const scannedRoots = [];
|
|
7252
|
+
const formatAdapters = adapters.filter((a) => !isSourceAdapter(a));
|
|
7253
|
+
const sourceAdapters = adapters.filter(isSourceAdapter);
|
|
7249
7254
|
const rootSet = /* @__PURE__ */ new Set();
|
|
7250
7255
|
for (const r of options.roots ?? [env.homeDir])
|
|
7251
7256
|
rootSet.add(resolve(r));
|
|
7252
|
-
for (const adapter of
|
|
7257
|
+
for (const adapter of formatAdapters) {
|
|
7253
7258
|
for (const r of adapter.globalRoots?.(env) ?? [])
|
|
7254
7259
|
rootSet.add(resolve(r));
|
|
7255
7260
|
}
|
|
7256
7261
|
const roots = [...rootSet].filter((r) => ![...rootSet].some((other) => other !== r && r.startsWith(other + "/")));
|
|
7262
|
+
const addItem = (item) => {
|
|
7263
|
+
if (seenItemPaths.has(item.path))
|
|
7264
|
+
return;
|
|
7265
|
+
seenItemPaths.add(item.path);
|
|
7266
|
+
items.push(item);
|
|
7267
|
+
};
|
|
7257
7268
|
const handleEntry = async (entry) => {
|
|
7258
|
-
for (const adapter of
|
|
7269
|
+
for (const adapter of formatAdapters) {
|
|
7259
7270
|
let matched = false;
|
|
7260
7271
|
try {
|
|
7261
7272
|
matched = adapter.matches(entry);
|
|
@@ -7270,12 +7281,8 @@ async function scan(adapters, options = {}) {
|
|
|
7270
7281
|
if (!matched)
|
|
7271
7282
|
continue;
|
|
7272
7283
|
try {
|
|
7273
|
-
for (const item of await adapter.parse(entry, env))
|
|
7274
|
-
|
|
7275
|
-
continue;
|
|
7276
|
-
seenItemPaths.add(item.path);
|
|
7277
|
-
items.push(item);
|
|
7278
|
-
}
|
|
7284
|
+
for (const item of await adapter.parse(entry, env))
|
|
7285
|
+
addItem(item);
|
|
7279
7286
|
} catch (err) {
|
|
7280
7287
|
issues.push({
|
|
7281
7288
|
path: entry.absPath,
|
|
@@ -7346,6 +7353,18 @@ async function scan(adapters, options = {}) {
|
|
|
7346
7353
|
}
|
|
7347
7354
|
scannedRoots.push(root);
|
|
7348
7355
|
await walk(root, root, 0);
|
|
7356
|
+
for (const adapter of sourceAdapters) {
|
|
7357
|
+
try {
|
|
7358
|
+
for (const item of await adapter.collect(root, env))
|
|
7359
|
+
addItem(item);
|
|
7360
|
+
} catch (err) {
|
|
7361
|
+
issues.push({
|
|
7362
|
+
path: root,
|
|
7363
|
+
adapter: adapter.id,
|
|
7364
|
+
message: `collect() threw: ${String(err)}`
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7349
7368
|
}
|
|
7350
7369
|
return { items, issues, scannedRoots, durationMs: Date.now() - started };
|
|
7351
7370
|
}
|
|
@@ -10250,9 +10269,164 @@ var Store = class {
|
|
|
10250
10269
|
import { createHash as createHash2 } from "crypto";
|
|
10251
10270
|
import { relative as relative5 } from "path";
|
|
10252
10271
|
|
|
10272
|
+
// ../adapter-git-log/dist/index.js
|
|
10273
|
+
import { execFile } from "child_process";
|
|
10274
|
+
import { existsSync as existsSync2 } from "fs";
|
|
10275
|
+
import { basename as basename4, join as join7 } from "path";
|
|
10276
|
+
import { promisify } from "util";
|
|
10277
|
+
var execFileAsync = promisify(execFile);
|
|
10278
|
+
var DEFAULTS = {
|
|
10279
|
+
limit: 200,
|
|
10280
|
+
maxFiles: 50,
|
|
10281
|
+
maxBodyChars: 4e3,
|
|
10282
|
+
includeMerges: false
|
|
10283
|
+
};
|
|
10284
|
+
var RECORD = "";
|
|
10285
|
+
var UNIT = "";
|
|
10286
|
+
var FORMAT = `${RECORD}%H${UNIT}%h${UNIT}%an${UNIT}%cI${UNIT}%s${UNIT}%b${UNIT}`;
|
|
10287
|
+
var ITEM_DIR = join7(".git", "commits");
|
|
10288
|
+
function gitLogAdapter(options = {}) {
|
|
10289
|
+
const opts = { ...DEFAULTS, ...options };
|
|
10290
|
+
return {
|
|
10291
|
+
id: "git-log",
|
|
10292
|
+
displayName: "Commit",
|
|
10293
|
+
async collect(root, _env) {
|
|
10294
|
+
if (!existsSync2(join7(root, ".git")))
|
|
10295
|
+
return [];
|
|
10296
|
+
const stdout = await runGitLog(root, opts);
|
|
10297
|
+
const items = [];
|
|
10298
|
+
for (const record2 of stdout.split(RECORD)) {
|
|
10299
|
+
const commit = parseRecord(record2);
|
|
10300
|
+
if (commit)
|
|
10301
|
+
items.push(toItem(root, commit, opts));
|
|
10302
|
+
}
|
|
10303
|
+
return items;
|
|
10304
|
+
}
|
|
10305
|
+
};
|
|
10306
|
+
}
|
|
10307
|
+
async function runGitLog(root, opts) {
|
|
10308
|
+
const args = [
|
|
10309
|
+
"--no-pager",
|
|
10310
|
+
"log",
|
|
10311
|
+
`--max-count=${opts.limit}`,
|
|
10312
|
+
"--name-only",
|
|
10313
|
+
"--no-color",
|
|
10314
|
+
`--format=${FORMAT}`
|
|
10315
|
+
];
|
|
10316
|
+
if (!opts.includeMerges)
|
|
10317
|
+
args.push("--no-merges");
|
|
10318
|
+
try {
|
|
10319
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
10320
|
+
cwd: root,
|
|
10321
|
+
timeout: 3e4,
|
|
10322
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
10323
|
+
windowsHide: true
|
|
10324
|
+
});
|
|
10325
|
+
return stdout;
|
|
10326
|
+
} catch (err) {
|
|
10327
|
+
const e = err;
|
|
10328
|
+
if (/does not have any commits yet|bad default revision/i.test(String(e.stderr ?? ""))) {
|
|
10329
|
+
return "";
|
|
10330
|
+
}
|
|
10331
|
+
if (e.code === "ENOENT") {
|
|
10332
|
+
throw new Error(`${root} has a .git but git is not on PATH; no commits indexed`);
|
|
10333
|
+
}
|
|
10334
|
+
throw new Error(`git log failed in ${root}: ${firstLine2(e.stderr) || String(err)}`);
|
|
10335
|
+
}
|
|
10336
|
+
}
|
|
10337
|
+
function parseRecord(record2) {
|
|
10338
|
+
if (!record2.trim())
|
|
10339
|
+
return void 0;
|
|
10340
|
+
const parts = record2.split(UNIT);
|
|
10341
|
+
const [sha, shortSha, author, date5, subject, body] = parts;
|
|
10342
|
+
if (!sha || !shortSha)
|
|
10343
|
+
return void 0;
|
|
10344
|
+
return {
|
|
10345
|
+
sha,
|
|
10346
|
+
shortSha,
|
|
10347
|
+
author: author ?? "",
|
|
10348
|
+
date: date5 ?? "",
|
|
10349
|
+
subject: subject ?? "",
|
|
10350
|
+
body: (body ?? "").trim(),
|
|
10351
|
+
// Everything past the last field is the file list, one path per line.
|
|
10352
|
+
files: parts.slice(6).join(UNIT).split("\n").map((l) => l.trim()).filter(Boolean)
|
|
10353
|
+
};
|
|
10354
|
+
}
|
|
10355
|
+
function toItem(root, c, opts) {
|
|
10356
|
+
const path = join7(root, ITEM_DIR, `${c.shortSha}.md`);
|
|
10357
|
+
const files = c.files.slice(0, opts.maxFiles);
|
|
10358
|
+
const hidden = c.files.length - files.length;
|
|
10359
|
+
const subject = c.subject || "(no subject)";
|
|
10360
|
+
return {
|
|
10361
|
+
id: stableId(path),
|
|
10362
|
+
format: "git-log",
|
|
10363
|
+
formatLabel: "Commit",
|
|
10364
|
+
name: `${c.shortSha} ${subject}`,
|
|
10365
|
+
// The description becomes the provenance line of every embedded chunk,
|
|
10366
|
+
// so it carries the subject, not the sha.
|
|
10367
|
+
description: subject,
|
|
10368
|
+
path,
|
|
10369
|
+
scope: "project",
|
|
10370
|
+
projectRoot: root,
|
|
10371
|
+
content: renderCommit(c, subject, files, hidden, opts.maxBodyChars),
|
|
10372
|
+
metadata: {
|
|
10373
|
+
sha: c.sha,
|
|
10374
|
+
shortSha: c.shortSha,
|
|
10375
|
+
author: c.author,
|
|
10376
|
+
subject,
|
|
10377
|
+
files: c.files,
|
|
10378
|
+
repo: basename4(root)
|
|
10379
|
+
},
|
|
10380
|
+
knowledge: {
|
|
10381
|
+
kind: "commit",
|
|
10382
|
+
updatedAt: isoDate(c.date),
|
|
10383
|
+
relatedPaths: c.files.length ? c.files : void 0,
|
|
10384
|
+
tags: conventionalTags(subject)
|
|
10385
|
+
}
|
|
10386
|
+
};
|
|
10387
|
+
}
|
|
10388
|
+
function renderCommit(c, subject, files, hidden, maxBodyChars) {
|
|
10389
|
+
const body = c.body.length > maxBodyChars ? `${c.body.slice(0, maxBodyChars)}
|
|
10390
|
+
|
|
10391
|
+
\u2026` : c.body;
|
|
10392
|
+
const lines = [
|
|
10393
|
+
`# ${subject}`,
|
|
10394
|
+
"",
|
|
10395
|
+
`commit \`${c.shortSha}\` \xB7 ${c.author} \xB7 ${c.date.slice(0, 10)}`
|
|
10396
|
+
];
|
|
10397
|
+
if (body)
|
|
10398
|
+
lines.push("", body);
|
|
10399
|
+
if (files.length) {
|
|
10400
|
+
lines.push("", "## Files", "");
|
|
10401
|
+
for (const f of files)
|
|
10402
|
+
lines.push(`- ${f}`);
|
|
10403
|
+
if (hidden > 0)
|
|
10404
|
+
lines.push(`- \u2026 and ${hidden} more`);
|
|
10405
|
+
}
|
|
10406
|
+
return `${lines.join("\n")}
|
|
10407
|
+
`;
|
|
10408
|
+
}
|
|
10409
|
+
function isoDate(raw2) {
|
|
10410
|
+
if (!raw2)
|
|
10411
|
+
return void 0;
|
|
10412
|
+
const t = Date.parse(raw2);
|
|
10413
|
+
return Number.isNaN(t) ? void 0 : new Date(t).toISOString();
|
|
10414
|
+
}
|
|
10415
|
+
var CONVENTIONAL = /^([a-z]{3,10})(?:\(([^)]{1,40})\))?!?:\s/;
|
|
10416
|
+
function conventionalTags(subject) {
|
|
10417
|
+
const m = CONVENTIONAL.exec(subject.toLowerCase());
|
|
10418
|
+
if (!m)
|
|
10419
|
+
return void 0;
|
|
10420
|
+
const tags = [m[1], ...m[2] ? [m[2]] : []];
|
|
10421
|
+
return tags;
|
|
10422
|
+
}
|
|
10423
|
+
function firstLine2(text) {
|
|
10424
|
+
return (text ?? "").split("\n").find((l) => l.trim()) ?? "";
|
|
10425
|
+
}
|
|
10426
|
+
|
|
10253
10427
|
// ../adapter-runbooks/dist/index.js
|
|
10254
10428
|
import { readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
10255
|
-
import { basename as
|
|
10429
|
+
import { basename as basename5, dirname as dirname7, relative as relative3, sep as sep4 } from "path";
|
|
10256
10430
|
var DOC_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "runbooks", "runbook", "wiki", "adr", "decisions"]);
|
|
10257
10431
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "todo", "backlog", "plans", "plan", "roadmap", "milestones", "milestone", "node_modules"]);
|
|
10258
10432
|
function segments(relPath) {
|
|
@@ -10283,7 +10457,7 @@ function runbooksAdapter() {
|
|
|
10283
10457
|
const relPathInProject = relative3(projectRoot, entry.absPath);
|
|
10284
10458
|
const fm = parseFrontmatter(raw2);
|
|
10285
10459
|
const data = fm.data;
|
|
10286
|
-
const title = data["title"] || firstHeading(raw2) ||
|
|
10460
|
+
const title = data["title"] || firstHeading(raw2) || basename5(entry.name, ".md");
|
|
10287
10461
|
const kindFromPath = segments(relPathInProject).some((p) => ["adr", "decisions"].includes(p.toLowerCase())) ? "decision" : "runbook";
|
|
10288
10462
|
const tags = parseTags(data["tags"]);
|
|
10289
10463
|
return [
|
|
@@ -10291,7 +10465,7 @@ function runbooksAdapter() {
|
|
|
10291
10465
|
id: stableId(entry.absPath),
|
|
10292
10466
|
format: "runbooks",
|
|
10293
10467
|
formatLabel: "Runbook",
|
|
10294
|
-
name: `${
|
|
10468
|
+
name: `${basename5(projectRoot)}${sep4}${relPathInProject}`,
|
|
10295
10469
|
description: title,
|
|
10296
10470
|
path: entry.absPath,
|
|
10297
10471
|
scope: "project",
|
|
@@ -10325,7 +10499,7 @@ function firstHeading(raw2) {
|
|
|
10325
10499
|
|
|
10326
10500
|
// ../adapter-tickets/dist/index.js
|
|
10327
10501
|
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
10328
|
-
import { basename as
|
|
10502
|
+
import { basename as basename6, dirname as dirname8, relative as relative4, sep as sep5 } from "path";
|
|
10329
10503
|
var TICKET_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "todo", "backlog"]);
|
|
10330
10504
|
var PLAN_DIRS = /* @__PURE__ */ new Set(["plans", "plan", "roadmap", "milestones", "milestone"]);
|
|
10331
10505
|
var CHECKBOX = /^\s*[-*+]\s+\[( |x|X)\]\s+/gm;
|
|
@@ -10386,14 +10560,14 @@ function ticketsAdapter() {
|
|
|
10386
10560
|
const data = fm.data;
|
|
10387
10561
|
const derived2 = deriveStatus(raw2);
|
|
10388
10562
|
const status = normaliseStatus(data["status"]) ?? derived2.status;
|
|
10389
|
-
const title = data["title"] || firstHeading2(raw2) ||
|
|
10563
|
+
const title = data["title"] || firstHeading2(raw2) || basename6(entry.name, ".md");
|
|
10390
10564
|
const tags = parseTags2(data["tags"]);
|
|
10391
10565
|
return [
|
|
10392
10566
|
{
|
|
10393
10567
|
id: stableId(entry.absPath),
|
|
10394
10568
|
format: "tickets",
|
|
10395
10569
|
formatLabel: isPlan ? "Plan" : "Ticket",
|
|
10396
|
-
name: `${
|
|
10570
|
+
name: `${basename6(projectRoot)}${sep5}${relPathInProject}`,
|
|
10397
10571
|
description: title,
|
|
10398
10572
|
path: entry.absPath,
|
|
10399
10573
|
scope: "project",
|
|
@@ -10437,7 +10611,8 @@ function knowledgeAdapters() {
|
|
|
10437
10611
|
claudeSkillsAdapter(),
|
|
10438
10612
|
cursorRulesAdapter(),
|
|
10439
10613
|
runbooksAdapter(),
|
|
10440
|
-
ticketsAdapter()
|
|
10614
|
+
ticketsAdapter(),
|
|
10615
|
+
gitLogAdapter()
|
|
10441
10616
|
];
|
|
10442
10617
|
}
|
|
10443
10618
|
function openStore(projectRoot, embedder) {
|
|
@@ -10769,7 +10944,7 @@ function projectState(store) {
|
|
|
10769
10944
|
const live = (i) => (i.kind === "ticket" || i.kind === "plan") && (i.status === "open" || i.status === "in-progress");
|
|
10770
10945
|
const rank = (i) => i.status === "in-progress" ? 0 : 1;
|
|
10771
10946
|
const open2 = items.filter(live).sort((a, b) => rank(a) - rank(b) || a.rel_path.localeCompare(b.rel_path)).map((i) => ({ relPath: i.rel_path, name: i.name, status: i.status ?? "unknown", updatedAt: i.updated_at }));
|
|
10772
|
-
const recentlyChanged =
|
|
10947
|
+
const recentlyChanged = items.filter((i) => i.kind !== "commit").sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? "")).slice(0, 10).map((i) => ({ relPath: i.rel_path, kind: i.kind, updatedAt: i.updated_at }));
|
|
10773
10948
|
const decisions = store.db.prepare("SELECT id, created_at, title FROM decisions ORDER BY id DESC LIMIT 10").all().map((d) => ({ id: d.id, createdAt: d.created_at, title: d.title }));
|
|
10774
10949
|
return { counts, open: open2, recentlyChanged, decisions };
|
|
10775
10950
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yurtsever/capsa",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
4
|
"description": "Your project's knowledge in one capsule. Local-first project memory and cockpit for AI coding agents: indexes runbooks, tickets, plans and instruction files, serves the smallest relevant context over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|