hypomnema 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +4 -2
- package/README.md +4 -2
- package/commands/audit.md +2 -2
- package/commands/crystallize.md +113 -23
- package/commands/feedback.md +40 -26
- package/commands/ingest.md +31 -9
- package/commands/upgrade.md +2 -2
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CONTRIBUTING.md +1 -1
- package/hooks/hooks.json +30 -1
- package/hooks/hypo-auto-commit.mjs +10 -4
- package/hooks/hypo-auto-minimal-crystallize.mjs +145 -0
- package/hooks/hypo-auto-stage.mjs +4 -3
- package/hooks/hypo-compact-guard.mjs +33 -24
- package/hooks/hypo-cwd-change.mjs +107 -24
- package/hooks/hypo-file-watch.mjs +23 -10
- package/hooks/hypo-first-prompt.mjs +37 -23
- package/hooks/hypo-hot-rebuild.mjs +22 -10
- package/hooks/hypo-lookup.mjs +171 -65
- package/hooks/hypo-personal-check.mjs +207 -112
- package/hooks/hypo-pre-commit.mjs +46 -0
- package/hooks/hypo-session-end.mjs +58 -0
- package/hooks/hypo-session-record.mjs +11 -5
- package/hooks/hypo-session-start.mjs +298 -52
- package/hooks/hypo-shared.mjs +793 -37
- package/hooks/hypo-web-fetch-ingest.mjs +121 -0
- package/hooks/version-check-fetch.mjs +74 -0
- package/hooks/version-check.mjs +184 -0
- package/package.json +17 -3
- package/scripts/crystallize.mjs +623 -18
- package/scripts/doctor.mjs +730 -47
- package/scripts/feedback-sync.mjs +974 -0
- package/scripts/feedback.mjs +253 -44
- package/scripts/graph.mjs +35 -22
- package/scripts/ingest.mjs +89 -16
- package/scripts/init.mjs +398 -113
- package/scripts/lib/design-history-stale.mjs +83 -0
- package/scripts/lib/extensions.mjs +749 -0
- package/scripts/lib/frontmatter.mjs +5 -1
- package/scripts/lib/hypo-ignore.mjs +12 -10
- package/scripts/lib/pkg-json.mjs +23 -5
- package/scripts/lib/project-create.mjs +225 -0
- package/scripts/lib/schema-vocab.mjs +96 -0
- package/scripts/lint.mjs +238 -31
- package/scripts/query.mjs +26 -10
- package/scripts/resume.mjs +11 -5
- package/scripts/session-audit.mjs +37 -27
- package/scripts/smoke-pack.mjs +224 -0
- package/scripts/stats.mjs +24 -10
- package/scripts/uninstall.mjs +363 -49
- package/scripts/upgrade.mjs +706 -202
- package/scripts/verify.mjs +24 -14
- package/scripts/weekly-report.mjs +59 -25
- package/skills/crystallize/SKILL.md +20 -7
- package/skills/ingest/SKILL.md +25 -5
- package/templates/.hypoignore +16 -2
- package/templates/Home.md +2 -0
- package/templates/SCHEMA.md +61 -6
- package/templates/extensions/agents/.gitkeep +0 -0
- package/templates/extensions/commands/.gitkeep +0 -0
- package/templates/extensions/hooks/.gitkeep +0 -0
- package/templates/extensions/skills/.gitkeep +0 -0
- package/templates/gitignore +5 -0
- package/templates/hot.md +2 -0
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +42 -2
- package/templates/hypo-help.md +1 -1
- package/templates/pages/observability/_index.md +77 -0
- package/templates/projects/_template/index.md +2 -2
- package/templates/projects/_template/prd.md +1 -1
package/hooks/hypo-lookup.mjs
CHANGED
|
@@ -14,8 +14,8 @@ import { join, basename } from 'path';
|
|
|
14
14
|
import { HYPO_DIR, buildOutput, loadHypoIgnore, isIgnored } from './hypo-shared.mjs';
|
|
15
15
|
|
|
16
16
|
const INDEX_PATH = join(HYPO_DIR, 'index.md');
|
|
17
|
-
const MAX_HITS
|
|
18
|
-
const MAX_CHARS
|
|
17
|
+
const MAX_HITS = 3;
|
|
18
|
+
const MAX_CHARS = 2000;
|
|
19
19
|
|
|
20
20
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
@@ -38,51 +38,120 @@ function buildPageMap(dir, root = dir, map = {}, ignorePatterns = [], hypoDir =
|
|
|
38
38
|
|
|
39
39
|
function extractKeywords(prompt) {
|
|
40
40
|
const stop = new Set([
|
|
41
|
-
'the',
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
41
|
+
'the',
|
|
42
|
+
'and',
|
|
43
|
+
'for',
|
|
44
|
+
'with',
|
|
45
|
+
'this',
|
|
46
|
+
'that',
|
|
47
|
+
'have',
|
|
48
|
+
'from',
|
|
49
|
+
'are',
|
|
50
|
+
'was',
|
|
51
|
+
'were',
|
|
52
|
+
'what',
|
|
53
|
+
'when',
|
|
54
|
+
'where',
|
|
55
|
+
'how',
|
|
56
|
+
'why',
|
|
57
|
+
'who',
|
|
58
|
+
'can',
|
|
59
|
+
'could',
|
|
60
|
+
'should',
|
|
61
|
+
'would',
|
|
62
|
+
'does',
|
|
63
|
+
'did',
|
|
64
|
+
'will',
|
|
65
|
+
'not',
|
|
66
|
+
'but',
|
|
67
|
+
'its',
|
|
68
|
+
'also',
|
|
69
|
+
'just',
|
|
70
|
+
'more',
|
|
71
|
+
'any',
|
|
72
|
+
'all',
|
|
73
|
+
'어떤',
|
|
74
|
+
'어떻게',
|
|
75
|
+
'무엇',
|
|
76
|
+
'이런',
|
|
77
|
+
'그런',
|
|
78
|
+
'하는',
|
|
79
|
+
'하고',
|
|
80
|
+
'해서',
|
|
81
|
+
'있어',
|
|
82
|
+
'없어',
|
|
83
|
+
'되는',
|
|
84
|
+
'이거',
|
|
85
|
+
'저거',
|
|
86
|
+
'그거',
|
|
87
|
+
'이건',
|
|
88
|
+
'그건',
|
|
89
|
+
'저건',
|
|
90
|
+
'같은',
|
|
91
|
+
'하면',
|
|
92
|
+
'되면',
|
|
93
|
+
'인지',
|
|
94
|
+
'에서',
|
|
95
|
+
'으로',
|
|
96
|
+
'까지',
|
|
97
|
+
'부터',
|
|
98
|
+
'에게',
|
|
99
|
+
'한테',
|
|
100
|
+
'에도',
|
|
101
|
+
'에만',
|
|
102
|
+
'에는',
|
|
47
103
|
]);
|
|
48
|
-
return [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
104
|
+
return [
|
|
105
|
+
...new Set(
|
|
106
|
+
prompt
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.split(/[\s,,.。??!!()\[\]{}'"\/\\:;=+*&%$#@~`|<>]+/)
|
|
109
|
+
.filter((w) => w.length >= 3 && !stop.has(w)),
|
|
110
|
+
),
|
|
111
|
+
];
|
|
53
112
|
}
|
|
54
113
|
|
|
55
114
|
function tokenize(text) {
|
|
56
|
-
return text
|
|
115
|
+
return text
|
|
116
|
+
.toLowerCase()
|
|
57
117
|
.split(/[\s\-_/.,,。??!!()\[\]{}'"\\:;=+*&%$#@~`|<>]+/)
|
|
58
|
-
.filter(w => w.length >= 2);
|
|
118
|
+
.filter((w) => w.length >= 2);
|
|
59
119
|
}
|
|
60
120
|
|
|
61
121
|
function bm25Score(queryTerms, entries, k1 = 1.5, b = 0.75) {
|
|
62
122
|
const N = entries.length;
|
|
63
123
|
if (N === 0) return [];
|
|
64
|
-
const docTokens = entries.map(e => tokenize(e.slug + ' ' + e.desc));
|
|
124
|
+
const docTokens = entries.map((e) => tokenize(e.slug + ' ' + e.desc));
|
|
65
125
|
const avgdl = docTokens.reduce((s, t) => s + t.length, 0) / N;
|
|
66
126
|
const df = {};
|
|
67
127
|
for (const tokens of docTokens) {
|
|
68
128
|
for (const t of new Set(tokens)) df[t] = (df[t] || 0) + 1;
|
|
69
129
|
}
|
|
70
|
-
const idf = t => Math.log(1 + (N - (df[t] || 0) + 0.5) / ((df[t] || 0) + 0.5));
|
|
71
|
-
|
|
72
|
-
return entries
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
130
|
+
const idf = (t) => Math.log(1 + (N - (df[t] || 0) + 0.5) / ((df[t] || 0) + 0.5));
|
|
131
|
+
|
|
132
|
+
return entries
|
|
133
|
+
.map((e, i) => {
|
|
134
|
+
const tokens = docTokens[i];
|
|
135
|
+
const dl = tokens.length || 1;
|
|
136
|
+
const tf = {};
|
|
137
|
+
for (const t of tokens) tf[t] = (tf[t] || 0) + 1;
|
|
138
|
+
let score = 0;
|
|
139
|
+
for (const q of queryTerms) {
|
|
140
|
+
const f = tf[q] || 0;
|
|
141
|
+
if (f === 0) continue;
|
|
142
|
+
const norm = 1 - b + (b * dl) / avgdl;
|
|
143
|
+
score += (idf(q) * (f * (k1 + 1))) / (f + k1 * norm);
|
|
144
|
+
}
|
|
145
|
+
return { ...e, score };
|
|
146
|
+
})
|
|
147
|
+
.sort((a, c) => c.score - a.score);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function typePrior(slug) {
|
|
151
|
+
if (/\/decisions\/|^decisions\//.test(slug)) return 1.5;
|
|
152
|
+
if (/\bprd\b|spec-v/.test(slug)) return 1.3;
|
|
153
|
+
if (/\/session-log\/|\/session-log$/.test(slug)) return 1.2;
|
|
154
|
+
return 1.0;
|
|
86
155
|
}
|
|
87
156
|
|
|
88
157
|
function parseIndexEntries(indexContent) {
|
|
@@ -92,7 +161,7 @@ function parseIndexEntries(indexContent) {
|
|
|
92
161
|
if (line.trimStart().startsWith('>')) continue;
|
|
93
162
|
const m = line.match(/\[\[([^\]]+)\]\]\s*[—\-]+\s*(.+)/);
|
|
94
163
|
if (!m) continue;
|
|
95
|
-
const raw
|
|
164
|
+
const raw = m[1].trim();
|
|
96
165
|
const desc = m[2].trim();
|
|
97
166
|
const slug = raw.includes('|') ? raw.split('|')[0].trim() : raw;
|
|
98
167
|
entries.push({ slug, desc });
|
|
@@ -104,10 +173,10 @@ function parseIndexEntries(indexContent) {
|
|
|
104
173
|
|
|
105
174
|
let input = '';
|
|
106
175
|
process.stdin.setEncoding('utf-8');
|
|
107
|
-
process.stdin.on('data', chunk => input += chunk);
|
|
176
|
+
process.stdin.on('data', (chunk) => (input += chunk));
|
|
108
177
|
process.stdin.on('end', () => {
|
|
109
178
|
try {
|
|
110
|
-
const data
|
|
179
|
+
const data = JSON.parse(input);
|
|
111
180
|
const prompt = (data.prompt || '').trim();
|
|
112
181
|
|
|
113
182
|
if (!prompt || !existsSync(INDEX_PATH)) {
|
|
@@ -121,58 +190,95 @@ process.stdin.on('end', () => {
|
|
|
121
190
|
return;
|
|
122
191
|
}
|
|
123
192
|
|
|
124
|
-
const entries
|
|
125
|
-
const scored
|
|
193
|
+
const entries = parseIndexEntries(readFileSync(INDEX_PATH, 'utf-8'));
|
|
194
|
+
const scored = bm25Score(keywords, entries)
|
|
195
|
+
.map((e) => ({ ...e, score: e.score * typePrior(e.slug) }))
|
|
196
|
+
.sort((a, c) => c.score - a.score)
|
|
197
|
+
.filter((e) => e.score > 0);
|
|
126
198
|
const topScore = scored[0]?.score ?? 0;
|
|
127
|
-
const matched
|
|
199
|
+
const matched = scored.filter((e) => e.score >= topScore * 0.5);
|
|
128
200
|
|
|
129
201
|
if (matched.length === 0) {
|
|
130
|
-
const topic
|
|
131
|
-
const closest = bm25Score(keywords, entries)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
202
|
+
const topic = keywords.slice(0, 5).join(', ');
|
|
203
|
+
const closest = bm25Score(keywords, entries)
|
|
204
|
+
.map((e) => ({ ...e, score: e.score * typePrior(e.slug) }))
|
|
205
|
+
.sort((a, c) => c.score - a.score)
|
|
206
|
+
.slice(0, 3)
|
|
207
|
+
.map((e) => `[[${e.slug}]]`)
|
|
208
|
+
.join(', ');
|
|
209
|
+
console.log(
|
|
210
|
+
JSON.stringify(
|
|
211
|
+
buildOutput(`[WIKI LOOKUP: miss] "${topic}" — no match. Closest: ${closest || 'none'}`, {
|
|
212
|
+
continue: true,
|
|
213
|
+
suppressOutput: true,
|
|
214
|
+
}),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
138
217
|
return;
|
|
139
218
|
}
|
|
140
219
|
|
|
141
220
|
const ignorePatterns = loadHypoIgnore(HYPO_DIR);
|
|
142
221
|
const pageMap = {
|
|
143
|
-
...buildPageMap(
|
|
144
|
-
|
|
222
|
+
...buildPageMap(
|
|
223
|
+
join(HYPO_DIR, 'pages'),
|
|
224
|
+
join(HYPO_DIR, 'pages'),
|
|
225
|
+
{},
|
|
226
|
+
ignorePatterns,
|
|
227
|
+
HYPO_DIR,
|
|
228
|
+
),
|
|
229
|
+
...buildPageMap(
|
|
230
|
+
join(HYPO_DIR, 'projects'),
|
|
231
|
+
join(HYPO_DIR, 'projects'),
|
|
232
|
+
{},
|
|
233
|
+
ignorePatterns,
|
|
234
|
+
HYPO_DIR,
|
|
235
|
+
),
|
|
145
236
|
};
|
|
146
237
|
|
|
147
238
|
const injected = [];
|
|
148
239
|
for (const { slug } of matched.slice(0, MAX_HITS)) {
|
|
149
|
-
const path =
|
|
150
|
-
|
|
151
|
-
|
|
240
|
+
const path =
|
|
241
|
+
pageMap[slug] ??
|
|
242
|
+
pageMap[slug.replace(/^(pages|projects)\//, '')] ??
|
|
243
|
+
pageMap[basename(slug)];
|
|
152
244
|
if (path && existsSync(path)) {
|
|
153
245
|
injected.push(`=== [[${slug}]] ===\n${readFileSync(path, 'utf-8').slice(0, MAX_CHARS)}`);
|
|
154
246
|
}
|
|
155
247
|
}
|
|
156
248
|
|
|
157
249
|
if (injected.length === 0) {
|
|
158
|
-
const slugs = matched
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
250
|
+
const slugs = matched
|
|
251
|
+
.slice(0, MAX_HITS)
|
|
252
|
+
.map((e) => e.slug)
|
|
253
|
+
.join(', ');
|
|
254
|
+
console.log(
|
|
255
|
+
JSON.stringify(
|
|
256
|
+
buildOutput(`[WIKI LOOKUP: index hit but files missing] ${slugs}`, {
|
|
257
|
+
continue: true,
|
|
258
|
+
suppressOutput: true,
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
);
|
|
162
262
|
return;
|
|
163
263
|
}
|
|
164
264
|
|
|
165
|
-
const overflow =
|
|
166
|
-
|
|
265
|
+
const overflow =
|
|
266
|
+
matched.length > MAX_HITS
|
|
267
|
+
? `\n(+${matched.length - MAX_HITS} more matches — search wiki index for more)`
|
|
268
|
+
: '';
|
|
167
269
|
|
|
168
|
-
console.log(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
270
|
+
console.log(
|
|
271
|
+
JSON.stringify(
|
|
272
|
+
buildOutput(
|
|
273
|
+
`[WIKI LOOKUP: ${injected.length} page(s) matched]\n\n` +
|
|
274
|
+
injected.join('\n\n') +
|
|
275
|
+
overflow,
|
|
276
|
+
{ continue: true, suppressOutput: true },
|
|
277
|
+
),
|
|
278
|
+
),
|
|
279
|
+
);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
process.stderr.write(`[hypo-lookup] error: ${err?.message ?? String(err)}\n`);
|
|
176
282
|
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
|
|
177
283
|
}
|
|
178
284
|
});
|