@jenga-ai/agent 1.2.0 → 1.2.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/README.md +26 -0
- package/lib/commands/init.js +19 -0
- package/lib/config-schema.js +4 -4
- package/lib/generate-agent-context.js +233 -0
- package/package.json +1 -1
- package/skills/distribute/scripts/distribute-changes.sh +2 -2
- package/skills/init/scripts/init.sh +12 -1
- package/skills/reconcile/scripts/detect-unlinked-code.sh +4 -2
- package/templates/agent-context.md.tpl +46 -0
package/README.md
CHANGED
|
@@ -2,8 +2,34 @@
|
|
|
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
|
|
|
5
|
+
[](https://www.npmjs.com/package/@jenga-ai/agent)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install @jenga-ai/agent
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What You Get
|
|
13
|
+
|
|
14
|
+
- **Three specialised agents** — Scrum Master, Developer, Tester — each with a distinct role and no self-graded work
|
|
15
|
+
- **A persistent scrum board** — Epics, Stories, and Tasks tracked as Markdown files with structured frontmatter, surviving every session boundary
|
|
16
|
+
- **28 slash-command skills** — from planning (`/pi-plan`, `/todo`) to execution (`/do`, `/dooo`) to review (`/status`, `/reconcile`)
|
|
17
|
+
- **An event-driven trigger queue** — async handoffs between agents with a full audit trail in `project/logs/events.json`
|
|
18
|
+
- **Isolated git worktrees per task** — the Developer never works directly on your main branch
|
|
19
|
+
- **Works with any AI agent or IDE** — Claude Code, GitHub Copilot, Warp, and Codex CLI are all supported today
|
|
20
|
+
|
|
5
21
|
> 📖 **Full reference:** [project/.wiki/documentation.md](project/.wiki/documentation.md) | [Intro Guide](project/.wiki/intro-guide.md)
|
|
6
22
|
|
|
23
|
+
## Platform Support
|
|
24
|
+
|
|
25
|
+
| Agent | Skills & Agents | Root Context File |
|
|
26
|
+
|---|---|---|
|
|
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` — generated by `/init` today |
|
|
29
|
+
| **Codex** | `.agents/` — mirrored automatically on install | `AGENTS.md` — generated by `/init` today |
|
|
30
|
+
|
|
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
|
+
|
|
7
33
|
---
|
|
8
34
|
|
|
9
35
|
## The Problem It Solves
|
package/lib/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ 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
|
+
import { generateAgentContext } from "../generate-agent-context.js";
|
|
7
8
|
|
|
8
9
|
const CONFIG_FILE = "jenga.cli.json";
|
|
9
10
|
|
|
@@ -198,6 +199,24 @@ export async function runInit(args, projectRoot = process.cwd()) {
|
|
|
198
199
|
console.warn(`Warning: Could not write .github/copilot-instructions.md — ${e.message}`);
|
|
199
200
|
}
|
|
200
201
|
|
|
202
|
+
// Generate CLAUDE.md / AGENTS.md from templates/agent-context.md.tpl.
|
|
203
|
+
// Unconditional — never gated on agentTarget (E41_S04): every agent
|
|
204
|
+
// benefits from these files being cheap to write, and users switch
|
|
205
|
+
// tools mid-project. Applies the J- collision rule and idempotent
|
|
206
|
+
// managed-block updates; see lib/generate-agent-context.js.
|
|
207
|
+
try {
|
|
208
|
+
const result = generateAgentContext(projectRoot, PACKAGE_ROOT);
|
|
209
|
+
if (result.skipped) {
|
|
210
|
+
console.warn("Warning: templates/agent-context.md.tpl not found — skipped CLAUDE.md/AGENTS.md generation.");
|
|
211
|
+
} else {
|
|
212
|
+
for (const { filename, mode } of result.written) {
|
|
213
|
+
console.log(`✓ ${filename} written (${mode})`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.warn(`Warning: Could not write CLAUDE.md/AGENTS.md — ${e.message}`);
|
|
218
|
+
}
|
|
219
|
+
|
|
201
220
|
console.log('\nRun `jenga start` to start the router.');
|
|
202
221
|
|
|
203
222
|
return config;
|
package/lib/config-schema.js
CHANGED
|
@@ -18,8 +18,8 @@ export const configSchema = {
|
|
|
18
18
|
sessionTimeout: { type: "number", minimum: 0 },
|
|
19
19
|
agentTarget: {
|
|
20
20
|
oneOf: [
|
|
21
|
-
{ type: "string", enum: ["claude", "copilot", "custom"] },
|
|
22
|
-
{ type: "array", items: { type: "string", enum: ["claude", "copilot", "custom"] }, minItems: 1 }
|
|
21
|
+
{ type: "string", enum: ["claude", "copilot", "custom", "codex"] },
|
|
22
|
+
{ type: "array", items: { type: "string", enum: ["claude", "copilot", "custom", "codex"] }, minItems: 1 }
|
|
23
23
|
]
|
|
24
24
|
}
|
|
25
25
|
},
|
|
@@ -45,10 +45,10 @@ export function validateConfig(config) {
|
|
|
45
45
|
if (typeof config.sessionTimeout !== "number" || config.sessionTimeout < 0) {
|
|
46
46
|
return { valid: false, error: "sessionTimeout must be a non-negative number" };
|
|
47
47
|
}
|
|
48
|
-
const validTargets = ["claude", "copilot", "custom"];
|
|
48
|
+
const validTargets = ["claude", "copilot", "custom", "codex"];
|
|
49
49
|
const targets = Array.isArray(config.agentTarget) ? config.agentTarget : [config.agentTarget];
|
|
50
50
|
if (targets.length === 0 || !targets.every(t => validTargets.includes(t))) {
|
|
51
|
-
return { valid: false, error: 'agentTarget must be one of: "claude", "copilot", "custom" (or an array of those values)' };
|
|
51
|
+
return { valid: false, error: 'agentTarget must be one of: "claude", "copilot", "custom", "codex" (or an array of those values)' };
|
|
52
52
|
}
|
|
53
53
|
return { valid: true };
|
|
54
54
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/generate-agent-context.js — CLAUDE.md / AGENTS.md generation
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for scaffolding the two root-level agent-context
|
|
6
|
+
* files from templates/agent-context.md.tpl (E41_S04_T02). Used by:
|
|
7
|
+
* - lib/commands/init.js (published `jenga init` CLI)
|
|
8
|
+
* - skills/init/scripts/init.sh (this repo's own board-scaffolding flow,
|
|
9
|
+
* invoked via `node` — see the CLI guard
|
|
10
|
+
* at the bottom of this file)
|
|
11
|
+
*
|
|
12
|
+
* Design constraints (per E41_S04 story — do not re-litigate):
|
|
13
|
+
* - CLAUDE.md and AGENTS.md are written unconditionally, never gated on
|
|
14
|
+
* `agentTarget`.
|
|
15
|
+
* - Both are written as real files — never a symlink.
|
|
16
|
+
* - No AGENT.md (singular) is ever scaffolded.
|
|
17
|
+
* - Collision rule: if the target file doesn't exist, write it directly.
|
|
18
|
+
* If it already exists, Jenga's copy goes to `J-<NAME>.md` instead, and
|
|
19
|
+
* a short, clearly-marked reference is inserted near the top of the
|
|
20
|
+
* existing file. The J- prefix always belongs to the incoming Jenga
|
|
21
|
+
* file; the user's file keeps its name and content.
|
|
22
|
+
* - Idempotent across repeat runs: managed blocks update in place, the
|
|
23
|
+
* reference line is never duplicated, and a J- file is updated in place
|
|
24
|
+
* rather than re-triggering collision handling (no J-J-CLAUDE.md).
|
|
25
|
+
*
|
|
26
|
+
* ESM, Node built-ins only — mirrors lib/mirror.js and lib/inject-settings.js.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
|
|
30
|
+
import { join, dirname } from "path";
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
|
|
33
|
+
// This file lives at <package>/lib/generate-agent-context.js — one level up
|
|
34
|
+
// is the installed jenga-agent package root, which holds templates/.
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const DEFAULT_PACKAGE_ROOT = join(__dirname, "..");
|
|
37
|
+
|
|
38
|
+
const CONTENT_START = "<!-- JENGA:START -->";
|
|
39
|
+
const CONTENT_END = "<!-- JENGA:END -->";
|
|
40
|
+
const REF_START = "<!-- JENGA:REF:START -->";
|
|
41
|
+
const REF_END = "<!-- JENGA:REF:END -->";
|
|
42
|
+
|
|
43
|
+
// One entry per generated file. skillDiscoveryPath reflects the reading
|
|
44
|
+
// agent's own discovery convention: Claude Code reads .claude/skills/,
|
|
45
|
+
// every other agent (Codex, generic AGENTS.md consumers, ...) reads
|
|
46
|
+
// .agents/skills/.
|
|
47
|
+
const TARGETS = [
|
|
48
|
+
{ filename: "CLAUDE.md", skillDiscoveryPath: ".claude/skills/" },
|
|
49
|
+
{ filename: "AGENTS.md", skillDiscoveryPath: ".agents/skills/" },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build the rendered {{SKILL_LIST}} block by scanning the first existing
|
|
54
|
+
* skills directory among .claude/skills/ and .agents/skills/ — mirrored
|
|
55
|
+
* copies hold identical content, so either is representative. Mirrors the
|
|
56
|
+
* skill-list extraction already used for the Copilot template in
|
|
57
|
+
* lib/commands/init.js.
|
|
58
|
+
*/
|
|
59
|
+
function buildSkillList(projectRoot) {
|
|
60
|
+
const candidates = [".claude/skills", ".agents/skills"].map((p) => join(projectRoot, p));
|
|
61
|
+
const skillsDir = candidates.find(existsSync);
|
|
62
|
+
if (!skillsDir) return "_No skills found._";
|
|
63
|
+
|
|
64
|
+
const skillLines = [];
|
|
65
|
+
for (const entry of readdirSync(skillsDir, { withFileTypes: true })) {
|
|
66
|
+
if (!entry.isDirectory()) continue;
|
|
67
|
+
const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
|
|
68
|
+
let description = "";
|
|
69
|
+
if (existsSync(skillMdPath)) {
|
|
70
|
+
const content = readFileSync(skillMdPath, "utf8");
|
|
71
|
+
const match = content.match(/^description:\s*(.+)$/m);
|
|
72
|
+
if (match) description = match[1].trim();
|
|
73
|
+
}
|
|
74
|
+
skillLines.push(`- **${entry.name}**${description ? `: ${description}` : ""}`);
|
|
75
|
+
}
|
|
76
|
+
return skillLines.length > 0 ? skillLines.join("\n") : "_No skills found._";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveTemplatePath(projectRoot, packageRoot) {
|
|
80
|
+
const candidates = [
|
|
81
|
+
join(packageRoot, "templates", "agent-context.md.tpl"),
|
|
82
|
+
join(projectRoot, "templates", "agent-context.md.tpl"),
|
|
83
|
+
];
|
|
84
|
+
return candidates.find(existsSync);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Replace only the JENGA:START..JENGA:END block in `existingContent` with
|
|
89
|
+
* `newBlock` (which itself includes the marker comments), preserving
|
|
90
|
+
* everything outside the markers. If no markers are present, append the
|
|
91
|
+
* block. Identical technique to the Copilot managed-block replace in
|
|
92
|
+
* lib/commands/init.js.
|
|
93
|
+
*/
|
|
94
|
+
function replaceManagedBlock(existingContent, newBlock) {
|
|
95
|
+
const startIdx = existingContent.indexOf(CONTENT_START);
|
|
96
|
+
const endIdx = existingContent.indexOf(CONTENT_END);
|
|
97
|
+
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
98
|
+
return existingContent.slice(0, startIdx) + newBlock + existingContent.slice(endIdx + CONTENT_END.length);
|
|
99
|
+
}
|
|
100
|
+
return existingContent + (existingContent.endsWith("\n") ? "" : "\n") + newBlock + "\n";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Write `rendered` (full file content, including the JENGA:START/END block)
|
|
105
|
+
* to `path`. If the file doesn't exist, write it in full. If it does,
|
|
106
|
+
* replace only the managed block, preserving any content outside it (e.g.
|
|
107
|
+
* a user's own edits to the title line of a Jenga-owned file).
|
|
108
|
+
*/
|
|
109
|
+
function writeManagedFile(path, rendered) {
|
|
110
|
+
if (!existsSync(path)) {
|
|
111
|
+
writeFileSync(path, rendered, "utf8");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const existing = readFileSync(path, "utf8");
|
|
115
|
+
const renderedStart = rendered.indexOf(CONTENT_START);
|
|
116
|
+
const renderedEnd = rendered.indexOf(CONTENT_END);
|
|
117
|
+
const newBlock = rendered.slice(renderedStart, renderedEnd + CONTENT_END.length);
|
|
118
|
+
const updated = replaceManagedBlock(existing, newBlock);
|
|
119
|
+
if (updated !== existing) writeFileSync(path, updated, "utf8");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Insert (or, on repeat runs, update in place) a short Jenga-marked
|
|
124
|
+
* reference block near the very top of a pre-existing user file, pointing
|
|
125
|
+
* at the J-<NAME>.md copy. Idempotent via its own REF marker pair, kept
|
|
126
|
+
* distinct from CONTENT_START/CONTENT_END so the two replace passes never
|
|
127
|
+
* interfere with each other in the same file.
|
|
128
|
+
*/
|
|
129
|
+
function insertOrUpdateReference(path, jFilename) {
|
|
130
|
+
const refBlock =
|
|
131
|
+
`${REF_START}\n` +
|
|
132
|
+
`> **Jenga framework note:** This project also uses [Jenga](https://github.com/samwelmunga/jenga-npm) — ` +
|
|
133
|
+
`see \`${jFilename}\` for Jenga's framework-specific agent context (skills, board, workflow). ` +
|
|
134
|
+
`This file is unrelated to Jenga and was left untouched.\n` +
|
|
135
|
+
`${REF_END}`;
|
|
136
|
+
|
|
137
|
+
const existing = readFileSync(path, "utf8");
|
|
138
|
+
const startIdx = existing.indexOf(REF_START);
|
|
139
|
+
const endIdx = existing.indexOf(REF_END);
|
|
140
|
+
|
|
141
|
+
let updated;
|
|
142
|
+
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
143
|
+
updated = existing.slice(0, startIdx) + refBlock + existing.slice(endIdx + REF_END.length);
|
|
144
|
+
} else {
|
|
145
|
+
// No existing reference — insert at the very top, ahead of any content,
|
|
146
|
+
// satisfying "early / before the first major section" regardless of
|
|
147
|
+
// the existing file's structure.
|
|
148
|
+
updated = refBlock + "\n\n" + existing;
|
|
149
|
+
}
|
|
150
|
+
if (updated !== existing) writeFileSync(path, updated, "utf8");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generate CLAUDE.md and AGENTS.md at projectRoot from the shared template,
|
|
155
|
+
* applying the J- collision rule and idempotent managed-block updates.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} projectRoot - project root directory (default: cwd)
|
|
158
|
+
* @param {string} packageRoot - installed jenga-agent package root (default:
|
|
159
|
+
* derived from this file's own location)
|
|
160
|
+
* @returns {{written: Array<{filename: string, mode: string}>, skipped?: boolean}}
|
|
161
|
+
*/
|
|
162
|
+
export function generateAgentContext(projectRoot = process.cwd(), packageRoot = DEFAULT_PACKAGE_ROOT) {
|
|
163
|
+
const tplPath = resolveTemplatePath(projectRoot, packageRoot);
|
|
164
|
+
if (!tplPath) {
|
|
165
|
+
return { written: [], skipped: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const tpl = readFileSync(tplPath, "utf8");
|
|
169
|
+
const skillList = buildSkillList(projectRoot);
|
|
170
|
+
const written = [];
|
|
171
|
+
|
|
172
|
+
for (const target of TARGETS) {
|
|
173
|
+
const rendered = tpl
|
|
174
|
+
.split("{{TARGET_FILENAME}}").join(target.filename)
|
|
175
|
+
.split("{{SKILL_DISCOVERY_PATH}}").join(target.skillDiscoveryPath)
|
|
176
|
+
.split("{{SKILL_LIST}}").join(skillList);
|
|
177
|
+
|
|
178
|
+
const targetPath = join(projectRoot, target.filename);
|
|
179
|
+
const jFilename = `J-${target.filename}`;
|
|
180
|
+
const jPath = join(projectRoot, jFilename);
|
|
181
|
+
|
|
182
|
+
if (existsSync(jPath)) {
|
|
183
|
+
// A J- file can only ever have been created by Jenga — this is a
|
|
184
|
+
// repeat run after a prior collision. Update it in place; never
|
|
185
|
+
// re-derive collision handling from it (no J-J-CLAUDE.md).
|
|
186
|
+
writeManagedFile(jPath, rendered);
|
|
187
|
+
if (existsSync(targetPath)) {
|
|
188
|
+
insertOrUpdateReference(targetPath, jFilename);
|
|
189
|
+
}
|
|
190
|
+
written.push({ filename: target.filename, mode: "updated-j-file" });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (existsSync(targetPath)) {
|
|
195
|
+
const existingContent = readFileSync(targetPath, "utf8");
|
|
196
|
+
if (existingContent.includes(CONTENT_START)) {
|
|
197
|
+
// Jenga's own file from a prior *non-collision* run — update in
|
|
198
|
+
// place rather than treating it as a fresh collision.
|
|
199
|
+
writeManagedFile(targetPath, rendered);
|
|
200
|
+
written.push({ filename: target.filename, mode: "direct-update" });
|
|
201
|
+
} else {
|
|
202
|
+
// Genuine pre-existing user file — collision. Write Jenga's copy
|
|
203
|
+
// as J-<NAME>.md and insert a reference into the user's file.
|
|
204
|
+
writeFileSync(jPath, rendered, "utf8");
|
|
205
|
+
insertOrUpdateReference(targetPath, jFilename);
|
|
206
|
+
written.push({ filename: target.filename, mode: "collision-created-j-file" });
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Neither the target nor its J- copy exists — first-run, no collision.
|
|
212
|
+
writeManagedFile(targetPath, rendered);
|
|
213
|
+
written.push({ filename: target.filename, mode: "direct-write" });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return { written };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// CLI guard — allows `node lib/generate-agent-context.js [projectRoot]`,
|
|
220
|
+
// used by skills/init/scripts/init.sh (this repo's own board-scaffolding
|
|
221
|
+
// flow, where node is guaranteed available). The published npm CLI path
|
|
222
|
+
// (lib/commands/init.js) imports generateAgentContext() directly instead.
|
|
223
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
224
|
+
const projectRoot = process.argv[2] || process.cwd();
|
|
225
|
+
const result = generateAgentContext(projectRoot);
|
|
226
|
+
if (result.skipped) {
|
|
227
|
+
console.warn("Warning: agent-context.md.tpl not found — skipped CLAUDE.md/AGENTS.md generation.");
|
|
228
|
+
} else {
|
|
229
|
+
for (const { filename, mode } of result.written) {
|
|
230
|
+
console.log(`✓ ${filename} (${mode})`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# 2. Reads <project_path>/jenga.config.json for target_dir and project_name.
|
|
14
14
|
# 3. Loads per-project exclusions from <project_path>/.jenga_ignore (warn, not error).
|
|
15
15
|
# 4. Rsyncs each included item to both <target_dir>/ and .claude/ in the project.
|
|
16
|
-
# 5. Copies
|
|
16
|
+
# 5. Copies AGENTS.md, CLAUDE.md, WARP.md to the project root.
|
|
17
17
|
# 6. Writes jenga.config.json atomically (temp + mv).
|
|
18
18
|
#
|
|
19
19
|
# Exit codes:
|
|
@@ -282,7 +282,7 @@ done
|
|
|
282
282
|
# Step 6: Copy root-level docs to the consuming project root
|
|
283
283
|
# ---------------------------------------------------------------------------
|
|
284
284
|
|
|
285
|
-
ROOT_DOCS="
|
|
285
|
+
ROOT_DOCS="AGENTS.md CLAUDE.md WARP.md"
|
|
286
286
|
|
|
287
287
|
for doc in $ROOT_DOCS; do
|
|
288
288
|
src="$REPO_ROOT/$doc"
|
|
@@ -73,7 +73,18 @@ cp "$SCRIPT_DIR/../../../templates/CHANGELOG_TEMPLATE.md" CHANGELOG.md
|
|
|
73
73
|
echo "→ Applying project files visibility ($VISIBILITY)..."
|
|
74
74
|
bash "$VISIBILITY_SCRIPT" "$VISIBILITY" "$PWD"
|
|
75
75
|
|
|
76
|
-
# ─── 12.
|
|
76
|
+
# ─── 12. Generate CLAUDE.md / AGENTS.md ──────────────────────────────────────
|
|
77
|
+
# Unconditional — never gated on agentTarget (E41_S04). Applies the J-
|
|
78
|
+
# collision rule and idempotent managed-block updates; see
|
|
79
|
+
# lib/generate-agent-context.js (shared with the published `jenga init` CLI).
|
|
80
|
+
echo "→ Generating CLAUDE.md / AGENTS.md..."
|
|
81
|
+
if command -v node >/dev/null 2>&1; then
|
|
82
|
+
node "$SCRIPT_DIR/../../../lib/generate-agent-context.js" "$PWD"
|
|
83
|
+
else
|
|
84
|
+
echo " Warning: node not found — skipped CLAUDE.md/AGENTS.md generation." >&2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# ─── 13. Initial commit ──────────────────────────────────────────────────────
|
|
77
88
|
echo "→ Staging and committing scaffolded files..."
|
|
78
89
|
git add -A
|
|
79
90
|
git commit -m "init: scaffold project structure and workflow config"
|
|
@@ -509,8 +509,10 @@ linked_by_board = linked_by_commit = 0
|
|
|
509
509
|
|
|
510
510
|
for rec in linkage.get("results", []):
|
|
511
511
|
# Key on the path we ASKED about, not the resolver's `target_relative`. The resolver
|
|
512
|
-
# realpath()s its target, so a tracked symlink (
|
|
513
|
-
#
|
|
512
|
+
# realpath()s its target, so a tracked symlink (e.g. this repo's now-retired
|
|
513
|
+
# `AGENTS.md -> AGENT.md`, before E41_S04_T05 made AGENTS.md a real file) reports its
|
|
514
|
+
# destination and would silently overwrite that destination's own record. The general
|
|
515
|
+
# case (any tracked symlink) still applies even though that specific example is gone.
|
|
514
516
|
path = rec.get("argument") or rec.get("target_relative")
|
|
515
517
|
if not path:
|
|
516
518
|
continue
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# {{TARGET_FILENAME}}
|
|
2
|
+
|
|
3
|
+
This file gives AI coding agents context on this project. The block between the Jenga markers is managed automatically by `jenga init` — do not edit it manually.
|
|
4
|
+
|
|
5
|
+
<!-- JENGA:START -->
|
|
6
|
+
## Jenga Agent Framework
|
|
7
|
+
|
|
8
|
+
This project uses **Jenga** — a skill-based AI agent framework. Jenga organises project work into Epics, Stories, and Tasks managed on a scrum board. It routes user messages to specialised **skills** that execute defined workflows.
|
|
9
|
+
|
|
10
|
+
### How Jenga Works
|
|
11
|
+
|
|
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.
|
|
14
|
+
- 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
|
+
|
|
16
|
+
### Skill Routing
|
|
17
|
+
|
|
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.
|
|
27
|
+
|
|
28
|
+
#### Routing decision table
|
|
29
|
+
|
|
30
|
+
| Situation | Action |
|
|
31
|
+
|-----------|--------|
|
|
32
|
+
| Message matches a skill keyword or intent | Invoke `/skill-name` |
|
|
33
|
+
| 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 |
|
|
35
|
+
|
|
36
|
+
### Available Skills
|
|
37
|
+
|
|
38
|
+
{{SKILL_LIST}}
|
|
39
|
+
|
|
40
|
+
### Notes
|
|
41
|
+
|
|
42
|
+
- Always resolve file paths relative to `JENGA_PROJECT_DIR`.
|
|
43
|
+
- When a skill asks you to read a file such as `SKILL.md` or a task file, look for it inside `JENGA_PROJECT_DIR/{{SKILL_DISCOVERY_PATH}}` or `JENGA_PROJECT_DIR/project/board/` respectively.
|
|
44
|
+
- Commit messages and branch names follow the EST naming convention (`E<n>_S<n>_T<n>`).
|
|
45
|
+
- Agent definitions (scrum-master, developer, tester, and any others) live in the `agents/` directory that sits alongside the skills directory shown above, at the same discovery root.
|
|
46
|
+
<!-- JENGA:END -->
|