@iamem/amem 0.1.2 → 0.1.3

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/cli.js CHANGED
@@ -3,7 +3,7 @@ import { mkdirSync, rmSync, writeFileSync, existsSync } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { buildAttestReport, formatAttestHuman } from "./attest.js";
5
5
  import { handleApi, logContextUsage } from "./api/routes.js";
6
- import { closeDb, getRepoByCwd, getRepoByName, renameWorkspace, listClaims, listComponents, listFlows, requireRepo, setReportedOnLatest, setReportedTokensSaved, touchSession, upsertRepo, upsertSetupState, wipeAllRepos, wipeRepo, openDb, } from "./db.js";
6
+ import { closeDb, getRepoByCwd, getRepoByName, renameWorkspace, listClaims, listComponents, listFlows, listRepos, listTasks, listTasksAll, insertTask, updateTask, completeTask, deleteTask, findTaskAnyRepo, normalizeTaskStatus, requireRepo, setReportedOnLatest, setReportedTokensSaved, touchSession, upsertRepo, upsertSetupState, wipeAllRepos, wipeRepo, openDb, } from "./db.js";
7
7
  import { handleHookPayload } from "./hook.js";
8
8
  import { installClaude, claudeInstallHealth } from "./install/claude.js";
9
9
  import { installCursor, cursorInstallHealth } from "./install/cursor.js";
@@ -28,6 +28,8 @@ import { embedIndexIssues, embedStatus, reindexAllEmbeds, setEmbedBackend, } fro
28
28
  import { acceptSafeCleanups, decayStaleClaims, hygieneReport, mergeDuplicate, runScheduledHygiene } from "./hygiene.js";
29
29
  import { hygieneSchedulePath, installHygieneSchedule, isHygieneScheduleInstalled, uninstallHygieneSchedule, writeHygieneHelperScript, } from "./hygiene-schedule.js";
30
30
  import { syncPinnedRules } from "./rules-sync.js";
31
+ import { listSkillDrafts } from "./db.js";
32
+ import { deleteSkill, findSkillOnDisk, importSkillFromPath, isValidSkillName, listIndexedSkills, readSkillBody, renderSkillMarkdown, skillsDir, slugifySkillName, syncSkillIndex, writeSkill, } from "./skills.js";
31
33
  import { buildSbom, writeItPack } from "./it-pack.js";
