@jenga-ai/agent 3.2.0 → 3.4.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 +52 -12
- package/agents/developer.md +16 -1
- package/agents/scrum-master.md +1 -0
- package/bin/jenga.js +10 -0
- package/lib/commands/dashboard.js +92 -0
- package/lib/skill-allow-list.json +6 -2
- package/package.json +21 -2
- package/project/app/api/lib/resolve-project-root.js +120 -0
- package/project/app/api/package.json +16 -0
- package/project/app/api/parsers/architecture.js +72 -0
- package/project/app/api/parsers/board.js +141 -0
- package/project/app/api/parsers/documentation.js +125 -0
- package/project/app/api/parsers/git-log.js +52 -0
- package/project/app/api/parsers/ideas.js +62 -0
- package/project/app/api/parsers/knowledge-graph.js +73 -0
- package/project/app/api/parsers/lib/markdown-dir-reader.js +163 -0
- package/project/app/api/parsers/rapports.js +148 -0
- package/project/app/api/parsers/todo.js +179 -0
- package/project/app/api/response.js +47 -0
- package/project/app/api/routes/architecture.js +23 -0
- package/project/app/api/routes/board.js +46 -0
- package/project/app/api/routes/documentation.js +24 -0
- package/project/app/api/routes/health.js +25 -0
- package/project/app/api/routes/history.js +55 -0
- package/project/app/api/routes/rapports.js +24 -0
- package/project/app/api/scripts/capture-snapshot.js +294 -0
- package/project/app/api/server.js +112 -0
- package/project/app/api/types.js +40 -0
- package/project/app/package.json +21 -0
- package/project/app/ui/dist/assets/index-7fj-vllY.js +104 -0
- package/project/app/ui/dist/assets/index-CdK3Qrep.css +1 -0
- package/project/app/ui/dist/index.html +13 -0
- package/project/app/ui/package.json +23 -0
- package/project/app/ui/scripts/build-snapshot-html.cjs +214 -0
- package/project/app/ui/scripts/dashboard-open.cjs +88 -0
- package/project/app/ui/scripts/dashboard-start.cjs +87 -0
- package/scripts/acquire-concurrency-slot.sh +220 -0
- package/scripts/compute-deploy-reconcile.sh +439 -0
- package/scripts/jenga-permission-level-switch.sh +19 -3
- package/scripts/mark-deployed.sh +532 -0
- package/scripts/populate-knowledge-graph.js +429 -0
- package/scripts/release-concurrency-slot.sh +129 -0
- package/scripts/validate-board.sh +60 -2
- package/scripts/verify-consumer-install.sh +470 -0
- package/skills/j-cloud-connect/SKILL.md +95 -0
- package/skills/j-cloud-connect/scripts/configure-backend.sh +267 -0
- package/skills/j-cloud-connect/scripts/install-rclone.sh +153 -0
- package/skills/j-dashboard/SKILL.md +144 -0
- package/skills/j-dashboard/scripts/launch.sh +121 -0
- package/skills/j-dashboard/scripts/resolve-app-dir.sh +164 -0
- package/skills/j-dashboard/scripts/snapshot.sh +267 -0
- package/skills/j-dashboard-share/SKILL.md +96 -0
- package/skills/j-dashboard-share/scripts/upload-snapshot.sh +173 -0
- package/skills/j-playbook/SKILL.md +12 -0
- package/skills/j-playbook-new/SKILL.md +155 -0
- package/skills/j-playbook-new/scripts/playbook-new.sh +332 -0
- package/skills/j-publish/scripts/npm_ci_pipeline.sh +6 -0
- package/skills/jenga/scripts/load-nl-catalog.js +22 -6
- package/skills/jenga/scripts/load-playbooks.sh +123 -24
- package/templates/SCRUM_BOARD_SCHEMA.md +18 -6
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Jenga AI solves each of these with structure: persistent engineering context mai
|
|
|
35
35
|
|
|
36
36
|
- **Three specialised agents** — Scrum Master, Developer, Tester — each with a distinct role and no self-graded work
|
|
37
37
|
- **A persistent, Kanban-style scrum board** — Epics, Stories, and Tasks tracked as Markdown files with structured frontmatter, surviving every session boundary
|
|
38
|
-
- **A coordinated skill pipeline — one agentic workflow, not a command list** — planning skills (`j.pi-plan`, `j.todo`) hand off to execution skills (`j.do`, `j.dooo`), which hand off to review skills (`j.status`, `j.reconcile`), each stage reading and writing the same board state
|
|
38
|
+
- **A coordinated skill pipeline — one agentic workflow, not a command list** — planning skills (`j.pi-plan`, `j.todo`) hand off to execution skills (`j.do`, `j.dooo`), which hand off to review skills (`j.status`, `j.reconcile`), each stage reading and writing the same board state
|
|
39
39
|
- **An event-driven trigger queue** — async handoffs between agents with a full audit trail in `project/logs/events.json`
|
|
40
40
|
- **Isolated git worktrees per task** — the Developer never works directly on your main branch
|
|
41
41
|
- **Works with any AI coding agent or AI-native IDE** — Claude Code, GitHub Copilot, and Codex CLI are all supported today
|
|
@@ -56,7 +56,7 @@ The Tester doesn't read your code and form an opinion about it. It executes the
|
|
|
56
56
|
|
|
57
57
|
`CLAUDE.md` and `AGENTS.md` are generated unconditionally by `j.init` — every agent gets a real, populated root-level context file, not just a pointer. If either file already exists as a genuine pre-existing user file, Jenga leaves it untouched, writes its own copy as `J-CLAUDE.md` / `J-AGENTS.md`, and inserts a short reference line into the original.
|
|
58
58
|
|
|
59
|
-
`.github/copilot-instructions.md` follows a different path, because Copilot needs it before `j.init` may ever run: `npm install` triggers `scripts/postinstall.js`, which writes it unconditionally and non-interactively, so `j.<name>`
|
|
59
|
+
`.github/copilot-instructions.md` follows a different path, because Copilot needs it before `j.init` may ever run: `npm install` triggers `scripts/postinstall.js`, which writes it unconditionally and non-interactively, so `j.<name>` (and `/j-<name>`) routing works from a consumer's very first Copilot command. A later `jenga init` run refines that same file using the project's actual chosen skills path.
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
@@ -171,6 +171,8 @@ Or clone directly:
|
|
|
171
171
|
|
|
172
172
|
Run `j.status` at any time to see where the project stands.
|
|
173
173
|
|
|
174
|
+
📖 **New to Jenga AI?** The [Intro Guide](https://samwelmunga.github.io/jenga-npm/getting-started.html) walks through the philosophy, the three pillars (role separation, board hierarchy, session continuity), and a full first-15-minutes walkthrough for both a new project and an existing codebase — mirrored at [project/.wiki/intro-guide.md](project/.wiki/intro-guide.md).
|
|
175
|
+
|
|
174
176
|
**CLI maintenance commands.** The `jenga` binary installed alongside the package (`jenga --help`)
|
|
175
177
|
also ships a couple of maintenance commands, distinct from the in-agent `j.<name>` skills above:
|
|
176
178
|
|
|
@@ -196,29 +198,67 @@ The framework is platform-agnostic by design — any AI agent that can read Mark
|
|
|
196
198
|
|
|
197
199
|
## Skills (Slash Commands)
|
|
198
200
|
|
|
199
|
-
Skills live in `.agents/skills/<name>/SKILL.md`. Invoke with `j.<name>` in your AI agent or IDE's command interface
|
|
201
|
+
Skills live in `.agents/skills/<name>/SKILL.md`. Invoke with `j.<name>` in your AI agent or IDE's command interface, or with its `/j-<name>` directory form (e.g. `/j-init`) — both resolve to the same skill. A handful of the most foundational commands:
|
|
200
202
|
|
|
201
|
-
>
|
|
202
|
-
> shadow one of Jenga's — e.g. GitHub Copilot's own built-in `/init
|
|
203
|
-
>
|
|
204
|
-
>
|
|
205
|
-
>
|
|
206
|
-
>
|
|
207
|
-
>
|
|
208
|
-
>
|
|
203
|
+
> **Why `/j-<name>`, not a bare `/<name>`?** Some host tools ship their own built-in command that can
|
|
204
|
+
> shadow one of Jenga's — e.g. GitHub Copilot's own built-in `/init`. Jenga sidesteps this
|
|
205
|
+
> structurally: every skill ships under the collision-safe `/j-<name>` directory name, so there's no
|
|
206
|
+
> bare `/<name>` directory for a host tool's own command to collide with in the first place.
|
|
207
|
+
> `/j-<name>` isn't a fallback to reach for when something misbehaves — it's simply how the skill is
|
|
208
|
+
> named. Two permanent exceptions ship bare-only, with no `/j-<name>` form: `/jenga` and
|
|
209
|
+
> `/jenga-permission-level` — deliberately excluded, since neither is the kind of skill a host tool's
|
|
210
|
+
> own built-in command is likely to name-collide with.
|
|
209
211
|
|
|
210
212
|
| Command | Description |
|
|
211
213
|
|---|---|
|
|
212
214
|
| `j.init` | Scaffold project directories, `workflow.json`, `PROJECT_SUMMARY.md`, initial git commit |
|
|
213
|
-
| `j.jenga` | Interactive-by-default board orchestrator — bare shows a picker + confirmation tree, `<ids>` scopes and confirms, `*` runs fully automated with no prompts |
|
|
214
215
|
| `j.todo` | Add missions to `project/todo.md` linked to epics and stories |
|
|
215
216
|
| `j.do` | Execute tasks from the scrum board, drives the Developer agent through the full loop |
|
|
216
217
|
| `j.status` | Print a full scrum board overview — epics, stories, tasks, rapports, queue depth |
|
|
217
218
|
|
|
219
|
+
### `/jenga` — one command, several behaviors
|
|
220
|
+
|
|
221
|
+
`/jenga` doesn't fit the table above, because what it does depends entirely on how it's invoked:
|
|
222
|
+
|
|
223
|
+
| Invocation | Behavior |
|
|
224
|
+
|---|---|
|
|
225
|
+
| `/jenga` (bare) | Renders a picker + confirmation tree before scoping the run |
|
|
226
|
+
| `/jenga <ids>` | Resolves an explicit fuzzy-ID scope and confirms it |
|
|
227
|
+
| `/jenga *` | Fully automated — decomposes, queues, and executes everything eligible, no prompts |
|
|
228
|
+
| `/jenga <free text>` | Natural-language dispatch — matches a single skill directly, or proposes a multi-skill [playbook](#playbooks) when the request spans more than one |
|
|
229
|
+
|
|
230
|
+
It's one of the two permanent bare-only exceptions noted above — there's no `/j-jenga` form.
|
|
231
|
+
|
|
218
232
|
> 📖 **Full skill list** (planning, review, committing & maintenance commands): [Docs site](https://samwelmunga.github.io/jenga-npm/skills.html) — mirrored at [project/.wiki/documentation.md#skills](project/.wiki/documentation.md#skills)
|
|
219
233
|
|
|
220
234
|
---
|
|
221
235
|
|
|
236
|
+
## Playbooks
|
|
237
|
+
|
|
238
|
+
Some workflows are always the same sequence of skills — plan it, build it, commit it. A **playbook** is a named, pre-defined chain of skills, run and confirmed as one unit instead of typed out one skill at a time.
|
|
239
|
+
|
|
240
|
+
Invoke one by describing what you want in plain language to `j.jenga` — it proposes a matching playbook as a numbered, editable list before anything runs — or name one directly:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
j.playbook idea-to-committed
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
which resolves to:
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
j.brainstorm → j.todo → j.do → j.commit
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Calling `j.playbook` with no id prints a table of every available playbook (id, name, and steps) instead of resolving one.
|
|
253
|
+
|
|
254
|
+
Nothing executes until you confirm the chain, and any step can be unchecked first. `brainstorm-to-mirror` extends the same chain through `j.dev-done` and `j.mirror-public` for a full public release; `understand-then-ship` prepends `j.uncharted` investigation for unfamiliar code before running the same pipeline.
|
|
255
|
+
|
|
256
|
+
When a step forwards its result into the next one, that value has a declared **output type** (a plain string, a list of board IDs, a list of files) so the chain can be validated before it runs. See [Getting Started](https://samwelmunga.github.io/jenga-npm/getting-started.html#how-playbooks-know-what-a-skill-produces) for how that works.
|
|
257
|
+
|
|
258
|
+
Want your own recurring chain? `j.playbook-new` walks you through authoring one — id, name, description, keywords, examples, and an ordered list of skills — writes it to `project/.playbooks/<id>.json` alongside the built-in ones, and self-validates the result before reporting success.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
222
262
|
## When to Use Jenga AI
|
|
223
263
|
|
|
224
264
|
**Use it when:**
|
package/agents/developer.md
CHANGED
|
@@ -225,6 +225,21 @@ This list is fixed and verbatim across both this file and `agents/tester.md` —
|
|
|
225
225
|
|
|
226
226
|
You do not run tests. Before calling the tester agent, **write an execution summary** to `project/documentation/summaries/<E##_S##_T##>-summary.md` using `$([ -f templates/EXECUTION_SUMMARY_TEMPLATE.md ] && echo templates/EXECUTION_SUMMARY_TEMPLATE.md || echo node_modules/@jenga-ai/agent/templates/EXECUTION_SUMMARY_TEMPLATE.md)`. Fill in all sections — what was implemented, files changed, commit SHAs, acceptance criteria coverage, and any concerns for the tester. This step is mandatory before every tester invocation.
|
|
227
227
|
|
|
228
|
+
### Tester Concurrency Cap (acquire before invoking, release on every exit)
|
|
229
|
+
|
|
230
|
+
Before any in-session tester invocation described below, acquire a tester slot:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
scripts/acquire-concurrency-slot.sh tester <task_id> <orchestrator_session_id>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`<orchestrator_session_id>` is the same session-id concept already used elsewhere in this file to name `project/queue/concurrency-slots-<session_id>.json` and `project/queue/handoffs/developer-<session_id>-<task_id>.json` — thread through that same value rather than inventing a new one (when this developer session is itself the orchestrating session, that is simply the current session's `session_id`).
|
|
237
|
+
|
|
238
|
+
- **On success (exit 0):** proceed with the tester invocation exactly as documented below, in-session. When the tester's session ends — regardless of outcome (`Passed`, `Failed`, `Rejected`, or `"error"`) — call `scripts/release-concurrency-slot.sh tester <task_id> <orchestrator_session_id>`. Every exit path out of the tester invocation releases the slot; a failed or errored tester run is not an exception to this.
|
|
239
|
+
- **On a full cap (non-zero exit):** do not poll or wait for a slot to free up — the "Prohibited — ad-hoc completion-polling loops" rule (Session Start — Queue Processing, above) applies here without exception; a retry loop waiting on the counter file would be exactly the kind of ad-hoc polling that rule forbids. Instead, skip the in-session tester invocation entirely and fall back to the existing mandatory mechanism in "Session End — Handoff" above: write `project/queue/handoffs/developer-<session_id>-<task_id>.json` in its documented shape (`status: "implementation_complete"`), unchanged from what's already specified there. A later tester session picks up the work via `on_session_end.sh`'s normal routing to the tester queue. A routine cap-full condition is expected flow control, not a blocking issue — do not write a problem rapport for it.
|
|
240
|
+
|
|
241
|
+
This gate governs only the in-session tester call described in this section; it does not change worktree creation, commit discipline, or the handoff file's shape.
|
|
242
|
+
|
|
228
243
|
When you reach a meaningful milestone within a task where verification is appropriate — or when the task is complete — call the tester agent. Before invoking the tester, compose a short `resolved_context` digest of what you already resolved during implementation — which files you touched and why, which acceptance criteria map to which changes, any conventions or precedent you followed — and persist it by calling `bash "$([ -f scripts/write-context-digest.sh ] && echo scripts/write-context-digest.sh || echo node_modules/@jenga-ai/agent/scripts/write-context-digest.sh)" --agent developer --session-id <session_id> --task-id <task_id>` with that content (stays under the ~100-line/few-hundred-token cap defined in `$([ -f templates/SCRUM_BOARD_SCHEMA.md ] && echo templates/SCRUM_BOARD_SCHEMA.md || echo node_modules/@jenga-ai/agent/templates/SCRUM_BOARD_SCHEMA.md)`'s `resolved_context` subsection; the script rejects oversized input rather than truncating it). Place the script's returned path in the sender object's `resolved_context` field. Always pass the following sender object when invoking the tester:
|
|
229
244
|
|
|
230
245
|
```json
|
|
@@ -245,7 +260,7 @@ When you reach a meaningful milestone within a task where verification is approp
|
|
|
245
260
|
|
|
246
261
|
All fields must be present except `resolved_context`, which is optional. This digest is a starting point only, never a restriction: the tester may and should still read the full execution summary, the diff itself, or any other source file when the digest doesn't cover what it needs. In addition to the sender object, include a short plain-text implementation summary: what was implemented, which files changed, and any known edge cases or concerns. Reference the execution summary at `project/documentation/summaries/<E##_S##_T##>-summary.md` for full detail.
|
|
247
262
|
|
|
248
|
-
Wait for the tester's response before continuing. If the tester returns `"failed"` or `"error"`, address the findings before proceeding.
|
|
263
|
+
Wait for the tester's response before continuing. If the tester returns `"failed"` or `"error"`, address the findings before proceeding. Either way — pass, fail, or error — release the tester slot now per "Tester Concurrency Cap" above; the slot must not remain held once the tester's response has been received.
|
|
249
264
|
|
|
250
265
|
---
|
|
251
266
|
|
package/agents/scrum-master.md
CHANGED
|
@@ -77,6 +77,7 @@ This is a self-contained procedure, not a session-start-only step. It may be inv
|
|
|
77
77
|
- `status_review`: Review the scrum board for any tasks or stories whose status should be updated based on recent activity.
|
|
78
78
|
- `story_rollup`: Check all tasks under the referenced story; if all are `Passed` or `Passed with remarks`, update the story status to `Passed` (or `Passed with remarks` if any remark exists). Then check epic rollup (see Rollup Logic).
|
|
79
79
|
- `elicitation_resume`: A `j.uncharted` conversational architecture elicitation session (`onboard`'s default flow, or `segment --mode investigate` — E20_S08_T03) ended mid-run without converging. Read `state_file` (`project/queue/elicitation-state/<elicitation_id>.json`, written by `skills/uncharted/scripts/elicitation-state.sh`) to see exactly where it left off — which nodes already converged, which are still pending or flagged, and any directory-triage/checkpoint data already confirmed — then resume the conversational flow documented in `skills/uncharted/SKILL.md`'s Multi-Session Persistence subsection from that point rather than restarting the elicitation from scratch. If the state file is missing or unreadable, report that to the user rather than silently starting a fresh elicitation under the same id.
|
|
80
|
+
- `capacity_starvation`: `skills/do/SKILL.md`'s per-session concurrency cap (E32_S15) blocked the same board item for 3 consecutive dispatch waves because its role (`developer` or `tester`) stayed at cap. Surface a plain warning naming the affected item and its consecutive-block wave count, suggesting the configured cap (`max_concurrent_developers` / `max_concurrent_testers` in `project/configs/scope-thresholds.json`) may be too low or the session may be starved. No automatic remediation — this trigger is informational only.
|
|
80
81
|
- After processing all triggers, **clear the file** by writing an empty file — do not leave processed triggers.
|
|
81
82
|
|
|
82
83
|
2. **Check `project/queue/project_summary_updates.jsonl`** — If non-empty, review each proposed update and apply, revise, or reject it with a short note. Clear the file after processing.
|
package/bin/jenga.js
CHANGED
|
@@ -21,6 +21,11 @@ Usage:
|
|
|
21
21
|
jenga status Show router status and active session
|
|
22
22
|
jenga doctor Scan .agents/ and .claude/ for orphaned package files and clean up
|
|
23
23
|
interactively (alias: jenga clean). Add --dry-run to preview only.
|
|
24
|
+
jenga dashboard start [--port <n>] [--serve-app]
|
|
25
|
+
Start the project dashboard API server (and optionally serve the
|
|
26
|
+
built UI). Port defaults to 3001, or JENGA_API_PORT if set.
|
|
27
|
+
jenga dashboard open [--port <n>]
|
|
28
|
+
Health-check the dashboard and open it in your browser.
|
|
24
29
|
|
|
25
30
|
Options:
|
|
26
31
|
--version, -v Print version
|
|
@@ -67,6 +72,11 @@ async function main() {
|
|
|
67
72
|
await runDoctor(args);
|
|
68
73
|
break;
|
|
69
74
|
}
|
|
75
|
+
case "dashboard": {
|
|
76
|
+
const { runDashboard } = await import("../lib/commands/dashboard.js");
|
|
77
|
+
await runDashboard(args);
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
70
80
|
default:
|
|
71
81
|
console.error(`Unknown command: ${cmd}\n`);
|
|
72
82
|
console.log(USAGE);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/commands/dashboard.js — `jenga dashboard start` / `jenga dashboard open` (E47_S03_T01)
|
|
3
|
+
*
|
|
4
|
+
* Reuse decision (documented per this task's `needs_docs: true`)
|
|
5
|
+
* ────────────────────────────────────────────────────────────────
|
|
6
|
+
* `project/app/ui/scripts/dashboard-start.cjs` and `dashboard-open.cjs` are standalone CommonJS
|
|
7
|
+
* scripts that parse `process.argv.slice(2)` directly at the top level and act on it immediately
|
|
8
|
+
* (start listening / run a health check then `process.exit`). They are not written as importable,
|
|
9
|
+
* args-parameterized functions.
|
|
10
|
+
*
|
|
11
|
+
* Two reuse strategies were considered:
|
|
12
|
+
* 1. Refactor their bodies into an exported function both the `.cjs` entry points and this
|
|
13
|
+
* module call.
|
|
14
|
+
* 2. Spawn the existing scripts as a child process, forwarding args and exit code.
|
|
15
|
+
*
|
|
16
|
+
* Chosen: (2), spawning as a child process. Refactoring (1) would require restructuring two
|
|
17
|
+
* scripts that are independently relied on elsewhere (root `package.json`'s `dashboard:start`/
|
|
18
|
+
* `dashboard:open` npm scripts, `skills/j-dashboard`'s launcher, and
|
|
19
|
+
* `scripts/verify-consumer-install.sh`'s Scenario E regression check) for a task whose job is CLI
|
|
20
|
+
* wiring, not a dashboard-scripts refactor. Spawning with `stdio: 'inherit'` reuses the scripts
|
|
21
|
+
* completely verbatim — zero duplicated port-parsing / `--serve-app` / health-check logic — and
|
|
22
|
+
* guarantees byte-identical output and exit-code behavior to running the `.cjs` scripts directly
|
|
23
|
+
* (this is what AC1/AC2's "same behavior" requirement asks for literally). It also avoids the
|
|
24
|
+
* process-global side effects a same-process dynamic `import()` would risk: both scripts call
|
|
25
|
+
* `process.exit()` directly at top level, which would kill the parent `bin/jenga.js` process
|
|
26
|
+
* immediately and unrecoverably if loaded in-process.
|
|
27
|
+
*/
|
|
28
|
+
import { spawn } from "child_process";
|
|
29
|
+
import { existsSync } from "fs";
|
|
30
|
+
import { join, dirname } from "path";
|
|
31
|
+
import { fileURLToPath } from "url";
|
|
32
|
+
|
|
33
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const projectRoot = join(__dirname, "..", "..");
|
|
35
|
+
|
|
36
|
+
const SUBCOMMANDS = {
|
|
37
|
+
start: "dashboard-start.cjs",
|
|
38
|
+
open: "dashboard-open.cjs",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DASHBOARD_USAGE = `
|
|
42
|
+
Usage:
|
|
43
|
+
jenga dashboard start [--port <n>] [--serve-app] Start the dashboard API server
|
|
44
|
+
(and optionally serve the built UI)
|
|
45
|
+
jenga dashboard open [--port <n>] Health-check the dashboard and open it
|
|
46
|
+
in your browser
|
|
47
|
+
`.trim();
|
|
48
|
+
|
|
49
|
+
export async function runDashboard(args, root = projectRoot) {
|
|
50
|
+
const [sub, ...rest] = args;
|
|
51
|
+
|
|
52
|
+
if (!sub || !(sub in SUBCOMMANDS)) {
|
|
53
|
+
if (sub) {
|
|
54
|
+
console.error(`Unknown dashboard subcommand: ${sub}\n`);
|
|
55
|
+
} else {
|
|
56
|
+
console.error("Missing dashboard subcommand.\n");
|
|
57
|
+
}
|
|
58
|
+
console.log(DASHBOARD_USAGE);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const scriptPath = join(root, "project", "app", "ui", "scripts", SUBCOMMANDS[sub]);
|
|
64
|
+
if (!existsSync(scriptPath)) {
|
|
65
|
+
console.error(`Error: dashboard script not found at ${scriptPath}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
const child = spawn(process.execPath, [scriptPath, ...rest], {
|
|
72
|
+
stdio: "inherit",
|
|
73
|
+
env: process.env,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on("exit", (code, signal) => {
|
|
77
|
+
if (signal) {
|
|
78
|
+
// Re-raise the same signal on ourselves so a Ctrl-C style termination propagates
|
|
79
|
+
// cleanly to any caller inspecting our own exit status, rather than reporting a
|
|
80
|
+
// fabricated exit code for a signal-based termination.
|
|
81
|
+
process.kill(process.pid, signal);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
process.exit(code === null ? 1 : code);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
child.on("error", (err) => {
|
|
88
|
+
console.error(`Error: failed to launch dashboard ${sub}: ${err.message}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generated_at": "2026-09-
|
|
3
|
-
"skill_count":
|
|
2
|
+
"generated_at": "2026-09-14T18:42:41.263Z",
|
|
3
|
+
"skill_count": 41,
|
|
4
4
|
"skills": [
|
|
5
5
|
"brainstorm",
|
|
6
6
|
"btw",
|
|
7
7
|
"clearify",
|
|
8
8
|
"close-story",
|
|
9
|
+
"cloud-connect",
|
|
9
10
|
"commit",
|
|
10
11
|
"continue",
|
|
12
|
+
"dashboard",
|
|
13
|
+
"dashboard-share",
|
|
11
14
|
"deep-dive",
|
|
12
15
|
"dev-done",
|
|
13
16
|
"distribute",
|
|
@@ -28,6 +31,7 @@
|
|
|
28
31
|
"lgtm",
|
|
29
32
|
"pi-plan",
|
|
30
33
|
"playbook",
|
|
34
|
+
"playbook-new",
|
|
31
35
|
"proceed",
|
|
32
36
|
"publish",
|
|
33
37
|
"reconcile",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jenga-ai/agent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"description": "An agentic development workflow for Claude Code, Copilot, and Codex — with a persistent Epic/Story/Task board, an isolated git worktree per task, and a separate tester agent that runs your test suite before anything is marked done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"postinstall": "node scripts/postinstall.js",
|
|
17
|
+
"prepack": "npm run ui:build --prefix project/app --",
|
|
17
18
|
"generate:legacy-paths": "node scripts/generate-legacy-shipped-paths.js",
|
|
19
|
+
"graph:populate": "node scripts/populate-knowledge-graph.js",
|
|
18
20
|
"test": "bats tests/*.bats",
|
|
19
21
|
"validate:npm-metadata": "bash scripts/validate_npm_metadata.sh",
|
|
20
22
|
"ui:dev": "npm run ui:dev --prefix project/app --",
|
|
@@ -44,7 +46,19 @@
|
|
|
44
46
|
"mcp/training_runner/package.json",
|
|
45
47
|
"mcp/training_runner/package-lock.json",
|
|
46
48
|
"README.md",
|
|
47
|
-
"LICENSE"
|
|
49
|
+
"LICENSE",
|
|
50
|
+
"project/app/api/**/*.js",
|
|
51
|
+
"project/app/api/routes/**",
|
|
52
|
+
"project/app/api/lib/**",
|
|
53
|
+
"project/app/api/parsers/**",
|
|
54
|
+
"project/app/api/package.json",
|
|
55
|
+
"project/app/ui/scripts/**",
|
|
56
|
+
"project/app/ui/dist/**",
|
|
57
|
+
"project/app/package.json",
|
|
58
|
+
"project/app/ui/package.json",
|
|
59
|
+
"!project/app/api/**/*.test.js",
|
|
60
|
+
"!project/app/api/node_modules/**",
|
|
61
|
+
"!project/app/ui/node_modules/**"
|
|
48
62
|
],
|
|
49
63
|
"repository": {
|
|
50
64
|
"type": "git",
|
|
@@ -82,6 +96,11 @@
|
|
|
82
96
|
"url": "https://knappkod.se/jenga-ai"
|
|
83
97
|
},
|
|
84
98
|
"license": "MIT",
|
|
99
|
+
"dependencies": {
|
|
100
|
+
"cors": "^2.8.5",
|
|
101
|
+
"express": "^4.18.2",
|
|
102
|
+
"gray-matter": "^4.0.3"
|
|
103
|
+
},
|
|
85
104
|
"devDependencies": {
|
|
86
105
|
"bats": "^1.13.0"
|
|
87
106
|
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file project/app/api/lib/resolve-project-root.js
|
|
3
|
+
*
|
|
4
|
+
* Resolves the invoking (consumer) project's root directory for the dashboard API, so that
|
|
5
|
+
* `project/app/api/parsers/*.js` never has to compute its data root via a fixed
|
|
6
|
+
* `path.resolve(__dirname, '../../../...')` climb — the same defect pattern `E46_S01` already fixed
|
|
7
|
+
* for `/init` (see `skills/init/scripts/init.sh`'s `PKG_ROOT` idiom and
|
|
8
|
+
* `lib/generate-agent-context.js`'s `realpathSync` symlink-safe comparison). A `__dirname` climb only
|
|
9
|
+
* ever resolves correctly when this module runs from this monorepo's own checkout; once mirrored into
|
|
10
|
+
* a real consumer's `node_modules/@jenga-ai/agent/`, climbing a fixed number of levels lands inside or
|
|
11
|
+
* above `node_modules`, never at the consuming project's own root.
|
|
12
|
+
*
|
|
13
|
+
* Resolution order:
|
|
14
|
+
* 1. Explicit override — the `JENGA_PROJECT_ROOT` env var. This is the primary mechanism for the
|
|
15
|
+
* real npm-consumer case: the dashboard's own launch path (`dashboard-start.cjs` /
|
|
16
|
+
* `dashboard-open.cjs` / `server.js`) already knows the invoking `cwd` and can set this before
|
|
17
|
+
* the parsers ever load.
|
|
18
|
+
* 2. Walk up from `cwd` (default `process.cwd()`) looking for a `project/board` directory — the
|
|
19
|
+
* marker every Jenga-initialized project has (see `/init`'s scaffold) — bounded to a generous
|
|
20
|
+
* but finite number of parent levels.
|
|
21
|
+
* 3. Fail loudly. Never silently fall back to `__dirname`, `cwd` itself, or `null` — an unresolved
|
|
22
|
+
* project root is always a thrown `Error` with a descriptive message naming both the override
|
|
23
|
+
* var and the path that was walked.
|
|
24
|
+
*
|
|
25
|
+
* Both the override path and every step of the walk-up are resolved through `fs.realpathSync`, so a
|
|
26
|
+
* symlinked invocation path (macOS `/tmp`/`$TMPDIR`, `npm link`, a symlinked home directory — the same
|
|
27
|
+
* bug class `E46_S01` fixed) does not break resolution.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
|
|
35
|
+
const OVERRIDE_ENV_VAR = 'JENGA_PROJECT_ROOT';
|
|
36
|
+
const PROJECT_MARKER = path.join('project', 'board');
|
|
37
|
+
const MAX_WALK_LEVELS = 20;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} dir absolute, already-realpath'd directory
|
|
41
|
+
* @returns {boolean} true if `dir/project/board` exists and is a directory
|
|
42
|
+
*/
|
|
43
|
+
function hasProjectMarker(dir) {
|
|
44
|
+
try {
|
|
45
|
+
return fs.statSync(path.join(dir, PROJECT_MARKER)).isDirectory();
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve an absolute, symlink-free directory path, throwing a descriptive error (not a raw ENOENT)
|
|
53
|
+
* if it doesn't exist.
|
|
54
|
+
* @param {string} label human-readable label used in the error message
|
|
55
|
+
* @param {string} rawPath the path as provided (env var value or cwd)
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function realpathOrThrow(label, rawPath) {
|
|
59
|
+
const resolved = path.resolve(rawPath);
|
|
60
|
+
try {
|
|
61
|
+
return fs.realpathSync(resolved);
|
|
62
|
+
} catch (err) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`[resolve-project-root] ${label} "${rawPath}" (resolved to "${resolved}") does not exist: ${err.message}`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Determine the invoking consumer project's root directory.
|
|
71
|
+
*
|
|
72
|
+
* @param {Object} [options]
|
|
73
|
+
* @param {string} [options.cwd] working directory to resolve/walk from (defaults to `process.cwd()`)
|
|
74
|
+
* @param {NodeJS.ProcessEnv} [options.env] environment to read the override from (defaults to `process.env`)
|
|
75
|
+
* @returns {string} absolute, symlink-resolved path to the project root
|
|
76
|
+
* @throws {Error} if no project root can be determined (fail loudly — never returns a wrong/empty path)
|
|
77
|
+
*/
|
|
78
|
+
function resolveProjectRoot({ cwd = process.cwd(), env = process.env } = {}) {
|
|
79
|
+
// 1. Explicit override.
|
|
80
|
+
const override = env && env[OVERRIDE_ENV_VAR];
|
|
81
|
+
if (override) {
|
|
82
|
+
const resolvedOverride = realpathOrThrow(`${OVERRIDE_ENV_VAR}`, override);
|
|
83
|
+
if (!fs.statSync(resolvedOverride).isDirectory()) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`[resolve-project-root] ${OVERRIDE_ENV_VAR} "${override}" (resolved to "${resolvedOverride}") is not a directory.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return resolvedOverride;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. Walk up from cwd looking for a project/board marker. realpath the starting point once so
|
|
92
|
+
// every subsequent path.dirname() step stays symlink-free.
|
|
93
|
+
let startDir;
|
|
94
|
+
try {
|
|
95
|
+
startDir = fs.realpathSync(path.resolve(cwd));
|
|
96
|
+
} catch (err) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`[resolve-project-root] cwd "${cwd}" does not exist: ${err.message}. ` +
|
|
99
|
+
`Set ${OVERRIDE_ENV_VAR} to your project's root directory instead.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let dir = startDir;
|
|
104
|
+
for (let i = 0; i < MAX_WALK_LEVELS; i++) {
|
|
105
|
+
if (hasProjectMarker(dir)) return dir;
|
|
106
|
+
const parent = path.dirname(dir);
|
|
107
|
+
if (parent === dir) break; // reached filesystem root
|
|
108
|
+
dir = parent;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 3. Fail loudly.
|
|
112
|
+
throw new Error(
|
|
113
|
+
`[resolve-project-root] Could not locate a project root (looked for a "${PROJECT_MARKER}" ` +
|
|
114
|
+
`directory) walking up from "${startDir}" (${MAX_WALK_LEVELS} levels). ` +
|
|
115
|
+
`Set the ${OVERRIDE_ENV_VAR} environment variable to your project's root directory, or run ` +
|
|
116
|
+
`the dashboard from inside a Jenga-initialized project.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = { resolveProjectRoot, OVERRIDE_ENV_VAR, PROJECT_MARKER, MAX_WALK_LEVELS };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jenga-dashboard-api",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Jenga AI dashboard API server",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node server.js",
|
|
8
|
+
"dev": "node --watch server.js",
|
|
9
|
+
"test": "node parsers/knowledge-graph.test.js && node lib/resolve-project-root.test.js && node parsers/lib/markdown-dir-reader.test.js && node parsers/todo.test.js && node parsers/documentation.test.js && node parsers/rapports.test.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"cors": "^2.8.5",
|
|
13
|
+
"express": "^4.18.2",
|
|
14
|
+
"gray-matter": "^4.0.3"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file project/app/api/parsers/architecture.js
|
|
3
|
+
* Reads package.json and project.config.json to return tech stack + dependency info.
|
|
4
|
+
* The SAD map itself is sourced from project/knowledge-graph/graph.json via ./knowledge-graph.js
|
|
5
|
+
* (E08_S05_T01) rather than parsed live from board epics/stories.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { readSADMap } = require('./knowledge-graph');
|
|
11
|
+
const { resolveProjectRoot } = require('../lib/resolve-project-root');
|
|
12
|
+
|
|
13
|
+
// Resolved relative to the invoking project's own root (E47_S02_T01/T02), not a fixed __dirname
|
|
14
|
+
// climb — see project/app/api/lib/resolve-project-root.js.
|
|
15
|
+
const ROOT = resolveProjectRoot();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Safely read and parse a JSON file.
|
|
19
|
+
* @param {string} filePath
|
|
20
|
+
* @returns {Object|null}
|
|
21
|
+
*/
|
|
22
|
+
function readJson(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse project config files into architecture metadata.
|
|
32
|
+
* @returns {Promise<Object>}
|
|
33
|
+
*/
|
|
34
|
+
async function parseArchitecture() {
|
|
35
|
+
const pkg = readJson(path.join(ROOT, 'package.json'));
|
|
36
|
+
const config = readJson(path.join(ROOT, 'project.config.json'));
|
|
37
|
+
|
|
38
|
+
// Build tech stack from project.config.json fields + package metadata
|
|
39
|
+
const tech_stack = [];
|
|
40
|
+
if (config) {
|
|
41
|
+
if (config.workflow) tech_stack.push({ name: config.workflow, description: `Workflow engine (v${(pkg && pkg.version) || 'unknown'})` }); // E26_S01_T03: version from package.json, not deprecated workflow_version
|
|
42
|
+
if (config.description) tech_stack.push({ name: 'Jenga AI', description: config.description });
|
|
43
|
+
}
|
|
44
|
+
if (pkg) {
|
|
45
|
+
tech_stack.push({ name: 'Node.js', description: 'JavaScript runtime' });
|
|
46
|
+
const express = pkg.dependencies && pkg.dependencies['express'];
|
|
47
|
+
if (express) tech_stack.push({ name: 'Express', description: `HTTP server framework (${express})` });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build dependency list
|
|
51
|
+
const dependencies = [];
|
|
52
|
+
if (pkg) {
|
|
53
|
+
for (const [name, version] of Object.entries(pkg.dependencies || {})) {
|
|
54
|
+
dependencies.push({ name, version, type: 'runtime' });
|
|
55
|
+
}
|
|
56
|
+
for (const [name, version] of Object.entries(pkg.devDependencies || {})) {
|
|
57
|
+
dependencies.push({ name, version, type: 'devDependency' });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
tech_stack,
|
|
63
|
+
dependencies,
|
|
64
|
+
sad_map: readSADMap(),
|
|
65
|
+
_sources: {
|
|
66
|
+
package_json: pkg ? { name: pkg.name, version: pkg.version } : null,
|
|
67
|
+
project_config: config || null,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { parseArchitecture };
|