@theglitchking/semantic-memory 1.7.0 → 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.
Files changed (41) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +20 -0
  4. package/dist/{chunk-P3GLY42T.js → chunk-2BFG4IBC.js} +11 -7
  5. package/dist/chunk-2BFG4IBC.js.map +1 -0
  6. package/dist/{chunk-NCGM36NX.js → chunk-2OMDSTSS.js} +3 -3
  7. package/dist/{chunk-BEACL25M.js → chunk-3ML5ESXI.js} +6 -6
  8. package/dist/{chunk-OT3MV7XF.js → chunk-ACZKDSMP.js} +3 -3
  9. package/dist/{chunk-MQS7JEBD.js → chunk-BSXIGPDY.js} +2 -2
  10. package/dist/{chunk-NIZLBNH4.js → chunk-M2PC64DB.js} +41 -11
  11. package/dist/chunk-M2PC64DB.js.map +1 -0
  12. package/dist/{chunk-DBFD3MTT.js → chunk-MTLYF357.js} +2 -2
  13. package/dist/{chunk-3FPUBAEW.js → chunk-N77YCMSX.js} +2 -2
  14. package/dist/{chunk-IEZVFKUT.js → chunk-PMOZKUEM.js} +17 -4
  15. package/dist/chunk-PMOZKUEM.js.map +1 -0
  16. package/dist/{chunk-CATPGINC.js → chunk-V6PSK4Y4.js} +3 -3
  17. package/dist/cli/index.js +6 -6
  18. package/dist/core/index.d.ts +2 -2
  19. package/dist/core/index.js +10 -10
  20. package/dist/core/skill-curator.js +5 -5
  21. package/dist/core/skill-queue.d.ts +7 -1
  22. package/dist/core/skill-queue.js +5 -1
  23. package/dist/core/skill-review.d.ts +1 -1
  24. package/dist/core/skill-review.js +4 -4
  25. package/dist/core/skill-worker.js +1 -1
  26. package/dist/{skill-land-XU2APPBJ.js → skill-land-EHXZW47F.js} +8 -8
  27. package/dist/{skill-merge-GXBQLBZA.js → skill-merge-HBZBALZX.js} +6 -6
  28. package/dist/{skill-review-BaW5n0Zg.d.ts → skill-review-BNvHipn9.d.ts} +5 -2
  29. package/package.json +1 -1
  30. package/dist/chunk-IEZVFKUT.js.map +0 -1
  31. package/dist/chunk-NIZLBNH4.js.map +0 -1
  32. package/dist/chunk-P3GLY42T.js.map +0 -1
  33. /package/dist/{chunk-NCGM36NX.js.map → chunk-2OMDSTSS.js.map} +0 -0
  34. /package/dist/{chunk-BEACL25M.js.map → chunk-3ML5ESXI.js.map} +0 -0
  35. /package/dist/{chunk-OT3MV7XF.js.map → chunk-ACZKDSMP.js.map} +0 -0
  36. /package/dist/{chunk-MQS7JEBD.js.map → chunk-BSXIGPDY.js.map} +0 -0
  37. /package/dist/{chunk-DBFD3MTT.js.map → chunk-MTLYF357.js.map} +0 -0
  38. /package/dist/{chunk-3FPUBAEW.js.map → chunk-N77YCMSX.js.map} +0 -0
  39. /package/dist/{chunk-CATPGINC.js.map → chunk-V6PSK4Y4.js.map} +0 -0
  40. /package/dist/{skill-land-XU2APPBJ.js.map → skill-land-EHXZW47F.js.map} +0 -0
  41. /package/dist/{skill-merge-GXBQLBZA.js.map → skill-merge-HBZBALZX.js.map} +0 -0
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Single-plugin marketplace for semantic-memory — the unified memory-layer plugin. v1.1.0 absorbs brain's strengths: AGENTS.md contract artifact, hard-gated verification sessions, multi-agent skill bundler (codex/copilot/pi), and SessionStart drift detection. v1.3 adds confidence decay — age-aware retrieval ranking with verify_note to reset the clock. All backwards-compatible with v1.0.x via deprecation shims.",
9
- "version": "1.7.0"
9
+ "version": "1.7.1"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "semantic-memory",
14
14
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, 44 tools across multi-corpus search/read/write/graph/lint/sessions/contract. v1.1.0 adds: AGENTS.md contract artifact, hard-gated verification sessions (session_start/run/finish/status), proposal-mode synthesize_note + synthesize_promote, multi-agent skill bundler (codex/copilot/pi via the new `skills` CLI), and SessionStart drift detection. v1.2 completes the hygiene line: `/healthcheck --fix` auto-remediation, `lint_vault({checks:['code_symbols']})` code-path drift, and a fresh-install CI smoke gate. v1.3 adds confidence decay: age-aware search ranking (0.5^(age/half-life), type-aware, floored, composes with load_priority), a `decay` block surfaced on decayed results, `verify_note` to reset the clock, `evergreen` opt-out, and `decay-config`/`decay-trace` CLI introspection. v1.3.1 adds local-only selection-logging telemetry (which note each search result actually got used), the `decay_candidates` lint, and a `selection-stats` CLI. v1.4.0 is the resident-expert bridge: a learned human→artifact lexicon (`manage_lexicon`) with propose-and-confirm alias capture, verbatim symptom capture + symptom-keyed indexing (terse symptom queries match the note), Tier-1 lexicon query expansion in the hook, archive path-class down-weight, injection hygiene (score-gate + dedup + top-3 + compact instructions), section-targeted `read_note`, and opt-in conditional tool registration. v1.5.0 is the expert-character layer: entity dossiers (`manage_dossier`) — per-component living notes (purpose / failure modes / knobs / incident log / current state) that accrete in place, with two-hop retrieval (utterance→entity→dossier injected first); usage-feedback ranking that boosts repeatedly-cited notes (capped, composes with decay/path-class) plus a `decoys` lint for retrieved-but-never-cited notes (surfaced, never auto-down-ranked); session paging (Stop drafts a durable session digest as a proposal, SessionStart pages in a curated digest instead of a broad sweep); and a speaker profile (`manage_profile`) that models how the human communicates — severity calibration, chronic omissions, verbosity, shorthand — injected at SessionStart. Backwards-compatible with v1.0.x — every previous tool name remains callable through deprecation shims (removed in v2.0.0).",
15
- "version": "1.7.0",
15
+ "version": "1.7.1",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "semantic-memory",
3
3
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, multiple typed corpora (vault / code / plans / docs / research / project-map), unified cross-corpus knowledge graph, conditional tool registration, in-process drift detection, and human↔code translation. Rebrand of `semantic-sidekick` at v1.0.0 — see CHANGELOG for migration.",
4
- "version": "1.7.0",
4
+ "version": "1.7.1",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to semantic-memory (formerly semantic-sidekick) will be documented here.
4
4
 
5
+ ## [1.7.1] - 2026-10-28 — Skill loop queue fixes
6
+
7
+ These three fixes come from the skill loop's first days of real use.
8
+
9
+ ### Fixed
10
+
11
+ - **Sessions in temporary directories are no longer reviewed.** Benchmark and test runs that start real `claude`
12
+ sessions in `mkdtemp` directories (jev-loop's speed checks) were queued like real work. On 2026-10-27 they used
13
+ all 10 of the day's reviews, so real sessions waited a day. A repo under the system temp directory (`os.tmpdir()`,
14
+ `/tmp`, `/var/tmp`) now gets no job. `SEMANTIC_MEMORY_SKILL_LOOP_ALLOW_TMP=1` turns this back on for scratch repos
15
+ and tests.
16
+ - **A worker started from a cron session now finds `claude`.** The detached worker inherits the session's PATH. Under
17
+ cron that PATH has no `~/.local/bin`, so the review failed with `spawn claude ENOENT` and the job stayed queued. The
18
+ worker now looks for `claude` on PATH, then in `~/.local/bin/claude` and `~/.claude/local/claude`.
19
+ `SEMANTIC_MEMORY_CLAUDE_BIN` still wins over all of these.
20
+ - **A resumed session's new evidence is reviewed.** A session was reviewed once, when it first ended, and never
21
+ again, so a correction given after a `--resume` was lost. When a reviewed session ends again with failures or
22
+ corrections recorded after its review, it gets one follow-up review. That review sees only the evidence and the
23
+ part of the transcript after the last review. Tool-call volume alone never re-queues a session.
24
+
5
25
  ## [1.7.0] - 2026-10-26 — The skill loop and jev mode
6
26
 
7
27
  Two new features, **both off by default**, each with its own switch. With both off, 1.7.0 behaves like 1.6.1.