32
34
  function usage() {
33
35
  console.log(`amem — local personal agent memory
@@ -43,6 +45,11 @@ Usage:
43
45
  amem context "<query>" [--workspace <name>] [--platform cursor|claude|luna]
44
46
  amem remember "<text>" [--workspace <name>] [--kind session] [--anchor <path>]
45
47
  amem recipe [--json]
48
+ amem task [list] [--status <backlog|next|doing|blocked|done>] [--include-done] [--all] [--json]
49
+ amem task add <title> [--body <notes>] [--status <status>] [--anchor <file>]
50
+ amem task update <id> [--title <title>] [--body <notes>] [--status <status>]
51
+ amem task complete <id>
52
+ amem task delete <id>
46
53
  amem propose validate <file.json>
47
54
  amem propose diff <file.json>
48
55
  amem propose apply <file.json>
@@ -60,6 +67,8 @@ Usage:
60
67
  amem hygiene schedule [--hour <0-23>]
61
68
  amem hygiene unschedule
62
69
  amem rules sync
70
+ amem skills list|show <name>|new <name> [--desc <text>]|rm <name>|sync|import <path>
71
+ amem skills drafts|approve <id>|dismiss <id>
63
72
  amem it-pack [--out <dir>]
64
73
  amem doctor [--attest] [--sbom] [--json]
65
74
  amem session touch --platform cursor|claude [--session-id <id>]
@@ -691,6 +700,235 @@ async function main() {
691
700
  console.log("Keep this file out of shared git if it contains personal notes.");
692
701
  break;
693
702
  }
703
+ case "task":
704
+ case "tasks": {
705
+ const sub = positional[1] || "list";
706
+ if (sub === "list") {
707
+ const isAll = Boolean(flags.get("all"));
708
+ const statusRaw = flagString(flags, "status");
709
+ const status = statusRaw ? (normalizeTaskStatus(statusRaw) ?? undefined) : undefined;
710
+ const includeDone = Boolean(flags.get("include-done") || flags.get("all") || status === "done");
711
+ let tasks;
712
+ if (isAll) {
713
+ tasks = listTasksAll({ status, includeDone });
714
+ }
715
+ else {
716
+ let repo;
717
+ try {
718
+ repo = resolveBinding(flags);
719
+ }
720
+ catch {
721
+ repo = getRepoByCwd() || ensurePersonalWorkspace();
722
+ }
723
+ tasks = listTasks(repo.id, { status, includeDone });
724
+ }
725
+ if (flags.get("json")) {
726
+ console.log(JSON.stringify({ tasks }, null, 2));
727
+ break;
728
+ }
729
+ if (tasks.length === 0) {
730
+ console.log("No tasks found.");
731
+ break;
732
+ }
733
+ const repoMap = new Map(listRepos().map((r) => [r.id, r.repo_name]));
734
+ for (const t of tasks) {
735
+ const repoLabel = isAll ? ` [${repoMap.get(t.repo_id) || "repo"}]` : "";
736
+ const body = t.body ? ` — ${t.body}` : "";
737
+ console.log(`[${t.status}] ${t.title}${body}${repoLabel} (${t.id})`);
738
+ }
739
+ break;
740
+ }
741
+ if (sub === "add") {
742
+ const title = positional.slice(2).join(" ") || flagString(flags, "title");
743
+ if (!title || !title.trim()) {
744
+ throw new Error("Usage: amem task add <title> [--body <notes>] [--status <status>] [--anchor <file>]");
745
+ }
746
+ let repo;
747
+ try {
748
+ repo = resolveBinding(flags);
749
+ }
750
+ catch {
751
+ repo = getRepoByCwd() || ensurePersonalWorkspace();
752
+ }
753
+ const body = flagString(flags, "body") || "";
754
+ const status = flagString(flags, "status") || "backlog";
755
+ const anchor = flagString(flags, "anchor");
756
+ const anchors = anchor ? [anchor] : undefined;
757
+ const task = insertTask({
758
+ repoId: repo.id,
759
+ title: title.trim(),
760
+ body,
761
+ status,
762
+ anchors,
763
+ source: "cli",
764
+ });
765
+ console.log(`Created task ${task.id} [${task.status}]: ${task.title}`);
766
+ break;
767
+ }
768
+ if (sub === "update") {
769
+ const id = positional[2];
770
+ if (!id)
771
+ throw new Error("Usage: amem task update <id> [--title <title>] [--body <notes>] [--status <status>]");
772
+ const found = findTaskAnyRepo(id);
773
+ if (!found)
774
+ throw new Error(`Task not found: ${id}`);
775
+ const title = flagString(flags, "title");
776
+ const body = flagString(flags, "body");
777
+ const status = flagString(flags, "status");
778
+ const anchor = flagString(flags, "anchor");
779
+ const anchors = anchor ? [anchor] : undefined;
780
+ const updated = updateTask(found.repo_id, id, {
781
+ title,
782
+ body,
783
+ status,
784
+ anchors,
785
+ });
786
+ if (!updated)
787
+ throw new Error(`Task not found: ${id}`);
788
+ console.log(`Updated task ${updated.id} [${updated.status}]: ${updated.title}`);
789
+ break;
790
+ }
791
+ if (sub === "complete" || sub === "done") {
792
+ const id = positional[2];
793
+ if (!id)
794
+ throw new Error("Usage: amem task complete <id>");
795
+ const found = findTaskAnyRepo(id);
796
+ if (!found)
797
+ throw new Error(`Task not found: ${id}`);
798
+ const completed = completeTask(found.repo_id, id);
799
+ if (!completed)
800
+ throw new Error(`Task not found: ${id}`);
801
+ console.log(`Completed task ${completed.id}: ${completed.title}`);
802
+ break;
803
+ }
804
+ if (sub === "rm" || sub === "delete") {
805
+ const id = positional[2];
806
+ if (!id)
807
+ throw new Error("Usage: amem task delete <id>");
808
+ const found = findTaskAnyRepo(id);
809
+ if (!found)
810
+ throw new Error(`Task not found: ${id}`);
811
+ const ok = deleteTask(found.repo_id, id);
812
+ if (!ok)
813
+ throw new Error(`Task not found: ${id}`);
814
+ console.log(`Deleted task ${id}`);
815
+ break;
816
+ }
817
+ throw new Error("Usage: amem task list|add|update|complete|delete");
818
+ }
819
+ case "skills": {
820
+ const sub = positional[1] || "list";
821
+ if (sub === "list") {
822
+ const skills = listIndexedSkills();
823
+ if (skills.length === 0) {
824
+ console.log(`No skills yet. Create one with: amem skills new <name>`);
825
+ console.log(`Skills live in ${skillsDir()}`);
826
+ break;
827
+ }
828
+ for (const s of skills) {
829
+ const flag = s.modified ? " (edited)" : "";
830
+ const used = s.uses > 0 ? ` · used ${s.uses}×` : "";
831
+ console.log(`${s.name}${flag}${used}`);
832
+ if (s.description)
833
+ console.log(` ${s.description}`);
834
+ }
835
+ console.log(`\n${skills.length} skill(s) in ${skillsDir()}`);
836
+ break;
837
+ }
838
+ if (sub === "show") {
839
+ const name = positional[2];
840
+ if (!name)
841
+ throw new Error("Usage: amem skills show <name>");
842
+ const body = readSkillBody(name);
843
+ if (body === null)
844
+ throw new Error(`Skill not found: ${name}`);
845
+ console.log(body);
846
+ break;
847
+ }
848
+ if (sub === "new") {
849
+ const name = positional[2];
850
+ if (!name)
851
+ throw new Error("Usage: amem skills new <name> [--desc <text>]");
852
+ const slug = slugifySkillName(name);
853
+ if (!isValidSkillName(slug))
854
+ throw new Error(`Invalid skill name: ${name}`);
855
+ if (findSkillOnDisk(slug))
856
+ throw new Error(`Skill already exists: ${slug}`);
857
+ const markdown = renderSkillMarkdown({
858
+ name: slug,
859
+ description: flagString(flags, "desc") || `Procedure: ${slug.replace(/[-_]+/g, " ")}`,
860
+ });
861
+ const written = writeSkill(slug, markdown);
862
+ syncSkillIndex();
863
+ console.log(`Created ${written.path}`);
864
+ console.log("Edit it, then agents will see it in their skill index.");
865
+ break;
866
+ }
867
+ if (sub === "rm") {
868
+ const name = positional[2];
869
+ if (!name)
870
+ throw new Error("Usage: amem skills rm <name>");
871
+ if (!deleteSkill(name))
872
+ throw new Error(`Skill not found: ${name}`);
873
+ syncSkillIndex();
874
+ console.log(`Deleted skill ${slugifySkillName(name)}`);
875
+ break;
876
+ }
877
+ if (sub === "sync") {
878
+ const skills = syncSkillIndex();
879
+ console.log(`Indexed ${skills.length} skill(s) from ${skillsDir()}`);
880
+ const edited = skills.filter((s) => s.modified);
881
+ if (edited.length > 0) {
882
+ console.log(`${edited.length} locally edited: ${edited.map((s) => s.name).join(", ")}`);
883
+ }
884
+ break;
885
+ }
886
+ if (sub === "drafts") {
887
+ const drafts = listSkillDrafts({ status: "pending", limit: 50 });
888
+ if (drafts.length === 0) {
889
+ console.log("No pending skill drafts.");
890
+ break;
891
+ }
892
+ for (const d of drafts) {
893
+ const label = d.kind === "revision" ? `revise ${d.target_skill}` : d.kind === "create" ? "staged" : "suggestion";
894
+ console.log(`${d.id} [${label}] ${d.title}`);
895
+ const why = JSON.parse(d.reasons || "[]");
896
+ if (Array.isArray(why) && why.length)
897
+ console.log(` why: ${why.join(", ")}`);
898
+ }
899
+ console.log(`\n${drafts.length} pending · approve with: amem skills approve <id>`);
900
+ break;
901
+ }
902
+ if (sub === "approve" || sub === "dismiss") {
903
+ const id = positional[2];
904
+ if (!id)
905
+ throw new Error(`Usage: amem skills ${sub} <draft-id>`);
906
+ const path = sub === "approve" ? "/api/skills/drafts/apply" : "/api/skills/drafts/dismiss";
907
+ const result = handleApi({
908
+ method: "POST",
909
+ pathname: path,
910
+ searchParams: new URLSearchParams(),
911
+ body: { id },
912
+ cwd: process.cwd(),
913
+ });
914
+ if (result.status >= 400) {
915
+ throw new Error(String(result.body?.error || "Failed"));
916
+ }
917
+ console.log(sub === "approve" ? `Applied ${id}` : `Dismissed ${id}`);
918
+ break;
919
+ }
920
+ if (sub === "import") {
921
+ const src = positional[2];
922
+ if (!src)
923
+ throw new Error("Usage: amem skills import <path-to-skill-dir>");
924
+ const result = importSkillFromPath(src, flagString(flags, "name"));
925
+ syncSkillIndex();
926
+ console.log(`Imported ${result.name} → ${result.path}`);
927
+ console.log("Review it before trusting: skills are instructions agents follow.");
928
+ break;
929
+ }
930
+ throw new Error("Usage: amem skills list|show|new|rm|sync|import");
931
+ }
694
932
  case "it-pack": {
695
933
  const out = resolve(flagString(flags, "out") || join(amemHome(), "it-pack"));
696
934
  const result = writeItPack(out);
@@ -919,7 +1157,8 @@ async function main() {
919
1157
  break;
920
1158
  }
921
1159
  if (sub === "activate") {
922
- throw new Error("Self-activate is disabled. Buy Pro/IT at https://getamem.com then: amem license apply --file <amem-license.json>");
1160
+ console.log("amem is 100% free and open all features are already unlocked.");
1161
+ break;
923
1162
  }
924
1163
  if (sub === "clear") {
925
1164
  clearLicense();
package/dist/context.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { type ClaimRow, type ComponentRow, type ConversationNoteRow, type FlowRow, type UsageEventRow } from "./db.js";
1
+ import { type AgentTaskRow, type ClaimRow, type ComponentRow, type ConversationNoteRow, type FlowRow, type UsageEventRow, type SkillDraftRow } from "./db.js";
2
2
  import { type ClaimFreshness } from "./freshness.js";
3
+ import { type RankedSkill } from "./skills.js";
3
4
  export type RankedClaim = ClaimRow & {
4
5
  score: number;
5
6
  freshness: ClaimFreshness;
@@ -14,12 +15,20 @@ export type ContextPacket = {
14
15
  notes: Array<ConversationNoteRow & {
15
16
  score: number;
16
17
  }>;
18
+ /** Deferred Kanban tasks (non-done), compact for agents. */
19
+ tasks: AgentTaskRow[];
20
+ /** Index-only skill matches; bodies load on demand via `amem_skill_view`. */
21
+ skills: RankedSkill[];
22
+ /** At most one pending "worth writing up" nudge from a previous session. */
23
+ skillDrafts: SkillDraftRow[];
17
24
  };
18
25
  export type BuildContextOptions = {
19
26
  limit?: number;
20
27
  rootPath?: string;
21
28
  /** Include cross-repo personal prefs (default true). */
22
29
  includePersonal?: boolean;
30
+ /** Include the ranked skill index (default true). */
31
+ includeSkills?: boolean;
23
32
  };
24
33
  export declare function buildContext(repoId: string, query: string, limitOrOpts?: number | BuildContextOptions): ContextPacket;
25
34
  export type ShowdownClaim = {
package/dist/context.js CHANGED
@@ -1,10 +1,25 @@
1
- import { getRepoByName, listClaims, listComponents, listConversationNotes, listEdges, listFlows, openDb, } from "./db.js";
1
+ import { getRepoByName, listClaims, listComponents, listConversationNotes, listEdges, listFlows, listOpenTasksForContext, listTasks, openDb, listSkillDrafts, } from "./db.js";
2
2
  import { assessClaimFreshness, freshnessScoreMultiplier, } from "./freshness.js";
3
3
  import { kindRankBoost } from "./kinds.js";
4
4
  import { ftsBoostFromBm25, keywordScoreClaim, searchClaimsFts, tokenize, } from "./search.js";
5
5
  import { embedBoostFromScore, searchClaimsEmbed, searchClaimsEmbedLive } from "./embed.js";
6
6
  import { FEATURE_LOCAL_EMBED, hasFeature } from "./license.js";
7
7
  import { PERSONAL_SLUG } from "./personal.js";
8
+ import { listIndexedSkills, rankSkills } from "./skills.js";
9
+ import { loadPolicy } from "./policy.js";
10
+ /** Reasons are stored as JSON; a malformed row must not break the whole packet. */
11
+ function safeReasons(raw) {
12
+ try {
13
+ const list = JSON.parse(raw);
14
+ if (Array.isArray(list) && list.length > 0) {
15
+ return ` (${list.filter((r) => typeof r === "string").join(", ")})`;
16
+ }
17
+ }
18
+ catch {
19
+ /* fall through */
20
+ }
21
+ return "";
22
+ }
8
23
  function scoreNote(note, queryTokens) {
9
24
  if (queryTokens.length === 0)
10
25
  return 0;
@@ -16,6 +31,17 @@ function scoreNote(note, queryTokens) {
16
31
  }
17
32
  return score;
18
33
  }
34
+ function scoreTask(task, queryTokens) {
35
+ if (queryTokens.length === 0)
36
+ return 0;
37
+ const hay = `${task.title} ${task.body} ${task.anchors || ""}`.toLowerCase();
38
+ let score = 0;
39
+ for (const token of queryTokens) {
40
+ if (hay.includes(token))
41
+ score += token.length > 4 ? 3 : 2;
42
+ }
43
+ return score;
44
+ }
19
45
  function rankClaims(claims, query, queryTokens, opts) {
20
46
  return claims.map((c) => {
21
47
  const freshness = assessClaimFreshness(opts.rootPath, c);
@@ -152,7 +178,44 @@ export function buildContext(repoId, query, limitOrOpts = 12) {
152
178
  const notes = notesRaw.length > 0
153
179
  ? notesRaw
154
180
  : listConversationNotes(repoId, 5).map((n) => ({ ...n, score: 0 }));
155
- return { query, claims: selected, flows, components, notes };
181
+ const openTasks = listOpenTasksForContext(repoId, 8);
182
+ const doneTasks = queryTokens.length > 0
183
+ ? listTasks(repoId, { status: "done", includeDone: true, limit: 20 })
184
+ .map((t) => ({ ...t, score: scoreTask(t, queryTokens) }))
185
+ .filter((t) => t.score > 0)
186
+ .sort((a, b) => b.score - a.score || b.updated_at.localeCompare(a.updated_at))
187
+ .slice(0, 4)
188
+ : [];
189
+ const tasks = [...openTasks, ...doneTasks];
190
+ const skillsOn = opts.includeSkills !== false && loadPolicy().policy.skills_enabled;
191
+ const skills = skillsOn ? rankSkillsForQuery(query) : [];
192
+ const skillDrafts = skillsOn ? pendingSkillNudges(repoId) : [];
193
+ return { query, claims: selected, flows, components, notes, tasks, skills, skillDrafts };
194
+ }
195
+ /**
196
+ * amem has no model, so it cannot write a skill itself. The nudge is how a session-end
197
+ * detection reaches an agent that can. One at a time — this rides in every packet.
198
+ */
199
+ function pendingSkillNudges(repoId) {
200
+ try {
201
+ return listSkillDrafts({ status: "pending", repoId, limit: 1 });
202
+ }
203
+ catch {
204
+ return [];
205
+ }
206
+ }
207
+ /**
208
+ * Progressive disclosure, level 0. Only names and descriptions reach the packet; the
209
+ * agent pulls a body with `amem_skill_view` if it decides the procedure applies. A skills
210
+ * directory that cannot be read must never break context, so this stays best-effort.
211
+ */
212
+ function rankSkillsForQuery(query) {
213
+ try {
214
+ return rankSkills(listIndexedSkills(), query, 3);
215
+ }
216
+ catch {
217
+ return [];
218
+ }
156
219
  }
157
220
  function toShowdownClaims(ranked) {
158
221
  return ranked.map((c) => ({
@@ -258,7 +321,11 @@ export function decorateUsageEvents(events) {
258
321
  }
259
322
  export function renderContextMarkdown(packet) {
260
323
  const lines = ["# Agent Memory Context", "", `Query: ${packet.query}`, ""];
261
- if (packet.claims.length === 0 && packet.notes.length === 0) {
324
+ if (packet.claims.length === 0 &&
325
+ packet.notes.length === 0 &&
326
+ (packet.tasks?.length ?? 0) === 0 &&
327
+ (packet.skills?.length ?? 0) === 0 &&
328
+ (packet.skillDrafts?.length ?? 0) === 0) {
262
329
  lines.push("No claims stored for this repository yet.", "");
263
330
  lines.push("Seed memory with the `amem-bootstrap` skill or `amem propose apply <file>`.");
264
331
  return lines.join("\n");
@@ -266,6 +333,41 @@ export function renderContextMarkdown(packet) {
266
333
  if (packet.claims.length === 0) {
267
334
  lines.push("No durable claims yet — using recent conversation memory.", "");
268
335
  }
336
+ if (packet.tasks && packet.tasks.length > 0) {
337
+ const hasDone = packet.tasks.some((t) => t.status === "done");
338
+ const hasOpen = packet.tasks.some((t) => t.status !== "done");
339
+ const header = hasDone && !hasOpen ? "## Completed tasks" : (hasDone ? "## Tasks (open & completed)" : "## Open tasks");
340
+ lines.push(header, "");
341
+ lines.push("_Project Kanban tasks. Use `amem_task_*` to add/update/complete._", "");
342
+ for (const task of packet.tasks) {
343
+ const body = task.body ? ` — ${task.body.replace(/\s+/g, " ").slice(0, 120)}` : "";
344
+ lines.push(`- **[${task.status}]** ${task.title}${body} (\`${task.id}\`)`);
345
+ }
346
+ lines.push("");
347
+ }
348
+ if (packet.skills && packet.skills.length > 0) {
349
+ lines.push("## Relevant skills", "");
350
+ lines.push("_Procedures that may apply. Only the index is shown — call `amem_skill_view` with the name to load one before following it._", "");
351
+ for (const skill of packet.skills) {
352
+ const desc = skill.description
353
+ ? ` — ${skill.description.replace(/\s+/g, " ").slice(0, 140)}`
354
+ : "";
355
+ lines.push(`- **${skill.name}**${desc}`);
356
+ }
357
+ lines.push("");
358
+ }
359
+ const nudge = packet.skillDrafts?.[0];
360
+ if (nudge) {
361
+ const why = safeReasons(nudge.reasons);
362
+ if (nudge.kind === "revision" && nudge.target_skill) {
363
+ lines.push("## Skill worth revising", "");
364
+ lines.push(`_You followed **${nudge.target_skill}** in a recent session and the work still went sideways${why}. If you learned what it was missing, update it with \`amem_skill_save\`._`, "");
365
+ }
366
+ else {
367
+ lines.push("## Worth saving as a skill", "");
368
+ lines.push(`_A recent session looked like a repeatable procedure: "${nudge.title}"${why}. If you can write it up, save it with \`amem_skill_save\` so the next agent does not rediscover it._`, "");
369
+ }
370
+ }
269
371
  const staleCount = packet.claims.filter((c) => c.freshness.status === "stale").length;
