@handamade/psi-mcp 0.9.0 → 0.9.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/dist/psi-index.json +1 -1
- package/dist/store.js +51 -35
- package/package.json +3 -3
package/dist/psi-index.json
CHANGED
package/dist/store.js
CHANGED
|
@@ -3,28 +3,32 @@ const MAX_RESULTS = 20;
|
|
|
3
3
|
* (~1.5k tokens)" (docs/superpowers/plans/2026-07-18-agent-access-plan.md),
|
|
4
4
|
* asserted by __tests__/store.test.ts's "stays within the response budget". */
|
|
5
5
|
const RESPONSE_BUDGET = 6000;
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
6
|
+
/** How the empty-query overview divides its budget across kinds. Topics,
|
|
7
|
+
* patterns, and components grow and cost very differently:
|
|
8
8
|
*
|
|
9
9
|
* topics — a fixed, slow-growing set of terse briefs (~90 chars each).
|
|
10
10
|
* Carries the rules an agent cannot guess, `getting-started`
|
|
11
|
-
* (the five required CSS imports) above all.
|
|
12
|
-
*
|
|
13
|
-
* phrases for keyword ranking and can run long, so the
|
|
14
|
-
* overview projects a trimmed copy (see overviewBrief) capped
|
|
15
|
-
* near topic size — the stored, untrimmed brief is still what
|
|
16
|
-
* score() ranks against.
|
|
11
|
+
* (the five required CSS imports) above all. Takes its full
|
|
12
|
+
* cost, unconditionally.
|
|
17
13
|
* components — verbose (up to 220 chars each) and the only axis that grows
|
|
18
14
|
* with every release. Also the axis a keyword query recovers
|
|
19
15
|
* best: search("menu") finds Menu; no query re-derives a house
|
|
20
|
-
* rule an agent doesn't know exists.
|
|
16
|
+
* rule an agent doesn't know exists. Reserved a floor of
|
|
17
|
+
* `COMPONENT_FLOOR` items before patterns spend the remainder,
|
|
18
|
+
* so catalog growth cannot starve them below the floor.
|
|
19
|
+
* patterns — composition recipes; their stored summaries carry match
|
|
20
|
+
* phrases for keyword ranking and can run long, so the overview
|
|
21
|
+
* projects a trimmed copy (see overviewBrief) at a *derived*
|
|
22
|
+
* per-call cap — the largest that lets every pattern fit
|
|
23
|
+
* alongside topics and the component floor. The stored,
|
|
24
|
+
* untrimmed brief is still what score() ranks against.
|
|
21
25
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
const
|
|
26
|
+
* Previously the overview filled topics, then patterns, then components
|
|
27
|
+
* until the budget ran out, so whichever kind was filled last absorbed all
|
|
28
|
+
* catalog growth — three more patterns was enough to starve components from
|
|
29
|
+
* six down to four (D61). Allocating by kind first means catalog growth
|
|
30
|
+
* shortens pattern summaries instead of evicting items off the tail. */
|
|
31
|
+
const COMPONENT_FLOOR = 8;
|
|
28
32
|
const TOPIC_SUMMARIES = {
|
|
29
33
|
variants: "Variant intent and typical use for all 8 flat variants",
|
|
30
34
|
rules: "House rules: accent budget, sizing, color binding",
|
|
@@ -61,12 +65,11 @@ function patternSummary(p) {
|
|
|
61
65
|
summary += `, blocked (gaps: ${p.gaps.join(", ")})`;
|
|
62
66
|
return summary;
|
|
63
67
|
}
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* shapes a copy used when search("") projects the overview list.
|
|
68
|
+
/** Most an overview pattern intro (intent + match phrases + parameter count)
|
|
69
|
+
* may cost. The effective limit is derived per-call (see the empty-query
|
|
70
|
+
* branch): patterns take whatever the budget leaves after topics and the
|
|
71
|
+
* component floor, so a growing catalog shortens summaries instead of
|
|
72
|
+
* evicting items (D61).
|
|
70
73
|
*
|
|
71
74
|
* The `, blocked (gaps: …)` tail is never subject to this cap: it is the
|
|
72
75
|
* backlog signal this cycle exists to publish, and patternSummary() puts it
|
|
@@ -75,7 +78,7 @@ function patternSummary(p) {
|
|
|
75
78
|
* 120 chars), which is exactly the bug this trims to avoid. So the overview
|
|
76
79
|
* projects a pattern's summary from the underlying PatternEntry directly:
|
|
77
80
|
* trim the intro, then always append the tail. */
|
|
78
|
-
const
|
|
81
|
+
const OVERVIEW_PATTERN_INTRO_MAX = 120;
|
|
79
82
|
/** Trims `s` to at most `limit` chars, backing off to the last word boundary
|
|
80
83
|
* and marking the cut with an ellipsis — never a mid-word fragment that
|
|
81
84
|
* could read as a complete (if coincidental) match phrase. */
|
|
@@ -86,8 +89,8 @@ function truncateAtWord(s, limit) {
|
|
|
86
89
|
const lastSpace = cut.lastIndexOf(" ");
|
|
87
90
|
return `${lastSpace > 0 ? cut.slice(0, lastSpace) : cut}…`;
|
|
88
91
|
}
|
|
89
|
-
function overviewPatternSummary(p) {
|
|
90
|
-
const intro = truncateAtWord(`${p.intent} — ${p.match.join(", ")}, ${p.parameters.length} parameters`,
|
|
92
|
+
function overviewPatternSummary(p, limit) {
|
|
93
|
+
const intro = truncateAtWord(`${p.intent} — ${p.match.join(", ")}, ${p.parameters.length} parameters`, limit);
|
|
91
94
|
return p.blocked ? `${intro}, blocked (gaps: ${p.gaps.join(", ")})` : intro;
|
|
92
95
|
}
|
|
93
96
|
export function createStore(index) {
|
|
@@ -98,14 +101,16 @@ export function createStore(index) {
|
|
|
98
101
|
* already-assembled, full-length `Brief.summary` — so the `blocked (gaps:
|
|
99
102
|
* …)` tail always survives the overview's trim. Always a shallow copy for
|
|
100
103
|
* patterns: the stored brief kept in `briefs` (and used by score()) is
|
|
101
|
-
* never mutated.
|
|
102
|
-
|
|
104
|
+
* never mutated. `limit` is derived per-call by the empty-query branch
|
|
105
|
+
* (D61), not a module constant, so a growing catalog shortens summaries
|
|
106
|
+
* instead of evicting items. */
|
|
107
|
+
function overviewBrief(b, limit) {
|
|
103
108
|
if (b.kind !== "pattern")
|
|
104
109
|
return b;
|
|
105
110
|
const pattern = patternsById.get(b.title);
|
|
106
111
|
if (!pattern)
|
|
107
112
|
return b;
|
|
108
|
-
return { ...b, summary: overviewPatternSummary(pattern) };
|
|
113
|
+
return { ...b, summary: overviewPatternSummary(pattern, limit) };
|
|
109
114
|
}
|
|
110
115
|
const briefs = [
|
|
111
116
|
...index.components.map((c) => ({
|
|
@@ -150,14 +155,25 @@ export function createStore(index) {
|
|
|
150
155
|
function search(query) {
|
|
151
156
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
152
157
|
if (terms.length === 0) {
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
// D61: allocate before filling. Topics take their full cost, components
|
|
159
|
+
// are reserved a floor, and patterns take the remainder via a derived
|
|
160
|
+
// cap — so catalog growth shortens summaries instead of evicting the
|
|
161
|
+
// tail. Whichever kind was filled last used to absorb all growth;
|
|
162
|
+
// that was the bug. (Tokens are omitted; they are discoverable via
|
|
163
|
+
// query.)
|
|
164
|
+
const topics = briefs.filter((b) => b.kind === "topic");
|
|
165
|
+
const patterns = briefs.filter((b) => b.kind === "pattern");
|
|
166
|
+
const components = briefs.filter((b) => b.kind === "component");
|
|
167
|
+
const reserve = JSON.stringify(components.slice(0, COMPONENT_FLOOR)).length;
|
|
168
|
+
const fits = (limit) => JSON.stringify([...topics, ...patterns.map((b) => overviewBrief(b, limit))]).length +
|
|
169
|
+
reserve <=
|
|
170
|
+
RESPONSE_BUDGET;
|
|
171
|
+
let limit = OVERVIEW_PATTERN_INTRO_MAX;
|
|
172
|
+
while (limit > 0 && !fits(limit))
|
|
173
|
+
limit -= 10;
|
|
174
|
+
const out = [...topics, ...patterns.map((b) => overviewBrief(b, limit))];
|
|
175
|
+
for (const component of components) {
|
|
176
|
+
out.push(component);
|
|
161
177
|
if (JSON.stringify(out).length > RESPONSE_BUDGET) {
|
|
162
178
|
out.pop();
|
|
163
179
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@handamade/psi-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server and agent-context generator for the Psi (Ψ) design system",
|
|
6
6
|
"type": "module",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"tsx": "^4.22.4",
|
|
34
34
|
"typescript": "^5.7.0",
|
|
35
35
|
"vitest": "^3.2.6",
|
|
36
|
-
"@handamade/psi-tokens": "0.9.
|
|
37
|
-
"@handamade/psi-react": "0.9.
|
|
36
|
+
"@handamade/psi-tokens": "0.9.1",
|
|
37
|
+
"@handamade/psi-react": "0.9.1"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsx scripts/build-index.ts && tsc -p tsconfig.build.json"
|