@@ -2,10 +2,10 @@ import {
2
2
  decideScope,
3
3
  indexSkill,
4
4
  promotionPeers
5
- } from "./chunk-3FPUBAEW.js";
5
+ } from "./chunk-N77YCMSX.js";
6
6
  import {
7
7
  runClaude
8
- } from "./chunk-IEZVFKUT.js";
8
+ } from "./chunk-PMOZKUEM.js";
9
9
  import {
10
10
  factIds,
11
11
  findSkills,
@@ -73,7 +73,7 @@ Answer with one JSON object and nothing else:
73
73
  var draftsDir = (repo) => join(stateDir(repo), "skill-drafts");
74
74
  var BRIEF_MAX = 4e4;
75
75
  var TIMELINE_MAX = 120;
76
- function transcriptTimeline(path, max = TIMELINE_MAX) {
76
+ function transcriptTimeline(path, max = TIMELINE_MAX, since) {
77
77
  if (!path) return [];
78
78
  let raw = "";
79
79
  try {
@@ -92,6 +92,8 @@ function transcriptTimeline(path, max = TIMELINE_MAX) {
92
92
  continue;
93
93
  }
94
94
  const content = e?.message?.content;
95
+ const shown = !since || String(e?.timestamp ?? "") > since;
96
+ if (!shown && e?.type !== "assistant") continue;
95
97
  if (e?.type === "user" && typeof content === "string" && !e.isMeta) {
96
98
  if (!content.trimStart().startsWith("<")) out.push(`USER: ${content.slice(0, 600)}`);
97
99
  } else if (e?.type === "assistant" && Array.isArray(content)) {
@@ -125,14 +127,16 @@ function topLevel(repo) {
125
127
  }
126
128
  function buildBrief(job, skills = findSkills(job.repo)) {
127
129
  const repo = job.repo;
128
- const failures = readEvidence(repo, "failures").filter((f) => f.session_id === job.session_id);
129
- const corrections = readEvidence(repo, "corrections").filter((c) => c.session_id === job.session_id);
130
+ const mine = (r2) => r2.session_id === job.session_id && (!job.since || String(r2.ts ?? "") > job.since);
131
+ const failures = readEvidence(repo, "failures").filter(mine);
132
+ const corrections = readEvidence(repo, "corrections").filter(mine);
130
133
  const used = new Set(job.evidence.skills_used);
131
134
  const r = (t) => redact(t, "broad").text;
132
135
  const lines = [
133
136
  `# Session ${job.session_id}`,
134
137
  `Repository: ${basename(repo)} \u2014 top level: ${topLevel(repo).join(", ")}`,
135
138
  `Why reviewed: ${job.reason}`,
139
+ ...job.since ? [`Follow-up review: this session was reviewed at ${job.since} and then resumed. Below is only what happened after that; judge the new evidence.`] : [],
136
140
  "",
137
141
  "## Failures (from the evidence log)",
138
142
  ...failures.length ? failures.map((f) => `- id ${f.id}: \`${r(String(f.command))}\`
@@ -149,7 +153,7 @@ function buildBrief(job, skills = findSkills(job.repo)) {
149
153
  [...factIds(repo)].slice(0, 200).join(", ") || "(none)",
150
154
  "",
151
155
  "## Timeline (prompts, tool calls, errors)",
152
- ...transcriptTimeline(job.transcript_path).map(r)
156
+ ...transcriptTimeline(job.transcript_path, TIMELINE_MAX, job.since).map(r)
153
157
  ];
154
158
  let brief = lines.join("\n");
155
159
  if (brief.length > BRIEF_MAX) brief = brief.slice(0, BRIEF_MAX) + "\n(brief cut at its size cap)";
@@ -288,4 +292,4 @@ export {
288
292
  listDrafts,
289
293
  reviewSession
290
294
  };
291
- //# sourceMappingURL=chunk-P3GLY42T.js.map
295
+ //# sourceMappingURL=chunk-2BFG4IBC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/skill-review.ts","../src/core/skill-review-prompt.ts"],"sourcesContent":["/**\n * The skill loop's reviewer (v1.7): one finished session in, zero to two skill drafts out.\n *\n * 1. A brief: the session's prompts, tool calls and errors (from the transcript, read fail-soft: its format\n * is internal to Claude Code), plus the evidence logs, the skills this repo loads and its KB fact ids.\n * Redacted (broad) and capped before the model sees it.\n * 2. A fenced `claude -p` (Read / Grep / Glob on the repo only; see claude-run.ts) answers JSON.\n * 3. Each draft is rendered, redacted, linted (skill-schema.ts) and written to\n * .claude/.semantic-memory/skill-drafts/<id>/ — or rejected with its reasons. Nothing is written anywhere\n * else; landing a draft is a person's call (`skill-loop review`).\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { redact } from \"./redact.js\";\nimport { readEvidence, stateDir } from \"./skill-evidence.js\";\nimport { factIds, findSkills, lintSkill, renderSkill, type SkillFile, type SkillLoopMeta, type SkillScope } from \"./skill-schema.js\";\nimport { runClaude } from \"./claude-run.js\";\nimport { REVIEW_PROMPT } from \"./skill-review-prompt.js\";\nimport type { ReviewJob } from \"./skill-queue.js\";\nimport type { ReviewOutcome } from \"./skill-worker.js\";\nimport { decideScope, indexSkill, promotionPeers, type ScopeDecision } from \"./skill-scope.js\";\n\nexport const draftsDir = (repo: string) => join(stateDir(repo), \"skill-drafts\");\nconst BRIEF_MAX = 40_000;\nconst TIMELINE_MAX = 120;\n\nexport interface DraftRecord {\n id: string;\n kind: \"new\" | \"patch\" | \"merge\";\n name: string;\n scope: SkillScope;\n /** patch: the SKILL.md it replaces, and that file's sha256 when the draft was made */\n target?: string;\n base_sha256?: string;\n session_id: string;\n created: string;\n warnings: string[];\n /** directory scope: where it lands (repo-relative subdirectory) */\n directory?: string;\n scope_why?: string;\n /** Jev thinks it holds in any repo */\n global_candidate?: boolean;\n /** other repos with the same lesson: review offers one global skill replacing the copies */\n promotion?: { repo: string; path: string }[];\n /** merge: the SKILL.md files the merged skill replaces (archived when it lands) */\n supersedes?: string[];\n /** filled by skill evals (Phase 14) when they run at approval time */\n evals?: Record<string, unknown>;\n}\n\n// --- the brief ---\n\n/**\n * The session's timeline from its transcript: prompts, tool calls, and the errors they returned. Fail-soft.\n * `since`: only entries after it (a follow-up review of a resumed session).\n */\nexport function transcriptTimeline(path: string | null, max = TIMELINE_MAX, since?: string): string[] {\n if (!path) return [];\n let raw = \"\";\n try {\n if (statSync(path).size > 200 * 1024 * 1024) return [\"(transcript too large to read)\"];\n raw = readFileSync(path, \"utf8\");\n } catch {\n return [];\n }\n const out: string[] = [];\n const calls = new Map<string, string>();\n for (const line of raw.split(\"\\n\")) {\n let e: any;\n try {\n e = JSON.parse(line);\n } catch {\n continue;\n }\n const content = e?.message?.content;\n // calls are remembered from the whole transcript so a result after `since` still names its call\n const shown = !since || String(e?.timestamp ?? \"\") > since;\n if (!shown && e?.type !== \"assistant\") continue;\n if (e?.type === \"user\" && typeof content === \"string\" && !e.isMeta) {\n // wrappers (slash-command echoes, task notifications, local command output) are not the user talking\n if (!content.trimStart().startsWith(\"<\")) out.push(`USER: ${content.slice(0, 600)}`);\n } else if (e?.type === \"assistant\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_use\") continue;\n const input = c.input ?? {};\n const what = typeof input.command === \"string\" ? input.command : typeof input.file_path === \"string\" ? input.file_path : typeof input.skill === \"string\" ? input.skill : JSON.stringify(input);\n calls.set(c.id, `${c.name}: ${String(what).slice(0, 300)}`);\n }\n } else if (e?.type === \"user\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_result\") continue;\n const call = calls.get(c.tool_use_id) ?? \"tool\";\n const text = typeof c.content === \"string\" ? c.content : Array.isArray(c.content) ? c.content.map((x: any) => x?.text ?? \"\").join(\" \") : \"\";\n out.push(c.is_error ? `TOOL ${call}\\n → ERROR (${c.tool_use_id}): ${text.slice(0, 500)}` : `TOOL ${call} → ok`);\n }\n }\n }\n // keep the end of long sessions (where the fix landed) and every error\n if (out.length <= max) return out;\n const errors = out.filter((l) => l.includes(\"→ ERROR\")).slice(-Math.floor(max / 2));\n const tail = out.slice(-(max - errors.length)).filter((l) => !errors.includes(l));\n return [`(${out.length - errors.length - tail.length} earlier entries omitted)`, ...errors, ...tail];\n}\n\nfunction topLevel(repo: string): string[] {\n try {\n return readdirSync(repo).filter((f) => !f.startsWith(\".\") || f === \".claude\" || f === \".documentation\").slice(0, 60);\n } catch {\n return [];\n }\n}\n\nexport function buildBrief(job: ReviewJob, skills: SkillFile[] = findSkills(job.repo)): string {\n const repo = job.repo;\n const mine = (r: Record<string, any>) => r.session_id === job.session_id && (!job.since || String(r.ts ?? \"\") > job.since);\n const failures = readEvidence(repo, \"failures\").filter(mine);\n const corrections = readEvidence(repo, \"corrections\").filter(mine);\n const used = new Set(job.evidence.skills_used);\n // free text is redacted (broad); the ids the reviewer must cite back (session, failure ids) are not, or broad\n // redaction would mask them too. The evidence logs were redacted when written.\n const r = (t: string) => redact(t, \"broad\").text;\n const lines = [\n `# Session ${job.session_id}`,\n `Repository: ${basename(repo)} — top level: ${topLevel(repo).join(\", \")}`,\n `Why reviewed: ${job.reason}`,\n ...(job.since ? [`Follow-up review: this session was reviewed at ${job.since} and then resumed. Below is only what happened after that; judge the new evidence.`] : []),\n \"\",\n \"## Failures (from the evidence log)\",\n ...(failures.length ? failures.map((f) => `- id ${f.id}: \\`${r(String(f.command))}\\`\\n error: ${r(String(f.error).slice(0, 500))}`) : [\"(none)\"]),\n \"\",\n \"## Corrections the user gave\",\n ...(corrections.length ? corrections.map((c, i) => `- correction ${i + 1} (${c.cue}): ${r(String(c.excerpt))}`) : [\"(none)\"]),\n \"\",\n \"## Skills this repository loads\",\n ...(skills.length\n ? skills.map((s) => `- ${s.skill.name || basename(s.dir)} (${s.scope}; learned: ${s.learned}${s.skill.meta?.pinned ? \"; pinned\" : \"\"}${used.has(s.skill.name) ? \"; USED this session\" : \"\"}): ${r(s.skill.description.slice(0, 200))}`)\n : [\"(none)\"]),\n ...skills\n .filter((s) => s.learned && used.has(s.skill.name))\n .flatMap((s) => [\"\", `### Current text of the learned skill ${s.skill.name} (used this session)`, r(s.skill.body.slice(0, 4000))]),\n \"\",\n \"## Knowledge-base fact ids (for cites)\",\n [...factIds(repo)].slice(0, 200).join(\", \") || \"(none)\",\n \"\",\n \"## Timeline (prompts, tool calls, errors)\",\n ...transcriptTimeline(job.transcript_path, TIMELINE_MAX, job.since).map(r),\n ];\n let brief = lines.join(\"\\n\");\n if (brief.length > BRIEF_MAX) brief = brief.slice(0, BRIEF_MAX) + \"\\n(brief cut at its size cap)\";\n return brief;\n}\n\n// --- the answer ---\n\nexport interface ReviewerDraft {\n kind: \"new\" | \"patch\";\n target?: string;\n name: string;\n description: string;\n scope: string;\n applies_to?: { paths?: string[]; commands?: string[] };\n triggers?: string[];\n provenance?: { sessions?: string[]; failures?: string[]; corrections?: string[] };\n cites?: string[];\n body: string;\n}\n\n/** The JSON object in the reviewer's answer (bare, or inside a ```json fence). */\nexport function parseReviewerJson(text: string): { drafts: ReviewerDraft[]; none_reason?: string } | null {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const candidates = [fenced?.[1], text, text.slice(text.indexOf(\"{\"), text.lastIndexOf(\"}\") + 1)].filter(Boolean) as string[];\n for (const c of candidates) {\n try {\n const v = JSON.parse(c.trim());\n if (v && Array.isArray(v.drafts)) return v;\n } catch {}\n }\n return null;\n}\n\n// a placeholder the model copied from redacted text is not evidence\nconst strs = (v: unknown) => (Array.isArray(v) ? v.filter((x): x is string => typeof x === \"string\" && !x.includes(\"[REDACTED]\")) : []);\nconst sha256 = (s: string) => createHash(\"sha256\").update(s).digest(\"hex\");\n\nexport type ScopeResolver = (d: ReviewerDraft) => Promise<ScopeDecision>;\n\n/**\n * Turn one reviewer draft into a draft folder, or say why not. `resolveScope` decides the final scope\n * (skill-scope.ts: deterministic rules, then Jev); the reviewer's own choice is never trusted for it.\n */\nexport async function materializeDraft(\n repo: string,\n job: ReviewJob,\n d: ReviewerDraft,\n skills: SkillFile[],\n resolveScope: ScopeResolver = (x) => decideScope(repo, x),\n env = process.env,\n merge?: { supersedes: string[] },\n): Promise<{ id?: string; rejected?: string }> {\n if (!d || typeof d.name !== \"string\" || typeof d.body !== \"string\" || typeof d.description !== \"string\") return { rejected: \"malformed draft\" };\n let target: SkillFile | undefined;\n if (d.kind === \"patch\") {\n target = skills.find((s) => s.skill.name === d.target);\n if (!target) return { rejected: `patch target \"${d.target}\" not found` };\n if (!target.learned) return { rejected: `\"${d.target}\" was not written by the skill loop: never patched` };\n if (target.skill.meta?.pinned) return { rejected: `\"${d.target}\" is pinned` };\n }\n const prov = {\n sessions: [...new Set([job.session_id, ...strs(d.provenance?.sessions)])],\n failures: strs(d.provenance?.failures),\n corrections: strs(d.provenance?.corrections),\n };\n if (prov.failures.length + prov.corrections.length === 0 && job.evidence.failures.length + job.evidence.corrections === 0) {\n return { rejected: \"no failure or correction behind it\" };\n }\n // a patch keeps its skill's scope; a new skill's comes from the resolver\n const decision: ScopeDecision = target\n ? { scope: target.scope === \"directory\" ? \"directory\" : \"project\", globalCandidate: false, why: \"patch: the skill's own scope\" }\n : await resolveScope(d);\n const scope: SkillScope = target?.scope ?? decision.scope;\n const paths = strs(d.applies_to?.paths);\n if (decision.directory && !paths.includes(decision.directory)) paths.unshift(decision.directory);\n const name = d.kind === \"patch\" ? target!.skill.name : d.name;\n const meta: SkillLoopMeta = {\n schema: 1,\n scope,\n status: \"draft\",\n pinned: false,\n triggers: strs(d.triggers),\n applies_to: { paths, commands: strs(d.applies_to?.commands) },\n provenance: prov,\n cites: strs(d.cites),\n supersedes: merge ? merge.supersedes.map((p) => basename(join(p, \"..\"))) : (target?.skill.meta?.supersedes ?? []),\n };\n // precise: a skill is prose and code, broad would mangle ordinary identifiers\n const text = redact(renderSkill({ name, description: d.description, meta, body: d.body }), \"precise\").text;\n const findings = lintSkill(join(draftsDir(repo), name, \"SKILL.md\"), text, { projectRoot: repo, others: skills.filter((s) => s.path !== target?.path) }, name);\n const errors = findings.filter((f) => f.severity === \"error\");\n if (errors.length) return { rejected: `lint: ${errors.map((f) => `${f.rule}: ${f.message}`).join(\"; \")}` };\n const id = `${name}-${sha256(text).slice(0, 8)}`;\n const dir = join(draftsDir(repo), id);\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"SKILL.md\"), text);\n const record: DraftRecord = {\n id,\n kind: merge ? \"merge\" : d.kind === \"patch\" ? \"patch\" : \"new\",\n name,\n scope,\n ...(target ? { target: target.path, base_sha256: sha256(readFileSync(target.path, \"utf8\")) } : {}),\n session_id: job.session_id,\n created: new Date().toISOString(),\n warnings: findings.filter((f) => f.severity === \"warn\").map((f) => `${f.rule}: ${f.message}`),\n ...(decision.directory ? { directory: decision.directory } : {}),\n scope_why: decision.why,\n ...(decision.globalCandidate ? { global_candidate: true } : {}),\n ...(merge ? { supersedes: merge.supersedes } : {}),\n };\n if (!target && !merge) {\n const peers = promotionPeers(repo, name, env);\n if (peers.length) record.promotion = peers.map((p) => ({ repo: p.repo, path: p.path }));\n indexSkill({ repo, name, path: join(dir, \"SKILL.md\"), status: \"draft\", ...(decision.globalCandidate ? { global_candidate: true } : {}) }, env);\n }\n writeFileSync(join(dir, \"draft.json\"), JSON.stringify(record, null, 1));\n return { id };\n}\n\nexport function listDrafts(repo: string): { dir: string; record: DraftRecord }[] {\n let ids: string[] = [];\n try {\n ids = readdirSync(draftsDir(repo));\n } catch {\n return [];\n }\n const out: { dir: string; record: DraftRecord }[] = [];\n for (const id of ids) {\n const dir = join(draftsDir(repo), id);\n try {\n if (existsSync(join(dir, \"SKILL.md\"))) out.push({ dir, record: JSON.parse(readFileSync(join(dir, \"draft.json\"), \"utf8\")) });\n } catch {}\n }\n return out.sort((a, b) => a.record.created.localeCompare(b.record.created));\n}\n\nexport interface ReviewOptions {\n env?: NodeJS.ProcessEnv;\n resolveScope?: ScopeResolver;\n maxTurns?: number;\n}\n\n/** Review one queued session (the worker's review function). */\nexport async function reviewSession(job: ReviewJob, opts: ReviewOptions = {}): Promise<ReviewOutcome> {\n const skills = findSkills(job.repo);\n const brief = buildBrief(job, skills);\n const run = await runClaude({\n prompt: `${REVIEW_PROMPT}\\n\\n---\\n\\n${brief}`,\n cwd: job.repo,\n tools: [\"Read\", \"Grep\", \"Glob\"],\n maxTurns: opts.maxTurns ?? 8,\n output: \"json\",\n timeoutMs: 10 * 60_000,\n env: opts.env,\n });\n if (!run.ok && !run.result) throw new Error(run.error ?? \"reviewer run failed\");\n const answer = parseReviewerJson(run.result);\n if (!answer) return { drafts: [], rejected: [{ reason: \"reviewer answer was not JSON\" }], costUsd: run.costUsd };\n const drafts: string[] = [];\n const rejected: { reason: string }[] = [];\n for (const d of answer.drafts.slice(0, 2)) {\n const env = opts.env ?? process.env;\n const r = await materializeDraft(job.repo, job, d, skills, opts.resolveScope ?? ((x) => decideScope(job.repo, x, env)), env);\n if (r.id) drafts.push(r.id);\n else rejected.push({ reason: `${d?.name ?? \"?\"}: ${r.rejected}` });\n }\n return { drafts, rejected, costUsd: run.costUsd, note: answer.drafts.length ? undefined : answer.none_reason?.slice(0, 200) };\n}\n","/**\n * The skill loop reviewer's fixed instructions (v1.7). The brief (what happened in one session) follows\n * them on stdin. The reviewer can read the repo (Read / Grep / Glob) and nothing else; it answers with\n * JSON, and the worker — not the model — writes drafts, after redaction and lint.\n */\nexport const REVIEW_PROMPT = `You review one finished Claude Code session in this repository and decide whether it taught a\nreusable procedure worth saving as a skill — a short, step-by-step playbook a future session should follow.\n\nDraft only from the evidence in the brief below: a command that failed and the fix that worked, a correction\nthe user gave, or a learned skill that was used and then failed or was corrected. Never invent steps, commands,\npaths or facts that the brief or the repository does not show. Most sessions teach nothing reusable: then\nanswer with no drafts. A draft is worth it only when the same situation is likely to come up again here.\n\nKinds of draft:\n- \"new\": a procedure no existing skill covers. Check the existing skills in the brief first.\n- \"patch\": an existing learned skill (listed with \"learned: true\") was used in this session and then failed or\n was corrected: give its full revised body. Never patch a skill that is pinned or not learned.\n\nEach draft:\n- name: kebab-case, specific (\"restart-vite-dev-server\", not \"dev-helper\").\n- description: what it does and when to use it, one or two sentences, starting with the action; include\n \"Use when ...\" with the literal error, command or request that should load it. Under 300 characters.\n- scope: \"project\" (about this repository) or \"directory\" (only about one subdirectory; give it in applies_to.paths).\n- applies_to: { paths: [repo-relative paths that exist], commands: [program names the steps run] }.\n- triggers: the literal error strings, commands or user phrases from the brief that this skill answers.\n- provenance: { sessions: [the session id], failures: [failure ids from the brief it came from], corrections: [correction\n numbers from the brief, as strings] } — at least one failure or correction.\n- cites: knowledge-base fact ids from the brief's list that the steps rely on (only ids that are listed).\n- body: markdown with exactly these sections, in order:\n ## When to use — the situation, in two or three lines.\n ## Steps — numbered, concrete commands or actions as the session finally did them.\n ## Pitfalls — what went wrong in the session and why; each line names the failure id or fact id it comes from.\n ## Verify — one command whose exit code proves it worked, in a fenced code block; or, if none exists, one\n sentence a reviewer can check.\n\nHard rules:\n- No secrets, tokens, passwords, keys, personal data, customer data, names of people or money amounts, even if\n the brief shows them. Use placeholders like <token>.\n- At most 2 drafts. No duplicates of existing skills.\n- Do not write or change any file. You may read files in this repository to get paths and commands right.\n\nAnswer with one JSON object and nothing else:\n{\"drafts\": [{\"kind\": \"new\" | \"patch\", \"target\": \"<existing skill name, patch only>\", \"name\": \"...\",\n \"description\": \"...\", \"scope\": \"project\" | \"directory\", \"applies_to\": {\"paths\": [], \"commands\": []},\n \"triggers\": [], \"provenance\": {\"sessions\": [], \"failures\": [], \"corrections\": []}, \"cites\": [], \"body\": \"...\"}],\n \"none_reason\": \"<when drafts is empty: one line on why>\"}\n`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAWA,SAAS,kBAAkB;AAC3B,SAAS,YAAY,WAAW,aAAa,cAAc,UAAU,qBAAqB;AAC1F,SAAS,UAAU,YAAY;;;ACRxB,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ADkBtB,IAAM,YAAY,CAAC,SAAiB,KAAK,SAAS,IAAI,GAAG,cAAc;AAC9E,IAAM,YAAY;AAClB,IAAM,eAAe;AAgCd,SAAS,mBAAmB,MAAqB,MAAM,cAAc,OAA0B;AACpG,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,MAAI,MAAM;AACV,MAAI;AACF,QAAI,SAAS,IAAI,EAAE,OAAO,MAAM,OAAO,KAAM,QAAO,CAAC,gCAAgC;AACrF,UAAM,aAAa,MAAM,MAAM;AAAA,EACjC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAgB,CAAC;AACvB,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,QAAI;AACJ,QAAI;AACF,UAAI,KAAK,MAAM,IAAI;AAAA,IACrB,QAAQ;AACN;AAAA,IACF;AACA,UAAM,UAAU,GAAG,SAAS;AAE5B,UAAM,QAAQ,CAAC,SAAS,OAAO,GAAG,aAAa,EAAE,IAAI;AACrD,QAAI,CAAC,SAAS,GAAG,SAAS,YAAa;AACvC,QAAI,GAAG,SAAS,UAAU,OAAO,YAAY,YAAY,CAAC,EAAE,QAAQ;AAElE,UAAI,CAAC,QAAQ,UAAU,EAAE,WAAW,GAAG,EAAG,KAAI,KAAK,SAAS,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,IACrF,WAAW,GAAG,SAAS,eAAe,MAAM,QAAQ,OAAO,GAAG;AAC5D,iBAAW,KAAK,SAAS;AACvB,YAAI,GAAG,SAAS,WAAY;AAC5B,cAAM,QAAQ,EAAE,SAAS,CAAC;AAC1B,cAAM,OAAO,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,KAAK,UAAU,KAAK;AAC7L,cAAM,IAAI,EAAE,IAAI,GAAG,EAAE,IAAI,KAAK,OAAO,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,MAC5D;AAAA,IACF,WAAW,GAAG,SAAS,UAAU,MAAM,QAAQ,OAAO,GAAG;AACvD,iBAAW,KAAK,SAAS;AACvB,YAAI,GAAG,SAAS,cAAe;AAC/B,cAAM,OAAO,MAAM,IAAI,EAAE,WAAW,KAAK;AACzC,cAAM,OAAO,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU,MAAM,QAAQ,EAAE,OAAO,IAAI,EAAE,QAAQ,IAAI,CAAC,MAAW,GAAG,QAAQ,EAAE,EAAE,KAAK,GAAG,IAAI;AACzI,YAAI,KAAK,EAAE,WAAW,QAAQ,IAAI;AAAA,kBAAgB,EAAE,WAAW,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC,KAAK,QAAQ,IAAI,YAAO;AAAA,MACjH;AAAA,IACF;AAAA,EACF;AAEA,MAAI,IAAI,UAAU,IAAK,QAAO;AAC9B,QAAM,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,cAAS,CAAC,EAAE,MAAM,CAAC,KAAK,MAAM,MAAM,CAAC,CAAC;AAClF,QAAM,OAAO,IAAI,MAAM,EAAE,MAAM,OAAO,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,SAAS,CAAC,CAAC;AAChF,SAAO,CAAC,IAAI,IAAI,SAAS,OAAO,SAAS,KAAK,MAAM,6BAA6B,GAAG,QAAQ,GAAG,IAAI;AACrG;AAEA,SAAS,SAAS,MAAwB;AACxC,MAAI;AACF,WAAO,YAAY,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,KAAK,MAAM,aAAa,MAAM,gBAAgB,EAAE,MAAM,GAAG,EAAE;AAAA,EACrH,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,WAAW,KAAgB,SAAsB,WAAW,IAAI,IAAI,GAAW;AAC7F,QAAM,OAAO,IAAI;AACjB,QAAM,OAAO,CAACA,OAA2BA,GAAE,eAAe,IAAI,eAAe,CAAC,IAAI,SAAS,OAAOA,GAAE,MAAM,EAAE,IAAI,IAAI;AACpH,QAAM,WAAW,aAAa,MAAM,UAAU,EAAE,OAAO,IAAI;AAC3D,QAAM,cAAc,aAAa,MAAM,aAAa,EAAE,OAAO,IAAI;AACjE,QAAM,OAAO,IAAI,IAAI,IAAI,SAAS,WAAW;AAG7C,QAAM,IAAI,CAAC,MAAc,OAAO,GAAG,OAAO,EAAE;AAC5C,QAAM,QAAQ;AAAA,IACZ,aAAa,IAAI,UAAU;AAAA,IAC3B,eAAe,SAAS,IAAI,CAAC,sBAAiB,SAAS,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IACvE,iBAAiB,IAAI,MAAM;AAAA,IAC3B,GAAI,IAAI,QAAQ,CAAC,kDAAkD,IAAI,KAAK,oFAAoF,IAAI,CAAC;AAAA,IACrK;AAAA,IACA;AAAA,IACA,GAAI,SAAS,SAAS,SAAS,IAAI,CAAC,MAAM,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAAA,WAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ;AAAA,IAChJ;AAAA,IACA;AAAA,IACA,GAAI,YAAY,SAAS,YAAY,IAAI,CAAC,GAAG,MAAM,gBAAgB,IAAI,CAAC,KAAK,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ;AAAA,IAC3H;AAAA,IACA;AAAA,IACA,GAAI,OAAO,SACP,OAAO,IAAI,CAAC,MAAM,KAAK,EAAE,MAAM,QAAQ,SAAS,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,cAAc,EAAE,OAAO,GAAG,EAAE,MAAM,MAAM,SAAS,aAAa,EAAE,GAAG,KAAK,IAAI,EAAE,MAAM,IAAI,IAAI,wBAAwB,EAAE,MAAM,EAAE,EAAE,MAAM,YAAY,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,IACpO,CAAC,QAAQ;AAAA,IACb,GAAG,OACA,OAAO,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,EAAE,MAAM,IAAI,CAAC,EACjD,QAAQ,CAAC,MAAM,CAAC,IAAI,yCAAyC,EAAE,MAAM,IAAI,wBAAwB,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,GAAI,CAAC,CAAC,CAAC;AAAA,IACnI;AAAA,IACA;AAAA,IACA,CAAC,GAAG,QAAQ,IAAI,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,KAAK,IAAI,KAAK;AAAA,IAC/C;AAAA,IACA;AAAA,IACA,GAAG,mBAAmB,IAAI,iBAAiB,cAAc,IAAI,KAAK,EAAE,IAAI,CAAC;AAAA,EAC3E;AACA,MAAI,QAAQ,MAAM,KAAK,IAAI;AAC3B,MAAI,MAAM,SAAS,UAAW,SAAQ,MAAM,MAAM,GAAG,SAAS,IAAI;AAClE,SAAO;AACT;AAkBO,SAAS,kBAAkB,MAAwE;AACxG,QAAM,SAAS,KAAK,MAAM,8BAA8B;AACxD,QAAM,aAAa,CAAC,SAAS,CAAC,GAAG,MAAM,KAAK,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,YAAY,GAAG,IAAI,CAAC,CAAC,EAAE,OAAO,OAAO;AAC/G,aAAW,KAAK,YAAY;AAC1B,QAAI;AACF,YAAM,IAAI,KAAK,MAAM,EAAE,KAAK,CAAC;AAC7B,UAAI,KAAK,MAAM,QAAQ,EAAE,MAAM,EAAG,QAAO;AAAA,IAC3C,QAAQ;AAAA,IAAC;AAAA,EACX;AACA,SAAO;AACT;AAGA,IAAM,OAAO,CAAC,MAAgB,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,CAAC,EAAE,SAAS,YAAY,CAAC,IAAI,CAAC;AACrI,IAAM,SAAS,CAAC,MAAc,WAAW,QAAQ,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK;AAQzE,eAAsB,iBACpB,MACA,KACA,GACA,QACA,eAA8B,CAAC,MAAM,YAAY,MAAM,CAAC,GACxD,MAAM,QAAQ,KACd,OAC6C;AAC7C,MAAI,CAAC,KAAK,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,gBAAgB,SAAU,QAAO,EAAE,UAAU,kBAAkB;AAC9I,MAAI;AACJ,MAAI,EAAE,SAAS,SAAS;AACtB,aAAS,OAAO,KAAK,CAAC,MAAM,EAAE,MAAM,SAAS,EAAE,MAAM;AACrD,QAAI,CAAC,OAAQ,QAAO,EAAE,UAAU,iBAAiB,EAAE,MAAM,cAAc;AACvE,QAAI,CAAC,OAAO,QAAS,QAAO,EAAE,UAAU,IAAI,EAAE,MAAM,qDAAqD;AACzG,QAAI,OAAO,MAAM,MAAM,OAAQ,QAAO,EAAE,UAAU,IAAI,EAAE,MAAM,cAAc;AAAA,EAC9E;AACA,QAAM,OAAO;AAAA,IACX,UAAU,CAAC,GAAG,oBAAI,IAAI,CAAC,IAAI,YAAY,GAAG,KAAK,EAAE,YAAY,QAAQ,CAAC,CAAC,CAAC;AAAA,IACxE,UAAU,KAAK,EAAE,YAAY,QAAQ;AAAA,IACrC,aAAa,KAAK,EAAE,YAAY,WAAW;AAAA,EAC7C;AACA,MAAI,KAAK,SAAS,SAAS,KAAK,YAAY,WAAW,KAAK,IAAI,SAAS,SAAS,SAAS,IAAI,SAAS,gBAAgB,GAAG;AACzH,WAAO,EAAE,UAAU,qCAAqC;AAAA,EAC1D;AAEA,QAAM,WAA0B,SAC5B,EAAE,OAAO,OAAO,UAAU,cAAc,cAAc,WAAW,iBAAiB,OAAO,KAAK,+BAA+B,IAC7H,MAAM,aAAa,CAAC;AACxB,QAAM,QAAoB,QAAQ,SAAS,SAAS;AACpD,QAAM,QAAQ,KAAK,EAAE,YAAY,KAAK;AACtC,MAAI,SAAS,aAAa,CAAC,MAAM,SAAS,SAAS,SAAS,EAAG,OAAM,QAAQ,SAAS,SAAS;AAC/F,QAAM,OAAO,EAAE,SAAS,UAAU,OAAQ,MAAM,OAAO,EAAE;AACzD,QAAM,OAAsB;AAAA,IAC1B,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU,KAAK,EAAE,QAAQ;AAAA,IACzB,YAAY,EAAE,OAAO,UAAU,KAAK,EAAE,YAAY,QAAQ,EAAE;AAAA,IAC5D,YAAY;AAAA,IACZ,OAAO,KAAK,EAAE,KAAK;AAAA,IACnB,YAAY,QAAQ,MAAM,WAAW,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG,IAAI,CAAC,CAAC,IAAK,QAAQ,MAAM,MAAM,cAAc,CAAC;AAAA,EACjH;AAEA,QAAM,OAAO,OAAO,YAAY,EAAE,MAAM,aAAa,EAAE,aAAa,MAAM,MAAM,EAAE,KAAK,CAAC,GAAG,SAAS,EAAE;AACtG,QAAM,WAAW,UAAU,KAAK,UAAU,IAAI,GAAG,MAAM,UAAU,GAAG,MAAM,EAAE,aAAa,MAAM,QAAQ,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,IAAI,EAAE,GAAG,IAAI;AAC5J,QAAM,SAAS,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO;AAC5D,MAAI,OAAO,OAAQ,QAAO,EAAE,UAAU,SAAS,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG;AACzG,QAAM,KAAK,GAAG,IAAI,IAAI,OAAO,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC;AAC9C,QAAM,MAAM,KAAK,UAAU,IAAI,GAAG,EAAE;AACpC,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,gBAAc,KAAK,KAAK,UAAU,GAAG,IAAI;AACzC,QAAM,SAAsB;AAAA,IAC1B;AAAA,IACA,MAAM,QAAQ,UAAU,EAAE,SAAS,UAAU,UAAU;AAAA,IACvD;AAAA,IACA;AAAA,IACA,GAAI,SAAS,EAAE,QAAQ,OAAO,MAAM,aAAa,OAAO,aAAa,OAAO,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC;AAAA,IAChG,YAAY,IAAI;AAAA,IAChB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,IAChC,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,aAAa,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE;AAAA,IAC5F,GAAI,SAAS,YAAY,EAAE,WAAW,SAAS,UAAU,IAAI,CAAC;AAAA,IAC9D,WAAW,SAAS;AAAA,IACpB,GAAI,SAAS,kBAAkB,EAAE,kBAAkB,KAAK,IAAI,CAAC;AAAA,IAC7D,GAAI,QAAQ,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,EAClD;AACA,MAAI,CAAC,UAAU,CAAC,OAAO;AACrB,UAAM,QAAQ,eAAe,MAAM,MAAM,GAAG;AAC5C,QAAI,MAAM,OAAQ,QAAO,YAAY,MAAM,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,KAAK,EAAE;AACtF,eAAW,EAAE,MAAM,MAAM,MAAM,KAAK,KAAK,UAAU,GAAG,QAAQ,SAAS,GAAI,SAAS,kBAAkB,EAAE,kBAAkB,KAAK,IAAI,CAAC,EAAG,GAAG,GAAG;AAAA,EAC/I;AACA,gBAAc,KAAK,KAAK,YAAY,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACtE,SAAO,EAAE,GAAG;AACd;AAEO,SAAS,WAAW,MAAsD;AAC/E,MAAI,MAAgB,CAAC;AACrB,MAAI;AACF,UAAM,YAAY,UAAU,IAAI,CAAC;AAAA,EACnC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAA8C,CAAC;AACrD,aAAW,MAAM,KAAK;AACpB,UAAM,MAAM,KAAK,UAAU,IAAI,GAAG,EAAE;AACpC,QAAI;AACF,UAAI,WAAW,KAAK,KAAK,UAAU,CAAC,EAAG,KAAI,KAAK,EAAE,KAAK,QAAQ,KAAK,MAAM,aAAa,KAAK,KAAK,YAAY,GAAG,MAAM,CAAC,EAAE,CAAC;AAAA,IAC5H,QAAQ;AAAA,IAAC;AAAA,EACX;AACA,SAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,QAAQ,cAAc,EAAE,OAAO,OAAO,CAAC;AAC5E;AASA,eAAsB,cAAc,KAAgB,OAAsB,CAAC,GAA2B;AACpG,QAAM,SAAS,WAAW,IAAI,IAAI;AAClC,QAAM,QAAQ,WAAW,KAAK,MAAM;AACpC,QAAM,MAAM,MAAM,UAAU;AAAA,IAC1B,QAAQ,GAAG,aAAa;AAAA;AAAA;AAAA;AAAA,EAAc,KAAK;AAAA,IAC3C,KAAK,IAAI;AAAA,IACT,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,IAC9B,UAAU,KAAK,YAAY;AAAA,IAC3B,QAAQ;AAAA,IACR,WAAW,KAAK;AAAA,IAChB,KAAK,KAAK;AAAA,EACZ,CAAC;AACD,MAAI,CAAC,IAAI,MAAM,CAAC,IAAI,OAAQ,OAAM,IAAI,MAAM,IAAI,SAAS,qBAAqB;AAC9E,QAAM,SAAS,kBAAkB,IAAI,MAAM;AAC3C,MAAI,CAAC,OAAQ,QAAO,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,EAAE,QAAQ,+BAA+B,CAAC,GAAG,SAAS,IAAI,QAAQ;AAC/G,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAiC,CAAC;AACxC,aAAW,KAAK,OAAO,OAAO,MAAM,GAAG,CAAC,GAAG;AACzC,UAAM,MAAM,KAAK,OAAO,QAAQ;AAChC,UAAM,IAAI,MAAM,iBAAiB,IAAI,MAAM,KAAK,GAAG,QAAQ,KAAK,iBAAiB,CAAC,MAAM,YAAY,IAAI,MAAM,GAAG,GAAG,IAAI,GAAG;AAC3H,QAAI,EAAE,GAAI,QAAO,KAAK,EAAE,EAAE;AAAA,QACrB,UAAS,KAAK,EAAE,QAAQ,GAAG,GAAG,QAAQ,GAAG,KAAK,EAAE,QAAQ,GAAG,CAAC;AAAA,EACnE;AACA,SAAO,EAAE,QAAQ,UAAU,SAAS,IAAI,SAAS,MAAM,OAAO,OAAO,SAAS,SAAY,OAAO,aAAa,MAAM,GAAG,GAAG,EAAE;AAC9H;","names":["r"]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  materializeDraft,
3
3
  parseReviewerJson
4
- } from "./chunk-P3GLY42T.js";
4
+ } from "./chunk-2BFG4IBC.js";
5
5
  import {
6
6
  runClaude
7
- } from "./chunk-IEZVFKUT.js";
7
+ } from "./chunk-PMOZKUEM.js";
8
8
  import {
9
9
  findSkills
10
10
  } from "./chunk-ZSWI3Y4Y.js";
@@ -115,4 +115,4 @@ export {
115
115
  consolidateOn,
116
116
  proposeMerges
117
117
  };
118
- //# sourceMappingURL=chunk-NCGM36NX.js.map
118
+ //# sourceMappingURL=chunk-2OMDSTSS.js.map
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  listDrafts,
3
3
  transcriptTimeline
4
- } from "./chunk-P3GLY42T.js";
4
+ } from "./chunk-2BFG4IBC.js";
5
5
  import {
6
6
  applyChange
7
7
  } from "./chunk-TKHWVISV.js";
8
8
  import {
9
9
  indexSkill
10
- } from "./chunk-3FPUBAEW.js";
10
+ } from "./chunk-N77YCMSX.js";
11
11
  import {
12
12
  buildEvalSet,
13
13
  runTrigger
14
- } from "./chunk-MQS7JEBD.js";
14
+ } from "./chunk-BSXIGPDY.js";
15
15
  import {
16
16
  parseVerify,
17
17
  runTaskEval
18
- } from "./chunk-DBFD3MTT.js";
18
+ } from "./chunk-MTLYF357.js";
19
19
  import {
20
20
  findSkills,
21
21
  lintSkill,
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-ZSWI3Y4Y.js";
25
25
  import {
26
26
  queueDir
27
- } from "./chunk-NIZLBNH4.js";
27
+ } from "./chunk-M2PC64DB.js";
28
28
 
