@jenga-ai/agent 1.2.4 → 2.0.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.
Files changed (100) hide show
  1. package/README.md +97 -91
  2. package/agents/developer.md +26 -7
  3. package/agents/scrum-master.md +57 -22
  4. package/agents/tester.md +68 -4
  5. package/hooks/on_session_end.sh +40 -1
  6. package/lib/generate-agent-context.js +18 -1
  7. package/lib/generate-copilot-instructions.js +18 -1
  8. package/lib/generate-skill-allow-list.js +191 -0
  9. package/lib/skill-allow-list.json +43 -0
  10. package/package.json +35 -20
  11. package/scripts/apply-j-prefix.sh +230 -0
  12. package/scripts/consume-context-digest.sh +103 -0
  13. package/scripts/postinstall.js +25 -0
  14. package/scripts/sweep-stale-context-digests.sh +132 -0
  15. package/scripts/validate-board.sh +5 -0
  16. package/scripts/write-context-digest.sh +230 -0
  17. package/skills/brainstorm/SKILL.md +1 -1
  18. package/skills/btw/SKILL.md +1 -1
  19. package/skills/clearify/SKILL.md +1 -1
  20. package/skills/close-story/SKILL.md +78 -6
  21. package/skills/close-story/scripts/check-privatized.sh +345 -0
  22. package/skills/commit/SKILL.md +12 -2
  23. package/skills/continue/SKILL.md +1 -1
  24. package/skills/deep-dive/SKILL.md +1 -1
  25. package/skills/dev-done/SKILL.md +46 -0
  26. package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
  27. package/skills/distribute/SKILL.md +1 -1
  28. package/skills/do/SKILL.md +100 -10
  29. package/skills/doc/README.md +155 -0
  30. package/skills/doc/SKILL.md +43 -13
  31. package/skills/doc/authoring-notes.md +72 -0
  32. package/skills/doc/scripts/resolve_last_update.py +149 -0
  33. package/skills/doc-sync/SKILL.md +1 -1
  34. package/skills/dooo/SKILL.md +1 -1
  35. package/skills/error/SKILL.md +1 -1
  36. package/skills/evaluate/SKILL.md +1 -1
  37. package/skills/examplify/SKILL.md +1 -1
  38. package/skills/help/SKILL.md +1 -1
  39. package/skills/idea/SKILL.md +1 -1
  40. package/skills/improve/SKILL.md +1 -1
  41. package/skills/init/SKILL.md +8 -7
  42. package/skills/init/assets/scope-thresholds_template.json +7 -0
  43. package/skills/init/scripts/init.sh +6 -0
  44. package/skills/j-init/SKILL.md +168 -0
  45. package/skills/j-init/assets/.gitignore_template +15 -0
  46. package/skills/j-init/assets/PROJECT_SUMMARY_template.md +13 -0
  47. package/skills/j-init/assets/directory_structure.txt +14 -0
  48. package/skills/j-init/assets/scope-thresholds_template.json +7 -0
  49. package/skills/j-init/assets/strategy_stub_template.md +38 -0
  50. package/skills/j-init/assets/test-config_template.json +4 -0
  51. package/skills/j-init/assets/workflow_template.json +30 -0
  52. package/skills/j-init/scripts/apply-project-visibility.sh +176 -0
  53. package/skills/j-init/scripts/detect-existing-codebase.sh +166 -0
  54. package/skills/j-init/scripts/init.sh +116 -0
  55. package/skills/jbp/SKILL.md +1 -1
  56. package/skills/jenga/SKILL.md +1 -1
  57. package/skills/jenga/scripts/render-confirmation.sh +55 -18
  58. package/skills/jenga-permission-level/SKILL.md +1 -1
  59. package/skills/lgtm/SKILL.md +1 -1
  60. package/skills/pi-plan/SKILL.md +1 -1
  61. package/skills/proceed/SKILL.md +1 -1
  62. package/skills/publish/SKILL.md +67 -1
  63. package/skills/publish/adapters/npm-ci.md +60 -4
  64. package/skills/publish/adapters/npm.md +18 -0
  65. package/skills/publish/assets/ci-contract.md +27 -0
  66. package/skills/publish/assets/publish.example.json +27 -0
  67. package/skills/publish/schemas/publish.schema.json +20 -0
  68. package/skills/publish/scripts/npm_ci_pipeline.sh +50 -1
  69. package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
  70. package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
  71. package/skills/publish/scripts/publish_common.sh +16 -0
  72. package/skills/publish/scripts/show_history.sh +12 -5
  73. package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
  74. package/skills/publish/scripts/write_ledger_entry.sh +92 -2
  75. package/skills/reconcile/SKILL.md +122 -12
  76. package/skills/reconcile/assets/report_format.md +17 -0
  77. package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
  78. package/skills/reconcile-origin/SKILL.md +1 -1
  79. package/skills/redo/SKILL.md +1 -1
  80. package/skills/skillify/SKILL.md +1 -1
  81. package/skills/spinoff/SKILL.md +1 -1
  82. package/skills/status/SKILL.md +1 -1
  83. package/skills/todo/SKILL.md +40 -3
  84. package/skills/todo/scripts/add_trivial_task.sh +216 -0
  85. package/skills/todo/scripts/update_story_tasks.py +87 -0
  86. package/skills/uncharted/SKILL.md +201 -22
  87. package/skills/uncharted/scripts/directory-triage.sh +342 -0
  88. package/skills/uncharted/scripts/elicitation-state.sh +457 -0
  89. package/skills/wtf/SKILL.md +1 -1
  90. package/templates/SCRUM_BOARD_SCHEMA.md +90 -2
  91. package/templates/agent-context.md.tpl +47 -12
  92. package/templates/copilot-instructions.md.tpl +36 -9
  93. package/mcp/router/README.md +0 -19
  94. package/mcp/router/embedder.js +0 -23
  95. package/mcp/router/index.js +0 -204
  96. package/mcp/router/matcher.js +0 -87
  97. package/mcp/router/package-lock.json +0 -1048
  98. package/mcp/router/package.json +0 -11
  99. package/mcp/router/skill-index.js +0 -104
  100. package/skills/route/SKILL.md +0 -180
