@jenga-ai/agent 1.2.3 → 1.3.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/README.md +4 -1
- package/agents/developer.md +18 -0
- package/agents/scrum-master.md +1 -0
- package/agents/tester.md +18 -0
- package/hooks/on_session_end.sh +27 -0
- package/lib/commands/init.js +23 -68
- package/lib/generate-copilot-instructions.js +142 -0
- package/package.json +18 -17
- package/scripts/postinstall.js +21 -0
- package/skills/commit/SKILL.md +11 -1
- package/skills/dev-done/SKILL.md +46 -0
- package/skills/dev-done/scripts/classify-commit-outcome.sh +114 -0
- package/skills/init/SKILL.md +7 -6
- package/skills/init/assets/scope-thresholds_template.json +7 -0
- package/skills/init/scripts/init.sh +6 -0
- package/skills/publish/SKILL.md +66 -0
- package/skills/publish/adapters/npm-ci.md +34 -0
- package/skills/publish/adapters/npm.md +18 -0
- package/skills/publish/assets/ci-contract.md +27 -0
- package/skills/publish/assets/publish.example.json +27 -0
- package/skills/publish/schemas/publish.schema.json +20 -0
- package/skills/publish/scripts/npm_ci_pipeline.sh +29 -0
- package/skills/publish/scripts/npm_stage_inspect.sh +829 -0
- package/skills/publish/scripts/npm_stage_pipeline.sh +427 -0
- package/skills/publish/scripts/publish_common.sh +16 -0
- package/skills/publish/scripts/show_history.sh +12 -5
- package/skills/publish/scripts/validate_npm_stage_env.sh +184 -0
- package/skills/publish/scripts/write_ledger_entry.sh +92 -2
- package/skills/reconcile/SKILL.md +121 -11
- package/skills/reconcile/assets/report_format.md +17 -0
- package/skills/reconcile/scripts/resolve-reconcile-scope.sh +489 -0
- package/skills/uncharted/SKILL.md +200 -21
- package/skills/uncharted/scripts/directory-triage.sh +342 -0
- package/skills/uncharted/scripts/elicitation-state.sh +457 -0
- package/templates/SCRUM_BOARD_SCHEMA.md +57 -0
- package/templates/agent-context.md.tpl +23 -11
- package/templates/copilot-instructions.md.tpl +21 -11
- package/mcp/router/README.md +0 -19
- package/mcp/router/embedder.js +0 -23
- package/mcp/router/index.js +0 -204
- package/mcp/router/matcher.js +0 -87
- package/mcp/router/package-lock.json +0 -1048
- package/mcp/router/package.json +0 -11
- package/mcp/router/skill-index.js +0 -104
- package/skills/route/SKILL.md +0 -180
package/mcp/router/package.json
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "jenga-router",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"type": "module",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"description": "Jenga Router — stdio MCP server for skill-based prompt routing",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
"@modelcontextprotocol/sdk": "^1.11.1",
|
|
9
|
-
"zod": "^3.24.4"
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, statSync, existsSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Parses YAML frontmatter from a markdown file.
|
|
6
|
-
* Returns an object with the parsed fields, or {} if no frontmatter found.
|
|
7
|
-
*/
|
|
8
|
-
function parseFrontmatter(content) {
|
|
9
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
10
|
-
if (!match) return {};
|
|
11
|
-
try {
|
|
12
|
-
const lines = match[1].split("\n");
|
|
13
|
-
const obj = {};
|
|
14
|
-
let currentKey = null;
|
|
15
|
-
let arrayMode = false;
|
|
16
|
-
for (const line of lines) {
|
|
17
|
-
const arrayItem = line.match(/^\s+-\s+(.*)/);
|
|
18
|
-
if (arrayItem && arrayMode && currentKey) {
|
|
19
|
-
obj[currentKey].push(arrayItem[1].trim().replace(/^["']|["']$/g, ""));
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
|
-
arrayMode = false;
|
|
23
|
-
const kv = line.match(/^(\w+):\s*(.*)/);
|
|
24
|
-
if (!kv) continue;
|
|
25
|
-
const [, key, val] = kv;
|
|
26
|
-
if (val.trim() === "[]") {
|
|
27
|
-
obj[key] = [];
|
|
28
|
-
currentKey = key;
|
|
29
|
-
arrayMode = false;
|
|
30
|
-
} else if (val.trim() === "") {
|
|
31
|
-
obj[key] = [];
|
|
32
|
-
currentKey = key;
|
|
33
|
-
arrayMode = true;
|
|
34
|
-
} else {
|
|
35
|
-
obj[key] = val.trim().replace(/^["']|["']$/g, "");
|
|
36
|
-
currentKey = key;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return obj;
|
|
40
|
-
} catch {
|
|
41
|
-
return {};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Recursively finds all SKILL.md files under a directory.
|
|
47
|
-
*/
|
|
48
|
-
function findSkillFiles(dir) {
|
|
49
|
-
if (!existsSync(dir)) return [];
|
|
50
|
-
const results = [];
|
|
51
|
-
try {
|
|
52
|
-
for (const entry of readdirSync(dir)) {
|
|
53
|
-
const full = join(dir, entry);
|
|
54
|
-
try {
|
|
55
|
-
if (statSync(full).isDirectory()) {
|
|
56
|
-
results.push(...findSkillFiles(full));
|
|
57
|
-
} else if (entry === "SKILL.md") {
|
|
58
|
-
results.push(full);
|
|
59
|
-
}
|
|
60
|
-
} catch {}
|
|
61
|
-
}
|
|
62
|
-
} catch {}
|
|
63
|
-
return results;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Builds the in-memory skill index from the skills directory.
|
|
68
|
-
* Optionally accepts an embedder ({ embed(text) }) to pre-compute skill embeddings.
|
|
69
|
-
* Returns an array of skill records.
|
|
70
|
-
*/
|
|
71
|
-
export async function buildSkillIndex(skillsPath, embedder = null) {
|
|
72
|
-
const start = Date.now();
|
|
73
|
-
const files = findSkillFiles(skillsPath);
|
|
74
|
-
const skills = [];
|
|
75
|
-
|
|
76
|
-
for (const filePath of files) {
|
|
77
|
-
try {
|
|
78
|
-
const content = readFileSync(filePath, "utf8");
|
|
79
|
-
const fm = parseFrontmatter(content);
|
|
80
|
-
if (!fm.name) {
|
|
81
|
-
process.stderr.write(`Warning: ${filePath} has no 'name' in frontmatter — skipped\n`);
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const skill = {
|
|
85
|
-
name: fm.name,
|
|
86
|
-
description: fm.description || "",
|
|
87
|
-
keywords: Array.isArray(fm.keywords) ? fm.keywords : [],
|
|
88
|
-
examples: Array.isArray(fm.examples) ? fm.examples : [],
|
|
89
|
-
path: filePath,
|
|
90
|
-
embedding: null,
|
|
91
|
-
};
|
|
92
|
-
if (embedder) {
|
|
93
|
-
skill.embedding = await embedder.embed(skill.description);
|
|
94
|
-
}
|
|
95
|
-
skills.push(skill);
|
|
96
|
-
} catch (e) {
|
|
97
|
-
process.stderr.write(`Warning: failed to parse ${filePath}: ${e.message}\n`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const elapsed = Date.now() - start;
|
|
102
|
-
process.stderr.write(`[jenga-router] Indexed ${skills.length} skills in ${elapsed}ms\n`);
|
|
103
|
-
return skills;
|
|
104
|
-
}
|
package/skills/route/SKILL.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: route
|
|
3
|
-
description: Intelligently routes a prompt to the best-matching skill in the project. Reads available skills, matches the input semantically and by keyword, enriches the prompt with board context and documentation references, then invokes the matched skill with the full context prepended.
|
|
4
|
-
keywords:
|
|
5
|
-
- "route"
|
|
6
|
-
- "dispatch"
|
|
7
|
-
- "auto skill"
|
|
8
|
-
- "smart prompt"
|
|
9
|
-
- "find skill"
|
|
10
|
-
examples:
|
|
11
|
-
- "route: I want to plan a new feature"
|
|
12
|
-
- "route: something is broken in the auth module"
|
|
13
|
-
- "route: let's review what's done so far"
|
|
14
|
-
- "route: analyze this approach in depth before we commit"
|
|
15
|
-
- "route: add a task to capture this idea"
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Route — Intelligent Skill Dispatcher
|
|
19
|
-
|
|
20
|
-
## Purpose
|
|
21
|
-
|
|
22
|
-
`/route <prompt>` takes any natural-language input, finds the best-matching skill in the project, enriches it with live board context and documentation references, and invokes the matched skill — all in a single, fully-loaded message.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Instructions
|
|
27
|
-
|
|
28
|
-
### Step 1 — Discover Available Skills
|
|
29
|
-
|
|
30
|
-
Scan both skill registries (they may differ slightly — check both):
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
.agents/skills/<name>/SKILL.md
|
|
34
|
-
.claude/skills/<name>/SKILL.md (if the directory exists)
|
|
35
|
-
skills/<name>/SKILL.md (if the directory exists)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
For each skill found, extract the following fields from its YAML front-matter:
|
|
39
|
-
|
|
40
|
-
| Field | Purpose |
|
|
41
|
-
|---|---|
|
|
42
|
-
| `name` | Canonical identifier |
|
|
43
|
-
| `description` | Primary match signal |
|
|
44
|
-
| `keywords` | Exact/partial keyword match |
|
|
45
|
-
| `examples` | Semantic match reference |
|
|
46
|
-
| `metadata.prefered_agent` | Which agent to delegate to |
|
|
47
|
-
|
|
48
|
-
Build an in-memory skill registry from this data before proceeding.
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
|
|
52
|
-
### Step 2 — Match the Prompt to a Skill
|
|
53
|
-
|
|
54
|
-
Run matching in three passes (stop at first confident match):
|
|
55
|
-
|
|
56
|
-
#### Pass 1 — Keyword Match
|
|
57
|
-
Check whether any phrase from a skill's `keywords` list appears verbatim (case-insensitive) in the prompt.
|
|
58
|
-
|
|
59
|
-
#### Pass 2 — Example Similarity
|
|
60
|
-
Compare the prompt against each skill's `examples` list. Treat this as a semantic similarity check: pick the skill whose examples most closely reflect the intent of the prompt.
|
|
61
|
-
|
|
62
|
-
#### Pass 3 — Description Match
|
|
63
|
-
If no clear winner has emerged, compare the prompt against each skill's `description` field. Pick the skill whose description best captures what the user is trying to do.
|
|
64
|
-
|
|
65
|
-
**If two or more skills score equally**, present the top candidates and ask the user to choose:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
More than one skill matches your prompt. Which should I apply?
|
|
69
|
-
1. /<skill-a> — <one-line description>
|
|
70
|
-
2. /<skill-b> — <one-line description>
|
|
71
|
-
3. Neither — describe what you need
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**If no skill matches at all**, inform the user and offer:
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
No matching skill found for: "<prompt>"
|
|
78
|
-
Would you like to:
|
|
79
|
-
1. Browse all available skills (/help)
|
|
80
|
-
2. Create a new skill for this use case (/btw)
|
|
81
|
-
3. Proceed without a skill (raw prompt)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
### Step 3 — Check the Board for Related Context
|
|
87
|
-
|
|
88
|
-
Read `project/configs/workflow.json` for board paths (default: `project/board/`).
|
|
89
|
-
|
|
90
|
-
Scan the board for items that are topically related to the prompt:
|
|
91
|
-
|
|
92
|
-
1. **Epics** — `project/board/epics/` — check `title` and `description` fields
|
|
93
|
-
2. **Stories** — `project/board/stories/` — check `title` and `description` fields
|
|
94
|
-
3. **Tasks** — `project/board/tasks/` — check `title` and `description` fields
|
|
95
|
-
|
|
96
|
-
Extract any items whose title or description contains keywords from the prompt. Limit to the **top 5 most relevant** results. Do not include items whose status is `Archived` or `Cancelled`.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
### Step 4 — Check Documentation
|
|
101
|
-
|
|
102
|
-
Scan the following directories for relevant documentation files:
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
project/documentation/plans/
|
|
106
|
-
project/documentation/summaries/
|
|
107
|
-
project/documentation/examples/
|
|
108
|
-
docs/
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
Match filenames and headings against the prompt keywords. Collect the **top 3 most relevant** file paths.
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
### Step 5 — Construct the Enriched Prompt
|
|
116
|
-
|
|
117
|
-
Assemble the following composite message in **exactly this order**:
|
|
118
|
-
|
|
119
|
-
#### Part A — Matched Skill (full content)
|
|
120
|
-
Inline the **full body** of the matched skill's `SKILL.md` (everything after the YAML front-matter). This is placed first so the agent reads the skill instructions before anything else.
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
<!-- SKILL: /<matched-skill-name> -->
|
|
124
|
-
<full skill instructions>
|
|
125
|
-
<!-- END SKILL -->
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
#### Part B — Board & Documentation Context
|
|
129
|
-
Immediately after the skill, append a context block:
|
|
130
|
-
|
|
131
|
-
```markdown
|
|
132
|
-
---
|
|
133
|
-
## 📋 Relevant Board Context
|
|
134
|
-
|
|
135
|
-
<list each matched board item as:>
|
|
136
|
-
- [<status>] **<ID>** — <title> (`project/board/<type>/<filename>`)
|
|
137
|
-
|
|
138
|
-
## 📄 Relevant Documentation
|
|
139
|
-
|
|
140
|
-
<list each matched doc as:>
|
|
141
|
-
- `<file-path>` — <one-line summary of the doc's purpose>
|
|
142
|
-
---
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
If no board items or docs were found, omit the corresponding section rather than leaving it empty.
|
|
146
|
-
|
|
147
|
-
#### Part C — Original Prompt
|
|
148
|
-
Finally, append the user's original unmodified prompt:
|
|
149
|
-
|
|
150
|
-
```
|
|
151
|
-
## 🗣 Original Prompt
|
|
152
|
-
|
|
153
|
-
> <original prompt verbatim>
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
### Step 6 — Invoke the Matched Skill
|
|
159
|
-
|
|
160
|
-
Deliver the enriched composite message to the appropriate agent:
|
|
161
|
-
|
|
162
|
-
- If the matched skill specifies `metadata.prefered_agent`, load that agent's definition from `agents/<prefered_agent>.md` and pass the full composite message to it.
|
|
163
|
-
- If no `prefered_agent` is specified, execute the skill instructions directly using the composite message as the working input.
|
|
164
|
-
|
|
165
|
-
Do **not** summarise or restate the composite message — deliver it as-is.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
|
|
169
|
-
### Step 7 — Report Routing Decision
|
|
170
|
-
|
|
171
|
-
Before invoking the skill, briefly inform the user:
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
Routing to: /<matched-skill-name>
|
|
175
|
-
Reason: <one sentence explaining why this skill was chosen>
|
|
176
|
-
Board items found: <count>
|
|
177
|
-
Docs found: <count>
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Then proceed immediately — do not wait for user confirmation unless the match was ambiguous (Step 2 tie-break already handled that).
|