@jenga-ai/agent 1.2.2 → 1.2.4
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 +12 -10
- package/lib/commands/init.js +23 -68
- package/lib/generate-agent-context.js +11 -2
- package/lib/generate-copilot-instructions.js +142 -0
- package/package.json +5 -1
- package/scripts/postinstall.js +24 -3
- package/scripts/validate_npm_metadata.sh +2 -2
- package/skills/distribute/CONFIG_SCHEMA.md +4 -4
- package/skills/distribute/SKILL.md +2 -2
- package/skills/distribute/scripts/distribute-changes.sh +1 -1
- package/skills/doc/SKILL.md +1 -1
- package/skills/init/SKILL.md +20 -4
- package/skills/init/scripts/apply-project-visibility.sh +1 -1
- package/skills/init/scripts/init.sh +19 -2
- package/skills/publish/scripts/npm_pipeline.sh +2 -2
- package/skills/uncharted/scripts/discover-subsystems.sh +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Jenga AI
|
|
2
2
|
|
|
3
3
|
**A structured multi-agent development workflow that works with any AI agent or AI-native IDE.** Three specialised AI agents — Scrum Master, Developer, and Tester — collaborate through a shared scrum board, an event-driven trigger queue, and 28 slash-command skills to take a project from idea to verified, committed code — across as many sessions as it takes.
|
|
4
4
|
|
|
@@ -25,16 +25,18 @@ npm install @jenga-ai/agent
|
|
|
25
25
|
| Agent | Skills & Agents | Root Context File |
|
|
26
26
|
|---|---|---|
|
|
27
27
|
| **Claude Code** | `.claude/` — mirrored automatically on install | `CLAUDE.md` — generated by `/init` today |
|
|
28
|
-
| **GitHub Copilot** | `.agents/` — mirrored automatically on install | `.github/copilot-instructions.md` —
|
|
28
|
+
| **GitHub Copilot** | `.agents/` — mirrored automatically on install | `.github/copilot-instructions.md` — bootstrapped at `npm install` time, refined by `jenga init` |
|
|
29
29
|
| **Codex** | `.agents/` — mirrored automatically on install | `AGENTS.md` — generated by `/init` today |
|
|
30
30
|
|
|
31
31
|
`CLAUDE.md` and `AGENTS.md` are generated unconditionally by `/init` — every agent gets a real, populated root-level context file out of the box, not just a pointer. If either file already exists as a genuine pre-existing user file, Jenga writes its own copy as `J-CLAUDE.md` / `J-AGENTS.md` instead and inserts a short reference into the existing file, leaving it otherwise untouched.
|
|
32
32
|
|
|
33
|
+
`.github/copilot-instructions.md` follows a different path: `scripts/postinstall.js` writes it unconditionally and non-interactively the moment `npm install` finishes, so Copilot has working `/skill-name` routing instructions even if a consumer's very first action is a Copilot slash command, before the separate `jenga init` CLI wizard has ever run. Running `jenga init` afterward refines the same file using the user's actual chosen skills path — it is never generated by the `/init` skill itself.
|
|
34
|
+
|
|
33
35
|
---
|
|
34
36
|
|
|
35
37
|
## The Problem It Solves
|
|
36
38
|
|
|
37
|
-
Without a framework like
|
|
39
|
+
Without a framework like Jenga AI, AI-assisted development has serious structural weaknesses:
|
|
38
40
|
|
|
39
41
|
| Problem | Reality |
|
|
40
42
|
|---|---|
|
|
@@ -45,7 +47,7 @@ Without a framework like JengaAgent, AI-assisted development has serious structu
|
|
|
45
47
|
| **No audit trail** | You can't replay *why* something was built, by which agent, based on which task |
|
|
46
48
|
| **Sessions just end** | Work-in-progress, unresolved problems, and incomplete stories silently vanish |
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
Jenga AI solves each of these with structure: persistent board state, strict agent roles, typed inter-agent contracts, and session-end hooks that preserve context between sessions.
|
|
49
51
|
|
|
50
52
|
---
|
|
51
53
|
|
|
@@ -53,7 +55,7 @@ JengaAgent solves each of these with structure: persistent board state, strict a
|
|
|
53
55
|
|
|
54
56
|
### Before / After
|
|
55
57
|
|
|
56
|
-
**Without
|
|
58
|
+
**Without Jenga AI:**
|
|
57
59
|
```
|
|
58
60
|
You: "Add user authentication"
|
|
59
61
|
AI agent: [writes auth code, declares it works, session ends]
|
|
@@ -63,7 +65,7 @@ You: "What's the status of auth?"
|
|
|
63
65
|
AI agent: "I don't have context from the previous session."
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
**With
|
|
68
|
+
**With Jenga AI:**
|
|
67
69
|
```
|
|
68
70
|
/todo → "Add user authentication" → linked to E01_S02
|
|
69
71
|
/do → Developer creates worktree E01_S02_T01-auth
|
|
@@ -81,7 +83,7 @@ Next session:
|
|
|
81
83
|
|
|
82
84
|
### Real-World Scenario: Building a Feature Across Sessions
|
|
83
85
|
|
|
84
|
-
Imagine you're building a REST API with auth, rate limiting, and an admin dashboard. Each is a separate Epic. Here's how
|
|
86
|
+
Imagine you're building a REST API with auth, rate limiting, and an admin dashboard. Each is a separate Epic. Here's how Jenga AI handles that across multiple days:
|
|
85
87
|
|
|
86
88
|
**Planning**
|
|
87
89
|
```
|
|
@@ -187,7 +189,7 @@ Run `/status` at any time to see where the project stands.
|
|
|
187
189
|
|
|
188
190
|
## Supported Platforms
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
Jenga AI has been tested with:
|
|
191
193
|
|
|
192
194
|
| Platform | Type |
|
|
193
195
|
|---|---|
|
|
@@ -261,7 +263,7 @@ Skills live in `.agents/skills/<name>/SKILL.md`. Invoke with `/<name>` in your A
|
|
|
261
263
|
|
|
262
264
|
## Distributing the Workflow
|
|
263
265
|
|
|
264
|
-
|
|
266
|
+
Jenga AI can propagate its workflow files to other projects on your machine via `/distribute`.
|
|
265
267
|
|
|
266
268
|
1. **Register consumer projects** — add each consuming project to `distribute.config.json` at the repo root (or pass a path directly: `/distribute /path/to/project`).
|
|
267
269
|
2. **Run `/distribute`** — choose `major`, `minor`, `patch`, or `amend` release type; the skill handles versioning, dry-run preview, file copy, and a version bump commit.
|
|
@@ -375,7 +377,7 @@ All agents log every incoming sender object to `project/logs/events.json` as the
|
|
|
375
377
|
|
|
376
378
|
---
|
|
377
379
|
|
|
378
|
-
## When to Use
|
|
380
|
+
## When to Use Jenga AI
|
|
379
381
|
|
|
380
382
|
**Use it when:**
|
|
381
383
|
- You're building a non-trivial project across multiple sessions
|
package/lib/commands/init.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createInterface } from "readline";
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync, appendFileSync
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { validateConfig } from "../config-schema.js";
|
|
6
6
|
import { injectSettings } from "../inject-settings.js";
|
|
7
7
|
import { generateAgentContext } from "../generate-agent-context.js";
|
|
8
|
+
import { generateCopilotInstructions } from "../generate-copilot-instructions.js";
|
|
8
9
|
|
|
9
10
|
const CONFIG_FILE = "jenga.cli.json";
|
|
10
11
|
|
|
@@ -125,74 +126,28 @@ export async function runInit(args, projectRoot = process.cwd()) {
|
|
|
125
126
|
console.warn("You can register it manually later by running jenga init again.");
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
// Generate .github/copilot-instructions.md from template
|
|
129
|
-
//
|
|
130
|
-
//
|
|
129
|
+
// Generate .github/copilot-instructions.md from template, via the shared generator
|
|
130
|
+
// (lib/generate-copilot-instructions.js) also used unconditionally by scripts/postinstall.js
|
|
131
|
+
// at install time (E46_S03_T01). Templates ship inside the jenga-agent package
|
|
132
|
+
// (node_modules/jenga-agent/templates/), with a bare `templates/` at the project root as a
|
|
133
|
+
// fallback for the dev-repo case — both handled inside the shared generator.
|
|
134
|
+
//
|
|
135
|
+
// skillsPath may be a string or an array (multi-target installs); pick the first existing
|
|
136
|
+
// directory — mirrored copies hold identical content. This pass uses the user's actual
|
|
137
|
+
// chosen skillsPath from the wizard, which may differ from postinstall's `.agents/skills`
|
|
138
|
+
// default and refines whatever postinstall already bootstrapped. The shared generator's
|
|
139
|
+
// idempotent marker-replace logic means running it again here never duplicates the JENGA
|
|
140
|
+
// block or corrupts content outside the markers.
|
|
131
141
|
try {
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
join(projectRoot,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// array (multi-target installs); pick the first existing directory —
|
|
142
|
-
// mirrored copies hold identical content.
|
|
143
|
-
const skillsPathList = Array.isArray(config.skillsPath) ? config.skillsPath : [config.skillsPath];
|
|
144
|
-
const skillsDir = skillsPathList
|
|
145
|
-
.map(p => join(projectRoot, p))
|
|
146
|
-
.find(existsSync);
|
|
147
|
-
let skillLines = [];
|
|
148
|
-
if (skillsDir) {
|
|
149
|
-
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
150
|
-
if (!entry.isDirectory()) continue;
|
|
151
|
-
const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
|
|
152
|
-
let description = "";
|
|
153
|
-
if (existsSync(skillMdPath)) {
|
|
154
|
-
const content = readFileSync(skillMdPath, "utf8");
|
|
155
|
-
const match = content.match(/^description:\s*(.+)$/m);
|
|
156
|
-
if (match) description = match[1].trim();
|
|
157
|
-
}
|
|
158
|
-
skillLines.push(`- **${entry.name}**${description ? `: ${description}` : ""}`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const skillList = skillLines.length > 0 ? skillLines.join("\n") : "_No skills found._";
|
|
162
|
-
const rendered = tpl.replace("{{SKILL_LIST}}", skillList);
|
|
163
|
-
|
|
164
|
-
const githubDir = join(projectRoot, ".github");
|
|
165
|
-
const copilotInstructionsPath = join(githubDir, "copilot-instructions.md");
|
|
166
|
-
|
|
167
|
-
if (!existsSync(githubDir)) mkdirSync(githubDir, { recursive: true });
|
|
168
|
-
|
|
169
|
-
if (!existsSync(copilotInstructionsPath)) {
|
|
170
|
-
writeFileSync(copilotInstructionsPath, rendered, "utf8");
|
|
171
|
-
} else {
|
|
172
|
-
// Replace only the JENGA block; preserve content outside the markers
|
|
173
|
-
const existing = readFileSync(copilotInstructionsPath, "utf8");
|
|
174
|
-
const startMarker = "<!-- JENGA:START -->";
|
|
175
|
-
const endMarker = "<!-- JENGA:END -->";
|
|
176
|
-
const startIdx = existing.indexOf(startMarker);
|
|
177
|
-
const endIdx = existing.indexOf(endMarker);
|
|
178
|
-
|
|
179
|
-
// Extract the new JENGA block from rendered template
|
|
180
|
-
const renderedStart = rendered.indexOf(startMarker);
|
|
181
|
-
const renderedEnd = rendered.indexOf(endMarker);
|
|
182
|
-
const newBlock = rendered.slice(renderedStart, renderedEnd + endMarker.length);
|
|
183
|
-
|
|
184
|
-
let updated;
|
|
185
|
-
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
186
|
-
updated =
|
|
187
|
-
existing.slice(0, startIdx) +
|
|
188
|
-
newBlock +
|
|
189
|
-
existing.slice(endIdx + endMarker.length);
|
|
190
|
-
} else {
|
|
191
|
-
// No existing markers — append the block
|
|
192
|
-
updated = existing + (existing.endsWith("\n") ? "" : "\n") + newBlock + "\n";
|
|
193
|
-
}
|
|
194
|
-
writeFileSync(copilotInstructionsPath, updated, "utf8");
|
|
195
|
-
}
|
|
142
|
+
const skillsPathList = Array.isArray(config.skillsPath) ? config.skillsPath : [config.skillsPath];
|
|
143
|
+
const skillsDir = skillsPathList
|
|
144
|
+
.map(p => join(projectRoot, p))
|
|
145
|
+
.find(existsSync);
|
|
146
|
+
|
|
147
|
+
const result = generateCopilotInstructions(projectRoot, PACKAGE_ROOT, skillsDir);
|
|
148
|
+
if (result.skipped) {
|
|
149
|
+
console.warn("Warning: templates/copilot-instructions.md.tpl not found — skipped .github/copilot-instructions.md generation.");
|
|
150
|
+
} else {
|
|
196
151
|
console.log("✓ .github/copilot-instructions.md written");
|
|
197
152
|
}
|
|
198
153
|
} catch (e) {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* ESM, Node built-ins only — mirrors lib/mirror.js and lib/inject-settings.js.
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
29
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, realpathSync } from "fs";
|
|
30
30
|
import { join, dirname } from "path";
|
|
31
31
|
import { fileURLToPath } from "url";
|
|
32
32
|
|
|
@@ -220,7 +220,16 @@ export function generateAgentContext(projectRoot = process.cwd(), packageRoot =
|
|
|
220
220
|
// used by skills/init/scripts/init.sh (this repo's own board-scaffolding
|
|
221
221
|
// flow, where node is guaranteed available). The published npm CLI path
|
|
222
222
|
// (lib/commands/init.js) imports generateAgentContext() directly instead.
|
|
223
|
-
|
|
223
|
+
//
|
|
224
|
+
// process.argv[1] is compared via realpath, not as a raw string: Node
|
|
225
|
+
// resolves import.meta.url through symlinks when loading an ES module, but
|
|
226
|
+
// leaves process.argv[1] exactly as the shell passed it. On macOS, TMPDIR
|
|
227
|
+
// and /tmp are themselves symlinks into /private, so any invocation from a
|
|
228
|
+
// path under either (a very ordinary occurrence — every consumer install
|
|
229
|
+
// under a symlinked, mapped, or `npm link`-ed directory hits this) made the
|
|
230
|
+
// two sides disagree and silently skipped generation with no error.
|
|
231
|
+
const invokedPath = process.argv[1] ? realpathSync(process.argv[1]) : null;
|
|
232
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
224
233
|
const projectRoot = process.argv[2] || process.cwd();
|
|
225
234
|
const result = generateAgentContext(projectRoot);
|
|
226
235
|
if (result.skipped) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/generate-copilot-instructions.js — .github/copilot-instructions.md generation
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for scaffolding the Copilot routing-instructions file from
|
|
6
|
+
* templates/copilot-instructions.md.tpl (extracted from lib/commands/init.js per E46_S03_T01).
|
|
7
|
+
* Used by:
|
|
8
|
+
* - scripts/postinstall.js (runs unconditionally, non-interactively, on every `npm install`)
|
|
9
|
+
* - lib/commands/init.js (the interactive `jenga init` CLI wizard, using the user's chosen
|
|
10
|
+
* skillsPath — may differ from postinstall's default)
|
|
11
|
+
*
|
|
12
|
+
* Root-cause context: `.github/copilot-instructions.md` teaches Copilot the `/skill-name` →
|
|
13
|
+
* `.agents/skills/<name>/SKILL.md` routing convention. It was previously written only by the
|
|
14
|
+
* `jenga init` CLI wizard, never by postinstall.js — so a consumer whose very first action was
|
|
15
|
+
* typing `/init` inside Copilot (before ever running `jenga init`) got no routing at all.
|
|
16
|
+
* Extracting this into a shared, parameterized function lets postinstall.js bootstrap a working
|
|
17
|
+
* routing file immediately, with `jenga init` free to refine it later using the user's actual
|
|
18
|
+
* chosen skills path.
|
|
19
|
+
*
|
|
20
|
+
* Collision behaviour (preserved verbatim from the original inline implementation — do not
|
|
21
|
+
* rewrite this algorithm, only relocate it):
|
|
22
|
+
* - If `.github/copilot-instructions.md` does not exist, write the rendered template in full.
|
|
23
|
+
* - If it exists, replace only the content between `<!-- JENGA:START -->` and
|
|
24
|
+
* `<!-- JENGA:END -->`, preserving everything outside the markers.
|
|
25
|
+
* - If it exists but has no JENGA markers, append the block to the end of the file.
|
|
26
|
+
* This makes repeat runs (postinstall run twice, or postinstall followed by `jenga init`)
|
|
27
|
+
* idempotent: the JENGA block is never duplicated and content outside the markers is never
|
|
28
|
+
* touched.
|
|
29
|
+
*
|
|
30
|
+
* ESM, Node built-ins only — mirrors lib/generate-agent-context.js and lib/mirror.js.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
34
|
+
import { join, dirname } from "path";
|
|
35
|
+
import { fileURLToPath } from "url";
|
|
36
|
+
|
|
37
|
+
// This file lives at <package>/lib/generate-copilot-instructions.js — one level up is the
|
|
38
|
+
// installed jenga-agent package root, which holds templates/.
|
|
39
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
40
|
+
const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
|
|
41
|
+
|
|
42
|
+
const START_MARKER = "<!-- JENGA:START -->";
|
|
43
|
+
const END_MARKER = "<!-- JENGA:END -->";
|
|
44
|
+
|
|
45
|
+
function resolveTemplatePath(projectRoot, packageRoot) {
|
|
46
|
+
const candidates = [
|
|
47
|
+
join(packageRoot, "templates", "copilot-instructions.md.tpl"),
|
|
48
|
+
join(projectRoot, "templates", "copilot-instructions.md.tpl"),
|
|
49
|
+
];
|
|
50
|
+
return candidates.find(existsSync);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build the {{SKILL_LIST}} markdown block by scanning `skillsDir` for subdirectories
|
|
55
|
+
* containing a SKILL.md, extracting each one's `description:` frontmatter line.
|
|
56
|
+
*/
|
|
57
|
+
function buildSkillList(skillsDir) {
|
|
58
|
+
if (!skillsDir || !existsSync(skillsDir)) return "_No skills found._";
|
|
59
|
+
|
|
60
|
+
const skillLines = [];
|
|
61
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
62
|
+
if (!entry.isDirectory()) continue;
|
|
63
|
+
const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
|
|
64
|
+
let description = "";
|
|
65
|
+
if (existsSync(skillMdPath)) {
|
|
66
|
+
const content = readFileSync(skillMdPath, "utf8");
|
|
67
|
+
const match = content.match(/^description:\s*(.+)$/m);
|
|
68
|
+
if (match) description = match[1].trim();
|
|
69
|
+
}
|
|
70
|
+
skillLines.push(`- **${entry.name}**${description ? `: ${description}` : ""}`);
|
|
71
|
+
}
|
|
72
|
+
return skillLines.length > 0 ? skillLines.join("\n") : "_No skills found._";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Replace only the JENGA:START..JENGA:END block in an existing copilot-instructions.md with the
|
|
77
|
+
* freshly rendered one, preserving everything outside the markers. If no existing file, write
|
|
78
|
+
* the rendered template in full. If the existing file has no markers, append the block.
|
|
79
|
+
*/
|
|
80
|
+
function writeCopilotInstructions(path, rendered) {
|
|
81
|
+
if (!existsSync(path)) {
|
|
82
|
+
writeFileSync(path, rendered, "utf8");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const existing = readFileSync(path, "utf8");
|
|
87
|
+
const startIdx = existing.indexOf(START_MARKER);
|
|
88
|
+
const endIdx = existing.indexOf(END_MARKER);
|
|
89
|
+
|
|
90
|
+
const renderedStart = rendered.indexOf(START_MARKER);
|
|
91
|
+
const renderedEnd = rendered.indexOf(END_MARKER);
|
|
92
|
+
const newBlock = rendered.slice(renderedStart, renderedEnd + END_MARKER.length);
|
|
93
|
+
|
|
94
|
+
let updated;
|
|
95
|
+
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
96
|
+
updated = existing.slice(0, startIdx) + newBlock + existing.slice(endIdx + END_MARKER.length);
|
|
97
|
+
} else {
|
|
98
|
+
// No existing markers — append the block.
|
|
99
|
+
updated = existing + (existing.endsWith("\n") ? "" : "\n") + newBlock + "\n";
|
|
100
|
+
}
|
|
101
|
+
writeFileSync(path, updated, "utf8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate (or idempotently refresh) `.github/copilot-instructions.md` at projectRoot from the
|
|
106
|
+
* shared template.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} projectRoot - project root directory (default: cwd)
|
|
109
|
+
* @param {string} packageRoot - installed jenga-agent package root (default: derived from this
|
|
110
|
+
* file's own location)
|
|
111
|
+
* @param {string} skillsDir - path to the skills directory, resolved relative to projectRoot if
|
|
112
|
+
* not already absolute (e.g. ".agents/skills" from postinstall, or the user's chosen
|
|
113
|
+
* skillsPath from the `jenga init` wizard)
|
|
114
|
+
* @returns {{written: boolean, path?: string, skipped?: boolean}}
|
|
115
|
+
*/
|
|
116
|
+
export function generateCopilotInstructions(
|
|
117
|
+
projectRoot = process.cwd(),
|
|
118
|
+
packageRoot = DEFAULT_PACKAGE_ROOT,
|
|
119
|
+
skillsDir
|
|
120
|
+
) {
|
|
121
|
+
const tplPath = resolveTemplatePath(projectRoot, packageRoot);
|
|
122
|
+
if (!tplPath) {
|
|
123
|
+
return { written: false, skipped: true };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const tpl = readFileSync(tplPath, "utf8");
|
|
127
|
+
|
|
128
|
+
const resolvedSkillsDir = skillsDir
|
|
129
|
+
? (skillsDir.startsWith("/") ? skillsDir : join(projectRoot, skillsDir))
|
|
130
|
+
: undefined;
|
|
131
|
+
const skillList = buildSkillList(resolvedSkillsDir);
|
|
132
|
+
const rendered = tpl.replace("{{SKILL_LIST}}", skillList);
|
|
133
|
+
|
|
134
|
+
const githubDir = join(projectRoot, ".github");
|
|
135
|
+
const copilotInstructionsPath = join(githubDir, "copilot-instructions.md");
|
|
136
|
+
|
|
137
|
+
if (!existsSync(githubDir)) mkdirSync(githubDir, { recursive: true });
|
|
138
|
+
|
|
139
|
+
writeCopilotInstructions(copilotInstructionsPath, rendered);
|
|
140
|
+
|
|
141
|
+
return { written: true, path: copilotInstructionsPath };
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jenga-ai/agent",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "Structured multi-agent development workflow for AI coding agents — scrum board, role-bounded scrum master / developer / tester agents, and 30+ slash-command skills. Works with Claude Code, GitHub Copilot, and Codex.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"postinstall": "node scripts/postinstall.js",
|
|
17
|
+
"test": "bats tests/*.bats",
|
|
17
18
|
"validate:npm-metadata": "bash scripts/validate_npm_metadata.sh",
|
|
18
19
|
"ui:dev": "npm run ui:dev --prefix project/app",
|
|
19
20
|
"ui:build": "npm run ui:build --prefix project/app",
|
|
@@ -74,5 +75,8 @@
|
|
|
74
75
|
},
|
|
75
76
|
"comments": {
|
|
76
77
|
"audit": "sharp <0.35.0 and adm-zip <0.6.0 are transitive deps of @huggingface/transformers with no upstream fix available. Not exploitable in this context: only text feature-extraction pipeline is used — no image processing or ZIP handling at application boundary. Review when @huggingface/transformers ships a patched release."
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"bats": "^1.13.0"
|
|
77
81
|
}
|
|
78
82
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* postinstall.js —
|
|
3
|
+
* postinstall.js — Jenga AI consumer installation hook
|
|
4
4
|
*
|
|
5
5
|
* Runs automatically when a consumer project installs jenga-agent via npm.
|
|
6
6
|
* Copies the discovery-bound dirs — `skills/` and `agents/` — into BOTH
|
|
@@ -32,6 +32,7 @@ import path from 'node:path';
|
|
|
32
32
|
import { fileURLToPath } from 'node:url';
|
|
33
33
|
|
|
34
34
|
import { mirror } from '../lib/mirror.js';
|
|
35
|
+
import { generateCopilotInstructions } from '../lib/generate-copilot-instructions.js';
|
|
35
36
|
|
|
36
37
|
// ESM equivalent of __dirname
|
|
37
38
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -65,7 +66,7 @@ function main() {
|
|
|
65
66
|
|
|
66
67
|
// Avoid running during development (when INIT_CWD === packageRoot itself)
|
|
67
68
|
if (path.resolve(consumerRoot) === path.resolve(packageRoot)) {
|
|
68
|
-
console.log('\n ℹ
|
|
69
|
+
console.log('\n ℹ Jenga AI postinstall: running inside the package itself — skipping copy.\n');
|
|
69
70
|
return;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -93,7 +94,7 @@ function main() {
|
|
|
93
94
|
const shouldCopy = isFirstInstall || isUpgrade;
|
|
94
95
|
|
|
95
96
|
console.log('\n╔══════════════════════════════════════════════════════╗');
|
|
96
|
-
console.log('║
|
|
97
|
+
console.log('║ Jenga AI — postinstall hook ║');
|
|
97
98
|
console.log('╚══════════════════════════════════════════════════════╝\n');
|
|
98
99
|
console.log(` Package version : ${packageVersion}`);
|
|
99
100
|
console.log(` Installed version: ${installedVersion ?? '(none — first install)'}`);
|
|
@@ -153,6 +154,26 @@ function main() {
|
|
|
153
154
|
totalSkipped += result.skipped.length;
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// Bootstrap .github/copilot-instructions.md unconditionally, right after the mirror step —
|
|
158
|
+
// no interactive prompts, since postinstall runs unattended during `npm install`. Without
|
|
159
|
+
// this, a consumer whose very first action is a Copilot slash command (before ever running
|
|
160
|
+
// the separate `jenga init` CLI wizard) gets no `/skill-name` routing instructions at all
|
|
161
|
+
// (E46_S03_T01). `.agents/skills` is passed as the skills directory because that's the
|
|
162
|
+
// directory the mirror step above just populated, regardless of which agentTarget the user
|
|
163
|
+
// eventually picks in `jenga init`. The shared generator's idempotent marker-replace logic
|
|
164
|
+
// (lib/generate-copilot-instructions.js) means a later `jenga init` run — using the user's
|
|
165
|
+
// actual chosen skillsPath — safely refines this file without duplicating the JENGA block.
|
|
166
|
+
try {
|
|
167
|
+
const result = generateCopilotInstructions(consumerRoot, packageRoot, '.agents/skills');
|
|
168
|
+
if (result.skipped) {
|
|
169
|
+
console.log(' ⚠ templates/copilot-instructions.md.tpl not found — skipped .github/copilot-instructions.md bootstrap');
|
|
170
|
+
} else {
|
|
171
|
+
console.log(' ✓ .github/copilot-instructions.md bootstrapped');
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.log(` ⚠ Could not bootstrap .github/copilot-instructions.md — ${e.message}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
156
177
|
// Write .jenga-version to record the installed version at consumer root
|
|
157
178
|
fs.writeFileSync(versionFile, packageVersion + '\n', 'utf8');
|
|
158
179
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# (CLAUDE.md), this is a script rather than an agent instruction so that
|
|
8
8
|
# future drift in package.json's publish-facing fields (e.g. keywords
|
|
9
9
|
# accidentally cleared, or repository.url silently re-pointed at the
|
|
10
|
-
# private samwelmunga/
|
|
10
|
+
# private samwelmunga/Jenga AI repo instead of the public jenga-npm
|
|
11
11
|
# mirror) is caught deterministically instead of relying on an agent
|
|
12
12
|
# noticing during review.
|
|
13
13
|
#
|
|
@@ -109,7 +109,7 @@ if (pkg.repository != null) {
|
|
|
109
109
|
if (!repoUrl || repoUrl.trim().length === 0) {
|
|
110
110
|
fail("repository is present but has no resolvable url.");
|
|
111
111
|
} else if (!repoUrl.includes(PUBLIC_REPO_MARKER)) {
|
|
112
|
-
fail("repository.url (\"" + repoUrl + "\") does not point at the public " + PUBLIC_REPO_MARKER + " repo — it must not reference a private repo (e.g. samwelmunga/
|
|
112
|
+
fail("repository.url (\"" + repoUrl + "\") does not point at the public " + PUBLIC_REPO_MARKER + " repo — it must not reference a private repo (e.g. samwelmunga/Jenga AI) or any other location.");
|
|
113
113
|
} else {
|
|
114
114
|
pass("repository.url points at the public " + PUBLIC_REPO_MARKER + " repo.");
|
|
115
115
|
}
|
|
@@ -6,7 +6,7 @@ This document is the canonical reference for the `jenga.config.json` file writte
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
`jenga.config.json` lives at the root of a consuming project and tracks which version of the
|
|
9
|
+
`jenga.config.json` lives at the root of a consuming project and tracks which version of the Jenga AI framework is currently installed there, where the framework files were placed, and when the last distribution occurred. It is read by the distribution script on subsequent runs to determine the target directory and detect whether an upgrade is needed.
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -40,17 +40,17 @@ This document is the canonical reference for the `jenga.config.json` file writte
|
|
|
40
40
|
|---|---|---|---|---|
|
|
41
41
|
| `project_name` | string | yes | — | Human-readable identifier for the consuming project. Must match the `name` field of the corresponding entry in the monorepo's `distribute.config.json`. |
|
|
42
42
|
| `target_dir` | string | yes | `.agents` | The directory under the project root where framework files are copied. `distribute-changes.sh` reads this field to resolve the destination path on every run. Change this only if the consuming project uses a non-standard layout. |
|
|
43
|
-
| `version` | string | yes | — | The
|
|
43
|
+
| `version` | string | yes | — | The Jenga AI semantic version currently installed in this project (e.g. `"2.3.1"`). Compared against the `version` field in the monorepo's `package.json` to determine whether an upgrade is required. |
|
|
44
44
|
| `updated_at` | string (ISO 8601 date) | yes | — | Date of the last successful distribution, in `YYYY-MM-DD` format. Does **not** include a time component. |
|
|
45
45
|
| `last_distributed` | string (ISO 8601 datetime) | yes | — | Full UTC timestamp of the last successful distribution, in `YYYY-MM-DDTHH:MM:SSZ` format. Provides more precision than `updated_at` and is useful for audit and ordering purposes. |
|
|
46
46
|
| `source` | string | yes | `"private"` | Distribution channel. Always `"private"` for projects that receive updates via the filesystem distribution mechanism. Distinguishes these projects from any future npm-installed consumers. Do not change this value manually. |
|
|
47
|
-
| `project_files_visibility` | string (enum) | no | `"visible"` | How
|
|
47
|
+
| `project_files_visibility` | string (enum) | no | `"visible"` | How Jenga AI's own working files appear in the consuming project. Exactly one of `visible` or `ignored` — no other value is accepted. Written by `/init`, not by distribution. See [Project files visibility](#project-files-visibility) below. |
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
51
|
## Project files visibility
|
|
52
52
|
|
|
53
|
-
`project_files_visibility` controls how
|
|
53
|
+
`project_files_visibility` controls how Jenga AI's own working files — the `project/` tree containing the scrum board, `todo.md`, `queue/`, `rapports/`, and `logs/` — appear in a consuming project.
|
|
54
54
|
|
|
55
55
|
This is distinct from `target_dir`. `target_dir` governs where the **distributed framework files** (skill and agent definitions) land; `project_files_visibility` governs the **working tree** that accumulates as the framework is used.
|
|
56
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: distribute
|
|
3
|
-
description: Distribute
|
|
3
|
+
description: Distribute Jenga AI framework files from this private monorepo to one or more consuming projects via the local filesystem. Manages release type selection, version bumping, dry-run preview, per-target file copy, and a post-distribution git commit.
|
|
4
4
|
keywords:
|
|
5
5
|
- distribute
|
|
6
6
|
- private distribution
|
|
@@ -16,7 +16,7 @@ examples:
|
|
|
16
16
|
|
|
17
17
|
# Distribute
|
|
18
18
|
|
|
19
|
-
Copies
|
|
19
|
+
Copies Jenga AI framework files from this monorepo to all active consuming projects registered in `distribute.config.json`. Manages the full version lifecycle: release type selection, `package.json` version bump, dry-run preview, per-target file copy, and a final git commit of the version bump.
|
|
20
20
|
|
|
21
21
|
Distinct from `/self-sync` (which mirrors root → in-repo `.claude/.agents/`) and `/mirror-public` (which syncs to the public GitHub repo). Do not call either of those skills from within this flow.
|
|
22
22
|
|
package/skills/doc/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ After target resolution, all generation must operate on a synthesis context obje
|
|
|
39
39
|
```yaml
|
|
40
40
|
target_path: README.md
|
|
41
41
|
objective: project overview
|
|
42
|
-
project_name:
|
|
42
|
+
project_name: Jenga AI
|
|
43
43
|
project_description: Structured multi-agent development workflow
|
|
44
44
|
features: []
|
|
45
45
|
getting_started: []
|
package/skills/init/SKILL.md
CHANGED
|
@@ -67,11 +67,11 @@ changes nothing on disk, so it is the only safe default.
|
|
|
67
67
|
Never treat `existing-codebase` as if it were `empty`, and never skip straight to step 2
|
|
68
68
|
on that verdict without the user (or the non-interactive default) choosing to.
|
|
69
69
|
|
|
70
|
-
### 2. Ask how
|
|
70
|
+
### 2. Ask how Jenga AI's working files should appear
|
|
71
71
|
|
|
72
72
|
Ask the user this question, verbatim, before running any script:
|
|
73
73
|
|
|
74
|
-
How should
|
|
74
|
+
How should Jenga AI's own working files (project/ — the scrum board, todo.md,
|
|
75
75
|
queue/, rapports/, and logs/) appear in this project?
|
|
76
76
|
1. Visible — keep them at `project/`, tracked and visible in directory listings
|
|
77
77
|
2. Ignored — keep them at `project/` but add them to `.gitignore` so they are never committed
|
|
@@ -96,10 +96,26 @@ of the mechanical work.
|
|
|
96
96
|
|
|
97
97
|
### 3. Run the scaffold script
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
`init.sh` is not guaranteed to live at a single fixed path: in a project that
|
|
100
|
+
installed Jenga via npm, it was mirrored to `.claude/skills/init/scripts/`
|
|
101
|
+
(Claude Code) and `.agents/skills/init/scripts/` (Copilot/other agents) by
|
|
102
|
+
`postinstall.js`, and neither of those exists yet in this framework's own
|
|
103
|
+
source checkout, where it lives at the bare `skills/init/scripts/` path
|
|
104
|
+
instead. This step runs before `CLAUDE.md`/`AGENTS.md` exist, so it cannot
|
|
105
|
+
rely on either file's routing instructions to resolve the path — it must
|
|
106
|
+
locate its own script directly. Execute the init script from the project
|
|
107
|
+
root, passing the choice from step 2:
|
|
100
108
|
|
|
101
109
|
```bash
|
|
102
|
-
|
|
110
|
+
INIT_SCRIPT=""
|
|
111
|
+
for candidate in .claude/skills/init/scripts/init.sh .agents/skills/init/scripts/init.sh skills/init/scripts/init.sh; do
|
|
112
|
+
[[ -f "$candidate" ]] && { INIT_SCRIPT="$candidate"; break; }
|
|
113
|
+
done
|
|
114
|
+
if [[ -z "$INIT_SCRIPT" ]]; then
|
|
115
|
+
echo "Error: could not locate init.sh under .claude/skills/, .agents/skills/, or skills/" >&2
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
chmod +x "$INIT_SCRIPT" && "$INIT_SCRIPT" --visibility <visible|ignored>
|
|
103
119
|
```
|
|
104
120
|
|
|
105
121
|
Omitting `--visibility` falls back to the `JENGA_PROJECT_FILES_VISIBILITY`
|
|
@@ -41,7 +41,7 @@ usage() {
|
|
|
41
41
|
exit 1
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
# Every
|
|
44
|
+
# Every Jenga AI working file named in E31_S05 — the scrum board, todo.md,
|
|
45
45
|
# queue/, rapports/ and logs/ — nests under this single root, so one entry
|
|
46
46
|
# covers them all. `ignored` consumes this list.
|
|
47
47
|
JENGA_WORKING_PATHS=("project")
|
|
@@ -5,6 +5,23 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
5
5
|
ASSETS_DIR="$SCRIPT_DIR/../assets"
|
|
6
6
|
VISIBILITY_SCRIPT="$SCRIPT_DIR/apply-project-visibility.sh"
|
|
7
7
|
|
|
8
|
+
# ─── Resolve the package root that owns templates/ and lib/ ──────────────────
|
|
9
|
+
# postinstall.js mirrors only skills/ and agents/ into .claude/ and .agents/ —
|
|
10
|
+
# templates/ and lib/ are never copied there, so a script running from a
|
|
11
|
+
# mirrored copy (.claude/skills/init/scripts/ or .agents/skills/init/scripts/)
|
|
12
|
+
# cannot reach its siblings via a fixed ../../../ climb the way it can in this
|
|
13
|
+
# monorepo checkout, where init.sh actually lives at skills/init/scripts/ with
|
|
14
|
+
# templates/ and lib/ three levels up. Consumers instead have them inside the
|
|
15
|
+
# installed npm package.
|
|
16
|
+
if [[ -d "$SCRIPT_DIR/../../../templates" ]]; then
|
|
17
|
+
PKG_ROOT="$SCRIPT_DIR/../../.."
|
|
18
|
+
elif [[ -d "$PWD/node_modules/@jenga-ai/agent/templates" ]]; then
|
|
19
|
+
PKG_ROOT="$PWD/node_modules/@jenga-ai/agent"
|
|
20
|
+
else
|
|
21
|
+
echo "Error: could not locate the jenga-agent package root (templates/ not found via monorepo checkout or node_modules/@jenga-ai/agent)." >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
8
25
|
# ─── 0. Resolve project_files_visibility ─────────────────────────────────────
|
|
9
26
|
# Defaults to `visible` — the only value that touches nothing on disk — so an
|
|
10
27
|
# unattended run can never silently relocate directories or edit .gitignore.
|
|
@@ -65,7 +82,7 @@ cp "$ASSETS_DIR/strategy_stub_template.md" docs/STRATEGY.md
|
|
|
65
82
|
|
|
66
83
|
# ─── 10. Create CHANGELOG.md ──────────────────────────────────────────────────
|
|
67
84
|
echo "→ Creating CHANGELOG.md from template..."
|
|
68
|
-
cp "$
|
|
85
|
+
cp "$PKG_ROOT/templates/CHANGELOG_TEMPLATE.md" CHANGELOG.md
|
|
69
86
|
|
|
70
87
|
# ─── 11. Apply project_files_visibility ──────────────────────────────────────
|
|
71
88
|
# Runs before the commit so the .gitignore entry (ignored) is captured in the
|
|
@@ -79,7 +96,7 @@ bash "$VISIBILITY_SCRIPT" "$VISIBILITY" "$PWD"
|
|
|
79
96
|
# lib/generate-agent-context.js (shared with the published `jenga init` CLI).
|
|
80
97
|
echo "→ Generating CLAUDE.md / AGENTS.md..."
|
|
81
98
|
if command -v node >/dev/null 2>&1; then
|
|
82
|
-
node "$
|
|
99
|
+
node "$PKG_ROOT/lib/generate-agent-context.js" "$PWD"
|
|
83
100
|
else
|
|
84
101
|
echo " Warning: node not found — skipped CLAUDE.md/AGENTS.md generation." >&2
|
|
85
102
|
fi
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# npm_pipeline.sh — Execute the npm publish pipeline for the
|
|
2
|
+
# npm_pipeline.sh — Execute the npm publish pipeline for the Jenga AI repo.
|
|
3
3
|
#
|
|
4
|
-
# Runs from the
|
|
4
|
+
# Runs from the Jenga AI repo root (where package.json lives) and invokes
|
|
5
5
|
# `npm publish --tag <dist_tag>` (or `--dry-run` when requested).
|
|
6
6
|
#
|
|
7
7
|
# Exit codes:
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
# itself. The epic cap — and the surfacing of anything it drops — is
|
|
46
46
|
# applied by a separate layer on top of this script's output.
|
|
47
47
|
# --include-framework Do not exclude Jenga scaffolding. Needed to point the script at the
|
|
48
|
-
#
|
|
48
|
+
# Jenga AI repo itself, where the scaffolding IS the application.
|
|
49
49
|
# --include-training-scaffolding
|
|
50
50
|
# Do not exclude .training/ job-template scaffolding. Needed to point the
|
|
51
|
-
# script at
|
|
51
|
+
# script at Jenga AI's own /train subsystem, or at a project that
|
|
52
52
|
# genuinely wants job templates catalogued as a candidate.
|
|
53
53
|
# --with-dependencies Additionally compute the "cohesion" signal by calling
|
|
54
54
|
# detect-dependencies.sh once per candidate. Off by default: it is an
|