@@ -10,28 +10,63 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
10
10
  ### How Jenga Works
11
11
 
12
12
  - Each **skill** is a self-contained instruction set stored under `{{SKILL_DISCOVERY_PATH}}<skill-name>/`.
13
- - Skills are invoked by typing `/skill-name` in the chat prompt.
13
+ - Skills are invoked by typing `j:skill-name` in the chat prompt (e.g. `j:status`, `j:commit`). The
14
+ older bare `/skill-name` form (e.g. `/status`, `/commit`) is a **permanent alias** — it keeps
15
+ resolving indefinitely, with no deprecation warning and no removal planned — so treat a message in
16
+ either form as the exact same invocation.
14
17
  - The active project directory is available via the `JENGA_PROJECT_DIR` environment variable. **Use `JENGA_PROJECT_DIR` — not `CLAUDE_PROJECT_DIR` or any other agent-specific variable** — as the canonical path to the project folder.
15
18
 
16
19
  ### Skill Routing
17
20
 
18
- When a user's message matches a known skill keyword or intent, invoke the skill immediately using the slash-command syntax:
19
-
20
- ```
21
- /skill-name
22
- ```
23
-
24
- Do **not** answer skill-related requests with free-form prose if a matching skill exists — delegate to the skill instead.
25
-
26
- For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill, answer directly using your full capabilities.
21
+ If you are Claude Code, both `j:skill-name` (the canonical form) and the older bare `/skill-name`
22
+ (a permanent alias) are native harness-level mechanisms: the harness itself intercepts the literal
23
+ command and loads the skill for you, independent of anything written here. If you are any other agent
24
+ (Codex, or a generic `AGENTS.md` consumer) with no equivalent native interception, you depend entirely
25
+ on the instructions below to know what "invoking a skill" concretely means — for either the
26
+ `j:skill-name` form or the older bare `/skill-name` form, both of which route to the same skill. Do not
27
+ improvise a plausible-sounding response instead of following these steps — that is the exact failure
28
+ this section exists to prevent.
29
+
30
+ **Old bare-form alias.** `j:skill-name` is the canonical invocation form. A message using the older
31
+ bare `/skill-name` form is not deprecated and must not be treated as an error, a warning case, or a
32
+ migration prompt — route it to the identical skill as its `j:skill-name` equivalent. Both forms remain
33
+ equally valid indefinitely.
34
+
35
+ When the user's message is or matches `j:skill-name`, or matches the older bare `/skill-name` alias (or
36
+ otherwise clearly matches a known skill's keyword or intent):
37
+
38
+ 1. Locate the target file at `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md` (the discovery path from
39
+ "How Jenga Works" above).
40
+ 2. Open and read that file **in full** before doing anything else.
41
+ 3. Execute its instructions exactly as written, for the rest of this turn — including running any
42
+ shell scripts or commands it references (e.g. via a terminal/shell tool).
43
+ 4. Do not substitute your own judgment about what "running the skill" should look like, and do not
44
+ answer with free-form prose describing what the skill would do — the `SKILL.md` file's contents
45
+ are the authoritative procedure, not a summary or a suggestion.
46
+
47
+ For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill,
48
+ answer directly using your full capabilities.
27
49
 