29
29
  // src/core/skill-land.ts
30
30
  import { spawnSync } from "child_process";
@@ -221,4 +221,4 @@ export {
221
221
  reviewedPrompts,
222
222
  evalDraft
223
223
  };
224
- //# sourceMappingURL=chunk-BEACL25M.js.map
224
+ //# sourceMappingURL=chunk-3ML5ESXI.js.map
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runTrigger
3
- } from "./chunk-MQS7JEBD.js";
3
+ } from "./chunk-BSXIGPDY.js";
4
4
  import {
5
5
  runClaude
6
- } from "./chunk-IEZVFKUT.js";
6
+ } from "./chunk-PMOZKUEM.js";
7
7
  import {
8
8
  parseSkill
9
9
  } from "./chunk-ZSWI3Y4Y.js";
@@ -120,4 +120,4 @@ export {
120
120
  withDescription,
121
121
  tuneDescription
122
122
  };
123
- //# sourceMappingURL=chunk-OT3MV7XF.js.map
123
+ //# sourceMappingURL=chunk-ACZKDSMP.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runClaude,
3
3
  skillsCalled
4
- } from "./chunk-IEZVFKUT.js";
4
+ } from "./chunk-PMOZKUEM.js";
5
5
  import {
6
6
  parseSkill
7
7
  } from "./chunk-ZSWI3Y4Y.js";
