skills-viewer 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/build/cli.js +0 -0
- package/build/server/cache.js +53 -0
- package/build/server/diagnose.js +3 -1
- package/build/server/flow.js +3 -1
- package/build/server/index.js +99 -8
- package/build/server/manage.js +45 -8
- package/build/server/memory-signals.js +341 -0
- package/build/server/memory-triage.js +1400 -0
- package/build/server/memory.js +607 -0
- package/build/server/scan.js +28 -13
- package/build/server/summary.js +3 -1
- package/build/server/usage.js +149 -29
- package/dist/assets/index-BtXAJZT3.css +1 -0
- package/dist/assets/index-CgQLhTsl.js +93 -0
- package/dist/index.html +2 -2
- package/package.json +16 -14
- package/dist/assets/index-C9NICndS.css +0 -1
- package/dist/assets/index-bdDgkHOX.js +0 -67
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# skills-viewer
|
|
2
2
|
|
|
3
|
-
Browse, search and manage your [Claude Code](https://code.claude.com) skills, commands, agents and
|
|
3
|
+
Browse, search and manage your [Claude Code](https://code.claude.com) skills, commands, agents, hooks and auto memory in the browser.
|
|
4
4
|
|
|
5
5
|
Scans every project registered in `~/.claude.json` (plus user scope, plugins and built-ins), and serves a local web UI to explore them — with usage stats, AI-generated summaries, full SKILL.md rendering, same-name diffs, and copy/delete management.
|
|
6
6
|
|
|
@@ -19,11 +19,12 @@ npx skills-viewer
|
|
|
19
19
|
- **All scopes in one view** — user (`~/.claude/skills`), every project's `.claude/skills` / `.claude/commands`, installed plugins, and Claude Code built-ins, grouped by source
|
|
20
20
|
- **Purpose grouping (AI)** — one haiku call classifies everything installed by _when you use it_ into 4–8 groups generated for your environment (planning / building / review / release / … as a role-agnostic guide — a designer's or PM's skills get their own groups). Switch the list between by-source, by-purpose and flat views; a frontmatter `category:` pins an item to a manual group that takes precedence
|
|
21
21
|
- **Search / sort** — incremental search over name + description + usage; sort by name, usage count, last used, updated date, or token cost
|
|
22
|
-
- **Diagnostics** —
|
|
22
|
+
- **Diagnostics** — a _no recent use_ badge (no recorded use within the transcript retention window) with an all / recent use / no recent use filter, plus static description lint: missing / too-short / too-long descriptions, missing trigger conditions ("use when …") that make auto-invocation unlikely, and name-echo descriptions
|
|
23
23
|
- **Token cost** — since every name + description is injected into each session, the estimated token overhead is shown per item, per scope, and as a per-session total for the current project
|
|
24
24
|
- **AI trigger diagnosis** — one click asks the model whether the description is likely to trigger auto-invocation, lists concrete issues, and proposes an improved description you can apply with one click (cached by content hash)
|
|
25
25
|
- **AI flow diagram** — extract the processing flow of orchestration-style skills (steps, branches, delegations, human gates) from the definition body and render it as a step diagram; delegated skills are clickable
|
|
26
26
|
- **AI model choice** — pick the model behind all AI features (haiku default / sonnet / opus) in settings; aliases are resolved by your claude CLI
|
|
27
|
+
- **Memory triage** — a _Memory_ view lists Claude Code's auto memory (`~/.claude/projects/<project>/memory/`, or the directory set by `autoMemoryDirectory`) per project with its context cost split into the always-on part (the `MEMORY.md` index line injected into every session) and the pay-per-use part (the body, read on demand), plus Read / Write counts from transcripts, `[[link]]` resolution and backlinks. **AI triage** reads every memory of a project with the claude CLI (one call, split into a few for very large projects) and proposes a destination per memory — keep / shrink / move to CLAUDE.md / move to your user CLAUDE.md / move to docs / move to a skill / delete / wrong project — with the reasoning and a paste-ready instruction for Claude Code (it is asked to always cover removing the `MEMORY.md` index line and re-pointing `[[link]]`s). Each memory is first judged for freshness (current / outdated / historical / obsolete) from mechanical signals — dates in the body, missing paths, merged or deleted branches, references to another project — and a _rewrite the body_ verdict covers memories whose gist still holds; the triage also flags when the `MEMORY.md` index line disagrees with the body. For feedback memories the instruction is built from a fixed template, so it reads the same on every model. The viewer never writes to memory: you paste the instruction into Claude Code, which inspects, asks when unsure, and executes after your approval
|
|
27
28
|
- **Edit in the browser** — inline editor for SKILL.md / commands / agents (project & user scopes) with mtime conflict detection and a one-generation backup in `~/.cache/skills-viewer/backups/`
|
|
28
29
|
- **What's changed** — a banner shows items added / updated / removed since your last launch (baseline advances only when you dismiss it); the CLI prints a one-line summary at startup too
|
|
29
30
|
- **Usage sparkline** — the detail pane charts the last 30 days of per-day usage
|
|
@@ -35,7 +36,7 @@ npx skills-viewer
|
|
|
35
36
|
- **Same-name diff** — when the same skill name exists in multiple scopes, the detail page lists the other definitions and shows a line diff between them
|
|
36
37
|
- **Open in editor** — via URL scheme (VS Code / Cursor / Zed / Windsurf / custom, configurable in the ⚙ settings modal), or the OS default opener
|
|
37
38
|
- **English / 日本語** — UI language auto-detected from the browser and switchable in settings; AI summaries are generated in the selected language (CLI messages follow `LANG`)
|
|
38
|
-
- **URL routing** — `/skills/:id?tab=md`, search/sort/grouping in query params; links are shareable across reloads
|
|
39
|
+
- **URL routing** — `/skills/:id?tab=md`, `/memory/:id`, `/memory/triage/:project`, search/sort/grouping in query params; links are shareable across reloads
|
|
39
40
|
|
|
40
41
|
## Usage
|
|
41
42
|
|
|
@@ -52,6 +53,7 @@ Run it from a project directory to have that project marked as “current” and
|
|
|
52
53
|
- Binds to `127.0.0.1` only
|
|
53
54
|
- Mutating APIs require a per-run token that other origins cannot read (same-origin policy), and requests with a non-localhost `Origin` are rejected
|
|
54
55
|
- Copy/delete are restricted to `.claude/skills/` / `.claude/commands/` / `.claude/agents/` paths; plugin directories are never written to; deletions go to the OS trash
|
|
56
|
+
- Reads stay inside `~/.claude`, the per-project `.claude` directories and the `autoMemoryDirectory` you configured — a setting that resolves to a filesystem root, your home directory or an ancestor of it is ignored
|
|
55
57
|
|
|
56
58
|
## Development
|
|
57
59
|
|
|
@@ -77,11 +79,13 @@ dist/ # prebuilt UI shipped in the npm package (generated by prepack
|
|
|
77
79
|
|
|
78
80
|
## Notes
|
|
79
81
|
|
|
80
|
-
- Usage stats only cover the transcript retention window of Claude Code (`cleanupPeriodDays`, default 30 days) — the
|
|
82
|
+
- Usage stats only cover the transcript retention window of Claude Code (`cleanupPeriodDays`, default 30 days) — the _no recent use_ badge and memory Read counts have the same limitation (projects without transcripts show no usage columns at all)
|
|
81
83
|
- Token costs are heuristic estimates (≈4 chars/token for ASCII, ≈1.5 for CJK), not exact tokenizer counts
|
|
82
84
|
- Usage is attributed per calling project (worktrees roll up to their parent project by encoded-path prefix). When scopes share a name, the resolution order project > user > plugin > built-in is assumed
|
|
83
85
|
- The built-in skill list is hardcoded in `server/scan.js` (they live inside the Claude Code binary); check `/skills` inside Claude Code for the authoritative list
|
|
84
86
|
- AI summaries require a logged-in `claude` CLI
|
|
87
|
+
- Memory triage sends each memory's body to the claude CLI, together with the project's `MEMORY.md` index, the **headings** of `CLAUDE.md` / `.claude/CLAUDE.md` / `~/.claude/CLAUDE.md` and the names + descriptions of the skills available to that project (never their bodies) so it can spot duplicates and promotion targets. Branch status for the freshness signals comes from local `git` (no network). Results are cached per memory by content hash (plus the index line); memory files themselves are never modified
|
|
88
|
+
- The Memory view resolves `autoMemoryDirectory` from your user settings and the current project only — project-scope settings of other projects are not read
|
|
85
89
|
|
|
86
90
|
## License
|
|
87
91
|
|
package/build/cli.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* AI 機能のキャッシュ(~/.cache/skills-viewer/*.json)の共通処理。
|
|
4
|
+
* どのキャッシュも「実パス → エントリ」の形なので、掃除の規則を 1 箇所に置く。
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.pruneMissing = pruneMissing;
|
|
41
|
+
const fs = __importStar(require("node:fs"));
|
|
42
|
+
/*
|
|
43
|
+
* 消えたファイル(削除・リネーム・別プロジェクトの撤去)のエントリを落とす。
|
|
44
|
+
* 呼ぶのは保存時だけにする — GET(読み取り API)で書き込みを発生させないため。
|
|
45
|
+
*/
|
|
46
|
+
function pruneMissing(store) {
|
|
47
|
+
const out = {};
|
|
48
|
+
for (const [key, value] of Object.entries(store)) {
|
|
49
|
+
if (fs.existsSync(key))
|
|
50
|
+
out[key] = value;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
package/build/server/diagnose.js
CHANGED
|
@@ -45,6 +45,7 @@ exports.attachDiagnoses = attachDiagnoses;
|
|
|
45
45
|
const fs = __importStar(require("node:fs"));
|
|
46
46
|
const os = __importStar(require("node:os"));
|
|
47
47
|
const path = __importStar(require("node:path"));
|
|
48
|
+
const cache_1 = require("./cache");
|
|
48
49
|
const summary_1 = require("./summary");
|
|
49
50
|
const DIAG_FILE = path.join(os.homedir(), '.cache', 'skills-viewer', 'diagnoses.json');
|
|
50
51
|
function loadDiagnoses() {
|
|
@@ -57,7 +58,8 @@ function loadDiagnoses() {
|
|
|
57
58
|
}
|
|
58
59
|
function saveDiagnoses(store) {
|
|
59
60
|
fs.mkdirSync(path.dirname(DIAG_FILE), { recursive: true });
|
|
60
|
-
|
|
61
|
+
// 保存のついでに死にエントリを掃除する(GET では書き込まないので掃除もしない)
|
|
62
|
+
fs.writeFileSync(DIAG_FILE, JSON.stringify((0, cache_1.pruneMissing)(store), null, 1));
|
|
61
63
|
}
|
|
62
64
|
function buildPrompt(name, content, lang) {
|
|
63
65
|
if (lang === 'ja') {
|
package/build/server/flow.js
CHANGED
|
@@ -45,6 +45,7 @@ exports.attachFlows = attachFlows;
|
|
|
45
45
|
const fs = __importStar(require("node:fs"));
|
|
46
46
|
const os = __importStar(require("node:os"));
|
|
47
47
|
const path = __importStar(require("node:path"));
|
|
48
|
+
const cache_1 = require("./cache");
|
|
48
49
|
const summary_1 = require("./summary");
|
|
49
50
|
const FLOW_FILE = path.join(os.homedir(), '.cache', 'skills-viewer', 'flows.json');
|
|
50
51
|
/*
|
|
@@ -63,7 +64,8 @@ function loadFlows() {
|
|
|
63
64
|
}
|
|
64
65
|
function saveFlows(store) {
|
|
65
66
|
fs.mkdirSync(path.dirname(FLOW_FILE), { recursive: true });
|
|
66
|
-
|
|
67
|
+
// 保存のついでに死にエントリを掃除する(GET では書き込まないので掃除もしない)
|
|
68
|
+
fs.writeFileSync(FLOW_FILE, JSON.stringify((0, cache_1.pruneMissing)(store), null, 1));
|
|
67
69
|
}
|
|
68
70
|
function buildPrompt(name, content, lang) {
|
|
69
71
|
if (lang === 'ja') {
|
package/build/server/index.js
CHANGED
|
@@ -46,12 +46,14 @@ const crypto = __importStar(require("node:crypto"));
|
|
|
46
46
|
const node_child_process_1 = require("node:child_process");
|
|
47
47
|
const scan_1 = require("./scan");
|
|
48
48
|
const usage_1 = require("./usage");
|
|
49
|
+
const memory_1 = require("./memory");
|
|
49
50
|
const summary_1 = require("./summary");
|
|
50
51
|
const manage_1 = require("./manage");
|
|
51
52
|
const edit_1 = require("./edit");
|
|
52
53
|
const diagnose_1 = require("./diagnose");
|
|
53
54
|
const flow_1 = require("./flow");
|
|
54
55
|
const groups_1 = require("./groups");
|
|
56
|
+
const memory_triage_1 = require("./memory-triage");
|
|
55
57
|
const snapshot_1 = require("./snapshot");
|
|
56
58
|
const errors_1 = require("./errors");
|
|
57
59
|
const locale_1 = require("./locale");
|
|
@@ -134,7 +136,66 @@ function attributeUsage(sections) {
|
|
|
134
136
|
}
|
|
135
137
|
return Object.keys(byDir).length > 0;
|
|
136
138
|
}
|
|
139
|
+
/*
|
|
140
|
+
* memory の Read(参照)/ Write・Edit(作成・更新)実績を付与する。
|
|
141
|
+
* skill と違って帰属先の解決は不要で、Read の file_path がそのまま実ファイルを指す。
|
|
142
|
+
* usageAvailable は「そのプロジェクトのトランスクリプトがあるか」= エンコード名で始まる
|
|
143
|
+
* ディレクトリ(worktree 分を含む)に jsonl が 1 件以上あるか。false なら Read 列は出さない。
|
|
144
|
+
* 判定そのもの(共有ストアの扱いを含む)は usageAvailableFor に集約している。
|
|
145
|
+
*/
|
|
146
|
+
function attributeMemoryUsage(memory) {
|
|
147
|
+
if (!memory.length)
|
|
148
|
+
return;
|
|
149
|
+
const { byPath, dirsWithTranscripts } = (0, usage_1.scanMemoryUsage)();
|
|
150
|
+
for (const sec of memory) {
|
|
151
|
+
// autoMemoryDirectory の置き場は id が置き場のパス由来なので、transcript の
|
|
152
|
+
// ディレクトリ名(現在のプロジェクトの slug)を別に持っている
|
|
153
|
+
sec.usageAvailable = (0, memory_1.usageAvailableFor)(sec, dirsWithTranscripts);
|
|
154
|
+
for (const it of sec.items) {
|
|
155
|
+
const u = byPath[it.path];
|
|
156
|
+
if (!u)
|
|
157
|
+
continue;
|
|
158
|
+
if (u.reads > 0) {
|
|
159
|
+
it.useCount = u.reads;
|
|
160
|
+
it.lastUsed = u.lastRead;
|
|
161
|
+
it.dailyUse = u.daily;
|
|
162
|
+
}
|
|
163
|
+
if (u.writes > 0)
|
|
164
|
+
it.writeCount = u.writes;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/*
|
|
169
|
+
* 実績付きの memory セクション一覧。/api/skills だけでなく /api/memory-triage からも
|
|
170
|
+
* 同じ事実(Read / W-E / usageAvailable)をプロンプトに載せる必要があるので共通化する。
|
|
171
|
+
*/
|
|
172
|
+
function memorySections(cwd) {
|
|
173
|
+
primeMemoryRoots(cwd);
|
|
174
|
+
const memory = (0, memory_1.scanMemory)(cwd);
|
|
175
|
+
attributeMemoryUsage(memory);
|
|
176
|
+
return memory;
|
|
177
|
+
}
|
|
178
|
+
/*
|
|
179
|
+
* この環境の自動メモリ置き場(autoMemoryDirectory)を usage 集計の許可ルートに設定する。
|
|
180
|
+
* skill 集計と memory 集計は同じ transcript キャッシュを共有するので、走査を始める前に
|
|
181
|
+
* 揃えておかないと同じファイルを二度読みすることになる(解決自体は memo 済みで安い)。
|
|
182
|
+
*/
|
|
183
|
+
function primeMemoryRoots(cwd) {
|
|
184
|
+
const auto = (0, memory_1.resolveAutoMemoryDir)(cwd);
|
|
185
|
+
if (!auto) {
|
|
186
|
+
(0, usage_1.setMemoryRoots)([]);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
/*
|
|
190
|
+
* 設定値そのものと、その実パス(異なるときだけ)の両方を許可ルートにする。
|
|
191
|
+
* transcript の file_path が symlink 解決済みで記録される環境があり、設定値だけを
|
|
192
|
+
* 前方一致に使うと、その置き場の Read / Write を丸ごと取り逃すため。
|
|
193
|
+
*/
|
|
194
|
+
const real = (0, memory_1.realDir)(auto.dir);
|
|
195
|
+
(0, usage_1.setMemoryRoots)(real !== auto.dir ? [auto.dir, real] : [auto.dir]);
|
|
196
|
+
}
|
|
137
197
|
function collect(cwd, lang) {
|
|
198
|
+
primeMemoryRoots(cwd);
|
|
138
199
|
const sections = (0, scan_1.scanSections)(cwd, lang);
|
|
139
200
|
const usageAvailable = attributeUsage(sections);
|
|
140
201
|
const summaries = (0, summary_1.loadSummaries)();
|
|
@@ -162,6 +223,12 @@ function collect(cwd, lang) {
|
|
|
162
223
|
(0, flow_1.attachFlows)(sections, lang);
|
|
163
224
|
const grp = (0, groups_1.attachGroups)(sections, lang);
|
|
164
225
|
const aiStale = (0, summary_1.staleItems)(sections, lang).length;
|
|
226
|
+
// memory は「呼び出す」ものではないので sections には混ぜず、別配列で同乗させる
|
|
227
|
+
const memory = memorySections(cwd);
|
|
228
|
+
// 共有ストア環境かどうかは cwd から解決した値で判定する(棚卸し側と同じ事実を見る)
|
|
229
|
+
(0, memory_triage_1.attachMemoryTriage)(memory, lang, undefined, {
|
|
230
|
+
sharedEnv: (0, memory_1.resolveAutoMemoryDir)(cwd)?.scope === 'user',
|
|
231
|
+
});
|
|
165
232
|
const targets = [
|
|
166
233
|
{ label: 'user skills', sub: '~/.claude/skills/', path: scan_1.HOME },
|
|
167
234
|
...(0, scan_1.listProjects)(cwd)
|
|
@@ -178,6 +245,7 @@ function collect(cwd, lang) {
|
|
|
178
245
|
changes: (0, snapshot_1.computeChanges)(sections),
|
|
179
246
|
...(grp.groups ? { groups: grp.groups } : {}),
|
|
180
247
|
...(grp.stale ? { groupsStale: true } : {}),
|
|
248
|
+
...(memory.length ? { memory: (0, memory_1.publicMemory)(memory) } : {}),
|
|
181
249
|
};
|
|
182
250
|
}
|
|
183
251
|
/* DNS rebinding 対策: same-origin GET には Origin が付かないため Host 側も検証する */
|
|
@@ -219,7 +287,7 @@ function handleApi(req, res, cwd) {
|
|
|
219
287
|
if (url.pathname === '/api/summary-status')
|
|
220
288
|
return send(200, (0, summary_1.summaryStatus)());
|
|
221
289
|
if (url.pathname === '/api/file') {
|
|
222
|
-
const real = (0, manage_1.assertReadableMd)(url.searchParams.get('src') || '');
|
|
290
|
+
const real = (0, manage_1.assertReadableMd)(url.searchParams.get('src') || '', cwd);
|
|
223
291
|
// mtime は編集画面の競合検出(/api/save の baseMtime)に使う
|
|
224
292
|
return send(200, {
|
|
225
293
|
content: fs.readFileSync(real, 'utf8'),
|
|
@@ -257,7 +325,7 @@ function handleApi(req, res, cwd) {
|
|
|
257
325
|
if (url.pathname === '/api/apply-description')
|
|
258
326
|
return send(200, (0, edit_1.doApplyDescription)(data));
|
|
259
327
|
if (url.pathname === '/api/diagnose') {
|
|
260
|
-
const real = (0, manage_1.assertReadableMd)(data.src);
|
|
328
|
+
const real = (0, manage_1.assertReadableMd)(data.src, cwd);
|
|
261
329
|
const name = data.name || path.basename(path.dirname(real));
|
|
262
330
|
(0, diagnose_1.diagnoseOne)(real, name, lang, model)
|
|
263
331
|
.then((d) => send(200, { ok: true, ...d }))
|
|
@@ -265,7 +333,7 @@ function handleApi(req, res, cwd) {
|
|
|
265
333
|
return;
|
|
266
334
|
}
|
|
267
335
|
if (url.pathname === '/api/flow') {
|
|
268
|
-
const real = (0, manage_1.assertReadableMd)(data.src);
|
|
336
|
+
const real = (0, manage_1.assertReadableMd)(data.src, cwd);
|
|
269
337
|
const name = data.name || path.basename(path.dirname(real));
|
|
270
338
|
(0, flow_1.flowOne)(real, name, lang, model)
|
|
271
339
|
.then((f) => send(200, { ok: true, ...f }))
|
|
@@ -281,7 +349,7 @@ function handleApi(req, res, cwd) {
|
|
|
281
349
|
if (url.pathname === '/api/delete')
|
|
282
350
|
return send(200, (0, manage_1.doDelete)(data));
|
|
283
351
|
if (url.pathname === '/api/open')
|
|
284
|
-
return send(200, (0, manage_1.openInEditor)(data));
|
|
352
|
+
return send(200, (0, manage_1.openInEditor)(data, cwd));
|
|
285
353
|
if (url.pathname === '/api/summarize-all')
|
|
286
354
|
return send(200, (0, summary_1.startSummarizeAll)((0, scan_1.scanSections)(cwd, lang), !!data.force, lang, model));
|
|
287
355
|
if (url.pathname === '/api/group-generate') {
|
|
@@ -291,8 +359,31 @@ function handleApi(req, res, cwd) {
|
|
|
291
359
|
.catch((e) => send(400, (0, errors_1.toErrorBody)(e)));
|
|
292
360
|
return;
|
|
293
361
|
}
|
|
362
|
+
if (url.pathname === '/api/memory-triage') {
|
|
363
|
+
// 1 プロジェクト分をまとめて 1 回の claude 呼び出しで棚卸しする(結果は再取得で反映)
|
|
364
|
+
const project = String(data.project || '');
|
|
365
|
+
const sec = memorySections(cwd).find((s) => s.id === project);
|
|
366
|
+
if (!sec)
|
|
367
|
+
throw new errors_1.ApiError('not-found', project);
|
|
368
|
+
const files = Array.isArray(data.files)
|
|
369
|
+
? data.files.filter((f) => typeof f === 'string')
|
|
370
|
+
: undefined;
|
|
371
|
+
// sections は「CLAUDE.md / skill に既に書いてある」「skill へ昇格」を判定させる文脈。
|
|
372
|
+
// AI を呼ぶときだけ要るので、フルスキャンは関数で渡して遅延させる
|
|
373
|
+
(0, memory_triage_1.triageProject)(sec, lang, model, {
|
|
374
|
+
force: !!data.force,
|
|
375
|
+
files,
|
|
376
|
+
sections: () => (0, scan_1.scanSections)(cwd, lang),
|
|
377
|
+
// 置き場の解決は起動ディレクトリ基準。process.cwd() 任せにせず、この
|
|
378
|
+
// リクエストと同じ cwd で解決した値を渡す(スキャン・読み取り許可と同じ事実を見る)
|
|
379
|
+
autoMemory: (0, memory_1.resolveAutoMemoryDir)(cwd),
|
|
380
|
+
})
|
|
381
|
+
.then((results) => send(200, { ok: true, results }))
|
|
382
|
+
.catch((e) => send(400, (0, errors_1.toErrorBody)(e)));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
294
385
|
if (url.pathname === '/api/summarize') {
|
|
295
|
-
const real = (0, manage_1.assertReadableMd)(data.src);
|
|
386
|
+
const real = (0, manage_1.assertReadableMd)(data.src, cwd);
|
|
296
387
|
// refs(関係候補)はスキャン結果から復元する
|
|
297
388
|
const sections = (0, scan_1.scanSections)(cwd, lang);
|
|
298
389
|
const item = sections.flatMap((s) => s.items).find((x) => x.path === real);
|
|
@@ -332,7 +423,7 @@ function serveStatic(req, res) {
|
|
|
332
423
|
}
|
|
333
424
|
/*
|
|
334
425
|
* 起動時の1〜2行サマリー(--no-open 運用でも価値が出るように)。
|
|
335
|
-
* 前回からの差分 + セッション注入トークン概算 +
|
|
426
|
+
* 前回からの差分 + セッション注入トークン概算 + 直近未使用の件数。失敗しても起動は止めない。
|
|
336
427
|
*/
|
|
337
428
|
function printStartupSummary(cwd) {
|
|
338
429
|
try {
|
|
@@ -350,8 +441,8 @@ function printStartupSummary(cwd) {
|
|
|
350
441
|
.length
|
|
351
442
|
: null;
|
|
352
443
|
console.log((0, locale_1.srvMsg)(`スキル定義のセッション注入 ≈${sessionTokens.toLocaleString()}tok` +
|
|
353
|
-
(unused !== null ? ` /
|
|
354
|
-
(unused !== null ? ` / ${unused}
|
|
444
|
+
(unused !== null ? ` / 直近未使用 ${unused} 件` : ''), `Skill definitions inject ≈${sessionTokens.toLocaleString()} tok/session` +
|
|
445
|
+
(unused !== null ? ` / ${unused} with no recent use` : '')));
|
|
355
446
|
}
|
|
356
447
|
catch {
|
|
357
448
|
/* サマリーは補助情報。失敗しても起動を妨げない */
|
package/build/server/manage.js
CHANGED
|
@@ -45,6 +45,7 @@ const os = __importStar(require("node:os"));
|
|
|
45
45
|
const path = __importStar(require("node:path"));
|
|
46
46
|
const node_child_process_1 = require("node:child_process");
|
|
47
47
|
const scan_1 = require("./scan");
|
|
48
|
+
const memory_1 = require("./memory");
|
|
48
49
|
const errors_1 = require("./errors");
|
|
49
50
|
const HOME = os.homedir();
|
|
50
51
|
/* realpath 解決(存在しないパスは not-found に正規化) */
|
|
@@ -77,19 +78,55 @@ function assertManagedPath(p) {
|
|
|
77
78
|
throw new errors_1.ApiError('not-managed-path', real);
|
|
78
79
|
return { real, kind };
|
|
79
80
|
}
|
|
80
|
-
/*
|
|
81
|
-
|
|
81
|
+
/*
|
|
82
|
+
* 自動メモリの置き場(settings の autoMemoryDirectory)の realpath。キャッシュは解決値ごとに 1 回。
|
|
83
|
+
* 設定が有効な環境では memory の実体が .claude の外へ丸ごと移るため、
|
|
84
|
+
* 一覧に出ている本文が読めない(fetchFile・棚卸しモーダル・エディタで開くが全滅する)。
|
|
85
|
+
*/
|
|
86
|
+
const autoRealMemo = new Map();
|
|
87
|
+
function autoMemoryRoot(cwd) {
|
|
88
|
+
const info = (0, memory_1.resolveAutoMemoryDir)(cwd);
|
|
89
|
+
if (!info)
|
|
90
|
+
return null;
|
|
91
|
+
const cached = autoRealMemo.get(info.dir);
|
|
92
|
+
if (cached)
|
|
93
|
+
return cached;
|
|
94
|
+
try {
|
|
95
|
+
const real = fs.realpathSync(info.dir);
|
|
96
|
+
// 解決できたときだけ覚える(置き場がまだ無い時点の失敗を焼き付けない。
|
|
97
|
+
// ディレクトリは後から作られ得るので、次のリクエストで解決し直せるようにする)
|
|
98
|
+
autoRealMemo.set(info.dir, real);
|
|
99
|
+
return real;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/*
|
|
106
|
+
* 解決済み autoMemoryDirectory の配下か。realpath 同士を path.sep 区切りで前方一致させる
|
|
107
|
+
* (symlink 経由のパスで一致が外れないように / `<dir>-other` のような兄弟を巻き込まないように)。
|
|
108
|
+
* `.claude` のルールは緩めず、この許可を足すだけ。
|
|
109
|
+
*/
|
|
110
|
+
function underAutoMemory(real, cwd) {
|
|
111
|
+
const root = autoMemoryRoot(cwd);
|
|
112
|
+
// isUnder はケース非依存 FS で case-fold する(root が `/USERS/…` のとき
|
|
113
|
+
// 実ファイルの realpath `/Users/…` と取り違えないように)
|
|
114
|
+
return !!root && real !== root && (0, memory_1.isUnder)(real, root);
|
|
115
|
+
}
|
|
116
|
+
const underDotClaude = (real) => real.includes(path.sep + '.claude' + path.sep);
|
|
117
|
+
/* 読み取り専用は plugin 配下も許可(.claude 配下 + 自動メモリの置き場配下の .md のみ) */
|
|
118
|
+
function assertReadableMd(p, cwd = process.cwd()) {
|
|
82
119
|
const real = realpathOrThrow(p);
|
|
83
120
|
if (!real.endsWith('.md'))
|
|
84
121
|
throw new errors_1.ApiError('not-md', real);
|
|
85
|
-
if (!real
|
|
122
|
+
if (!underDotClaude(real) && !underAutoMemory(real, cwd))
|
|
86
123
|
throw new errors_1.ApiError('not-readable-path', real);
|
|
87
124
|
return real;
|
|
88
125
|
}
|
|
89
|
-
/* エディタで開くのは .claude
|
|
90
|
-
function assertOpenablePath(p) {
|
|
126
|
+
/* エディタで開くのは .claude 配下(settings.json 等も含む)と自動メモリの置き場配下 */
|
|
127
|
+
function assertOpenablePath(p, cwd) {
|
|
91
128
|
const real = realpathOrThrow(p);
|
|
92
|
-
if (!real
|
|
129
|
+
if (!underDotClaude(real) && !underAutoMemory(real, cwd))
|
|
93
130
|
throw new errors_1.ApiError('not-openable-path', real);
|
|
94
131
|
return real;
|
|
95
132
|
}
|
|
@@ -190,8 +227,8 @@ function detectEditor() {
|
|
|
190
227
|
}
|
|
191
228
|
return (editorCache = { cmd: null });
|
|
192
229
|
}
|
|
193
|
-
function openInEditor({ src }) {
|
|
194
|
-
const real = assertOpenablePath(src);
|
|
230
|
+
function openInEditor({ src }, cwd = process.cwd()) {
|
|
231
|
+
const real = assertOpenablePath(src, cwd);
|
|
195
232
|
const { cmd } = detectEditor();
|
|
196
233
|
if (cmd) {
|
|
197
234
|
(0, node_child_process_1.spawn)(cmd, [real], { detached: true, stdio: 'ignore' }).unref();
|