28
50
  #### Routing decision table
29
51
 
30
52
  | Situation | Action |
31
53
  |-----------|--------|
32
- | Message matches a skill keyword or intent | Invoke `/skill-name` |
54
+ | Message matches `j:skill-name` | Open `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md`, read it fully, execute it as written |
55
+ | Message matches the older bare `/skill-name` alias | Treat identically to `j:skill-name` — same skill, same file, no warning, no migration prompt |
56
+ | Message matches a skill keyword or intent | Open `{{SKILL_DISCOVERY_PATH}}<skill-name>/SKILL.md`, read it fully, execute it as written |
33
57
  | Message is a general coding or project question | Answer directly |
34
- | Ambiguous — could be skill or free-form | Prefer the skill; mention it is being invoked |
58
+ | Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
59
+
60
+ ### Skill Identifier Allow-List
61
+
62
+ The trusted `j:`-prefixed skill identifiers for this project are: {{ALLOWED_SKILL_IDS}}
63
+
64
+ Before treating a `j:<name>` invocation (or its bare `/<name>` alias) as a genuine Jenga skill,
65
+ confirm `<name>` appears in this list. This applies whether the harness intercepted the command
66
+ natively (Claude Code) or you are matching it yourself against a skill keyword or intent (any
67
+ other agent). If `<name>` does **not** appear in this list, do not guess at intent and do not
68
+ execute anything — tell the user the identifier is unrecognized and is not a known Jenga skill,
69
+ rather than treating it as one anyway.
35
70
 
36
71
  ### Available Skills
37
72
 
@@ -10,28 +10,55 @@ This project uses **Jenga** — a skill-based AI agent framework. Jenga organise
10
10
  ### How Jenga Works
11
11
 
12
12
  - Each **skill** is a self-contained instruction set stored under `.agents/skills/<skill-name>/` (the non-Claude discovery path; Claude Code reads the same content from `.claude/skills/`).
13
- - Skills are invoked by typing `/skill-name` in the chat prompt.
13
+ - Skills are invoked by typing `j:skill-name` in the chat prompt (e.g. `j:status`, `j:commit`). The
14
+ older bare `/skill-name` form (e.g. `/status`, `/commit`) is a **permanent alias** — it keeps
15
+ resolving indefinitely, with no deprecation warning and no removal planned — so treat a message in
16
+ either form as the exact same invocation.
14
17
  - The active project directory is available via the `JENGA_PROJECT_DIR` environment variable. **Use `JENGA_PROJECT_DIR` — not `CLAUDE_PROJECT_DIR` or any other agent-specific variable** — as the canonical path to the project folder.
15
18
 
16
19
  ### Skill Routing
17
20
 