@@ -134,4 +134,4 @@ export {
134
134
  makeScratch,
135
135
  runTrigger
136
136
  };
137
- //# sourceMappingURL=chunk-MQS7JEBD.js.map
137
+ //# sourceMappingURL=chunk-BSXIGPDY.js.map
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-R6GNP7DS.js";
5
5
 
6
6
  // src/core/skill-queue.ts
7
- import { appendFileSync, existsSync, mkdirSync, openSync, writeSync, closeSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, statSync } from "fs";
8
- import { homedir } from "os";
9
- import { join } from "path";
7
+ import { appendFileSync, mkdirSync, openSync, writeSync, closeSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync, statSync } from "fs";
8
+ import { homedir, tmpdir } from "os";
9
+ import { join, sep } from "path";
10
10
  var smHome = (env = process.env) => env.SEMANTIC_MEMORY_STATE_HOME || join(env.HOME || homedir(), ".semantic-memory");
11
11
  var queueDir = (env = process.env) => join(smHome(env), "review-queue");
12
12
  var runsPath = (env = process.env) => join(smHome(env), "skill-loop-runs.jsonl");
@@ -16,6 +16,30 @@ var dailyRunCap = (env = process.env) => Math.max(0, Number(env.SEMANTIC_MEMORY_
16
16
  var minToolCalls = (env = process.env) => Number(env.SEMANTIC_MEMORY_SKILL_LOOP_MIN_TOOL_CALLS) || 20;
17
17
  var TRANSCRIPT_SCAN_MAX = 50 * 1024 * 1024;
18
18
  var safeName = (s) => s.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 120);
19
+ var real = (p) => {
20
+ try {
21
+ return realpathSync(p);
22
+ } catch {
23
+ return p;
24
+ }
25
+ };
26
+ function inTempDir(repo, env = process.env) {
27
+ if (env.SEMANTIC_MEMORY_SKILL_LOOP_ALLOW_TMP === "1") return false;
28
+ const r = real(repo) + sep;
29
+ return [tmpdir(), "/tmp", "/var/tmp"].some((d) => r.startsWith(real(d) + sep));
30
+ }
31
+ function reviewedAt(session_id, env = process.env) {
32
+ let last = "";
33
+ for (const d of ["done", "failed"]) {
34
+ try {
35
+ const j = JSON.parse(readFileSync(join(queueDir(env), d, `${safeName(session_id)}.json`), "utf8"));
36
+ const at = String(j.finished || j.ts || "");
37
+ if (at > last) last = at;
38
+ } catch {
39
+ }
40
+ }
41
+ return last || null;
42
+ }
19
43
  function countToolCalls(transcript) {
20
44
  if (!transcript) return 0;
21
45
  try {
@@ -27,19 +51,22 @@ function countToolCalls(transcript) {
27
51
  }
28
52
  function sessionJob(repo, input, env = process.env) {
29
53
  const session_id = typeof input.session_id === "string" ? input.session_id : "";
30
- if (!session_id) return null;
31
- const failures = readEvidence(repo, "failures").filter((f) => f.session_id === session_id);
32
- const corrections = readEvidence(repo, "corrections").filter((c) => c.session_id === session_id).length;
33
- const skills_used = [...new Set(readEvidence(repo, "usage").filter((u) => u.session_id === session_id).map((u) => String(u.skill)))];
54
+ if (!session_id || inTempDir(repo, env)) return null;
55
+ const since = reviewedAt(session_id, env) ?? void 0;
56
+ const mine = (r) => r.session_id === session_id && (!since || String(r.ts ?? "") > since);
57
+ const failures = readEvidence(repo, "failures").filter(mine);
58
+ const corrections = readEvidence(repo, "corrections").filter(mine).length;
59
+ const skills_used = [...new Set(readEvidence(repo, "usage").filter(mine).map((u) => String(u.skill)))];
34
60
  const transcript_path = typeof input.transcript_path === "string" ? input.transcript_path : null;
35
- const tool_calls = failures.length || corrections ? 0 : countToolCalls(transcript_path);
61
+ const tool_calls = failures.length || corrections || since ? 0 : countToolCalls(transcript_path);
36
62
  const why = failures.length ? `${failures.length} failure(s)` : corrections ? `${corrections} correction(s)` : tool_calls >= minToolCalls(env) ? `${tool_calls} tool calls` : "";
37
63
  if (!why) return null;
38
64
  return {
65
+ ...since ? { since } : {},
39
66
  session_id,
40
67
  repo,
41
68
  transcript_path,
42
- reason: `${why}; ended: ${input.reason ?? "unknown"}`,
69
+ reason: `${why}${since ? `; resumed since its review at ${since}` : ""}; ended: ${input.reason ?? "unknown"}`,
43
70
  ts: (/* @__PURE__ */ new Date()).toISOString(),
44
71
  evidence: { failures: failures.map((f) => String(f.id ?? "")).filter(Boolean), corrections, skills_used, tool_calls }
45
72
  };
@@ -47,7 +74,8 @@ function sessionJob(repo, input, env = process.env) {
47
74
  function enqueue(job, env = process.env) {
48
75
  mkdirSync(queueDir(env), { recursive: true });
49
76
  const path = join(queueDir(env), `${safeName(job.session_id)}.json`);
50
- if (["done", "failed"].some((d) => existsSync(join(queueDir(env), d, `${safeName(job.session_id)}.json`)))) return path;
77
+ const last = reviewedAt(job.session_id, env);
78
+ if (last && (!job.since || job.since < last)) return path;
51
79
  writeFileSync(path, JSON.stringify(job, null, 1));
52
80
  return path;
53
81
  }
@@ -151,6 +179,8 @@ export {
151
179
  lockPath,
152
180
  logPath,
153
181
  dailyRunCap,
182
+ inTempDir,
183
+ reviewedAt,
154
184
  sessionJob,
155
185
  enqueue,
156
186
  pendingJobs,
@@ -162,4 +192,4 @@ export {
162
192
  takeLock,
163
193
  log
164
194
  };
165
- //# sourceMappingURL=chunk-NIZLBNH4.js.map
195
+ //# sourceMappingURL=chunk-M2PC64DB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/skill-queue.ts"],"sourcesContent":["/**\n * The skill loop's review queue (v1.7). SessionEnd writes one job per session that has signal and starts\n * the worker detached; the worker drains this repo's jobs oldest first, inside a global daily cap. The\n * session never waits: SessionEnd hooks get 1.5 s by default, so the hook only writes and spawns.\n *\n * ~/.semantic-memory/review-queue/<session>.json pending jobs (all repos)\n * ~/.semantic-memory/review-queue/{done,failed}/ finished jobs; a resumed session is reviewed again only\n * for the failures / corrections recorded after that (`since`)\n * ~/.semantic-memory/skill-loop-runs.jsonl one line per review run (the daily cap)\n * <repo>/.claude/.semantic-memory/skill-loop.lock the repo's worker (pid)\n * <repo>/.claude/.semantic-memory/skill-loop.log what the worker did\n *\n * Standalone for hooks (no embedder).\n */\nimport { appendFileSync, mkdirSync, openSync, writeSync, closeSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync, statSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { join, sep } from \"node:path\";\nimport { readEvidence, stateDir } from \"./skill-evidence.js\";\n\nexport interface ReviewJob {\n session_id: string;\n repo: string;\n transcript_path: string | null;\n reason: string;\n ts: string;\n evidence: { failures: string[]; corrections: number; skills_used: string[]; tool_calls: number };\n attempts?: number;\n /** a follow-up review of a resumed session: only evidence recorded after this time (the last review's end) */\n since?: string;\n}\n\nexport const smHome = (env = process.env) => env.SEMANTIC_MEMORY_STATE_HOME || join(env.HOME || homedir(), \".semantic-memory\");\nexport const queueDir = (env = process.env) => join(smHome(env), \"review-queue\");\nconst runsPath = (env = process.env) => join(smHome(env), \"skill-loop-runs.jsonl\");\nexport const lockPath = (repo: string) => join(stateDir(repo), \"skill-loop.lock\");\nexport const logPath = (repo: string) => join(stateDir(repo), \"skill-loop.log\");\n\nexport const dailyRunCap = (env = process.env) => Math.max(0, Number(env.SEMANTIC_MEMORY_SKILL_LOOP_DAILY_RUNS ?? 10) || 0);\nconst minToolCalls = (env = process.env) => Number(env.SEMANTIC_MEMORY_SKILL_LOOP_MIN_TOOL_CALLS) || 20;\n// shortcut: counts tool calls by scanning the transcript text; above this size the count is skipped (signal then\n// needs a failure or a correction). Upgrade if long tool-only sessions should qualify.\nconst TRANSCRIPT_SCAN_MAX = 50 * 1024 * 1024;\n\nconst safeName = (s: string) => s.replace(/[^A-Za-z0-9_.-]/g, \"_\").slice(0, 120);\n\nconst real = (p: string) => {\n try {\n return realpathSync(p);\n } catch {\n return p;\n }\n};\n/** Throwaway repos (test runs, benchmarks in mkdtemp dirs) are never reviewed: they would use the daily cap. */\nexport function inTempDir(repo: string, env = process.env): boolean {\n if (env.SEMANTIC_MEMORY_SKILL_LOOP_ALLOW_TMP === \"1\") return false;\n const r = real(repo) + sep;\n return [tmpdir(), \"/tmp\", \"/var/tmp\"].some((d) => r.startsWith(real(d) + sep));\n}\n\n/** When this session's last review finished (done or failed record), or null if it was never reviewed. */\nexport function reviewedAt(session_id: string, env = process.env): string | null {\n let last = \"\";\n for (const d of [\"done\", \"failed\"]) {\n try {\n const j = JSON.parse(readFileSync(join(queueDir(env), d, `${safeName(session_id)}.json`), \"utf8\"));\n const at = String(j.finished || j.ts || \"\");\n if (at > last) last = at;\n } catch {}\n }\n return last || null;\n}\n\nfunction countToolCalls(transcript: string | null): number {\n if (!transcript) return 0;\n try {\n if (statSync(transcript).size > TRANSCRIPT_SCAN_MAX) return 0;\n return readFileSync(transcript, \"utf8\").split('\"type\":\"tool_use\"').length - 1;\n } catch {\n return 0;\n }\n}\n\n/** The job SessionEnd would write for this session, or null when the session has nothing to learn from. */\nexport function sessionJob(repo: string, input: { session_id?: string; transcript_path?: string; reason?: string }, env = process.env): ReviewJob | null {\n const session_id = typeof input.session_id === \"string\" ? input.session_id : \"\";\n if (!session_id || inTempDir(repo, env)) return null;\n const since = reviewedAt(session_id, env) ?? undefined;\n const mine = (r: Record<string, any>) => r.session_id === session_id && (!since || String(r.ts ?? \"\") > since);\n const failures = readEvidence(repo, \"failures\").filter(mine);\n const corrections = readEvidence(repo, \"corrections\").filter(mine).length;\n const skills_used = [...new Set(readEvidence(repo, \"usage\").filter(mine).map((u) => String(u.skill)))];\n const transcript_path = typeof input.transcript_path === \"string\" ? input.transcript_path : null;\n // a resumed session needs new failures or corrections: its tool-call count is mostly the reviewed part\n const tool_calls = failures.length || corrections || since ? 0 : countToolCalls(transcript_path);\n const why = failures.length ? `${failures.length} failure(s)` : corrections ? `${corrections} correction(s)` : tool_calls >= minToolCalls(env) ? `${tool_calls} tool calls` : \"\";\n if (!why) return null;\n return {\n ...(since ? { since } : {}),\n session_id,\n repo,\n transcript_path,\n reason: `${why}${since ? `; resumed since its review at ${since}` : \"\"}; ended: ${input.reason ?? \"unknown\"}`,\n ts: new Date().toISOString(),\n evidence: { failures: failures.map((f) => String(f.id ?? \"\")).filter(Boolean), corrections, skills_used, tool_calls },\n };\n}\n\nexport function enqueue(job: ReviewJob, env = process.env): string {\n mkdirSync(queueDir(env), { recursive: true });\n const path = join(queueDir(env), `${safeName(job.session_id)}.json`);\n // a session that ends twice (e.g. /clear then exit) is one job; once reviewed, only a follow-up job made after\n // that review (since = its end) goes in\n const last = reviewedAt(job.session_id, env);\n if (last && (!job.since || job.since < last)) return path;\n writeFileSync(path, JSON.stringify(job, null, 1));\n return path;\n}\n\nexport function pendingJobs(repo: string, env = process.env): { path: string; job: ReviewJob }[] {\n let files: string[] = [];\n try {\n files = readdirSync(queueDir(env)).filter((f) => f.endsWith(\".json\"));\n } catch {\n return [];\n }\n const jobs: { path: string; job: ReviewJob }[] = [];\n for (const f of files) {\n try {\n const path = join(queueDir(env), f);\n const job = JSON.parse(readFileSync(path, \"utf8\")) as ReviewJob;\n if (job.repo === repo) jobs.push({ path, job });\n } catch {}\n }\n return jobs.sort((a, b) => a.job.ts.localeCompare(b.job.ts));\n}\n\nexport function finishJob(path: string, outcome: \"done\" | \"failed\", result: Record<string, unknown>, env = process.env): void {\n const dir = join(queueDir(env), outcome);\n mkdirSync(dir, { recursive: true });\n const job = JSON.parse(readFileSync(path, \"utf8\"));\n writeFileSync(path, JSON.stringify({ ...job, finished: new Date().toISOString(), result }, null, 1));\n renameSync(path, join(dir, path.split(\"/\").pop()!));\n}\n\nexport function bumpAttempts(path: string): number {\n const job = JSON.parse(readFileSync(path, \"utf8\")) as ReviewJob;\n job.attempts = (job.attempts ?? 0) + 1;\n writeFileSync(path, JSON.stringify(job, null, 1));\n return job.attempts;\n}\n\nconst localDay = (d = new Date()) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, \"0\")}-${String(d.getDate()).padStart(2, \"0\")}`;\n\nexport function runsToday(env = process.env, now = new Date()): number {\n try {\n const day = localDay(now);\n return readFileSync(runsPath(env), \"utf8\").split(\"\\n\").filter((l) => l.includes(`\"day\":\"${day}\"`)).length;\n } catch {\n return 0;\n }\n}\n\nexport function recordRun(repo: string, session_id: string, env = process.env, now = new Date()): void {\n mkdirSync(smHome(env), { recursive: true });\n appendFileSync(runsPath(env), JSON.stringify({ day: localDay(now), ts: now.toISOString(), repo, session_id }) + \"\\n\");\n}\n\nconst alive = (pid: number) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n};\n\nexport function lockHolder(repo: string): number | null {\n try {\n const pid = Number(readFileSync(lockPath(repo), \"utf8\").trim());\n return pid && alive(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\n/** Take the repo's worker lock (reclaiming a dead holder's). Returns a release function, or null if held. */\nexport function takeLock(repo: string): (() => void) | null {\n mkdirSync(stateDir(repo), { recursive: true });\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(lockPath(repo), \"wx\");\n writeSync(fd, `${process.pid}\\n`);\n closeSync(fd);\n return () => {\n try {\n if (Number(readFileSync(lockPath(repo), \"utf8\").trim()) === process.pid) unlinkSync(lockPath(repo));\n } catch {}\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"EEXIST\") throw e;\n if (lockHolder(repo) !== null) return null;\n try {\n unlinkSync(lockPath(repo));\n } catch {}\n }\n }\n return null;\n}\n\nexport function log(repo: string, line: string): void {\n try {\n mkdirSync(stateDir(repo), { recursive: true });\n appendFileSync(logPath(repo), `${new Date().toISOString()} ${line}\\n`);\n } catch {}\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runClaude
3
- } from "./chunk-IEZVFKUT.js";
3
+ } from "./chunk-PMOZKUEM.js";
4
4
  import {
5
5
  parseSkill
6
6
  } from "./chunk-ZSWI3Y4Y.js";
@@ -127,4 +127,4 @@ export {
127
127
  parseVerify,
128
128
  runTaskEval
129
129
  };
130
- //# sourceMappingURL=chunk-DBFD3MTT.js.map
130
+ //# sourceMappingURL=chunk-MTLYF357.js.map
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-GYR7U6KA.js";
8
8
  import {
9
9
  smHome
10
- } from "./chunk-NIZLBNH4.js";
10
+ } from "./chunk-M2PC64DB.js";
11
11
 
12
12
  // src/core/skill-scope.ts
13
13
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
@@ -110,4 +110,4 @@ export {
110
110
  indexSkill,
111
111
  promotionPeers
112
112
  };
113
- //# sourceMappingURL=chunk-3FPUBAEW.js.map
113
+ //# sourceMappingURL=chunk-N77YCMSX.js.map
@@ -1,8 +1,8 @@
1
1
  // src/core/claude-run.ts
2
2
  import { spawn } from "child_process";
3
- import { openSync, writeSync, closeSync, readFileSync, unlinkSync, mkdirSync } from "fs";
3
+ import { accessSync, constants, openSync, writeSync, closeSync, readFileSync, unlinkSync, mkdirSync } from "fs";
4
4
  import { homedir } from "os";
5
- import { join } from "path";
5
+ import { delimiter, join } from "path";
6
6
  var MAX_CLAUDE = 5;
7
7
  function claudeConcurrency(env = process.env) {
8
8
  const n = Number(env.SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY);
@@ -53,7 +53,20 @@ async function acquireSlot(opts = {}) {
53
53
  await new Promise((r) => setTimeout(r, opts.pollMs ?? 250));
54
54
  }
55
55
  }
56
- var claudeBin = (env = process.env) => env.SEMANTIC_MEMORY_CLAUDE_BIN || "claude";
56
+ var executable = (p) => {
57
+ try {
58
+ accessSync(p, constants.X_OK);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ };
64
+ function claudeBin(env = process.env) {
65
+ if (env.SEMANTIC_MEMORY_CLAUDE_BIN) return env.SEMANTIC_MEMORY_CLAUDE_BIN;
66
+ if ((env.PATH ?? "").split(delimiter).some((d) => d && executable(join(d, "claude")))) return "claude";
67
+ const home = env.HOME || homedir();
68
+ return [join(home, ".local", "bin", "claude"), join(home, ".claude", "local", "claude")].find(executable) ?? "claude";
69
+ }
57
70
  var skillLoopModel = (env = process.env) => env.SEMANTIC_MEMORY_SKILL_LOOP_MODEL || "sonnet";
58
71
  function fencedArgs(o, env = process.env) {
59
72
  const args = [
@@ -154,4 +167,4 @@ export {
154
167
  runClaude,
155
168
  skillsCalled
156
169
  };
157
- //# sourceMappingURL=chunk-IEZVFKUT.js.map
170
+ //# sourceMappingURL=chunk-PMOZKUEM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/claude-run.ts"],"sourcesContent":["/**\n * Every `claude` process the skill loop starts goes through here (v1.7): the reviewer, eval runs, graders,\n * description tuning. Two guarantees:\n *\n * - **At most 5 at once, machine-wide.** A slot file under ~/.semantic-memory/claude-slots/ is held for the\n * life of each run (SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY, 1–5, higher clamps to 5). A slot whose\n * holder died is reclaimed.\n * - **Fenced.** No hooks, no MCP servers (claude.ai connectors included), no user settings, only the tools\n * asked for, never Agent/Task (no nested subagents), nothing outside the working directory (dontAsk),\n * no session file, and SEMANTIC_MEMORY_REVIEWER=1 so any semantic-memory hook that still runs exits at once.\n * Verified flag by flag on Claude Code 2.1.310 (.planning/v17-skill-loop-and-jev-mode/notes.md, Phase 2).\n */\nimport { spawn } from \"node:child_process\";\nimport { accessSync, constants, openSync, writeSync, closeSync, readFileSync, unlinkSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { delimiter, join } from \"node:path\";\n\nexport const MAX_CLAUDE = 5;\n\nexport function claudeConcurrency(env = process.env): number {\n const n = Number(env.SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY);\n return Number.isFinite(n) && n >= 1 ? Math.min(MAX_CLAUDE, Math.floor(n)) : MAX_CLAUDE;\n}\n\nexport const slotsDir = (env = process.env) =>\n env.SEMANTIC_MEMORY_CLAUDE_SLOTS_DIR || join(env.HOME || homedir(), \".semantic-memory\", \"claude-slots\");\n\nconst alive = (pid: number) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n};\n\n/** Take one of the machine-wide slots, waiting for one to free up. Resolves to its release function. */\nexport async function acquireSlot(opts: { env?: NodeJS.ProcessEnv; timeoutMs?: number; pollMs?: number } = {}): Promise<() => void> {\n const env = opts.env ?? process.env;\n const dir = slotsDir(env);\n const max = claudeConcurrency(env);\n const deadline = Date.now() + (opts.timeoutMs ?? 30 * 60_000);\n mkdirSync(dir, { recursive: true });\n for (;;) {\n for (let i = 0; i < max; i++) {\n const path = join(dir, `slot-${i}`);\n try {\n const fd = openSync(path, \"wx\");\n writeSync(fd, `${process.pid}\\n`);\n closeSync(fd);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n try {\n unlinkSync(path);\n } catch {}\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"EEXIST\") throw e;\n // held: reclaim it when its holder is gone (crashed worker, killed run)\n try {\n const pid = Number(readFileSync(path, \"utf8\").trim());\n if (!pid || !alive(pid)) unlinkSync(path);\n } catch {}\n }\n }\n if (Date.now() > deadline) throw new Error(`no claude slot free after ${opts.timeoutMs ?? 1_800_000} ms (${max} in use)`);\n await new Promise((r) => setTimeout(r, opts.pollMs ?? 250));\n }\n}\n\nexport interface ClaudeRunOptions {\n prompt: string;\n cwd: string;\n /** built-in tools the run may use; [] = none */\n tools: string[];\n /** with dontAsk, only these permission rules run un-prompted, e.g. \"Edit\", \"Bash(npm test:*)\"; the rest is denied */\n allowedTools?: string[];\n maxTurns: number;\n model?: string;\n /** \"json\": one result object; \"stream-json\": every event (to see which tools were called) */\n output?: \"json\" | \"stream-json\";\n timeoutMs?: number;\n /** extra directories the run may read */\n addDirs?: string[];\n appendSystemPrompt?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nexport interface ClaudeRunResult {\n ok: boolean;\n /** final assistant text */\n result: string;\n /** stream-json events (empty for json output) */\n events: any[];\n costUsd: number;\n turns: number;\n error?: string;\n}\n\nconst executable = (p: string) => {\n try {\n accessSync(p, constants.X_OK);\n return true;\n } catch {\n return false;\n }\n};\n/** `claude` from PATH, else its usual install places: a worker started from a cron session has cron's PATH. */\nexport function claudeBin(env = process.env): string {\n if (env.SEMANTIC_MEMORY_CLAUDE_BIN) return env.SEMANTIC_MEMORY_CLAUDE_BIN;\n if ((env.PATH ?? \"\").split(delimiter).some((d) => d && executable(join(d, \"claude\")))) return \"claude\";\n const home = env.HOME || homedir();\n return [join(home, \".local\", \"bin\", \"claude\"), join(home, \".claude\", \"local\", \"claude\")].find(executable) ?? \"claude\";\n}\nexport const skillLoopModel = (env = process.env) => env.SEMANTIC_MEMORY_SKILL_LOOP_MODEL || \"sonnet\";\n\n/** The fenced argument list (exported for tests: these flags are the security boundary). */\nexport function fencedArgs(o: Pick<ClaudeRunOptions, \"tools\" | \"allowedTools\" | \"maxTurns\" | \"model\" | \"output\" | \"addDirs\" | \"appendSystemPrompt\">, env = process.env): string[] {\n const args = [\n \"-p\",\n \"--tools\", o.tools.join(\",\"),\n \"--disallowedTools\", \"Task,Agent\",\n \"--strict-mcp-config\",\n \"--settings\", JSON.stringify({ disableAllHooks: true }),\n \"--setting-sources\", \"project,local\",\n \"--permission-mode\", \"dontAsk\",\n \"--no-session-persistence\",\n \"--max-turns\", String(o.maxTurns),\n \"--model\", o.model ?? skillLoopModel(env),\n \"--output-format\", o.output ?? \"json\",\n ];\n if (o.allowedTools?.length) args.push(\"--allowedTools\", o.allowedTools.join(\",\"));\n if ((o.output ?? \"json\") === \"stream-json\") args.push(\"--verbose\");\n for (const d of o.addDirs ?? []) args.push(\"--add-dir\", d);\n if (o.appendSystemPrompt) args.push(\"--append-system-prompt\", o.appendSystemPrompt);\n return args;\n}\n\n/** Run one fenced `claude -p` inside a slot. Never throws: failures come back as `ok: false` with the reason. */\nexport async function runClaude(o: ClaudeRunOptions): Promise<ClaudeRunResult> {\n const env = { ...(o.env ?? process.env), SEMANTIC_MEMORY_REVIEWER: \"1\" };\n const fail = (error: string): ClaudeRunResult => ({ ok: false, result: \"\", events: [], costUsd: 0, turns: 0, error });\n let release: () => void;\n try {\n release = await acquireSlot({ env });\n } catch (e) {\n return fail((e as Error).message);\n }\n try {\n return await new Promise<ClaudeRunResult>((done) => {\n const child = spawn(claudeBin(env), fencedArgs(o, env), { cwd: o.cwd, env, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n done(fail(`timed out after ${o.timeoutMs ?? 600_000} ms`));\n }, o.timeoutMs ?? 600_000);\n child.stdout.on(\"data\", (d) => (stdout += d));\n child.stderr.on(\"data\", (d) => (stderr += d));\n child.on(\"error\", (e) => {\n clearTimeout(timer);\n done(fail(`could not start ${claudeBin(env)}: ${e.message}`));\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n done(parseClaudeOutput(stdout, o.output ?? \"json\", code, stderr));\n });\n child.stdin.on(\"error\", () => {});\n child.stdin.end(o.prompt);\n });\n } finally {\n release();\n }\n}\n\nexport function parseClaudeOutput(stdout: string, output: \"json\" | \"stream-json\", code: number | null, stderr = \"\"): ClaudeRunResult {\n const events: any[] = [];\n let final: any = null;\n if (output === \"json\") {\n try {\n final = JSON.parse(stdout.trim());\n } catch {}\n } else {\n for (const line of stdout.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const e = JSON.parse(line);\n events.push(e);\n if (e.type === \"result\") final = e;\n } catch {}\n }\n }\n if (!final) return { ok: false, result: \"\", events, costUsd: 0, turns: 0, error: `no result from claude (exit ${code}): ${stderr.trim().slice(0, 300)}` };\n return {\n ok: !final.is_error && final.subtype !== \"error_during_execution\",\n result: typeof final.result === \"string\" ? final.result : \"\",\n events,\n costUsd: Number(final.total_cost_usd) || 0,\n turns: Number(final.num_turns) || 0,\n error: final.is_error ? String(final.result ?? final.subtype ?? \"error\") : final.subtype === \"error_max_turns\" ? \"max turns reached\" : undefined,\n };\n}\n\n/** Names of the skills a stream-json run invoked through the Skill tool, in order. */\nexport const skillsCalled = (events: any[]): string[] =>\n events\n .filter((e) => e.type === \"assistant\")\n .flatMap((e) => (Array.isArray(e.message?.content) ? e.message.content : []))\n .filter((c: any) => c.type === \"tool_use\" && c.name === \"Skill\" && typeof c.input?.skill === \"string\")\n .map((c: any) => c.input.skill);\n"],"mappings":"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","names":[]}
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-TKHWVISV.js";
6
6
  import {
7
7
  indexSkill
8
- } from "./chunk-3FPUBAEW.js";
8
+ } from "./chunk-N77YCMSX.js";
9
9
  import {
10
10
  parseVerify
11
- } from "./chunk-DBFD3MTT.js";
11
+ } from "./chunk-MTLYF357.js";
12
12
  import {
13
13
  findSkills,
14
14
  parseSkill,
@@ -183,4 +183,4 @@ export {
183
183
  verifySkills,
184
184
  curatorState
185
185
  };
186
- //# sourceMappingURL=chunk-CATPGINC.js.map
186
+ //# sourceMappingURL=chunk-V6PSK4Y4.js.map
package/dist/cli/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  tuneDescription
4
- } from "../chunk-OT3MV7XF.js";
4
+ } from "../chunk-ACZKDSMP.js";
5
5
  import {
6
6
  buildEvalSet,
7
7
  runTrigger
8
- } from "../chunk-MQS7JEBD.js";
8
+ } from "../chunk-BSXIGPDY.js";
9
9
  import {
10
10
  runTaskEval
11
- } from "../chunk-DBFD3MTT.js";
12
- import "../chunk-IEZVFKUT.js";
11
+ } from "../chunk-MTLYF357.js";
12
+ import "../chunk-PMOZKUEM.js";
13
13
  import {
14
14
  parseSkill
15
15
  } from "../chunk-ZSWI3Y4Y.js";
@@ -404,7 +404,7 @@ function registerSkillLoopCommands(program2) {
404
404
  function registerReviewCommands(cmd) {
405
405
  cmd.command("review").description("List skill drafts with their evidence (and diff, for patches); approve, reject or evaluate one.").option("--json", "JSON output").option("--approve <id>", "Land a draft (writes the skill and a ledger entry)").option("--promote", "With --approve: land it as a global skill replacing the project copies (needs a promotion)").option("--override <why>", "With --approve: land it although its eval gate did not adopt it (the reason is ledgered)").option("--reject <id>", "Drop a draft (ledgered)").option("--eval <id>", "Run the trigger eval on a draft and record the scores and the gate's verdict").option("--task", "With --eval: also replay the task with and without the skill (runs the draft's Verify command)").action(async (opts) => {
406
406
  const { repoRoot } = await import("../core/skill-evidence.js");
407
- const land = await import("../skill-land-XU2APPBJ.js");
407
+ const land = await import("../skill-land-EHXZW47F.js");
408
408
  const repo = repoRoot(process.cwd());
409
409
  try {
410
410
  if (opts.approve) {
@@ -461,7 +461,7 @@ function registerCuratorCommands(cmd) {
461
461
  if (opts.auto) log(repo, `curator: ${changes.map((a) => `${a.action} ${a.skill} (${a.why})`).join(", ") || "nothing to do"}`);
462
462
  else for (const a of r.actions) console.log(`${opts.dryRun ? "would " : ""}${a.action.padEnd(10)} ${a.skill} \u2014 ${a.why}`);
463
463
  if (!opts.auto && !r.actions.length) console.log("no learned skills here");
464
- const { consolidateOn, proposeMerges } = await import("../skill-merge-GXBQLBZA.js");
464
+ const { consolidateOn, proposeMerges } = await import("../skill-merge-HBZBALZX.js");
465
465
  if (!opts.dryRun && (opts.merge || consolidateOn(repo))) {
466
466
  const m = await proposeMerges(repo);
467
467
  const line = `merge: ${m.drafted.length} draft(s) for review${m.skipped.length ? `; ${m.skipped.join("; ")}` : ""}`;
@@ -3,8 +3,8 @@ import { EventEmitter } from 'node:events';
3
3
  export { REDACTED, RedactMode, RedactResult, redact, redactDeep } from './redact.js';
4
4
  export { commandVerbs, errorSignal, failureQuery } from './failure-query.js';
5
5
  export { Feature, FeatureState, describeFeature, featureOn, featureState, jevAvailable } from './features.js';
6
- import { P as ParsedSkill, D as DraftRecord, S as SkillLoopMeta } from '../skill-review-BaW5n0Zg.js';
7
- export { a as SKILL_SCOPES, b as SKILL_SECTIONS, c as SKILL_STATUSES, d as SkillFile, e as SkillFinding, f as SkillScope, g as SkillStatus, h as buildBrief, i as decideScope, j as draftsDir, k as findSkills, l as lintSkill, m as lintSkills, n as listDrafts, p as parseSkill, r as renderSkill, o as reviewSession } from '../skill-review-BaW5n0Zg.js';
6
+ import { P as ParsedSkill, D as DraftRecord, S as SkillLoopMeta } from '../skill-review-BNvHipn9.js';
7
+ export { a as SKILL_SCOPES, b as SKILL_SECTIONS, c as SKILL_STATUSES, d as SkillFile, e as SkillFinding, f as SkillScope, g as SkillStatus, h as buildBrief, i as decideScope, j as draftsDir, k as findSkills, l as lintSkill, m as lintSkills, n as listDrafts, p as parseSkill, r as renderSkill, o as reviewSession } from '../skill-review-BNvHipn9.js';
8
8
  import { L as LedgerEntry } from '../skill-curator-D7HHZaUI.js';
9
9
  export { c as curate, r as readLedger, a as restore, b as rollback, v as verifySkills } from '../skill-curator-D7HHZaUI.js';
10
10
  export { JEV_BAR, JevAnswer, JevCaller, JevQuestion, askJev, failureState, jevSpendToday, jevVerdict, relevanceQuestion } from './jev.js';
@@ -14,10 +14,10 @@ import {
14
14
  evalDraft,
15
15
  gateSkill,
16
16
  rejectDraft
17
- } from "../chunk-BEACL25M.js";
17
+ } from "../chunk-3ML5ESXI.js";
18
18
  import {
19
19
  proposeMerges
20
- } from "../chunk-NCGM36NX.js";
20
+ } from "../chunk-2OMDSTSS.js";
21
21
  import {
22
22
  filterToDrift,
23
23
  formatDriftBanner,
@@ -28,19 +28,19 @@ import {
28
28
  draftsDir,
29
29
  listDrafts,
30
30
  reviewSession
31
- } from "../chunk-P3GLY42T.js";
31
+ } from "../chunk-2BFG4IBC.js";
32
32
  import {
33
33
  curate,
34
34
  restore,
35
35
  verifySkills
36
- } from "../chunk-CATPGINC.js";
36
+ } from "../chunk-V6PSK4Y4.js";
37
37
  import {
38
38
  readLedger,
39
39
  rollback
40
40
  } from "../chunk-TKHWVISV.js";
41
41
  import {
42
42
  decideScope
43
- } from "../chunk-3FPUBAEW.js";
43
+ } from "../chunk-N77YCMSX.js";
44
44
  import {
45
45
  FrontmatterManager,
46
46
  INDEX_FILE,
@@ -85,7 +85,7 @@ import {
85
85
  MAX_TUNE_ITERATIONS,
86
86
  tuneDescription,
87
87
  withDescription
88
- } from "../chunk-OT3MV7XF.js";
88
+ } from "../chunk-ACZKDSMP.js";
89
89
  import {
90
90
  MAX_TRIGGER_RUNS,
91
91
  buildEvalSet,
@@ -94,12 +94,12 @@ import {
94
94
  ratesOf,
95
95
  runTrigger,
96
96
  splitOf
97
- } from "../chunk-MQS7JEBD.js";
97
+ } from "../chunk-BSXIGPDY.js";
98
98
  import {
99
99
  parseVerify,
100
100
  runTaskEval
101
- } from "../chunk-DBFD3MTT.js";
102
- import "../chunk-IEZVFKUT.js";
101
+ } from "../chunk-MTLYF357.js";
102
+ import "../chunk-PMOZKUEM.js";
103
103
  import {
104
104
  SKILL_SCOPES,
105
105
  SKILL_SECTIONS,
@@ -137,7 +137,7 @@ import {
137
137
  jevVerdict,
138
138
  relevanceQuestion
139
139
  } from "../chunk-GYR7U6KA.js";
140
- import "../chunk-NIZLBNH4.js";
140
+ import "../chunk-M2PC64DB.js";
141
141
  import "../chunk-R6GNP7DS.js";
142
142
  import {
143
143
  REDACTED,
@@ -11,16 +11,16 @@ import {
11
11
  plan,
12
12
  restore,
13
13
  verifySkills
14
- } from "../chunk-CATPGINC.js";
14
+ } from "../chunk-V6PSK4Y4.js";
15
15
  import "../chunk-TKHWVISV.js";
16
- import "../chunk-3FPUBAEW.js";
17
- import "../chunk-DBFD3MTT.js";
18
- import "../chunk-IEZVFKUT.js";
16
+ import "../chunk-N77YCMSX.js";
17
+ import "../chunk-MTLYF357.js";
18
+ import "../chunk-PMOZKUEM.js";
19
19
  import "../chunk-ZSWI3Y4Y.js";
20
20
  import "../chunk-J7LYDRML.js";
21
21
  import "../chunk-VQCNWRTL.js";
22
22
  import "../chunk-GYR7U6KA.js";
23
- import "../chunk-NIZLBNH4.js";
23
+ import "../chunk-M2PC64DB.js";
24
24
  import "../chunk-R6GNP7DS.js";
25
25
  import "../chunk-H2BES6FU.js";
26
26
  import "../chunk-PZ5AY32C.js";
@@ -11,12 +11,18 @@ interface ReviewJob {
11
11
  tool_calls: number;
12
12
  };
13
13
  attempts?: number;
14
+ /** a follow-up review of a resumed session: only evidence recorded after this time (the last review's end) */
15
+ since?: string;
14
16
  }
15
17
  declare const smHome: (env?: NodeJS.ProcessEnv) => string;
16
18
  declare const queueDir: (env?: NodeJS.ProcessEnv) => string;
17
19
  declare const lockPath: (repo: string) => string;
18
20
  declare const logPath: (repo: string) => string;
19
21
  declare const dailyRunCap: (env?: NodeJS.ProcessEnv) => number;
22
+ /** Throwaway repos (test runs, benchmarks in mkdtemp dirs) are never reviewed: they would use the daily cap. */
23
+ declare function inTempDir(repo: string, env?: NodeJS.ProcessEnv): boolean;
24
+ /** When this session's last review finished (done or failed record), or null if it was never reviewed. */
25
+ declare function reviewedAt(session_id: string, env?: NodeJS.ProcessEnv): string | null;
20
26
  /** The job SessionEnd would write for this session, or null when the session has nothing to learn from. */
21
27
  declare function sessionJob(repo: string, input: {
22
28
  session_id?: string;
@@ -37,4 +43,4 @@ declare function lockHolder(repo: string): number | null;
37
43
  declare function takeLock(repo: string): (() => void) | null;
38
44
  declare function log(repo: string, line: string): void;
39
45
 
40
- export { type ReviewJob, bumpAttempts, dailyRunCap, enqueue, finishJob, lockHolder, lockPath, log, logPath, pendingJobs, queueDir, recordRun, runsToday, sessionJob, smHome, takeLock };
46
+ export { type ReviewJob, bumpAttempts, dailyRunCap, enqueue, finishJob, inTempDir, lockHolder, lockPath, log, logPath, pendingJobs, queueDir, recordRun, reviewedAt, runsToday, sessionJob, smHome, takeLock };
@@ -3,6 +3,7 @@ import {
3
3
  dailyRunCap,
4
4
  enqueue,
5
5
  finishJob,
6
+ inTempDir,
6
7
  lockHolder,
7
8
  lockPath,
8
9
  log,
@@ -10,11 +11,12 @@ import {
10
11
  pendingJobs,
11
12
  queueDir,
12
13
  recordRun,
14
+ reviewedAt,
13
15
  runsToday,
14
16
  sessionJob,
15
17
  smHome,
16
18
  takeLock
17
- } from "../chunk-NIZLBNH4.js";
19
+ } from "../chunk-M2PC64DB.js";
18
20
  import "../chunk-R6GNP7DS.js";
19
21
  import "../chunk-H2BES6FU.js";
20
22
  import "../chunk-PZ5AY32C.js";
@@ -23,6 +25,7 @@ export {
23
25
  dailyRunCap,
24
26
  enqueue,
25
27
  finishJob,
28
+ inTempDir,
26
29
  lockHolder,
27
30
  lockPath,
28
31
  log,
@@ -30,6 +33,7 @@ export {
30
33
  pendingJobs,
31
34
  queueDir,
32
35
  recordRun,
36
+ reviewedAt,
33
37
  runsToday,
34
38
  sessionJob,
35
39
  smHome,
@@ -1,3 +1,3 @@
1
- export { D as DraftRecord, R as ReviewOptions, q as ReviewerDraft, s as ScopeResolver, h as buildBrief, j as draftsDir, n as listDrafts, t as materializeDraft, u as parseReviewerJson, o as reviewSession, v as transcriptTimeline } from '../skill-review-BaW5n0Zg.js';
1
+ export { D as DraftRecord, R as ReviewOptions, q as ReviewerDraft, s as ScopeResolver, h as buildBrief, j as draftsDir, n as listDrafts, t as materializeDraft, u as parseReviewerJson, o as reviewSession, v as transcriptTimeline } from '../skill-review-BNvHipn9.js';
2
2
  import './skill-queue.js';
3
3
  import './skill-worker.js';
@@ -6,14 +6,14 @@ import {
6
6
  parseReviewerJson,
7
7
  reviewSession,
8
8
  transcriptTimeline
9
- } from "../chunk-P3GLY42T.js";
10
- import "../chunk-3FPUBAEW.js";
11
- import "../chunk-IEZVFKUT.js";
9
+ } from "../chunk-2BFG4IBC.js";
10
+ import "../chunk-N77YCMSX.js";
11
+ import "../chunk-PMOZKUEM.js";
12
12
  import "../chunk-ZSWI3Y4Y.js";
13
13
  import "../chunk-J7LYDRML.js";
14
14
  import "../chunk-VQCNWRTL.js";
15
15
  import "../chunk-GYR7U6KA.js";
16
- import "../chunk-NIZLBNH4.js";
16
+ import "../chunk-M2PC64DB.js";
17
17
  import "../chunk-R6GNP7DS.js";
18
18
  import "../chunk-H2BES6FU.js";
19
19
  import "../chunk-PZ5AY32C.js";
@@ -8,7 +8,7 @@ import {
8
8
  recordRun,
9
9
  runsToday,
10
10
  takeLock
11
- } from "../chunk-NIZLBNH4.js";
11
+ } from "../chunk-M2PC64DB.js";
12
12
  import "../chunk-R6GNP7DS.js";
13
13
  import "../chunk-H2BES6FU.js";
14
14
  import "../chunk-PZ5AY32C.js";
@@ -5,18 +5,18 @@ import {
5
5
  evalDraft,
6
6
  rejectDraft,
7
7
  reviewedPrompts
8
- } from "./chunk-BEACL25M.js";
9
- import "./chunk-P3GLY42T.js";
8
+ } from "./chunk-3ML5ESXI.js";
9
+ import "./chunk-2BFG4IBC.js";
10
10
  import "./chunk-TKHWVISV.js";
11
- import "./chunk-3FPUBAEW.js";
12
- import "./chunk-MQS7JEBD.js";
13
- import "./chunk-DBFD3MTT.js";
14
- import "./chunk-IEZVFKUT.js";
11
+ import "./chunk-N77YCMSX.js";
12
+ import "./chunk-BSXIGPDY.js";
13
+ import "./chunk-MTLYF357.js";
14
+ import "./chunk-PMOZKUEM.js";
15
15
  import "./chunk-ZSWI3Y4Y.js";
16
16
  import "./chunk-J7LYDRML.js";
17
17
  import "./chunk-VQCNWRTL.js";
18
18
  import "./chunk-GYR7U6KA.js";
19
- import "./chunk-NIZLBNH4.js";
19
+ import "./chunk-M2PC64DB.js";
20
20
  import "./chunk-R6GNP7DS.js";
21
21
  import "./chunk-H2BES6FU.js";
22
22
  import "./chunk-PZ5AY32C.js";
@@ -28,4 +28,4 @@ export {
28
28
  rejectDraft,
29
29
  reviewedPrompts
30
30
  };
31
- //# sourceMappingURL=skill-land-XU2APPBJ.js.map
31
+ //# sourceMappingURL=skill-land-EHXZW47F.js.map
@@ -3,15 +3,15 @@ import {
3
3
  consolidateOn,
4
4
  mergeCandidates,
5
5
  proposeMerges
6
- } from "./chunk-NCGM36NX.js";
7
- import "./chunk-P3GLY42T.js";
8
- import "./chunk-3FPUBAEW.js";
9
- import "./chunk-IEZVFKUT.js";
6
+ } from "./chunk-2OMDSTSS.js";
7
+ import "./chunk-2BFG4IBC.js";
8
+ import "./chunk-N77YCMSX.js";
9
+ import "./chunk-PMOZKUEM.js";
10
10
  import "./chunk-ZSWI3Y4Y.js";
11
11
  import "./chunk-J7LYDRML.js";
12
12
  import "./chunk-VQCNWRTL.js";
13
13
  import "./chunk-GYR7U6KA.js";
14
- import "./chunk-NIZLBNH4.js";
14
+ import "./chunk-M2PC64DB.js";
15
15
  import "./chunk-R6GNP7DS.js";
16
16
  import "./chunk-H2BES6FU.js";
17
17
  import "./chunk-PZ5AY32C.js";
@@ -21,4 +21,4 @@ export {
21
21
  mergeCandidates,
22
22
  proposeMerges
23
23
  };
24
- //# sourceMappingURL=skill-merge-GXBQLBZA.js.map
24
+ //# sourceMappingURL=skill-merge-HBZBALZX.js.map
@@ -126,8 +126,11 @@ interface DraftRecord {
126
126
  /** filled by skill evals (Phase 14) when they run at approval time */
127
127
  evals?: Record<string, unknown>;
128
128
  }
129
- /** The session's timeline from its transcript: prompts, tool calls, and the errors they returned. Fail-soft. */
130
- declare function transcriptTimeline(path: string | null, max?: number): string[];
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[];
131
134
  declare function buildBrief(job: ReviewJob, skills?: SkillFile[]): string;
132
135
  interface ReviewerDraft {
133
136
  kind: "new" | "patch";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/semantic-memory",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, multiple typed corpora (vault, code, plans, docs, research, project-map), unified cross-corpus knowledge graph, conditional tool registration, in-process drift detection, and human↔code translation. Rebrand of @theglitchking/semantic-sidekick at v1.0 — see CHANGELOG for migration.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/claude-run.ts"],"sourcesContent":["/**\n * Every `claude` process the skill loop starts goes through here (v1.7): the reviewer, eval runs, graders,\n * description tuning. Two guarantees:\n *\n * - **At most 5 at once, machine-wide.** A slot file under ~/.semantic-memory/claude-slots/ is held for the\n * life of each run (SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY, 1–5, higher clamps to 5). A slot whose\n * holder died is reclaimed.\n * - **Fenced.** No hooks, no MCP servers (claude.ai connectors included), no user settings, only the tools\n * asked for, never Agent/Task (no nested subagents), nothing outside the working directory (dontAsk),\n * no session file, and SEMANTIC_MEMORY_REVIEWER=1 so any semantic-memory hook that still runs exits at once.\n * Verified flag by flag on Claude Code 2.1.310 (.planning/v17-skill-loop-and-jev-mode/notes.md, Phase 2).\n */\nimport { spawn } from \"node:child_process\";\nimport { openSync, writeSync, closeSync, readFileSync, unlinkSync, mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport const MAX_CLAUDE = 5;\n\nexport function claudeConcurrency(env = process.env): number {\n const n = Number(env.SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY);\n return Number.isFinite(n) && n >= 1 ? Math.min(MAX_CLAUDE, Math.floor(n)) : MAX_CLAUDE;\n}\n\nexport const slotsDir = (env = process.env) =>\n env.SEMANTIC_MEMORY_CLAUDE_SLOTS_DIR || join(env.HOME || homedir(), \".semantic-memory\", \"claude-slots\");\n\nconst alive = (pid: number) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n};\n\n/** Take one of the machine-wide slots, waiting for one to free up. Resolves to its release function. */\nexport async function acquireSlot(opts: { env?: NodeJS.ProcessEnv; timeoutMs?: number; pollMs?: number } = {}): Promise<() => void> {\n const env = opts.env ?? process.env;\n const dir = slotsDir(env);\n const max = claudeConcurrency(env);\n const deadline = Date.now() + (opts.timeoutMs ?? 30 * 60_000);\n mkdirSync(dir, { recursive: true });\n for (;;) {\n for (let i = 0; i < max; i++) {\n const path = join(dir, `slot-${i}`);\n try {\n const fd = openSync(path, \"wx\");\n writeSync(fd, `${process.pid}\\n`);\n closeSync(fd);\n let released = false;\n return () => {\n if (released) return;\n released = true;\n try {\n unlinkSync(path);\n } catch {}\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"EEXIST\") throw e;\n // held: reclaim it when its holder is gone (crashed worker, killed run)\n try {\n const pid = Number(readFileSync(path, \"utf8\").trim());\n if (!pid || !alive(pid)) unlinkSync(path);\n } catch {}\n }\n }\n if (Date.now() > deadline) throw new Error(`no claude slot free after ${opts.timeoutMs ?? 1_800_000} ms (${max} in use)`);\n await new Promise((r) => setTimeout(r, opts.pollMs ?? 250));\n }\n}\n\nexport interface ClaudeRunOptions {\n prompt: string;\n cwd: string;\n /** built-in tools the run may use; [] = none */\n tools: string[];\n /** with dontAsk, only these permission rules run un-prompted, e.g. \"Edit\", \"Bash(npm test:*)\"; the rest is denied */\n allowedTools?: string[];\n maxTurns: number;\n model?: string;\n /** \"json\": one result object; \"stream-json\": every event (to see which tools were called) */\n output?: \"json\" | \"stream-json\";\n timeoutMs?: number;\n /** extra directories the run may read */\n addDirs?: string[];\n appendSystemPrompt?: string;\n env?: NodeJS.ProcessEnv;\n}\n\nexport interface ClaudeRunResult {\n ok: boolean;\n /** final assistant text */\n result: string;\n /** stream-json events (empty for json output) */\n events: any[];\n costUsd: number;\n turns: number;\n error?: string;\n}\n\nexport const claudeBin = (env = process.env) => env.SEMANTIC_MEMORY_CLAUDE_BIN || \"claude\";\nexport const skillLoopModel = (env = process.env) => env.SEMANTIC_MEMORY_SKILL_LOOP_MODEL || \"sonnet\";\n\n/** The fenced argument list (exported for tests: these flags are the security boundary). */\nexport function fencedArgs(o: Pick<ClaudeRunOptions, \"tools\" | \"allowedTools\" | \"maxTurns\" | \"model\" | \"output\" | \"addDirs\" | \"appendSystemPrompt\">, env = process.env): string[] {\n const args = [\n \"-p\",\n \"--tools\", o.tools.join(\",\"),\n \"--disallowedTools\", \"Task,Agent\",\n \"--strict-mcp-config\",\n \"--settings\", JSON.stringify({ disableAllHooks: true }),\n \"--setting-sources\", \"project,local\",\n \"--permission-mode\", \"dontAsk\",\n \"--no-session-persistence\",\n \"--max-turns\", String(o.maxTurns),\n \"--model\", o.model ?? skillLoopModel(env),\n \"--output-format\", o.output ?? \"json\",\n ];\n if (o.allowedTools?.length) args.push(\"--allowedTools\", o.allowedTools.join(\",\"));\n if ((o.output ?? \"json\") === \"stream-json\") args.push(\"--verbose\");\n for (const d of o.addDirs ?? []) args.push(\"--add-dir\", d);\n if (o.appendSystemPrompt) args.push(\"--append-system-prompt\", o.appendSystemPrompt);\n return args;\n}\n\n/** Run one fenced `claude -p` inside a slot. Never throws: failures come back as `ok: false` with the reason. */\nexport async function runClaude(o: ClaudeRunOptions): Promise<ClaudeRunResult> {\n const env = { ...(o.env ?? process.env), SEMANTIC_MEMORY_REVIEWER: \"1\" };\n const fail = (error: string): ClaudeRunResult => ({ ok: false, result: \"\", events: [], costUsd: 0, turns: 0, error });\n let release: () => void;\n try {\n release = await acquireSlot({ env });\n } catch (e) {\n return fail((e as Error).message);\n }\n try {\n return await new Promise<ClaudeRunResult>((done) => {\n const child = spawn(claudeBin(env), fencedArgs(o, env), { cwd: o.cwd, env, stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n let stdout = \"\";\n let stderr = \"\";\n const timer = setTimeout(() => {\n child.kill(\"SIGKILL\");\n done(fail(`timed out after ${o.timeoutMs ?? 600_000} ms`));\n }, o.timeoutMs ?? 600_000);\n child.stdout.on(\"data\", (d) => (stdout += d));\n child.stderr.on(\"data\", (d) => (stderr += d));\n child.on(\"error\", (e) => {\n clearTimeout(timer);\n done(fail(`could not start ${claudeBin(env)}: ${e.message}`));\n });\n child.on(\"close\", (code) => {\n clearTimeout(timer);\n done(parseClaudeOutput(stdout, o.output ?? \"json\", code, stderr));\n });\n child.stdin.on(\"error\", () => {});\n child.stdin.end(o.prompt);\n });\n } finally {\n release();\n }\n}\n\nexport function parseClaudeOutput(stdout: string, output: \"json\" | \"stream-json\", code: number | null, stderr = \"\"): ClaudeRunResult {\n const events: any[] = [];\n let final: any = null;\n if (output === \"json\") {\n try {\n final = JSON.parse(stdout.trim());\n } catch {}\n } else {\n for (const line of stdout.split(\"\\n\")) {\n if (!line.trim()) continue;\n try {\n const e = JSON.parse(line);\n events.push(e);\n if (e.type === \"result\") final = e;\n } catch {}\n }\n }\n if (!final) return { ok: false, result: \"\", events, costUsd: 0, turns: 0, error: `no result from claude (exit ${code}): ${stderr.trim().slice(0, 300)}` };\n return {\n ok: !final.is_error && final.subtype !== \"error_during_execution\",\n result: typeof final.result === \"string\" ? final.result : \"\",\n events,\n costUsd: Number(final.total_cost_usd) || 0,\n turns: Number(final.num_turns) || 0,\n error: final.is_error ? String(final.result ?? final.subtype ?? \"error\") : final.subtype === \"error_max_turns\" ? \"max turns reached\" : undefined,\n };\n}\n\n/** Names of the skills a stream-json run invoked through the Skill tool, in order. */\nexport const skillsCalled = (events: any[]): string[] =>\n events\n .filter((e) => e.type === \"assistant\")\n .flatMap((e) => (Array.isArray(e.message?.content) ? e.message.content : []))\n .filter((c: any) => c.type === \"tool_use\" && c.name === \"Skill\" && typeof c.input?.skill === \"string\")\n .map((c: any) => c.input.skill);\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/skill-queue.ts"],"sourcesContent":["/**\n * The skill loop's review queue (v1.7). SessionEnd writes one job per session that has signal and starts\n * the worker detached; the worker drains this repo's jobs oldest first, inside a global daily cap. The\n * session never waits: SessionEnd hooks get 1.5 s by default, so the hook only writes and spawns.\n *\n * ~/.semantic-memory/review-queue/<session>.json pending jobs (all repos)\n * ~/.semantic-memory/review-queue/{done,failed}/ finished jobs, never re-reviewed\n * ~/.semantic-memory/skill-loop-runs.jsonl one line per review run (the daily cap)\n * <repo>/.claude/.semantic-memory/skill-loop.lock the repo's worker (pid)\n * <repo>/.claude/.semantic-memory/skill-loop.log what the worker did\n *\n * Standalone for hooks (no embedder).\n */\nimport { appendFileSync, existsSync, mkdirSync, openSync, writeSync, closeSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { readEvidence, stateDir } from \"./skill-evidence.js\";\n\nexport interface ReviewJob {\n session_id: string;\n repo: string;\n transcript_path: string | null;\n reason: string;\n ts: string;\n evidence: { failures: string[]; corrections: number; skills_used: string[]; tool_calls: number };\n attempts?: number;\n}\n\nexport const smHome = (env = process.env) => env.SEMANTIC_MEMORY_STATE_HOME || join(env.HOME || homedir(), \".semantic-memory\");\nexport const queueDir = (env = process.env) => join(smHome(env), \"review-queue\");\nconst runsPath = (env = process.env) => join(smHome(env), \"skill-loop-runs.jsonl\");\nexport const lockPath = (repo: string) => join(stateDir(repo), \"skill-loop.lock\");\nexport const logPath = (repo: string) => join(stateDir(repo), \"skill-loop.log\");\n\nexport const dailyRunCap = (env = process.env) => Math.max(0, Number(env.SEMANTIC_MEMORY_SKILL_LOOP_DAILY_RUNS ?? 10) || 0);\nconst minToolCalls = (env = process.env) => Number(env.SEMANTIC_MEMORY_SKILL_LOOP_MIN_TOOL_CALLS) || 20;\n// shortcut: counts tool calls by scanning the transcript text; above this size the count is skipped (signal then\n// needs a failure or a correction). Upgrade if long tool-only sessions should qualify.\nconst TRANSCRIPT_SCAN_MAX = 50 * 1024 * 1024;\n\nconst safeName = (s: string) => s.replace(/[^A-Za-z0-9_.-]/g, \"_\").slice(0, 120);\n\nfunction countToolCalls(transcript: string | null): number {\n if (!transcript) return 0;\n try {\n if (statSync(transcript).size > TRANSCRIPT_SCAN_MAX) return 0;\n return readFileSync(transcript, \"utf8\").split('\"type\":\"tool_use\"').length - 1;\n } catch {\n return 0;\n }\n}\n\n/** The job SessionEnd would write for this session, or null when the session has nothing to learn from. */\nexport function sessionJob(repo: string, input: { session_id?: string; transcript_path?: string; reason?: string }, env = process.env): ReviewJob | null {\n const session_id = typeof input.session_id === \"string\" ? input.session_id : \"\";\n if (!session_id) return null;\n const failures = readEvidence(repo, \"failures\").filter((f) => f.session_id === session_id);\n const corrections = readEvidence(repo, \"corrections\").filter((c) => c.session_id === session_id).length;\n const skills_used = [...new Set(readEvidence(repo, \"usage\").filter((u) => u.session_id === session_id).map((u) => String(u.skill)))];\n const transcript_path = typeof input.transcript_path === \"string\" ? input.transcript_path : null;\n const tool_calls = failures.length || corrections ? 0 : countToolCalls(transcript_path);\n const why = failures.length ? `${failures.length} failure(s)` : corrections ? `${corrections} correction(s)` : tool_calls >= minToolCalls(env) ? `${tool_calls} tool calls` : \"\";\n if (!why) return null;\n return {\n session_id,\n repo,\n transcript_path,\n reason: `${why}; ended: ${input.reason ?? \"unknown\"}`,\n ts: new Date().toISOString(),\n evidence: { failures: failures.map((f) => String(f.id ?? \"\")).filter(Boolean), corrections, skills_used, tool_calls },\n };\n}\n\nexport function enqueue(job: ReviewJob, env = process.env): string {\n mkdirSync(queueDir(env), { recursive: true });\n const path = join(queueDir(env), `${safeName(job.session_id)}.json`);\n // a session that ends twice (e.g. /clear then exit) is one job, and one already reviewed stays done\n if ([\"done\", \"failed\"].some((d) => existsSync(join(queueDir(env), d, `${safeName(job.session_id)}.json`)))) return path;\n writeFileSync(path, JSON.stringify(job, null, 1));\n return path;\n}\n\nexport function pendingJobs(repo: string, env = process.env): { path: string; job: ReviewJob }[] {\n let files: string[] = [];\n try {\n files = readdirSync(queueDir(env)).filter((f) => f.endsWith(\".json\"));\n } catch {\n return [];\n }\n const jobs: { path: string; job: ReviewJob }[] = [];\n for (const f of files) {\n try {\n const path = join(queueDir(env), f);\n const job = JSON.parse(readFileSync(path, \"utf8\")) as ReviewJob;\n if (job.repo === repo) jobs.push({ path, job });\n } catch {}\n }\n return jobs.sort((a, b) => a.job.ts.localeCompare(b.job.ts));\n}\n\nexport function finishJob(path: string, outcome: \"done\" | \"failed\", result: Record<string, unknown>, env = process.env): void {\n const dir = join(queueDir(env), outcome);\n mkdirSync(dir, { recursive: true });\n const job = JSON.parse(readFileSync(path, \"utf8\"));\n writeFileSync(path, JSON.stringify({ ...job, finished: new Date().toISOString(), result }, null, 1));\n renameSync(path, join(dir, path.split(\"/\").pop()!));\n}\n\nexport function bumpAttempts(path: string): number {\n const job = JSON.parse(readFileSync(path, \"utf8\")) as ReviewJob;\n job.attempts = (job.attempts ?? 0) + 1;\n writeFileSync(path, JSON.stringify(job, null, 1));\n return job.attempts;\n}\n\nconst localDay = (d = new Date()) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, \"0\")}-${String(d.getDate()).padStart(2, \"0\")}`;\n\nexport function runsToday(env = process.env, now = new Date()): number {\n try {\n const day = localDay(now);\n return readFileSync(runsPath(env), \"utf8\").split(\"\\n\").filter((l) => l.includes(`\"day\":\"${day}\"`)).length;\n } catch {\n return 0;\n }\n}\n\nexport function recordRun(repo: string, session_id: string, env = process.env, now = new Date()): void {\n mkdirSync(smHome(env), { recursive: true });\n appendFileSync(runsPath(env), JSON.stringify({ day: localDay(now), ts: now.toISOString(), repo, session_id }) + \"\\n\");\n}\n\nconst alive = (pid: number) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (e) {\n return (e as NodeJS.ErrnoException).code === \"EPERM\";\n }\n};\n\nexport function lockHolder(repo: string): number | null {\n try {\n const pid = Number(readFileSync(lockPath(repo), \"utf8\").trim());\n return pid && alive(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\n/** Take the repo's worker lock (reclaiming a dead holder's). Returns a release function, or null if held. */\nexport function takeLock(repo: string): (() => void) | null {\n mkdirSync(stateDir(repo), { recursive: true });\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(lockPath(repo), \"wx\");\n writeSync(fd, `${process.pid}\\n`);\n closeSync(fd);\n return () => {\n try {\n if (Number(readFileSync(lockPath(repo), \"utf8\").trim()) === process.pid) unlinkSync(lockPath(repo));\n } catch {}\n };\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== \"EEXIST\") throw e;\n if (lockHolder(repo) !== null) return null;\n try {\n unlinkSync(lockPath(repo));\n } catch {}\n }\n }\n return null;\n}\n\nexport function log(repo: string, line: string): void {\n try {\n mkdirSync(stateDir(repo), { recursive: true });\n appendFileSync(logPath(repo), `${new Date().toISOString()} ${line}\\n`);\n } catch {}\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/skill-review.ts","../src/core/skill-review-prompt.ts"],"sourcesContent":["/**\n * The skill loop's reviewer (v1.7): one finished session in, zero to two skill drafts out.\n *\n * 1. A brief: the session's prompts, tool calls and errors (from the transcript, read fail-soft: its format\n * is internal to Claude Code), plus the evidence logs, the skills this repo loads and its KB fact ids.\n * Redacted (broad) and capped before the model sees it.\n * 2. A fenced `claude -p` (Read / Grep / Glob on the repo only; see claude-run.ts) answers JSON.\n * 3. Each draft is rendered, redacted, linted (skill-schema.ts) and written to\n * .claude/.semantic-memory/skill-drafts/<id>/ — or rejected with its reasons. Nothing is written anywhere\n * else; landing a draft is a person's call (`skill-loop review`).\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { redact } from \"./redact.js\";\nimport { readEvidence, stateDir } from \"./skill-evidence.js\";\nimport { factIds, findSkills, lintSkill, renderSkill, type SkillFile, type SkillLoopMeta, type SkillScope } from \"./skill-schema.js\";\nimport { runClaude } from \"./claude-run.js\";\nimport { REVIEW_PROMPT } from \"./skill-review-prompt.js\";\nimport type { ReviewJob } from \"./skill-queue.js\";\nimport type { ReviewOutcome } from \"./skill-worker.js\";\nimport { decideScope, indexSkill, promotionPeers, type ScopeDecision } from \"./skill-scope.js\";\n\nexport const draftsDir = (repo: string) => join(stateDir(repo), \"skill-drafts\");\nconst BRIEF_MAX = 40_000;\nconst TIMELINE_MAX = 120;\n\nexport interface DraftRecord {\n id: string;\n kind: \"new\" | \"patch\" | \"merge\";\n name: string;\n scope: SkillScope;\n /** patch: the SKILL.md it replaces, and that file's sha256 when the draft was made */\n target?: string;\n base_sha256?: string;\n session_id: string;\n created: string;\n warnings: string[];\n /** directory scope: where it lands (repo-relative subdirectory) */\n directory?: string;\n scope_why?: string;\n /** Jev thinks it holds in any repo */\n global_candidate?: boolean;\n /** other repos with the same lesson: review offers one global skill replacing the copies */\n promotion?: { repo: string; path: string }[];\n /** merge: the SKILL.md files the merged skill replaces (archived when it lands) */\n supersedes?: string[];\n /** filled by skill evals (Phase 14) when they run at approval time */\n evals?: Record<string, unknown>;\n}\n\n// --- the brief ---\n\n/** The session's timeline from its transcript: prompts, tool calls, and the errors they returned. Fail-soft. */\nexport function transcriptTimeline(path: string | null, max = TIMELINE_MAX): string[] {\n if (!path) return [];\n let raw = \"\";\n try {\n if (statSync(path).size > 200 * 1024 * 1024) return [\"(transcript too large to read)\"];\n raw = readFileSync(path, \"utf8\");\n } catch {\n return [];\n }\n const out: string[] = [];\n const calls = new Map<string, string>();\n for (const line of raw.split(\"\\n\")) {\n let e: any;\n try {\n e = JSON.parse(line);\n } catch {\n continue;\n }\n const content = e?.message?.content;\n if (e?.type === \"user\" && typeof content === \"string\" && !e.isMeta) {\n // wrappers (slash-command echoes, task notifications, local command output) are not the user talking\n if (!content.trimStart().startsWith(\"<\")) out.push(`USER: ${content.slice(0, 600)}`);\n } else if (e?.type === \"assistant\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_use\") continue;\n const input = c.input ?? {};\n const what = typeof input.command === \"string\" ? input.command : typeof input.file_path === \"string\" ? input.file_path : typeof input.skill === \"string\" ? input.skill : JSON.stringify(input);\n calls.set(c.id, `${c.name}: ${String(what).slice(0, 300)}`);\n }\n } else if (e?.type === \"user\" && Array.isArray(content)) {\n for (const c of content) {\n if (c?.type !== \"tool_result\") continue;\n const call = calls.get(c.tool_use_id) ?? \"tool\";\n const text = typeof c.content === \"string\" ? c.content : Array.isArray(c.content) ? c.content.map((x: any) => x?.text ?? \"\").join(\" \") : \"\";\n out.push(c.is_error ? `TOOL ${call}\\n → ERROR (${c.tool_use_id}): ${text.slice(0, 500)}` : `TOOL ${call} → ok`);\n }\n }\n }\n // keep the end of long sessions (where the fix landed) and every error\n if (out.length <= max) return out;\n const errors = out.filter((l) => l.includes(\"→ ERROR\")).slice(-Math.floor(max / 2));\n const tail = out.slice(-(max - errors.length)).filter((l) => !errors.includes(l));\n return [`(${out.length - errors.length - tail.length} earlier entries omitted)`, ...errors, ...tail];\n}\n\nfunction topLevel(repo: string): string[] {\n try {\n return readdirSync(repo).filter((f) => !f.startsWith(\".\") || f === \".claude\" || f === \".documentation\").slice(0, 60);\n } catch {\n return [];\n }\n}\n\nexport function buildBrief(job: ReviewJob, skills: SkillFile[] = findSkills(job.repo)): string {\n const repo = job.repo;\n const failures = readEvidence(repo, \"failures\").filter((f) => f.session_id === job.session_id);\n const corrections = readEvidence(repo, \"corrections\").filter((c) => c.session_id === job.session_id);\n const used = new Set(job.evidence.skills_used);\n // free text is redacted (broad); the ids the reviewer must cite back (session, failure ids) are not, or broad\n // redaction would mask them too. The evidence logs were redacted when written.\n const r = (t: string) => redact(t, \"broad\").text;\n const lines = [\n `# Session ${job.session_id}`,\n `Repository: ${basename(repo)} — top level: ${topLevel(repo).join(\", \")}`,\n `Why reviewed: ${job.reason}`,\n \"\",\n \"## Failures (from the evidence log)\",\n ...(failures.length ? failures.map((f) => `- id ${f.id}: \\`${r(String(f.command))}\\`\\n error: ${r(String(f.error).slice(0, 500))}`) : [\"(none)\"]),\n \"\",\n \"## Corrections the user gave\",\n ...(corrections.length ? corrections.map((c, i) => `- correction ${i + 1} (${c.cue}): ${r(String(c.excerpt))}`) : [\"(none)\"]),\n \"\",\n \"## Skills this repository loads\",\n ...(skills.length\n ? skills.map((s) => `- ${s.skill.name || basename(s.dir)} (${s.scope}; learned: ${s.learned}${s.skill.meta?.pinned ? \"; pinned\" : \"\"}${used.has(s.skill.name) ? \"; USED this session\" : \"\"}): ${r(s.skill.description.slice(0, 200))}`)\n : [\"(none)\"]),\n ...skills\n .filter((s) => s.learned && used.has(s.skill.name))\n .flatMap((s) => [\"\", `### Current text of the learned skill ${s.skill.name} (used this session)`, r(s.skill.body.slice(0, 4000))]),\n \"\",\n \"## Knowledge-base fact ids (for cites)\",\n [...factIds(repo)].slice(0, 200).join(\", \") || \"(none)\",\n \"\",\n \"## Timeline (prompts, tool calls, errors)\",\n ...transcriptTimeline(job.transcript_path).map(r),\n ];\n let brief = lines.join(\"\\n\");\n if (brief.length > BRIEF_MAX) brief = brief.slice(0, BRIEF_MAX) + \"\\n(brief cut at its size cap)\";\n return brief;\n}\n\n// --- the answer ---\n\nexport interface ReviewerDraft {\n kind: \"new\" | \"patch\";\n target?: string;\n name: string;\n description: string;\n scope: string;\n applies_to?: { paths?: string[]; commands?: string[] };\n triggers?: string[];\n provenance?: { sessions?: string[]; failures?: string[]; corrections?: string[] };\n cites?: string[];\n body: string;\n}\n\n/** The JSON object in the reviewer's answer (bare, or inside a ```json fence). */\nexport function parseReviewerJson(text: string): { drafts: ReviewerDraft[]; none_reason?: string } | null {\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n const candidates = [fenced?.[1], text, text.slice(text.indexOf(\"{\"), text.lastIndexOf(\"}\") + 1)].filter(Boolean) as string[];\n for (const c of candidates) {\n try {\n const v = JSON.parse(c.trim());\n if (v && Array.isArray(v.drafts)) return v;\n } catch {}\n }\n return null;\n}\n\n// a placeholder the model copied from redacted text is not evidence\nconst strs = (v: unknown) => (Array.isArray(v) ? v.filter((x): x is string => typeof x === \"string\" && !x.includes(\"[REDACTED]\")) : []);\nconst sha256 = (s: string) => createHash(\"sha256\").update(s).digest(\"hex\");\n\nexport type ScopeResolver = (d: ReviewerDraft) => Promise<ScopeDecision>;\n\n/**\n * Turn one reviewer draft into a draft folder, or say why not. `resolveScope` decides the final scope\n * (skill-scope.ts: deterministic rules, then Jev); the reviewer's own choice is never trusted for it.\n */\nexport async function materializeDraft(\n repo: string,\n job: ReviewJob,\n d: ReviewerDraft,\n skills: SkillFile[],\n resolveScope: ScopeResolver = (x) => decideScope(repo, x),\n env = process.env,\n merge?: { supersedes: string[] },\n): Promise<{ id?: string; rejected?: string }> {\n if (!d || typeof d.name !== \"string\" || typeof d.body !== \"string\" || typeof d.description !== \"string\") return { rejected: \"malformed draft\" };\n let target: SkillFile | undefined;\n if (d.kind === \"patch\") {\n target = skills.find((s) => s.skill.name === d.target);\n if (!target) return { rejected: `patch target \"${d.target}\" not found` };\n if (!target.learned) return { rejected: `\"${d.target}\" was not written by the skill loop: never patched` };\n if (target.skill.meta?.pinned) return { rejected: `\"${d.target}\" is pinned` };\n }\n const prov = {\n sessions: [...new Set([job.session_id, ...strs(d.provenance?.sessions)])],\n failures: strs(d.provenance?.failures),\n corrections: strs(d.provenance?.corrections),\n };\n if (prov.failures.length + prov.corrections.length === 0 && job.evidence.failures.length + job.evidence.corrections === 0) {\n return { rejected: \"no failure or correction behind it\" };\n }\n // a patch keeps its skill's scope; a new skill's comes from the resolver\n const decision: ScopeDecision = target\n ? { scope: target.scope === \"directory\" ? \"directory\" : \"project\", globalCandidate: false, why: \"patch: the skill's own scope\" }\n : await resolveScope(d);\n const scope: SkillScope = target?.scope ?? decision.scope;\n const paths = strs(d.applies_to?.paths);\n if (decision.directory && !paths.includes(decision.directory)) paths.unshift(decision.directory);\n const name = d.kind === \"patch\" ? target!.skill.name : d.name;\n const meta: SkillLoopMeta = {\n schema: 1,\n scope,\n status: \"draft\",\n pinned: false,\n triggers: strs(d.triggers),\n applies_to: { paths, commands: strs(d.applies_to?.commands) },\n provenance: prov,\n cites: strs(d.cites),\n supersedes: merge ? merge.supersedes.map((p) => basename(join(p, \"..\"))) : (target?.skill.meta?.supersedes ?? []),\n };\n // precise: a skill is prose and code, broad would mangle ordinary identifiers\n const text = redact(renderSkill({ name, description: d.description, meta, body: d.body }), \"precise\").text;\n const findings = lintSkill(join(draftsDir(repo), name, \"SKILL.md\"), text, { projectRoot: repo, others: skills.filter((s) => s.path !== target?.path) }, name);\n const errors = findings.filter((f) => f.severity === \"error\");\n if (errors.length) return { rejected: `lint: ${errors.map((f) => `${f.rule}: ${f.message}`).join(\"; \")}` };\n const id = `${name}-${sha256(text).slice(0, 8)}`;\n const dir = join(draftsDir(repo), id);\n mkdirSync(dir, { recursive: true });\n writeFileSync(join(dir, \"SKILL.md\"), text);\n const record: DraftRecord = {\n id,\n kind: merge ? \"merge\" : d.kind === \"patch\" ? \"patch\" : \"new\",\n name,\n scope,\n ...(target ? { target: target.path, base_sha256: sha256(readFileSync(target.path, \"utf8\")) } : {}),\n session_id: job.session_id,\n created: new Date().toISOString(),\n warnings: findings.filter((f) => f.severity === \"warn\").map((f) => `${f.rule}: ${f.message}`),\n ...(decision.directory ? { directory: decision.directory } : {}),\n scope_why: decision.why,\n ...(decision.globalCandidate ? { global_candidate: true } : {}),\n ...(merge ? { supersedes: merge.supersedes } : {}),\n };\n if (!target && !merge) {\n const peers = promotionPeers(repo, name, env);\n if (peers.length) record.promotion = peers.map((p) => ({ repo: p.repo, path: p.path }));\n indexSkill({ repo, name, path: join(dir, \"SKILL.md\"), status: \"draft\", ...(decision.globalCandidate ? { global_candidate: true } : {}) }, env);\n }\n writeFileSync(join(dir, \"draft.json\"), JSON.stringify(record, null, 1));\n return { id };\n}\n\nexport function listDrafts(repo: string): { dir: string; record: DraftRecord }[] {\n let ids: string[] = [];\n try {\n ids = readdirSync(draftsDir(repo));\n } catch {\n return [];\n }\n const out: { dir: string; record: DraftRecord }[] = [];\n for (const id of ids) {\n const dir = join(draftsDir(repo), id);\n try {\n if (existsSync(join(dir, \"SKILL.md\"))) out.push({ dir, record: JSON.parse(readFileSync(join(dir, \"draft.json\"), \"utf8\")) });\n } catch {}\n }\n return out.sort((a, b) => a.record.created.localeCompare(b.record.created));\n}\n\nexport interface ReviewOptions {\n env?: NodeJS.ProcessEnv;\n resolveScope?: ScopeResolver;\n maxTurns?: number;\n}\n\n/** Review one queued session (the worker's review function). */\nexport async function reviewSession(job: ReviewJob, opts: ReviewOptions = {}): Promise<ReviewOutcome> {\n const skills = findSkills(job.repo);\n const brief = buildBrief(job, skills);\n const run = await runClaude({\n prompt: `${REVIEW_PROMPT}\\n\\n---\\n\\n${brief}`,\n cwd: job.repo,\n tools: [\"Read\", \"Grep\", \"Glob\"],\n maxTurns: opts.maxTurns ?? 8,\n output: \"json\",\n timeoutMs: 10 * 60_000,\n env: opts.env,\n });\n if (!run.ok && !run.result) throw new Error(run.error ?? \"reviewer run failed\");\n const answer = parseReviewerJson(run.result);\n if (!answer) return { drafts: [], rejected: [{ reason: \"reviewer answer was not JSON\" }], costUsd: run.costUsd };\n const drafts: string[] = [];\n const rejected: { reason: string }[] = [];\n for (const d of answer.drafts.slice(0, 2)) {\n const env = opts.env ?? process.env;\n const r = await materializeDraft(job.repo, job, d, skills, opts.resolveScope ?? ((x) => decideScope(job.repo, x, env)), env);\n if (r.id) drafts.push(r.id);\n else rejected.push({ reason: `${d?.name ?? \"?\"}: ${r.rejected}` });\n }\n return { drafts, rejected, costUsd: run.costUsd, note: answer.drafts.length ? undefined : answer.none_reason?.slice(0, 200) };\n}\n","/**\n * The skill loop reviewer's fixed instructions (v1.7). The brief (what happened in one session) follows\n * them on stdin. The reviewer can read the repo (Read / Grep / Glob) and nothing else; it answers with\n * JSON, and the worker — not the model — writes drafts, after redaction and lint.\n */\nexport const REVIEW_PROMPT = `You review one finished Claude Code session in this repository and decide whether it taught a\nreusable procedure worth saving as a skill — a short, step-by-step playbook a future session should follow.\n\nDraft only from the evidence in the brief below: a command that failed and the fix that worked, a correction\nthe user gave, or a learned skill that was used and then failed or was corrected. Never invent steps, commands,\npaths or facts that the brief or the repository does not show. Most sessions teach nothing reusable: then\nanswer with no drafts. A draft is worth it only when the same situation is likely to come up again here.\n\nKinds of draft:\n- \"new\": a procedure no existing skill covers. Check the existing skills in the brief first.\n- \"patch\": an existing learned skill (listed with \"learned: true\") was used in this session and then failed or\n was corrected: give its full revised body. Never patch a skill that is pinned or not learned.\n\nEach draft:\n- name: kebab-case, specific (\"restart-vite-dev-server\", not \"dev-helper\").\n- description: what it does and when to use it, one or two sentences, starting with the action; include\n \"Use when ...\" with the literal error, command or request that should load it. Under 300 characters.\n- scope: \"project\" (about this repository) or \"directory\" (only about one subdirectory; give it in applies_to.paths).\n- applies_to: { paths: [repo-relative paths that exist], commands: [program names the steps run] }.\n- triggers: the literal error strings, commands or user phrases from the brief that this skill answers.\n- provenance: { sessions: [the session id], failures: [failure ids from the brief it came from], corrections: [correction\n numbers from the brief, as strings] } — at least one failure or correction.\n- cites: knowledge-base fact ids from the brief's list that the steps rely on (only ids that are listed).\n- body: markdown with exactly these sections, in order:\n ## When to use — the situation, in two or three lines.\n ## Steps — numbered, concrete commands or actions as the session finally did them.\n ## Pitfalls — what went wrong in the session and why; each line names the failure id or fact id it comes from.\n ## Verify — one command whose exit code proves it worked, in a fenced code block; or, if none exists, one\n sentence a reviewer can check.\n\nHard rules:\n- No secrets, tokens, passwords, keys, personal data, customer data, names of people or money amounts, even if\n the brief shows them. Use placeholders like <token>.\n- At most 2 drafts. No duplicates of existing skills.\n- Do not write or change any file. You may read files in this repository to get paths and commands right.\n\nAnswer with one JSON object and nothing else:\n{\"drafts\": [{\"kind\": \"new\" | \"patch\", \"target\": \"<existing skill name, patch only>\", \"name\": \"...\",\n \"description\": \"...\", \"scope\": \"project\" | \"directory\", \"applies_to\": {\"paths\": [], \"commands\": []},\n \"triggers\": [], \"provenance\": {\"sessions\": [], \"failures\": [], \"corrections\": []}, \"cites\": [], \"body\": \"...\"}],\n \"none_reason\": \"<when drafts is empty: one line on why>\"}\n`;\n"],"mappings":"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","names":[]}