270
372
  if (staleCount > 0) {
271
373
  lines.push(`_${staleCount} claim(s) marked stale — anchored files changed after the claim was written. Verify before trusting._`, "");
package/dist/db.d.ts CHANGED
@@ -96,6 +96,21 @@ export type SetupStateRow = {
96
96
  setup_completed_at: string | null;
97
97
  updated_at: string;
98
98
  };
99
+ export type AgentTaskStatus = "backlog" | "next" | "doing" | "blocked" | "done";
100
+ export type AgentTaskRow = {
101
+ repo_id: string;
102
+ id: string;
103
+ title: string;
104
+ body: string;
105
+ status: AgentTaskStatus;
106
+ anchors: string;
107
+ source: string;
108
+ created_at: string;
109
+ updated_at: string;
110
+ completed_at: string | null;
111
+ };
112
+ export declare const AGENT_TASK_STATUSES: readonly AgentTaskStatus[];
113
+ export declare function normalizeTaskStatus(raw: unknown): AgentTaskStatus | null;
99
114
  export declare function openDb(): Database.Database;
100
115
  export declare function closeDb(): void;
101
116
  declare function nowIso(): string;
@@ -189,4 +204,130 @@ export declare function listProposalDraftsAll(opts?: {
189
204
  export declare function countProposalDrafts(repoId: string, status?: string): number;
190
205
  export declare function countProposalDraftsAll(status?: string): number;
191
206
  export declare function setProposalDraftStatus(id: string, status: "pending" | "applied" | "dismissed"): ProposalDraftRow | null;
207
+ export type SkillRow = {
208
+ name: string;
209
+ path: string;
210
+ description: string;
211
+ version: string | null;
212
+ tags: string;
213
+ repo_id: string | null;
214
+ content_hash: string;
215
+ origin_hash: string | null;
216
+ source: string;
217
+ uses: number;
218
+ last_used_at: string | null;
219
+ created_at: string;
220
+ updated_at: string;
221
+ };
222
+ export declare function listSkillRows(): SkillRow[];
223
+ export declare function getSkillRow(name: string): SkillRow | null;
224
+ /**
225
+ * Index one skill found on disk. Disk is the source of truth, so this only ever refreshes
226
+ * derived columns — it must not clobber the repo tag or usage counters a user built up.
227
+ */
228
+ export declare function upsertSkillRow(input: {
229
+ name: string;
230
+ path: string;
231
+ description?: string;
232
+ version?: string | null;
233
+ tags?: string[];
234
+ contentHash: string;
235
+ source?: string;
236
+ repoId?: string | null;
237
+ }): SkillRow;
238
+ /** Optional memory tag. Skills are a global library; the tag is only a filter hint. */
239
+ export declare function setSkillRepo(name: string, repoId: string | null): void;
240
+ export declare function deleteSkillRow(name: string): boolean;
241
+ /** Drop index rows whose skill is no longer on disk. */
242
+ export declare function pruneSkillRows(keepNames: string[]): number;
243
+ export declare function recordSkillUse(name: string, ctx?: {
244
+ repoId?: string | null;
245
+ sessionId?: string | null;
246
+ }): void;
247
+ /**
248
+ * Skills used recently in a repo. MCP clients do not always carry a session id, so
249
+ * recency in the same memory is the fallback for correlating a view to a session.
250
+ */
251
+ export declare function listRecentSkillUses(repoId: string, minutes?: number, limit?: number): string[];
252
+ export declare function listSkillsUsedInSession(sessionId: string, limit?: number): string[];
253
+ export type SkillDraftRow = {
254
+ id: string;
255
+ repo_id: string | null;
256
+ name: string | null;
257
+ title: string;
258
+ summary: string;
259
+ content: string | null;
260
+ kind: string;
261
+ target_skill: string | null;
262
+ status: string;
263
+ source: string;
264
+ session_id: string | null;
265
+ reasons: string;
266
+ created_at: string;
267
+ updated_at: string;
268
+ };
269
+ export declare function insertSkillDraft(input: {
270
+ repoId?: string | null;
271
+ name?: string | null;
272
+ title: string;
273
+ summary?: string;
274
+ content?: string | null;
275
+ kind?: "suggestion" | "create" | "revision";
276
+ targetSkill?: string | null;
277
+ source?: string;
278
+ sessionId?: string | null;
279
+ reasons?: string[];
280
+ }): SkillDraftRow;
281
+ export declare function getSkillDraft(id: string): SkillDraftRow | null;
282
+ export declare function listSkillDrafts(opts?: {
283
+ status?: string;
284
+ repoId?: string;
285
+ limit?: number;
286
+ }): SkillDraftRow[];
287
+ export declare function setSkillDraftStatus(id: string, status: string): SkillDraftRow | null;
288
+ export declare function skillDraftExists(source: string): boolean;
289
+ export declare function getTask(repoId: string, id: string): AgentTaskRow | null;
290
+ export declare function listTasks(repoId: string, opts?: {
291
+ status?: AgentTaskStatus;
292
+ includeDone?: boolean;
293
+ limit?: number;
294
+ }): AgentTaskRow[];
295
+ /**
296
+ * Tasks across every memory. The UI's "All memory" scope needs this because agents file
297
+ * tasks against whatever repo they were working in, which is often not the repo the UI
298
+ * was launched from — without it those tasks are invisible.
299
+ */
300
+ export declare function listTasksAll(opts?: {
301
+ status?: AgentTaskStatus;
302
+ includeDone?: boolean;
303
+ limit?: number;
304
+ }): AgentTaskRow[];
305
+ export declare function countTasksAll(opts?: {
306
+ status?: AgentTaskStatus;
307
+ openOnly?: boolean;
308
+ }): number;
309
+ /** Find a task without knowing its repo, so all-memory edits can resolve their owner. */
310
+ export declare function findTaskAnyRepo(id: string): AgentTaskRow | null;
311
+ /** Open tasks for context injection — prefer doing/next/blocked, then backlog. */
312
+ export declare function listOpenTasksForContext(repoId: string, limit?: number): AgentTaskRow[];
313
+ export declare function countTasks(repoId: string, opts?: {
314
+ status?: AgentTaskStatus;
315
+ openOnly?: boolean;
316
+ }): number;
317
+ export declare function insertTask(input: {
318
+ repoId: string;
319
+ title: string;
320
+ body?: string;
321
+ status?: AgentTaskStatus | string;
322
+ anchors?: string[];
323
+ source?: string;
324
+ }): AgentTaskRow;
325
+ export declare function updateTask(repoId: string, id: string, patch: {
326
+ title?: string;
327
+ body?: string;
328
+ status?: AgentTaskStatus | string;
329
+ anchors?: string[];
330
+ }): AgentTaskRow | null;
331
+ export declare function completeTask(repoId: string, id: string): AgentTaskRow | null;
332
+ export declare function deleteTask(repoId: string, id: string): boolean;
192
333
  export { nowIso };