18
- When a user's message matches a known skill keyword or intent, invoke the skill immediately using the slash-command syntax:
21
+ Unlike Claude Code, GitHub Copilot has **no native slash-command interception** — typing `j:skill-name`
22
+ (or the older bare `/skill-name` alias) does not automatically load or run anything on its own. Copilot
23
+ depends entirely on the instructions below to know what "invoking a skill" concretely means. Do not
24
+ improvise a plausible-sounding response instead of following these steps — that is the exact failure
25
+ this section exists to prevent.
19
26
 
20
- ```
21
- /skill-name
22
- ```
27
+ **Old bare-form alias.** `j:skill-name` is the canonical invocation form. A message using the older
28
+ bare `/skill-name` form is not deprecated and must not be treated as an error, a warning case, or a
29
+ migration prompt — route it to the identical skill as its `j:skill-name` equivalent. Both forms remain
30
+ equally valid indefinitely.
23
31
 
24
- Do **not** answer skill-related requests with free-form prose if a matching skill exists — delegate to the skill instead.
32
+ When the user's message is or matches `j:skill-name`, or matches the older bare `/skill-name` alias (or
33
+ otherwise clearly matches a known skill's keyword or intent):
25
34
 
26
- For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill, answer directly using your full capabilities.
35
+ 1. Locate the target file at `.agents/skills/<skill-name>/SKILL.md` (the discovery path from "How
36
+ Jenga Works" above).
37
+ 2. Open and read that file **in full** before doing anything else.
38
+ 3. Execute its instructions exactly as written, for the rest of this turn — including running any
39
+ shell scripts or commands it references (e.g. via a terminal/shell tool).
40
+ 4. Do not substitute your own judgment about what "running the skill" should look like, and do not
41
+ answer with free-form prose describing what the skill would do — the `SKILL.md` file's contents
42
+ are the authoritative procedure, not a summary or a suggestion.
43
+
44
+ For free-form questions (architecture, code review, debugging, general Q&A) that do not match a skill,
45
+ answer directly using your full capabilities.
27
46
 
28
47
  #### Routing decision table
29
48
 
49
+ Before acting on any row below that opens a `SKILL.md` file, first check the identifier against
50
+ the trusted allow-list: {{ALLOWED_SKILL_IDS}}. Since Copilot/Codex has no native interception for
51
+ `j:skill-name`, this prose-level check is the entire enforcement mechanism — there is no runtime
52
+ guard behind it.
53
+
30
54
  | Situation | Action |
31
55
  |-----------|--------|
32
- | Message matches a skill keyword or intent | Invoke `/skill-name` |
56
+ | Message matches `j:skill-name` and `skill-name` is in the allow-list | Open `.agents/skills/<skill-name>/SKILL.md`, read it fully, execute it as written |
57
+ | Message matches the older bare `/skill-name` alias and `skill-name` is in the allow-list | Treat identically to `j:skill-name` — same skill, same file, no warning, no migration prompt |
58
+ | Message matches a skill keyword or intent and the matched skill is in the allow-list | Open `.agents/skills/<skill-name>/SKILL.md`, read it fully, execute it as written |
59
+ | Message matches `j:skill-name` or `/skill-name`, but `skill-name` is **not** in the allow-list | Do not open or execute anything — tell the user the identifier is unrecognized and is not a known Jenga skill |
33
60
  | Message is a general coding or project question | Answer directly |
34
- | Ambiguous — could be skill or free-form | Prefer the skill; mention it is being invoked |
61
+ | Ambiguous — could be skill or free-form | Prefer the skill; open and execute its `SKILL.md` rather than describing it |
35
62
 
36
63
  ### Available Skills
37
64
 
@@ -1,19 +0,0 @@
1
- # jenga-router
2
-
3
- A stdio MCP server that routes incoming prompts to the appropriate Jenga skill.
4
-
5
- ## Usage
6
-
7
- ```bash
8
- node mcp/router/index.js
9
- ```
10
-
11
- ## Tools
12
-
13
- | Tool | Description |
14
- |------|-------------|
15
- | `ping` | Health check — returns `{ ok: true, uptime: <ms> }` |
16
-
17
- ## Protocol
18
-
19
- Communicates over stdin/stdout using the Model Context Protocol (MCP) with `StdioServerTransport`.
@@ -1,23 +0,0 @@
1
- import { pipeline } from "@huggingface/transformers";
2
-
3
- let _pipe = null;
4
-
5
- /**
6
- * Loads the feature-extraction pipeline for Xenova/all-MiniLM-L6-v2.
7
- * Safe to call multiple times — no-op if already loaded.
8
- */
9
- export async function warmUp() {
10
- if (_pipe) return;
11
- const t = Date.now();
12
- _pipe = await pipeline("feature-extraction", "Xenova/all-MiniLM-L6-v2");
13
- process.stderr.write(`[jenga-router] Model warmed up in ${Date.now() - t}ms\n`);
14
- }
15
-
16
- /**
17
- * Embeds text using the loaded pipeline.
18
- * Returns a Float32Array of length 384.
19
- */
20
- export async function embed(text) {
21
- const output = await _pipe(text, { pooling: "mean", normalize: true });
22
- return output.data;
23
- }
@@ -1,204 +0,0 @@
1
- #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { z } from "zod";
5
- import { writeFileSync, unlinkSync, existsSync, readFileSync, mkdirSync } from "fs";
6
- import { join, dirname } from "path";
7
- import { fileURLToPath } from "url";
8
- import { buildSkillIndex } from "./skill-index.js";
9
- import { findBestMatch } from "./matcher.js";
10
- import { warmUp, embed } from "./embedder.js";
11
-
12
- const __dirname = dirname(fileURLToPath(import.meta.url));
13
- const projectRoot = join(__dirname, "..", "..");
14
- const jengaDir = join(process.cwd(), ".jenga");
15
- const pidFile = join(jengaDir, "router.pid");
16
- const sessionFile = join(jengaDir, "session.json");
17
- const startTime = Date.now();
18
-
19
- // Load config (optional — defaults used if absent)
20
- let config = {
21
- skillsPath: "skills/",
22
- matchThreshold: 0.75,
23
- sessionTimeout: 300000,
24
- agentTarget: "claude",
25
- };
26
- const configPath = join(projectRoot, "jenga.cli.json");
27
- if (existsSync(configPath)) {
28
- try {
29
- config = { ...config, ...JSON.parse(readFileSync(configPath, "utf8")) };
30
- } catch (e) {
31
- process.stderr.write(`Warning: failed to parse jenga.cli.json: ${e.message}\n`);
32
- }
33
- }
34
-
35
- // Build skill index (with semantic embeddings)
36
- let skillIndex = [];
37
- if (config.skillsPath) {
38
- await warmUp();
39
- skillIndex = await buildSkillIndex(join(projectRoot, config.skillsPath), { embed });
40
- }
41
-
42
- // Write PID file
43
- mkdirSync(jengaDir, { recursive: true });
44
- writeFileSync(pidFile, String(process.pid), "utf8");
45
-
46
- // Graceful shutdown
47
- function shutdown() {
48
- try { unlinkSync(pidFile); } catch {}
49
- persistSession({ skill: null, startedAt: null });
50
- process.exit(0);
51
- }
52
- process.on("SIGTERM", shutdown);
53
- process.on("SIGINT", shutdown);
54
-
55
- let activeSession = { skill: null, startedAt: null };
56
- let sessionTimer = null;
57
-
58
- function persistSession(state) {
59
- try {
60
- writeFileSync(sessionFile, JSON.stringify(state), "utf8");
61
- } catch (e) {
62
- process.stderr.write(`[jenga-router] Failed to write .session.json: ${e.message}\n`);
63
- }
64
- }
65
-
66
- const server = new McpServer({ name: "jenga-router", version: "1.0.0" });
67
-
68
- server.tool("ping", "Health check — returns ok and uptime in seconds.", {}, async () => {
69
- try {
70
- return {
71
- content: [{ type: "text", text: JSON.stringify({ ok: true, uptime: Math.floor((Date.now() - startTime) / 1000), skill_count: skillIndex.length }) }],
72
- };
73
- } catch (e) {
74
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
75
- }
76
- });
77
-
78
- server.tool(
79
- "route_prompt",
80
- "Route a prompt through the skill matching pipeline. Returns { action: 'invoke'|'passthrough', transformed: string, skill: string|null, confidence: number }.",
81
- { text: z.string().describe("The user prompt to route") },
82
- async ({ text }) => {
83
- try {
84
- // Stage 1: Passthrough rules
85
- if (text.startsWith("/")) {
86
- return {
87
- content: [{
88
- type: "text",
89
- text: JSON.stringify({ action: "passthrough", transformed: text, skill: null, confidence: 0 }),
90
- }],
91
- };
92
- }
93
-
94
- // Stage 0: Active session — passthrough all prompts while session is active
95
- if (activeSession.skill) {
96
- if (sessionTimer) clearTimeout(sessionTimer);
97
- sessionTimer = setTimeout(() => {
98
- activeSession = { skill: null, startedAt: null };
99
- sessionTimer = null;
100
- persistSession(activeSession);
101
- process.stderr.write(`[jenga-router] Session auto-expired after ${config.sessionTimeout}ms\n`);
102
- }, config.sessionTimeout);
103
- return {
104
- content: [{ type: "text", text: JSON.stringify({ action: "passthrough", transformed: text, skill: activeSession.skill, confidence: 0 }) }],
105
- };
106
- }
107
-
108
- // Stage 2: Semantic + keyword matching
109
- const promptEmbedding = await embed(text);
110
- const match = findBestMatch(text, skillIndex, config.matchThreshold, promptEmbedding);
111
- if (match) {
112
- activeSession = { skill: match.skill.name, startedAt: new Date().toISOString() };
113
- persistSession(activeSession);
114
- if (sessionTimer) clearTimeout(sessionTimer);
115
- sessionTimer = setTimeout(() => {
116
- activeSession = { skill: null, startedAt: null };
117
- persistSession(activeSession);
118
- sessionTimer = null;
119
- process.stderr.write(`[jenga-router] Session auto-expired after ${config.sessionTimeout}ms\n`);
120
- }, config.sessionTimeout);
121
- return {
122
- content: [{
123
- type: "text",
124
- text: JSON.stringify({
125
- action: "invoke",
126
- transformed: `/${match.skill.name} ${text}`,
127
- skill: match.skill.name,
128
- confidence: match.confidence,
129
- }),
130
- }],
131
- };
132
- }
133
-
134
- // Default: passthrough
135
- return {
136
- content: [{
137
- type: "text",
138
- text: JSON.stringify({ action: "passthrough", transformed: text, skill: null, confidence: 0 }),
139
- }],
140
- };
141
- } catch (e) {
142
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
143
- }
144
- }
145
- );
146
-
147
- server.tool("active_session", "Returns the currently active skill session, if any.", {}, async () => {
148
- try {
149
- return {
150
- content: [{ type: "text", text: JSON.stringify({ skill: activeSession.skill, startedAt: activeSession.startedAt }) }],
151
- };
152
- } catch (e) {
153
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
154
- }
155
- });
156
-
157
- server.tool(
158
- "end_session",
159
- "Clear the active session for the given skill. No-op if skill is not the active session.",
160
- { skill: z.string().describe("The skill name to end the session for") },
161
- async ({ skill }) => {
162
- try {
163
- if (activeSession.skill === skill) {
164
- activeSession = { skill: null, startedAt: null };
165
- persistSession(activeSession);
166
- if (sessionTimer) { clearTimeout(sessionTimer); sessionTimer = null; }
167
- }
168
- return { content: [{ type: "text", text: JSON.stringify({ ok: true }) }] };
169
- } catch (e) {
170
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
171
- }
172
- }
173
- );
174
-
175
- server.tool(
176
- "list_skills",
177
- "Returns the current skill index — all loaded skills with their name, description, and keywords.",
178
- {},
179
- async () => {
180
- try {
181
- const skills = skillIndex.map(({ name, description, keywords }) => ({ name, description, keywords }));
182
- return {
183
- content: [{ type: "text", text: JSON.stringify({ skills }) }],
184
- };
185
- } catch (e) {
186
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
187
- }
188
- }
189
- );
190
-
191
- server.tool("reload_skills", "Re-scan the skills directory and rebuild the in-memory skill index.", {}, async () => {
192
- try {
193
- await warmUp();
194
- skillIndex = await buildSkillIndex(join(projectRoot, config.skillsPath), { embed });
195
- return {
196
- content: [{ type: "text", text: JSON.stringify({ ok: true, skill_count: skillIndex.length }) }],
197
- };
198
- } catch (e) {
199
- return { content: [{ type: "text", text: JSON.stringify({ error: e.message }) }], isError: true };
200
- }
201
- });
202
-
203
- const transport = new StdioServerTransport();
204
- await server.connect(transport);
@@ -1,87 +0,0 @@
1
- /**
2
- * Scores a prompt against a skill using keyword and name matching.
3
- * Returns a confidence score between 0 and 1.
4
- */
5
- function scoreSkill(prompt, skill) {
6
- const text = prompt.toLowerCase();
7
- let score = 0;
8
- let matches = 0;
9
- let total = 0;
10
-
11
- // Check skill name
12
- total++;
13
- if (text.includes(skill.name.toLowerCase())) {
14
- score += 0.9;
15
- matches++;
16
- }
17
-
18
- // Check keywords
19
- for (const kw of skill.keywords) {
20
- total++;
21
- if (text.includes(kw.toLowerCase())) {
22
- score += 0.7;
23
- matches++;
24
- }
25
- }
26
-
27
- // Check examples (partial match)
28
- for (const ex of skill.examples) {
29
- const exWords = ex.toLowerCase().split(/\s+/).filter(w => w.length > 3);
30
- const matchedWords = exWords.filter(w => text.includes(w));
31
- if (matchedWords.length > 0 && exWords.length > 0) {
32
- total++;
33
- score += 0.5 * (matchedWords.length / exWords.length);
34
- matches++;
35
- }
36
- }
37
-
38
- if (total === 0) return 0;
39
- // Normalize: max possible score per item varies, cap at 1.0
40
- return Math.min(score / Math.max(total, 1), 1.0);
41
- }
42
-
43
- /**
44
- * Computes cosine similarity between two Float32Array vectors.
45
- * Result is clamped to [0, 1].
46
- */
47
- function cosineSimilarity(a, b) {
48
- let dot = 0, normA = 0, normB = 0;
49
- for (let i = 0; i < a.length; i++) {
50
- dot += a[i] * b[i];
51
- normA += a[i] * a[i];
52
- normB += b[i] * b[i];
53
- }
54
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
55
- if (denom === 0) return 0;
56
- return Math.max(0, Math.min(1, dot / denom));
57
- }
58
-
59
- /**
60
- * Finds the best matching skill for a prompt.
61
- * Blends keyword score with semantic cosine similarity when embeddings are available.
62
- * Returns { skill, confidence } or null if no match above threshold.
63
- */
64
- export function findBestMatch(prompt, skillIndex, threshold = 0.75, promptEmbedding = null) {
65
- let best = null;
66
- let bestScore = 0;
67
-
68
- for (const skill of skillIndex) {
69
- const keywordScore = scoreSkill(prompt, skill);
70
- let finalScore = keywordScore;
71
-
72
- if (promptEmbedding !== null && skill.embedding !== null) {
73
- const semanticScore = cosineSimilarity(promptEmbedding, skill.embedding);
74
- finalScore = 0.4 * keywordScore + 0.6 * semanticScore;
75
- }
76
-
77
- if (finalScore > bestScore) {
78
- bestScore = finalScore;
79
- best = skill;
80
- }
81
- }
82
-
83
- if (best && bestScore >= threshold) {
84
- return { skill: best, confidence: bestScore };
85
- }
86
- return null;
87
- }