@theglitchking/semantic-memory 1.6.1 → 1.7.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +81 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +19 -2
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/commands/jev-mode.md +18 -0
- package/commands/skill-loop.md +23 -0
- package/dist/chunk-2BFG4IBC.js +295 -0
- package/dist/chunk-2BFG4IBC.js.map +1 -0
- package/dist/chunk-2OMDSTSS.js +118 -0
- package/dist/chunk-2OMDSTSS.js.map +1 -0
- package/dist/{chunk-TMMTOFHI.js → chunk-34VN47IW.js} +2 -2
- package/dist/chunk-3ML5ESXI.js +224 -0
- package/dist/chunk-3ML5ESXI.js.map +1 -0
- package/dist/{chunk-ZLIHQZ4C.js → chunk-6TIKWOY4.js} +5 -5
- package/dist/{chunk-KSH2QO6V.js → chunk-7UAVDOI6.js} +5 -5
- package/dist/chunk-ACZKDSMP.js +123 -0
- package/dist/chunk-ACZKDSMP.js.map +1 -0
- package/dist/{chunk-PGVIBUL3.js → chunk-AIXQFTSB.js} +5 -5
- package/dist/chunk-BSXIGPDY.js +137 -0
- package/dist/chunk-BSXIGPDY.js.map +1 -0
- package/dist/{chunk-HZ7B5KHY.js → chunk-EXAUVIGW.js} +4 -4
- package/dist/chunk-GYR7U6KA.js +80 -0
- package/dist/chunk-GYR7U6KA.js.map +1 -0
- package/dist/chunk-M2PC64DB.js +195 -0
- package/dist/chunk-M2PC64DB.js.map +1 -0
- package/dist/chunk-MTLYF357.js +130 -0
- package/dist/chunk-MTLYF357.js.map +1 -0
- package/dist/chunk-N77YCMSX.js +113 -0
- package/dist/chunk-N77YCMSX.js.map +1 -0
- package/dist/chunk-PJXYCCI2.js +177 -0
- package/dist/chunk-PJXYCCI2.js.map +1 -0
- package/dist/chunk-PMOZKUEM.js +170 -0
- package/dist/chunk-PMOZKUEM.js.map +1 -0
- package/dist/chunk-R6GNP7DS.js +87 -0
- package/dist/chunk-R6GNP7DS.js.map +1 -0
- package/dist/chunk-TKHWVISV.js +71 -0
- package/dist/chunk-TKHWVISV.js.map +1 -0
- package/dist/chunk-V6PSK4Y4.js +186 -0
- package/dist/chunk-V6PSK4Y4.js.map +1 -0
- package/dist/{chunk-GYBUBJYM.js → chunk-VBLCUWT4.js} +17 -8
- package/dist/chunk-VBLCUWT4.js.map +1 -0
- package/dist/chunk-VQCNWRTL.js +121 -0
- package/dist/chunk-VQCNWRTL.js.map +1 -0
- package/dist/{chunk-HUG7E25D.js → chunk-Y7H76Q7F.js} +1 -1
- package/dist/chunk-Y7H76Q7F.js.map +1 -0
- package/dist/chunk-ZSWI3Y4Y.js +232 -0
- package/dist/chunk-ZSWI3Y4Y.js.map +1 -0
- package/dist/cli/index.js +381 -52
- package/dist/cli/index.js.map +1 -1
- package/dist/core/features.d.ts +38 -0
- package/dist/core/features.js +28 -0
- package/dist/core/index.d.ts +285 -2
- package/dist/core/index.js +170 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/jev.d.ts +35 -0
- package/dist/core/jev.js +20 -0
- package/dist/core/skill-curator.d.ts +1 -0
- package/dist/core/skill-curator.js +41 -0
- package/dist/core/skill-evidence.d.ts +25 -0
- package/dist/core/skill-evidence.js +23 -0
- package/dist/core/skill-queue.d.ts +46 -0
- package/dist/core/skill-queue.js +42 -0
- package/dist/core/skill-review.d.ts +3 -0
- package/dist/core/skill-review.js +29 -0
- package/dist/core/skill-worker.d.ts +21 -0
- package/dist/core/skill-worker.js +70 -0
- package/dist/core/skill-worker.js.map +1 -0
- package/dist/{dossier-EN26WDKC.js → dossier-RXBLQOXC.js} +3 -3
- package/dist/failure-eval-IUJO47UC.js +18 -0
- package/dist/failure-eval-IUJO47UC.js.map +1 -0
- package/dist/{healthcheck-53OVXUQQ.js → healthcheck-S7D6YNTA.js} +7 -7
- package/dist/healthcheck-S7D6YNTA.js.map +1 -0
- package/dist/{lexicon-XNA5IFIA.js → lexicon-SEJSLBHO.js} +4 -4
- package/dist/lexicon-SEJSLBHO.js.map +1 -0
- package/dist/{lint-O4RQ47NX.js → lint-3JNLRM5U.js} +6 -6
- package/dist/lint-3JNLRM5U.js.map +1 -0
- package/dist/mcp/server.js +19 -17
- package/dist/mcp/server.js.map +1 -1
- package/dist/{profile-SIWAVD7O.js → profile-SNULTJ27.js} +4 -4
- package/dist/profile-SNULTJ27.js.map +1 -0
- package/dist/{schema-UJNYYRZZ.js → schema-RDYUK6CC.js} +2 -2
- package/dist/schema-RDYUK6CC.js.map +1 -0
- package/dist/skill-curator-D7HHZaUI.d.ts +71 -0
- package/dist/skill-land-EHXZW47F.js +31 -0
- package/dist/skill-land-EHXZW47F.js.map +1 -0
- package/dist/skill-ledger-AYXVNFX7.js +18 -0
- package/dist/skill-ledger-AYXVNFX7.js.map +1 -0
- package/dist/skill-merge-HBZBALZX.js +24 -0
- package/dist/skill-merge-HBZBALZX.js.map +1 -0
- package/dist/skill-review-BNvHipn9.d.ts +182 -0
- package/dist/skill-schema-RVB3EHEF.js +30 -0
- package/dist/skill-schema-RVB3EHEF.js.map +1 -0
- package/hooks/hooks.json +26 -1
- package/hooks/jev-bridge.py +109 -0
- package/hooks/session-start.js +5 -0
- package/hooks/skill-loop.js +65 -0
- package/hooks/vault-context.js +104 -6
- package/package.json +4 -2
- package/dist/chunk-5F5ZIAW2.js +0 -79
- package/dist/chunk-5F5ZIAW2.js.map +0 -1
- package/dist/chunk-GYBUBJYM.js.map +0 -1
- package/dist/chunk-HUG7E25D.js.map +0 -1
- package/dist/failure-eval-37JRNTIW.js +0 -10
- /package/dist/{chunk-TMMTOFHI.js.map → chunk-34VN47IW.js.map} +0 -0
- /package/dist/{chunk-ZLIHQZ4C.js.map → chunk-6TIKWOY4.js.map} +0 -0
- /package/dist/{chunk-KSH2QO6V.js.map → chunk-7UAVDOI6.js.map} +0 -0
- /package/dist/{chunk-PGVIBUL3.js.map → chunk-AIXQFTSB.js.map} +0 -0
- /package/dist/{chunk-HZ7B5KHY.js.map → chunk-EXAUVIGW.js.map} +0 -0
- /package/dist/{dossier-EN26WDKC.js.map → core/features.js.map} +0 -0
- /package/dist/{failure-eval-37JRNTIW.js.map → core/jev.js.map} +0 -0
- /package/dist/{healthcheck-53OVXUQQ.js.map → core/skill-curator.js.map} +0 -0
- /package/dist/{lexicon-XNA5IFIA.js.map → core/skill-evidence.js.map} +0 -0
- /package/dist/{lint-O4RQ47NX.js.map → core/skill-queue.js.map} +0 -0
- /package/dist/{profile-SIWAVD7O.js.map → core/skill-review.js.map} +0 -0
- /package/dist/{schema-UJNYYRZZ.js.map → dossier-RXBLQOXC.js.map} +0 -0
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
readSpeakerProfile,
|
|
6
6
|
speakerProfileTemplate,
|
|
7
7
|
updateProfileSection
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-6TIKWOY4.js";
|
|
9
|
+
import "./chunk-EXAUVIGW.js";
|
|
10
10
|
import "./chunk-EYVFC745.js";
|
|
11
|
-
import "./chunk-H2BES6FU.js";
|
|
12
11
|
import "./chunk-J7LYDRML.js";
|
|
12
|
+
import "./chunk-H2BES6FU.js";
|
|
13
13
|
import "./chunk-PZ5AY32C.js";
|
|
14
14
|
export {
|
|
15
15
|
PROFILE_SECTIONS,
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
speakerProfileTemplate,
|
|
20
20
|
updateProfileSection
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=profile-
|
|
22
|
+
//# sourceMappingURL=profile-SNULTJ27.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
installDefaultSchema,
|
|
3
3
|
loadSchema,
|
|
4
4
|
validateNote
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Y7H76Q7F.js";
|
|
6
6
|
import "./chunk-J7LYDRML.js";
|
|
7
7
|
import "./chunk-PZ5AY32C.js";
|
|
8
8
|
export {
|
|
@@ -10,4 +10,4 @@ export {
|
|
|
10
10
|
loadSchema,
|
|
11
11
|
validateNote
|
|
12
12
|
};
|
|
13
|
-
//# sourceMappingURL=schema-
|
|
13
|
+
//# sourceMappingURL=schema-RDYUK6CC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type LedgerAction = "land" | "patch" | "stale" | "archive" | "restore" | "promote" | "merge" | "reject" | "rollback";
|
|
2
|
+
interface FileChange {
|
|
3
|
+
path: string;
|
|
4
|
+
/** sha256 of the bytes before (null: the file did not exist) */
|
|
5
|
+
before: string | null;
|
|
6
|
+
after: string | null;
|
|
7
|
+
}
|
|
8
|
+
interface LedgerEntry {
|
|
9
|
+
id: string;
|
|
10
|
+
ts: string;
|
|
11
|
+
action: LedgerAction;
|
|
12
|
+
actor: "user" | "curator" | "reviewer";
|
|
13
|
+
skill: string;
|
|
14
|
+
why: string;
|
|
15
|
+
files: FileChange[];
|
|
16
|
+
/** rollback: the entry it reversed */
|
|
17
|
+
reverts?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function readLedger(repo: string): LedgerEntry[];
|
|
20
|
+
/** Undo one entry: every file it touched goes back to its exact bytes before (or aside, if it did not exist). */
|
|
21
|
+
declare function rollback(repo: string, id: string): LedgerEntry;
|
|
22
|
+
|
|
23
|
+
declare const STALE_DAYS = 14;
|
|
24
|
+
declare const ARCHIVE_DAYS = 30;
|
|
25
|
+
declare const CURATOR_INTERVAL_DAYS = 7;
|
|
26
|
+
declare const IDLE_HOURS = 2;
|
|
27
|
+
declare const curatorStatePath: (repo: string) => string;
|
|
28
|
+
interface CuratorState {
|
|
29
|
+
first_seen?: string;
|
|
30
|
+
last_run?: string;
|
|
31
|
+
last_report?: string;
|
|
32
|
+
flagged?: {
|
|
33
|
+
name: string;
|
|
34
|
+
reason: string;
|
|
35
|
+
at: string;
|
|
36
|
+
}[];
|
|
37
|
+
}
|
|
38
|
+
declare function readState(repo: string): CuratorState;
|
|
39
|
+
interface CuratorAction {
|
|
40
|
+
skill: string;
|
|
41
|
+
path: string;
|
|
42
|
+
action: "stale" | "archive" | "reactivate" | "keep" | "skip";
|
|
43
|
+
why: string;
|
|
44
|
+
}
|
|
45
|
+
declare function plan(repo: string, now?: number, env?: NodeJS.ProcessEnv): CuratorAction[];
|
|
46
|
+
/** Run the curator. Dry run: report only. Returns the actions (and the ledger ids of the changes made). */
|
|
47
|
+
declare function curate(repo: string, opts?: {
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
now?: number;
|
|
50
|
+
env?: NodeJS.ProcessEnv;
|
|
51
|
+
}): {
|
|
52
|
+
actions: CuratorAction[];
|
|
53
|
+
ledger: string[];
|
|
54
|
+
};
|
|
55
|
+
/** Whether SessionStart should start the curator now (and records first sight of the repo). */
|
|
56
|
+
declare function curatorDue(repo: string, now?: number): boolean;
|
|
57
|
+
/** Bring an archived skill back where it was, active again. */
|
|
58
|
+
declare function restore(repo: string, name: string): LedgerEntry;
|
|
59
|
+
declare function listArchived(repo: string): string[];
|
|
60
|
+
/** Run each landed learned skill's Verify command (they were approved by a person); flag failures, archive nothing. */
|
|
61
|
+
declare function verifySkills(repo: string, opts?: {
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
}): {
|
|
65
|
+
skill: string;
|
|
66
|
+
result: "pass" | "fail" | "prose" | "none";
|
|
67
|
+
detail: string;
|
|
68
|
+
}[];
|
|
69
|
+
declare const curatorState: typeof readState;
|
|
70
|
+
|
|
71
|
+
export { ARCHIVE_DAYS as A, CURATOR_INTERVAL_DAYS as C, IDLE_HOURS as I, type LedgerEntry as L, STALE_DAYS as S, restore as a, rollback as b, curate as c, type CuratorAction as d, curatorDue as e, curatorState as f, curatorStatePath as g, listArchived as l, plan as p, readLedger as r, verifySkills as v };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
approveDraft,
|
|
3
|
+
describeDrafts,
|
|
4
|
+
destinationFor,
|
|
5
|
+
evalDraft,
|
|
6
|
+
rejectDraft,
|
|
7
|
+
reviewedPrompts
|
|
8
|
+
} from "./chunk-3ML5ESXI.js";
|
|
9
|
+
import "./chunk-2BFG4IBC.js";
|
|
10
|
+
import "./chunk-TKHWVISV.js";
|
|
11
|
+
import "./chunk-N77YCMSX.js";
|
|
12
|
+
import "./chunk-BSXIGPDY.js";
|
|
13
|
+
import "./chunk-MTLYF357.js";
|
|
14
|
+
import "./chunk-PMOZKUEM.js";
|
|
15
|
+
import "./chunk-ZSWI3Y4Y.js";
|
|
16
|
+
import "./chunk-J7LYDRML.js";
|
|
17
|
+
import "./chunk-VQCNWRTL.js";
|
|
18
|
+
import "./chunk-GYR7U6KA.js";
|
|
19
|
+
import "./chunk-M2PC64DB.js";
|
|
20
|
+
import "./chunk-R6GNP7DS.js";
|
|
21
|
+
import "./chunk-H2BES6FU.js";
|
|
22
|
+
import "./chunk-PZ5AY32C.js";
|
|
23
|
+
export {
|
|
24
|
+
approveDraft,
|
|
25
|
+
describeDrafts,
|
|
26
|
+
destinationFor,
|
|
27
|
+
evalDraft,
|
|
28
|
+
rejectDraft,
|
|
29
|
+
reviewedPrompts
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=skill-land-EHXZW47F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyChange,
|
|
3
|
+
archiveDir,
|
|
4
|
+
ledgerPath,
|
|
5
|
+
readLedger,
|
|
6
|
+
rollback
|
|
7
|
+
} from "./chunk-TKHWVISV.js";
|
|
8
|
+
import "./chunk-R6GNP7DS.js";
|
|
9
|
+
import "./chunk-H2BES6FU.js";
|
|
10
|
+
import "./chunk-PZ5AY32C.js";
|
|
11
|
+
export {
|
|
12
|
+
applyChange,
|
|
13
|
+
archiveDir,
|
|
14
|
+
ledgerPath,
|
|
15
|
+
readLedger,
|
|
16
|
+
rollback
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=skill-ledger-AYXVNFX7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MERGE_MIN_OVERLAP,
|
|
3
|
+
consolidateOn,
|
|
4
|
+
mergeCandidates,
|
|
5
|
+
proposeMerges
|
|
6
|
+
} from "./chunk-2OMDSTSS.js";
|
|
7
|
+
import "./chunk-2BFG4IBC.js";
|
|
8
|
+
import "./chunk-N77YCMSX.js";
|
|
9
|
+
import "./chunk-PMOZKUEM.js";
|
|
10
|
+
import "./chunk-ZSWI3Y4Y.js";
|
|
11
|
+
import "./chunk-J7LYDRML.js";
|
|
12
|
+
import "./chunk-VQCNWRTL.js";
|
|
13
|
+
import "./chunk-GYR7U6KA.js";
|
|
14
|
+
import "./chunk-M2PC64DB.js";
|
|
15
|
+
import "./chunk-R6GNP7DS.js";
|
|
16
|
+
import "./chunk-H2BES6FU.js";
|
|
17
|
+
import "./chunk-PZ5AY32C.js";
|
|
18
|
+
export {
|
|
19
|
+
MERGE_MIN_OVERLAP,
|
|
20
|
+
consolidateOn,
|
|
21
|
+
mergeCandidates,
|
|
22
|
+
proposeMerges
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=skill-merge-HBZBALZX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { ReviewJob } from './core/skill-queue.js';
|
|
2
|
+
import { ReviewOutcome } from './core/skill-worker.js';
|
|
3
|
+
|
|
4
|
+
declare const SKILL_SCOPES: readonly ["directory", "project", "global"];
|
|
5
|
+
declare const SKILL_STATUSES: readonly ["draft", "active", "stale", "archived"];
|
|
6
|
+
declare const SKILL_SECTIONS: readonly ["When to use", "Steps", "Pitfalls", "Verify"];
|
|
7
|
+
type SkillScope = (typeof SKILL_SCOPES)[number];
|
|
8
|
+
type SkillStatus = (typeof SKILL_STATUSES)[number];
|
|
9
|
+
interface SkillLoopMeta {
|
|
10
|
+
schema: 1;
|
|
11
|
+
scope: SkillScope;
|
|
12
|
+
status: SkillStatus;
|
|
13
|
+
pinned: boolean;
|
|
14
|
+
triggers: string[];
|
|
15
|
+
applies_to: {
|
|
16
|
+
paths: string[];
|
|
17
|
+
commands: string[];
|
|
18
|
+
};
|
|
19
|
+
provenance: {
|
|
20
|
+
sessions: string[];
|
|
21
|
+
failures: string[];
|
|
22
|
+
corrections: string[];
|
|
23
|
+
};
|
|
24
|
+
cites: string[];
|
|
25
|
+
supersedes: string[];
|
|
26
|
+
}
|
|
27
|
+
interface ParsedSkill {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
/** null when the file has no `skill_loop:` block: not a learned skill */
|
|
31
|
+
meta: SkillLoopMeta | null;
|
|
32
|
+
/** the raw `skill_loop` value, for lint messages on a malformed block */
|
|
33
|
+
rawMeta: unknown;
|
|
34
|
+
body: string;
|
|
35
|
+
/** `## ` headings in the body, in order */
|
|
36
|
+
sections: string[];
|
|
37
|
+
frontmatter: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
declare function parseSkill(raw: string): ParsedSkill;
|
|
40
|
+
/** A learned skill's SKILL.md text. Field order is fixed so a re-render of an unchanged skill is byte-identical. */
|
|
41
|
+
declare function renderSkill(skill: {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
meta: SkillLoopMeta;
|
|
45
|
+
body: string;
|
|
46
|
+
extra?: Record<string, unknown>;
|
|
47
|
+
}): string;
|
|
48
|
+
interface SkillFile {
|
|
49
|
+
path: string;
|
|
50
|
+
/** directory holding SKILL.md; its basename is the skill's name in Claude Code */
|
|
51
|
+
dir: string;
|
|
52
|
+
scope: SkillScope;
|
|
53
|
+
learned: boolean;
|
|
54
|
+
skill: ParsedSkill;
|
|
55
|
+
}
|
|
56
|
+
/** Every skill Claude Code would load for this repo: directory, project and global scope. */
|
|
57
|
+
declare function findSkills(projectRoot: string, opts?: {
|
|
58
|
+
home?: string;
|
|
59
|
+
global?: boolean;
|
|
60
|
+
}): SkillFile[];
|
|
61
|
+
interface SkillFinding {
|
|
62
|
+
path: string;
|
|
63
|
+
rule: "frontmatter" | "name" | "description" | "skill_loop" | "provenance" | "sections" | "cites" | "applies_to" | "secrets" | "name_clash";
|
|
64
|
+
severity: "error" | "warn";
|
|
65
|
+
message: string;
|
|
66
|
+
}
|
|
67
|
+
interface SkillLintContext {
|
|
68
|
+
/** repo root the skill belongs to (applies_to paths resolve against it) */
|
|
69
|
+
projectRoot: string;
|
|
70
|
+
facts?: Set<string>;
|
|
71
|
+
/** every other skill Claude Code would load alongside (for name clashes) */
|
|
72
|
+
others?: SkillFile[];
|
|
73
|
+
}
|
|
74
|
+
/** Lint one learned skill. `dirName` is the folder name Claude Code will use (defaults to the skill's name). */
|
|
75
|
+
declare function lintSkill(path: string, raw: string, ctx: SkillLintContext, dirName?: string): SkillFinding[];
|
|
76
|
+
/** Lint every learned skill this repo would load (and its drafts, when given). */
|
|
77
|
+
declare function lintSkills(projectRoot: string, opts?: {
|
|
78
|
+
home?: string;
|
|
79
|
+
extra?: string[];
|
|
80
|
+
}): {
|
|
81
|
+
files: number;
|
|
82
|
+
findings: SkillFinding[];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
interface ScopeDecision {
|
|
86
|
+
scope: Exclude<SkillScope, "global">;
|
|
87
|
+
/** directory scope: the subdirectory (repo-relative) */
|
|
88
|
+
directory?: string;
|
|
89
|
+
/** Jev said this holds in any repo: offer promotion once another repo has it too */
|
|
90
|
+
globalCandidate: boolean;
|
|
91
|
+
why: string;
|
|
92
|
+
}
|
|
93
|
+
declare function decideScope(repo: string, draft: {
|
|
94
|
+
name: string;
|
|
95
|
+
description: string;
|
|
96
|
+
body: string;
|
|
97
|
+
applies_to?: {
|
|
98
|
+
paths?: string[];
|
|
99
|
+
};
|
|
100
|
+
}, env?: NodeJS.ProcessEnv): Promise<ScopeDecision>;
|
|
101
|
+
|
|
102
|
+
declare const draftsDir: (repo: string) => string;
|
|
103
|
+
interface DraftRecord {
|
|
104
|
+
id: string;
|
|
105
|
+
kind: "new" | "patch" | "merge";
|
|
106
|
+
name: string;
|
|
107
|
+
scope: SkillScope;
|
|
108
|
+
/** patch: the SKILL.md it replaces, and that file's sha256 when the draft was made */
|
|
109
|
+
target?: string;
|
|
110
|
+
base_sha256?: string;
|
|
111
|
+
session_id: string;
|
|
112
|
+
created: string;
|
|
113
|
+
warnings: string[];
|
|
114
|
+
/** directory scope: where it lands (repo-relative subdirectory) */
|
|
115
|
+
directory?: string;
|
|
116
|
+
scope_why?: string;
|
|
117
|
+
/** Jev thinks it holds in any repo */
|
|
118
|
+
global_candidate?: boolean;
|
|
119
|
+
/** other repos with the same lesson: review offers one global skill replacing the copies */
|
|
120
|
+
promotion?: {
|
|
121
|
+
repo: string;
|
|
122
|
+
path: string;
|
|
123
|
+
}[];
|
|
124
|
+
/** merge: the SKILL.md files the merged skill replaces (archived when it lands) */
|
|
125
|
+
supersedes?: string[];
|
|
126
|
+
/** filled by skill evals (Phase 14) when they run at approval time */
|
|
127
|
+
evals?: Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The session's timeline from its transcript: prompts, tool calls, and the errors they returned. Fail-soft.
|
|
131
|
+
* `since`: only entries after it (a follow-up review of a resumed session).
|
|
132
|
+
*/
|
|
133
|
+
declare function transcriptTimeline(path: string | null, max?: number, since?: string): string[];
|
|
134
|
+
declare function buildBrief(job: ReviewJob, skills?: SkillFile[]): string;
|
|
135
|
+
interface ReviewerDraft {
|
|
136
|
+
kind: "new" | "patch";
|
|
137
|
+
target?: string;
|
|
138
|
+
name: string;
|
|
139
|
+
description: string;
|
|
140
|
+
scope: string;
|
|
141
|
+
applies_to?: {
|
|
142
|
+
paths?: string[];
|
|
143
|
+
commands?: string[];
|
|
144
|
+
};
|
|
145
|
+
triggers?: string[];
|
|
146
|
+
provenance?: {
|
|
147
|
+
sessions?: string[];
|
|
148
|
+
failures?: string[];
|
|
149
|
+
corrections?: string[];
|
|
150
|
+
};
|
|
151
|
+
cites?: string[];
|
|
152
|
+
body: string;
|
|
153
|
+
}
|
|
154
|
+
/** The JSON object in the reviewer's answer (bare, or inside a ```json fence). */
|
|
155
|
+
declare function parseReviewerJson(text: string): {
|
|
156
|
+
drafts: ReviewerDraft[];
|
|
157
|
+
none_reason?: string;
|
|
158
|
+
} | null;
|
|
159
|
+
type ScopeResolver = (d: ReviewerDraft) => Promise<ScopeDecision>;
|
|
160
|
+
/**
|
|
161
|
+
* Turn one reviewer draft into a draft folder, or say why not. `resolveScope` decides the final scope
|
|
162
|
+
* (skill-scope.ts: deterministic rules, then Jev); the reviewer's own choice is never trusted for it.
|
|
163
|
+
*/
|
|
164
|
+
declare function materializeDraft(repo: string, job: ReviewJob, d: ReviewerDraft, skills: SkillFile[], resolveScope?: ScopeResolver, env?: NodeJS.ProcessEnv, merge?: {
|
|
165
|
+
supersedes: string[];
|
|
166
|
+
}): Promise<{
|
|
167
|
+
id?: string;
|
|
168
|
+
rejected?: string;
|
|
169
|
+
}>;
|
|
170
|
+
declare function listDrafts(repo: string): {
|
|
171
|
+
dir: string;
|
|
172
|
+
record: DraftRecord;
|
|
173
|
+
}[];
|
|
174
|
+
interface ReviewOptions {
|
|
175
|
+
env?: NodeJS.ProcessEnv;
|
|
176
|
+
resolveScope?: ScopeResolver;
|
|
177
|
+
maxTurns?: number;
|
|
178
|
+
}
|
|
179
|
+
/** Review one queued session (the worker's review function). */
|
|
180
|
+
declare function reviewSession(job: ReviewJob, opts?: ReviewOptions): Promise<ReviewOutcome>;
|
|
181
|
+
|
|
182
|
+
export { type DraftRecord as D, type ParsedSkill as P, type ReviewOptions as R, type SkillLoopMeta as S, SKILL_SCOPES as a, SKILL_SECTIONS as b, SKILL_STATUSES as c, type SkillFile as d, type SkillFinding as e, type SkillScope as f, type SkillStatus as g, buildBrief as h, decideScope as i, draftsDir as j, findSkills as k, lintSkill as l, lintSkills as m, listDrafts as n, reviewSession as o, parseSkill as p, type ReviewerDraft as q, renderSkill as r, type ScopeResolver as s, materializeDraft as t, parseReviewerJson as u, transcriptTimeline as v };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GENERATED_BY,
|
|
3
|
+
SKILL_SCOPES,
|
|
4
|
+
SKILL_SECTIONS,
|
|
5
|
+
SKILL_STATUSES,
|
|
6
|
+
factIds,
|
|
7
|
+
findSkills,
|
|
8
|
+
isLearned,
|
|
9
|
+
lintSkill,
|
|
10
|
+
lintSkills,
|
|
11
|
+
parseSkill,
|
|
12
|
+
renderSkill
|
|
13
|
+
} from "./chunk-ZSWI3Y4Y.js";
|
|
14
|
+
import "./chunk-J7LYDRML.js";
|
|
15
|
+
import "./chunk-H2BES6FU.js";
|
|
16
|
+
import "./chunk-PZ5AY32C.js";
|
|
17
|
+
export {
|
|
18
|
+
GENERATED_BY,
|
|
19
|
+
SKILL_SCOPES,
|
|
20
|
+
SKILL_SECTIONS,
|
|
21
|
+
SKILL_STATUSES,
|
|
22
|
+
factIds,
|
|
23
|
+
findSkills,
|
|
24
|
+
isLearned,
|
|
25
|
+
lintSkill,
|
|
26
|
+
lintSkills,
|
|
27
|
+
parseSkill,
|
|
28
|
+
renderSkill
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=skill-schema-RVB3EHEF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/hooks/hooks.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "semantic-sidekick activation + mode hooks. SessionStart reconciles .mcp.json + resets mode + preloads vault context. UserPromptSubmit injects vault search results (mode-aware, fingerprinted). Stop emits mode-specific transition-capture prompts (research\u2192synthesis, outage\u2192postmortem, vault-first\u2192generic capture). PostToolUseFailure (opt-in, SEMANTIC_MEMORY_FAILURE_HOOK=1) looks up a knowledge-base fact for a failed Bash/MCP call.",
|
|
2
|
+
"description": "semantic-sidekick activation + mode hooks. SessionStart reconciles .mcp.json + resets mode + preloads vault context. UserPromptSubmit injects vault search results (mode-aware, fingerprinted). Stop emits mode-specific transition-capture prompts (research\u2192synthesis, outage\u2192postmortem, vault-first\u2192generic capture). PostToolUseFailure (opt-in, SEMANTIC_MEMORY_FAILURE_HOOK=1) looks up a knowledge-base fact for a failed Bash/MCP call. PostToolUse (Skill) and SessionEnd feed the skill loop (v1.7, off by default: SEMANTIC_MEMORY_SKILL_LOOP=1).",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionStart": [
|
|
5
5
|
{
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
{
|
|
12
12
|
"type": "command",
|
|
13
13
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/vault-context.js\""
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"type": "command",
|
|
17
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/skill-loop.js\""
|
|
14
18
|
}
|
|
15
19
|
]
|
|
16
20
|
}
|
|
@@ -45,6 +49,27 @@
|
|
|
45
49
|
}
|
|
46
50
|
]
|
|
47
51
|
}
|
|
52
|
+
],
|
|
53
|
+
"PostToolUse": [
|
|
54
|
+
{
|
|
55
|
+
"matcher": "Skill",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/skill-loop.js\""
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"SessionEnd": [
|
|
65
|
+
{
|
|
66
|
+
"hooks": [
|
|
67
|
+
{
|
|
68
|
+
"type": "command",
|
|
69
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/skill-loop.js\""
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
48
73
|
]
|
|
49
74
|
}
|
|
50
75
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""semantic-memory → Jev, through jev-loop's router (v1.7 jev mode). Stdlib only.
|
|
3
|
+
|
|
4
|
+
echo '{"cwd": "...", "caller": "semantic-memory-judge", "state": "...",
|
|
5
|
+
"questions": [{"key": "a", "instructions": "..."}]}' | python3 jev-bridge.py
|
|
6
|
+
|
|
7
|
+
Prints one JSON line: {"scores": {key: probability}, "tokens": n} or {"scores": {}, "error": "..."}.
|
|
8
|
+
Never raises, never exits non-zero: every failure is an empty answer with a reason.
|
|
9
|
+
|
|
10
|
+
The only way semantic-memory reaches TypeSafe is `router.ask`, which redacts the state and every
|
|
11
|
+
question (redact.py), caches answers, writes the usage ledger under the caller's name and enforces the
|
|
12
|
+
batch budget. This file never builds a request itself and never prints the key.
|
|
13
|
+
|
|
14
|
+
Callers: semantic-memory-hook (the failure hook) and semantic-memory-judge / semantic-memory-skill-loop.
|
|
15
|
+
jev-loop counts all three as batch (capped and cached) until it lists semantic-memory-hook as live. The
|
|
16
|
+
hook caller has its own daily ceiling here, SEMANTIC_MEMORY_JEV_DAILY_TOKENS (default 500000).
|
|
17
|
+
"""
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
CALLERS = {"semantic-memory-hook", "semantic-memory-judge", "semantic-memory-skill-loop"}
|
|
25
|
+
LIVE_CALLER = "semantic-memory-hook"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def out(obj):
|
|
29
|
+
sys.stdout.write(json.dumps(obj) + "\n")
|
|
30
|
+
sys.stdout.flush()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def live_tokens_today(logs, caller):
|
|
34
|
+
"""Tokens this caller spent today, from jev-loop's usage ledger (numbers only)."""
|
|
35
|
+
day, used = datetime.now().date().isoformat(), 0
|
|
36
|
+
for path in Path(logs).glob("jev-usage-*.jsonl"):
|
|
37
|
+
for line in path.read_text().splitlines():
|
|
38
|
+
try:
|
|
39
|
+
r = json.loads(line)
|
|
40
|
+
except ValueError:
|
|
41
|
+
continue
|
|
42
|
+
if r.get("caller") == caller and str(r.get("time", "")).startswith(day):
|
|
43
|
+
used += r.get("input_tokens", 0) + r.get("output_tokens", 0)
|
|
44
|
+
return used
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main():
|
|
48
|
+
try:
|
|
49
|
+
req = json.loads(sys.stdin.read() or "{}")
|
|
50
|
+
except ValueError as e:
|
|
51
|
+
return out({"scores": {}, "error": f"bad input: {e}"})
|
|
52
|
+
caller = req.get("caller")
|
|
53
|
+
if caller not in CALLERS:
|
|
54
|
+
return out({"scores": {}, "error": f"unknown caller {caller!r}"})
|
|
55
|
+
state, questions = req.get("state"), req.get("questions") or []
|
|
56
|
+
if not isinstance(state, str) or not state or not questions:
|
|
57
|
+
return out({"scores": {}, "error": "state and questions are required"})
|
|
58
|
+
if not all(isinstance(q, dict) and q.get("key") and isinstance(q.get("instructions"), str) for q in questions):
|
|
59
|
+
return out({"scores": {}, "error": "each question needs a key and instructions"})
|
|
60
|
+
|
|
61
|
+
jev_dir = Path(os.environ.get("JEV_LOOP_DIR") or Path.home() / ".claude" / "jev-loop")
|
|
62
|
+
if not (jev_dir / "router.py").exists():
|
|
63
|
+
return out({"scores": {}, "error": f"jev-loop not found at {jev_dir}"})
|
|
64
|
+
if not os.environ.get("TYPESAFE_API_KEY"):
|
|
65
|
+
# cron and plain shells have no Claude settings env: read the key the way jev-loop's daily.sh does
|
|
66
|
+
try:
|
|
67
|
+
settings = Path(os.environ.get("SEMANTIC_MEMORY_CLAUDE_SETTINGS") or Path.home() / ".claude" / "settings.json")
|
|
68
|
+
os.environ["TYPESAFE_API_KEY"] = json.loads(settings.read_text())["env"]["TYPESAFE_API_KEY"]
|
|
69
|
+
except Exception:
|
|
70
|
+
return out({"scores": {}, "error": "TYPESAFE_API_KEY is not set"})
|
|
71
|
+
|
|
72
|
+
# the router names its caller from argv[0] (older jev-loop) or JEV_CALLER: set both before importing it
|
|
73
|
+
os.environ["JEV_CALLER"] = caller
|
|
74
|
+
sys.argv = [caller]
|
|
75
|
+
sys.path.insert(0, str(jev_dir))
|
|
76
|
+
try:
|
|
77
|
+
import router # noqa: E402
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return out({"scores": {}, "error": f"router import failed: {type(e).__name__}: {e}"})
|
|
80
|
+
|
|
81
|
+
cwd = req.get("cwd") or os.getcwd()
|
|
82
|
+
if router.repo_for(cwd, []) == "excluded":
|
|
83
|
+
return out({"scores": {}, "error": "excluded path: nothing sent"})
|
|
84
|
+
|
|
85
|
+
if caller == LIVE_CALLER:
|
|
86
|
+
cap = int(os.environ.get("SEMANTIC_MEMORY_JEV_DAILY_TOKENS") or 500_000)
|
|
87
|
+
used = live_tokens_today(router.LOGS, caller)
|
|
88
|
+
if used >= cap:
|
|
89
|
+
return out({"scores": {}, "error": f"daily ceiling reached: {used} of {cap} tokens (SEMANTIC_MEMORY_JEV_DAILY_TOKENS)", "ceiling": True})
|
|
90
|
+
timeout = req.get("timeout_seconds")
|
|
91
|
+
if isinstance(timeout, (int, float)) and timeout > 0:
|
|
92
|
+
router.R["timeout_seconds"] = timeout
|
|
93
|
+
|
|
94
|
+
state = state[: router.R["max_prompt_chars"]]
|
|
95
|
+
items = [(q["key"], q["instructions"]) for q in questions]
|
|
96
|
+
try:
|
|
97
|
+
scores, tokens = router.ask_all(state, items)
|
|
98
|
+
except Exception as e: # JevBudget, HTTP errors, timeouts: no answer, never a crash
|
|
99
|
+
router.flush_ledger()
|
|
100
|
+
return out({"scores": {}, "error": f"{type(e).__name__}: {e}"[:300]})
|
|
101
|
+
router.flush_ledger()
|
|
102
|
+
out({"scores": {k: v for k, v in scores.items() if isinstance(v, (int, float))}, "tokens": tokens})
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
try:
|
|
107
|
+
main()
|
|
108
|
+
except BaseException as e: # noqa: BLE001 — the bridge must never crash its caller
|
|
109
|
+
out({"scores": {}, "error": f"{type(e).__name__}: {e}"[:300]})
|
package/hooks/session-start.js
CHANGED
|
@@ -18,6 +18,11 @@ import { reconcile } from "./reconcile.js";
|
|
|
18
18
|
|
|
19
19
|
async function main() {
|
|
20
20
|
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
21
|
+
// recursion guard: a skill-loop claude run (v1.7) must not reconcile or update anything
|
|
22
|
+
if (process.env.SEMANTIC_MEMORY_REVIEWER === "1") {
|
|
23
|
+
emit();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
21
26
|
|
|
22
27
|
// 1. Reconcile — no-deps, always runs.
|
|
23
28
|
try {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// skill-loop.js — the skill loop's hooks (v1.7). Off by default: with the skill loop off (or inside a
|
|
3
|
+
// skill-loop claude run) every event leaves at once, writes nothing and spawns nothing.
|
|
4
|
+
//
|
|
5
|
+
// SessionStart → once per session (startup / resume): "N skill drafts waiting"; starts the curator
|
|
6
|
+
// detached when it is due (every 7 days, repo quiet >= 2 h)
|
|
7
|
+
// PostToolUse (matcher Skill) → one usage line in .claude/.semantic-memory/skill-usage.jsonl
|
|
8
|
+
// SessionEnd → a review job when the session has signal (a failure, a correction or many
|
|
9
|
+
// tool calls), and the repo's worker started detached. Must finish well inside
|
|
10
|
+
// SessionEnd's 1.5 s: it only writes the job and spawns.
|
|
11
|
+
//
|
|
12
|
+
// Never blocks the session and never fails it: errors are swallowed (SIDEKICK_DEBUG=1 prints them).
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const DEBUG = process.env.SIDEKICK_DEBUG === "1";
|
|
19
|
+
const debug = (msg) => DEBUG && process.stderr.write(`[skill-loop] ${msg}\n`);
|
|
20
|
+
const dist = (m) => new URL(`../dist/core/${m}.js`, import.meta.url).href;
|
|
21
|
+
|
|
22
|
+
function readStdinJson() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = readFileSync(0, "utf8");
|
|
25
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function main(input = readStdinJson(), env = process.env) {
|
|
32
|
+
if (env.SEMANTIC_MEMORY_REVIEWER === "1") return;
|
|
33
|
+
const cwd = input.cwd || env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
34
|
+
const { featureOn } = await import(dist("features"));
|
|
35
|
+
if (!featureOn("skill_loop", cwd, env)) return;
|
|
36
|
+
const ev = await import(dist("skill-evidence"));
|
|
37
|
+
const root = ev.repoRoot(cwd);
|
|
38
|
+
const session_id = typeof input.session_id === "string" ? input.session_id : null;
|
|
39
|
+
if (input.hook_event_name === "SessionStart") {
|
|
40
|
+
if (input.source === "clear" || input.source === "compact") return;
|
|
41
|
+
const { listDrafts } = await import(dist("skill-review"));
|
|
42
|
+
const n = listDrafts(root).length;
|
|
43
|
+
const { curatorDue } = await import(dist("skill-curator"));
|
|
44
|
+
if (curatorDue(root)) {
|
|
45
|
+
const { spawn } = await import("node:child_process");
|
|
46
|
+
const cli = new URL("../dist/cli/index.js", import.meta.url).pathname;
|
|
47
|
+
spawn(process.execPath, [cli, "skill-loop", "curate", "--auto"], { cwd: root, env, detached: true, stdio: "ignore" }).unref();
|
|
48
|
+
}
|
|
49
|
+
if (n) process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: `[skill loop] ${n} skill draft${n > 1 ? "s" : ""} waiting for review (\`/skill-loop review\`).` } }) + "\n");
|
|
50
|
+
} else if (input.hook_event_name === "PostToolUse" && input.tool_name === "Skill") {
|
|
51
|
+
const skill = input.tool_input?.skill;
|
|
52
|
+
if (typeof skill === "string" && skill) ev.appendEvidence(root, "usage", { session_id, skill, via: "tool" }, env);
|
|
53
|
+
} else if (input.hook_event_name === "SessionEnd") {
|
|
54
|
+
const q = await import(dist("skill-queue"));
|
|
55
|
+
const job = q.sessionJob(root, input, env);
|
|
56
|
+
if (!job) return;
|
|
57
|
+
q.enqueue(job, env);
|
|
58
|
+
const { spawnWorker } = await import(dist("skill-worker"));
|
|
59
|
+
spawnWorker(root, env);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
64
|
+
main().catch((e) => debug(e.message));
|
|
65
|
+
}
|